- Replaced 3-way-merge and session-chain replay strategies with a consistent anchor remapping flow for drift recovery.
- Removed legacy reconciliation logic and associated session-replay warning constants.
- Updated recovery flow to mandate anchor consistency and validate against duplicated context.
- Refined test suite to verify anchor mapping mechanics and explicit refusal of ambiguous remappings.
- Introduced strict validation for boundary repairs to prevent accidental code deletion when payload context is insufficient.
- Added `ambiguousBoundaryEchoMessage` to identify when a partial echo is too short to safely replace a range.
- Added `ambiguousCloserSpareMessage` to identify when a spare closer replacement lacks structural evidence (indentation or delimiter balance) for placement.
- Implemented rejection logic in `repairReplacementBoundaries` for ambiguous cases, forcing the user to provide explicit edits instead of guessing.
- Added test cases covering one-sided echo and ambiguous closer sparing to ensure errors trigger on unsafe edits.
- Simplified match logic to rely exclusively on content hash equality.
- Removed strict validation that rejected colliding snapshot tags.
- Updated recovery behavior to resolve collisions to the most-recently recorded snapshot.
- Refactored tests to expect successful preview and patching despite tag ambiguity.
Codex reviewer flagged that the per-reveal cap only limits the line
count, not each line's width. A minified-bundle-style wide line
(megabytes on one row) would be stored verbatim in the RevealedLine
and formatted straight into the thrown edit error — bypassing the
column-truncation read/search already apply to long lines and dumping
that much content into the tool result, TUI, and model context.
assertSeenLines now clips each revealed line at
SEEN_LINE_REVEAL_MAX_COLUMNS (512, matching search's DEFAULT_MAX_COLUMN)
with a trailing ellipsis marker and treats any clip as truncated. The
existing truncated-gated merge keeps the guard closed on clipped
reveals so the model cannot land an edit having only seen the first
512 chars of a wide line, and the message keeps the range-re-read
guidance.
Codex reviewer flagged that when an anchor range exceeds
`SEEN_LINE_REVEAL_CAP`, merging the revealed prefix into `seenLines`
lets a model split a blind over-cap edit into two <=cap-line retries
and slip past the range-re-read gate: attempt 1 reveals+merges lines
100-139, attempt 2 reveals+merges the 140-159 tail, attempt 3 applies
— all without a single range read.
The merge is now gated on `truncated === false`: only a reveal that
covered EVERY unseen anchor line joins `seenLines`. Truncated reveals
keep the range-re-read guidance and the reveal window stays anchored
at the head across retries, so the same over-cap patch keeps rejecting
until the model actually re-reads the range.
Structural-summary reads (default for parseable code >100 lines) mint a
`[path#tag]` that only marks declaration/boundary lines as displayed;
edits anchored inside an elided body then hit `#assertSeenLines` in
`packages/hashline/src/patcher.ts` and reject with "never displayed
(it showed a partial range, a search hit, or a folded summary)". The
existing message pointed at a range re-read, but that made every such
recovery a three-turn round-trip (edit-fail → range read → edit-retry)
and models frequently retried the same edit instead of following the
hint — 5-8 out of 10 edits failed for the reporter.
The rejection now:
- Inlines the actual file content at the unseen anchor lines, from
`matchedSnapshot.text` (which by definition equals the live normalized
content on the no-drift path), up to `SEEN_LINE_REVEAL_CAP` (40) lines.
- Merges the revealed lines into the snapshot's `seenLines` set, so a
straight retry with the same `[path#tag]` header succeeds without a
follow-up read. The content is inside the error the model receives,
which is the proof it has now seen those lines.
- For anchor ranges over the cap, only the revealed prefix is merged;
the message keeps the range-re-read guidance for the remainder so
runaway blind edits don't sneak past.
Fixes#4224
- Added `byHashExact` to `SnapshotStore` to resolve historical versions only when a tag is unambiguous.
- Prevented recovery and edit-preview paths from incorrectly applying anchors against colliding tags.
- Implemented `InMemorySnapshotStore.byHashExact` returning null when multiple recorded versions share a tag.
- Added comprehensive unit tests validating single-match resolution and multi-collider rejection.
- Accelerates duplicate-line verification from quadratic $O(N^2)$ to linear $O(N \log N)$ by precomputing anchor neighbors in a single sorted pass.
- Optimizes duplicate checks to $O(1)$ by collecting line values into a pre-allocated Set instead of repeatedly searching arrays.
- Prevents silent content corruption by aborting recovery if multiple historical snapshots share the same 16-bit hash tag.
- Validates the optimized duplicate-line shifting behavior and collision rejection logic with new test coverage.
Clarified hashline minus-row errors and model-facing prompt examples so Markdown list rows use the + body-row prefix instead of triggering write fallbacks.
Fixes#4179
The visible 4-hex hashline tag is the low 16 bits of a non-cryptographic hash, so two genuinely different file states can collide. Snapshot storage keyed dedup on the hash alone, fusing distinct texts (and their seenLines) into one stored entry. The patcher treated `computeFileHash(live) === expected` as an exact live match and took the no-drift path — applying line-anchored edits directly to unrelated live content and bypassing recovery.
Keeps the visible tag format for backward compatibility and widens identity at the two junctures where it matters:
- Store dedup now compares (hash, text). Distinct texts with the same 4-hex tag are retained as separate versions with independent seenLines; identical repeated reads still fuse as before.
- New abstract SnapshotStore.byContent(path, text) disambiguates collisions by content.
- Patcher requires the stored snapshot for (path, expected) to equal live text before taking the no-drift path. On collision it falls through to Recovery, whose line-precise 3-way merge fails cleanly on colliding-but-different content and raises MismatchError. When no snapshot is retained for the tag, behavior is unchanged (external mint / aged out).
- #assertSeenLines now consults the exact matched snapshot, so seen-line validation cannot read a collider's provenance.
Regression tests added to both snapshots.test.ts and patcher.test.ts covering the concrete `1D84` pair from the reporter.
Fixes#4075
- Rejected duplicate-line stale anchor remaps unless surrounding context still maps around the target.
- Added regression coverage for duplicate-line drift that would overwrite the wrong occurrence.
Fixes#3775
- Remapped stale hashline anchors through unchanged-line diffs after prior insertions or deletions shifted targets.
- Added regression coverage for in-session insertion and deletion drift.
Fixes#3775
GLM 5.2 inserts an extra `.` between the line number/range and the
trailing `:` (e.g. `SWAP 2.=3.:`, `INS.POST 2.:`, `DEL 2.=3.`).
The parser failed with "payload line has no preceding hunk header"
and could not recover. Add a `skipStrayDot` helper that skips a
spurious `.` before the optional colon or end-of-line, wired into
`consumeOptionalColon` (covers SWAP, INS.*, SWAP.BLK, INS.BLK.POST)
and the DEL / DEL.BLK branches.
- Implemented file deletion (`REM`) and movement (`MV`) operations within the hashline grammar and parser.
- Added filesystem support for executing delete and move commands while maintaining snapshot history migration.
- Integrated file operation detection and parsing logic into the coding agent and patcher components.
- Added comprehensive integration tests and documentation for the new file-level operation syntax.
- Automatically rebind edits to the correct file when an authored path does not exist but the filename and snapshot tag uniquely match a file read earlier in the session.
- Prevent path recovery for paths that would escalate write privileges, ensuring compatibility with read-only internal URL targets.
- Surface warning messages to the model and user upon successful path recovery to encourage correct future path usage.
- Improved delimiter-balance logic to correctly identify and spare partially deleted structural closers.
- Prevented premature deletion of structural closers by accounting for existing code below the modification range.
- Added support for tracking inserted lines to improve boundary repair accuracy across multi-section updates.
- Refined the suffix-closer identification to skip lines restated by the new payload and account for projected closers appearing after the patch.
- Updated logic to verify if the patch prefix already satisfies missing structural openers before sparing deleted suffix lines.
- Fixed a bug where replacement payloads that restated existing suffix tails caused erroneous retention of structural closers.
The dropped-closer repair fired per replacement group, so a `SWAP` that
deleted a structural closer kept it even when another hunk had already
removed the matching opener (a deliberate wrapper removal) — leaving a
stray `}` and corrupting valid multi-hunk edits.
Defer the repair to a second pass that sums each hunk's delimiter delta
per contiguous hunk (so backtick/block-comment state never bleeds across
non-adjacent hunks), suppresses it when the matching opener was deleted or
replaced just above the range, and consumes the residual per repair so a
genuine missing closer elsewhere in the patch still fires.
Fixes#3142
- Clarified the message instructing users or models on how to handle unseen lines.
- Added specific instructions recommending a ranged read to bypass summarization and mint a fresh tag.
Replaced the one-sided JSX echo guard regex with a small scanner that ignores tag-closing characters inside quotes and expression braces, so self-closing tags with comparison props still allow boundary-echo repair.\n\nFixes #2705
Scanned replacement payload prefixes as one JSX/XML fragment so the single-line boundary echo guard preserves nested closers whose opener spans multiple lines.\n\nFixes #2705
Avoided treating a single-line replacement payload's own JSX/XML closing tag as an off-by-one echo when the payload opened the same tag.\n\nFixes #2705
Auto-repaired single-line replacement expansions that retype a surviving JSX/XML closing boundary below the edited line, preserving ordinary statement duplicates while removing the block-end echo.\n\nFixes #2705
- Renamed line and block patch op verbs to XCHG, DEL, and INS in parsing and formatting.
- Updated grammar and tokenizer to support XCHG.BLK, DEL.BLK, and INS.PRE/POST/HEAD/TAIL forms.
- Updated diagnostics, docs, prompts, tests, and changelog to use XCHG/DEL/INS-based operators.
- Expanded session-stats parsing to normalize legacy op aliases to compact IDs.
- Tracked seen-line provenance in snapshots and propagated it from read/search/ast-grep rows.
- Rejected hashline edits on unseen lines before patching, throwing unseen-line errors.
- Rejected single-line block anchors in strict mode and dropped them in unresolved lenient mode.
- Trimmed one-sided keeper-echo duplicates during multi-line replacements with warning output.
- Updated boundary repair to skip dropping closing suffix lines when the replacement payload already restates them.
- Added a helper that checks whether a replacement payload ends with the same trailing deleted lines before sparing structural closers.
- Added a boundary repair test that confirms a replacement preserving an internal closer produces no duplicate closer or warning.
- Added a focused-agent left-key input listener that consumes double left taps when the input is empty.
- Routed focused session left-tap behavior through #handleFocusedLeftTap so it matches the Esc-style unfocus timing.
- Updated tests/fixtures for the new listener flow and refreshed a hashline block-edit error assertion.
- Added a SessionFocusController to switch transcript and input context between main and subagent sessions.
- Added agent-hub Enter activation and double-left return behavior for focused local agents.
- Added view-session-based event and render logic to avoid stale focus-session state.
- Added status-line focused agent display with ghost icon and focused-mode border dimming.
- Handled unresolvable `insert after block` edits by lowering them to plain `insert after` operations with a warning when no syntactic block can be resolved.
- Added a new `insertAfterBlockUnresolvedLoweredWarning` message and narrowed unresolved-block errors to replace/delete paths.
- Updated block tests to validate warning-based lowering for missing resolvers and blank/invalid `insert after block` anchors.
- Scope.
- Added a trailing-sentinel guard to identify newline-generated phantom lines and drop delete edits targeting them.
- Updated edit validation to allow those no-op deletes while preserving real line bounds checks.
- Adjusted format-v2 tests to expect no-op handling of sentinel deletes and clipping for ranges ending at the sentinel.
- Updated block resolution to detect structural-closing-line anchors and convert unresolvable `insert after block` edits into plain `insert after` inserts while emitting a dedicated warning.
- Propagated these resolver warnings through `PatchSection` and `Patcher` results so parse and application callers receive them.
- Added tests covering the closer-anchor fallback and confirming non-closer unresolved block anchors still throw.
- Fixed deep insert-after-block landing by retreating inward corrections across trailing closers.
- Added blockStart tracking and warning diagnostics for inward correction of block inserts.
- Clarified insert-after-block prompt and changelog guidance to require opener-based landing rules.
- Added regression tests for nested, empty, and conflicted inward landing scenarios.
Updated the hashline edit prompt and internal edit docs to make insert-after-block anchors opener-only, and added regression coverage for closing-delimiter anchors and prompt guidance.\n\nFixes #2292
- Added a shared `formatAnchoredContext` helper and switched mismatch formatting to use it.
- Extended unresolved block-edit errors to append nearby numbered context with `*`-marked anchor lines.
- Updated unit and integration tests and docs to reflect the new unresolved-block preview behavior.
- Separated non-contiguous diff regions with a single blank gap row, normalized after block-context insertion.
- Rendered gap rows as one dim ellipsis in the TUI and HTML export.
- Applied the same blank-separator dedupe and edge-trimming to hashline's compact diff preview.
- Rendered added/current lines without `+`/space prefix padding.
- Collapsed long added runs with a bare `…` marker instead of `+…`.
- Normalized adjacent ASCII/Unicode elision markers to one `…`.
- Added matching-bracket utilities to locate partner lines for visible spans.
- Added read tool output to use displayContent text and startLine for bracket-aware previews.
- Added matching-bracket context rows to generateDiffString and generateUnifiedDiffString.
- Adjusted diff row insertion to deduplicate and keep contiguous changed groups together.
- Removed the mid-skip placeholder line that was output when skipping lines in the middle of a diff, relying instead on the line number jump to convey the gap. Removed the corresponding placeholder-filtering logic from the hashline diff preview that was handling these placeholders.