Skip to content

[finding] check:ratchet-remedy-authority sweeps scripts/** but declares no path, so no card that edits a gate script can derive it — the one population it exists to police #11935

Description

@os-zhuang

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

constSCRIPTS_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:

constROOT_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.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions