test(gates): pin hook wiring and enforce the bats TAP plan

The repo's gates were not pinned to their wiring. Deleting the
check-skill-version-bump block from .pre-commit-config.yaml left the
whole suite green; deleting eight blocks at once, run-tests among them,
also left it green. Only 4 of 20 hook ids had their wiring pinned
anywhere, so a merge conflict resolved badly could stop the suite
running at pre-push forever while every test still reported green.

test-adr0020-contract now derives the repo-authored hooks from the
repo: local entries and pins each one's id, entry and stages against an
explicit expected set, both directions, with the same non-vacuity
guards the file already applies to its own fixtures. Upstream hooks and
their rev: values are untouched, so a rev bump does not churn the test.
Mutation-checked: a removed block, a repointed entry and a hook moved
off pre-push each go red; a rev bump, a comment edit and reordering
stay green. 29 -> 44 assertions.

run-bats computed each file's TAP plan and then discarded it, so a
process printing "1..10", three ok lines and exit 0 was counted as
"3 tests, 0 failures" with seven tests silently gone. That is exactly
the wrapper-swallows-the-status case the runner's own comment puts in
its threat model, and the plan was the only surviving signal. The plan
is now enforced in both directions when a file emits exactly one.

Also: test-no-pipefail-early-exit-grep's live-tree floor goes from 20 to
50 against an actual 57, matching test-vale-wrap's per-glob discipline,
and test-vale-wrap's header names the real path to vale-wrap.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwD8Egs5r4ndqeFLmhusX2
This commit is contained in:
2026-09-20 12:34:07 +00:00
parent 8cfd54f925
commit 384756b343
5 changed files with 271 additions and 10 deletions

View File

@@ -444,6 +444,38 @@ else
pass "a root under .claude/worktrees/ runs its own files and skips nested worktrees"
fi
# --- 11. A file that emits fewer results than its own plan promised fails the
# run. This is the third aggregation signal, and the only one left standing in
# exactly the case run-bats.sh's own comment puts in its threat model: a bats
# formatter or wrapper that swallows the exit status. A process printing `1..10`,
# three `ok` lines and exiting 0 emits no `not ok` and no non-zero status, so both
# other halves stay silent -- the plan was already being computed for the
# aggregate zero-count guard and was then thrown away, so the run was counted as
# "6 tests, 0 failures" and went green with fourteen tests silently gone.
echo ""
echo "--- a file emitting fewer results than its plan fails the run ---"
DIR11="$(make_fake_repo)"
FIXTURES+=("$DIR11")
seed_bats_files "$DIR11"
install_stub_bats "$DIR11" <<'EOF'
#!/usr/bin/env bash
echo "1..10"
echo "ok 1 first"
echo "ok 2 second"
echo "ok 3 third"
exit 0
EOF
run_fake "$DIR11"
if [[ $FAKE_RC -eq 0 ]]; then
fail "a file delivering 3 of its 10 planned tests exited 0 — a truncated run reported as a pass"
elif ! grep -q "planned 10 test(s) but emitted 3 result line(s)" <<< "$FAKE_OUT"; then
fail "the run failed but not with the plan-shortfall message: $FAKE_OUT"
elif grep -q "^6 tests, 0 failures$" <<< "$FAKE_OUT"; then
pass "a plan promising more tests than were delivered fails the run and names the shortfall"
else
fail "the plan-shortfall run failed with the wrong count: $FAKE_OUT"
fi
echo ""
echo "Results: $PASS passed, $FAIL failed"
[[ $FAIL -eq 0 ]]