Contributing¶
Setup¶
Install pre-commit hooks:
This runs ruff, mypy, and trailing whitespace checks on every commit.
Code quality¶
All checks need to pass. CI runs them on every push.
uv run ruff check src/ tests/ # lint
uv run ruff format src/ tests/ # format
uv run mypy # type check (strict mode)
uv run pytest # test + coverage (minimum enforced by pytest-cov)
uv run bandit -r src/ -q -c pyproject.toml # security
uv run vulture src/ --min-confidence 80 # dead code
uv run complexipy src/ --max-complexity-allowed 15 # cognitive complexity
Conventions¶
- Python 3.11+, type annotations on all function signatures
- mypy strict mode. Use
dict[str, Any]not baredict. - Click for the CLI. Each command module exports a Click command or group.
- Config goes in
developer.yaml(copy fromexample.developer.yaml). The Pydantic models inconfig.py(DeveloperConfig,RepoConfig) are the in-code representation. - Architectural decisions go in
DECISIONS.mdwith context, rationale, and revisit triggers.
Adding a new repo¶
Add a block to your developer.yaml (copied from example.developer.yaml). See Configuration.
Adding a new audit type¶
- Create
src/developer/templates/audit/<name>.md.j2-- include_labels.md.j2and_personality.md.j2, use{{ max_findings }}for the limit - Add an
AuditSpectoAUDITSinsrc/developer/services/audit.py: the template, the tool profile, a one-line help text, and a function that gathers the template's extra variables from the worktree. Thedeveloper audit <name>command and the API's/audit/<name>endpoint come from that entry.
Adding a new resolve stage¶
- Create
src/developer/templates/resolve/<name>.md.j2 - Define the
Stageinsrc/developer/pipelines/resolve.py, add it toSTAGES, and add its name toSTAGE_NAMESinsrc/developer/config.py - Call it from the
resolve()function where it belongs, withrun.stages.ask()orrun.stages.work()
Adding a new pipeline¶
Bigger lift, but the pattern is well-established:
- Create a pipeline file in
src/developer/pipelines/ - Define its
Stages and aSTAGEStuple, and add their names toSTAGE_NAMESinsrc/developer/config.py - Write the pipeline as a function of its input and a
Run, using ordinary control flow for its branches - Do any git or GitHub work in that function, never through the agent
- Create Pydantic models in
src/developer/models/for structured outputs - Create Jinja2 templates in
src/developer/templates/<pipeline>/ - Add a Click command in
src/developer/commands/and register it incli.py - Add a service in
src/developer/services/that runs the pipeline and returns a typed result, and call it from the Click command - Add a
POSTendpoint insrc/developer/api/routes.pywith a_run_*task function that calls the same service, and a request model inapi/models.py
API conventions¶
The API lives in src/developer/api/. It mirrors the CLI — same pipelines, same building blocks — but returns structured JSON instead of terminal output.
- Routes go in
api/routes.py. Each pipeline endpoint has a_run_*task function that calls the pipeline's service insrc/developer/services/, the same one the CLI uses. - Request/response models go in
api/models.py. - Task execution uses the app's
TaskStore(api/tasks.py). Task functions run in daemon threads, keyed by the repo and target they work on, and log progress withtask.log_message(). Tests give the store an executor that runs tasks synchronously. - Put pipeline logic in the pipeline and its service, never in a Click command or an API route, so the CLI and the API can't drift apart.