Uh oh!
There was an error while loading. Please reload this page.
fix(ci): read a cross-package declaration initialiser that spans lines - #11486
Merged
Conversation
…-package scan `scanPathExpressions` matched a `const X = …` initialiser with a regex carrying a hard two-line window, so a call a formatter broke past the print width matched nothing at all -- no binding, no depth, no name, no flag. A read ARGUMENT never carried that window, which is why the asymmetry read as `new URL()` seen / `resolve()` unseen; measured, the split is POSITION, not spelling. `declarationInitialiser()` reads the statement to a depth-0 terminator, sharing the one quote-aware scanner with `balancedArgs`. `withoutTrailingComma()` restores the NAME half, which a formatter's trailing comma cost on its own by arriving as an empty final argument. The unheld-glob failure text now orders its remedies so deleting a correct declaration is last, not first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
This was referenced Aug 23, 2026
os-steve
marked this pull request as ready for review
August 23, 2026 23:18
Uh oh!
There was an error while loading. Please reload this page.
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#11093
What was actually wrong — the split is POSITION, not spelling
The card reports the asymmetry as "multi-line
new URL()is recognised, multi-lineresolve()is not". Re-derived onorigin/mainat7f30b6bewith positive controls, thatframing is falsified: every recognised call was invisible when it spanned lines in
declaration position, and every one of them was already visible in read-argument
position.
resolve()on one lineresolve()across linesjoin()across linesnew URL()across linesA read argument is read by
balancedArgs(), which has always been line-agnostic. Adeclaration initialiser was matched by a regex carrying a hard two-line window
(
[^;\n]+(?:\n\s*[^;\n]*)??), so a call a formatter broke over three or more lines matchednothing at all: no binding, no depth, no name, and therefore no flag. Prettier breaks any
argument list past the print width, so this was the default spelling for a long relative
literal, not an exotic one.
The second half, which is the silent one
declarationInitialiser()alone would have restored the depth (and with it the escape flag)and still left the name lost.
splitTopLevelyields a formatter's trailing comma as anEMPTY final argument, and an empty argument was read as "one I cannot fold", which nulls the
segments in
pathExpression(). A read flagged as escaping whose path holds no glob is the#10566 failure — reported against the glob, not against the scan.
withoutTrailingComma()closes that half; the two are ablated separately below.
Changes
scanBalanced()— the single quote-aware scanner.balancedArgs()becomes a thin wrapperover it (semantics unchanged: it still returns the argument text or
null), and the newdeclaration reader shares it, per this file's own "one balancer" rule.
[and{aredeliberately not counted; the docblock says why.
declarationInitialiser()— reads aconst X = …initialiser to a;or newline at parendepth 0. Because the head match now advances past the
=rather than past the wholestatement, a declaration nested inside another one's initialiser is reached as well.
withoutTrailingComma()+,?in the three seed patterns — a formatter's trailing comma ispunctuation, not an unreadable argument.
with deleting last. "The read is REAL and this scan does not SEE it" leads. Rendered
output is in the evidence section below.
RECOGNISED_PATH_SPELLINGSand the file header record the shape and the measurement.Out of scope by Zone 1 ruling 4: no
turbo.jsoninputs and no package globs are touched.AGENTS.md's excerpt of the spellings list is left as it stands — it is an excerpt, and it is
not falsified by this change.
Evidence
All gate results below quote each gate's own printed verdict line; exit codes were
captured before any pipe. Local union re-run at the final commit
943ea6c6, clean tree.Premise re-derivation on
7f30b6be, end to end, with a positive control. A probe test inpackages/specreading two real files out of package, one spelling each, then the roster readstraight out of
findEscapingPackages():Zone 2 — does any existing glob's verdict move? No, measured rather than asserted. A full
snapshot of the gate's answer (every escaping package, its tests, every rostered path and
directory, and every
globHolderVerdict()unheld/stray verdict) is byte-identical beforeand after:
diff -uproduced 0 lines. The same snapshot pipeline moved by exactly one linewhen the probe above was present, which is what makes the zero-diff a measurement rather than
a silent instrument.
Non-vacuity, two ablation legs. Each mutation was proven on disk by grepping the text it
injected and the text it removed — not by an editor's exit code — and each script carried a
trap … EXIT INT TERMrestore; the tree was confirmed clean and marker-free after both.10/116 self-test case(s) failed— every new case thatclaims a line-spanning read is seen.
withoutTrailingComma()only:3/116 self-test case(s) failed, and they areexactly the NAME assertions. The flag assertions stay green, which is the point: the name
half fails silently on its own.
The two remaining new cases are guards, not proofs, and pass in both directions by design: the
in-package negative, and "a declaration FOLLOWING a multi-line one is still read".
Rendered failure text (produced by injecting one unheld glob into
@objectstack/core,restored by trap):
Gates. Derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(6 families) and treated as a floor. All green at
943ea6c6:check:cross-package-test-inputs(self-test)All 116 self-test cases passed.check:cross-package-test-inputs(verify)OK: 14 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check:entry-guard✓ check:entry-guard: 138 scripts/ file(s) — every entry guard goes through invoked-as.mjscheck:parse-guardcheck:pnpm-filter-targets✓ check:pnpm-filter-targets: 120/148 --filter occurrence(s) … resolvecheck-ci-filter-parity.mjsOK: all 89 declared cross-package glob(s) (77 unique) are covered by core or crosspkg …check:nul-bytescheck-nul-bytes: OK (scanned 6435 text file(s) … no raw ASCII control bytes)Added beyond the derivation, because this diff rewrites remedy text inside a gate script and
edits a published declaration list — neither is reachable from the path derivation:
check:ratchet-remedy-authorityOK check-ratchet-remedy-authority: 123 scripts swept … Control corpus: 21 hand-classified scripts, set-equality audited both ways.(its control expects this fileexcluded; still excluded)check:declaration-mirrorscheck:console-injectionDeclared narrowing — ESLint. The repo-wide
pnpm lintwas not run; it is CI-owned.The narrowing is measured, not asserted, on three counts: (1) the population comes from
ESLint's own config resolution, not from a guess about which files count — the changed path was
handed to ESLint and came back as a lint result rather than as an ignore warning; (2) the file
count is read from
--format json: 1 file, 0 errors, 0 warnings; (3) invariance foruntouched files — this repo runs one
eslint.config.mjswhich never enables type-awarelinting (no
parserOptions.project, no typed@typescript-eslintrules) for any file, aproperty
eslint.config.mjsitself records as measured with a positive control. With nocross-file program, a diff confined to one file cannot move any untouched file's verdict.
No changeset: this edits a CI-internal script and releases nothing — the
skip-changesetcaselint.yml names in its own words. The label is applied on this PR.
Generated by Claude Code