Sibling of #9964, found while measuring it. That card fixed check:pm-skill-ratchet alone; the measurement showed the same hole open in five other gate families, and the fix for each is now a one-line declaration with a landed precedent.
The measurement
Measuring the option-2 extractor widening (114 families x 6326 tracked files) enumerated every (family, root file) pair that a bare top-level *.md literal would create. Nine of the seventeen are genuine — the gate really reads that repo-root file from a module-body literal — and all nine except the ratchet's are still invisible to dispatch-gates:
| gate | root file it really reads | where the literal lives |
|---|
check:pm-skill-id-lint | AGENTS.md | EXTRA_FILES = ['.claude/agents/os-dev.md', 'AGENTS.md'] |
check:pm-governed-merges | AGENTS.md, CLAUDE.md | GOVERNED_SURFACES rows agents-md / claude-md (exact:) |
check:pm-governed-prose | AGENTS.md | register entry path: 'AGENTS.md' |
check:docs-audit-scope | AGENTS.md | const AGENTS_REL = 'AGENTS.md' |
check:required-contexts | AGENTS.md | INSTRUCTION_SURFACES entry file: 'AGENTS.md' |
check:doc-anchors | README.md, ARCHITECTURE.md | const EXTRA_SOURCES = ['README.md', 'ARCHITECTURE.md'] |
So an AGENTS.md-only card today derives one gate where six genuinely read the file, and a README.md or ARCHITECTURE.md card still derives zero while check:doc-anchors is REQUIRED in lint.yml and is the repo's only fragment coverage.
check-doc-anchors.mjs already says so about itself, in its own docblock: "README.md or ARCHITECTURE.md still has to reach this gate by judgment". That line was written before the declaration form existed.
Why this is now cheap
The extractor is deliberately closed to bare top-level filenames and should stay closed — re-measured under #9964, widening it costs 17 new pairs of which 8 are fabricated (gates spell README.md / CHANGELOG.md as basenames they join with a package directory), so a README.md card would gain six leads of which five are false. That refusal is correct and should not be revisited.
What changed is that #9964 established the per-gate escape hatch: declare the root-file population as the subtree spelling (AGENTS.md/**), which extractWatchHints already accepts and collapseHint reduces back to the single file. Measured cost of the ratchet's declaration: +1 watch-hint pair, one family, one file, zero fabricated. Each gate above is the same one-line change plus a self-test pin.
Scope note
Deliberately not folded into #9964's PR: those are five other gates, so it is a different file surface and a different set of gate self-tests — outside that card's claimed files. Filing it rather than fixing it in passing.
Each row is independently landable, so this may be worth splitting per gate at triage; check:doc-anchors is the highest-value one (REQUIRED, unique coverage, and its own docblock already flags the gap).
Generated by Claude Code
Sibling of #9964, found while measuring it. That card fixed
check:pm-skill-ratchetalone; the measurement showed the same hole open in five other gate families, and the fix for each is now a one-line declaration with a landed precedent.The measurement
Measuring the option-2 extractor widening (114 families x 6326 tracked files) enumerated every (family, root file) pair that a bare top-level
*.mdliteral would create. Nine of the seventeen are genuine — the gate really reads that repo-root file from a module-body literal — and all nine except the ratchet's are still invisible todispatch-gates:check:pm-skill-id-lintAGENTS.mdEXTRA_FILES = ['.claude/agents/os-dev.md', 'AGENTS.md']check:pm-governed-mergesAGENTS.md,CLAUDE.mdGOVERNED_SURFACESrowsagents-md/claude-md(exact:)check:pm-governed-proseAGENTS.mdpath: 'AGENTS.md'check:docs-audit-scopeAGENTS.mdconst AGENTS_REL = 'AGENTS.md'check:required-contextsAGENTS.mdINSTRUCTION_SURFACESentryfile: 'AGENTS.md'check:doc-anchorsREADME.md,ARCHITECTURE.mdconst EXTRA_SOURCES = ['README.md', 'ARCHITECTURE.md']So an
AGENTS.md-only card today derives one gate where six genuinely read the file, and aREADME.mdorARCHITECTURE.mdcard still derives zero whilecheck:doc-anchorsis REQUIRED inlint.ymland is the repo's only fragment coverage.check-doc-anchors.mjsalready says so about itself, in its own docblock: "README.mdorARCHITECTURE.mdstill has to reach this gate by judgment". That line was written before the declaration form existed.Why this is now cheap
The extractor is deliberately closed to bare top-level filenames and should stay closed — re-measured under #9964, widening it costs 17 new pairs of which 8 are fabricated (gates spell
README.md/CHANGELOG.mdas basenames they join with a package directory), so aREADME.mdcard would gain six leads of which five are false. That refusal is correct and should not be revisited.What changed is that #9964 established the per-gate escape hatch: declare the root-file population as the subtree spelling (
AGENTS.md/**), whichextractWatchHintsalready accepts andcollapseHintreduces back to the single file. Measured cost of the ratchet's declaration: +1 watch-hint pair, one family, one file, zero fabricated. Each gate above is the same one-line change plus a self-test pin.Scope note
Deliberately not folded into #9964's PR: those are five other gates, so it is a different file surface and a different set of gate self-tests — outside that card's claimed files. Filing it rather than fixing it in passing.
Each row is independently landable, so this may be worth splitting per gate at triage;
check:doc-anchorsis the highest-value one (REQUIRED, unique coverage, and its own docblock already flags the gap).Generated by Claude Code