NOW RAG is actually usable... might switch to gemma3:4b from glm-4.6 cloud for expenses reasons but we'll see

This commit is contained in:
Renn F
2026-01-03 05:07:45 +01:00
parent 16dda8134b
commit 1d173a5203
76 changed files with 4196 additions and 10668 deletions
+101
View File
@@ -0,0 +1,101 @@
# Python Coding Standards
## Package Manager
Use `uv` for all Python operations.
```bash
# Add dependency
uv add package-name
# Add dev dependency
uv add --dev package-name
# Sync dependencies
uv sync
# Run command
uv run pytest
```
## Before Every Commit
```bash
uv run ruff format . # Format code
uv run ruff check . # Lint
uv run mypy roboco/ # Type check
uv run pytest # Tests
```
## Type Hints Required
All functions MUST have type hints:
```python
# Good
async def fetch_user(user_id: UUID) -> User | None:
...
# Bad - no type hints
def fetch_user(user_id):
...
```
## Naming Conventions
| Type | Convention | Example |
|------|------------|---------|
| Classes | PascalCase | `TaskService` |
| Functions | snake_case | `get_user` |
| Variables | snake_case | `user_id` |
| Constants | SCREAMING | `MAX_RETRIES` |
| Private | Leading `_` | `_cache` |
## Line Length
Maximum 88 characters (Black default).
## Imports
Sorted order: stdlib, third-party, local.
```python
import asyncio
from pathlib import Path
from fastapi import FastAPI
from pydantic import BaseModel
from roboco.models import Task
from roboco.services import TaskService
```
## Async by Default
ALL I/O operations must be async:
```python
# Good
async def fetch_user(user_id: str) -> User:
return await db.users.get(user_id)
# Bad - blocking
def fetch_user(user_id: str) -> User:
return db.users.get(user_id) # Blocks!
```
## Concurrent Operations
Use `asyncio.gather` for independent async calls:
```python
# Good - parallel
task, comments = await asyncio.gather(
get_task(task_id),
get_comments(task_id),
)
# Bad - sequential
task = await get_task(task_id)
comments = await get_comments(task_id) # Waits unnecessarily
```
+93
View File
@@ -0,0 +1,93 @@
# Python Error Handling
## Never Bare Except
Always catch specific exceptions:
```python
# Good
try:
result = await service.process(data)
except ValidationError as e:
logger.warning("Validation failed", error=str(e))
raise
except ServiceUnavailableError:
await retry_with_backoff(service.process, data)
# Bad - NEVER do this
try:
result = await service.process(data)
except:
pass
```
## Custom Exceptions
Define domain-specific exceptions:
```python
class TaskError(Exception):
"""Base exception for task operations."""
class TaskNotFoundError(TaskError):
"""Task does not exist."""
class TaskAlreadyClaimedError(TaskError):
"""Task is already claimed."""
# Usage
if task is None:
raise TaskNotFoundError(f"Task {task_id} not found")
```
## Preserve Exception Chain
When re-raising:
```python
# Good - preserves chain
try:
result = await external_api.call()
except ExternalAPIError as e:
raise ServiceError("External API failed") from e
# Bad - loses traceback
except ExternalAPIError:
raise ServiceError("External API failed")
```
## Structured Logging
Use structlog, NEVER print:
```python
import structlog
logger = structlog.get_logger(__name__)
# Good
logger.info(
"Task completed",
task_id=task.id,
duration_ms=elapsed,
)
# Bad - NEVER use print
print(f"Task {task.id} completed")
```
## Validation at Boundaries
Validate external input at API boundaries:
```python
# API boundary - validate
@router.post("/tasks")
async def create_task(request: TaskCreate) -> TaskResponse:
# Pydantic validates automatically
...
# Internal service - trust validated data
async def process_task(task: Task) -> None:
# No need to re-validate
...
```
+86
View File
@@ -0,0 +1,86 @@
# Python Security Standards
## No Hardcoded Secrets
NEVER hardcode secrets:
```python
# Bad - NEVER
API_KEY = "sk-abc123xyz789"
DATABASE_URL = "postgresql://user:password@host/db"
# Good - environment variables
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
api_key: str
database_url: str
model_config = {"env_prefix": "ROBOCO_"}
```
## SQL Injection Prevention
NEVER use string concatenation for SQL:
```python
# Bad - SQL injection vulnerability
query = f"SELECT * FROM users WHERE id = '{user_id}'"
# Good - parameterized query
result = await session.execute(
select(User).where(User.id == user_id)
)
```
## Command Injection Prevention
NEVER pass user input directly to shell:
```python
# Bad - command injection
import os
os.system(f"process_file {filename}")
# Good - use subprocess with list
import subprocess
subprocess.run(["process_file", filename], check=True)
```
## No eval() or exec()
NEVER use on untrusted input:
```python
# Bad - code injection
result = eval(user_input)
# Good - safe parsing
import ast
result = ast.literal_eval(user_input) # Only literals
```
## Non-Security Hashes
When hashing for non-security purposes:
```python
import hashlib
content_hash = hashlib.md5(
content.encode(),
usedforsecurity=False # Required flag
).hexdigest()[:12]
```
## Security Tools
Run before merge:
```bash
# Security scan
uv run bandit -r roboco/ -ll
# Dependency audit
uv run pip-audit
uv run safety scan
```
+81
View File
@@ -0,0 +1,81 @@
# Testing Standards
## Coverage Target
Minimum 80% code coverage for all modules.
```bash
# Run with coverage
uv run pytest --cov=roboco --cov-report=term-missing
```
## Async Tests
Use pytest-asyncio:
```python
import pytest
@pytest.mark.asyncio
async def test_fetch_user() -> None:
user = await fetch_user("test-123")
assert user.name == "Test User"
```
## Test Structure
Follow AAA pattern: Arrange, Act, Assert.
```python
@pytest.mark.asyncio
async def test_task_claim_success() -> None:
# Arrange
task = await create_test_task(status=TaskStatus.PENDING)
agent = await create_test_agent()
# Act
claimed_task = await task_service.claim(task.id, agent.id)
# Assert
assert claimed_task.status == TaskStatus.CLAIMED
assert claimed_task.assigned_to == agent.id
```
## Test Factories
Use factory-boy for test data:
```python
from factory import Factory, Faker, LazyAttribute
class TaskFactory(Factory):
class Meta:
model = Task
title = Faker('sentence')
status = TaskStatus.PENDING
created_at = LazyAttribute(lambda _: datetime.now(UTC))
```
## Before Submitting to QA
Run full test suite:
```bash
# Backend
uv run pytest
uv run ruff check .
uv run mypy roboco/
# Frontend
pnpm test
pnpm lint
pnpm typecheck
```
## Quality Gates
All tests MUST pass before:
- Submitting for verification
- Creating pull request
- Merging to main
+119
View File
@@ -0,0 +1,119 @@
# TypeScript Coding Standards
## Package Manager
Use `pnpm` for all TypeScript/JavaScript operations.
```bash
# Install dependencies
pnpm install
# Add dependency
pnpm add package-name
# Add dev dependency
pnpm add -D package-name
```
## Before Every Commit
```bash
pnpm format # Format code
pnpm lint # Lint
pnpm typecheck # Type check
pnpm test # Tests
```
## Type Safety
Enable strict mode in tsconfig:
```json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
}
}
```
## Avoid `any`
Never use `any`. Use `unknown` or generics:
```typescript
// Bad
function process(data: any): any { ... }
// Good
function process<T>(data: T): ProcessedData<T> { ... }
```
## Null Checks
Use optional chaining and nullish coalescing:
```typescript
// Good
const name = user?.profile?.name ?? "Anonymous";
// Bad
const name = user && user.profile && user.profile.name || "Anonymous";
```
## Async/Await
Use async/await over raw Promises:
```typescript
// Good
async function fetchUser(id: string): Promise<User> {
const response = await api.get(`/users/${id}`);
return response.data;
}
// Bad
function fetchUser(id: string): Promise<User> {
return api.get(`/users/${id}`).then(r => r.data);
}
```
## Error Handling
Use typed errors:
```typescript
class ApiError extends Error {
constructor(
message: string,
public statusCode: number
) {
super(message);
}
}
try {
await api.call();
} catch (error) {
if (error instanceof ApiError) {
// Handle API error
}
throw error;
}
```
## Component Props
Define explicit prop types:
```typescript
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}
export function Button({ label, onClick, disabled }: ButtonProps) {
// ...
}
```