Measured while reworking #11052 / PR #11931, where this cost a full round trip. Filed unassigned — out of that card's scope.
What
scripts/check-ratchet-remedy-authority.mjs sweeps every scripts/*.{mjs,mts} for a gate whose author-facing remedy expands a shrink-only registry. It is a required gate (lint.yml, "Type Check · source gates"). But it builds its population as
const SCRIPTS_DIR = join(REPO_ROOT, 'scripts');
— an assembled path, carrying no string literal with a separator. hintCovers in scripts/pm/dispatch-gates.mjs therefore has nothing to match, and the family lands in the Undetermined (source names no path at all — NOT known irrelevant) bucket, 53 families deep.
Verified directly:
$ node scripts/pm/dispatch-gates.mjs --residue scripts/check-doc-authoring.mjs \
scripts/check-skills-token-ratchet.mjs skills/objectstack-ui/SKILL.md
Undetermined (source names no path at all — NOT known irrelevant): 53 famil(ies).
...
- pnpm check:ratchet-remedy-authority [lint.yml]
Why it bites in exactly the wrong place
The gate's population is gate scripts. So the card most likely to violate it — one that adds or edits a gate's remedy text — is precisely the card whose derived list omits it.
That is not hypothetical. PR #11931 added a new corpus rule to check-doc-authoring.mjs whose failure text offered ⛔ Do NOT silence this by adding an EXAMPLE_ID_ALLOWLIST entry…. The derivation named 14 families, all of which were run green locally; this one was not among them, and CI went red on it after the report was filed. The rework was cheap, but the round trip was avoidable and the same shape will recur for the next author who touches a gate message.
Note the asymmetry that hides it: the family does appear in the matched list once the diff touches its own script file (gate script 'scripts/check-ratchet-remedy-authority.mjs'). So a card fixing this gate sees it, and every other card does not.
The fix already has an idiom in-repo
check-doc-authoring.mjs solved the identical problem for its own ROOTS with a provenance-only declaration read by nobody at runtime:
const ROOT_WATCH_HINTS = ['.claude/**', 'docs/**', 'skills/**', 'content/**'];
Its docblock argues the case at length (the #9964 declaration pattern), including why a subtree spelling is required — hintCovers refuses a bare single-segment literal such as scripts as too generic, by design and by measurement. So the remedy here is a scripts/** declaration in that spelling, plus a --self-test case pinning it against the real extractor in both directions (declares what it sweeps; declares nothing it does not).
Worth checking the rest of the 53-family Undetermined bucket in the same pass — this gate is unlikely to be the only sweeper that assembles its root. The bucket is honestly labelled ("NOT known irrelevant"), but a dispatch prompt that says "run every matched family" turns an honest bucket into an unrun one.
Not fixed here
PR #11931 is a skills/** cleanup under a maintainer ruling; adding a declaration to a third gate script — and auditing a 53-family bucket — would be an unmeasured widening riding on a ruled card. scripts/** is not a governed surface, so this needs no special merge handling.
Measured while reworking #11052 / PR #11931, where this cost a full round trip. Filed unassigned — out of that card's scope.
What
scripts/check-ratchet-remedy-authority.mjssweeps everyscripts/*.{mjs,mts}for a gate whose author-facing remedy expands a shrink-only registry. It is a required gate (lint.yml, "Type Check · source gates"). But it builds its population as— an assembled path, carrying no string literal with a separator.
hintCoversinscripts/pm/dispatch-gates.mjstherefore has nothing to match, and the family lands in theUndetermined (source names no path at all — NOT known irrelevant)bucket, 53 families deep.Verified directly:
Why it bites in exactly the wrong place
The gate's population is gate scripts. So the card most likely to violate it — one that adds or edits a gate's remedy text — is precisely the card whose derived list omits it.
That is not hypothetical. PR #11931 added a new corpus rule to
check-doc-authoring.mjswhose failure text offered⛔ Do NOT silence this by adding an EXAMPLE_ID_ALLOWLIST entry…. The derivation named 14 families, all of which were run green locally; this one was not among them, and CI went red on it after the report was filed. The rework was cheap, but the round trip was avoidable and the same shape will recur for the next author who touches a gate message.Note the asymmetry that hides it: the family does appear in the matched list once the diff touches its own script file (
gate script 'scripts/check-ratchet-remedy-authority.mjs'). So a card fixing this gate sees it, and every other card does not.The fix already has an idiom in-repo
check-doc-authoring.mjssolved the identical problem for its own ROOTS with a provenance-only declaration read by nobody at runtime:Its docblock argues the case at length (the
#9964declaration pattern), including why a subtree spelling is required —hintCoversrefuses a bare single-segment literal such asscriptsas too generic, by design and by measurement. So the remedy here is ascripts/**declaration in that spelling, plus a--self-testcase pinning it against the real extractor in both directions (declares what it sweeps; declares nothing it does not).Worth checking the rest of the 53-family
Undeterminedbucket in the same pass — this gate is unlikely to be the only sweeper that assembles its root. The bucket is honestly labelled ("NOT known irrelevant"), but a dispatch prompt that says "run every matched family" turns an honest bucket into an unrun one.Not fixed here
PR #11931 is a
skills/**cleanup under a maintainer ruling; adding a declaration to a third gate script — and auditing a 53-family bucket — would be an unmeasured widening riding on a ruled card.scripts/**is not a governed surface, so this needs no special merge handling.