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:
@@ -1783,7 +1783,14 @@ fi
|
||||
# merge took out with the script, and cases 28-30 cannot backstop it. They key
|
||||
# on `Kyberforge.VagueWording` and `KyberforgeCopilot.ProactivePhrase`, so
|
||||
# DescriptionOpener, PaddingPhrase, SentenceOpenerThereIs and CompositionNote
|
||||
# can each be retired underneath a passing probe.
|
||||
# are invisible to them.
|
||||
#
|
||||
# Which is a statement about the LEVEL of each rule, and only that. It used to
|
||||
# read as though those four rules were uncovered outright, and they were: case
|
||||
# 35 at the end of this file is what closed that, enumerating the style
|
||||
# directories at run time and demanding an alert from every rule it finds. The
|
||||
# two cases are complementary and neither subsumes the other — 35 proves a rule
|
||||
# still matches text, this one proves the match is still blocking.
|
||||
#
|
||||
# The original's comments, verbatim:
|
||||
#
|
||||
@@ -2364,6 +2371,124 @@ EOF_MUT34
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 35. Every shipped rule actually fires on a fixture ---------------------
|
||||
#
|
||||
# The last coverage class the deleted scripts/check-vale-style-sync.sh and its
|
||||
# consumer suite took with them (ADR-0025). Cases 28-31 keep a rule LOADED, at
|
||||
# `error`, and in scope; none of them asks whether the rule still MATCHES
|
||||
# anything. Measured: rewriting CompositionNote.yml's tokens so they match no
|
||||
# text left this suite at 63/63 passed — the rule shipped, was loaded, was
|
||||
# blocking, and was inert.
|
||||
#
|
||||
# The rule list is discovered from the style directories at run time, never
|
||||
# hardcoded, and a discovered rule with no fixture row is a FAILURE rather than
|
||||
# a silent skip. That direction is the one that decays: a hardcoded list lets
|
||||
# rule #7 ship uncovered, and a fixture table read as "check the rows I have"
|
||||
# does exactly the same.
|
||||
VALE_ASSETS35="$FACTORY_AUDIT/assets/vale"
|
||||
|
||||
# `<Style>.<Rule>` for every rule file under styles/Kyberforge*/, which is how
|
||||
# vale itself names an alert.
|
||||
discovered_rules35() {
|
||||
local dir rule style name
|
||||
for dir in "$VALE_ASSETS35"/styles/Kyberforge*/; do
|
||||
[[ -d "$dir" ]] || continue
|
||||
style="${dir%/}"
|
||||
style="${style##*/}"
|
||||
for rule in "$dir"*.yml; do
|
||||
[[ -f "$rule" ]] || continue
|
||||
name="${rule##*/}"
|
||||
printf '%s.%s\n' "$style" "${name%.yml}"
|
||||
done
|
||||
done | sort
|
||||
}
|
||||
|
||||
# `<Style>.<Rule>|<path>|<description>|<body>`. The path decides which [glob]
|
||||
# section of .vale.ini applies, so KyberforgeCopilot's row has to be an
|
||||
# .agent.md file — that style is loaded nowhere else (ADR-0013). Each fixture
|
||||
# carries exactly the one trigger its rule is about; the rest of the text is
|
||||
# deliberately clean, so an alert for the wrong rule cannot satisfy the row.
|
||||
RULE_FIXTURES35="$(
|
||||
cat << 'EOF_FIX35'
|
||||
Kyberforge.CompositionNote|composition/SKILL.md|Use when the caller wants a cross-cutting probe. Do not use for anything else.|Body text.
|
||||
Kyberforge.DescriptionOpener|opener/SKILL.md|This is the description opener under test. Do not use for anything else.|Body text.
|
||||
Kyberforge.PaddingPhrase|padding/SKILL.md|Use when the caller wants a probe. Do not use for anything else.|See references for more info.
|
||||
Kyberforge.SentenceOpenerThereIs|sentence-opener/SKILL.md|Use when the caller wants a probe. Do not use for anything else.|There is a defect here.
|
||||
Kyberforge.VagueWording|vague/SKILL.md|Use when the caller helps with a probe. Do not use for anything else.|Body text.
|
||||
KyberforgeCopilot.ProactivePhrase|proactive/probe.agent.md|Use when the caller wants a probe. Use proactively.|Body text.
|
||||
EOF_FIX35
|
||||
)"
|
||||
|
||||
echo ""
|
||||
echo "--- every shipped Vale rule has a fixture, and every fixture names a shipped rule (no vale needed) ---"
|
||||
|
||||
DISCOVERED35="$(discovered_rules35)"
|
||||
UNCOVERED35=""
|
||||
STALE_FIXTURES35=""
|
||||
while IFS= read -r RULE35; do
|
||||
[[ -n "$RULE35" ]] || continue
|
||||
FOUND35=false
|
||||
while IFS='|' read -r FID35 _; do
|
||||
[[ "$FID35" == "$RULE35" ]] && { FOUND35=true; break; }
|
||||
done <<EOF_COV35
|
||||
$RULE_FIXTURES35
|
||||
EOF_COV35
|
||||
[[ "$FOUND35" == true ]] || UNCOVERED35+="$RULE35 "
|
||||
done <<EOF_RULES35
|
||||
$DISCOVERED35
|
||||
EOF_RULES35
|
||||
while IFS='|' read -r FID35 _; do
|
||||
[[ -n "$FID35" ]] || continue
|
||||
grep -qxF "$FID35" <<< "$DISCOVERED35" || STALE_FIXTURES35+="$FID35 "
|
||||
done <<EOF_STALE35
|
||||
$RULE_FIXTURES35
|
||||
EOF_STALE35
|
||||
|
||||
if [[ -z "$DISCOVERED35" ]]; then
|
||||
fail "no rule files were found under $VALE_ASSETS35/styles/Kyberforge*/ — either the styles were gutted or this discovery no longer reaches them, and every assertion below would be vacuous"
|
||||
elif [[ -n "$UNCOVERED35" ]]; then
|
||||
fail "shipped Vale rule(s) have no fixture row, so nothing proves they still match anything: $UNCOVERED35"
|
||||
elif [[ -n "$STALE_FIXTURES35" ]]; then
|
||||
fail "fixture row(s) name a rule that no longer ships, so those rows prove nothing about the live styles: $STALE_FIXTURES35"
|
||||
else
|
||||
pass "all $(printf '%s\n' "$DISCOVERED35" | wc -l | tr -d ' ') shipped rule(s) have a fixture row, and every row names a live rule"
|
||||
fi
|
||||
|
||||
if [[ "$VALE_READY" == true ]]; then
|
||||
echo ""
|
||||
echo "--- and each of those fixtures actually raises its own rule's alert ---"
|
||||
TREE35="$(mktemp -d)"
|
||||
new_fixture "$TREE35"
|
||||
INERT35=""
|
||||
while IFS='|' read -r RID35 RPATH35 RDESC35 RBODY35; do
|
||||
[[ -n "$RID35" ]] || continue
|
||||
mkdir -p "$TREE35/$(dirname "$RPATH35")"
|
||||
{
|
||||
echo "---"
|
||||
echo "name: probe"
|
||||
echo "description: $RDESC35"
|
||||
echo "---"
|
||||
echo ""
|
||||
echo "$RBODY35"
|
||||
} > "$TREE35/$RPATH35"
|
||||
# vale exits non-zero merely for HAVING alerts, which is the expected
|
||||
# outcome for every row here, hence the `|| true`.
|
||||
REPORT35="$( { (cd "$TREE35" && vale --config "$VALE_ASSETS35/.vale.ini" "$RPATH35" 2>&1) | sed -E 's/\x1b\[[0-9;]*m//g'; } || true)"
|
||||
if ! grep -qE 'in [0-9]+ files?\.' <<< "$REPORT35"; then
|
||||
INERT35+="[$RID35: vale printed no summary line for $RPATH35, so it did not run: ${REPORT35:-<no output>}] "
|
||||
elif ! grep -qF "$RID35" <<< "$REPORT35"; then
|
||||
INERT35+="[$RID35 raised no alert on its own fixture: ${REPORT35:-<no output>}] "
|
||||
fi
|
||||
done <<EOF_FIRE35
|
||||
$RULE_FIXTURES35
|
||||
EOF_FIRE35
|
||||
if [[ -n "$INERT35" ]]; then
|
||||
fail "a shipped rule matched nothing on the fixture written for it — it is loaded and blocking but inert, which is indistinguishable from a passing file: $INERT35"
|
||||
else
|
||||
pass "every shipped Vale rule raises its own alert on the fixture written for it"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
[[ $FAIL -eq 0 ]] || exit 1
|
||||
|
||||
Reference in New Issue
Block a user