fix(gates): close six PR #135 review findings in gates and their tests

B1: check-skill-version-bump.sh resolves every merge-base with `git merge-base
--all` instead of the single base git happens to pick. A criss-cross history has
two, so the verdict turned on that choice: a skill byte-identical to main's tip
could still be reported "not above merge-base" / "not above main tip" and fail a
push that should pass. A skill now counts as changed only when it differs from
EVERY base, and its version must exceed the version at every base it exists at
as well as at the main tip; with more than one base the failure names which one.
Case 40 in tests/test-skill-version-bump.sh builds the criss-cross fixture and
pins both directions.

B2: check-apm-current.sh no longer assumes the remote default branch is `main`
when origin/HEAD is unset. A checkout whose default is `master` was standing on
its default branch and being told "this is a feature branch, so discard it" --
to throw away a real lock update. With origin/HEAD unset nothing is asserted and
the neutral advice stands. tests/test-apm-current-hook.sh covers the unset case
on both `main` and `master`.

#4: the required-frontmatter checks folded into skill-size-check.sh by c8a7c9e
were untested apart from the leading-zero shape -- mutating the missing-version
ERROR into a no-op left every suite green. tests/test-adr0020-frontmatter.sh now
pins name presence and non-emptiness, metadata.version presence and semver
shape, and the four grep defects the deleted test-skill-frontmatter.sh named.

#5: nothing asked whether a Vale rule still MATCHES anything -- rewriting
CompositionNote.yml's tokens to match nothing left test-vale-wrap.sh at 63/63.
Case 35 enumerates the rule files under the Kyberforge* style directories at run
time, requires an alert from each on its own fixture, and fails when a
discovered rule has no fixture row. The stale comment at case 31 is corrected.

#6: tests/run-tests.sh --strict exited 0 when discovery found no test-*.sh at
all; strictness only ever acted on skips, and with no suites there were none. It
now cross-checks the git index the way run-bats.sh does and fails
unconditionally on an empty set, naming the search root.

N9: the skill-size-check hook description in .pre-commit-config.yaml covered
only the size, context-budget and boundary-target gates. It now also names the
required frontmatter fields, matching docs/spec/gates.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwD8Egs5r4ndqeFLmhusX2
This commit is contained in:
2026-09-19 21:08:10 +00:00
parent e62188c3c3
commit ea119d83b0
9 changed files with 693 additions and 49 deletions

View File

@@ -150,6 +150,23 @@ if command -v git > /dev/null 2>&1; then
commit -q --allow-empty -m init
touch "$REPO/apm.lock.yaml"
# origin/HEAD is unset here — git writes it on clone and `git remote add` does
# not — so the hook cannot know what the default branch is and must not guess.
# It used to assume `main`, which is why the `master` case further down was a
# live defect.
out="$(run_hook_in "$REPO" "$REPO")"
advice="$(advice_of "$out")"
grep -q "commit it or discard it deliberately" <<< "$advice" \
&& pass "with origin/HEAD unset, keeps the neutral lock advice" \
|| fail "with origin/HEAD unset the advice should stay neutral: $advice"
# Needles are the two DECISION phrases, not the bare words: the fixed prefix
# of every notice already says "behind the remote default branch".
grep -qE "this is (the default|a feature) branch, so" <<< "$advice" \
&& fail "with origin/HEAD unset the hook must not claim to know which branch this is: $advice" \
|| pass "with origin/HEAD unset, claims nothing about which branch this is"
git -C "$REPO" update-ref refs/remotes/origin/main HEAD
git -C "$REPO" symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
out="$(run_hook_in "$REPO" "$REPO")"
grep -q "default branch, so commit it or discard it deliberately" <<< "$(advice_of "$out")" \
&& pass "on main, says to commit or discard the lock deliberately" \
@@ -174,6 +191,26 @@ if command -v git > /dev/null 2>&1; then
grep -q "default branch, so commit it" <<< "$(advice_of "$out")" \
&& pass "reads the default branch from origin/HEAD when it is set" \
|| fail "should treat origin/HEAD's branch as the default: $(advice_of "$out")"
# The regression the `${default_branch:-main}` fallback caused: a repo whose
# default branch is `master`, with origin/HEAD unset (no clone wrote it), was
# standing on its DEFAULT branch and was told to discard the lock as feature
# churn. Assuming `main` is the only way to reach that verdict, so the case is
# pinned on the branch name that makes the assumption wrong.
MASTER_REPO="$WORK/master-repo"
mkdir -p "$MASTER_REPO"
git -C "$MASTER_REPO" init -q -b master
git -C "$MASTER_REPO" -c user.email=probe@example.invalid -c user.name=probe \
commit -q --allow-empty -m init
touch "$MASTER_REPO/apm.lock.yaml"
out="$(run_hook_in "$MASTER_REPO" "$MASTER_REPO")"
advice="$(advice_of "$out")"
grep -qF "feature branch, so discard it" <<< "$advice" \
&& fail "on master with origin/HEAD unset the hook assumed main and told the reader to discard a real lock update: $advice" \
|| pass "on master with origin/HEAD unset, does not misread the default branch as a feature branch"
grep -q "commit it or discard it deliberately" <<< "$advice" \
&& pass "on master with origin/HEAD unset, falls back to the neutral lock advice" \
|| fail "on master with origin/HEAD unset the advice should be neutral: $advice"
else
echo " (git not on PATH — branch-specific advice cases not run)"
fi