LearnNewsExamplesServices
Frontmatter
id16484
titleMulti-line template state tracking, so a growth expression across a template boundary is neither lost nor faked
stateClosed
labels
bugai
assigneesneo-gpt
createdAtAug 4, 2026, 9:32 AM
updatedAtAug 25, 2026, 7:59 PM
githubUrlhttps://github.com/neomjs/neo/issues/16484
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 25, 2026, 7:59 PM

Multi-line template state tracking, so a growth expression across a template boundary is neither lost nor faked

neo-opus-grace
neo-opus-grace commented on Aug 4, 2026, 9:32 AM

Context

Successor to #16443, carrying the residual @neo-gpt-emmy recorded when she approved PR #16444 as Approve+Follow-Up under the temporary incident exception (review).

Filed at @tobiu's direction while the GPT family is near its rate limit: an approval given under a firefighting threshold carries conditions, and those conditions should not depend on anyone remembering them after the PR closes. Low priority — the gate delivered by #16443 is green and useful as-is.

Live latest-open sweep: checked the latest 20 open issues at 2026-08-04T07:29Z; no equivalent found. A2A in-flight claim sweep (all read-states): no competing [lane-claim].

The Problem

lint-retry-bounds.mjs discards every continuation line of a multi-line template literal:

if (wasOpen) return;

That guard predates stripLiterals learning to preserve ${…} substitutions. Once it did, the guard began throwing away exactly the code that repair existed to keep. A real growth expression written across a template boundary — `…${base * 2 ** n}…` — is matched, preserved, and dropped one line later. Discovered and discarded in the same pass, which is worse than never scanning it.

And removing the guard does not fix it. That was measured on PR #16444 rather than assumed. Lifting it immediately surfaces ai/demo-agents/dev.mjs:258 as a candidate whose match is **AI Generated PR** — markdown bold inside literal text, precisely what stripLiterals exists to blank, and a case this suite already pins with a dedicated test.

Traced from the file start:

NEO_CODE_BLOCK_1

The template opened at dev.mjs:118 is never seen to close, so by 258 the scanner still believes it is inside one. stripLiterals(line, true) enters with quote = '\'`, meets 258's opening backtick, reads it as a close, and emits the remainder of the line as code.

So if (wasOpen) return masks two defects: the escape above, and a stripLiterals multi-line state-tracking bug. Lifting it trades a known false negative for a false positive — the worse of the two, because a gate that cries wolf is one contributors learn to route around, and that reasoning is the module's stated premise for why it reports unclassified and never unbounded.

The guard was therefore retained on #16444 with the measurement recorded at the site. The escape is explicitly not closed.

The Architectural Reality

  • ai/scripts/lint/lint-retry-bounds.mjsstripLiterals (the quote/substDepth state machine), discoverCandidates (the wasOpen guard and the inTemplate carry between lines), findGrowthMatches.
  • ai/demo-agents/dev.mjs:118 and :258 — the live reproduction; 118 opens a template the scanner never sees close, 258 is where the consequence surfaces.
  • test/playwright/unit/ai/scripts/lint/lintRetryBounds.spec.mjs — already pins "literal contents cannot match — markdown bold is not an exponent", which is the test the false positive would break.

The Fix

Track multi-line template state correctly across lines, so a continuation line can be scanned for preserved substitution code without the literal text leaking through as code. Then remove the wasOpen guard, because it exists only to compensate for the mis-tracking.

Then complete the witness-contract truth-fold deferred from #16444: choose explicitly between strong resolvable references and source-keyed inline/rationale semantics, and state the chosen contract in one place (registry $schema + the validator's JSDoc) so a witness author is not guessing.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback / Error Semantics Docs Evidence
stripLiterals template state this ticket Carries open/closed template state correctly across lines Unresolvable state ⇒ treat the line as literal (skip), never as code module JSDoc dev.mjs:258 is not a candidate
continuation-line scanning this ticket Scans preserved ${…} code on continuation lines module JSDoc a growth expression across a template boundary IS a candidate
witness contract registry $schema + validateEntry One stated contract for what a witness may be An entry that does not satisfy it fails, with the rule named registry $schema a prose witness and a path witness both resolve deterministically

Decision Record impact

none — a scanner correctness fix inside an existing lint.

Acceptance Criteria

  • Opposing fixtures, both required. A false-negative fixture: a growth expression inside a ${…} substitution on a continuation line of a multi-line template IS discovered. A false-positive fixture: markdown bold inside multi-line template literal text is NOT discovered.
  • Both fixtures are end-to-end through discoverCandidates, not unit calls to stripLiterals — the escape lived in the seam between them.
  • ai/demo-agents/dev.mjs produces no candidate at line 258, and the live tree stays fully classified (exit 0).
  • The wasOpen guard is removed, not merely narrowed — retaining it means the state tracking is still not trusted.
  • Mutation-proven both ways: reintroducing the guard fails the FN fixture; breaking the state tracking fails the FP fixture.
  • The witness contract is stated in exactly one authoritative place, and validateEntry's behaviour matches it.

Out of Scope

  • Widening or narrowing the discovery PATTERNS. The population under scan does not change here.
  • Re-litigating the admission boundary settled on #16444 (vocabulary annotates, it does not admit).

Avoided Traps

  • Removing wasOpen alone. Measured on #16444: it converts a false negative into a false positive. The state fix must land first or with it.
  • Pinning the fix with only the FN fixture. That is what makes the naive removal look correct — the FP fixture is what catches it, which is why both are required rather than recommended.

Related

  • Delivered gate: #16443 via PR #16444 (approved at 50d455c89c)
  • The retained guard and its measurement: the comment block at the wasOpen site

Retrieval Hint: stripLiterals multi-line template state tracking wasOpen continuation line false positive markdown bold

Origin Session ID: 8132018f-e94a-4ef5-bce4-ec788ef56b7e

tobiu referenced in commit 60dea8f - "fix(lint): preserve multiline template state (#16484) (#17769) on Aug 25, 2026, 7:59 PM
tobiu closed this issue on Aug 25, 2026, 7:59 PM