Contributing¶
Local dev loop¶
API (Python / FastAPI)¶
python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
# spin up the data services
docker compose up -d db redis
alembic upgrade head
uvicorn api.app:app --reload
Web (Next.js)¶
Full stack via Docker: make all or docker compose up --build.
Code style¶
- Python: ruff (
ruff check .,ruff format .) + mypy (mypy core api flows). 88-column line length. Google-style docstrings on public functions. Pydantic v2 at every I/O boundary. - TypeScript: eslint + prettier. Tailwind 4 utility-first; keep components dumb, put logic in hooks.
- Commit messages: conventional-ish —
feat(scope): ...,fix(scope): ...,chore(scope): .... Keep the subject ≤ 72 chars.
Tests¶
pytest # all tests
pytest tests/unit/ # unit only
pytest tests/integration/ -m integ # integration (uses testcontainers)
pytest --cov=core --cov=api --cov=flows
Integration tests spin up a real Postgres via testcontainers. They're
slower; CI runs them on PR.
Migrations¶
alembic revision --autogenerate -m "short description"
alembic upgrade head
alembic downgrade -1 # roll back last migration locally
Migrations live under core/db/migrations/versions/. Make sure the
down-revision is clean — we keep migrations reversible where possible.
Evals¶
Scoring changes should be validated against the eval harness:
Labeled samples live in tests/data/. If you change the scoring prompt
in a way that moves scores, rebuild the golden set.
Docs¶
Docs auto-deploy to GitHub Pages on push to master via
.github/workflows/docs.yml. The config is mkdocs.yml at repo root;
sources under docs/.
Where to put things¶
- New LLM adapter →
core/llm/backends/. - New ingest source →
core/sources/<name>/. - API route →
api/routes/, register inapi/app.py. - UI component →
web/src/components/(shared) orweb/src/app/(main)/<route>/_components/(route-scoped). - Design spec →
dev/specs/(local only, gitignored). - Ticket →
dev/tickets/open/→ moved todev/tickets/closed/on merge (local only, gitignored).
Before opening a PR¶
ruff check .passes.mypyclean (or justify the# type: ignore).pytest -m 'not eval and not integ'green.- Migrations applied and reversible.
- If you changed a scoring prompt or the profile schema, re-run the eval and note the delta in the PR description.