utils/repo_contract
Krzysztof kuhy Rudnicki 6d96c58f01 repo_contract: idempotent installer for new repos
teatr-pw and teatr-pw-pytania (created ad hoc 2026-10-09) had no shim, hook or
'## Commands' section: the gate was only ever rolled out by hand. The
installer writes shim + pre-commit hook + CI workflow and bootstraps the
Commands section and scripts/test_changed.sh, exiting 1 while <FILL IN>
remains.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TjyHmRrjZssaxXDWcGuCeQ
2026-10-10 12:46:04 +02:00
..
templates repo contract + coverage_gaps gates, test_changed, dependency bumps 2026-10-04 16:27:28 +02:00
tests repo_contract: idempotent installer for new repos 2026-10-10 12:46:04 +02:00
__init__.py repo contract + coverage_gaps gates, test_changed, dependency bumps 2026-10-04 16:27:28 +02:00
bootstrap.py repo contract + coverage_gaps gates, test_changed, dependency bumps 2026-10-04 16:27:28 +02:00
check.py repo contract + coverage_gaps gates, test_changed, dependency bumps 2026-10-04 16:27:28 +02:00
contract.py repo contract + coverage_gaps gates, test_changed, dependency bumps 2026-10-04 16:27:28 +02:00
infer.py repo contract + coverage_gaps gates, test_changed, dependency bumps 2026-10-04 16:27:28 +02:00
install.py repo_contract: idempotent installer for new repos 2026-10-10 12:46:04 +02:00
README.md repo_contract: idempotent installer for new repos 2026-10-10 12:46:04 +02:00
sources.py repo contract + coverage_gaps gates, test_changed, dependency bumps 2026-10-04 16:27:28 +02:00
stacks.py repo_contract: prune .utils (CI checkout of the gate) from the has-tests probe 2026-10-04 16:54:53 +02:00
sweep.py repo contract + coverage_gaps gates, test_changed, dependency bumps 2026-10-04 16:27:28 +02:00

repo_contract

Shared gate: every repo tells an agent how to run and test it, so nobody has to explore. Enforced like the other shared gates (real gate in ~/src/utils, a thin shim per repo, pre-commit + CI).

The contract

CLAUDE.md (or AGENTS.md that CLAUDE.md symlinks to) has a ## Commands section with one line per key, exactly - <key>: \``:

## Commands

- run: `<command>` | n/a: library
- test: `<full suite>`
- test-changed: `scripts/test_changed.sh`
- lint: `<command>`
- coverage: `<command writing a report>`
- coverage-gaps: `coverage-gaps <report>`

Rules (all machine-checked):

  1. All six keys present, once each. A value is a backticked command or n/a: <reason> / n/a (<reason>). A bare n/a fails: n/a carries a reason.
  2. test-changed invokes an existing, executable scripts/test_changed.sh-style script that runs only tests related to files changed vs HEAD, falls back to the full suite when it cannot map, and prints failures plus a one-line summary only.
  3. coverage-gaps runs the shared lister coverage-gaps <report> (~/src/utils/coverage_gaps).
  4. test, test-changed, lint, coverage are quiet: no -v/--verbose/ expanded reporter, in the command or in the script's runner lines.
  5. No placeholders (<FILL IN>, TODO, ...).
  6. If test is n/a, then test-changed, coverage, coverage-gaps are n/a too, and the repo must contain no tests (bounded scan).

Use

python3 ~/src/utils/repo_contract/check.py --repo <path>      # 0 pass / 1 violations / 2 unreadable
python3 ~/src/utils/repo_contract/bootstrap.py --repo <path>  # print proposal
python3 ~/src/utils/repo_contract/bootstrap.py --repo <path> --write
python3 ~/src/utils/repo_contract/sweep.py                    # ~/data/claude-scratch/repo-contract/SWEEP.md

Bootstrap never overwrites: it appends the section (or inserts missing lines at the end of an existing ## Commands), creates CLAUDE.md -> AGENTS.md when only AGENTS.md exists, and writes scripts/test_changed.sh only if absent. Sources, in priority order: existing CLAUDE.md, scripts/<key>.sh, Makefile, package.json (node only), CI workflows, stack defaults. Anything it cannot infer is written as <FILL IN>, which the gate rejects until a human fills it.

Adding the gate to a repo

Run this on every new repo right after git init (idempotent):

~/src/utils/scripts/install_repo_contract_gate.sh <repo>   # --check to preview

It writes the scripts/check_repo_contract.sh shim, the pre-commit hook, the CI workflow (skipped when the repo has .dep-freshness-no-workflow, i.e. no GitHub Actions), then bootstraps the ## Commands section and scripts/test_changed.sh. It exits 1 while a <FILL IN> remains. Never copy scripts/check_repo_contract.sh from this repo (it is the real gate, not a shim). The hook only fires on commits touching CLAUDE.md/AGENTS.md/test_changed.sh, so a repo that never got the installer is caught only by sweep.py.

Tests

PYTHONPATH=. python -m pytest repo_contract/tests -q --cov=repo_contract --cov-branch --cov-fail-under=100