mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
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:
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
...
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user