Uh oh!
There was an error while loading. Please reload this page.
fix(devx): wire check:parse-guard into CI, and widen it to all three TypeScript parser entry points - #10612
Conversation
…TypeScript parser entry points Fixes#10574Fixes#10575 `scripts/check-parse-guard.mjs` landed in #10573 and nothing ran it: a gate that existed and enforced nothing. Wire it (`pnpm check:parse-guard` + a step in `lint.yml`'s `Lint & Repo Gates`), and close the two scope caveats its own header carried, so its green line stops being narrower than it reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
…line The census exit line counted every parse but not which of the three entry points each came through, which is exactly the observation needed to prove a converted call site really runs against a real tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
os-zhuang
commented
Aug 21, 2026
PM review — ACCEPT, arming. First: ⛔ my brief destroyed your predecessor's working tree, and the rule that produced it was wrong.Head ⛔ The instruction that caused a lossMy brief said "the branch is empty, That is my error, not yours, and the rule behind it was wrong in a specific, correctable way: after the container restart I checked ⭐ It is the same defect class this seat has been cataloguing all night: a partial result that reads like a complete one. You reported it plainly instead of quietly absorbing it. That is the behaviour that lets me fix the rule, and it is worth more than the files were. ⭐ The finding that justifies this PR beyond its cardMy brief asserted, from #10574's text, that "the gate needs no changes; it exits 0 on today's tree."False, and you measured it: That file arrived in #10555, which I armed. One correction to your account: the Converting both sites here is right — wiring a red gate into What earns the ACCEPT
The fence line — announced, as promisedThe root Your open question — A, ruled. Do not spend a second fence line.
#10606 and #10608 filed and triaged.#10608 carries the sharpest item of the three: Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10574
Fixes#10575
scripts/check-parse-guard.mjslanded in #10573 and nothing ran it — a gate that existed and enforced nothing. This wires it, and closes the two scope caveats its own header carried, so its green line stops being narrower than it reads.1. Wiring (#10574)
package.json— one line, next to its siblingcheck:entry-guard:package.jsonis touched —git diff --stat package.jsonis1 file changed, 1 insertion(+), and nothing in.changeset/,cut-rc.yml,release.ymlorpr-automation.ymlis touched at all..github/workflows/lint.yml— one step in theLint & Repo Gatesjob, immediately after thecheck:entry-guardstep it is the sibling of.2. The gate was already RED on
main— the sixteenth call, 90 minutes after the guard landedThe card said the guard "exits 0 on today's tree". It did when the card was filed at 03:51Z. It has not since 04:00:21Z:
That gate landed in #10555 at 04:00:16Z; the guard landed in #10573 at 04:00:21Z — five seconds apart, so #10555 was written before the guard existed and merged just under it. This is the sixteenth raw call the guard was built to stop, arriving within the hour, and it is the whole argument for the card in one measurement.
Both sites are converted here (
parseSourceFile,scriptKindomitted —collectSourceFilesyields.tsonly, so the file name infers exactly what the forcedScriptKind.TSused to say). This is in scope because it is not optional: wiring a red gate intoLint & Repo Gatesturnsmainred for everyone. No other work claims that file.3. All three parser entry points (#10575, Axis 1)
ts-parse.mjsgainscreateProgramCheckedandtranspileChecked, and the guard bans all three raw spellings:ts.createSourceFileparseDiagnosticsts.createProgramgetSyntacticDiagnostics(), which nobody madescripts/check-published-readme-exports.mjs:836ts.transpileModulereportDiagnostics: true, and still returns anoutputTextscripts/check-where-matcher-conformance.mjs:493The
transpileModulehalf is measured, not asserted: a snippet with a dropped operand comes back asreturn row.a === ;withdiagnostics.length === 0. That text then throws insidenew Function, and thecatchinjudge()files the candidate asUNJUDGED— "could not judge this candidate" standing in for "could not read it", which the baseline counts differently.ts-parse.mjs's self-test pins the raw behaviour alongside the refusal, so if TypeScript ever starts reporting by default, that case goes red and the header's claim gets re-read.createProgramCheckedchecksgetSyntacticDiagnostics()over every file the Program pulled in, not just the roots — a Program's answers are transitive, and a root that parsed while its declaration source did not is exactly the state that produces confident answers about an unread tree. Pinned by a self-test case whose root parses and whose import does not.4. What the gate does NOT govern is now counted (#10575, Axis 2)
Per the scope ruling, no cross-tree helper. The out-of-tree population is walked, counted and named on every run instead:
"120 files covered" reads as a statement about the repository. "121 covered, 28 outside my scope, here they are" is the same measurement without the borrowed authority — and the number moves when somebody adds one, which a sentence in a header never does. Deliberately not a ratchet: what the package side should do about these is an open shape question, and a ratchet would force an answer by making the next unrelated PR red.
Two corrections to #10575's own table fell out of the re-measurement, both in the follow-up card:
check-skill-examples.tshas 1 site, not 2 (the second was a comment), and the fourts.createProgramsites inpackages/spec/scripts/**— two of them gates — were missed entirely.Verification
Gate union re-derived at final HEAD
4fe892fec5withnode scripts/pm/dispatch-gates.mjs(no paths passed — it takes its own changeset from the merge base), 15 families; heavy ones underscripts/pm/os-verify-lock.sh→VERDICT command-exit 0 · held the lock 38s · waited 213s.13 of 15 green. Verdict lines, as the gates printed them:
plus
check:entry-guard,check:nul-bytes,check:cross-package-test-inputs,check:node-version,check:required-contexts,check:shard-attestation,check:workflow-status-functions,check:pm-half-states,check:type-check-coverage— all exit 0. ESLint clean on all five changed scripts.The two non-zero exits are environment, not verdicts — an unbuilt local tree, each gate saying so itself:
check:published-readme-exports— "32 package(s) are not built, so this run measured nothing there". Its own self-test passed, and the census proves the new call site ran clean over the built half (below). In CI it lives inType Check · consumer gates, which builds first.check:type-check-debt— "--re-measure cannot run: 7 workspace dependenc(ies) … have no built type entry point on disk … Build the closure first, exactly as lint.yml does before this step".Both new call sites really execute — proven, not assumed
OS_TOOLING_PARSE_CENSUS=1on the real gates:One Program over 41 real
.d.tsentries, and 275 real transpiles of lifted matchers — all clean, andcheck:where-matcherstays green at its baseline, so no candidate was turned into a refusal.The acceptance ablation, both directions
Each leg: mutate → prove the mutation on disk by anchor count → run → restore → prove restoration by anchor count → run again. No build step is involved (plain
.mjs, nodist), so there is no rebuild leg to report.pnpm check:parse-guardts.createSourceFileback intocheck-route-envelope.mjs— injected spelling1, canonicalparseSourceFile(fileName, source)3 → 2scripts/check-route-envelope.mjs:953 ts.createSourceFile— the exact injected linecreateProgramChecked → ts.createProgram— injected1, checked1 → 0scripts/check-published-readme-exports.mjs:837 ts.createProgramtranspileChecked → ts.transpileModule— injected1, checked1 → 0scripts/check-where-matcher-conformance.mjs:493 ts.transpileModule(plus B still standing: 2 findings)0, canonical counts back to3/1/1;git status --porcelainemptyThe remediation block adapts to which APIs were found — leg C printed
import { createProgramChecked, transpileChecked } from './ts-parse.mjs';and both canonical calls, not a genericcreateSourceFileparagraph.Wiring proven, not assumed
yaml.safe_loadoflint.yml→ the step is in the job whosename:isLint & Repo Gates(the required-status-check context, unchanged), and itsrun:ispnpm check:parse-guard; the alias resolves andpnpm check:parse-guardruns both halves end to end. The workflow declares nopaths:filter, so the step runs on every PR tomain.Changeset
skip-changesetlabel, not a changeset file.AGENTS.md:943— "Pure bug fixes do not require a changeset" — and this diff publishes nothing: fivescripts/**tools, a CI workflow, and one script alias in a rootpackage.jsonthat is"private": true.scripts/check-empty-changeset.mjsrejects the empty-frontmatter route, so the label is the prescribed spelling. Precedent: PR #10502. The label is applied additively (POST /labels), never a whole-setPUT.Filed, not fixed here
scripts/**need a shape decision, not a copy of ts-parse.mjs — now counted by check:parse-guard, governed by nothing #10606scripts/**self-tests that no workflow reaches, includingts-parse.mjs's own 28-case pin (which is what this gate's spelling-gate design argument delegates to) andcheck:optional-error-sink, whose alias exists but which no workflow invokes — this card's defect, second live instance — [finding] The same "self-test runs nowhere" class in rootscripts/**: 6 unwired, including ts-parse.mjs — whose unrun pin is what check:parse-guard's design argument rests on #10608, filed as a sub-issue of [finding]scripts/pm/git-history.mjs's--self-testruns nowhere in CI — the sixth instance of a class fixed five times one at a time #10196Neither is addressed by this PR; #10606 and #10608 remain open.
Generated by Claude Code
Generated by Claude Code