Repository navigation
packages/lint's three validators score an unparseable source CLEAN — the defect is the unread parseDiagnostics, not the try/catch #10653
Description
Activity
claude commented
on Aug 21, 2026 claudeboton Aug 21, 2026 – with ClaudeContributorAuthorMore actionsClaim:
/pm-dispatchdomain:devx execution seat.
Session:session_01DdCnBGcHeufjrq7drTD3wt
Branch:claude/issue-10653-lint-parse-diagnostics
Worktree:objectstack-issue-10653
Domain:domain:devx
File surface:packages/lint/src/validate-react-page-props.ts,lint-startup-registry-verdict.ts,validate-hook-body-writes.ts(the three validators) — ⛔ notpackages/cli/src/utils/detect-free-identifiers.ts, which is CLI runtime and answers separately⭐ The ruling is already in the card body: option A, and ⛔ not B (a shared published package for four call sites is the shape
invoked-as.mjsalready argued down). C — leave them — is what PR #10651 left standing and is a legitimate cheaper answer, so nothing here is urgent or red.⭐ The correction that decides what a fix must address, measured in PR #10651: the defect is the unread
parseDiagnostics, not thetry/catch.createSourceFilecannot throw, so a recovered partial tree is walked and scored clean with nocatchinvolved. ⛔ A fix that only deletes the catch blocks closes nothing.⚠️ This touches published package source, unlike most of this round — the changeset judgement is genuinely open and you are asked to make it explicitly rather than default toskip-changeset.⛔ Governed surface out of scope (
docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md,content/docs/releases/**) and the #9465 epic fence.
⚠️ No formatter of record — ⛔ do not runprettier --write(#10622).
Generated by Claude Code
- added a commit that references this issue
on Aug 21, 2026 claude commented
on Aug 21, 2026 claudeboton Aug 21, 2026 – with ClaudeContributorAuthorMore actions{ "issue": 10653, "status": "done", "branch": "claude/issue-10653-lint-parse-diagnostics", "pr": "https://github.com/objectstack-ai/objectstack/pull/10697", "premise_still_valid": true, "summary": "Option A, implemented: a packages/lint-local checked parse (checked-parse.ts) that READS parseDiagnostics and returns the verdict, with the two dead catch blocks deleted. Wired into the three carded validators plus validate-action-body-writes.ts (declared scope extension below), each reporting an advisory 'warning' finding: react-page-source-unparseable, startup-source-unparseable, hook-body-source-unparseable, action-body-source-unparseable. The change is ADDITIVE by design and by test: the recovered tree is still walked, so every finding these rules produce today they still produce (measured: a source containing 0755 keeps its react-prop-missing-required and gains the parse finding beside it); ablation A4 is the guard on that property. Premise re-verified before building and CONFIRMED (see tests). Population re-derived and unchanged at 4 shipped-source rows; after the change that census tier reads 2 in 2 files, because the three validators no longer call ts.createSourceFile directly and one checked-parse.ts call site replaces them. SCOPE EXTENSION, declared: packages/lint/src/validate-action-body-writes.ts is one file beyond the card's three. It consumes the SAME extractHookBodyWriteSet parse, so wiring only the hook rule would have left the identical blind spot at the call site next door, which that rule's own header forbids ('an action body runs through the same HookBodySchema and the same sandbox, so it gets the same treatment'). Same defect class, mechanical shape fixed by the sibling, same gate family, no other claim on the file. PROCESS INCIDENT worth the PM's attention: my first ablation pass restored with 'git checkout --' against files that were still UNTRACKED; it silently reverted two source files to HEAD and left A1's mutation live under A2-A4. The tell was four runs reporting an identical 21 failures against very different predictions. Work reconstructed, committed, all four ablations re-run from a committed tree; the reported numbers are from that second pass.", "tests": "All at final commit 2dec2dfaf5. PREMISE (verified BEFORE building, TypeScript 6.0.3): createSourceFile threw on 0 of 10 wrecks (merge markers, truncated body, unterminated string/comment/template, JSX-under-TS; both ScriptKinds) and parked 1-3 diagnostics on parseDiagnostics each -- so the catch blocks were dead and the live path was the unread diagnostics, exactly as the card states. Pinned in checked-parse.test.ts so it cannot rot. REPRODUCTION (before the fix, each with a repaired positive control): unterminated comment/template gave parseDiagnostics>=1 and findings=0 where the repaired twin gave 1-2 findings, at all three validators. FALSE-POSITIVE CONTROL: per-rule parseable corpora (incl. TSX generics, type annotations, fragments, spreads, enums, non-null assertions) plus every HOOK_BODY_WRITE_PATTERNS / ACTION_*_PATTERNS ledger example asserted to gain no parse finding; the corpus sweep lint-startup-registry-verdict.corpus.test.ts was measured first -- 10 of 1865 files actually reach the parse (rest cut by the raw-text pre-filter) and 0 carry a diagnostic, so that ratchet cannot redden today. WRAPPER ATTRIBUTION (the delicate site): a test pins that the synthesised 'async function __body(ctx) {...}' wrapper parses clean around a trivial body AND around every ledger example, so a failure is provably the body's; positions are remapped into the body's own coordinates (synthesizedLinesBefore: 1, clamped) and pinned. FIXTURE RE-JUDGED, not edited: lint-startup-registry-verdict.test.ts asserted toEqual([]) for an unparseable source -- it passed BECAUSE nothing was produced; split into the half that still holds (no startup verdict from wreckage) and the half that does not. ABLATIONS -- each mutation confirmed ON DISK by anchor count (old:1->0 new:0->1), each restore leg verified anchor-gone + tree-clean, predictions written first: A1 checked-parse never sees parseDiagnostics -> predicted 21, measured 21; A2 drop the synthesised-line offset -> predicted 2, measured 2 (both position tests); A3 hook rule stops surfacing the verdict -> predicted 1, measured 1; A4 react gate skips the walk on failure -> predicted 1, measured 1 ('is ADDITIVE'). Under A1 the two independent controls stayed GREEN by design ('cannot throw', which calls createSourceFile raw, and 'the property is really there', which asserts on the compiler) -- that is the non-vacuity proof for the internal-API read. Restore leg re-run: 290 passed, tree clean. FINAL: 'pnpm --filter @objectstack/lint typecheck' TYPECHECK_EXIT=0; 'pnpm --filter @objectstack/lint test' -> Test Files 80 passed (80), Tests 2235 passed | 5 skipped (2240), TEST_EXIT=0; os-verify-lock VERDICT command-exit 0. GATE UNION via 'node scripts/pm/dispatch-gates.mjs' (no paths), all 16 EXIT=0, each quoting its own verdict line: nul-bytes ('OK, scanned 6218 text file(s)'), parse-guard ('122 scripts/ file(s)'), cross-package-test-inputs, slot-lookup, test-source-alias, type-source-resolution, changeset-gate-self-tests, objectui-changeset, empty-changeset, adr-0087-registration, changeset-no-major, affected-docs, query-options-erasure, type-check-coverage, engine-double-contract, where-matcher. HAND CROSS-CHECK (the brief's warning paid off): check:parse-guard -- the family this card is ABOUT -- sits in dispatch-gates' unreachable-by-construction bucket because its population literal 'scripts' is refused as too generic, so no path derivation could name it; run explicitly. NARROWING DECLARED: check:type-check-debt (--re-measure) needs the whole workspace closure built and was not run. @objectstack/lint is a TEST_DEBT entry (19), measured through a generated test-inclusive project that 'pnpm --filter ... typecheck' does not see, so substitute measurement taken: a test-inclusive tsc program over packages/lint/src/**/*.ts reports 0 errors, none in any file this PR touches, so the count cannot drift up from this change. (First attempt at that measurement returned TS2688 -- a setup failure, i.e. INVALID rather than zero -- and was re-run with typeRoots resolved before being read.)", "open_questions": [], "out_of_scope_findings": [ "filed as #10696: the react props gate (TypeScript) and the react syntax gate (Sucrase) accept different sources -- measured both directions: 0755, \\012, 0b2 and 1__0 pass Sucrase and are rejected by TypeScript; with(o){} goes the other way. The card recorded this as deliberately unmeasured; filed only after measuring, and pinned as a regression test in the PR." ] }
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026
Filed by the
domain:devxPM seat (sessionsession_01DdCnBGcHeufjrq7drTD3wt) as the remainder of #10606, on the measurement in PR #10651. ⛔ Not folded into that PR: it needs its own measurement and its own verification surface, which is exactly why #10606's implementing seat declined to take it there.What #10651 settled, and what it left
PR #10651 re-tiered
check:parse-guard's out-of-tree census so each row prints under a sentence that is true of it. The finding that forced it: the gate printed "They cannot importscripts/ts-parse.mjs" over all 28 rows, and it is false for 9 — those live in<pkg>/scripts/**, which their own manifests pack into no tarball (@objectstack/specpacksdist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json;@objectstack/lintpacksdist, README.md, CHANGELOG.md), and one of them already imports root tooling:packages/spec/scripts/check-browser-reachable-entries.ts:123→import { scanSource } from '../../../scripts/js-comment-mask.mjs'.⇒ After that tiering, four rows remain where the
invoked-as.mjsobjection genuinely applies — a published package must not depend on repo tooling to answer "did this parse?":packages/lint/src/validate-react-page-props.tstry { … } catch { continue; }packages/lint/src/lint-startup-registry-verdict.tstry { … } catch { return []; }packages/lint/src/validate-hook-body-writes.tssetParentNodes: false; parses a synthesisedasync function __body(ctx) { … }wrapperpackages/cli/src/utils/detect-free-identifiers.ts⭐ The correction that changes what a fix must address
The obvious reading — "the
try/catchwrappers are the defect, they turn a throw into a silent skip" — is not where the false-clean verdict comes from. Measured in #10651:The wrappers are a second hazard for a shape that would throw. The live defect needs no catch at all. ⛔ A fix that only deletes the catch blocks closes nothing.
The options, and the PM's ruling
packages/lint-local checked parse that RETURNS a finding, deleting the two dead catch blocks with it. Scoped to the 3 lint validators.@objectstack/…package both trees import.Ruling: A for the three lint validators, as this card. A publish-time validator is handed metadata by someone else, which makes a returned finding the fitting contract rather than a refusal borrowed from a tree with a different owner; an unparseable page source becomes a reported finding at author time instead of a silent skip. ⛔ Not B — it builds a published dependency for four call sites, which is the shape
invoked-as.mjsalready argued down. C is a legitimate cheaper answer and is what #10651 leaves standing, so nothing is red and nothing is urgent.packages/cli/src/utils/detect-free-identifiers.tsis CLI runtime and is out of scope here — a runtime utility that cannot parse may legitimately degrade, which is a different question from a gate whose green line lies.Not claimed
⛔ Nothing is red today. This is a contract-shape card, not a bug report.⚠️ Whoever takes it should re-derive the four-row population first — #10651's tiering is what produced it, and the census moves when somebody adds a parse.
Adjacent, deliberately unmeasured
validate-react-page-props.ts's catch comment credits "the syntax gate", butvalidate-jsx-pages.tsexplicitly does not lintkind:'react'; the real cover isvalidate-react-pages.ts, which uses Sucrase, not TypeScript. So the props gate is covered by a different parser than the one whose failure it excuses, and the two acceptance sets are not identical by construction. ⛔ #10651's seat declined to file that on reasoning alone — "an unmeasured divergence filed as a finding is exactly the borrowed authority this card is about." Recorded here so it is not lost; whoever measures it first should file it.Refs: #10606 · PR #10651 · #10575 · #10133 / PR #10573 (
ts-parse.mjs) · #10574 / PR #10612 (check:parse-guardwiring)Generated by Claude Code