Skip to content

Latest commit

 

History

History
176 lines (122 loc) · 9.43 KB

File metadata and controls

176 lines (122 loc) · 9.43 KB

Development

Prerequisites

  • Python 3.14 (see pyproject.toml requires-python)
  • Node 24 LTS (see frontend/package.json engines.node)
  • uv for Python deps + venv
  • just for the task runner (optional but recommended)
  • Docker (for the local stack: app + frontend + Jaeger)

First-time setup

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 ..

Running the stack

docker compose up

For backend-only iteration without Docker:

uv run uvicorn src.api.main:app --reload --port 8000

For frontend-only iteration:

cd frontend && npm run dev

The justfile

just (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.

Branching

        main  ◄── release PR ◄── develop ◄── feat/123-short-name
                                          ◄── fix/124-bug-name
                                          ◄── chore/125-config-change
  • main is protected: every required CI context must pass + 1 review + commit-type sync + branch-protection sync.
  • develop is the integration branch; same gates as main minus a strictness flag (strict: false so 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.

Commit messages

Seven allowed prefixes (enforced in three places — [tool.commitizen], pr-title.yml, check_commit_types.py):

  • feat: — new capability
  • fix: — bug fix
  • docs: — documentation only
  • test: — tests / eval-harness changes
  • refactor: — internal change with no behaviour delta
  • chore: — tooling, deps, infra
  • release:develop → main release PRs only

Subject is lowercase after the colon (Title Case is rejected unless it's an all-caps initialism).

CI pipeline (.github/workflows/)

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)

Action-pinning policy

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.P comment 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; @v8 would not resolve.
  • Third-party (anything else) — pin to a 40-hex-char SHA with a trailing # vN.M.P comment 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.

Version-bump policy

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.tomlfeat: 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.

Creating a release

The release flow chains four workflows and one script:

  1. Pre-stage CHANGELOG (changelog-prestage.yml, manual dispatch) — pass the new tag (e.g. v0.3.0); the workflow opens a chore: pre-stage CHANGELOG … PR against develop that merges origin/main into the branch and inserts the new ## [<version>] - <date> heading + footer compare-link. Merge that PR.
  2. Open the release PRrelease: vX.Y.Z from developmain. Conflict-free now that develop has main's CHANGELOG shape. Admin-merge with gh pr merge --admin --merge once green.
  3. Tag the merge commitgit tag vX.Y.Z && git push origin vX.Y.Z. Triggers release.yml.
  4. release.yml builds the image, pushes to GHCR, generates the CycloneDX SBOM, publishes the GitHub Release.
  5. changelog-rollup.yml auto-fires on the successful release and opens a chore: roll up CHANGELOG … PR against develop that bumps pyproject.toml + uv.lock PATCH (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 by changelog-rollup.yml post-release.
  • rollup_changelog.py … --no-bump — CHANGELOG edits only. Used by changelog-prestage.yml pre-release.

Testing policy

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.

Local agent hook setup

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.json

The example wires:

  • PreToolUse:Bashpretooluse_bash.py (forbidden-flag blocker + secret scan + audit log)
  • PostToolUse:Write|Editposttooluse_writeedit.py (ruff / prettier on touched files)
  • SessionStart:startup|resumesessionstart.py (injects current branch + git status --short as session context)

.claude/settings.local.json and .claude/bash-log.txt are gitignored — your local config never ships.

Pre-commit setup

uv run pre-commit install --hook-type pre-commit --hook-type commit-msg wires both stages. The hooks:

  1. Ruff (lint + format auto-fix)
  2. Generic hygiene (YAML/TOML/JSON parse, merge conflicts, large files >500 KB, trailing whitespace, EOF, line endings)
  3. Gitleaks (secret scan)
  4. Commitizen (conventional-commit lint at commit-msg)
  5. Local mypy --strict against the project's uv env

pre-commit run --all-files runs the full suite against every file — the same job CI runs.

Branch-protection sync setup

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.