docs: fix the review findings on the hook-contract retirement

Why: a review of 4de5b6b and 3c5f6a6 found seven places that still
described the retired external hook contract as current, or that could
pass without checking anything.

Implementation Notes:
- gates.md: drop the "one caveat below" pointer; the caveat is gone.
- SIMPLIFICATION-AUDIT.md: the §7 note now says all but one entry is
  closed and lists 36 (4de5b6b) as struck. Finding 2's count chain gets
  a closing note: pre-push is 8 repo-authored hooks (10 reported).
- .pre-commit-config.yaml: the check-vale-style-sync comment points at
  case 32 (one-plugin narrowing guard), not the deleted case 33.
- ADR-0014: the retirement pointer now covers the ADR-0025 amendment
  above it too, naming case 33 and the exported hook IDs. ADR-0022 gets
  a reciprocal amended-by note on its check-release-needed comparison.
  Historical body text is unchanged.
- test-vale-wrap.sh case 32: property 3 fails when a class's corpus
  regex matches no tracked file, instead of passing vacuously after a
  layout move. The corpus regexes are now globals so a new Part D can
  point them at a missing layout and require that failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-16 12:30:42 +00:00
parent 3c5f6a6e37
commit 14248e04b9
6 changed files with 51 additions and 11 deletions

View File

@@ -2002,6 +2002,11 @@ PC_CONFIG32="$REPO_ROOT/.pre-commit-config.yaml"
# repository paths, so a regex matching no tracked path matches nothing the gate
# will ever hand the hook.
REPO_FILES32="$(cd "$REPO_ROOT" && git ls-files)"
# Each class's full authoring-source corpus, per the layout AGENTS.md fixes
# (`plugins/<name>/.apm/` is the only authoring source). Globals, not locals,
# so Part D can point them at a layout that no longer exists.
CORPUS_RE_SKILL32='^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$'
CORPUS_RE_AGENT32='^plugins/[^/]+/\.apm/agents/[^/]+\.agent\.md$'
# Prints one failure token per defect; empty output means every prefilter hook in
# $1 is scoped to a live corpus of its own artifact class. The config path is an
@@ -2010,7 +2015,7 @@ REPO_FILES32="$(cd "$REPO_ROOT" && git ls-files)"
# the live check.
prefilter_scope_failures32() {
local config="$1" files="$2"
local records id re class matched count offenders offending m corpus missing nmissing
local records id re class matched count offenders offending m corpus missing nmissing corpus_re
local seen_skill=false seen_agent=false bad=""
records="$(hook_records28 "$config")"
if [[ -z "$records" ]]; then
@@ -2061,13 +2066,21 @@ EOF_MATCHED32
if [[ "$offending" -gt 0 ]]; then
bad+="[$id: 'files: $re' selects $count file(s), $offending of them outside the $class artifact class, so the two prefilter scopes are no longer independently addressable and vale is handed files no glob in its config covers — first: $offenders] "
fi
# The class's full authoring-source corpus, per the layout AGENTS.md fixes
# (`plugins/<name>/.apm/` is the only authoring source). Every path in it
# must be selected, or part of the corpus is silently unprefiltered.
# Every path in the class's corpus (CORPUS_RE_*32 above) must be
# selected, or part of the corpus is silently unprefiltered.
if [[ "$class" == skill ]]; then
corpus="$(printf '%s\n' "$files" | { grep -E '^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$' || true; })"
corpus_re="$CORPUS_RE_SKILL32"
else
corpus="$(printf '%s\n' "$files" | { grep -E '^plugins/[^/]+/\.apm/agents/[^/]+\.agent\.md$' || true; })"
corpus_re="$CORPUS_RE_AGENT32"
fi
corpus="$(printf '%s\n' "$files" | { grep -E "$corpus_re" || true; })"
# An empty corpus makes the comparison below vacuous: nothing can be
# missing from nothing. The corpus regex is hard-coded to today's layout,
# so a layout move would silently turn this property into a pass. Fail
# instead, the same way property 1 fails on zero matches.
if [[ -z "$(printf '%s\n' "$corpus" | grep . || true)" ]]; then
bad+="[$id: the $class corpus regex '$corpus_re' matches no tracked file, so this case cannot check that 'files: $re' selects the whole $class corpus; update the regex to the current layout] "
continue
fi
missing="$(comm -23 <(printf '%s\n' "$corpus" | grep . | sort) <(printf '%s\n' "$matched" | grep . | sort) || true)"
nmissing="$(printf '%s\n' "$missing" | grep -c . || true)"
@@ -2152,6 +2165,25 @@ else
pass "narrowing either hook's 'files:' regex to one plugin is caught by Part A as an incomplete corpus"
fi
# Part D: proof that property 3 cannot pass vacuously. Each corpus regex is
# pointed at a layout no tracked file has -- what a future move of
# plugins/*/.apm/ would do to the hard-coded regexes -- and the live config must
# then fail as an empty corpus rather than pass with nothing to compare.
SAVED_SKILL_RE32="$CORPUS_RE_SKILL32"
SAVED_AGENT_RE32="$CORPUS_RE_AGENT32"
CORPUS_RE_SKILL32='^zzz-no-such-path/SKILL\.md$'
CORPUS_RE_AGENT32='^zzz-no-such-path/[^/]+\.agent\.md$'
EMPTY_FAILS32="$(prefilter_scope_failures32 "$PC_CONFIG32" "$REPO_FILES32")"
CORPUS_RE_SKILL32="$SAVED_SKILL_RE32"
CORPUS_RE_AGENT32="$SAVED_AGENT_RE32"
if ! grep -qF "vale-audit-prefilter-skill: the skill corpus regex" <<< "$EMPTY_FAILS32"; then
fail "a skill corpus regex matching no tracked file did not fail Part A, so property 3 would pass vacuously after a layout move: ${EMPTY_FAILS32:-<no failure>}"
elif ! grep -qF "vale-audit-prefilter-agent: the agent corpus regex" <<< "$EMPTY_FAILS32"; then
fail "an agent corpus regex matching no tracked file did not fail Part A, so property 3 would pass vacuously after a layout move: ${EMPTY_FAILS32:-<no failure>}"
else
pass "a corpus regex matching no tracked file fails Part A instead of passing vacuously"
fi
# --- 34. Every glob section loads a real style, asserted without vale --------
#
# Case 28 asks this behaviourally -- it lints a probe through the real config