Skip to content

Contribution workflow

Each repository takes changes through pull requests against its main branch. This page walks the loop from an idea to a merged change, and the checks that gate it. New here? Start with For contributors and Run it locally.

Most changes should have an issue first, so the approach can be agreed before you write code. Look for an existing one, or open one in the repository the change belongs to. Say on the issue that you are taking it.

Fork the repository on GitHub (members of the organisation can branch in it directly), then create a branch named for the type of change and, where there is one, the issue number:

Terminal window
git switch -c fix-142/charter-facet-count

Commit messages follow Conventional Commits: feat: …, fix: …, docs: …, refactor: …, chore: ….

3. Enable the pre-commit hooks (once per clone)

Section titled “3. Enable the pre-commit hooks (once per clone)”

Hooks live in each repo’s .githooks/ and are wired up per-clone via core.hooksPath. They are off until you enable them; run this once in each repository you work in:

Terminal window
git config core.hooksPath .githooks

The hook runs the fast CI checks before every commit, so lint and formatting problems surface before CI. It does not run the type-check or the tests.

Backend (api/) runs:

Terminal window
uvx ruff check # lint
uvx ruff format --check . # formatting
uv lock --check # lockfile is in sync
uv run python scripts/check_architecture_boundaries.py

Frontend (frontend/) runs:

Terminal window
pnpm lint # Oxlint
pnpm format # Prettier check

If a hook fails, fix it and re-commit. The usual one-liners:

Terminal window
# backend
uvx ruff check --fix && uvx ruff format .
# frontend
pnpm lint:fix && pnpm format:fix

CI (GitHub Actions) runs on every pull request into main and every push to it. It is stricter than the hook — notably it also type-checks and runs the full test suites. Reproduce it locally before you push.

Backend (.github/workflows/ci.yml) — three jobs:

JobWhat it runs
lintuv lock --check, uvx ruff check, uvx ruff format --check ., uvx mypy apps config, architecture-boundary check, makemigrations --check (no missing migrations)
unit-testsbuilds the api image, then just pytest-focused
integration-testsjust coverage (enforces the coverage gate)

A separate backend Security workflow runs CodeQL and a pip-audit dependency audit, on pull requests and on a schedule. A newly published vulnerability in a dependency can turn it red on a pull request that changed nothing; the fix is a lockfile upgrade (uv lock --upgrade-package <name>), in its own pull request.

Terminal window
cd api
uvx mypy apps config # the hook does NOT run this — CI does
just pytest-focused # fast SQLite loop
just coverage # full suite + coverage gate

Frontend (.github/workflows/ci.yml) — five jobs: lint (pnpm lint), format (pnpm format), test (pnpm test), build (pnpm build then pnpm bundle-budget), and lighthouse. The bundle-budget step fails the build when the gzip bundle exceeds .bundle-budget.json, so a new route can trip CI even when lint and tests pass:

Terminal window
cd frontend
pnpm lint && pnpm format && pnpm test
pnpm build && pnpm bundle-budget
# intentional growth? regenerate the budget and commit it:
pnpm bundle-budget:update

Push your branch and open a pull request against the repository’s main:

Terminal window
git push -u origin fix-142/charter-facet-count
gh pr create --fill

Keep the description short: what the change does and why, in a few sentences, and Closes #142. A maintainer reviews it; CI must be green before it merges.

If a feature needs both an API change and an interface change, open one pull request in each repository and get the backend one merged first, so the frontend never depends on an API that does not exist yet.

See also Working in the codebase and The API.