Uh oh!
There was an error while loading. Please reload this page.
fix(pm): discover gates invoked with --self-test, whose scripts carry no check- prefix - #11554
Merged
Merged
Conversation
`collectInvocations` recognised exactly two spellings — a `check:*` npm script and a script whose basename carries `check-`. A gate following neither was not a family at all: absent from the matched list, the convention list, the unreachable list, and from all three residue buckets, because it never entered the universe those partition. `node scripts/pm/bare-root-worklist.mjs --self-test` shipped a red on the required lane for a diff whose seven derived families were all run and all green, and no local derivation could have named it. The third matcher keys on the flag rather than on a name: `--self-test` is the script's own declaration that this invocation verifies the script instead of doing its work. Two scripts in this tree are invoked BOTH ways, which no filename rule can separate. A `check-` basename is skipped here so an existing family is never split into two keys. A self-test family follows no import. `bare-root-worklist.mjs` statically imports `dispatch-gates.mjs`, whose module-body literals are join bases and tier globs; inheriting them handed one gate 2553 fabricated (gate, file) pairs — the same fabrication `check-dispatch-gates.mjs` exists to avoid by spawning the tool rather than importing it. Measured over 6465 tracked files: 140 -> 149 families, 52774 -> 52880 pairs (+106), zero lost, zero re-attributed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
The matcher ablation destructured undefined and crashed, printing no case name — a self-test that dies reports "something is broken" where the value is "this exact case went red". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
All nine promoted scripts become gate files, not six; four were followed on the base tree over 111 import edges and all four declare zero literals, which is why the net subtraction is zero. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
node scripts/<x>.mjs --self-test
This was referenced Aug 24, 2026
os-steve
marked this pull request as ready for review
August 24, 2026 03:31
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 24, 2026
This was referenced Aug 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#11404
extractCheckInvocationsrecognised exactly two spellings — acheck:*npm script, or a script whose basename carriescheck-. A gate following neither convention was not a family at all: absent from the matched list, the convention-triggered list, the unreachable-by-construction list, and from all three residue buckets, because it never entered the universe those buckets partition.node scripts/pm/bare-root-worklist.mjs --self-testshipped a red on the requiredLint & Repo Gateslane for a diff whose seven derived families were all run and all green.The route, and why it beats the widening
Route (a): key on
--self-test. The flag is the script's own declaration that this invocation verifies the script instead of doing its work — not a heuristic over filenames. The discriminating specimen is in the tree twice:scripts/partition-test-shards.mjsandscripts/pr-labels.mjsare each invoked both ways —--self-testinlint.yml, and doing real work inci.yml/pr-automation.yml. No filename rule can separate the gate invocation from the work invocation of one script.The refused direction — any
scripts/**script in arun:step — admits 12 distinct scripts across the 26 workflow files, and the three it adds beyond this matcher are all non-gate tooling:release-github-releases.mjs(the release run),run-with-stall-guard.mjs(a test wrapper, 7 invocations),docs-audit/affected-docs.mjs(a docs-drift query). That is the fabrication directionhintCoversprices at +139084 pairs.A
check-basename is skipped by the new matcher rather than re-keyed: 21 of the 30 scripts invoked with--self-testalready have one and are already families under their bare path key. Admitting them again under a… --self-testkey would split each into two and move its matches — a re-attribution reported as a gain. Skipping makes zero re-attribution a property of the code, not a number that happened to come out right.A self-test family follows no import — the half that keeps this honest
bare-root-worklist.mjsstatically imports./dispatch-gates.mjs, whose module-body literals are join bases (packages/plugins,packages/drivers,packages/services) and a tier glob (packages/spec/src/**) — not a population anything reads. Inheriting them handed that one gate 2553 (gate, file) pairs, 96% of the change's entire price, every one a lead the gate would never justify.That exact fabrication is already a decided verdict:
scripts/pm/check-dispatch-gates.mjsexists as a separate file that spawns the tool rather than importing it, for no other reason (#8162; its header measures the same literals reaching the same trees). It arrives here by a new route and is refused the same way. Cost of the refusal on this tree, measured: zero — of the nine self-test families onlybare-root-worklistinherits anything at all.Independent confirmation: with the narrowing ablated, the escapable-literal ledger inside this tool's own self-test goes FRESH on
scripts/pm/bare-root-worklist.mjs --self-test scripts— a second, unrelated gate catching the same fabrication.The price, measured the way #11512 priced import-following
Over 6465 tracked files, before → after:
Re-attribution was checked as a property, not a total: every pre-existing
(file, family, hint)claim was recorded before and looked up after — 0 no longer standing — and no existing family's pair count moved by one.The +106 is 0.08% of the refused widening. Six of the nine new families contribute no pairs at all; the three that do declare the population themselves —
release-rehearsal-clone.mjs+75 (the.changesettree its self-test clones),pm/ci-failure.mjs+30 (.github),pr-labels.mjs+1 (.github/labeler.yml).The direction that could subtract, measured rather than argued: all nine promoted scripts become gate files, which
discoverFamiliesexcludes from import-following. Four were being followed on the base tree over 111 import edges —invoked-as.mjsby 77 families,ts-parse.mjsby 18,js-comment-mask.mjsby 14,pm/git-history.mjsby 2 — and all four declare zero path literals, so the edges carried nothing to lose. The three that do declare literals are followed by no family at all. Net: zero hints, zero pairs. Asserted in the self-test, because a lead that stops appearing looks exactly like a lead that was never earned.The incident, reproduced: absent before, present after
PR #11397's real diff (
scripts/check-i18n-coverage.mjs+scripts/pm/bare-root-worklist.mjs), derived from a worktree pinned at the basecd932772and from this branch:For a diff touching only another gate's source, the gate now appears in the residue's undetermined bucket, where before it appeared in no bucket at all.
Assumptions in the card that did not survive re-derivation
The card's numbers predate #11512, which changed this file. Re-derived on this base:
discoverFamilies().byCheck.sizeis 140, not 139. The two empty filters (bare-root,--self-test) reproduce exactly — the root cause is unchanged.lint.ymlsteps": there are nine--self-teststeps inlint.ymlwhose script lackscheck-, and 10 such invocations across all workflows.bare-root-worklistdeclares no population of its own" is not a limitation — it is enforced. The card reads it as an obstacle the derivation cannot get past. Its own self-test asserts, mechanically, thatextractWatchHintsover its source returns zero, with the stated reason "it reads gate sources, never a repo subtree, and a stray path literal in it would name it for cards it has nothing to say about." Giving it a hint for this card's convenience would fail the very gate the card is about. The landed import-following derivation did not make it reachable — it made it inheritable, which is the fabrication refused above.Non-vacuity
Ablation, two legs, each mutating one anchored line, proving the mutation on disk by counting both the removed and the injected text, restoring from a pristine copy, and confirming byte-identity with
git hash-object. Wrapped intrap restore EXIT INT TERM. No build leg:node scripts/pm/dispatch-gates.mjs --self-testexecutes this source file directly andbare-root-worklist.mjsimports it by relative path — nothing resolves through a packageexportsfield, so nodist/copy can serve a stale answer.check-*—docs-drift-check.ymlruns one onpackages/**and the tool cannot mention it in any bucket #9187 measured as invisible is discovered nowe4f83d03…= baseline)The first ablation run aborted both legs on its own guard — the injected-form check was grepping the perl-escaped spelling, so it could not prove the text had landed. Recorded because a guard that had merely trusted the editor's exit code would have read a no-op as a result.
#9187's fixture was re-based, and that is part of the delivery: its invisible-step fixture was verbatim
node scripts/some-mapper.mjs --self-test, which this matcher now discovers, so it had stopped being an example of the thing it illustrates. The retired shape is pinned two cases below as discovered now, so the pair records the move rather than losing it.What this does NOT close
bare-root-worklist --self-testenters the universe with zero hints — reached by identity, and otherwise printed as a family whose population is not path-expressible. That is #11199's ground, one level down, and it is deliberately not folded in: this card asks why a gate never becomes a family, #11199 asks what an existing family's population is. Progress with a name is the deliverable — before this change--residuecould not report the gate as anything.Verification
Derived gate union re-run at final HEAD
cd272342, exit codes captured before any pipe:Beyond the derived list, by reading what this diff actually touches:
bare-root-worklist.mjs --self-test(exit=0 — the gate this card is about, and a direct consumer of the edited module),check:nul-bytes(exit=0, self-test + live), and the eight other newly-promoted self-test gates (all exit=0).Lint is narrowed and the narrowing is declared:
eslint --no-inline-config --format jsonover the one changed file — 1 file linted, 0 errors, 0 warnings. The population is read from the repo's owneslint.config.mjs(the CI invocation), whose header records, with a positive control, that this repo "never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file" — so this diff cannot move the verdict on any file it does not contain. The repo-wide sweep is CI's run.No changeset: this PR touches one internal tooling file and publishes nothing.
Generated by Claude Code
Generated by Claude Code