testsAndMisc/docs/superpowers/contracts/mandatory-hooks-2026-08-03.json
Krzysztof Rudnicki 2b3c1a69f7 fix(hooks): make git hooks and their toolchain mandatory and fail-closed
A completely absent `prettier` binary went unnoticed long enough for
unformatted markdown to reach main. Two independent causes, both fixed here.

No pre-push hook existed. The three hooks declaring `stages: [pre-push]` in
.pre-commit-config.yaml — prettier, ci-mirror, pytest-coverage — therefore
never ran locally at all. Adds one.

The pre-commit hook failed open. shfmt and shellcheck were wrapped in
`if command -v`, and the entire pre-commit framework was skipped with a
warning when its binary was missing. A silently skipped gate is
indistinguishable from a passing one, so a missing tool now aborts.

- lib/common.sh: require_tool tries `sudo -n pacman -S` and aborts with the
  exact command when a tool cannot be installed non-interactively;
  verify_toolchain applies it to all eight binaries the hooks invoke.
- The pre-push hook resolves git's stdin refs into --from-ref/--to-ref.
  Without a range, `pre-commit run --hook-stage pre-push` checks *staged*
  files, and nothing is staged at push time, so prettier would have received
  zero files and passed vacuously.
- ensure_hooks_path repairs core.hooksPath drift on every hook run.
- meta/scripts/install_hooks.sh covers the one case the hooks cannot
  self-heal — a fresh clone where the config was never set, so nothing runs.
  `--check` reports state and exits non-zero when incomplete.

Note `pre-commit install` must never be used here: it writes into
.git/hooks/, which git ignores entirely while core.hooksPath is set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 11:09:11 +02:00

21 lines
2.2 KiB
JSON

{
"title": "Make git hooks and their toolchain mandatory and fail-closed",
"objective": "Close the gap that let an entirely absent `prettier` binary go unnoticed while unformatted markdown reached main. Two causes: there was no pre-push hook at all, so the `stages: [pre-push]` hooks (prettier, ci-mirror, pytest-coverage) never ran locally; and the existing pre-commit hook failed open, skipping shellcheck and the whole pre-commit framework with a warning when their binaries were missing. Success means a commit or push is impossible while any required tool is missing or the hook wiring is broken, and a fresh clone has one documented command that makes it correct.",
"acceptance_criteria": [
"A pre-push hook exists and runs `pre-commit run --hook-stage pre-push` over the pushed commit range, not over the (empty) staged set",
"Every external binary the hooks invoke is verified on every hook run; a missing one is auto-installed via non-interactive pacman or aborts the operation with the exact command to run",
"No code path in either hook skips a check because a tool is absent",
"core.hooksPath drift is repaired automatically by any hook run",
"meta/scripts/install_hooks.sh wires a fresh clone, and --check reports the state without changing anything and exits non-zero when incomplete",
"All four shell files pass `shellcheck -x -S warning` with no suppression comments added",
"The requirement is documented in CLAUDE.md, including why `pre-commit install` must not be used"
],
"out_of_scope": [
"Porting the hook setup to kuhy's other repositories — noted as a follow-up, not done here",
"Changing which hooks .pre-commit-config.yaml defines, or their stages",
"Fixing the pre-existing codespell and check-python-location failures surfaced in another session's concurrently staged billsplit work",
"Using `pre-commit install` — it writes to .git/hooks/, which git ignores entirely while core.hooksPath is set"
],
"verifier": "shellcheck -x -S warning over all four scripts; ./meta/scripts/install_hooks.sh --check (exit 0 healthy, exit 1 on drift); require_tool against a non-existent package (must abort); ensure_hooks_path against an unset config (must repair); and a real commit + push through the hooks themselves"
}