@@ -149,6 +149,48 @@ export const ROOTS = Object.freeze([
149149/** Every tracked `package.json`, the repo root's included. */
150150export const MANIFEST_PATHSPEC = ':(glob)**/package.json' ;
151151
152+ /**
153+ * The population above, in the spellings `scripts/pm/dispatch-gates.mjs` can
154+ * match, so a card that adds a script under one of these roots is routed to
155+ * this gate -- a new script is the input this gate is most likely to fail on.
156+ * Without them the derivation reads the `'scripts/'` label above as a bare
157+ * root no dispatch can name, and only `.githooks` reaches the gate.
158+ *
159+ * Honest at the precision the instrument allows, measured on this tree with
160+ * this file's own `kindOf`: the walk descends the whole of each root, and
161+ *
162+ * scripts/** 326 of 434 75.1% the rest: 102 .json, 3 .md, 3 .txt
163+ * packages/**\/scripts/** 171 of 174 98.3% the rest: 2 .json, 1 data file
164+ * .githooks/** 2 of 2 100.0%
165+ *
166+ * The files it skips are baselines and prose a card can touch without this
167+ * gate having anything to say -- a lead the dispatch prints and the gate then
168+ * passes, which is the cheap direction. `packages/**\/scripts/**` carries its
169+ * glob in a non-final segment, so the derivation matches it as a PATTERN: it
170+ * reaches `scripts/` directories under `packages/` and nothing else there.
171+ *
172+ * ⛔ Provenance, never a lookup key: nothing reads these arrays but the
173+ * self-test, which pins them against ROOTS and against the live manifest
174+ * population in both directions. They are literals because the extractor reads
175+ * literals; assembling them at runtime would hide them from the very derivation
176+ * they exist for.
177+ */
178+ const ROOT_DIR_WATCH_HINTS = [ 'scripts/**' , 'packages/**/scripts/**' , '.githooks/**' ] ;
179+
180+ /**
181+ * The manifest half: the root `package.json` (a bare filename is refused as too
182+ * generic; `/**` collapses back to it exactly), and the member manifests,
183+ * enumerated per root that holds one -- `**\/package.json` is dropped by the
184+ * extractor's admission, so the population is spelled the way
185+ * `scripts/git-merge-regen.mjs` spells the same one.
186+ */
187+ const ROOT_FILE_WATCH_HINTS = [ 'package.json/**' ] ;
188+ const DECLARED_WATCH_HINTS = [
189+ 'packages/**/package.json' ,
190+ 'apps/**/package.json' ,
191+ 'examples/**/package.json' ,
192+ ] ;
193+
152194/** The file that owns this rule, and so is not judged by it (see the header). */
153195export const RULE_OWNING_FILES = Object . freeze ( [ SELF ] ) ;
154196
@@ -767,7 +809,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
767809 [ NOT_PNPM ] : 6 ,
768810 [ PROSE ] : 6 ,
769811 [ TREE ] : 13 ,
770- [ PIN ] : 3 ,
812+ [ PIN ] : 5 ,
771813} ) ;
772814
773815// Deleting a battery from the roster silences its floor as surely as zeroing
@@ -930,6 +972,31 @@ const PIN_CASES = [
930972 return labels === 'scripts/ packages/**/scripts/ .githooks/' ? null : labels ;
931973 } ,
932974 } ,
975+ {
976+ label : 'ROOT_DIR_WATCH_HINTS declares exactly the walked roots' ,
977+ check : ( ) => {
978+ const walked = ROOTS . map ( ( r ) => r . pathspec . replace ( / ^ : \( g l o b \) / , '' ) ) ;
979+ return JSON . stringify ( walked ) === JSON . stringify ( [ ...ROOT_DIR_WATCH_HINTS ] )
980+ ? null
981+ : `walked ${ JSON . stringify ( walked ) } but declared ${ JSON . stringify ( ROOT_DIR_WATCH_HINTS ) } ` ;
982+ } ,
983+ } ,
984+ {
985+ label : 'the manifest hints reach every tracked package.json, and each hint reaches one' ,
986+ check : ( ) => {
987+ const manifests = gitLsFiles ( REPO_ROOT , [ MANIFEST_PATHSPEC ] ) ;
988+ const roots = DECLARED_WATCH_HINTS . map ( ( h ) => h . slice ( 0 , h . indexOf ( '/**/package.json' ) ) ) ;
989+ const reached = ( path ) =>
990+ ( path === 'package.json' && ROOT_FILE_WATCH_HINTS . includes ( 'package.json/**' ) ) ||
991+ roots . some ( ( root ) => path . startsWith ( `${ root } /` ) ) ;
992+ const unreached = manifests . filter ( ( path ) => ! reached ( path ) ) ;
993+ const dead = roots . filter ( ( root ) => ! manifests . some ( ( path ) => path . startsWith ( `${ root } /` ) ) ) ;
994+ if ( manifests . length === 0 ) return 'git ls-files listed no package.json' ;
995+ if ( unreached . length > 0 ) return `undeclared manifest root(s): ${ unreached . join ( ', ' ) } ` ;
996+ if ( dead . length > 0 ) return `declared root(s) holding no tracked package.json: ${ dead . join ( ', ' ) } ` ;
997+ return null ;
998+ } ,
999+ } ,
9331000 {
9341001 label : 'no fix line names a spelling this gate rejects' ,
9351002 check : ( ) => {
0 commit comments