Uh oh!
There was an error while loading. Please reload this page.
feat(devx): re-derive the comment-mask corpus sweep as a script, and gate on it - #10670
Conversation
PM review — ACCEPT, arming when its seven checks clear. ⭐ You falsified the module's own header claim, in both directions, with a mutation each.Head ⭐ The positive control — this is what makes "0 disagreements" mean somethingMy brief made this non-negotiable: a green over a corpus is only as good as the corpus being non-empty, so prove the sweep would have caught #10427.
The same 16 files, the same 47,310/4,281 split, the same 10,252-byte maximum in the same file the card reports. I verified ⭐⭐ The header claim you falsified
⇒ Neither instrument dominates. That is the symmetric proof of the card's own thesis — "the self-test pins shapes someone thought of, the sweep finds shapes the tree actually contains, and neither substitutes for the other" — and the header now says so with both measurements rather than asserting a ranking. Correcting a claim in the file that the card was built on, while implementing the card, is the best possible outcome for a And the precision on the card's own number: "the card's 'passed all 22 pinned cases' is history, not current behaviour" — #10632 added the case written from that mutation, so it now fails the self-test and passes the sweep. Distinguishing a card's historical measurement from today's is exactly the discipline this lane has needed all night. ⭐ The gate carries its own anti-vacuity machineryThis is what I most wanted and did not specify: the instrument is built so it cannot pass by measuring nothing.
The CI shape, decided with reasons rather than tasteA hard gate, as one step in the already-required Cost reported honestly and against context: 43–48 s, of which 45.4 s of a 49.4 s run is the parser itself — so Wiring proven by parsing, not grep: The corrections to my brief, accepted
⭐ Declared narrowing accepted: Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10640
scripts/js-comment-mask.mjsis the one answer to "is this span a comment, or code?" for ~20 source-scanning gates. It had one instrument in the tree — its--self-test, which pins shapes someone wrote down — and one instrument that existed only as a paragraph in its header: the corpus sweep that found the defect PR #10632 repaired. That sweep ran once, in an agent's scratchpad, and left with it.This adds the second instrument as a script, and wires it.
What landed
scripts/check-comment-mask-corpus.mjs— walks every.{ts,tsx,mts,cts,js,mjs,cjs,jsx}file in the repo, parses each with@typescript-eslint/parser(already a root dependency), and diffs its comment ranges againstscanSource'scommentarray byte for byte. Both directions are reported and named:FABRICATES(parser says comment, mask says code — gates then manufacture findings out of prose) andOVER-MASKS(the quieter direction, where a gate goes blind over real code).lint.yml, in the already-requiredLint & Repo Gatesjob.js-comment-mask.mjs— see "A claim in the header that is now measured to be false" below. No existing issue citation was renumbered or refreshed.The numbers, on this tree at
b3b77c28434,740 files, 72.1 MB of source, 0 disagreements in either direction, 0 unparseable. The card's ~4,700 estimate and #10427's 4,739 both re-derive correctly (the delta is this PR's own new file).
Cost split, measured: 45.4 s parser · 2.9 s
scanSource· 0.6 s IO. A 4-worker sharded prototype of the same sweep measured 33.7 s on the same (contended, 4-vCPU) box — a 1.5x payoff in exchange for a shard protocol plus an assertion that no shard silently dropped files. Declined: the one thing this instrument must be is obviously correct, and CI prints its own number on every run anyway.The positive control — proof the sweep can go red
A green over a corpus is worth exactly its ability to fail, so the script ships the control rather than describing it.
--masker <path>points the comparison at another implementation ofscanSource:16 files, 15 of them in the
FABRICATESdirection, largest single file 10,252 fabricated bytes inscripts/check-durability-degradation-log-level.mjs— the same 16 files and the same 10,252 that #10427 reported. The instrument is re-derived, not re-described.Both instruments, both directions — measured in one sitting
The card's claim was that the self-test and the sweep do not subsume each other. It had one direction measured. This PR has the other:
scanSource{counting inside${...}'return'fromREGEX_AFTER_KEYWORDThe second row is the sweep catching what no pinned case holds, on today's tree:
The existing case named "regex literal after the
returnkeyword" survives that mutation by accident — its/["]/` puts a double quote first, and a phantom string is line-bounded, so the comment on the next line is still masked correctly. The corpus writes the shape that isn't.Each mutation was confirmed on disk by anchor count before the run and after the restore (
'return', 'typeof', 'instanceof'1 → 0 → 1;if (c === '{') frame.braces++;1 → 0 → 1), never by an editor's exit code —perl -0piandsed -iexit 0 on zero matches.The shape decision: a hard gate on every PR, as a step in
Lint & Repo GatesThe card offered on-demand / advisory / hard gate. The argument for the hard gate:
scanSource, or someone wrote a file with a shape the mask reads wrong. The second arrives on a PR that never touched the masker, and it silently turns ~20 gates into liars about that file. No convention a human or an agent has to remember at the right moment can catch that arrival.scanSource" is precisely what the header already said, and the reason this issue exists.#4690is the standing precedent for a check that runs nowhere.#5617is what a gate that reports without blocking is worth.name:is a required-context contract (scripts/check-required-contexts.mjs) that only a maintainer-present Settings change can enrol; an unenrolled job is advisory by construction.Lint & Repo Gatesis already required, alreadymerge_group-triggered, and carries nopaths:filter.Invoked as
node scripts/…rather than apnpm check:*alias, following the several steps in this lane that name the#9465fence on rootpackage.jsonas the reason.Wiring verified by parsing the workflow, not grepping it (
yaml.safe_load): the step lands injobs.lint(name: Lint & Repo Gates), index 13 of 80, no step-levelif:, nocontinue-on-error, no matrix; thepull_request:trigger carriesbranchesonly, andmerge_group:is present.Two design points worth the review
The one reconciliation. A
#!line is a comment to node and toscanSource;@typescript-eslint/parserdoes not report it incommentsat all. 132 files here carry a shebang (this new script included), so without the reconciliation the sweep opens with one phantom disagreement per shebang file, 18–19 bytes each — measured as 131 an hour before the script joined the corpus it walks, which is its own small lesson about a count written into prose. A verifier that cries wolf on its first run is a verifier someone turns off, so the shebang is added back explicitly, in one place, with the measurement beside it. It is the only reconciliation — every other byte of disagreement fails the gate.Why "0 disagreements" is falsifiable here. The gate runs its own 12-case comparator self-test before every sweep, against the real parser with stub maskers: a masker that flags nothing must be reported as
FABRICATES, one that flags everything asOVER-MASKS, one that skips the shebang must be caught by the reconciliation, an unparseable source must refuse rather than score clean. Plus a corpus floor: fewer than 1,000 files is a refusal (exit 3), not a pass, because a green over an empty corpus prints the same line as a green over a clean one. Unparseable files are fatal, forscripts/ts-parse.mjs's reason — a file the oracle could not read is a file this sweep did not check.A claim in the header that is now measured to be false
js-comment-mask.mjssaid the sweep "is the STRONGER instrument of the two", then gave a counterexample to its own ordering two sentences later. Thereturnablation above supplies the counterexample in the other direction, so the ordering is replaced with the card's formulation, backed by both measurements. The measurement paragraph now also names the script that re-derives it.Scope notes
scripts/**+ one workflow step publishes nothing →skip-changeset.js-comment-mask.mjs --self-teststill runs in no workflow on this branch's base; PR fix(ci): run the four self-tests that ran in no workflow, including the two the scripts/ routing gates delegate to #10649 is the fix for that (#10608), in flight while this was written. This PR deliberately does not take that step — it is that card's, and the two are complementary rather than duplicated.check:parse-guardgoverns the threets.*entry points because none of them throws on unreadable source.@typescript-eslint/parser.parsethrows by default (pinned by a self-test case here), so this new parser call is not the silent-green shape that gate exists to close. No issue filed.Local verification at
b3b77c2843✓ … 4740 files, 0 disagree, 0 unparseable, 44.3sAll 12 self-test cases passed.js-comment-mask --self-test✓ js-comment-mask self-test: 23 cases pass.check:nul-bytesOK (scanned 6212 text file(s) … no raw ASCII control bytes)check:entry-guard✓ 124 scripts/ file(s) — every entry guard goes through invoked-as.mjscheck:parse-guard✓ 123 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjscheck-declaration-mirrors.mjsOK: 2 hand-written declaration(s) agree with their modulescheck:required-contexts✓ 6 required context name(s) pinned across 2 workflow(s)check:workflow-status-functionsOK (scanned 26 workflow file(s), 49 job(s) …)check:node-versionOK (32 setup-node step(s) across 26 workflow(s), all on Node 22)check:shard-attestation✓ 2 aggregate gate(s) count 3 declared leg(s)check:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declaredcheck:type-check-coverageOK — 64/77 workspace packages type-checkedGate union re-derived on the final commit with
node scripts/pm/dispatch-gates.mjs(no paths passed — 3 paths off the merge base, 11 families named, non-empty), cross-checked by hand: it does not namecheck:nul-bytes,check:entry-guard,check:parse-guardorcheck-declaration-mirrors.mjsfor this surface, and all four were run anyway.One declared narrowing:
check:type-check-debtrefuses to run without a built workspace closure (55 workspace dependenc(ies) … have no built type entry point on disk) — by design, since a number measured without the closure is a number from a different world. This diff adds no TypeScript to any package; CI builds the closure before that step and runs it there.Generated by Claude Code
Generated by Claude Code