- Python 3.14 (see
pyproject.tomlrequires-python) - Node 24 LTS (see
frontend/package.jsonengines.node) uvfor Python deps + venvjustfor the task runner (optional but recommended)- Docker (for the local stack: app + frontend + Jaeger)
git clone https://github.com/constk/harness-python-react.git
cd harness-python-react
# Backend deps + venv
uv sync --extra dev
# Pre-commit hooks (commit-msg + pre-commit stages)
uv run pre-commit install --hook-type pre-commit --hook-type commit-msg
# Frontend deps
cd frontend && npm ci && cd ..docker compose up- Backend: http://localhost:8000/api/v1/health
- Frontend (Vite dev server with HMR): http://localhost:5173
- Jaeger UI: http://localhost:16686
For backend-only iteration without Docker:
uv run uvicorn src.api.main:app --reload --port 8000For frontend-only iteration:
cd frontend && npm run devjust (no args) lists every recipe. The most-used:
| Recipe | What it runs |
|---|---|
just lint |
ruff check . + ruff format --check . |
just typecheck |
mypy --strict src/ tests/ |
just test |
pytest tests/ -m "not integration" |
just architecture |
lint-imports |
just check |
lint typecheck architecture test (the pre-push gate) |
just frontend-check |
npm run lint && format:check && check && test |
just docker-build |
Builds harness-python-react:dev for sanity checks |
Every recipe uses uv run --frozen — bare uv run silently re-resolves when pyproject.toml drifts from uv.lock; --frozen aborts loudly instead.
main ◄── release PR ◄── develop ◄── feat/123-short-name
◄── fix/124-bug-name
◄── chore/125-config-change
mainis protected: every required CI context must pass + 1 review + commit-type sync + branch-protection sync.developis the integration branch; same gates asmainminus a strictness flag (strict: falseso PRs don't need rebases).- Feature branches are short-lived and named
<type>/<issue-number>-<kebab-title>. - Optional Beads queues can mirror GitHub issues for local execution, but GitHub remains the source of truth for requirements, PR linkage, and closure. See
docs/BEADS.md.
Seven allowed prefixes (enforced in three places — [tool.commitizen], pr-title.yml, check_commit_types.py):
feat:— new capabilityfix:— bug fixdocs:— documentation onlytest:— tests / eval-harness changesrefactor:— internal change with no behaviour deltachore:— tooling, deps, infrarelease:—develop → mainrelease PRs only
Subject is lowercase after the colon (Title Case is rejected unless it's an all-caps initialism).
| Workflow | Triggers | Required? |
|---|---|---|
ci.yml |
push/PR to develop+main | Yes — backend + frontend gates + meta-gates + version/action/tests audits |
security.yml |
push/PR + weekly schedule | Yes — 4 jobs (gitleaks, pip-audit, npm audit, trivy) |
pr-title.yml |
PR open/edit/sync | Yes — conventional-commit lint |
release.yml |
tag v*.*.* |
No — tag-triggered |
release-drafter.yml |
push to main + PR label events | No |
branch-protection.yml |
weekly + push to .github/branch-protection/** | No |
artifact-cleanup.yml |
weekly | No |
eval-nightly.yml |
workflow_dispatch only by default |
No |
codeql.yml |
workflow_dispatch only (placeholder) |
No |
pin-freshness-audit.yml |
weekly + workflow_dispatch |
No — async second layer of action-pinning policy |
changelog-rollup.yml |
after release.yml succeeds + workflow_dispatch |
No — opens a chore: roll up CHANGELOG … PR against develop |
changelog-prestage.yml |
workflow_dispatch only |
No — operator-triggered before opening the release PR (closes the same-line CHANGELOG conflict class) |
Audited by the Action pinning audit CI job (.github/scripts/check_action_pins.py). Three buckets:
- First-party (
actions/*,github/*) — pin to major tag (@v4). SHA + trailing# vN.M.Pcomment also accepted; that's the form used in elevated-permissions workflows (release, branch-protection). astral-sh/setup-uv— pin to latest patch tag (@v8.0.0) or SHA. astral-sh does not publish a floating major tag for v8;@v8would not resolve.- Third-party (anything else) — pin to a 40-hex-char SHA with a trailing
# vN.M.Pcomment naming the resolved tag. A moving branch in a supply-chain workflow defeats the point of the scan.
When bumping a third-party action, update the SHA and the trailing comment in the same PR. Dependabot's github-actions ecosystem opens those PRs automatically.
A second layer runs out-of-band: .github/workflows/pin-freshness-audit.yml (weekly Monday 06:00 UTC + workflow_dispatch) re-resolves every pin against api.github.com. It catches the silently-deprecated-tag class — a tag pin that no longer resolves, or a SHA pin whose documented # vN.M.P tag has been re-tagged upstream. Default warn-only with an auto-filed harness,security issue; PIN_FRESHNESS_STRICT=1 (workflow_dispatch input) escalates to a hard failure. Where the on-PR Action pinning audit checks pin shape, this checks pin freshness — separate failure classes.
Audited by the Version bump check CI job (.github/scripts/check_version_bump.py). Every PR bumps [project] version in pyproject.toml AND the matching [[package]] block in uv.lock. The bump direction follows commitizen's bump_map in pyproject.toml — feat: is MINOR, everything else is PATCH. release: PRs are exempt because the dev version IS the release version.
The uv.lock self-version is hand-edited (one line); avoid uv lock mid-PR because it would re-resolve transitive deps and pull in unintended upgrades. The Version bump check gate enforces both halves.
The release flow chains four workflows and one script:
- Pre-stage CHANGELOG (
changelog-prestage.yml, manual dispatch) — pass the new tag (e.g.v0.3.0); the workflow opens achore: pre-stage CHANGELOG …PR against develop that mergesorigin/maininto the branch and inserts the new## [<version>] - <date>heading + footer compare-link. Merge that PR. - Open the release PR —
release: vX.Y.Zfromdevelop→main. Conflict-free now that develop has main's CHANGELOG shape. Admin-merge withgh pr merge --admin --mergeonce green. - Tag the merge commit —
git tag vX.Y.Z && git push origin vX.Y.Z. Triggersrelease.yml. release.ymlbuilds the image, pushes to GHCR, generates the CycloneDX SBOM, publishes the GitHub Release.changelog-rollup.ymlauto-fires on the successful release and opens achore: roll up CHANGELOG …PR against develop that bumpspyproject.toml+uv.lockPATCH (so develop's[Unreleased]section can accumulate again).
The shared script is .github/scripts/rollup_changelog.py:
rollup_changelog.py --tag vX.Y.Z --prior-tag vA.B.C --date YYYY-MM-DD— full rollup (CHANGELOG edits + version bump). Used bychangelog-rollup.ymlpost-release.rollup_changelog.py … --no-bump— CHANGELOG edits only. Used bychangelog-prestage.ymlpre-release.
Audited by the Tests required CI job (.github/scripts/check_tests_present.py). feat: and fix: PRs that touch src/ MUST also touch tests/. Other prefixes get a ::warning:: if src/ is touched without tests but don't block. The 75 % coverage gate alone doesn't catch behaviour-change-without-test (a single new line on already-covered code can pass), which is why this is a separate axis.
The .claude/hooks/ scripts enforce the harness from the LLM-coder side: blocking --no-verify, scanning staged diffs for secrets, formatting after every Write/Edit. Opt in by copying the example settings file:
cp .claude/settings.local.json.example .claude/settings.local.jsonThe example wires:
PreToolUse:Bash→pretooluse_bash.py(forbidden-flag blocker + secret scan + audit log)PostToolUse:Write|Edit→posttooluse_writeedit.py(ruff / prettier on touched files)SessionStart:startup|resume→sessionstart.py(injects current branch +git status --shortas session context)
.claude/settings.local.json and .claude/bash-log.txt are gitignored — your local config never ships.
uv run pre-commit install --hook-type pre-commit --hook-type commit-msg wires both stages. The hooks:
- Ruff (lint + format auto-fix)
- Generic hygiene (YAML/TOML/JSON parse, merge conflicts, large files >500 KB, trailing whitespace, EOF, line endings)
- Gitleaks (secret scan)
- Commitizen (conventional-commit lint at
commit-msg) - Local mypy
--strictagainst the project's uv env
pre-commit run --all-files runs the full suite against every file — the same job CI runs.
The branch-protection.yml workflow needs a BRANCH_PROTECTION_TOKEN secret with admin:repo scope on this repo. The default GITHUB_TOKEN cannot edit branch protection on the repo it runs in. Create a fine-grained PAT scoped to this repo only.