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.
1. Start from an issue
Section titled “1. Start from an issue”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.
2. Fork and branch
Section titled “2. Fork and branch”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:
git switch -c fix-142/charter-facet-countCommit 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:
git config core.hooksPath .githooksThe 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:
uvx ruff check # lintuvx ruff format --check . # formattinguv lock --check # lockfile is in syncuv run python scripts/check_architecture_boundaries.pyFrontend (frontend/) runs:
pnpm lint # Oxlintpnpm format # Prettier checkIf a hook fails, fix it and re-commit. The usual one-liners:
# backenduvx ruff check --fix && uvx ruff format .# frontendpnpm lint:fix && pnpm format:fix4. Make the change and pass CI
Section titled “4. Make the change and pass CI”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:
| Job | What it runs |
|---|---|
lint | uv lock --check, uvx ruff check, uvx ruff format --check ., uvx mypy apps config, architecture-boundary check, makemigrations --check (no missing migrations) |
unit-tests | builds the api image, then just pytest-focused |
integration-tests | just 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.
cd apiuvx mypy apps config # the hook does NOT run this — CI doesjust pytest-focused # fast SQLite loopjust coverage # full suite + coverage gateFrontend (.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:
cd frontendpnpm lint && pnpm format && pnpm testpnpm build && pnpm bundle-budget# intentional growth? regenerate the budget and commit it:pnpm bundle-budget:update5. Open a pull request
Section titled “5. Open a pull request”Push your branch and open a pull request against the repository’s main:
git push -u origin fix-142/charter-facet-countgh pr create --fillKeep 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.