pi-better-edit

Hash-anchored read/edit/undo tools for pi-coding-agent. Every line gets a unique 4-char hash (A-Za-z0-9) that stays stable across edits; stale or ambiguous anchors are rejected, never fuzzy-matched. Undo persists across restarts.

Packages

Package details

extension

Install pi-better-edit from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-better-edit
Package
pi-better-edit
Version
2.12.0
Published
Oct 13, 2026
Downloads
3,505/mo · 1,299/wk
Author
rianico
License
MIT
Types
extension
Size
1.4 MB
Dependencies
4 dependencies · 3 peers
Pi manifest JSON
{
  "extensions": [
    "./dist/index.js"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README


What is pi-better-edit? A high-precision file editing extension for pi-coding-agent that replaces volatile line numbers and token-wasting code echoes with immutable 4-character line hashes addressed by (file, content) (szJx│code).

Core Philosophy: Local compute is free; the model's context window is the most precious resource. By shifting verification, snapshotting, and alignment to the host, pi-better-edit slashes output tokens — line-anchored feedback cut repair tokens by 22–58% in published paired experiments (Lamberti 2026, arXiv:2607.12713) — auto-rebases external file drift (e.g., Prettier, Git), and eliminates silent miswrites without forcing full-file re-reads.


Why You Need It

The 3 Fatal Editing Traps of Autonomous Coding Agents

File editing is the #1 point of failure for autonomous agents. Traditional tools break down in three distinct ways:

Fatal Trap in Traditional Tools Why It Breaks Agents How pi-better-edit Solves It
str_replace Token Bleed Must re-type 30+ lines of unchanged code just to change 1 line ($O(S+R)$), burning expensive output tokens (billed ~5–6× input). $O(R)$ Payloads: Sends only two 4-char hashes (anchor_from, anchor_to) + replacement. Cuts output tokens — 22–58% repair-token cuts in published paired experiments (Lamberti 2026).
Line-Number Coordinate Rot Inserting 1 line shifts all line numbers below it. Agents suffer off-by-one errors or must repeatedly re-read the file. Position-Independent Anchors: Line hashes follow content, not line coordinates. Exterior shifts auto-rebase cleanly.
Silent Miswrites & Drift Duplicate lines match the wrong function; external formatters (Prettier) or git updates cause blind overwrites or fatal errors. Line-Identity MVCC: Unique anchors via coprime probing; format-tolerant whitespace hashing; fail-closed reject-and-serve.

[!NOTE] Empirical Grounding Published findings corroborate the failure modes above:

  • Token Bleed: Line-anchored feedback cut repair tokens by 22–58% while improving patch correctness in paired experiments (Lamberti 2026, arXiv:2607.12713).
  • Silent Drift: Subword tokenizers drift across model versions, so tools must select anchors from tokenizer-stable regions (TokDrift, arXiv:2510.14972).

Core Pillars

1. 🪙 Token Economics

Published paired experiments report 22–58% repair-token cuts for line-anchored feedback (Lamberti 2026, arXiv:2607.12713).

  • $O(R)$ Edit Payloads: The model emits only { "anchor_from": "a1bX", "anchor_to": "c3dX", "text": "..." }, never regurgitating existing code.
  • Self-Serving Diffs: Every applied edit returns fresh anchors in the post-edit diff — zero re-read roundtrips to chain edits.
  • Disjoint Multi-Window Reads: Query up to 16 disjoint slices (windows: [{offset, limit}, ...]) in one turn instead of dumping 2,000 lines into context.
  • Zero-Token Auto-Rebase: Non-conflicting shifts resolve locally via $O(m \log m)$ Patience LIS alignment — 0 tokens, 0 retries.
  • Atomic Multi-Item Batches: Apply up to 32 same-file edits in one tool call; overlapping spans abort atomically before touching disk.

2. 🛡️ Resistance to External Writes (Drift & Concurrency)

  • Auto-Formatter Immunity: Strips a frozen 28-code-point whitespace class (ADR-0029) before hashing, so Prettier, Black, gofmt, and rustfmt format-on-save passes do not rotate anchors. Not total immunity: measured formatter churn includes rewriting U+200B ZWSP to a space, which rotates the affected anchor and fails closed.
  • Exterior Shift Auto-Rebase: External edits, git checkouts, or background processes outside the edit span rebase seamlessly without agent intervention.
  • Fail-Closed Reject-and-Serve: Contested interior spans fail closed without disk corruption and immediately return fresh on-disk rows in the error ([E_STALE_RANGE], [E_UNVERIFIED_RANGE]) — recovering in exactly 1 turn.
  • Session-Keyed Leases: Leases are isolated per session (served_leases), preventing cross-agent race conditions or state pollution.

3. 🎯 Zero Silent Miswrites (Formal MVCC)

  • Decoupled Line Identity: Every line is tracked by an immutable, monotonic line_id in CAS snapshot storage, not ephemeral coordinates.
  • Collision-Free Anchors: Coprime bitset probing ensures duplicate lines in a file receive distinct, unambiguous 4-character hashes.
  • No Heuristic Guessing (ADR-0016): Retires fuzzy matching. If an anchor cannot be unambiguously resolved via lease lineage, it fails closed safely.
  • Persisted Undo: undo_last_edit restores exact file content, BOM, line endings, and original anchors, persisting across session restarts.

Quick Start

Installation

# From npm
pi install npm:pi-better-edit

# From GitHub
pi install git:github.com/Rianico/pi-better-edit

# From local directory
pi install /path/to/pi-better-edit

Zero configuration required. pi automatically activates the extension on start.

Runtime Requirement Supported Version
Node.js ≥ 24.0.0
pi-coding-agent ≥ 0.75.0 (peer dependency)

How It Works

1. Read the file

read returns each line prefixed by a stable 4-character hash anchor:

ve7x│function hello() {
szJx│  console.log("world");
kQmx│}

2. Apply an edit

edit targets inclusive anchor bounds using the canonical named-object payload:

{
  "file": "src/main.ts",
  "edits": [
    {
      "anchor_from": "szJx",
      "anchor_to": "szJx",
      "text": "  console.log('hi');\n"
    }
  ]
}

3. Receive the diff with fresh anchors

The tool applies the edit and returns a unified diff showing fresh anchors for subsequent edits—eliminating the need for follow-up read calls:

- szJx│   console.log("world");
+ a3mX│   console.log('hi');
  kQmx│ }

4. Batch multiple edits atomically

Batch up to 32 edits to the same file in a single transaction. If any edit fails or overlaps, none write:

{
  "file": "src/main.ts",
  "edits": [
    { "anchor_from": "a1bX", "anchor_to": "a1bX", "text": "// Header comment\n" },
    { "anchor_from": "c3dX", "anchor_to": "c3dX", "text": "  return true;\n" }
  ]
}

Systematic Architecture

pi-better-edit v2 replaces ad-hoc string matching and heuristic healing with a formal Multi-Version Concurrency Control (MVCC) architecture.

┌──────────────────────────────────────────────────────────────────────────────────┐
│                                STORAGE TIER                                      │
│  src/hash-store.ts & src/snapshot-store/                                         │
│  - file_snapshots: CAS snapshots (snapshot_id, path, snapshot_hash, line_count)  │
│  - line_lineage: Coordinate authority (snapshot_id, line_number) -> (line_id)    │
│  - line_id_counters: Monotonic integer block allocator per path                  │
│  - served_leases: Session-keyed immutable leases (session_id, file_path, anchor) │
│  - file_undo: Snapshot-pinned undo history surviving restarts                    │
└────────────────────────────────────────┬─────────────────────────────────────────┘
                                         │
┌────────────────────────────────────────▼─────────────────────────────────────────┐
│                                SESSION TIER                                      │
│  src/served-session/session.ts                                                   │
│  - Leases: Granted on every serve path: read, diff, write auto-read, truncated   │
│    serves, fresh-read rejections, undo (see §1)                                  │
│  - Immutability: Leases are strictly READ-ONLY during edit resolution            │
│  - Re-Serve Upsert: Atomic upsert updates leases when presentation changes       │
└────────────────────────────────────────┬─────────────────────────────────────────┘
                                         │
┌────────────────────────────────────────▼─────────────────────────────────────────┐
│                       RESOLUTION & REBASE TIER                                   │
│  src/hashline/lease-resolve.ts & src/hashline/served-verification.ts             │
│  - On-Demand CAS Materialization: Materializes current disk state                │
│  - Patience LIS Pin Backbone: O(m log m) non-crossing line alignment             │
│  - Minimal Displacement Tie-Breaking: Deterministic unique pairing               │
│  - Span Contiguity Gate: Asserts interior span is not torn                       │
└────────────────────────────────────────┬─────────────────────────────────────────┘
                                         │
┌────────────────────────────────────────▼─────────────────────────────────────────┐
│                                MUTATION TIER                                     │
│  src/mutation-engine/pipeline.ts & src/hashline/apply.ts                         │
│  - Working Buffer: Preceding delta rebase for multi-item batches                 │
│  - WAL Lineage Commit: Atomically commits final snapshot and updates leases      │
│  - Fail-Closed Intercepts: Rejections emit fresh read ranges                     │
└──────────────────────────────────────────────────────────────────────────────────┘

1. Immutable Line Identity & Leases

  • Every line has an immutable surrogate key (line_id) allocated from a monotonic counter (line_id_counters).
  • When lines are delivered to an agent on any serve path — a default (served) read, diffs, the write auto-read hook, truncated serves, fresh-read rejections, or undo_last_edit — a session-scoped lease (served_leases) binds (session_id, file_path, anchor) -> line_id.
  • During an edit, lease lookups are strictly read-only. An edit cannot re-stamp or guess a lease.

2. Multi-Version Snapshot Lineage

  • Content-addressed CAS snapshots (file_snapshots) track each materialized file version.
  • line_lineage maps each line coordinate to its immutable line_id, 32-bit canon hash, and verbatim presentation anchor.
  • When disk content shifts externally, the tool pairs the latest snapshot ($S_{latest}$) with disk using Patience LIS alignment, preserving identities for surviving lines and allocating fresh IDs only for novel lines.

3. Patience LIS Pin Backbone ($O(m \log m)$)

  • Uniquely matching anchor pins form candidate pairs.
  • The engine computes the Longest Increasing Subsequence (LIS) via patience sorting in $O(m \log m)$ time.
  • Multiple maximal LIS candidates are disambiguated by minimal total displacement ($\sum |p_i - c_i|$).
  • Contested symmetric swaps (e.g. equal-length function swaps) or ambiguous duplicate blocks fail closed, marking affected lines as retired rather than guessing.

4. Working Buffer with Preceding Deltas

  • Multi-item batches (edits: [e_0, e_1, ...]) resolve their baseline coordinates $s'_k$ in the current snapshot.
  • Active in-memory buffer positions are computed by accounting strictly for preceding edits: $$\Delta_k = \sum_{j < k, s'{end, j} < s'{start, k}} \left( |R_j| - (s'{end, j} - s'{start, j} + 1) \right)$$
  • If any two edit items overlap or nest, the batch aborts atomically ([E_BATCH_ABORT]) before modifying disk.

5. Unified Span Verification (ADR-0023)

  • resolveLeasedEdit verifies the entire span against line_lineage before touching disk.
  • Canon evidence is file-scoped and verified via 32-bit digests (canon_hash), eliminating duplicate plaintext storage.

Tools

Tool Parameters Description
read file, offset (1-based), limit, windows (optional), mode (optional) Returns file content formatted as HASH│content by default. mode: "verbatim" returns plain text with no hash prefixes and records no leases; the default "served" leases every shown line. Lines >50KB are replaced with a marker hint. windows: [{offset, limit}, …] reads up to 16 disjoint ranges in one turn: each renders under === Lines A-B of N === and, in the default "served" mode, every shown line is leased, so anchors from all of them work in one edit.
edit file, edits, mode (optional) Applies single or batched edits atomically. Each item bounds an inclusive anchor_from/anchor_to range, places its payload with optional at, and carries exactly one payload — text or text_ref. mode: "literal" declares verbatim text.
undo_last_edit path Restores the previous file state, BOM, line endings, and original anchors. Persists across restarts.

Payload Contract

{
  "file": "src/example.ts",
  "edits": [
    {
      "anchor_from": "a1bX",
      "anchor_to": "c3dX",
      "text": "const status = 'ready';\n"
    }
  ],
  "mode": "general"
}

An edit item is flat, with no discriminator field: it bounds its range with the inclusive anchor pair, places its payload with the optional at, and carries exactly one payload — text or text_ref. Both or neither is refused with the offending keys named. No operation tag exists because the payload's presence is the intent, and placement is an independent axis.

Item key Values Meaning
anchor_from / anchor_to bare 4-char hash anchors Inclusive target range — both boundary lines are touched.
at (optional) "in-place" | "before" | "after" Placement relative to the resolved range. Omitted means "in-place". "before"/"after" insert at the range boundary and require a single-line resolved target; the underscore spelling "in_place" is refused — the canonical spelling is "in-place".
text string By-value payload — bare file content (\n joins lines). "" deletes the range when placed in-place (no at). With "before"/"after" an empty text does NOT delete: it writes nothing and the call narrates [W_NOOP_INSERT] as a no-op.
text_ref { anchor_from, anchor_to, file?, mode } By-reference payload — the bytes of a served span bounded by two anchors of the file it names. mode is required (never inferred): "copy" re-inserts the span and keeps the source; "cut" additionally retires it. file may name another served file — a foreign-source copy (never called "cross-file" here; that word named a dropped multi-file batching idea). Both modes apply to a foreign file, and a foreign cut commits insert and retirement as one correlated transaction.

Example items — every shipped shape, one each:

{ "anchor_from": "a1bX", "anchor_to": "c3dX", "text": "const status = 'ready';\n" }
{ "anchor_from": "a1bX", "anchor_to": "c3dX", "text": "" }
{ "anchor_from": "kQmX", "anchor_to": "kQmX", "at": "after", "text": "// Footer comment\n" }
{ "anchor_from": "p9rX", "anchor_to": "p9rX", "at": "before", "text": "  return true;\n" }
{ "anchor_from": "m2zX", "anchor_to": "m2zX", "text_ref": { "anchor_from": "d2xX", "anchor_to": "d2xX", "mode": "copy" } }
{ "anchor_from": "m2zX", "anchor_to": "m2zX", "text_ref": { "anchor_from": "d2xX", "anchor_to": "e5vX", "mode": "cut" } }
{ "anchor_from": "m2zX", "anchor_to": "m2zX", "text_ref": { "anchor_from": "g7tX", "anchor_to": "h3sX", "file": "src/helper.ts", "mode": "cut" } }

(in order: in-place replace; in-place delete; after-insert; before-insert; by-reference copy; by-reference cut in this file; foreign-source cut.) The legacy fused verb for retiring a referenced span is retired — the current word is cut.

  • file: Path to the target text file (must be a file, never a directory).
  • edits: Array of 1 to 32 edit items; text and text_ref are mutually exclusive per item.
  • mode: "general" (default) refuses text containing served anchor prefixes; "literal" allows verbatim insertion of lines beginning with HASH│.

Interoperability with pi-lens

pi-better-edit composes with pi-lens diagnostics, formatting, and its read-before-edit guard:

  • Read expansion: pi-lens widens a partial read (limit <= 100) to the enclosing symbol or markdown heading section (cap 300 lines, 200 ms budget, disabled by --no-lsp); a read without limit is never widened. Anchors always describe the rows actually served, so failure triage counts served rows rather than the requested window. To keep the requested window, pass limit > 100, read the whole file, or start pi with --no-lsp.
  • Format and autofix: pi-lens' deferred agent_end format and autofix passes rewrite files outside the model's turn. A whitespace-only rewrite is absorbed by the whitespace-insensitive canon (ADR-0005), so anchors survive; a real fix rotates the affected anchors, and the next edit fails closed with [E_STALE_RANGE] or [E_TARGET_LOST] and serves the current rows.
  • Guard compatibility: every served row and every landed mutation (reads, multi-window reads, bash views, post-edit diffs, reject-and-serve payloads, edits and writes) is reported to pi-lens through its unified v2 I/O bridge (pi-lens:io-bridge). Reads whose producer holds the file's bytes travel as caller evidence — one single-range slice per span; diffs and reject-and-serve payloads, which hold no raw text, travel as one disk-evidence call. Mutations lead the rows they produced, so the guard sees the post-edit state; retries and chained edits satisfy the read-before-edit guard without manual re-reads.
  • Overlapping reports are merged by pi-lens and are expected, not a bug.
  • Opt-in: /pi-better-edit lens (auto by default when pi-lens is detected, else off; override via PI_BETTER_EDIT_LENS_BRIDGE=auto|on|off). Core editing never depends on pi-lens — with the bridge off or absent, behavior is unchanged.
  • Removal recipe: delete src/integrations/pi-lens/, drop its two imports plus the registerLensCommand(pi) and attachIOBridgeAdapter() calls from index.ts, and drop the content argument from the seam producers (src/read.ts, and the write and bash auto-read paths in src/lifecycle-hooks/index.ts). The seams stay domain-neutral and need no other change; test/arch/lens-bridge-boundary.test.ts fails until the directory and the wiring are gone.

Bash View

Pure single-file views (cat, head, tail, sed -n prints, and the admitted grep/rg search shapes) are intercepted: the raw stdout is swapped for the same lines as HASH│content rows under a frozen header, and the rows carry working leases, so the next edit anchors with no re-read round trip (ADR-0033). Anything else — pipelines with transforms, compounds, multi-file shapes, errors, truncation — passes through byte-identical.

  • Option: bash.view in .pi/agents/pi-better-edit.json (on by default; off passes every view and search through raw). Resolves per key through env PI_BETTER_EDIT_BASH_VIEW over project file over global file over the default, with the same rung-source reporting as the other settings.
  • Slash command: /pi-better-edit bash-view [on|off|status] [project|global] (alias /pi-better-edit bash ...). Bare invocation offers the five-option picker (On/Off per scope, Status); headless hosts get the combined status text instead.
  • First notice: the first interception of each session warns once — naming the round trip saved and the off-ramp — and later interceptions stay silent until the next session.

Settings overlay

/pi-better-edit settings (bare /pi-better-edit opens the same overlay) reviews the lens bridge, bash view and store janitor rows with effective-source diagnostics (ADR-0038). The target scope switch steers later saves without persisting anything itself; headless hosts get the combined status text instead.

Error and Warning Contract

pi-better-edit enforces a strict, machine-actionable diagnostic contract (ADR-0021):

  • [E_*] marks a refusal from the call that raised it: read, edit, or undo_last_edit. Nothing was written to disk.
  • [W_*] indicates an applied mutation with an informational warning.
  • Range-family rejections carry structured details.cause values (retirement, never-served, served-range staleness) — never-served on a leased span means a boundary row, because an unread interior between two leased boundaries is accepted (ADR-0024).

Domain Rejections ([E_*])

Error Code Description Remedy / Agent Action
[E_BAD_PAYLOAD] Payload fails schema validation (missing fields, wrong types). Fix the payload fields and retry.
[E_MALFORMED_ANCHOR] Anchor is not valid — not a bare 4-char anchor (e.g. carries │, a diff prefix, or a row suffix). Pass the bare 4-char anchor (e.g. "szJx") and retry.
[E_STALE_ANCHOR] Anchor no longer resolves to its leased identity in the file. The latest known status is in this rejection. Retry as it directs.
[E_UNKNOWN_ANCHOR] The path has not served the anchor in this session. Re-read the file to establish fresh anchor leases.
[E_FOREIGN_ANCHOR] Anchor was served for a different file, not for the targeted one. Ensure anchors match the target file path.
[E_STALE_RANGE] A line in the edit range changed on disk, or a boundary line was never served (an unread interior between leased boundaries applies, ADR-0024). Current range served as a fresh read; decide next edit from fresh rows.
[E_UNVERIFIED_RANGE] One boundary lease retired while surviving bound is live and unshifted. Named window served as fresh read; decide next edit from fresh rows.
[E_TARGET_LOST] Target line identity deleted or reordered without a stable anchor bound. Read the file and re-target.
[E_SUSPICIOUS_TEXT] Replacement line starts with a served HASH│ anchor. Omit the copied anchors or declare mode: "literal".
[E_BATCH_ABORT] Two or more items in the batch target overlapping spans. Merge the overlapping ranges into a single edit, or split them into separate calls.
[E_NOOP_LOOP] Identical edit submitted 3 consecutive times without change; the range already has this text. Inspect the current range; the range already contains the target content.
[E_EMPTY_RANGE] Edit would result in an empty non-empty file. Use write to clear the file.
[E_NOT_FOUND] Target file does not exist on disk. Check the parent directory and retry with the corrected file.
[E_ACCESS] Target file is unreadable, unwritable, or in a symlink loop. Fix the path or permissions and retry.
[E_UNSUPPORTED_FILE] Target path is a directory, binary file, image, or UTF-16/32 text. Choose a text file and retry.
[E_LOSSY_TEXT] File bytes do not round-trip a UTF-8 decode; a line-addressed rewrite would destroy the original bytes. Re-encode the file as valid UTF-8 with a byte-level tool, then retry.
[E_UNDO_STALE] Cannot undo on the target path: the file was modified or deleted after the last edit. Undo refused to prevent data loss; re-read the file.
[E_UNDO_UNAVAILABLE] Cannot persist undo history to the hash store. The edit was not applied. The file is unchanged. Retry the edit. If the store cannot be recovered, use write.
[E_UNDO_REVERT_FAILED] A correlated cut-undo revert was interrupted mid-transaction and could not be completed; no undo history was cleared. Fix the file access failure. The next run repairs the interrupted revert.
[E_LARGE_FILE] A served read or edit load exceeds the served admission budget (200,000 lines, SERVED_MAX_LINES) or the 14,766,336-line ceiling of allocatable 4-char anchors; mode: "verbatim" reads are not capped. Split the file or use a non-line-based approach for very large files.
[E_INTERNAL_INVARIANT] Internal tool invariant failure (the message names the failed invariant). No remedy; check the error message details.
[E_UNKNOWN] Unknown filesystem or invariant failure (first message line reported). Check the error message details.

Applied Warnings ([W_*])

Warning Code Audience Description
[W_NEVER_SERVED_SHAPE] [MODEL] Replacement line starts with an anchor-shaped token never served for this session and file. Applied verbatim.
[W_SERVED_PREFIX_MISMATCH] [MODEL] Replacement line starts with a served anchor but the content differs. Applied verbatim.
[W_REVERSED_ANCHORS] [USER] The anchors were reversed (anchor_from after anchor_to). Applied with the range swapped.
[W_UNICODE_LITERAL] [USER] Replacement line contains a literal \uDDDD sequence. Applied verbatim.
[W_LITERAL_BYPASS] [USER] Served-echo check bypassed by literal declaration.
[W_NOOP] [USER] Identical edit did not change the range; the range already has this text. A resend rejects.
[W_NOOP_INSERT] [MODEL] An empty insertion with "before"/"after" and text: "" writes nothing. Provide text or drop the item.

Comparison

Capability Comparison

Feature pi-better-edit v2 @oh-my-pi/hashline Traditional str_replace
Addressing Model 4-char (file, content)-addressed anchors File tag + line numbers Verbatim code strings
Line Identity Immutable MVCC line_id Coordinate line numbers None (text matching)
Exterior Shift Tolerance Auto-rebases (0 tokens, 0 retries) Model must recalculate line numbers Fails if surrounding context shifts
Duplicate Line Safety Collision-resolved unique anchors Ambiguous position-based indexing Prone to matching wrong instance
Concurrent Disk Drift Fail-closed reject-and-serve Tag mismatch / best-effort 3-way merge Silent overwrite or blind failure
Batch Support Atomic up to 32 items with delta shifts Multi-section patch preflight Sequential individual calls
Undo Persistence Survives restarts (CAS snapshot pinned) None None
Session Isolation Session-keyed leases (served_leases) None N/A
Deterministic Battery 27 deterministic scenarios run by pnpm test (test/e2e/tool-battery.test.ts) N/A N/A

Edge Case Behavior

Edge Case Scenario pi-better-edit v2 @oh-my-pi/hashline
Wrong Coordinate / Off-by-one Impossible: Anchors bind to line_id; verified against lineage before writing. Possible: Wrong line number against a valid tag silently mutates the wrong code.
Lines Inserted Above Target Auto-rebases cleanly: Identity is decoupled from coordinates. Every edit renumbers: Agent must track offsets.
Deleted Function Guard Target (Probe E) Fail-closed intercept: Rejects edit; zero code corruption. Tag mismatch / merge hazard.
Equal-Length Symmetric Function Swap (Probe K) Fail-closed intercept: Contested reorder retires safely. Applies to wrong block or requires manual recovery.
Batch Items Overlap Atomic abort ([E_BATCH_ABORT]); nothing written. Preflight validation failure.

Independent Benchmark

Explicit Edit Benchmark is an independent, community-run dataset that scores harnesses and Pi editing extensions on the same 226 byte-exact edit tasks (replacements, insertions, deletions, moves, copies, unicode, large files). It is maintained by alexshpunt, not by this project, and every observation ships with its configuration.

Published pi-better-edit arm Value
Quality score 95.9%
First-attempt exact 94.7%
Exact after recovery 99.6%
Coverage 226 tasks · Pi 0.85.1 · gpt-5.6-luna, low reasoning

Scope. That arm is pinned to pi-better-edit@1.7.0 — the retired 1.x heuristic era — so the score describes the predecessor architecture, not the MVCC v2 line. It updates here when the benchmark pin moves to 2.x.


How Anchors Work

  1. Whitespace Canonicalization: Each line is stripped of a frozen 28-code-point whitespace class (ASCII plus Unicode spaces, NBSP, BOM, and directional marks — ADR-0029) before hashing. External formatting passes (prettier, black, gofmt, rustfmt) do not alter line hashes. Token-level edits (quotes, semicolons, variable names) rotate the hash, and so do the significant zero-width characters the class deliberately excludes (U+200B ZWSP, U+200C/D ZWNJ/ZWJ) — oxfmt normalizes ZWSP to a space, which rotates the anchor and fails closed rather than passing silently.
  2. xxHash32 & Base62 Space: Canonical lines are hashed using xxHash32 seeded with the file's canonical path ((canonical path, canonical line) → fileBaseIndex) and mapped to 4-character base62 strings (A-Za-z0-9), providing $62^4 - 10^4 = 14,766,336$ allocatable anchors — the 10,000 all-digit spellings are reserved and never served, since a served one would be indistinguishable from a line number. Base62 strings occupy tokenizer-stable token regions across model families (TokDrift, arXiv:2510.14972). The live width record is ADR-0030.
  3. Collision-Free Coprime Probing: When duplicate lines occur in a file, collision resolution probes using a stride coprime to the hash space ($62^2 + 62 + 1 = 3,907$). Every line in a file receives a unique anchor.
  4. SQLite WAL CAS Storage: Line hashes and snapshots are persisted in ~/.config/pi-better-edit/hash-store.sqlite (honoring XDG_CONFIG_HOME; PI_BETTER_EDIT_CONFIG_DIR overrides both it and XDG_CONFIG_HOME, and names the app config dir itself — no pi-better-edit suffix is appended, and it is the ONLY thing that can move the store: no config file key can). Snapshot retention is governed by proportional LRU vacuuming under a 50MB budget. Maintenance is split by WHAT IT COSTS: an open reads the database header and takes no write lock unless the store's provenance stamp has moved, while the store janitor (src/store-janitor.ts) runs the generation sweep and SQLite's full PRAGMA quick_check on session_start and session_shutdown, throttled through one meta row to about once a day and never on a tool call. That periodic deep check is the half of issue #100 that closes "not detected at open" and "not healed on reopen" — a detected failure goes to the open path's own quarantine-and-rebuild — and it deliberately leaves the use-time clause, "not quarantined at use", to #100. The journal has one owner too: PRAGMA wal_checkpoint(TRUNCATE) runs on exit, SIGINT and SIGTERM, so the janitor adds no checkpoint of its own and its writes ride the WAL every other writer's do. /pi-better-edit lens status reports the resolved store path, the store settings with the scope each value came from, and that override by name.

Upgrading from 1.x

Version 2.0 represents a major architectural upgrade from heuristic healing to formal MVCC:

  1. Heuristic Healing Deleted (ADR-0016): Heuristic guessing of relocated anchors (tryHealOrphanedSpan) is completely removed to eliminate silent miswrites on duplicate code.
  2. Boundary Rule Replaces E_UNSERVED_RANGE (ADR-0020): The old E_UNSERVED_RANGE code is retired. If one boundary lease is retired while the other survives unshifted, the tool emits [E_UNVERIFIED_RANGE] with a fresh read. If both bounds are lost, it emits [E_TARGET_LOST].
  3. Unified Diagnostic Contract (ADR-0021): Rejections use [E_*]; successful mutations with caveats use [W_*]. Structured diagnoses live in details.cause.
  4. Additive Store Migration: The SQLite schema migrates additively from version 6 to 7. Existing project files are untouched.

Development

# Install dependencies
pnpm install

# Run unit and integration tests
pnpm test

# Run quality checks
pnpm run lint
pnpm run format
pnpm run typecheck

License

MIT

Acknowledgments