Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion scripts/check-position-name-fold-loaders.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,27 @@ const DECLARED_INSTRUMENTS = [
'scripts/check-position-name-fold-loaders.mjs',
];

const SCAN_ROOTS = ['packages', 'examples', 'apps', 'scripts', 'tools', '.github'];
/**
* The roots the walk visits, spelled as the subtree population this gate
* DECLARES to `scripts/pm/dispatch-gates.mjs` (the `ROOT_DIR_WATCH_HINTS`
* idiom; `check:watch-hint-literal` holds it to a literal array the extractor
* admits). A literal is how the declaration reaches the derivation: the
* extractor reads source text, and a bare single-segment word (`'packages'`)
* yields no hint at all, so while the walk read a private list of bare words a
* card editing any file under these roots was told nothing about this gate and
* met it first in CI (#24037).
*
* ONE list, two readers: {@link SCAN_ROOTS} is DERIVED from it, so the walk and
* the declaration cannot drift apart. ⛔ Never hand-copy the roots back into a
* second literal — the declaration would then be free to say one thing while
* the walk does another, which is the silent-green shape the derivation exists
* to refuse. Every root is live in the tree: a root the tree does not have
* scans nothing and would be printed by the derivation as a dead lead.
*/
export const ROOT_DIR_WATCH_HINTS = ['packages/**', 'examples/**', 'apps/**', 'scripts/**', '.github/**'];

/** The directories the walk descends from, read off {@link ROOT_DIR_WATCH_HINTS}. */
const SCAN_ROOTS = ROOT_DIR_WATCH_HINTS.map((hint) => hint.replace(/\/\*\*$/, ''));

const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', '.turbo', '.next', 'coverage', '.git']);

Expand Down
43 changes: 16 additions & 27 deletions scripts/pm/bare-root-worklist.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -942,31 +942,16 @@ const TRIAGE = new Map([
+ 'skills, IS declared beside the constant: that one is small, is the surface the gate '
+ 'exists for, and naming a skills-only card is the discoverability this idiom is for',
}],
['scripts/check-position-name-fold-loaders.mjs SCAN_ROOTS packages', {
verdict: 'REFUSE-WIDE',
why: 'every text file under the root is walked and any one of them could name the #13419 '
+ 'fixture — 5562 of 5625 (99%), measured 2026-08-31. The population is not a subset of the '
+ 'root that a narrower glob could describe; it IS the root. A true declaration here would '
+ 'name this gate on every card touching a package, which is the precision trade this '
+ 'verdict refuses',
}],
['scripts/check-position-name-fold-loaders.mjs SCAN_ROOTS examples', {
verdict: 'REFUSE-WIDE',
why: '240 of 243 (99%). Refused with its packages half rather than split from it: the loader '
+ 'this gate hunts is a COMPOSITION site, and nothing says one lands under examples/ rather '
+ 'than packages/ — declaring only the small root would read as a claim about where that '
+ 'happens, which is exactly what is not known',
}],
['scripts/check-position-name-fold-loaders.mjs SCAN_ROOTS apps', {
verdict: 'REFUSE-WIDE',
why: '36 of 40 (90%) — same trade, same reason as the examples half',
}],
['scripts/check-position-name-fold-loaders.mjs SCAN_ROOTS scripts', {
verdict: 'REFUSE-WIDE',
why: '295 of 298 (99%). This root is where the gate own allowed readers live '
+ '(DECLARED_INSTRUMENTS), so it is walked wholesale for the same reason as the others: a '
+ 'new reader is found, then classified, never assumed absent',
}],
// WITHDRAWN 2026-10-18 (#24037): the four REFUSE-WIDE rows of
// check-position-name-fold-loaders.mjs SCAN_ROOTS (packages 99%, examples
// 99%, apps 90%, scripts 99%, measured 2026-08-31). Ruling 6243472660 (the
// maintainer's letter B on #24171, term 3) orders that gate's roots made the
// ROOT_DIR_WATCH_HINTS declaration the derivation reads, on a MEASURED
// consumer — PR #24028 reconciled its local gate union complete and met this
// gate red in CI. The gate now declares the whole roots and derives
// SCAN_ROOTS from that literal, so the bare words these rows judged no longer
// exist and the verdicts left with them (the STALE remedy below). The
// measured reasons stand in this file's history at f5ef054a.
['scripts/check-single-authz-resolver.mjs SCAN_ROOTS packages', {
verdict: 'REFUSE-WIDE',
why: 'walks every non-test TS source under the root — 1898 of 4903 (39%). True, and it would '
Expand Down Expand Up @@ -1349,7 +1334,8 @@ const TRIAGE = new Map([
* `check:type-check-coverage`/`check:type-check-debt` 55.8%). Each is measured on this tree by the SAME method as
* every other row here, and the ratio does not change the verdict either
* direction — existing `TRIAGE` rows already span 39% (`check:authz-resolver`)
* to 99% (`scripts/check-position-name-fold-loaders.mjs`) under the identical
* to 99% (`scripts/check-position-name-fold-loaders.mjs`, a row since withdrawn
* when that gate declared its roots — #24037) under the identical
* REFUSE-WIDE trade, wide or narrow within that span is still the same
* width-alone trade. Recorded as measured rather than clipped to the summary
* range; the discrepancy against the issue's own "32–52%" framing is stated
Expand Down Expand Up @@ -2113,7 +2099,10 @@ const SELF_TEST_BATTERIES = Object.freeze({
// ⭐ 43 → 128 (#15926): the loop now runs over EVERY recorded row rather than
// the eleven DECLARED-NARROWER ones, and the RED end of the new direction is
// driven on constructed gates beside it. A FLOOR, measured on a run.
"The DECLARATION a row describes, read from the gate's own SOURCE": 128,
// 128 → 126 (#24037): the loop registers two cases per recorded row, and the
// four check-position-name-fold-loaders rows were withdrawn (134 → 126 on a
// run) — fewer rows to judge, not cases that stopped registering.
"The DECLARATION a row describes, read from the gate's own SOURCE": 126,
'CENSUS_REFUSE_WIDE, pinned on its OWN terms (#14695)': 5,
});

Expand Down
46 changes: 46 additions & 0 deletions scripts/pm/dispatch-gates.self-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3851,6 +3851,52 @@ export function selfTest({ tier = 'full' } = {}) {
);
}

// ── The name-fold loader gate's WALKED roots, declared (#24037) ───────────
//
// `check-position-name-fold-loaders.mjs` reads every text file under its
// roots, and kept them as a private list of bare words, which the extractor
// admits as nothing. A card editing a file under those roots therefore
// derived no command for it: PR #24028 reconciled its local union complete
// and met this gate red in CI, over a line it added to
// `scripts/cross-package-test-inputs.mjs`. The gate now declares the roots
// with the ROOT_DIR_WATCH_HINTS idiom and derives its walk from that literal,
// with no change to the extractor.
//
// Pinned against the LIVE tree, for the reason the scripts/** case above
// states. The probes are read off the family's own extracted hints rather
// than retyped here, so every root the gate declares gets one, and the first
// probe is the exact path the measured miss edited. The ablation strips the
// subtree hints and the verdict must leave MATCHED, so these cases cannot pass
// through another literal that happens to cover a probe.
const NAME_FOLD = 'scripts/check-position-name-fold-loaders.mjs';
const nameFoldFamilies = discoverFamilies().byCheck;
for (const key of [NAME_FOLD, `${NAME_FOLD} --self-test`]) {
const entry = nameFoldFamilies.get(key);
t(`${key} is discovered at all — the pins below mean nothing without this`, Boolean(entry));
const roots = (entry?.hints ?? []).filter((h) => h.endsWith('/**')).map((h) => h.slice(0, -'/**'.length));
// The floor: the five roots the walk reads at this commit. A loop over an
// emptied hint set runs zero cases and prints nothing, which reads as a pass.
t(`${key} declares at least the five subtree roots its walk reads — read: [${roots.join(', ')}]`, roots.length >= 5);
const probes = ['scripts/cross-package-test-inputs.mjs', ...roots.map((r) => `${r}/the-one-nobody-has-written-yet.ts`)];
for (const probe of probes) {
const verdict = entry ? classifyEntry(entry, [probe]).verdict : null;
t(`a change to ${probe}, under a root this gate walks, derives ${key} — read: ${verdict}`, verdict === 'matched');
}
const undeclared = entry ? { ...entry, hints: entry.hints.filter((h) => !h.endsWith('/**')) } : null;
const residual = undeclared
? ['scripts/cross-package-test-inputs.mjs', 'packages/the-one-nobody-has-written-yet/src/index.ts']
.map((p) => classifyEntry(undeclared, [p]).verdict)
: [];
t(
`…and it is the subtree declaration doing it: strip it and ${key} is NOT MATCHED for the measured miss `
+ `or a brand-new package file — read: [${residual.join(', ')}]`,
Boolean(undeclared)
&& undeclared.hints.length < entry.hints.length
&& residual.length === 2
&& residual.every((v) => ['silent', 'undetermined'].includes(v)),
);
}

// ── The MANIFEST population of check:merge-driver (#15501) ────────────────
//
// That family's `git-merge-regen --self-test` refuses a generator with no
Expand Down
Loading