Contributing
Running tests
Section titled “Running tests”cd apiuv run pytest tests/ -vRequires uv installed (curl -LsSf https://astral.sh/uv/install.sh | sh). Tests use an in-memory fake table — no AWS credentials or running stack needed. The full suite takes a few minutes.
First-time setup
Section titled “First-time setup”Install pre-commit and activate the hooks:
pip install pre-commitcd web && npm install # so the oxlint/oxfmt hooks find node_modulescd ..pre-commit installSome hooks need tools on your PATH: trivy
and terraform; the hadolint and
shellcheck hooks run in Docker.
The hooks run automatically on git commit:
- gitleaks — blocks commits containing secrets
- ruff (Python) and oxlint / oxfmt (TypeScript) — lint + format, auto-fixing
- trivy-fs — blocks a HIGH/CRITICAL dependency CVE with a known fix; runs only when a
dependency manifest or the
Dockerfilechanges - actionlint, terraform_fmt / terraform_validate, hadolint, shellcheck — lint workflows, Terraform, the Dockerfile, and shell scripts
- file hygiene — large files (>500KB), merge-conflict markers, trailing whitespace, YAML/JSON/TOML syntax
To run all hooks manually: pre-commit run --all-files
Running locally
Section titled “Running locally”Open in VS Code and press F5 → select “Full Stack”. Starts FastAPI on localhost:8000 and Vite on localhost:5173 with debuggers attached.
Alternatively:
cd api && uv run uvicorn main:app --reload --port 8000 # backendcd web && npm run dev # frontendSee Local Development for first-time setup and debugging tips.
Commit messages
Section titled “Commit messages”This repo uses Conventional Commits — release-please
parses commit messages on every push to main to generate CHANGELOG.md and bump the version.
Use a type: prefix on every commit (feat:, fix:, docs:, chore:, refactor:, test:).
SQLite schema changes
Section titled “SQLite schema changes”The self-hosted SQLite backend stores each item as a JSON blob (pk, data), so adding a
field to an existing type needs no migration, and a new table only needs its name added to
_TABLE_NAMES in api/lib/db/sqlite_backend.py (it is created on next boot). See
Feature Recipes.
Anything beyond that — a new index, reshaping stored data — is a migration, and the PR must:
-
Add it to the
MIGRATIONSlist inapi/lib/db/migrations.py(see that file’s docstring). Self-hosters’ existing data must upgrade automatically on next boot. -
Add a
BREAKING CHANGE:footer to the commit message, even when it isn’t an API break.release-pleasesurfaces such commits under their own heading inCHANGELOG.md, which is the only signal self-hosters get to read closely before upgrading. Example:feat: index results by gameIdBREAKING CHANGE: adds an index on results.gameId. Migration runs automatically on nextboot — no manual action needed, called out so self-hosters notice it before upgrading.
Pull requests
Section titled “Pull requests”- Tests must pass:
cd api && uv run pytest tests/ -v - Keep scope small — one thing per PR
- Fill-out the PR template