Skip to content

Commit d17f352

Browse files
claude[bot]claude
andauthored
fix(tooling): walk the references/ subtrees in check:skill-frame-sync (#15060)
The anti-dormancy scan skipped every directory named `references`, an idiom carried over from the prose gates over `content/docs`, where that directory is generated from spec. This gate never walks `content/docs`; under its two scan roots (`.claude`, `skills`) the same name holds hand-written operative prose, so the skip hid 32 of 72 markdown files - 44% of the population - from the half of the gate that exists to notice a new copy of the decision frame. Drop `references` from SCAN_SKIP_DIRS, with the reason it was there and why it does not hold recorded on the set. No narrowed carve-out for the generated pages under `skills/**`: a fingerprint hit there would mean the generator emits a copy, which is a real finding whose fix site is the generator. Pin the population in the self-test from the walk's own output rather than as a count, so restoring the skip goes red and names itself. Before: 2 copies isomorphic, 40 markdown files scanned. After: 2 copies isomorphic, 72 markdown files scanned, 0 undeclared copies. Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox Co-authored-by: Claude <noreply@anthropic.com>
1 parent fc58a99 commit d17f352

1 file changed

Lines changed: 89 additions & 2 deletions

File tree

scripts/check-skill-frame-sync.mjs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,57 @@ const SCAN_ROOTS = ['.claude', 'skills'];
342342
* than re-spelled, so re-scoping a root cannot leave this describing the old one.
343343
*/
344344
const ROOT_DIR_WATCH_HINTS = ['skills/**'];
345-
const SCAN_SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'references']);
345+
/**
346+
* Directories the anti-dormancy walk refuses to descend into: trees that are
347+
* INSTALLED or GENERATED, where nothing hand-written lives.
348+
*
349+
* ## Why `references` is NOT one of them (#15056)
350+
*
351+
* It was, until this edit, carried over from the idiom the repo's prose gates
352+
* share — and that idiom states its reason outright, in
353+
* `check-corpus-claim-drift.mjs`:
354+
*
355+
* > Generated `references/` is skipped: the spec source is the fix site
356+
* > there, so a finding in a generated file names the wrong file.
357+
*
358+
* That sentence is about `content/docs/references/`, which IS generated from
359+
* spec. This gate never walks `content/docs`. Under SCAN_ROOTS the same
360+
* directory NAME holds hand-written operative prose: the PM protocol files
361+
* under `.claude/skills/pm-dispatch/references/` — `decision-analysis.md`
362+
* among them, whose subject matter IS escalation analysis and so is the single
363+
* likeliest place for a third copy to be pasted — and the published reference
364+
* companions that ship verbatim to third parties via `npx skills add`, the
365+
* exact distribution path this gate's header gives as the reason the published
366+
* copy is watched at all. So the reason did not hold here, and the skip hid 32
367+
* of 72 markdown files (44%) from the half of this gate whose entire job is to
368+
* notice a copy of the frame appearing somewhere new. A scan that cannot see
369+
* 44% of its population is the #4690 anti-pattern one directory level down: it
370+
* runs, it passes, and it reports nothing about the files it never opened.
371+
*
372+
* Two sibling gates over this same corpus already carry the corrected verdict.
373+
* `check-skill-identifier-liveness.mjs` keeps `references` OUT of its skip set
374+
* and pins the difference in its self-test ("under `skills/**` those files are
375+
* hand-authored published content, unlike content/docs/references which is
376+
* generated"). `check-doc-authoring.mjs` gave its published-catalog rule a
377+
* SECOND walk rather than reuse the skipping one, because reusing it "would
378+
* have produced a gate that runs, passes, and cannot see a ninth of the
379+
* population it exists to guard".
380+
*
381+
* Hence no narrowed carve-out for the generated pages under `skills/**` either
382+
* (`references/_index.md`, `references/react-blocks.md`). That is the same
383+
* refusal `check-doc-authoring.mjs` records beside them — "an exemption over a
384+
* surface that no longer needs one is where the next regeneration would smuggle
385+
* one back in. A red here is fixed AT THE SPEC SOURCE, never by hand-editing
386+
* the artifact" — and it holds a fortiori here: a fingerprint hit in a
387+
* generated page would mean the GENERATOR is emitting a copy of the frame,
388+
* which is a real finding whose fix site is the generator, not a false one.
389+
*
390+
* The self-test pins this from the WALK's own output rather than as a file
391+
* count: a count has to be re-typed whenever a page is added, and the failure
392+
* it must catch — "the scan reaches no reference page at all" — is not a
393+
* statement a count can make.
394+
*/
395+
const SCAN_SKIP_DIRS = new Set(['node_modules', '.git', 'dist']);
346396
const SCAN_EXTENSIONS = ['.md', '.mdx'];
347397

348398
function matchAllOf(text, source) {
@@ -935,11 +985,48 @@ function selfTest() {
935985
for (const f of declFailures) console.error(` ✗ dispatch-gates declaration: ${f}`);
936986
failed += declFailures.length;
937987

988+
// ── The scan population (#15056) ──────────────────────────────────────────
989+
//
990+
// What SCAN_SKIP_DIRS leaves out IS the anti-dormancy half's reach, and a
991+
// skip is invisible in the pass line: "40 markdown files scanned" reads
992+
// exactly like 72 to anyone not counting. `references` sat in that set for
993+
// reasons belonging to a corpus this gate does not walk, and hid 44% of the
994+
// population — see the SCAN_SKIP_DIRS docblock.
995+
//
996+
// Pinned from the WALK, on the real tree, by the same call `main()` makes —
997+
// never as a hand-typed file count. A count has to be re-typed whenever a
998+
// page is added, and it cannot state the thing that actually went wrong:
999+
// "the scan reaches no reference page at all".
1000+
const INSTALLED_OR_GENERATED = new Set(['node_modules', '.git', 'dist']);
1001+
const popFailures = [];
1002+
let popCases = 0;
1003+
const pop = (label, ok) => { popCases += 1; if (!ok) popFailures.push(label); };
1004+
const walked = SCAN_ROOTS.flatMap((root) => walkMarkdown(root, []));
1005+
const inReferences = walked.filter((f) => /(^|[\\/])references[\\/]/.test(f.file));
1006+
pop('the walk reaches the `references/` subtrees under SCAN_ROOTS — hand-written '
1007+
+ 'operative prose, not an installed or generated tree. Restoring the skip empties '
1008+
+ 'this and every gate stays green, which is the defect this case exists for',
1009+
inReferences.length > 0);
1010+
pop('the walk reaches more than those subtrees, so the case above is judging a real '
1011+
+ 'population rather than passing on a coincidence',
1012+
walked.length > inReferences.length);
1013+
pop('every SCAN_SKIP_DIRS entry is DECLARED installed-or-generated here — the '
1014+
+ 'criterion the three surviving entries meet and `references` never did, so a '
1015+
+ 'fourth entry has to be stated rather than appended',
1016+
[...SCAN_SKIP_DIRS].every((d) => INSTALLED_OR_GENERATED.has(d)));
1017+
for (const f of popFailures) console.error(` ✗ scan population: ${f}`);
1018+
failed += popFailures.length;
1019+
9381020
if (failed > 0) {
9391021
console.error(`\n✗ check-skill-frame-sync self-test failed (${failed} case(s)).`);
9401022
process.exit(1);
9411023
}
942-
console.log(`✓ check-skill-frame-sync self-test: ${cases.length} cases pass, plus 5 dispatch-gates declaration cases.`);
1024+
console.log(
1025+
`✓ check-skill-frame-sync self-test: ${cases.length} cases pass, plus 5 `
1026+
+ `dispatch-gates declaration cases and ${popCases} scan-population cases `
1027+
+ `(${walked.length} markdown files walked under the scan roots, `
1028+
+ `${inReferences.length} of them inside a references/ directory).`,
1029+
);
9431030
selfTestReachedVerdict = true;
9441031
}
9451032

0 commit comments

Comments
 (0)