PUT ... @name with no matching capture no longer fails the patch: it
pastes nothing (a span target is still removed, i.e. it degrades to a
cut) and surfaces a warning naming the available registers. Anonymous
empty/ambiguous pastes still error. validateClipboardSequence now only
guards anonymous sequencing; applyEdits threads clipboard warnings into
ApplyResult.warnings.
- Implemented in-house, zero-dependency utility modules in `pi-utils` covering DOM manipulation, markdown parsing, templating, browser automation helpers, and terminal buffers.
- Migrated packages across the repository to consume the new internal utilities and `omptype` schema validators instead of external dependencies.
- Removed multiple external runtime and development dependencies including Zod, Marked, LRU cache, Turndown, and Puppeteer browser packages.
- Update Lark grammar to support the unified put, cut, move, and remove operations.
- Update documentation and prompts to reflect canonical inclusive range separators and named register rules.
- Replaced legacy `SWAP`, `INS`, and `PASTE` commands with unified `PUT` and `CUT` hunks across parser, grammar, tokenizer, and test suites.
- Added support for named registers and span paste operations in clipboard and block execution logic.
- Implemented indentation repair and enhanced gap locator formatting for improved patch resilience.
- Updated documentation, system prompts, and session analysis scripts to reflect the new syntax and header shapes.
- Add new structural, multi-edit, and block-level mutation classes with updated category mappings.
- Introduce hunk extraction, placement, rendering, and solver utilities along with unit tests.
- Implement size-based mutation planning, prompt validation logic, and new prompt markdown templates.
- Update benchmark generation scripts and package configurations to support empirical edit shape statistics.
- Removed copy and delete operations across tokenizer, parser, grammar, and clipboard logic.
- Standardized line-editing operations and block resolvers to use cut exclusively.
- Updated documentation, prompts, and test suites to reflect the removal of copy and delete syntax.
- Regrouped `grammar.lark` around shared `target` and `pos` rules, reducing hunk rules from twelve to seven.
- Maintained byte-identical language acceptance while simplifying internal grammar structure.
- Implemented clipboard register management, parsing, and execution rules for CUT, COPY, and PASTE operations in the hashline engine.
- Added session-persistent clipboard state and integration across agent session execution, diff previews, and streaming tools.
- Added comprehensive validation, error messages, recovery handling, and test coverage for clipboard and block operations.
Root cause of the reported "edit tool silently reformats the whole
file" corruption: fs/write_text_file has no verbatim guarantee. When
an ACP client (e.g. Zed with format_on_save: on) reformats a buffer
on save, routeWriteThroughBridge reported the pre-write content as
successfully written, and Patcher.commit keyed the returned snapshot
tag on that same pre-write text instead of what actually landed on
disk. The next edit anchored on that tag then resolved hunks against
a baseline the file had already drifted away from, which is what
produced whole-file "corruption" from single-line hunks -- reproduced
live in this session against real Swift/JSON/TypeScript files with
Zed as the ACP client.
- routeWriteThroughBridge reads the file back after the bridge write
and returns the verified content plus a drift flag (best-effort:
ACP defines no ordering between the client acking the write and its
own async format-on-save settling, so this degrades gracefully to
the old stale-tag-on-next-read failure mode, never to corruption).
- HashlineFilesystem.writeText propagates that verified content in
view-space (the same space readText returns -- e.g. a notebook's
editable cell text, not its raw JSON), not storage-space, so tag
validation on the next edit compares like with like.
- Patcher.commit keys fileHash/header/snapshot on the verified
post-write content (normalized, so BOM/line-ending restoration never
produces a false "drift") when it diverges from what was sent, and
appends a warning naming the drift -- but deliberately leaves the
returned `after` (and therefore the model-visible diff) scoped to
the intended hunk. Diffing against the full drifted file would
balloon the tool response to span every reformatted line (measured
~6.8x inflation on a 245-line file with one touched line); the
warning is the correct O(1) channel for "your editor reformatted
this," not an O(file-size) diff.
- write.ts keys its own snapshot header on the verified bridge content
too (no diff-size concern there since write always replaces the
whole file).
Caught via code review (dispatched against the first pass of this
fix): a naive "just use the verified content everywhere" fix broke
.ipynb editing outright (write-space vs read-space content mismatch,
tag invalid on every notebook edit) and would have inflated every
drifted edit response by ~6.8x. Both are now covered by regression
tests that fail against the pre-fix code and pass against this one.
(cherry picked from commit 35ab80e43be5800b2f48728e4400eb9fd7f7f7d2)
- Validate line numbers as safe integers and reject values exceeding `Number.MAX_SAFE_INTEGER`.
- Impose a maximum expansion limit of 100,000 lines on patch ranges to prevent memory and CPU exhaustion.
- Inline delete range iteration directly into execution to avoid allocating intermediate anchor arrays.
- Add leniency rule in parser to auto-accept bare `-` rows as literal content when hunks represent Markdown bullet lists.
- Emit MINUS_BULLET_AUTO_PIPED_WARNING when bullet-shaped rows lack explicit plus prefixes.
- Reject ambiguous or non-bullet minus rows to prevent unified-diff contamination.
- Implemented native UTF-16 text processing in Rust diff module with support for unpaired surrogates.
- Removed `similar` crate from Rust workspace and `diff` npm package from coding-agent, hashline, and natives.
- Removed jsdiff fallback wrappers and `isWellFormed()` guards from TypeScript diff implementations.
- Added comprehensive test suite for native diff functions covering random inputs and edge cases including surrogates and emoji.
- Renamed model `codex-auto-review` to `gpt-5.3-codex-spark` with updated pricing and context window.
- `DEL N:` previously consumed the colon and body rows then failed with the
wrong guidance; it now falls through to contamination detection which
emits the corrective "has no colon" message, matching `DEL.BLK N`.
- Aligns tokenizer with the leniency removal in 766790cba.
- Accepted comma-separated ranges and harmless malformed trailers emitted by local models.
- Added focused array-input diagnostics and reinforced canonical hashline syntax in the edit prompt.
- Covered the recovered forms with behavioral regression tests.
Fixes#5805
- Added the `enforceSeenLines` option to hashline `PatcherOptions` (defaults `true`); the seen-line guard in `Patcher` now runs only when enabled.
- Added the `edit.enforceSeenLines` coding-agent setting (default off) and wired it through `edit/hashline/execute.ts` into the `Patcher`.
- Stopped `file-snapshot-store` excluding column-clipped (>512-char) lines from a snapshot's seen set, so single-line edits on long lines apply without a full-width re-read.
- Updated `seen-line-guard` tests and the hashline/coding-agent changelogs.
- 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.
- Removed the unused `delete` method from `InMemoryFilesystem`.
- Updated `relocate` to create new snapshot objects instead of mutating shared references.
- Simplified the merging logic in `relocate` to handle history concatenation more cleanly.
- 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.
- Added tree-sitter markdown support to resolve headings into full sections in `pi-ast`.
- Enabled block operations (`SWAP.BLK`, `DEL.BLK`, `INS.BLK.POST`) on markdown headings so they encompass the entire section, including nested deeper headings.
- Updated system prompt to guide agents in using structured markdown heading edits for plans.
- Fixed `plan-mode-guard` to correctly resolve local protocol options for subagents.
- 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.