Skip to content

Commit 3195fff

Browse files
committed
fix(devx): re-adjudicate 17 bare-root verdicts whose "unspellable" basis is false
#12300 taught `hintCovers` to MATCH a glob in a non-final segment instead of collapsing it. That retirement was general, but 17 of the TRIAGE map's 26 REFUSE-UNSPELLABLE verdicts were written against the collapse, so each kept asserting "no precise spelling exists" about a population that now has one — silently, with `--self-test` green, because the self-test audits keys and verdicts and never what a `why` SAYS. Re-measured all 17 on post-merge main, driven through the real hintCovers and trackedFiles. Every refusal was false of the tree. Split by measured downstream pull, per the maintainer ruling: - 15 rows with no recorded consumer move to a new verdict, SPELLABLE-UNDECLARED — "a precise live spelling exists; declaration deferred, reason recorded". Every record carries a `spelling`, and `--self-test` pins each spelling LIVE, PRECISE and COMPLETE in hintCovers' own terms against the live corpus. An unpinned vocabulary value is the allowlist rot the ruling rejects the option on. - 2 rows with measured pull get their spelling declared beside their population constant under the ROOT_DIR_WATCH_HINTS idiom, and their refusal withdrawn to DECLARED-NARROWER: check:objectql-double-limit reaches 0 -> 2755 of its test corpus, check:skill-refs 9 -> 12 of its reference files. One row was re-measured into a different population, not merely fresher digits: #12392 made check-skills-token-ratchet walk skill directories recursively, so its old 11-of-50 filename filter is now a 49-file subtree. Also fixes a phantom assertion in the new declaration pin: spelling the needle as a literal lets `includes` find it in the assertion's own source. The needle is assembled instead. The same shape in check-cli-command-ids is filed as #12472, unfixed here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
1 parent 68c5dba commit 3195fff

3 files changed

Lines changed: 501 additions & 117 deletions

File tree

packages/spec/scripts/build-skill-references.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,44 @@ const SPEC_SRC = path.resolve(__dirname, '../src');
3636
const SKILLS_DIR = path.resolve(REPO_ROOT, 'skills');
3737
const SPEC_PKG = '@objectstack/spec';
3838

39+
/**
40+
* ## The dispatch-gates declaration — the `ROOT_DIR_WATCH_HINTS` idiom (#12310)
41+
*
42+
* `scripts/pm/dispatch-gates.mjs` derives WHICH gates a card must run by matching the
43+
* path literals in each gate's source against the card's changed files. Every literal it
44+
* could recover from THIS file was an import specifier (`lib/export-list`,
45+
* `@objectstack/spec`, the `*.zod.ts` entries of SKILL_MAP) — none of them a repo path —
46+
* plus the emitted-surface string below, which reaches the 9 `_index.md` files this
47+
* generator writes and nothing else. `SKILLS_DIR` resolves to a bare single-segment word
48+
* that `hintCovers` REFUSES as too generic. So #12310 measured this gate as one no card
49+
* can name: the gate that keeps skill cross-references honest, scoring the same quiet
50+
* green for every card in the tree, `skills/**` cards included.
51+
*
52+
* ⚠️ Until #12300 there was nothing honest to write here. The real population —
53+
* every file inside each managed `references/` folder — collapsed to a double slash no
54+
* tree can hold (#12246), so the only spellable claim was the bare root. #12300 taught
55+
* `hintCovers` to MATCH a glob in a non-final segment instead of collapsing it, and the
56+
* spelling below is now live: measured at 12 of the 12 tracked reference files, 100%
57+
* precise and complete.
58+
*
59+
* ⛔ Only what this generator actually reads belongs here, and it reads the FOLDER, not
60+
* just its own output: `manageDir(refsDir, ownsReferenceEntry(refsDir))` enumerates each
61+
* `skills/<name>/references/` and prunes it wholesale, so hand-written notes and
62+
* `react-blocks.md` are inputs to the `--check` verdict too. Declaring only the
63+
* `_index.md` files would under-name it by three; declaring the bare `skills` root would
64+
* name this gate for every SKILL.md and every eval fixture it never opens — a fabricated
65+
* lead, which `hintCovers` prices above the silence it would cure.
66+
*
67+
* ⛔ It must be spelled as a LITERAL, not built from `SKILLS_DIR` — the hint extractor
68+
* reads source text, so a computed path would produce no hint and leave this gate exactly
69+
* as invisible. This generator carries no `--self-test` of its own, so the coupling is
70+
* held from the other side, in `scripts/pm/bare-root-worklist.mjs`: that file's recorded
71+
* verdict for this row carries a LIVENESS and PRECISION pin over the live corpus, and
72+
* reds if this declaration ever over-names or goes dead. A declaration that can drift
73+
* from the scan is worse than none — it replaces a silent gate with a lying one.
74+
*/
75+
export const ROOT_DIR_WATCH_HINTS = ['skills/*/references/**'];
76+
3977
const CHECK = process.argv.includes('--check');
4078
const { emit, manageDir, flush } = createSink({ check: CHECK, repoRoot: REPO_ROOT });
4179

scripts/check-objectql-double-limit.mjs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,38 @@ const repoRoot = resolve(__dirname, '..');
230230
const BASELINE_PATH = 'scripts/objectql-double-limit.baseline.json';
231231
const SCAN_ROOT = 'packages';
232232

233+
/**
234+
* ## The dispatch-gates declaration — the `ROOT_DIR_WATCH_HINTS` idiom (#12322)
235+
*
236+
* `scripts/pm/dispatch-gates.mjs` derives WHICH gates a card must run by matching the
237+
* path literals in each gate's source against the card's changed files. `SCAN_ROOT`
238+
* above is a bare single-segment word, which `hintCovers` REFUSES as too generic (a
239+
* measured refusal, +139084 fabricated pairs, and it stays). The population sentence
240+
* this gate does state — every `*.test.ts` under that root — lives in a COMMENT, which
241+
* the hint extractor masks by design. So before this declaration the derivation reached
242+
* this gate for 0 of the ~2755-file test corpus: #12322 measured it, and a dev adding a
243+
* hand-written ObjectQL double ran a full green local union with no signal that the gate
244+
* exists to fail on it.
245+
*
246+
* ⚠️ Until #12300 there was nothing honest to write here. Every glob spelling of this
247+
* population collapsed to a malformed double-separator prefix matching NOTHING, so the
248+
* only spellable claim was the bare root — TRUE of 2755 test files and FALSE of the
249+
* ~2500 non-test files under the same root, the costlier error, since a find double can
250+
* only land in a test file. #12300 taught `hintCovers` to MATCH a glob in a non-final
251+
* segment instead of collapsing it, so the precise spelling below is live: measured at
252+
* 2755 of 2755 on this tree, 100% precise and complete against `testFilesUnder`'s own
253+
* admission.
254+
*
255+
* ⛔ It must be spelled as a LITERAL, not built from `SCAN_ROOT` — the hint extractor
256+
* reads source text, so a computed `` `${SCAN_ROOT}/**` `` would produce no hint and
257+
* leave the gate exactly as invisible. Both directions are pinned in `--self-test`
258+
* below, and the recorded verdict for this row in `scripts/pm/bare-root-worklist.mjs`
259+
* carries its own liveness-and-precision pin over the live corpus. A declaration that
260+
* can drift from the scan is worse than none — it replaces a silent gate with a lying
261+
* one.
262+
*/
263+
const ROOT_DIR_WATCH_HINTS = ['packages/**/*.test.ts'];
264+
233265
// ---------------------------------------------------------------------------
234266
// The probe vocabulary. Field names are deliberately synthetic so no double can
235267
// special-case them (many branch on `organization_id`, `user_id`, `id`).
@@ -1163,6 +1195,47 @@ async function selfTest() {
11631195
reconcile(fakeWrong, { 'b.test.ts': { blind: 0, wrong: 1 } }).length === 0
11641196
&& reconcile(fakeWrong, { 'b.test.ts': { blind: 1 } }).length === 2);
11651197

1198+
// -- the dispatch-gates declaration (#12322's landing obligation) ---------
1199+
//
1200+
// Enforcement cannot hold either half here: the declaration is read by ANOTHER TOOL
1201+
// (`scripts/pm/dispatch-gates.mjs`), so a wrong or stale one runs green in this file
1202+
// forever and pays itself out as a dev dispatched on a test-file card with this gate
1203+
// missing from the brief. A missing declaration is a silent gate; a surplus one is a
1204+
// lying gate. Driven through this gate's OWN corpus walk, not a copy of its regex.
1205+
const DECLARED_TAIL = '.test.ts';
1206+
const walked = testFilesUnder(join(repoRoot, SCAN_ROOT))
1207+
.map((abs) => relative(repoRoot, abs).replace(/\\/g, '/'));
1208+
// ⚠️ The needle is ASSEMBLED, never spelled. Written as a literal here it would appear
1209+
// in this assertion's own source text, so `includes` would find it in the check rather
1210+
// than in the declaration and stay green with the declaration deleted -- a phantom pin.
1211+
// Measured: the naive spelling survived deleting the whole declaration line.
1212+
const declNeedle = `'${SCAN_ROOT}/` + '*'.repeat(2) + `/*${DECLARED_TAIL}'`;
1213+
const ownSource = readFileSync(fileURLToPath(import.meta.url), 'utf8');
1214+
expect('the declaration is spelled as a LITERAL in this source, not computed -- the hint '
1215+
+ 'extractor reads source text, so a computed root would build no hint at all',
1216+
ownSource.includes(declNeedle));
1217+
expect('the declared hint is rooted at the population constant this gate actually walks',
1218+
ROOT_DIR_WATCH_HINTS.length === 1 && ROOT_DIR_WATCH_HINTS[0].startsWith(`${SCAN_ROOT}/`));
1219+
expect('the corpus walk is non-empty, so the two directions below judge something',
1220+
walked.length > 0);
1221+
expect('nothing is declared that this gate does not walk -- every file testFilesUnder admits '
1222+
+ 'lies under the declared root and ends in the declared extension',
1223+
ROOT_DIR_WATCH_HINTS.every((h) => h.endsWith(DECLARED_TAIL))
1224+
&& walked.every((f) => f.startsWith(`${SCAN_ROOT}/`) && f.endsWith(DECLARED_TAIL)));
1225+
// …and the declared filter is a NARROWING rather than the bare root wearing a glob:
1226+
// a non-test sibling sitting in the same directory as an admitted file is NOT admitted.
1227+
const admitted = new Set(walked);
1228+
const siblingDir = walked.length ? dirname(join(repoRoot, walked[0])) : null;
1229+
const nonTestSibling = siblingDir
1230+
? readdirSync(siblingDir)
1231+
.map((e) => relative(repoRoot, join(siblingDir, e)).replace(/\\/g, '/'))
1232+
.find((f) => !f.endsWith(DECLARED_TAIL) && statSync(join(repoRoot, f)).isFile())
1233+
: null;
1234+
expect('a non-test sibling exists to prove the filter discriminates', Boolean(nonTestSibling));
1235+
expect('and the declaration is a NARROWING, not the bare root wearing a glob -- that sibling '
1236+
+ 'is under the same root and is NOT in the walk',
1237+
Boolean(nonTestSibling) && !admitted.has(nonTestSibling));
1238+
11661239
if (failures.length > 0) {
11671240
console.error(`x check-objectql-double-limit --self-test (${failures.length} failure(s)):\n`);
11681241
for (const f of failures) console.error(` - ${f}`);

0 commit comments

Comments
 (0)