RAG expansion + Optimal API

This commit is contained in:
Renn F
2025-12-27 21:53:38 +01:00
parent 3f48a93504
commit 1116e597d0
58 changed files with 11768 additions and 7840 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-171
View File
@@ -1,171 +0,0 @@
---
# CEO Directive: API-First Architecture Initiative
**From:** CEO
**To:** Board (Product Owner, Head of Marketing, Auditor)
**Priority:** High
**Date:** 2024-12-23
---
## Strategic Context
We're positioning RoboCo for a two-phase growth strategy:
**Phase B (Now):** Two products, one backend. RoboCo Panel (full experience) and Codepanion (lightweight CLI, open source). Both consume the same API.
**Phase C (Future):** Platform play. The orchestration API becomes the product. Third parties build on us.
For this to work, **the API must be the only way in.** No backdoors. No direct DB access from frontends. No "just this once" shortcuts.
If we can't use our own API to build our own products, no one else can either.
---
## Objective
**Establish API-first architecture across the entire system.**
This means:
1. Every action the Panel takes goes through the public API
2. Every action Codepanion will take goes through the public API
3. Internal services communicate through well-defined interfaces
4. The API is documented, consistent, and pleasant to use
---
## Success Criteria
- [ ] Zero direct database access from Panel frontend
- [ ] Zero direct core imports that bypass API in frontend code
- [ ] Codepanion can connect and perform basic operations (create session, send message, retrieve history)
- [ ] API documentation covers all endpoints Codepanion needs
- [ ] Auditor has verified no backdoors remain in critical paths
---
## Board Responsibilities
### Product Owner
1. **Define the API contract** for Codepanion integration
- What endpoints does Codepanion need?
- What's the minimal surface area?
- What can wait for v2?
2. **Prioritize the cleanup work**
- Which backdoors are blockers vs. nice-to-have?
- What's the MVP for "API-first"?
3. **Approve the architecture decisions**
- Review proposals from Main PM
- Sign off on API design
### Head of Marketing
1. **Prepare positioning for Codepanion**
- Open source CLI angle
- "Works standalone, better connected" messaging
- Developer-first tone
2. **Draft initial README/landing content**
- What does Codepanion do?
- Why would a dev use it?
- How does it connect to RoboCo?
3. **Identify launch channels**
- Where do we announce?
- What's the content calendar?
### Auditor
1. **Conduct the backdoor audit**
- Review all frontend → backend communication
- Flag every direct DB access
- Flag every import that bypasses API layer
- Produce audit report with file, line, severity
2. **Verify fixes**
- After cleanup, re-audit critical paths
- Confirm API-first compliance
3. **Establish ongoing monitoring**
- How do we prevent new backdoors?
- What checks should be part of code review?
---
## Deliverables to Main PM
Once board alignment is complete, hand off to Main PM:
1. **Audit Report** (from Auditor)
- List of all backdoors with severity ratings
2. **API Specification** (from Product Owner)
- Endpoints needed for Codepanion MVP
- Request/response schemas
- Authentication approach
3. **Prioritized Task List** (from Product Owner)
- Ordered by: blockers first, then high-value, then nice-to-have
4. **Marketing Brief** (from Head of Marketing)
- Positioning document for Codepanion
- README draft
- Launch plan outline
---
## Constraints
- **Timeline:** Codepanion MVP should be shippable within 2 weeks of Main PM receiving handoff
- **Scope:** Fix what's necessary for Codepanion. Don't boil the ocean.
- **Principle:** If in doubt, expose it through API. We'd rather have a slightly larger API surface than hidden backdoors.
---
## Notes on Documentation
For now:
- Task documentation lives in the task's session (tied via session-task link)
- Technical documentation (API docs, architecture) should be markdown in the repo
- User-facing documentation (README, guides) prepared by Documenter roles, reviewed by PM
Future consideration: dedicated documentation system. But not now.
---
## Notes on Git Integration
Git integration is coming. For this initiative:
- All code changes go through PRs
- PRs link to task IDs in commit messages
- Once git integration lands, this becomes automated
For now, manual discipline.
---
## Communication
- Board discussions in `management` channel
- Main PM coordination in `cross-cell` channel
- Cell work in respective cell channels (`backend-cell`, `frontend-cell`, `uxui-cell`)
- Each task gets its own session within the appropriate channel
- Journals capture decisions, learnings, blockers
---
## Final Word
This is the foundation. If we get API-first right, everything else becomes easier — Codepanion, platform expansion, third-party integrations, even our own Panel development.
If we get it wrong, we're building on sand.
Make it solid.
---
**CEO**
-436
View File
@@ -1,436 +0,0 @@
# RoboCo Workflows & Permissions
Visual documentation of task lifecycles, permissions, and workflows.
## 0. Terminology Dictionary
**CRITICAL: Understand these concepts before reading further.**
### Communication Hierarchy
```
Channel → Group → Session → Messages
┌─────────────────────────────────────────────────────────────────────────┐
│ CHANNEL (e.g., "backend-cell") │
│ A named communication space for a team/topic │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ GROUP (e.g., "Sprint 5 Backend Work") │ │
│ │ A collection within a channel (project, sprint, topic) │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ SESSION (e.g., "TASK-123 Discussion") │ │ │
│ │ │ A conversation thread, usually tied to a task │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────────────────────────────────────────┐ │ │ │
│ │ │ │ MESSAGES │ │ │ │
│ │ │ │ Individual messages within a session │ │ │ │
│ │ │ │ Types: action, dialogue, blocker, question │ │ │ │
│ │ │ └─────────────────────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Tools:
- roboco_channel_list() → List channels you can access
- roboco_channel_history() → Get messages from a channel's sessions
- roboco_session_history_for_task() → Get messages for a specific task's session
- roboco_message_send() → Send to channel (routes via task_id to session)
```
### Journal vs Documentation
```
┌─────────────────────────────────────┬─────────────────────────────────────┐
│ JOURNAL │ DOCUMENTATION │
├─────────────────────────────────────┼─────────────────────────────────────┤
│ WHAT: Agent's personal reflections │ WHAT: Project/codebase docs │
│ │ │
│ WHERE: Database (journal entries) │ WHERE: /app/docs/ files │
│ │ │
│ WHO WRITES: Each agent, for self │ WHO WRITES: Documenter agents │
│ │ │
│ PURPOSE: │ PURPOSE: │
│ - Track decisions & rationale │ - API documentation │
│ - Record learnings & struggles │ - README updates │
│ - Reflect on task completion │ - Changelog entries │
│ - Build context for future sessions │ - Component/feature docs │
│ │ │
│ AUDIENCE: Self + cell members │ AUDIENCE: All developers, users │
│ │ │
│ TOOLS: │ TOOLS: │
│ - roboco_journal_entry() │ - Write tool to /app/docs/ │
│ - roboco_journal_reflect() │ - roboco_task_docs_complete() │
│ - roboco_journal_decision() │ │
│ - roboco_journal_learning() │ │
│ - roboco_journal_read_team() │ │
└─────────────────────────────────────┴─────────────────────────────────────┘
KEY DISTINCTION:
- Journal = "What I learned/decided while doing this task" (internal notes)
- Documentation = "How this feature works for others" (external docs)
Documenter reads journals to UNDERSTAND what was built, then writes documentation.
```
### Task Notes vs Journals
```
┌─────────────────────────────────────┬─────────────────────────────────────┐
│ TASK NOTES │ JOURNALS │
│ (dev_notes, qa_notes) │ │
├─────────────────────────────────────┼─────────────────────────────────────┤
│ Attached to the task record │ Separate entries linked by task_id │
│ │ │
│ Brief handoff summaries │ Detailed journey records │
│ │ │
│ Set via: │ Set via: │
│ - roboco_task_submit_qa(notes) │ - roboco_journal_*() tools │
│ - roboco_task_qa_pass(notes) │ │
│ - roboco_task_docs_complete(notes) │ │
│ │ │
│ Examples: │ Examples: │
│ "Implemented X, tested Y" │ "Decided to use pattern X because Y"│
│ "QA passed, verified all criteria" │ "Struggled with Z, solved via W" │
└─────────────────────────────────────┴─────────────────────────────────────┘
```
## 1. Task Lifecycle State Machine
```mermaid
stateDiagram-v2
[*] --> pending: Task Created
pending --> claimed: Developer claims
pending --> cancelled: PM cancels
claimed --> in_progress: Developer starts
claimed --> pending: Developer unclaims
claimed --> cancelled: PM cancels
in_progress --> blocked: Developer blocked
in_progress --> paused: Developer pauses
in_progress --> verifying: Developer self-verifies
in_progress --> cancelled: PM cancels
blocked --> in_progress: Unblocked
blocked --> cancelled: PM cancels
paused --> in_progress: Developer resumes
paused --> cancelled: PM cancels
verifying --> awaiting_qa: Submit for QA
verifying --> needs_revision: Self-found issues
verifying --> awaiting_documentation: Skip QA (small tasks)
verifying --> cancelled: PM cancels
awaiting_qa --> awaiting_documentation: QA PASS
awaiting_qa --> needs_revision: QA FAIL
awaiting_qa --> blocked: Blocked during QA
awaiting_qa --> cancelled: PM cancels
needs_revision --> in_progress: Developer resumes
needs_revision --> cancelled: PM cancels
awaiting_documentation --> awaiting_pm_review: Documenter marks docs done
awaiting_documentation --> cancelled: PM cancels
awaiting_pm_review --> completed: PM completes
awaiting_pm_review --> cancelled: PM cancels
completed --> [*]
cancelled --> [*]
quarantined --> pending: Un-quarantined
```
## 2. Agent Hierarchy & Roles
```
+-------+
| CEO |
+-------+
|
+---------------+---------------+
| | |
+---------+ +-----------+ +---------+
| Product | | Head | | Auditor |
| Owner | | Marketing | | (silent)|
+---------+ +-----------+ +---------+
| | |
+-------+-------+ |
| |
+---------+ |
| Main PM |<-----------------+
+---------+ (observes all)
|
+-----------+-----------+
| | |
+-------+ +-------+ +-------+
| BE PM | | FE PM | | UX PM |
+-------+ +-------+ +-------+
| | |
+-------+ +-------+ +-------+
|Backend| |Frontend| | UX/UI |
| Cell | | Cell | | Cell |
+-------+ +-------+ +-------+
Each Cell:
- 2 Developers (BE/FE) or 1 Developer (UX)
- 1 QA Engineer
- 1 Documenter
- 1 Cell PM
```
## 3. Notification Permissions
```
WHO CAN SEND NOTIFICATIONS:
+------------------+-------------+----------------------------------------------+
| Sender Role | Can Send? | Scope |
+------------------+-------------+----------------------------------------------+
| CEO | YES | Anyone |
| Auditor | YES | Anyone |
| Main PM | YES | Anyone |
| Product Owner | YES | main-pm, head-marketing, auditor, ceo |
| Head Marketing | YES | main-pm, product-owner, auditor, ceo |
| Cell PM | YES | Own cell only |
+------------------+-------------+----------------------------------------------+
| Developer | NO | - |
| QA | NO | - |
| Documenter | NO | - |
+------------------+-------------+----------------------------------------------+
TOOLS VISIBILITY:
+----------------------+------------+----------+---------+---------+---------+
| Tool | Dev/QA/Doc | Cell PM | Main PM | Board | Aud/CEO |
+----------------------+------------+----------+---------+---------+---------+
| roboco_notify_list | YES | YES | YES | YES | YES |
| roboco_notify_get | YES | YES | YES | YES | YES |
| roboco_notify_ack | YES | YES | YES | YES | YES |
| roboco_notify_send | HIDDEN | YES | YES | YES | YES |
| roboco_escalate | HIDDEN | YES | YES | HIDDEN | HIDDEN |
| roboco_request_appr | HIDDEN | YES | YES | YES | HIDDEN |
+----------------------+------------+----------+---------+---------+---------+
Note: "Board" = Product Owner + Head Marketing. Auditor/CEO can send but not escalate or request approval.
```
## 4. QA Workflow (Full Detail)
### QA Status Acceptance
QA can call `qa_pass` or `qa_fail` from ANY of these statuses:
- `awaiting_qa` (initial)
- `claimed` (after QA claims)
- `in_progress` (after QA starts)
This allows QA to follow the full workflow: SCAN → CLAIM → PLAN → START → TEST → VERDICT
### QA Workflow Diagram
```mermaid
sequenceDiagram
participant Dev as Developer
participant Task as Task System
participant QA as QA Engineer
participant Doc as Documenter
participant PM as Cell PM
Dev->>Task: Submit for QA (awaiting_qa)
Note over Task: assigned_to = QA<br/>quick_context = original_developer:Dev
QA->>Task: roboco_task_claim() → status = claimed
QA->>Task: roboco_task_plan() → save test plan
QA->>Task: roboco_task_start() → status = in_progress
QA->>Task: Test work against criteria
alt QA PASS
QA->>Task: roboco_task_qa_pass(qa_notes)
Task->>Task: status = awaiting_documentation
Note over Task: HANDOFF TO DOCUMENTER
Doc->>Task: roboco_task_claim() → status = claimed
Doc->>Task: roboco_task_plan() → save doc plan
Doc->>Task: roboco_task_start() → status = in_progress
Doc->>Task: GATHER info (see section 4a)
Doc->>Task: WRITE docs to /app/docs/
Doc->>Task: roboco_task_docs_complete()
Task->>Task: status = awaiting_pm_review
PM->>Task: roboco_task_complete()
Task->>Task: status = completed
else QA FAIL
QA->>Task: roboco_task_qa_fail(qa_notes, issues)
Task->>Task: status = needs_revision
Task->>Task: assigned_to = original Dev
Dev->>Task: roboco_task_start() → status = in_progress
Dev->>Task: Fix issues, resubmit
end
```
### 4a. Documenter Information Gathering
```
DOCUMENTER MUST GATHER FROM 3 SOURCES:
┌─────────────────────────────────────────────────────────────────────────────┐
│ SOURCE 1: TASK DETAILS (roboco_task_get) │
│ ───────────────────────────────────────── │
│ • description, acceptance_criteria │
│ • dev_notes (developer's handoff summary) │
│ • qa_notes (QA's verification notes) │
│ • quick_context (original developer, etc.) │
├─────────────────────────────────────────────────────────────────────────────┤
│ SOURCE 2: TEAM JOURNALS (roboco_journal_read_team) │
│ ───────────────────────────────────────────────── │
│ Read developer's journey: │
│ roboco_journal_read_team("be-dev-1", task_id=task_id, limit=20) │
│ │
│ What to look for: │
│ • Decisions made and WHY │
│ • Struggles encountered and how solved │
│ • Learnings documented │
│ • Design/architecture choices │
├─────────────────────────────────────────────────────────────────────────────┤
│ SOURCE 3: SESSION MESSAGES (roboco_session_history_for_task) │
│ ──────────────────────────────────────────────────────────── │
│ Read discussion context: │
│ roboco_session_history_for_task(task_id) │
│ │
│ What to look for: │
│ • Questions asked and answers given │
│ • Clarifications from PM/dev │
│ • Blockers discussed and resolutions │
│ • Design decisions made in discussion │
└─────────────────────────────────────────────────────────────────────────────┘
NOTE: Journals are INPUT for understanding. Documentation is OUTPUT to /app/docs/.
```
## 5. Block/Unblock Workflow
```mermaid
sequenceDiagram
participant Dev as Developer
participant Task as Task System
participant PM as Cell PM
Dev->>Task: Working on task (in_progress)
Note over Dev: Encounters blocker
Dev->>Task: roboco_task_block(reason, type, what_needed)
Task->>Task: POST /tasks/{id}/soft-block
Task->>Task: status = blocked
Task->>Task: dev_notes += blocker info
Note over Dev: Can work on other tasks
alt Blocker resolved
Dev->>Task: roboco_task_unblock()
Task->>Task: POST /tasks/{id}/unblock
Task->>Task: status = in_progress
Dev->>Task: Continue working
else Need PM help
Dev->>PM: roboco_report_blocker() via message channel
PM->>Task: Resolves blocker
Dev->>Task: roboco_task_unblock()
end
```
## 6. Task Role Restrictions
```
ROLE-BASED TRANSITIONS:
+-------------------------------+-------------------------------------------+
| Transition | Allowed Roles |
+-------------------------------+-------------------------------------------+
| awaiting_qa → awaiting_doc | QA only |
| awaiting_qa → needs_rev | QA only |
| awaiting_doc → awaiting_pm | Documenter only |
| awaiting_pm → completed | Cell PM, Main PM, Product Owner, Head Mkt |
| * → cancelled | Cell PM, Main PM, Product Owner, Head Mkt |
+-------------------------------+-------------------------------------------+
Note: CEO and Auditor are NOT in the cancel/complete roles list - they observe but don't directly act on tasks.
VALID START STATUSES (for roboco_task_start):
+------------------+------------------------------------------+
| Status | Who Can Start |
+------------------+------------------------------------------+
| claimed | Assigned agent (requires plan) |
| paused | Assigned agent (resume) |
| needs_revision | Original developer (fix QA issues) |
+------------------+------------------------------------------+
QA VERDICT ACCEPTANCE (qa_pass/qa_fail):
+------------------+------------------------------------------+
| Status | Reason |
+------------------+------------------------------------------+
| awaiting_qa | Task submitted for QA review |
| claimed | QA claimed the task |
| in_progress | QA started working (plan + start) |
+------------------+------------------------------------------+
All three statuses are valid because QA follows the full workflow:
awaiting_qa → claim → plan → start → test → verdict
DOCUMENTER COMPLETION ACCEPTANCE (docs_complete):
+----------------------+------------------------------------------+
| Status | Reason |
+----------------------+------------------------------------------+
| awaiting_documentation | Task ready for documentation |
| claimed | Documenter claimed the task |
| in_progress | Documenter started (plan + start) |
+----------------------+------------------------------------------+
All three statuses are valid because Documenter follows the full workflow:
awaiting_documentation → claim → plan → start → gather → write → docs_complete
```
## 7. Escalation Chain
```
Developer/QA/Doc → Cell PM → Main PM → Product Owner → CEO
+------------+ +---------+ +---------+ +---------------+ +-----+
| be-dev-1 |---->| | | | | | | |
| be-dev-2 |---->| be-pm |---->| | | | | |
| be-qa |---->| | | | | | | |
| be-doc |---->| | | | | | | |
+------------+ +---------+ | | | | | |
| main-pm |---->| product-owner |---->| CEO |
+------------+ +---------+ | | | | | |
| fe-dev-1 |---->| | | | | | | |
| fe-dev-2 |---->| fe-pm |---->| | | | | |
| fe-qa |---->| | | | | | | |
| fe-doc |---->| | | | | | | |
+------------+ +---------+ +---------+ +---------------+ +-----+
```
## 8. Communication vs Notification
```
+-------------------+----------------------------------+----------------------------------+
| Mechanism | Who Can Use | Purpose |
+-------------------+----------------------------------+----------------------------------+
| Messages | Everyone | Constant stream, logged |
| (roboco_message) | | discussions, updates |
+-------------------+----------------------------------+----------------------------------+
| Blocker Reports | Everyone | Signal blocked status |
| (roboco_report_ | | PM auto-notified |
| blocker) | | |
+-------------------+----------------------------------+----------------------------------+
| Notifications | PM, Board, Auditor, CEO | Formal signals requiring |
| (roboco_notify) | | acknowledgment |
+-------------------+----------------------------------+----------------------------------+
| Escalations | PMs only | High-priority issues |
| (roboco_escalate) | | up the chain |
+-------------------+----------------------------------+----------------------------------+
```
+18
View File
@@ -0,0 +1,18 @@
# Backend Documentation
Documentation for the Backend Cell team.
## Access
- **READ**: Backend Cell (developers, QA, PM, documenter)
- **WRITE**: be-doc only
## Contents
- `/api/` - API documentation
- `/qa/` - QA-related docs
## Contributing
Backend team members should request documentation updates through the Cell PM.
Only the Backend Documenter (be-doc) can write to this directory.
+23
View File
@@ -0,0 +1,23 @@
# Bug Documentation
Documentation for bugs organized by team.
## Structure
- `/backend/` - Backend bugs (be-doc writes)
- `/frontend/` - Frontend bugs (fe-doc writes)
- `/ux_ui/` - UX/UI bugs (ux-doc writes)
- `/resolved/` - Archive for resolved bugs
## Access
- **READ**: All agents
- **WRITE**: Team-specific documenters
## Bug Documentation Template
When documenting a bug:
1. Description and reproduction steps
2. Root cause analysis
3. Solution implemented
4. Prevention measures
+15
View File
@@ -0,0 +1,15 @@
# Feature Documentation
Documentation for features organized by team.
## Structure
- `/backend/` - Backend features (be-doc writes)
- `/frontend/` - Frontend features (fe-doc writes)
- `/ux_ui/` - UX/UI features (ux-doc writes)
- `/shared/` - Cross-team features (any documenter writes)
## Access
Each team subdirectory follows the same access pattern as the main team docs.
The `/shared/` directory is readable by all and writable by any documenter.
+18
View File
@@ -0,0 +1,18 @@
# Frontend Documentation
Documentation for the Frontend Cell team.
## Access
- **READ**: Frontend Cell (developers, QA, PM, documenter)
- **WRITE**: fe-doc only
## Contents
- `/components/` - Component documentation
- `/qa/` - QA-related docs
## Contributing
Frontend team members should request documentation updates through the Cell PM.
Only the Frontend Documenter (fe-doc) can write to this directory.
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
# Initiatives Documentation
Cross-team initiatives and major projects.
## Structure
Each initiative gets its own subdirectory:
```
initiatives/
├── {initiative-id}/
│ ├── README.md
│ ├── design.md
│ ├── progress.md
│ └── ...
```
## Access
- **READ**: Assigned teams
- **WRITE**: Assigned documenters
## Creating an Initiative
1. Main PM creates initiative task
2. Creates initiative directory
3. Assigns teams and documenters
4. Teams document their contributions
+22
View File
@@ -0,0 +1,22 @@
# RoboCo Self Documentation
Meta-documentation about the RoboCo system itself.
## Structure
- `/architecture/` - System architecture docs
- `/incidents/` - Incident reports and post-mortems
- `/reports/` - System reports and analytics
## Access
- **READ**: Board + PM roles
- **WRITE**: Main PM, Auditor
## Purpose
This directory contains documentation about RoboCo's own operations:
- How the system works
- Issues encountered
- Performance reports
- Architectural decisions
+576
View File
@@ -0,0 +1,576 @@
# Code Review Guidelines
Standards for conducting effective code reviews in the RoboCo system.
---
## Table of Contents
1. [Review Philosophy](#review-philosophy)
2. [Reviewer Responsibilities](#reviewer-responsibilities)
3. [Author Responsibilities](#author-responsibilities)
4. [Review Checklist](#review-checklist)
5. [Feedback Guidelines](#feedback-guidelines)
6. [Severity Classification](#severity-classification)
7. [Common Issues](#common-issues)
8. [Automated Checks](#automated-checks)
---
## Review Philosophy
### CR-001: Purpose of Code Review
**Goals:**
1. **Catch bugs** - Find defects before they reach production
2. **Maintain quality** - Ensure code meets standards
3. **Share knowledge** - Spread understanding across team
4. **Improve design** - Identify better approaches
5. **Ensure consistency** - Keep codebase uniform
**NOT goals:**
- Demonstrate superiority
- Nitpick style (that's what linters are for)
- Rewrite someone's code
- Block progress indefinitely
### CR-002: Review Mindset
**As Reviewer:**
- Assume the author did their best
- Ask questions before making assumptions
- Explain the "why" behind suggestions
- Be constructive, not destructive
- Praise good work
**As Author:**
- Code review is about the code, not you
- Every suggestion is an opportunity to learn
- Explain your reasoning when disagreeing
- Thank reviewers for their time
---
## Reviewer Responsibilities
### CR-010: Response Time
| Priority | First Response | Full Review |
|----------|---------------|-------------|
| Urgent (blocker fix) | < 2 hours | < 4 hours |
| Normal | < 4 hours | < 1 day |
| Low (refactor, docs) | < 1 day | < 2 days |
### CR-011: Review Depth
**Quick Pass (5 min):**
- Does the PR description make sense?
- Are tests included?
- Does CI pass?
**Thorough Review (30+ min):**
- Understand the full context
- Check logic and edge cases
- Verify tests cover scenarios
- Review documentation updates
### CR-012: What to Review
| Must Review | Should Review | Don't Review |
|-------------|---------------|--------------|
| Logic correctness | Code style | Auto-generated code |
| Error handling | Performance | Formatting (linter handles) |
| Security concerns | Naming | Import order (linter handles) |
| Test coverage | Documentation | |
| API contracts | Code organization | |
---
## Author Responsibilities
### CR-020: Before Requesting Review
**Pre-submission Checklist:**
```markdown
- [ ] All automated checks pass (lint, type check, tests)
- [ ] Self-reviewed the diff
- [ ] PR description explains the change
- [ ] Tests cover new functionality
- [ ] Documentation updated if needed
- [ ] No debugging code left in
- [ ] No unrelated changes included
- [ ] Commit history is clean
```
### CR-021: Writing Good PR Descriptions
**Template:**
```markdown
## Summary
Brief description of what this PR does.
## Changes
- Added X functionality
- Modified Y behavior
- Removed deprecated Z
## Testing
How to test this change:
1. Step one
2. Step two
3. Expected result
## Related
- Task: TASK-123
- Related PR: #456
```
### CR-022: Keeping PRs Small
**Size Guidelines:**
| Lines Changed | Classification | Review Time |
|---------------|----------------|-------------|
| < 100 | Small | 15 min |
| 100-300 | Medium | 30 min |
| 300-500 | Large | 1 hour |
| > 500 | Too Large | Split it! |
**How to Split Large PRs:**
1. **By layer**: API → Service → Repository
2. **By feature**: Core logic → Edge cases → Polish
3. **By concern**: Main feature → Tests → Docs
---
## Review Checklist
### CR-030: Functionality
```markdown
- [ ] Code does what PR description says
- [ ] Edge cases are handled
- [ ] Error conditions are handled gracefully
- [ ] No obvious bugs or logic errors
- [ ] Performance is acceptable for use case
```
### CR-031: Code Quality
```markdown
- [ ] Follows project coding standards
- [ ] No code duplication (DRY)
- [ ] Functions/classes have single responsibility
- [ ] Naming is clear and consistent
- [ ] Comments explain "why", not "what"
- [ ] No dead code or TODOs without context
```
### CR-032: Security
```markdown
- [ ] No hardcoded secrets or credentials
- [ ] User input is validated and sanitized
- [ ] SQL queries use parameterized statements
- [ ] No command injection vulnerabilities
- [ ] Sensitive data is not logged
- [ ] Access control is properly enforced
```
### CR-033: Testing
```markdown
- [ ] New code has tests
- [ ] Tests cover happy path
- [ ] Tests cover error cases
- [ ] Tests are readable and maintainable
- [ ] No tests skipped without reason
- [ ] Mocking is appropriate (not excessive)
```
### CR-034: API Design
```markdown
- [ ] API is intuitive and consistent
- [ ] Breaking changes are noted
- [ ] Error responses are informative
- [ ] Documentation is updated
- [ ] Backwards compatibility maintained
```
### CR-035: Data Handling
```markdown
- [ ] Database migrations are reversible
- [ ] Indexes are used appropriately
- [ ] No N+1 query issues
- [ ] Large data sets are handled efficiently
- [ ] Transactions are used correctly
```
---
## Feedback Guidelines
### CR-040: How to Give Feedback
**Structure:**
```markdown
[Severity]: [Issue]
[Context/Reason]
[Suggestion if applicable]
```
**Examples:**
```markdown
# Good feedback
BLOCKER: This SQL query is vulnerable to injection
The user input is concatenated directly. An attacker could
extract all data with: `'; DROP TABLE users; --`
Suggestion: Use parameterized queries:
```python
await db.execute("SELECT * FROM users WHERE id = :id", {"id": user_id})
```
# Bad feedback
"This is wrong."
```
### CR-041: Severity Prefixes
Use prefixes to indicate urgency:
| Prefix | Meaning | Action |
|--------|---------|--------|
| `BLOCKER:` | Must fix, security/correctness issue | Cannot merge |
| `MAJOR:` | Should fix, significant concern | Should address |
| `MINOR:` | Nice to fix, improvement | Can defer |
| `NIT:` | Nitpick, style preference | Optional |
| `QUESTION:` | Need clarification | Explain |
| `PRAISE:` | Good work | Keep doing this! |
### CR-042: Types of Comments
**Actionable:**
```markdown
MAJOR: This function modifies its input parameter, which can cause
unexpected behavior for callers. Consider returning a new object instead.
```
**Question (non-blocking):**
```markdown
QUESTION: Is this timeout intentionally set to 5 minutes? Seems long
for a health check.
```
**Suggestion (optional):**
```markdown
NIT: Could use list comprehension here for readability:
`[x.name for x in items if x.active]`
```
**Praise:**
```markdown
PRAISE: Great error handling here! The retry logic with backoff
is exactly what we need for this external API.
```
### CR-043: What NOT to Do
**Avoid:**
- Personal attacks: "Who wrote this garbage?"
- Vague criticism: "This is confusing"
- Style debates: "I prefer X" (unless it violates standards)
- Demands without explanation: "Change this"
- Blocking for non-issues: Minor style preferences
---
## Severity Classification
### CR-050: Severity Definitions
| Severity | Definition | Examples |
|----------|------------|----------|
| **BLOCKER** | Security vulnerability, data loss risk, breaks build | SQL injection, missing auth check, crashes |
| **MAJOR** | Significant bug, performance issue, design flaw | Logic error, N+1 queries, missing validation |
| **MINOR** | Improvement opportunity, minor bug | Better naming, missing edge case, documentation |
| **NIT** | Style preference, optional enhancement | Alternative approach, formatting preference |
### CR-051: Blocking vs Non-Blocking
**Block merge for:**
- Security vulnerabilities (any severity)
- Logic errors that affect functionality
- Missing tests for critical paths
- Breaking API changes without migration
- Violations of ERROR-level coding standards
**Don't block merge for:**
- Style preferences covered by linters
- "I would have done it differently"
- Missing documentation (unless API change)
- Code that works but isn't "perfect"
- Minor optimizations
---
## Common Issues
### CR-060: Logic Issues
**Missing null checks:**
```python
# Bad
user.name.lower() # What if user is None?
# Good
if user and user.name:
user.name.lower()
```
**Off-by-one errors:**
```python
# Bad
for i in range(len(items) + 1): # IndexError on last iteration
items[i]
# Good
for i in range(len(items)):
items[i]
```
**Race conditions:**
```python
# Bad
if task.status == "pending":
# Another process could change status here!
task.status = "claimed"
db.save(task)
# Good - Use atomic operations
await db.execute(
"UPDATE tasks SET status = 'claimed' WHERE id = :id AND status = 'pending'",
{"id": task.id}
)
```
### CR-061: Performance Issues
**N+1 queries:**
```python
# Bad
tasks = await db.query(Task).all()
for task in tasks:
owner = await db.query(User).filter_by(id=task.owner_id).first() # N queries!
# Good
tasks = await db.query(Task).options(selectinload(Task.owner)).all()
```
**Unbounded queries:**
```python
# Bad
users = await db.query(User).all() # Could be millions
# Good
users = await db.query(User).limit(100).all()
```
**Memory bloat:**
```python
# Bad
data = [x for x in huge_iterator] # Loads all into memory
# Good
for x in huge_iterator: # Process one at a time
process(x)
```
### CR-062: Security Issues
**SQL injection:**
```python
# Bad
f"SELECT * FROM users WHERE id = '{user_input}'"
# Good
"SELECT * FROM users WHERE id = :id", {"id": user_input}
```
**Missing authorization:**
```python
# Bad
@router.delete("/tasks/{task_id}")
async def delete_task(task_id: str) -> None:
await db.delete_task(task_id) # Anyone can delete any task!
# Good
@router.delete("/tasks/{task_id}")
async def delete_task(task_id: str, current_user: User = Depends()) -> None:
task = await db.get_task(task_id)
if task.owner_id != current_user.id:
raise HTTPException(403, "Not authorized")
await db.delete_task(task_id)
```
**Sensitive data exposure:**
```python
# Bad
logger.info(f"User login: {user.email}, password: {password}")
# Good
logger.info("User login", user_id=user.id)
```
---
## Automated Checks
### CR-070: Required Checks
All PRs must pass these automated checks before merge:
| Check | Tool | Purpose |
|-------|------|---------|
| Formatting | ruff format | Code style consistency |
| Linting | ruff check | Code quality issues |
| Type checking | mypy | Type safety |
| Tests | pytest | Functionality verification |
| Dead code | vulture | Remove unused code |
| Security | bandit | Security vulnerabilities |
| Complexity | xenon | Maintainability |
### CR-071: CI Pipeline
```yaml
# .github/workflows/ci.yml
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- name: Format check
run: uv run ruff format --check .
- name: Lint
run: uv run ruff check .
- name: Type check
run: uv run mypy roboco/
- name: Tests
run: uv run pytest --cov=roboco --cov-fail-under=80
- name: Security scan
run: uv run bandit -r roboco/ -ll
```
### CR-072: Pre-commit Hooks
Use pre-commit hooks to catch issues early:
```yaml
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: ruff-format
name: ruff format
entry: uv run ruff format
language: system
types: [python]
- id: ruff-check
name: ruff check
entry: uv run ruff check --fix
language: system
types: [python]
- id: mypy
name: mypy
entry: uv run mypy
language: system
types: [python]
```
---
## Quick Reference
### Review Flow
```
1. Author creates PR
└─► Auto-checks run
2. Reviewer assigned
└─► Quick pass (5 min)
└─► Issues? Request changes early
3. Full review
└─► Check functionality
└─► Check code quality
└─► Check security
└─► Check tests
4. Feedback given
└─► BLOCKER/MAJOR: Must address
└─► MINOR/NIT: Optional
5. Author addresses feedback
└─► Push changes
└─► Reply to comments
6. Re-review if needed
└─► Approve or request more changes
7. Merge
└─► Delete branch
```
### Comment Templates
**Blocker:**
```markdown
BLOCKER: [Brief issue]
[Why this is a problem]
[How to fix it]
```
**Question:**
```markdown
QUESTION: [What you don't understand]
[Context for why you're asking]
```
**Praise:**
```markdown
PRAISE: [What's good about this]
[Why it's particularly good]
```
### Time Estimates
| PR Size | Lines | Review Time |
|---------|-------|-------------|
| XS | < 50 | 10 min |
| S | 50-100 | 15 min |
| M | 100-300 | 30 min |
| L | 300-500 | 1 hour |
| XL | > 500 | Split it! |
@@ -0,0 +1,997 @@
# Design Principles
Foundational design principles for building maintainable, scalable software in the RoboCo system.
---
## Table of Contents
1. [SOLID Principles](#solid-principles)
2. [DRY - Don't Repeat Yourself](#dry---dont-repeat-yourself)
3. [KISS - Keep It Simple, Stupid](#kiss---keep-it-simple-stupid)
4. [YAGNI - You Aren't Gonna Need It](#yagni---you-arent-gonna-need-it)
5. [Separation of Concerns](#separation-of-concerns)
6. [Composition Over Inheritance](#composition-over-inheritance)
7. [Fail Fast](#fail-fast)
8. [Law of Demeter](#law-of-demeter)
9. [Dependency Injection](#dependency-injection)
10. [Immutability](#immutability)
---
## SOLID Principles
### ARCH-001: Single Responsibility Principle (SRP)
**Severity:** WARNING
**Principle:** A class should have one, and only one, reason to change.
Each module, class, or function should do one thing well.
```python
# Bad - Multiple responsibilities
class TaskManager:
def create_task(self, data: TaskCreate) -> Task:
# Creates task
...
def send_notification(self, task: Task, recipient: str) -> None:
# Sends notification
...
def generate_report(self, tasks: list[Task]) -> Report:
# Generates report
...
# Good - Single responsibility per class
class TaskService:
def create(self, data: TaskCreate) -> Task:
...
class NotificationService:
def send(self, notification: Notification) -> None:
...
class ReportService:
def generate(self, tasks: list[Task]) -> Report:
...
```
### ARCH-002: Open/Closed Principle (OCP)
**Severity:** WARNING
**Principle:** Software entities should be open for extension, but closed for modification.
Design systems that can be extended without modifying existing code.
```python
# Bad - Need to modify class for new types
class TaskProcessor:
def process(self, task: Task) -> None:
if task.type == "bug":
self._process_bug(task)
elif task.type == "feature":
self._process_feature(task)
elif task.type == "refactor": # Added later - modifies existing code
self._process_refactor(task)
# Good - Extend via new classes
from abc import ABC, abstractmethod
class TaskProcessor(ABC):
@abstractmethod
def process(self, task: Task) -> None:
...
class BugProcessor(TaskProcessor):
def process(self, task: Task) -> None:
...
class FeatureProcessor(TaskProcessor):
def process(self, task: Task) -> None:
...
class RefactorProcessor(TaskProcessor): # Added without modifying existing code
def process(self, task: Task) -> None:
...
# Registry pattern for extension
PROCESSORS: dict[str, type[TaskProcessor]] = {
"bug": BugProcessor,
"feature": FeatureProcessor,
"refactor": RefactorProcessor,
}
def get_processor(task_type: str) -> TaskProcessor:
return PROCESSORS[task_type]()
```
### ARCH-003: Liskov Substitution Principle (LSP)
**Severity:** ERROR
**Principle:** Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.
Derived classes must be substitutable for their base classes.
```python
# Bad - Subclass violates base class contract
class Bird:
def fly(self) -> None:
print("Flying")
class Penguin(Bird):
def fly(self) -> None:
raise NotImplementedError("Penguins can't fly!") # Violates LSP
# Good - Proper abstraction
from abc import ABC, abstractmethod
class Bird(ABC):
@abstractmethod
def move(self) -> None:
...
class FlyingBird(Bird):
def move(self) -> None:
self.fly()
def fly(self) -> None:
print("Flying")
class SwimmingBird(Bird):
def move(self) -> None:
self.swim()
def swim(self) -> None:
print("Swimming")
```
### ARCH-004: Interface Segregation Principle (ISP)
**Severity:** WARNING
**Principle:** Many client-specific interfaces are better than one general-purpose interface.
Don't force clients to depend on methods they don't use.
```python
# Bad - Fat interface
class Worker(ABC):
@abstractmethod
def work(self) -> None: ...
@abstractmethod
def eat(self) -> None: ...
@abstractmethod
def sleep(self) -> None: ...
class Robot(Worker):
def work(self) -> None:
...
def eat(self) -> None:
raise NotImplementedError() # Robots don't eat
def sleep(self) -> None:
raise NotImplementedError() # Robots don't sleep
# Good - Segregated interfaces
class Workable(ABC):
@abstractmethod
def work(self) -> None: ...
class Feedable(ABC):
@abstractmethod
def eat(self) -> None: ...
class Sleepable(ABC):
@abstractmethod
def sleep(self) -> None: ...
class Human(Workable, Feedable, Sleepable):
def work(self) -> None: ...
def eat(self) -> None: ...
def sleep(self) -> None: ...
class Robot(Workable):
def work(self) -> None: ...
```
### ARCH-005: Dependency Inversion Principle (DIP)
**Severity:** ERROR
**Principle:** Depend on abstractions, not concretions.
High-level modules should not depend on low-level modules.
```python
# Bad - High-level depends on low-level
class PostgreSQLDatabase:
def query(self, sql: str) -> list[dict]:
...
class TaskRepository:
def __init__(self) -> None:
self.db = PostgreSQLDatabase() # Tight coupling
def get_task(self, task_id: str) -> Task:
return self.db.query(f"SELECT * FROM tasks WHERE id = '{task_id}'")
# Good - Depend on abstraction
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
async def query(self, sql: str, params: dict) -> list[dict]:
...
class PostgreSQLDatabase(Database):
async def query(self, sql: str, params: dict) -> list[dict]:
...
class TaskRepository:
def __init__(self, db: Database) -> None:
self.db = db # Depends on abstraction
async def get_task(self, task_id: str) -> Task:
result = await self.db.query(
"SELECT * FROM tasks WHERE id = :id",
{"id": task_id}
)
return Task(**result[0])
```
---
## DRY - Don't Repeat Yourself
### ARCH-010: No Code Duplication
**Severity:** WARNING
**Principle:** Every piece of knowledge must have a single, unambiguous, authoritative representation.
Eliminate duplication of logic, data, and knowledge.
```python
# Bad - Duplicated validation logic
class UserService:
def create_user(self, email: str) -> User:
if not re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email):
raise ValueError("Invalid email")
...
class InviteService:
def send_invite(self, email: str) -> None:
if not re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email): # Duplicated!
raise ValueError("Invalid email")
...
# Good - Single source of truth
EMAIL_PATTERN = re.compile(r"^[\w\.-]+@[\w\.-]+\.\w+$")
def validate_email(email: str) -> str:
"""Validate and return email or raise ValueError."""
if not EMAIL_PATTERN.match(email):
raise ValueError(f"Invalid email: {email}")
return email.lower()
class UserService:
def create_user(self, email: str) -> User:
validated_email = validate_email(email)
...
class InviteService:
def send_invite(self, email: str) -> None:
validated_email = validate_email(email)
...
```
### ARCH-011: Extract Common Patterns
**Severity:** WARNING
When you see the same pattern three times, extract it.
```python
# Bad - Repeated error handling pattern
async def get_user(user_id: str) -> User:
try:
result = await db.query("SELECT * FROM users WHERE id = :id", {"id": user_id})
if not result:
raise NotFoundError(f"User {user_id} not found")
return User(**result[0])
except DatabaseError as e:
logger.error("Database error", error=str(e))
raise
async def get_task(task_id: str) -> Task:
try:
result = await db.query("SELECT * FROM tasks WHERE id = :id", {"id": task_id})
if not result:
raise NotFoundError(f"Task {task_id} not found")
return Task(**result[0])
except DatabaseError as e:
logger.error("Database error", error=str(e))
raise
# Good - Extract common pattern
T = TypeVar('T')
async def get_by_id(
table: str,
id_value: str,
model: type[T],
entity_name: str,
) -> T:
"""Generic get-by-id with error handling."""
try:
result = await db.query(
f"SELECT * FROM {table} WHERE id = :id",
{"id": id_value}
)
if not result:
raise NotFoundError(f"{entity_name} {id_value} not found")
return model(**result[0])
except DatabaseError as e:
logger.error("Database error", table=table, error=str(e))
raise
async def get_user(user_id: str) -> User:
return await get_by_id("users", user_id, User, "User")
async def get_task(task_id: str) -> Task:
return await get_by_id("tasks", task_id, Task, "Task")
```
### ARCH-012: But Avoid False DRY
**Severity:** INFO
Not all similar code is duplicate. Don't abstract too early.
```python
# False DRY - These look similar but serve different purposes
def format_user_name(user: User) -> str:
return f"{user.first_name} {user.last_name}"
def format_agent_name(agent: Agent) -> str:
return f"{agent.role}: {agent.slug}"
# Don't force these into a single function just because they both "format names"
# They have different semantics and will evolve independently
```
---
## KISS - Keep It Simple, Stupid
### ARCH-020: Prefer Simple Solutions
**Severity:** WARNING
**Principle:** The simplest solution that works is often the best.
Avoid unnecessary complexity.
```python
# Bad - Over-engineered solution
class TaskStatusStrategyFactory:
_strategies: dict[str, type[TaskStatusStrategy]] = {}
@classmethod
def register(cls, status: str) -> Callable:
def decorator(strategy_class: type[TaskStatusStrategy]) -> type[TaskStatusStrategy]:
cls._strategies[status] = strategy_class
return strategy_class
return decorator
@classmethod
def create(cls, task: Task) -> TaskStatusStrategy:
return cls._strategies[task.status]()
@TaskStatusStrategyFactory.register("pending")
class PendingStatusStrategy(TaskStatusStrategy):
def can_transition_to(self, new_status: str) -> bool:
return new_status in ["claimed", "cancelled"]
# Good - Simple and clear
VALID_TRANSITIONS = {
"pending": {"claimed", "cancelled"},
"claimed": {"in_progress", "pending"},
"in_progress": {"completed", "blocked", "paused"},
# ... etc
}
def can_transition(current: str, new: str) -> bool:
return new in VALID_TRANSITIONS.get(current, set())
```
### ARCH-021: Avoid Premature Abstraction
**Severity:** WARNING
Don't abstract before you have concrete requirements.
```python
# Bad - Premature abstraction
class AbstractDataProcessor(ABC):
@abstractmethod
def preprocess(self, data: Any) -> Any: ...
@abstractmethod
def process(self, data: Any) -> Any: ...
@abstractmethod
def postprocess(self, data: Any) -> Any: ...
def run(self, data: Any) -> Any:
data = self.preprocess(data)
data = self.process(data)
return self.postprocess(data)
# When you only have one implementation!
class TaskDataProcessor(AbstractDataProcessor):
def preprocess(self, data: Any) -> Any:
return data # Does nothing
def process(self, data: Any) -> Any:
return transform_task(data)
def postprocess(self, data: Any) -> Any:
return data # Does nothing
# Good - Start simple, abstract when needed
def process_task_data(data: dict) -> Task:
return transform_task(data)
# Later, when you ACTUALLY need abstraction:
# Then create the base class with proven patterns
```
### ARCH-022: Readable Over Clever
**Severity:** WARNING
Code is read more often than written. Optimize for readability.
```python
# Bad - Clever but unreadable
result = reduce(
lambda acc, x: {**acc, x[0]: x[1]},
filter(lambda t: t[1] > 0, map(lambda k: (k, data.get(k, 0)), keys)),
{}
)
# Good - Clear and readable
result = {}
for key in keys:
value = data.get(key, 0)
if value > 0:
result[key] = value
```
---
## YAGNI - You Aren't Gonna Need It
### ARCH-030: Don't Build Speculatively
**Severity:** WARNING
**Principle:** Only implement features when you actually need them.
```python
# Bad - Building for hypothetical future
class TaskService:
def __init__(
self,
db: Database,
cache: Cache,
queue: MessageQueue,
analytics: AnalyticsService,
audit_log: AuditLogService,
rate_limiter: RateLimiter,
circuit_breaker: CircuitBreaker,
feature_flags: FeatureFlagService,
) -> None:
# Most of these aren't used yet
...
def create_task(self, data: TaskCreate) -> Task:
# Just creates a task in the database
return self.db.create_task(data)
# Good - Only what you need now
class TaskService:
def __init__(self, db: Database) -> None:
self.db = db
async def create_task(self, data: TaskCreate) -> Task:
return await self.db.create_task(data)
# Add cache, queue, etc. when you actually need them
```
### ARCH-031: Delete Unused Code
**Severity:** ERROR
**Tools:** vulture
Remove dead code. It's not "just in case" - it's noise.
```python
# Bad - Keeping "just in case" code
class TaskService:
def create_task(self, data: TaskCreate) -> Task:
...
# def create_task_v2(self, data: TaskCreateV2) -> Task:
# """New version - not sure if we'll use this"""
# ...
# def _experimental_feature(self) -> None:
# """Might need this later"""
# pass
# Good - Clean codebase
class TaskService:
def create_task(self, data: TaskCreate) -> Task:
...
# Use version control for history, not comments
```
---
## Separation of Concerns
### ARCH-040: Layer Architecture
**Severity:** ERROR
Organize code into distinct layers with clear responsibilities.
```
┌─────────────────────────────────────────┐
│ API Layer (Routes) │ ← HTTP handling, validation
├─────────────────────────────────────────┤
│ Service Layer (Business) │ ← Business logic, orchestration
├─────────────────────────────────────────┤
│ Repository Layer (Data Access) │ ← Database queries, caching
├─────────────────────────────────────────┤
│ Model Layer (Domain) │ ← Data structures, entities
└─────────────────────────────────────────┘
```
```python
# Good - Clear layer separation
# models/task.py - Domain entities
class Task(BaseModel):
id: UUID
title: str
status: TaskStatus
# repositories/task.py - Data access
class TaskRepository:
async def get_by_id(self, task_id: UUID) -> Task | None:
result = await self.db.query(...)
return Task(**result) if result else None
# services/task.py - Business logic
class TaskService:
def __init__(self, repo: TaskRepository, notifier: NotificationService) -> None:
self.repo = repo
self.notifier = notifier
async def complete_task(self, task_id: UUID) -> Task:
task = await self.repo.get_by_id(task_id)
if task is None:
raise TaskNotFoundError(task_id)
task.status = TaskStatus.COMPLETED
await self.repo.update(task)
await self.notifier.notify_completion(task)
return task
# api/routes/tasks.py - HTTP handling
@router.post("/tasks/{task_id}/complete")
async def complete_task(task_id: UUID, service: TaskService = Depends()) -> TaskResponse:
task = await service.complete_task(task_id)
return TaskResponse.from_orm(task)
```
### ARCH-041: No Business Logic in Routes
**Severity:** ERROR
API routes should only handle HTTP concerns.
```python
# Bad - Business logic in route
@router.post("/tasks")
async def create_task(request: TaskCreate, db: AsyncSession = Depends()) -> TaskResponse:
# Validation
if len(request.title) < 3:
raise HTTPException(400, "Title too short")
# Business logic (should be in service)
task = Task(**request.dict())
task.created_at = datetime.now(UTC)
task.status = TaskStatus.PENDING
if request.assigned_to:
agent = await db.query(Agent).filter_by(id=request.assigned_to).first()
if agent is None:
raise HTTPException(400, "Agent not found")
task.assigned_to = agent.id
db.add(task)
await db.commit()
# Send notification (should be in service)
await send_notification(task.assigned_to, f"New task: {task.title}")
return TaskResponse.from_orm(task)
# Good - Route delegates to service
@router.post("/tasks")
async def create_task(
request: TaskCreate,
service: TaskService = Depends(),
) -> TaskResponse:
task = await service.create(request)
return TaskResponse.from_orm(task)
```
### ARCH-042: No Database Queries in Routes
**Severity:** ERROR
Database access should be in repository or service layer.
```python
# Bad - Direct DB access in route
@router.get("/tasks")
async def list_tasks(
status: TaskStatus | None = None,
db: AsyncSession = Depends(),
) -> list[TaskResponse]:
query = select(Task)
if status:
query = query.where(Task.status == status)
result = await db.execute(query)
return [TaskResponse.from_orm(t) for t in result.scalars()]
# Good - Delegate to service/repository
@router.get("/tasks")
async def list_tasks(
status: TaskStatus | None = None,
service: TaskService = Depends(),
) -> list[TaskResponse]:
tasks = await service.list(status=status)
return [TaskResponse.from_orm(t) for t in tasks]
```
---
## Composition Over Inheritance
### ARCH-050: Prefer Composition
**Severity:** WARNING
**Principle:** Favor object composition over class inheritance.
```python
# Bad - Deep inheritance hierarchy
class BaseProcessor:
def process(self, data: Any) -> Any:
...
class ValidatingProcessor(BaseProcessor):
def process(self, data: Any) -> Any:
self.validate(data)
return super().process(data)
class LoggingValidatingProcessor(ValidatingProcessor):
def process(self, data: Any) -> Any:
self.log_start(data)
result = super().process(data)
self.log_end(result)
return result
class CachingLoggingValidatingProcessor(LoggingValidatingProcessor):
... # Getting ridiculous
# Good - Composition with mixins or decorators
class TaskProcessor:
def __init__(
self,
validator: Validator | None = None,
logger: Logger | None = None,
cache: Cache | None = None,
) -> None:
self.validator = validator
self.logger = logger
self.cache = cache
def process(self, data: Any) -> Any:
if self.validator:
self.validator.validate(data)
if self.logger:
self.logger.log_start(data)
result = self._do_process(data)
if self.cache:
self.cache.set(data.id, result)
if self.logger:
self.logger.log_end(result)
return result
```
### ARCH-051: Use Protocols Over ABC
**Severity:** INFO
**Python:** Use Protocol for structural typing when possible.
```python
# Good - Protocol-based typing
from typing import Protocol
class Repository(Protocol):
async def get(self, id: str) -> dict | None: ...
async def save(self, entity: dict) -> None: ...
class TaskService:
def __init__(self, repo: Repository) -> None:
self.repo = repo
# Any class with get/save methods works, no inheritance needed
class InMemoryRepo:
async def get(self, id: str) -> dict | None:
return self.data.get(id)
async def save(self, entity: dict) -> None:
self.data[entity["id"]] = entity
# Works with TaskService without explicit inheritance!
```
---
## Fail Fast
### ARCH-060: Validate Early
**Severity:** ERROR
**Principle:** Detect and report errors as early as possible.
```python
# Bad - Late failure
def process_order(order: dict) -> Receipt:
# ... lots of processing ...
# Fails late after doing work
if order.get("customer_id") is None:
raise ValueError("Missing customer_id")
# More processing that depends on customer_id
...
# Good - Fail fast
def process_order(order: dict) -> Receipt:
# Validate immediately
if order.get("customer_id") is None:
raise ValueError("Missing customer_id")
if order.get("items") is None or len(order["items"]) == 0:
raise ValueError("Order must have items")
# Now process with confidence
...
```
### ARCH-061: Use Guard Clauses
**Severity:** WARNING
Return early for invalid states instead of deep nesting.
```python
# Bad - Deep nesting
def process_task(task: Task | None, agent: Agent | None) -> Result:
if task is not None:
if task.status == TaskStatus.PENDING:
if agent is not None:
if agent.can_claim(task):
return do_process(task, agent)
else:
return Result(error="Agent cannot claim")
else:
return Result(error="No agent")
else:
return Result(error="Task not pending")
else:
return Result(error="No task")
# Good - Guard clauses
def process_task(task: Task | None, agent: Agent | None) -> Result:
if task is None:
return Result(error="No task")
if task.status != TaskStatus.PENDING:
return Result(error="Task not pending")
if agent is None:
return Result(error="No agent")
if not agent.can_claim(task):
return Result(error="Agent cannot claim")
return do_process(task, agent)
```
---
## Law of Demeter
### ARCH-070: Don't Talk to Strangers
**Severity:** WARNING
**Principle:** Only talk to immediate friends, not friends of friends.
```python
# Bad - Chained method calls
def get_customer_city(order: Order) -> str:
return order.customer.address.city.name
# If any of these are None, it fails
# Also tightly coupled to internal structure
# Good - Ask, don't dig
class Order:
def get_customer_city(self) -> str:
return self.customer.get_city_name()
class Customer:
def get_city_name(self) -> str:
if self.address and self.address.city:
return self.address.city.name
return "Unknown"
```
---
## Dependency Injection
### ARCH-080: Inject Dependencies
**Severity:** ERROR
**Principle:** Dependencies should be provided, not created internally.
```python
# Bad - Creates own dependencies
class TaskService:
def __init__(self) -> None:
self.db = PostgresDatabase() # Hard to test
self.cache = RedisCache() # Hard to swap
self.notifier = EmailNotifier()
# Good - Inject dependencies
class TaskService:
def __init__(
self,
db: Database,
cache: Cache,
notifier: Notifier,
) -> None:
self.db = db
self.cache = cache
self.notifier = notifier
# FastAPI dependency injection
def get_task_service(
db: Database = Depends(get_database),
cache: Cache = Depends(get_cache),
notifier: Notifier = Depends(get_notifier),
) -> TaskService:
return TaskService(db, cache, notifier)
```
---
## Immutability
### ARCH-090: Prefer Immutable Data
**Severity:** WARNING
**Principle:** Immutable objects are easier to reason about and safer in concurrent code.
```python
# Bad - Mutable state
class Task:
def __init__(self, title: str) -> None:
self.title = title
self.tags = [] # Mutable!
task = Task("Fix bug")
task.tags.append("urgent")
task.title = "Changed!" # Mutation
# Good - Immutable with dataclasses
from dataclasses import dataclass
@dataclass(frozen=True)
class Task:
title: str
tags: tuple[str, ...] = ()
def with_tag(self, tag: str) -> "Task":
"""Return new Task with additional tag."""
return Task(
title=self.title,
tags=self.tags + (tag,)
)
task = Task("Fix bug")
task_with_tag = task.with_tag("urgent") # Returns new instance
```
### ARCH-091: Avoid Side Effects in Functions
**Severity:** WARNING
Pure functions are easier to test and reason about.
```python
# Bad - Side effects
def process_tasks(tasks: list[Task]) -> None:
for task in tasks:
task.processed = True # Mutates input!
global_counter += 1 # Global state!
send_notification() # Side effect!
# Good - Pure function
def process_tasks(tasks: list[Task]) -> list[ProcessedTask]:
return [
ProcessedTask(
task=task,
processed_at=datetime.now(UTC)
)
for task in tasks
]
# Handle side effects separately
processed = process_tasks(tasks)
for p in processed:
await notifier.send(p)
```
---
## Quick Reference
### Principle Severity
| Principle | Severity | Impact |
|-----------|----------|--------|
| Liskov Substitution | ERROR | Breaks polymorphism |
| Dependency Inversion | ERROR | Prevents testing |
| No DB in Routes | ERROR | Violates layering |
| Validate Early | ERROR | Wastes resources |
| Inject Dependencies | ERROR | Untestable code |
| Single Responsibility | WARNING | Maintenance burden |
| Open/Closed | WARNING | Modification risk |
| DRY | WARNING | Bug propagation |
| KISS | WARNING | Complexity cost |
| YAGNI | WARNING | Wasted effort |
| Composition | WARNING | Rigid hierarchy |
| Immutability | WARNING | Concurrency bugs |
### Anti-Pattern Detection
| Anti-Pattern | Signs | Fix |
|--------------|-------|-----|
| God Class | Class > 500 lines | Split by responsibility |
| Feature Envy | Uses other class's data heavily | Move method to that class |
| Shotgun Surgery | One change requires many edits | Consolidate related code |
| Primitive Obsession | Many primitives for one concept | Create value object |
| Long Parameter List | > 5 parameters | Use parameter object |
| Data Clumps | Same data together often | Create class for data |
| Speculative Generality | "We might need this" | Delete until needed |
+923
View File
@@ -0,0 +1,923 @@
# Python Coding Standards
Comprehensive standards for Python development in the RoboCo system. These standards are enforced through automated tooling (see [Tooling Enforcement](#tooling-enforcement) section).
---
## Table of Contents
1. [Code Style](#code-style)
2. [Type Safety](#type-safety)
3. [Error Handling](#error-handling)
4. [Data Validation](#data-validation)
5. [Async Patterns](#async-patterns)
6. [Testing](#testing)
7. [Dependencies](#dependencies)
8. [Tooling Enforcement](#tooling-enforcement)
9. [Code Complexity](#code-complexity)
10. [Security](#security)
---
## Code Style
### PY-001: Use Type Hints
**Severity:** ERROR
**Tools:** mypy, ruff
All function signatures MUST include type hints for parameters and return values.
```python
# Good
def process_task(task_id: str, priority: int = 1) -> TaskResult:
...
async def fetch_user(user_id: UUID) -> User | None:
...
# Bad - Missing type hints
def process_task(task_id, priority=1):
...
```
### PY-002: Docstrings Required
**Severity:** WARNING
**Tools:** ruff (D100-D417)
All public functions, classes, and modules MUST have docstrings following Google style.
```python
def calculate_metrics(data: list[float]) -> MetricsResult:
"""Calculate statistical metrics from data points.
Args:
data: List of numeric values to analyze.
Returns:
MetricsResult containing mean, median, and std dev.
Raises:
ValueError: If data is empty.
"""
```
### PY-003: Import Organization
**Severity:** ERROR
**Tools:** ruff (I001-I002)
Imports MUST be sorted in this order: stdlib, third-party, local. Use `ruff` to enforce.
```python
# Good
import asyncio
from pathlib import Path
from fastapi import FastAPI
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from roboco.models import Task, User
from roboco.services import TaskService
```
### PY-004: Line Length
**Severity:** ERROR
**Tools:** ruff (E501)
Maximum line length is 88 characters (Black default). Use line breaks for long expressions.
```python
# Good - Multi-line function call
result = await service.create_task(
title=request.title,
description=request.description,
assigned_to=agent_id,
priority=request.priority,
)
# Good - Multi-line string
error_message = (
f"Task {task_id} cannot be claimed: "
f"current status is {task.status}, expected 'pending'"
)
```
### PY-005: Naming Conventions
**Severity:** ERROR
**Tools:** ruff (N801-N818)
| Type | Convention | Example |
|------|------------|---------|
| Classes | PascalCase | `TaskService`, `UserModel` |
| Functions | snake_case | `get_user`, `process_task` |
| Variables | snake_case | `user_id`, `task_count` |
| Constants | SCREAMING_SNAKE | `MAX_RETRIES`, `DEFAULT_TIMEOUT` |
| Private | Leading underscore | `_internal_method`, `_cache` |
| Type Variables | PascalCase | `T`, `TaskT`, `ResponseT` |
---
## Type Safety
### PY-010: Strict Mypy Configuration
**Severity:** ERROR
**Tools:** mypy
The project uses strict mypy configuration. All code MUST pass these checks:
```toml
# pyproject.toml settings (enforced)
[tool.mypy]
python_version = "3.13"
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
no_implicit_optional = true
strict_optional = true
warn_return_any = true
warn_unreachable = true
```
### PY-011: No `Any` Types
**Severity:** ERROR
**Tools:** mypy
Avoid `Any` type. Use `object`, generics, or `TypeVar` instead.
```python
# Bad
def process_data(data: Any) -> Any:
...
# Good - Use generics
T = TypeVar('T')
def process_data(data: T) -> T:
...
# Good - Use Union for multiple types
def process_data(data: str | bytes) -> ProcessedData:
...
```
### PY-012: Use `None` Explicitly
**Severity:** ERROR
**Tools:** mypy
Use `| None` for optional values. Never use implicit optional.
```python
# Bad - Implicit optional
def get_user(user_id: str, cache: dict = None) -> User:
...
# Good - Explicit optional
def get_user(user_id: str, cache: dict[str, User] | None = None) -> User:
...
```
### PY-013: Use Type Aliases for Complex Types
**Severity:** WARNING
**Tools:** ruff
Create type aliases for complex or repeated types.
```python
# Good - Type aliases improve readability
type TaskCallback = Callable[[Task, TaskStatus], Awaitable[None]]
type SearchResults = list[tuple[str, float, dict[str, Any]]]
async def search_with_callback(
query: str,
callback: TaskCallback,
) -> SearchResults:
...
```
---
## Error Handling
### PY-020: Specific Exceptions
**Severity:** ERROR
**Tools:** ruff (E722, B001)
NEVER use bare `except:`. 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
```
### PY-021: Use Custom Exceptions
**Severity:** WARNING
**Tools:** code review
Define domain-specific exceptions for better error handling.
```python
# Good - Custom exceptions
class TaskError(Exception):
"""Base exception for task operations."""
class TaskNotFoundError(TaskError):
"""Task does not exist."""
class TaskAlreadyClaimedError(TaskError):
"""Task is already claimed by another agent."""
# Usage
async def claim_task(task_id: str, agent_id: str) -> Task:
task = await get_task(task_id)
if task is None:
raise TaskNotFoundError(f"Task {task_id} not found")
if task.assigned_to and task.assigned_to != agent_id:
raise TaskAlreadyClaimedError(f"Task claimed by {task.assigned_to}")
...
```
### PY-022: Structured Logging
**Severity:** ERROR
**Tools:** ruff, code review
Use structlog with context for all logging. NEVER use print statements.
```python
# Good
import structlog
logger = structlog.get_logger(__name__)
logger.info(
"Task completed",
task_id=task.id,
duration_ms=elapsed,
agent_id=agent.id,
)
# Bad - Never use print
print(f"Task {task.id} completed in {elapsed}ms")
```
### PY-023: Re-raise with Context
**Severity:** WARNING
**Tools:** code review
When catching and re-raising, preserve the original exception chain.
```python
# Good - Preserve exception chain
try:
result = await external_api.call()
except ExternalAPIError as e:
raise ServiceError("External API failed") from e
# Bad - Loses original traceback
try:
result = await external_api.call()
except ExternalAPIError:
raise ServiceError("External API failed")
```
---
## Data Validation
### PY-030: Pydantic Models
**Severity:** ERROR
**Tools:** ruff, mypy
Use Pydantic for all API request/response models and configuration.
```python
from pydantic import BaseModel, Field, field_validator
class TaskRequest(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
priority: int = Field(default=1, ge=1, le=5)
tags: list[str] = Field(default_factory=list)
@field_validator('tags')
@classmethod
def validate_tags(cls, v: list[str]) -> list[str]:
return [tag.lower().strip() for tag in v]
```
### PY-031: Validate at Boundaries
**Severity:** ERROR
**Tools:** code review
Validate external input at system boundaries. Trust internal data.
```python
# API boundary - validate thoroughly
@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 task.title here
...
```
### PY-032: Use Enums for Finite Values
**Severity:** WARNING
**Tools:** ruff
Use Enums for status values, types, and other finite sets.
```python
from enum import StrEnum
class TaskStatus(StrEnum):
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
# Usage - type-safe comparisons
if task.status == TaskStatus.PENDING:
...
```
---
## Async Patterns
### PY-040: Async by Default
**Severity:** ERROR
**Tools:** code review
Use async functions for ALL I/O operations. All database and API calls MUST be async.
```python
# Good - async I/O
async def fetch_user(user_id: str) -> User:
return await db.users.get(user_id)
async def call_external_api(data: dict) -> Response:
async with httpx.AsyncClient() as client:
return await client.post(url, json=data)
# Bad - Blocking I/O
def fetch_user(user_id: str) -> User:
return db.users.get(user_id) # Blocking!
```
### PY-041: Use `asyncio.gather` for Concurrent Operations
**Severity:** WARNING
**Tools:** code review
Execute independent async operations concurrently.
```python
# Good - Concurrent execution
async def get_task_details(task_id: str) -> TaskDetails:
task, comments, history = await asyncio.gather(
get_task(task_id),
get_comments(task_id),
get_history(task_id),
)
return TaskDetails(task=task, comments=comments, history=history)
# Bad - Sequential when not needed
async def get_task_details(task_id: str) -> TaskDetails:
task = await get_task(task_id)
comments = await get_comments(task_id) # Waits unnecessarily
history = await get_history(task_id) # Waits unnecessarily
...
```
### PY-042: Proper Context Manager Usage
**Severity:** ERROR
**Tools:** ruff (ASYNC)
Use async context managers for resources that need cleanup.
```python
# Good - Proper cleanup
async with AsyncSession(engine) as session:
async with session.begin():
result = await session.execute(query)
# Good - httpx client
async with httpx.AsyncClient() as client:
response = await client.get(url)
```
### PY-043: Avoid Blocking in Async Code
**Severity:** ERROR
**Tools:** ruff (ASYNC), bandit
NEVER call blocking functions from async code.
```python
# Bad - Blocks event loop
async def process_file(path: Path) -> str:
return path.read_text() # Blocking!
# Good - Use async file I/O
import aiofiles
async def process_file(path: Path) -> str:
async with aiofiles.open(path) as f:
return await f.read()
# Good - Run blocking in thread pool
async def process_file(path: Path) -> str:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, path.read_text)
```
---
## Testing
### PY-050: Test Coverage
**Severity:** ERROR
**Tools:** pytest-cov
Maintain minimum 80% code coverage for all modules.
```bash
# Run with coverage
uv run pytest --cov=roboco --cov-report=term-missing
```
### PY-051: Async Tests
**Severity:** ERROR
**Tools:** pytest-asyncio
Use pytest-asyncio for testing async code.
```python
import pytest
@pytest.mark.asyncio
async def test_fetch_user() -> None:
user = await fetch_user("test-123")
assert user.name == "Test User"
```
### PY-052: Test Structure
**Severity:** WARNING
**Tools:** code review
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
```
### PY-053: Use Factories for Test Data
**Severity:** WARNING
**Tools:** code review
Use factory-boy for consistent test data generation.
```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))
```
---
## Dependencies
### PY-060: Use UV
**Severity:** ERROR
**Tools:** pyproject.toml
Use `uv` as the package manager. Lock dependencies in `pyproject.toml` and `uv.lock`.
```bash
# Add dependency
uv add package-name
# Add dev dependency
uv add --dev package-name
# Sync dependencies
uv sync
```
### PY-061: Pin Dependencies
**Severity:** WARNING
**Tools:** deptry
Keep `uv.lock` committed. Run `uv lock` when updating dependencies.
### PY-062: Audit Dependencies
**Severity:** ERROR
**Tools:** pip-audit, safety
Run security audits on dependencies regularly.
```bash
# Audit for vulnerabilities
uv run pip-audit
uv run safety scan
```
---
## Tooling Enforcement
All Python code MUST pass these automated checks before merge.
### Ruff (Linting & Formatting)
```bash
# Format code
uv run ruff format .
# Check linting
uv run ruff check .
# Auto-fix issues
uv run ruff check --fix .
```
**Enabled Rule Sets:**
| Rule | Description |
|------|-------------|
| `E`, `W` | pycodestyle (PEP 8) |
| `F` | Pyflakes (errors) |
| `I` | isort (imports) |
| `B` | flake8-bugbear (common bugs) |
| `C4` | flake8-comprehensions |
| `UP` | pyupgrade (Python upgrades) |
| `ARG` | unused arguments |
| `SIM` | simplification |
| `TCH` | type checking |
| `PTH` | pathlib usage |
| `PL` | Pylint |
| `RUF` | Ruff-specific |
### Mypy (Type Checking)
```bash
uv run mypy roboco/
```
**Configuration (pyproject.toml):**
```toml
[tool.mypy]
python_version = "3.13"
strict = true
plugins = ["pydantic.mypy"]
```
### Vulture (Dead Code)
```bash
uv run vulture roboco/ vulture_whitelist.py
```
Finds unused code. Add false positives to `vulture_whitelist.py`.
### Bandit (Security)
```bash
uv run bandit -r roboco/ -ll
```
Scans for security issues. Severity threshold: medium.
### Radon (Complexity)
```bash
# Cyclomatic complexity
uv run radon cc roboco/ -nc
# Maintainability index
uv run radon mi roboco/ -nc
```
### Xenon (Complexity Thresholds)
```bash
uv run xenon roboco/ --max-absolute B --max-modules A --max-average A
```
**Thresholds:**
| Metric | Maximum | Grade |
|--------|---------|-------|
| Absolute complexity | B | 6-10 |
| Module complexity | A | 1-5 |
| Average complexity | A | 1-5 |
### Deptry (Dependency Analysis)
```bash
uv run deptry .
```
Finds unused, missing, and misplaced dependencies.
### Semgrep (Static Analysis)
```bash
uv run semgrep --config=auto roboco/
```
Advanced pattern-based static analysis.
---
## Code Complexity
### PY-070: Maximum Cyclomatic Complexity
**Severity:** ERROR
**Tools:** radon, xenon
Functions MUST have cyclomatic complexity <= 10 (grade B or better).
```python
# Bad - Too complex (CC > 10)
def process_request(request: Request) -> Response:
if request.type == "A":
if request.priority == 1:
if request.urgent:
... # Deep nesting = high complexity
elif request.type == "B":
...
# Good - Decomposed into smaller functions
def process_request(request: Request) -> Response:
handler = get_handler(request.type)
return handler.process(request)
```
### PY-071: Maximum Function Length
**Severity:** WARNING
**Tools:** code review
Functions SHOULD be <= 50 lines. Consider decomposition if longer.
### PY-072: Maximum Arguments
**Severity:** WARNING
**Tools:** ruff (PLR0913)
Functions SHOULD have <= 5 arguments. Use dataclasses or Pydantic models for more.
```python
# Bad - Too many arguments
def create_task(
title: str,
description: str,
priority: int,
due_date: datetime,
assigned_to: str,
tags: list[str],
parent_id: str | None,
) -> Task:
...
# Good - Use a model
class TaskCreate(BaseModel):
title: str
description: str
priority: int = 1
due_date: datetime | None = None
assigned_to: str | None = None
tags: list[str] = []
parent_id: str | None = None
def create_task(params: TaskCreate) -> Task:
...
```
### PY-073: Avoid Deep Nesting
**Severity:** WARNING
**Tools:** code review
Maximum nesting depth SHOULD be 4 levels. Use early returns and guard clauses.
```python
# Bad - Deep nesting
def process(data: Data) -> Result:
if data.valid:
if data.type == "A":
if data.ready:
if data.value > 0:
return process_a(data)
return None
# Good - Guard clauses
def process(data: Data) -> Result | None:
if not data.valid:
return None
if data.type != "A":
return None
if not data.ready:
return None
if data.value <= 0:
return None
return process_a(data)
```
---
## Security
### PY-080: No Hardcoded Secrets
**Severity:** BLOCKER
**Tools:** bandit (B105, B106, B107)
NEVER hardcode secrets. Use environment variables.
```python
# Bad - NEVER do this
API_KEY = "sk-abc123xyz789"
DATABASE_URL = "postgresql://user:password@host/db"
# Good - Load from environment
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
api_key: str
database_url: str
model_config = {"env_prefix": "ROBOCO_"}
```
### PY-081: Use `usedforsecurity` for Non-Security Hashes
**Severity:** WARNING
**Tools:** bandit (B324)
When using hash functions for non-security purposes, add `usedforsecurity=False`.
```python
# Good - Non-security hash for ID generation
import hashlib
content_hash = hashlib.md5(
content.encode(),
usedforsecurity=False
).hexdigest()[:12]
```
### PY-082: SQL Injection Prevention
**Severity:** BLOCKER
**Tools:** bandit (B608), semgrep
NEVER construct SQL with string concatenation. Use parameterized queries.
```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)
)
```
### PY-083: Command Injection Prevention
**Severity:** BLOCKER
**Tools:** bandit (B602, B603, B604)
NEVER pass user input directly to shell commands.
```python
# Bad - Command injection vulnerability
import os
os.system(f"process_file {filename}")
# Good - Use subprocess with list
import subprocess
if not SAFE_FILENAME_PATTERN.match(filename):
raise ValidationError("Invalid filename")
subprocess.run(["process_file", filename], check=True)
```
### PY-084: No `eval` or `exec`
**Severity:** BLOCKER
**Tools:** bandit (B307)
NEVER use `eval()` or `exec()` on untrusted input.
```python
# Bad - Code injection vulnerability
result = eval(user_input)
# Good - Use ast.literal_eval for safe parsing
import ast
result = ast.literal_eval(user_input) # Only parses literals
```
---
## Quick Reference
### Before Committing
```bash
# Format
uv run ruff format .
# Lint
uv run ruff check .
# Type check
uv run mypy roboco/
# Dead code
uv run vulture roboco/ vulture_whitelist.py
# Full check (recommended)
make lint
```
### Severity Levels
| Level | Action | Blocks PR |
|-------|--------|-----------|
| BLOCKER | Must fix immediately | Yes |
| ERROR | Must fix before merge | Yes |
| WARNING | Should fix | No |
| INFO | Consider improving | No |
### Rule ID Reference
| Prefix | Category |
|--------|----------|
| PY-00X | Code style |
| PY-01X | Type safety |
| PY-02X | Error handling |
| PY-03X | Data validation |
| PY-04X | Async patterns |
| PY-05X | Testing |
| PY-06X | Dependencies |
| PY-07X | Complexity |
| PY-08X | Security |
+985
View File
@@ -0,0 +1,985 @@
# TypeScript Coding Standards
Comprehensive standards for TypeScript/React development in the RoboCo system.
---
## Table of Contents
1. [Code Style](#code-style)
2. [Type Safety](#type-safety)
3. [React Patterns](#react-patterns)
4. [State Management](#state-management)
5. [Error Handling](#error-handling)
6. [Testing](#testing)
7. [Build & Tools](#build--tools)
8. [Performance](#performance)
9. [Security](#security)
---
## Code Style
### TS-001: Strict Mode
**Severity:** ERROR
**Tools:** TypeScript compiler
All TypeScript files MUST use strict mode. No `any` types unless absolutely necessary.
```typescript
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true
}
}
```
### TS-002: Explicit Return Types
**Severity:** ERROR
**Tools:** ESLint (@typescript-eslint/explicit-function-return-type)
Functions MUST have explicit return type annotations.
```typescript
// Good
function processTask(taskId: string): Promise<TaskResult> {
// ...
}
async function fetchUser(userId: string): Promise<User | null> {
// ...
}
// Bad - Inferred return type
function processTask(taskId: string) {
// ...
}
```
### TS-003: Interface Over Type
**Severity:** WARNING
**Tools:** ESLint
Prefer `interface` for object shapes, `type` for unions/intersections.
```typescript
// Good - Object shape with interface
interface User {
id: string;
name: string;
email: string;
}
// Good - Interface for extending
interface AdminUser extends User {
permissions: Permission[];
}
// Good - Union type
type TaskStatus = 'pending' | 'in_progress' | 'completed';
// Good - Intersection type
type WithTimestamps<T> = T & {
createdAt: Date;
updatedAt: Date;
};
```
### TS-004: Naming Conventions
**Severity:** ERROR
**Tools:** ESLint
| Type | Convention | Example |
|------|------------|---------|
| Interfaces | PascalCase | `User`, `TaskService` |
| Types | PascalCase | `TaskStatus`, `ApiResponse` |
| Classes | PascalCase | `TaskManager`, `UserStore` |
| Functions | camelCase | `fetchUser`, `processTask` |
| Variables | camelCase | `userId`, `taskCount` |
| Constants | SCREAMING_SNAKE | `MAX_RETRIES`, `API_BASE_URL` |
| Enums | PascalCase | `TaskStatus`, `UserRole` |
| Components | PascalCase | `TaskCard`, `UserProfile` |
| Hooks | camelCase with use prefix | `useTask`, `useAuth` |
### TS-005: Import Organization
**Severity:** WARNING
**Tools:** ESLint (import/order)
Organize imports in this order: external, internal, relative.
```typescript
// External dependencies
import React, { useState, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
// Internal aliases (configured in tsconfig)
import { TaskService } from '@/services/task';
import { Button } from '@/components/ui';
// Relative imports
import { TaskCard } from './TaskCard';
import type { TaskProps } from './types';
```
---
## Type Safety
### TS-010: No `any` Types
**Severity:** ERROR
**Tools:** ESLint (@typescript-eslint/no-explicit-any)
NEVER use `any`. Use `unknown`, generics, or proper types.
```typescript
// Bad
function processData(data: any): any {
return data.value;
}
// Good - Use unknown and narrow
function processData(data: unknown): string {
if (typeof data === 'object' && data !== null && 'value' in data) {
return String(data.value);
}
throw new Error('Invalid data format');
}
// Good - Use generics
function processData<T extends { value: string }>(data: T): string {
return data.value;
}
```
### TS-011: Use Type Guards
**Severity:** WARNING
**Tools:** Code review
Create type guards for runtime type checking.
```typescript
// Type guard function
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
typeof (value as User).id === 'string'
);
}
// Usage
function processResponse(data: unknown): void {
if (isUser(data)) {
console.log(data.name); // TypeScript knows data is User
}
}
```
### TS-012: Discriminated Unions
**Severity:** WARNING
**Tools:** Code review
Use discriminated unions for state variants.
```typescript
// Good - Discriminated union for API states
type ApiState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
// Usage with exhaustiveness checking
function renderState<T>(state: ApiState<T>): React.ReactNode {
switch (state.status) {
case 'idle':
return null;
case 'loading':
return <Spinner />;
case 'success':
return <DataView data={state.data} />;
case 'error':
return <ErrorMessage error={state.error} />;
default:
// Exhaustiveness check
const _exhaustive: never = state;
return _exhaustive;
}
}
```
### TS-013: Const Assertions
**Severity:** WARNING
**Tools:** Code review
Use `as const` for immutable literal types.
```typescript
// Good - Const assertion for immutable config
const ROUTES = {
home: '/',
tasks: '/tasks',
settings: '/settings',
} as const;
type Route = typeof ROUTES[keyof typeof ROUTES];
// Type: '/' | '/tasks' | '/settings'
// Good - Const assertion for tuples
const tuple = [1, 'hello'] as const;
// Type: readonly [1, 'hello']
```
### TS-014: Avoid Type Assertions
**Severity:** WARNING
**Tools:** ESLint
Avoid type assertions (`as`). Use type guards or proper typing instead.
```typescript
// Bad - Type assertion
const user = response.data as User;
// Good - Validate with type guard
function isUser(data: unknown): data is User {
// ... validation
}
const user = isUser(response.data) ? response.data : null;
// Good - Zod schema validation
import { z } from 'zod';
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
});
const user = UserSchema.parse(response.data);
```
---
## React Patterns
### TS-020: Functional Components
**Severity:** ERROR
**Tools:** ESLint
Use functional components with hooks. No class components.
```typescript
// Good - Functional component with typed props
interface TaskCardProps {
task: Task;
onComplete: (taskId: string) => void;
className?: string;
}
const TaskCard: React.FC<TaskCardProps> = ({ task, onComplete, className }) => {
const [isLoading, setIsLoading] = useState(false);
const handleComplete = async (): Promise<void> => {
setIsLoading(true);
await completeTask(task.id);
onComplete(task.id);
setIsLoading(false);
};
return (
<div className={className}>
<h3>{task.title}</h3>
<Button onClick={handleComplete} disabled={isLoading}>
Complete
</Button>
</div>
);
};
```
### TS-021: Custom Hooks
**Severity:** WARNING
**Tools:** Code review
Extract reusable logic into custom hooks with `use` prefix.
```typescript
// Good - Custom hook with proper types
interface UseTaskResult {
task: Task | null;
isLoading: boolean;
error: Error | null;
refetch: () => Promise<void>;
}
function useTask(taskId: string): UseTaskResult {
const [task, setTask] = useState<Task | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchTask = useCallback(async (): Promise<void> => {
setIsLoading(true);
setError(null);
try {
const data = await taskService.get(taskId);
setTask(data);
} catch (err) {
setError(err instanceof Error ? err : new Error('Unknown error'));
} finally {
setIsLoading(false);
}
}, [taskId]);
useEffect(() => {
void fetchTask();
}, [fetchTask]);
return { task, isLoading, error, refetch: fetchTask };
}
```
### TS-022: Memoization
**Severity:** WARNING
**Tools:** ESLint (react-hooks/exhaustive-deps)
Use `useMemo` and `useCallback` appropriately.
```typescript
// Good - Memoize expensive computation
const sortedTasks = useMemo(() => {
return tasks.slice().sort((a, b) => b.priority - a.priority);
}, [tasks]);
// Good - Memoize callback for child components
const handleSelect = useCallback((taskId: string) => {
setSelectedId(taskId);
onTaskSelect?.(taskId);
}, [onTaskSelect]);
// Bad - Over-memoization (simple computation)
const fullName = useMemo(() => `${first} ${last}`, [first, last]);
// Just use: const fullName = `${first} ${last}`;
```
### TS-023: Event Handlers
**Severity:** WARNING
**Tools:** Code review
Type event handlers properly.
```typescript
// Good - Properly typed event handlers
const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
// ...
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
setValue(event.target.value);
};
const handleClick = (event: React.MouseEvent<HTMLButtonElement>): void => {
event.stopPropagation();
// ...
};
```
### TS-024: Children Props
**Severity:** WARNING
**Tools:** Code review
Use `React.ReactNode` for children props.
```typescript
// Good - Typed children
interface LayoutProps {
children: React.ReactNode;
sidebar?: React.ReactNode;
}
const Layout: React.FC<LayoutProps> = ({ children, sidebar }) => (
<div className="layout">
{sidebar && <aside>{sidebar}</aside>}
<main>{children}</main>
</div>
);
```
---
## State Management
### TS-030: Local State First
**Severity:** WARNING
**Tools:** Code review
Prefer local component state. Only lift state when necessary.
```typescript
// Good - Local state for component-specific data
function TaskEditor(): React.ReactElement {
const [draft, setDraft] = useState('');
const [isValid, setIsValid] = useState(false);
// This state doesn't need to be global
return <textarea value={draft} onChange={(e) => setDraft(e.target.value)} />;
}
```
### TS-031: Server State with React Query
**Severity:** ERROR
**Tools:** Code review
Use React Query (TanStack Query) for server state management.
```typescript
// Good - React Query for API data
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function TaskList(): React.ReactElement {
const queryClient = useQueryClient();
const { data: tasks, isLoading, error } = useQuery({
queryKey: ['tasks'],
queryFn: () => taskService.list(),
});
const createTask = useMutation({
mutationFn: taskService.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
},
});
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return (
<ul>
{tasks.map((task) => (
<TaskItem key={task.id} task={task} />
))}
</ul>
);
}
```
### TS-032: Zustand for Client State
**Severity:** WARNING
**Tools:** Code review
Use Zustand for global client state when needed.
```typescript
// Good - Zustand store with proper types
import { create } from 'zustand';
interface UIStore {
sidebarOpen: boolean;
theme: 'light' | 'dark';
toggleSidebar: () => void;
setTheme: (theme: 'light' | 'dark') => void;
}
const useUIStore = create<UIStore>((set) => ({
sidebarOpen: true,
theme: 'light',
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
setTheme: (theme) => set({ theme }),
}));
```
---
## Error Handling
### TS-040: Error Boundaries
**Severity:** ERROR
**Tools:** Code review
Wrap major UI sections in error boundaries.
```typescript
// Error boundary component
interface ErrorBoundaryProps {
children: React.ReactNode;
fallback: React.ReactNode;
}
class ErrorBoundary extends React.Component<
ErrorBoundaryProps,
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError(): { hasError: boolean } {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo): void {
console.error('Error boundary caught:', error, info);
}
render(): React.ReactNode {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
// Usage
<ErrorBoundary fallback={<ErrorFallback />}>
<TaskDashboard />
</ErrorBoundary>
```
### TS-041: Type-Safe Error Handling
**Severity:** WARNING
**Tools:** Code review
Use discriminated unions for error states.
```typescript
// Result type for operations that can fail
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
// Usage
async function fetchTask(id: string): Promise<Result<Task>> {
try {
const task = await api.get(`/tasks/${id}`);
return { success: true, data: task };
} catch (err) {
return {
success: false,
error: err instanceof Error ? err : new Error('Unknown error'),
};
}
}
// Consumer
const result = await fetchTask(id);
if (result.success) {
console.log(result.data.title);
} else {
console.error(result.error.message);
}
```
### TS-042: Catch Block Typing
**Severity:** ERROR
**Tools:** TypeScript (useUnknownInCatchVariables)
Handle `unknown` type in catch blocks.
```typescript
// Good - Handle unknown error type
try {
await riskyOperation();
} catch (error: unknown) {
if (error instanceof ApiError) {
handleApiError(error);
} else if (error instanceof Error) {
handleGenericError(error);
} else {
handleUnknownError(String(error));
}
}
```
---
## Testing
### TS-050: Testing Library
**Severity:** ERROR
**Tools:** Jest, Testing Library
Use React Testing Library. Test behavior, not implementation.
```typescript
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
describe('TaskCard', () => {
it('shows loading state while completing task', async () => {
const user = userEvent.setup();
const onComplete = vi.fn();
render(<TaskCard task={mockTask} onComplete={onComplete} />);
await user.click(screen.getByRole('button', { name: /complete/i }));
expect(screen.getByRole('button')).toBeDisabled();
await waitFor(() => {
expect(onComplete).toHaveBeenCalledWith(mockTask.id);
});
});
it('displays error message when task fails to load', async () => {
render(<TaskView taskId="invalid" />);
expect(await screen.findByRole('alert')).toHaveTextContent(/failed/i);
});
});
```
### TS-051: Mock External Dependencies
**Severity:** WARNING
**Tools:** Jest/Vitest
Mock API calls and external services.
```typescript
import { vi } from 'vitest';
import { taskService } from '@/services/task';
vi.mock('@/services/task');
describe('useTask', () => {
it('returns task data on success', async () => {
vi.mocked(taskService.get).mockResolvedValue(mockTask);
const { result } = renderHook(() => useTask('task-123'));
await waitFor(() => {
expect(result.current.task).toEqual(mockTask);
expect(result.current.isLoading).toBe(false);
});
});
});
```
### TS-052: Test Coverage
**Severity:** WARNING
**Tools:** c8/istanbul
Maintain minimum 80% test coverage.
```bash
# Run tests with coverage
pnpm test:coverage
```
---
## Build & Tools
### TS-060: Use PNPM
**Severity:** ERROR
**Tools:** pnpm
Use `pnpm` as the package manager.
```bash
# Install dependencies
pnpm install
# Add dependency
pnpm add package-name
# Add dev dependency
pnpm add -D package-name
```
### TS-061: ESLint Configuration
**Severity:** ERROR
**Tools:** ESLint
Use ESLint with TypeScript parser.
```javascript
// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
{
plugins: {
react: reactPlugin,
'react-hooks': reactHooksPlugin,
},
rules: {
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/strict-boolean-expressions': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
}
);
```
### TS-062: Prettier Configuration
**Severity:** WARNING
**Tools:** Prettier
Use Prettier for consistent formatting.
```json
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true
}
```
### TS-063: TypeScript Configuration
**Severity:** ERROR
**Tools:** TypeScript
Use strict TypeScript configuration.
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"jsx": "react-jsx",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"exclude": ["node_modules"]
}
```
---
## Performance
### TS-070: Lazy Loading
**Severity:** WARNING
**Tools:** React, Webpack/Vite
Use lazy loading for code splitting.
```typescript
import { lazy, Suspense } from 'react';
// Lazy load route components
const TaskDashboard = lazy(() => import('./pages/TaskDashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App(): React.ReactElement {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/tasks" element={<TaskDashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
```
### TS-071: Virtual Lists
**Severity:** WARNING
**Tools:** react-virtual, react-window
Use virtualization for long lists.
```typescript
import { useVirtualizer } from '@tanstack/react-virtual';
function TaskList({ tasks }: { tasks: Task[] }): React.ReactElement {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: tasks.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} className="overflow-auto h-screen">
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map((item) => (
<TaskItem key={tasks[item.index].id} task={tasks[item.index]} />
))}
</div>
</div>
);
}
```
---
## Security
### TS-080: XSS Prevention
**Severity:** BLOCKER
**Tools:** ESLint (react/no-danger)
NEVER use `dangerouslySetInnerHTML` with user input.
```typescript
// Bad - XSS vulnerability
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// Good - Use a sanitization library if HTML is required
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />
// Good - Prefer text content
<div>{userContent}</div>
```
### TS-081: No `eval`
**Severity:** BLOCKER
**Tools:** ESLint (no-eval)
NEVER use `eval()` or `Function()` constructor.
```typescript
// Bad - Code injection vulnerability
eval(userInput);
new Function(userInput)();
// Good - Use proper parsing
JSON.parse(userInput);
```
### TS-082: Secure HTTP Calls
**Severity:** ERROR
**Tools:** Code review
Always use HTTPS for API calls.
```typescript
// Good - Secure configuration
const api = axios.create({
baseURL: process.env.API_URL, // Should be https://
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
});
```
### TS-083: Environment Variables
**Severity:** ERROR
**Tools:** Code review
Never expose secrets in frontend code.
```typescript
// Good - Only public env vars exposed
const config = {
apiUrl: import.meta.env.VITE_API_URL,
publicKey: import.meta.env.VITE_PUBLIC_KEY,
};
// Bad - Never do this (even though it won't work)
const secret = import.meta.env.VITE_SECRET_KEY; // Exposed in browser!
```
---
## Quick Reference
### Before Committing
```bash
# Format
pnpm format
# Lint
pnpm lint
# Type check
pnpm typecheck
# Test
pnpm test
# All checks
pnpm check
```
### Severity Levels
| Level | Action | Blocks PR |
|-------|--------|-----------|
| BLOCKER | Must fix immediately | Yes |
| ERROR | Must fix before merge | Yes |
| WARNING | Should fix | No |
| INFO | Consider improving | No |
### Rule ID Reference
| Prefix | Category |
|--------|----------|
| TS-00X | Code style |
| TS-01X | Type safety |
| TS-02X | React patterns |
| TS-03X | State management |
| TS-04X | Error handling |
| TS-05X | Testing |
| TS-06X | Build & tools |
| TS-07X | Performance |
| TS-08X | Security |
+172
View File
@@ -0,0 +1,172 @@
# Security Standards (OWASP Top 10)
Security standards based on OWASP Top 10 for the RoboCo system.
## SEC-001: Injection Prevention
### SQL Injection
NEVER construct SQL queries with string concatenation.
```python
# NEVER DO THIS
query = f"SELECT * FROM users WHERE id = '{user_id}'"
# ALWAYS USE parameterized queries
result = await db.execute(
"SELECT * FROM users WHERE id = :id",
{"id": user_id}
)
```
### Command Injection
NEVER pass user input directly to shell commands.
```python
# NEVER DO THIS
os.system(f"process_file {filename}")
# ALWAYS validate and sanitize
if not SAFE_FILENAME_PATTERN.match(filename):
raise ValidationError("Invalid filename")
subprocess.run(["process_file", filename], check=True)
```
## SEC-002: Authentication
### Password Storage
NEVER store passwords in plain text. Use bcrypt or argon2.
### Token Security
- JWT tokens MUST have short expiration (15-60 minutes)
- Refresh tokens MUST be stored securely (httpOnly cookies)
- Always validate token signatures
### Session Management
- Generate new session IDs after login
- Implement session timeout
- Invalidate sessions on logout
## SEC-003: Sensitive Data Exposure
### Environment Variables
Store secrets in environment variables, NEVER in code.
```python
# NEVER DO THIS
API_KEY = "sk-abc123xyz789"
# ALWAYS load from environment
API_KEY = os.getenv("API_KEY")
if not API_KEY:
raise ConfigurationError("API_KEY not set")
```
### Logging
NEVER log sensitive data (passwords, tokens, PII).
```python
# NEVER log credentials
logger.info(f"User login: {username}, password: {password}")
# Log safely
logger.info("User login", username=username, masked_password="***")
```
## SEC-004: Access Control
### Authorization Checks
Verify permissions on EVERY request, not just at entry points.
```python
async def update_task(task_id: str, agent: Agent):
task = await get_task(task_id)
# ALWAYS check permissions
if not await can_modify_task(agent, task):
raise PermissionDenied("Cannot modify this task")
# Proceed with update
```
### Principle of Least Privilege
Agents should have minimum permissions needed for their role.
## SEC-005: Security Misconfiguration
### Headers
Set security headers on all responses:
- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY`
- `Content-Security-Policy`
- `Strict-Transport-Security`
### CORS
Configure CORS strictly. Never use `*` in production.
```python
# Development only
origins = ["http://localhost:3000"]
# Production
origins = ["https://app.roboco.ai"]
```
## SEC-006: Cross-Site Scripting (XSS)
### Output Encoding
Always encode user input before rendering in HTML.
### Content Security Policy
Implement strict CSP to prevent inline scripts.
### React/JSX
Never use `dangerouslySetInnerHTML` with user input.
## SEC-007: Insecure Deserialization
### Pickle/Eval
NEVER use `pickle.loads()` or `eval()` on untrusted data.
### JSON Validation
Always validate JSON structure with Pydantic before processing.
## SEC-008: Vulnerable Dependencies
### Dependency Scanning
Run `pip-audit` and `npm audit` in CI pipeline.
### Updates
Keep dependencies updated. Review security advisories weekly.
## SEC-009: Logging & Monitoring
### Audit Trail
Log all security-relevant events:
- Authentication attempts
- Authorization failures
- Data access
- Configuration changes
### Alerting
Set up alerts for:
- Multiple failed login attempts
- Permission denied spikes
- Unusual access patterns
## SEC-010: API Security
### Rate Limiting
Implement rate limiting on all endpoints.
### Input Validation
Validate all input parameters:
- Type checking
- Length limits
- Format validation
- Range checks
```python
class TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
priority: int = Field(..., ge=1, le=5)
```
+444
View File
@@ -0,0 +1,444 @@
# Agent Roles and Permissions
Comprehensive reference for agent roles, permissions, and organizational structure in the RoboCo system. Derived from actual implementation in the codebase.
**Source Files:**
- Role Definitions: `roboco/models/base.py` (lines 56-78)
- Agent Config: `roboco/agents_config.py`
- Permissions Model: `roboco/models/permissions.py`
---
## Table of Contents
1. [Agent Roles](#agent-roles)
2. [Organizational Structure](#organizational-structure)
3. [Agent Roster](#agent-roster)
4. [Permission Levels](#permission-levels)
5. [Task Permissions](#task-permissions)
6. [Knowledge Base Permissions](#knowledge-base-permissions)
7. [Communication Permissions](#communication-permissions)
8. [Role Capabilities](#role-capabilities)
---
## Agent Roles
### ROLE-001: AgentRole Enum
**Source:** `roboco/models/base.py`
```python
class AgentRole(str, Enum):
# System (internal orchestrator operations)
SYSTEM = "system"
# Executive
CEO = "ceo"
# Board
PRODUCT_OWNER = "product_owner"
HEAD_MARKETING = "head_marketing"
AUDITOR = "auditor"
# Management
MAIN_PM = "main_pm"
CELL_PM = "cell_pm"
# Cell Members
DEVELOPER = "developer"
QA = "qa"
DOCUMENTER = "documenter"
```
### ROLE-002: Role Descriptions
| Role | Description | Count |
|------|-------------|-------|
| `ceo` | Human executive, final authority | 1 |
| `product_owner` | Product strategy and direction | 1 |
| `head_marketing` | Marketing and external comms | 1 |
| `auditor` | Silent observer, quality oversight | 1 |
| `main_pm` | Coordinates all cells | 1 |
| `cell_pm` | Manages a single cell | 3 |
| `developer` | Writes code | 5 |
| `qa` | Reviews and tests | 3 |
| `documenter` | Writes documentation | 3 |
| **Total** | | **19** |
---
## Organizational Structure
### ROLE-010: Hierarchy
```
CEO (Renzo - Human)
┌──────────────────────┼──────────────────────┐
│ │ │
Product Owner Head of Marketing Auditor
(Board) (Board) (Silent Observer)
│ │ │
└──────────────────────┼──────────────────────┘
Main PM
┌──────────────────────┼──────────────────────┐
│ │ │
Backend Cell Frontend Cell UX/UI Cell
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
PM DEV*2 QA PM DEV*2 QA PM DEV QA
DOC DOC DOC
```
### ROLE-011: Cells
| Cell | PM | Developers | QA | Documenter |
|------|-----|------------|-----|------------|
| Backend | be-pm | be-dev-1, be-dev-2 | be-qa | be-doc |
| Frontend | fe-pm | fe-dev-1, fe-dev-2 | fe-qa | fe-doc |
| UX/UI | ux-pm | ux-dev | ux-qa | ux-doc |
---
## Agent Roster
### ROLE-020: Complete Agent List
**Source:** `roboco/agents_config.py`
| Slug | Role | Cell | Team |
|------|------|------|------|
| `ceo` | ceo | - | executive |
| `product-owner` | product_owner | - | board |
| `head-marketing` | head_marketing | - | board |
| `auditor` | auditor | - | board |
| `main-pm` | main_pm | - | management |
| `be-pm` | cell_pm | backend | management |
| `fe-pm` | cell_pm | frontend | management |
| `ux-pm` | cell_pm | uxui | management |
| `be-dev-1` | developer | backend | developers |
| `be-dev-2` | developer | backend | developers |
| `fe-dev-1` | developer | frontend | developers |
| `fe-dev-2` | developer | frontend | developers |
| `ux-dev` | developer | uxui | developers |
| `be-qa` | qa | backend | qa |
| `fe-qa` | qa | frontend | qa |
| `ux-qa` | qa | uxui | qa |
| `be-doc` | documenter | backend | documentation |
| `fe-doc` | documenter | frontend | documentation |
| `ux-doc` | documenter | uxui | documentation |
---
## Permission Levels
### ROLE-030: Permission Hierarchy
**Source:** `roboco/models/permissions.py`
```python
ROLE_PERMISSION_LEVELS: dict[str, str] = {
"system": "CEO", # System/orchestrator - CEO-level access
"ceo": "CEO", # Full access
"product_owner": "BOARD", # Cross-org access
"head_marketing": "BOARD", # Cross-org access
"auditor": "AUDITOR", # Special: silent read all
"main_pm": "MAIN_PM", # All cells access
"cell_pm": "CELL_PM", # Own cell + PM channel
"developer": "CELL_MEMBER", # Own cell only
"qa": "CELL_MEMBER", # Own cell only
"documenter": "CELL_MEMBER", # Own cell only
}
```
### ROLE-031: Level Descriptions
| Level | Description | Scope |
|-------|-------------|-------|
| `CEO` | Full access to everything | Organization-wide |
| `BOARD` | Cross-organization access | Cross-cell |
| `AUDITOR` | Silent read access to all | Read-only, all channels |
| `MAIN_PM` | All cells access | All cells |
| `CELL_PM` | Own cell + PM channel | Single cell + PM |
| `CELL_MEMBER` | Own cell only | Single cell |
---
## Task Permissions
### ROLE-040: Task Permission Matrix
**Source:** `roboco/models/permissions.py`
| Role | VIEW_ALL | VIEW_OWN | CREATE | ASSIGN | CLAIM | UPDATE_OWN | CLOSE | CHANGE_PRIORITY |
|------|:--------:|:--------:|:------:|:------:|:-----:|:----------:|:-----:|:---------------:|
| system | X | | X | X | X | X | X | X |
| ceo | X | | X | X | | | X | X |
| product_owner | X | | X | X | | | X | X |
| head_marketing | X | | X | X | | | X | X |
| auditor | X | | X | X | | | X | X |
| main_pm | X | | X | X | X | X | X | X |
| cell_pm | | X | X | X | X | X | X | X |
| developer | | X | | | X | X | X | |
| qa | | X | | | X | X | | |
| documenter | | X | | | X | X | X | |
### ROLE-041: Key Task Capabilities
**Who can CREATE tasks:**
- ceo, product_owner, head_marketing, auditor
- main_pm, cell_pm
**Who can ASSIGN tasks:**
- ceo, product_owner, head_marketing, auditor
- main_pm, cell_pm
**Who can CLAIM tasks:**
- main_pm, cell_pm
- developer, qa, documenter (role-appropriate statuses)
**Who can CLOSE (complete) tasks:**
- ceo, product_owner, head_marketing, auditor
- main_pm, cell_pm
- developer, documenter (their own tasks)
**Who can CANCEL tasks:**
- cell_pm, main_pm, product_owner, head_marketing
- NOT ceo (by design)
- NOT auditor
---
## Knowledge Base Permissions
### ROLE-050: KB Permission Matrix
**Source:** `roboco/models/permissions.py`
| Role | INDEX_CODE | INDEX_DOCS | SEARCH | QUERY | VIEW_STATS | CLEAR | REFRESH |
|------|:----------:|:----------:|:------:|:-----:|:----------:|:-----:|:-------:|
| ceo | X | X | X | X | X | X | X |
| product_owner | | X | X | X | X | | |
| head_marketing | | X | X | X | | | |
| auditor | | | X | X | X | | |
| main_pm | X | X | X | X | X | X | X |
| cell_pm | X | X | X | X | X | | |
| developer | X | X | X | X | | | |
| qa | | | X | X | | | |
| documenter | | X | X | X | | | |
### ROLE-051: KB Capability Summary
**Who can INDEX_CODE:**
- ceo, main_pm, cell_pm, developer
**Who can INDEX_DOCS:**
- ceo, product_owner, head_marketing
- main_pm, cell_pm
- developer, documenter
**Who can SEARCH/QUERY:**
- Everyone
**Who can CLEAR_INDEX/REFRESH:**
- ceo, main_pm only
---
## Communication Permissions
### ROLE-060: Notification Permissions
**Who CAN send notifications:**
- cell_pm
- main_pm
- product_owner
- head_marketing
- auditor
- ceo
**Who CANNOT send notifications:**
- developer
- qa
- documenter
### ROLE-061: Channel Access
**Cell Channels** (e.g., `#backend-cell`):
- Read: Cell members + Main PM
- Write: Cell members
- Silent: Auditor
**Cross-Cell Channels** (e.g., `#dev-all`, `#qa-all`):
- Read/Write: Respective role members + Cell PMs + Main PM
- Silent: Auditor
**Management Channels** (e.g., `#main-pm-board`):
- Read/Write: Board + Main PM
- Silent: Auditor
**Broadcast Channels** (e.g., `#announcements`):
- Read: Everyone
- Write: PMs and Board only
### ROLE-062: Communication Matrix
Each role can communicate with:
| Role | Can Communicate With |
|------|---------------------|
| CEO | Everyone |
| Board Members | CEO, other board, Auditor, Main PM |
| Auditor | Everyone (silent read all channels) |
| Main PM | CEO, Board, Cell PMs |
| Cell PM | CEO, Auditor, Main PM, other Cell PMs, cell members |
| Cell Members | CEO, Auditor, own Cell PM, other cell members |
---
## Role Capabilities
### ROLE-070: Developer Capabilities
```markdown
CAN:
- Claim pending and needs_revision tasks
- Start, pause, resume work
- Submit for verification and QA
- Block tasks (with dependency)
- Search and query knowledge base
- Index code and documentation
- Journal their work
CANNOT:
- Create or assign tasks
- Pass/fail QA
- Complete tasks
- Cancel tasks
- Send notifications
- Clear/refresh KB indexes
```
### ROLE-071: QA Capabilities
```markdown
CAN:
- Claim awaiting_qa tasks
- Pass or fail QA
- Block tasks
- Search and query knowledge base
- Journal their work
CANNOT:
- Claim pending tasks
- Create or assign tasks
- Index content
- Complete documentation
- Complete tasks
- Cancel tasks
- Send notifications
```
### ROLE-072: Documenter Capabilities
```markdown
CAN:
- Claim awaiting_documentation tasks
- Complete documentation
- Index documentation
- Search and query knowledge base
- Journal their work
CANNOT:
- Claim pending tasks
- Create or assign tasks
- Index code
- Pass/fail QA
- Cancel tasks
- Send notifications
```
### ROLE-073: Cell PM Capabilities
```markdown
CAN:
- Create tasks in backlog
- Activate backlog → pending
- Assign tasks to cell members
- Complete awaiting_pm_review tasks
- Cancel any task (in cell)
- Unblock blocked tasks
- Send notifications
- Index code and documentation
- Full KB access (except clear/refresh)
CANNOT:
- Access other cells' tasks (unless Main PM)
- Clear/refresh KB indexes
```
### ROLE-074: Main PM Capabilities
```markdown
CAN:
- Everything Cell PM can do
- Access ALL cells
- Clear and refresh KB indexes
- Coordinate cross-cell work
```
### ROLE-075: Auditor Capabilities
```markdown
CAN:
- View all tasks
- View all channels (silent)
- Search and query knowledge base
- View KB stats
- Create tasks
- Assign tasks
CANNOT:
- Claim tasks
- Update tasks
- Clear KB indexes
- Write to most channels (silent observer)
```
---
## Quick Reference
### Role by Task Action
| Action | Allowed Roles |
|--------|---------------|
| Create task | CEO, Board, PMs |
| Activate task | PMs |
| Assign task | CEO, Board, PMs |
| Claim task | Developer, QA, Documenter, PMs |
| Pass QA | QA only |
| Fail QA | QA only |
| Complete docs | Documenter only |
| Complete task | PMs only |
| Cancel task | PMs, Board (not CEO, not Auditor) |
### Role Hierarchy
```
CEO
└─ Board (Product Owner, Head Marketing)
└─ Auditor (silent observer)
└─ Main PM
└─ Cell PMs
└─ Cell Members (Developer, QA, Documenter)
```
### Escalation Chain
```
Developer/QA/Documenter → Cell PM → Main PM → Product Owner → CEO
```
+467
View File
@@ -0,0 +1,467 @@
# Task Lifecycle Standards
Comprehensive standards for task management in the RoboCo system. These standards are derived from the actual implementation in the codebase.
**Source Files:**
- Task Status Enum: `roboco/models/base.py` (lines 19-34)
- Lifecycle Enforcement: `roboco/enforcement/task_lifecycle.py`
- Task Service: `roboco/services/task.py`
---
## Table of Contents
1. [Task States](#task-states)
2. [Valid Transitions](#valid-transitions)
3. [Role-Restricted Transitions](#role-restricted-transitions)
4. [Task Service Methods](#task-service-methods)
5. [Workflow by Role](#workflow-by-role)
6. [Quality Gates](#quality-gates)
7. [State Categories](#state-categories)
---
## Task States
### WF-001: TaskStatus Enum
**Source:** `roboco/models/base.py`
```python
class TaskStatus(str, Enum):
"""Task lifecycle states."""
BACKLOG = "backlog" # PM setup phase
PENDING = "pending" # Ready for work
CLAIMED = "claimed" # Agent has ownership
IN_PROGRESS = "in_progress" # Active work
BLOCKED = "blocked" # Waiting on dependency
PAUSED = "paused" # Temporarily stopped
VERIFYING = "verifying" # Self-verification
NEEDS_REVISION = "needs_revision"# QA rejected
AWAITING_QA = "awaiting_qa" # Ready for QA
AWAITING_DOCUMENTATION = "awaiting_documentation" # QA passed
AWAITING_PM_REVIEW = "awaiting_pm_review" # Docs done
COMPLETED = "completed" # TERMINAL
CANCELLED = "cancelled" # TERMINAL
```
### WF-002: State Diagram
```
PM CREATES
┌──────────┐
│ BACKLOG │───► cancelled
└────┬─────┘
│ activate()
┌──────────┐
┌──────│ PENDING │◄─────────────────────────────────┐
│ └────┬─────┘───► cancelled │
│ │ │
│ claim() │
│ │ │
▼ ▼ │
┌──────────┐ │
│ CLAIMED │───► pending, cancelled │
└────┬─────┘ │
│ start() │
▼ │
┌─────────────┐ │
┌──────────│ IN_PROGRESS │───► completed, cancelled │
│ └──────┬──────┘ │
│ │ │
block() pause() │
│ │ │
▼ ▼ │
┌──────────┐ ┌─────────┐ │
│ BLOCKED │ │ PAUSED │ │
└────┬─────┘ └────┬────┘ │
│ │ │
unblock() resume() │
│ │ │
└────────►───────►└──────►──────┐ │
│ │
submit_for_verification() │
│ │
▼ │
┌───────────┐ │
│ VERIFYING │───► cancelled │
└─────┬─────┘ │
│ │
┌───────────────────────┼───────────────────────┐ │
│ │ │ │
submit_for_qa() needs_revision direct to docs│
│ │ │ │
▼ ▼ ▼ │
┌─────────────┐ ┌─────────────────┐ ┌─────────────────────┐
│ AWAITING_QA │◄───────│ NEEDS_REVISION │──│ AWAITING_DOCUMENTATION│
└──────┬──────┘ └─────────────────┘ └──────────┬──────────┘
│ ▲ │
┌─────────┼─────────┐ │ │
│ │ │ │ docs_complete()
pass_qa() fail_qa() block │ │
│ │ │ │ ▼
│ └─────────┴───────────────┘ ┌────────────────────┐
│ │ AWAITING_PM_REVIEW │
└────────────────────────────────────────────────└─────────┬──────────┘
complete()
┌───────────┐
│ COMPLETED │
└───────────┘
```
---
## Valid Transitions
### WF-010: Transition Matrix
**Source:** `roboco/enforcement/task_lifecycle.py` (lines 17-56)
| From Status | Valid Next States |
|-------------|-------------------|
| `backlog` | pending, cancelled |
| `pending` | claimed, cancelled |
| `claimed` | in_progress, pending, cancelled |
| `in_progress` | blocked, paused, verifying, completed, cancelled |
| `blocked` | in_progress, cancelled |
| `paused` | in_progress, cancelled |
| `verifying` | awaiting_qa, needs_revision, awaiting_documentation, cancelled |
| `needs_revision` | claimed, in_progress, cancelled |
| `awaiting_qa` | claimed, awaiting_documentation, needs_revision, blocked, cancelled |
| `awaiting_documentation` | claimed, awaiting_pm_review, cancelled |
| `awaiting_pm_review` | claimed, completed, cancelled |
| `completed` | *(TERMINAL - no transitions)* |
| `cancelled` | *(TERMINAL - no transitions)* |
### WF-011: Invalid Transitions
Any transition NOT in the matrix above will raise `InvalidTransitionError`.
```python
from roboco.enforcement.task_lifecycle import validate_task_transition
# This will raise InvalidTransitionError
validate_task_transition(
TaskStatus.PENDING,
TaskStatus.COMPLETED, # Cannot skip the workflow!
agent_role="developer"
)
```
---
## Role-Restricted Transitions
### WF-020: Permission Matrix
**Source:** `roboco/enforcement/task_lifecycle.py` (lines 66-93)
Certain transitions require specific roles:
| Transition | Allowed Roles |
|------------|---------------|
| `backlog → pending` | cell_pm, main_pm, product_owner, head_marketing |
| `awaiting_qa → claimed` | qa |
| `awaiting_qa → awaiting_documentation` | qa |
| `awaiting_qa → needs_revision` | qa |
| `awaiting_documentation → claimed` | documenter |
| `awaiting_documentation → awaiting_pm_review` | documenter |
| `awaiting_pm_review → claimed` | cell_pm, main_pm, product_owner, head_marketing |
| `awaiting_pm_review → completed` | cell_pm, main_pm, product_owner, head_marketing |
| `in_progress → completed` | cell_pm, main_pm, product_owner, head_marketing |
| `* → cancelled` | cell_pm, main_pm, product_owner, head_marketing |
### WF-021: Role Validation
```python
from roboco.enforcement.task_lifecycle import can_agent_transition
# Check if role can make transition
if can_agent_transition(
current_status=TaskStatus.AWAITING_QA,
new_status=TaskStatus.AWAITING_DOCUMENTATION,
agent_role="developer" # False - only QA can do this
):
# proceed
```
---
## Task Service Methods
### WF-030: Status Change Methods
**Source:** `roboco/services/task.py`
| Method | Status Change | Calling Roles |
|--------|---------------|---------------|
| `activate()` | BACKLOG → PENDING | PM roles |
| `claim()` | → CLAIMED | developer, qa, documenter (based on current status) |
| `start()` | CLAIMED/PAUSED/NEEDS_REVISION → IN_PROGRESS | Owner |
| `block()` | IN_PROGRESS → BLOCKED | Owner |
| `soft_block()` | IN_PROGRESS → BLOCKED | Owner (external factor) |
| `unblock()` | BLOCKED → IN_PROGRESS | Owner or PM |
| `pause()` | IN_PROGRESS → PAUSED | Owner |
| `resume()` | PAUSED → IN_PROGRESS | Owner |
| `submit_for_verification()` | IN_PROGRESS → VERIFYING | Developer |
| `submit_for_qa()` | VERIFYING → AWAITING_QA | Developer |
| `pass_qa()` | AWAITING_QA → AWAITING_DOCUMENTATION | QA |
| `fail_qa()` | AWAITING_QA → NEEDS_REVISION | QA |
| `docs_complete()` | AWAITING_DOCUMENTATION → AWAITING_PM_REVIEW | Documenter |
| `submit_for_pm_review()` | IN_PROGRESS → AWAITING_PM_REVIEW | Any (non-dev tasks) |
| `complete()` | AWAITING_PM_REVIEW → COMPLETED | PM roles |
| `cancel()` | ANY → CANCELLED | PM roles |
### WF-031: Core Validation Method
All status changes go through `_validate_and_set_status()`:
```python
def _validate_and_set_status(
self,
task: TaskTable,
new_status: TaskStatus,
agent_role: str | None = None,
) -> None:
"""
Validate and set task status with lifecycle enforcement.
This is the single point of truth for status changes.
"""
```
---
## Workflow by Role
### WF-040: Developer Workflow
```
PENDING
│ claim()
CLAIMED
│ start()
IN_PROGRESS ←──────────────┐
│ │
│ submit_for_ │ (from NEEDS_REVISION)
│ verification() │
▼ │
VERIFYING │
│ │
│ submit_for_qa() │
▼ │
AWAITING_QA ──fail_qa()──► NEEDS_REVISION
│ │
│ pass_qa() (by QA) │ claim() + start()
▼ │
[QA/Docs workflow] └───────────────────┘
```
**Developer can:**
- Claim `pending` and `needs_revision` tasks
- Start, pause, resume work
- Submit for verification and QA
- Block (with dependency)
**Developer cannot:**
- Pass/fail QA (that's QA's job)
- Complete tasks (that's PM's job)
- Cancel tasks
### WF-041: QA Workflow
```
AWAITING_QA
│ claim()
CLAIMED (QA owns)
│ start()
IN_PROGRESS
├── pass_qa() ────► AWAITING_DOCUMENTATION
└── fail_qa() ────► NEEDS_REVISION (reassigned to developer)
```
**QA can:**
- Claim `awaiting_qa` tasks only
- Pass or fail QA
- Block tasks
**QA cannot:**
- Claim pending tasks (developers do that)
- Complete documentation
- Complete tasks
### WF-042: Documenter Workflow
```
AWAITING_DOCUMENTATION
│ claim()
CLAIMED (Documenter owns)
│ start()
IN_PROGRESS
│ docs_complete()
AWAITING_PM_REVIEW
```
**Documenter can:**
- Claim `awaiting_documentation` tasks
- Complete documentation
**Documenter cannot:**
- Claim pending tasks
- Pass/fail QA
- Complete tasks
### WF-043: PM Workflow
```
BACKLOG
│ activate()
PENDING
│ (developers claim)
...
AWAITING_PM_REVIEW
│ complete()
COMPLETED
```
**PM can:**
- Create tasks in backlog
- Activate backlog → pending
- Complete awaiting_pm_review tasks
- Cancel any task
- Unblock blocked tasks
---
## Quality Gates
### WF-050: Before Claiming
```markdown
- [ ] Task is in valid claim status for your role
- [ ] No existing in_progress task (one at a time)
- [ ] Dependencies are completed
```
### WF-051: Before Starting
```markdown
- [ ] Task is claimed by you
- [ ] Plan is documented
- [ ] Cell channel notified
```
### WF-052: Before Submit to QA
```markdown
- [ ] Tests passing: `uv run pytest`
- [ ] Linting clean: `uv run ruff check .`
- [ ] Type check: `uv run mypy roboco/`
- [ ] Self-review completed
- [ ] Journal reflection written
```
### WF-053: Before Completion
```markdown
- [ ] QA approved
- [ ] Documentation complete
- [ ] PM review approved
```
---
## State Categories
### WF-060: Helper Functions
**Source:** `roboco/enforcement/task_lifecycle.py`
| Function | Returns True For |
|----------|-----------------|
| `is_terminal_state()` | completed, cancelled |
| `is_waiting_state()` | blocked, paused, awaiting_qa, awaiting_documentation, awaiting_pm_review |
| `is_active_state()` | claimed, in_progress, verifying, needs_revision |
### WF-061: Terminal States
Once a task reaches `COMPLETED` or `CANCELLED`, no further transitions are possible.
```python
get_valid_transitions(TaskStatus.COMPLETED) # Returns []
get_valid_transitions(TaskStatus.CANCELLED) # Returns []
```
### WF-062: Waiting States
Tasks in waiting states are "on hold" pending some external action:
- `BLOCKED` - Waiting for blocker task
- `PAUSED` - Waiting for agent to resume
- `AWAITING_QA` - Waiting for QA review
- `AWAITING_DOCUMENTATION` - Waiting for docs
- `AWAITING_PM_REVIEW` - Waiting for PM approval
### WF-063: Active States
Tasks where an agent is actively working:
- `CLAIMED` - Agent owns, about to start
- `IN_PROGRESS` - Active development
- `VERIFYING` - Self-verification
- `NEEDS_REVISION` - Fixing QA issues
---
## Quick Reference
### Valid Claim Statuses by Role
| Role | Can Claim From |
|------|----------------|
| Developer | `pending`, `needs_revision` |
| QA | `awaiting_qa` |
| Documenter | `awaiting_documentation` |
| PM | `pending`, `backlog` |
### Common Status Flows
**Happy Path:**
```
backlog → pending → claimed → in_progress → verifying → awaiting_qa
→ awaiting_documentation → awaiting_pm_review → completed
```
**QA Rejection:**
```
awaiting_qa → needs_revision → claimed → in_progress → verifying → awaiting_qa
```
**Direct PM Review (non-dev task):**
```
pending → claimed → in_progress → awaiting_pm_review → completed
```
**Cancellation:**
```
any_state → cancelled
```
+18
View File
@@ -0,0 +1,18 @@
# UX/UI Documentation
Documentation for the UX/UI Cell team.
## Access
- **READ**: UX/UI Cell (developer, QA, PM, documenter)
- **WRITE**: ux-doc only
## Contents
- `/design/` - Design system documentation
- `/qa/` - QA-related docs
## Contributing
UX/UI team members should request documentation updates through the Cell PM.
Only the UX/UI Documenter (ux-doc) can write to this directory.