From 57ef9075c6ea1f9cba3b1447673826f39abb52b5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 02:30:44 +0000 Subject: [PATCH] fix(gates): declare the workspace parents as globs, so the dispatch deriver can read the whole population MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `scripts/pm/dispatch-gates.mjs` decides which cards are told to run a gate by scanning that gate's module body for the path literals it operates on, and its covering rule refuses a literal with no path separator as too generic (measured and deliberate: admitting bare top-level words takes it from 19k watch-hint pairs to 158k). `check-test-source-alias.mjs` and `check-type-source-resolution.mjs` each declared their whole population as `WORKSPACE_PARENT_DIRS`, of whose 11 entries 8 carried a separator and 3 did not (`packages`, `apps`, `examples`). So the derivation's answer was decided by WHERE a package happens to sit: measured, 1832 of the 4844 tracked files under packages/ derived check:test-source-alias and the rest did not, the misses being exactly the flat `packages/` layouts plus all of apps/ and examples/. Same test file, two layouts, two different answers, with nothing in the output saying so. Spelling the array as the pnpm-workspace globs it already documents itself to be (`packages/*`, `apps/*`, …) and re-deriving the directory names from it puts a separator in every entry. The walk is unchanged — both gates still report the same package census — and there is no second list to keep in sync. Measured on this tree, whole-corpus, before -> after: matched (family, tracked-file) pairs 37903 -> 43356 (+5453) check:test-source-alias 1833 -> 5116 (+3283) check:type-source-resolution 2946 -> 5116 (+2170) The two deltas sum to the total, so no other family moved; exactly two families resolve to the two changed scripts, measured in both trees. For scale, the alternative #9626 measured and refused (admitting bare top-level literals globally) was +139084, and took `packages/spec/src/index.ts` from 7 matched families to 34. This takes it from 13 to 14. Each gate's self-test now pins the readability property, because the regression is a tidy-up nobody would flag: the live gate stays green when an entry loses its separator, so only an assertion can be loud about it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt --- scripts/check-test-source-alias.mjs | 80 ++++++++++++++++++++---- scripts/check-type-source-resolution.mjs | 80 ++++++++++++++++++++---- 2 files changed, 134 insertions(+), 26 deletions(-) diff --git a/scripts/check-test-source-alias.mjs b/scripts/check-test-source-alias.mjs index ed760b803f..0503dda71d 100644 --- a/scripts/check-test-source-alias.mjs +++ b/scripts/check-test-source-alias.mjs @@ -502,21 +502,55 @@ const KNOWN_UNALIASED_TEST_IMPORTS = { // ── workspace enumeration ─────────────────────────────────────────────────── -/** Directory globs from pnpm-workspace.yaml, which are all `/*`. */ -const WORKSPACE_PARENT_DIRS = [ - 'packages', - 'packages/apps', - 'packages/adapters', - 'packages/connectors', - 'packages/drivers', - 'packages/plugins', - 'packages/qa', - 'packages/services', - 'packages/triggers', - 'apps', - 'examples', +/** + * The workspace globs from pnpm-workspace.yaml, spelled AS GLOBS. + * + * ## Why the `/*` is written out rather than left to the comment (#9955) + * + * This array IS this gate's declared population: every package it walks lives + * directly under one of these parents. `scripts/pm/dispatch-gates.mjs` derives + * the gate list a dispatch brief pastes by scanning each gate's module body for + * the path literals it operates on — so this array is the only thing that tells + * that tool which cards should be sent here. + * + * Its covering rule refuses a literal with NO path separator (`packages`, + * `apps`, `examples`) as too generic, deliberately and measured: admitting bare + * top-level words takes that tool from 19k watch-hint pairs to 158k, because + * `packages` is a path COMPONENT in dozens of gates that never read the root. + * The sanctioned escape is for a gate to declare its own subtree in a spelling + * with a separator in it, which is what these entries now do. + * + * Written as bare directory names, 8 of the 11 entries carried a separator and + * 3 did not, so the derivation's answer for this gate was decided by WHERE a + * package happens to sit: measured on this tree, 1832 of the 4844 tracked files + * under packages/ derived this gate, and the ones that did not were exactly the + * flat `packages/` layouts plus all of apps/ and examples/. A new test in + * a nested package named this gate; the identical test in a flat one did not, + * and nothing in the output said so. That is worse than an honest blind spot — + * it works for a third of the tree, so it reads as working. + * + * The dropped `/*` is re-derived below, so the walk is unchanged and there is + * no second list to keep in sync. Keep the separator in every entry: a tidy-up + * back to bare directory names re-opens the blind spot silently, and the + * self-test case at the bottom of this file is what makes that loud instead. + */ +const WORKSPACE_PARENT_GLOBS = [ + 'packages/*', + 'packages/apps/*', + 'packages/adapters/*', + 'packages/connectors/*', + 'packages/drivers/*', + 'packages/plugins/*', + 'packages/qa/*', + 'packages/services/*', + 'packages/triggers/*', + 'apps/*', + 'examples/*', ]; +/** The parent directories those globs enumerate — each glob minus its leaf. */ +const WORKSPACE_PARENT_DIRS = WORKSPACE_PARENT_GLOBS.map((glob) => glob.replace(/\/\*$/, '')); + const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', 'coverage', '.turbo', '.next', '.cache']); const TEST_FILE = /\.(test|spec)\.[cm]?[jt]sx?$/; const SOURCE_FILE = /\.[cm]?[jt]sx?$/; @@ -2644,6 +2678,26 @@ function selfTest() { 'an empty tree did not trip the published-subpath census guard — a population that silently went to zero', ); rmSync(empty, { recursive: true, force: true }); + + // ── the declared population must stay READABLE by the dispatch deriver ─ + // + // scripts/pm/dispatch-gates.mjs decides which cards are told to run this + // gate by scanning this file's module body for the path literals it + // operates on, and its covering rule refuses a literal carrying no path + // separator (after the leading ./ or ../ an extractor strips) as too + // generic. WORKSPACE_PARENT_GLOBS is this gate's WHOLE declared + // population, so an entry that loses its separator takes every package + // under that parent out of the derived gate list SILENTLY: the gate keeps + // working, CI keeps failing on it, and no dispatch brief sends anyone + // here. That is what the bare spelling cost, measured in that constant's + // docblock (#9955). Asserted here rather than left to review because the + // regression is a tidy-up nobody would flag. + for (const glob of WORKSPACE_PARENT_GLOBS) { + expect( + glob.replace(/^(?:\.\.?(?:\/|$))+/, '').includes('/'), + `workspace parent ${glob} carries no path separator, so scripts/pm/dispatch-gates.mjs refuses it as too generic and every package under it drops out of the derived gate list`, + ); + } } finally { rmSync(root, { recursive: true, force: true }); } diff --git a/scripts/check-type-source-resolution.mjs b/scripts/check-type-source-resolution.mjs index f0c30bbc49..6b83912690 100644 --- a/scripts/check-type-source-resolution.mjs +++ b/scripts/check-type-source-resolution.mjs @@ -305,21 +305,55 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = { // ── workspace enumeration ─────────────────────────────────────────────────── -/** Directory globs from pnpm-workspace.yaml, which are all `/*`. */ -const WORKSPACE_PARENT_DIRS = [ - 'packages', - 'packages/apps', - 'packages/adapters', - 'packages/connectors', - 'packages/drivers', - 'packages/plugins', - 'packages/qa', - 'packages/services', - 'packages/triggers', - 'apps', - 'examples', +/** + * The workspace globs from pnpm-workspace.yaml, spelled AS GLOBS. + * + * ## Why the `/*` is written out rather than left to the comment (#9955) + * + * This array IS this gate's declared population: every package it walks lives + * directly under one of these parents. `scripts/pm/dispatch-gates.mjs` derives + * the gate list a dispatch brief pastes by scanning each gate's module body for + * the path literals it operates on — so this array is the only thing that tells + * that tool which cards should be sent here. + * + * Its covering rule refuses a literal with NO path separator (`packages`, + * `apps`, `examples`) as too generic, deliberately and measured: admitting bare + * top-level words takes that tool from 19k watch-hint pairs to 158k, because + * `packages` is a path COMPONENT in dozens of gates that never read the root. + * The sanctioned escape is for a gate to declare its own subtree in a spelling + * with a separator in it, which is what these entries now do. + * + * Written as bare directory names, 8 of the 11 entries carried a separator and + * 3 did not, so the derivation's answer for this gate was decided by WHERE a + * package happens to sit: measured on this tree, 1832 of the 4844 tracked files + * under packages/ derived this gate, and the ones that did not were exactly the + * flat `packages/` layouts plus all of apps/ and examples/. A new test in + * a nested package named this gate; the identical test in a flat one did not, + * and nothing in the output said so. That is worse than an honest blind spot — + * it works for a third of the tree, so it reads as working. + * + * The dropped `/*` is re-derived below, so the walk is unchanged and there is + * no second list to keep in sync. Keep the separator in every entry: a tidy-up + * back to bare directory names re-opens the blind spot silently, and the + * self-test case at the bottom of this file is what makes that loud instead. + */ +const WORKSPACE_PARENT_GLOBS = [ + 'packages/*', + 'packages/apps/*', + 'packages/adapters/*', + 'packages/connectors/*', + 'packages/drivers/*', + 'packages/plugins/*', + 'packages/qa/*', + 'packages/services/*', + 'packages/triggers/*', + 'apps/*', + 'examples/*', ]; +/** The parent directories those globs enumerate — each glob minus its leaf. */ +const WORKSPACE_PARENT_DIRS = WORKSPACE_PARENT_GLOBS.map((glob) => glob.replace(/\/\*$/, '')); + const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', 'coverage', '.turbo', '.next', '.cache']); const SOURCE_FILE = /\.[cm]?[jt]sx?$/; @@ -1313,6 +1347,26 @@ function selfTest() { const emptyResult = check(empty, {}); expect(has(emptyResult.failures, 'the scan is broken'), 'an empty tree did not trip the census guard'); rmSync(empty, { recursive: true, force: true }); + + // ── the declared population must stay READABLE by the dispatch deriver ─ + // + // scripts/pm/dispatch-gates.mjs decides which cards are told to run this + // gate by scanning this file's module body for the path literals it + // operates on, and its covering rule refuses a literal carrying no path + // separator (after the leading ./ or ../ an extractor strips) as too + // generic. WORKSPACE_PARENT_GLOBS is this gate's WHOLE declared + // population, so an entry that loses its separator takes every package + // under that parent out of the derived gate list SILENTLY: the gate keeps + // working, CI keeps failing on it, and no dispatch brief sends anyone + // here. That is what the bare spelling cost, measured in that constant's + // docblock (#9955). Asserted here rather than left to review because the + // regression is a tidy-up nobody would flag. + for (const glob of WORKSPACE_PARENT_GLOBS) { + expect( + glob.replace(/^(?:\.\.?(?:\/|$))+/, '').includes('/'), + `workspace parent ${glob} carries no path separator, so scripts/pm/dispatch-gates.mjs refuses it as too generic and every package under it drops out of the derived gate list`, + ); + } } finally { rmSync(root, { recursive: true, force: true }); }