scripts/check-parse-guard.mjs landed in #10573 (for #10133) and is not wired into CI. Nothing runs it, so it is a gate that exists and enforces nothing.
What it guards, and what its absence costs
ts.createSourceFile never throws — a syntax error, or the wrong ScriptKind, returns a recovered partial tree with the errors on parseDiagnostics. #10573 routed all 32 call sites across 15 gates through scripts/ts-parse.mjs, which refuses instead. The guard is the half that stops a sixteenth raw call being typed into a new gate, and that matters more than it sounds: the symptom of a missing refusal is a green line, so an unguarded gate looks exactly like a guarded one.
This is the same reason check-entry-guard.mjs exists next to invoked-as.mjs. The one-time sweep of 33 files did not stop a twelfth hand-typed spelling of "was I run?"; the gate did.
Why #10573 did not do it
Two claimed surfaces, both live at the time:
Both are mechanical and each is one line plus a step. The gate itself needs no changes: it already carries --self-test (13 cases) and exits 0 on today's tree with 120 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
Acceptance
pnpm check:parse-guard exists and runs the self-test before the scan, matching the shape of the other check:* entries- a step in
lint.yml's gate job invokes it - re-introducing a raw
ts.createSourceFile into any scripts/** file turns that job red and names the file and line — verified, not assumed
Refs: #10133 (the defect class) · #10573 (where the guard landed) · #9465 (the package.json fence) · #8331 (the lint.yml claim)
Generated by Claude Code
scripts/check-parse-guard.mjslanded in #10573 (for #10133) and is not wired into CI. Nothing runs it, so it is a gate that exists and enforces nothing.What it guards, and what its absence costs
ts.createSourceFilenever throws — a syntax error, or the wrongScriptKind, returns a recovered partial tree with the errors onparseDiagnostics. #10573 routed all 32 call sites across 15 gates throughscripts/ts-parse.mjs, which refuses instead. The guard is the half that stops a sixteenth raw call being typed into a new gate, and that matters more than it sounds: the symptom of a missing refusal is a green line, so an unguarded gate looks exactly like a guarded one.This is the same reason
check-entry-guard.mjsexists next toinvoked-as.mjs. The one-time sweep of 33 files did not stop a twelfth hand-typed spelling of "was I run?"; the gate did.Why #10573 did not do it
Two claimed surfaces, both live at the time:
package.json— needs a"check:parse-guard": "node scripts/check-parse-guard.mjs --self-test && node scripts/check-parse-guard.mjs"entry, and rootpackage.jsonis fenced by Migrate the release toolchain to @changesets/cli v3 — one atomic PR carrying the bump, the pre-mode restructure, and the gates that model v2's semantics #9465.github/workflows/lint.yml— needs a step in theLint & Repo Gatesjob, and that file was claimed by in-flight work on lint.yml's "Build the ledgered packages' dependencies" step duplicates the closure refresh once #8330 lands #8331Both are mechanical and each is one line plus a step. The gate itself needs no changes: it already carries
--self-test(13 cases) and exits 0 on today's tree with120 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.Acceptance
pnpm check:parse-guardexists and runs the self-test before the scan, matching the shape of the othercheck:*entrieslint.yml's gate job invokes itts.createSourceFileinto anyscripts/**file turns that job red and names the file and line — verified, not assumedRefs: #10133 (the defect class) · #10573 (where the guard landed) · #9465 (the
package.jsonfence) · #8331 (thelint.ymlclaim)Generated by Claude Code