Extension Edit @piex-dev/hashline

hashline — Edit by Content Anchor, Not Fragile Line Numbers

TL;DR

After installing @piex-dev/hashline, default edits go from "guessing line numbers" to "editing against the version you read": fewer silent mistakes, at the cost of occasional re-reads. A tax worth paying.

Overview

The edit tool of an AI coding assistant has a fundamental conflict: the model describes where to change with line numbers, but line numbers are deeply unstable in the real world. A user edits the file while the model thinks; a prior edit shifted lines; an unread region gets edited against an "assumed" line number — all cause silent, wrong writes.

@piex-dev/hashline overrides Pi's built-in edit with content anchors: tag on read, verify on write, reject lines never shown.

How it works

On read, a [path#TAG] is injected and seen-lines recorded; an edit must carry the same tag. The whole-file 4-hex tag acts like an optimistic lock: any external change invalidates it and forces a re-read. Stricter than per-line hashing, but it buys tree-sitter block ops and boundary repair.

hashline
[src/main.ts#A3F2]
SWAP 2.=2:
+const x = 2;

Phase 1 guards: 3 consecutive byte-identical noops → [E_NOOP_LOOP]; re-sending the same payload after success with an unchanged file → [E_DUPLICATE_EDIT]; CRLF / code-fence / extra-blank-line dialects normalized.

Usage

Install

bash
pi install npm:@piex-dev/hashline

Active immediately: hashline overrides Pi's built-in edit tool and hooks read results. No extra switch, no config file.

Source: extensions/hashline

Configuration

Works out of the box, no required config. For local development, install runtime deps first:

bash
cd extensions/hashline && npm install && cd ../..

Deps: @oh-my-pi/hashline ^17.1.3 (runtime), patch-package ^8.0.1 (runtime), @earendil-works/pi-coding-agent (peer), typebox (peer).

Workflow

flow
1. LLM calls read /tmp/file.js
2. hashline hook → inject header: [/tmp/file.js#A1B2]
3. LLM receives tagged file content
4. LLM emits a hashline patch
5. Patcher verifies #A1B2 + seen-lines → applies edit
6. Returns new tag: #C3D4

Verify

bash
pi -e ./extensions/hashline/src/hashline.ts -p "what is 1+1" --no-session

Implementation

Wraps @oh-my-pi/hashline: hashline.ts overrides edit and hooks read; PiexNodeFilesystem drives node:fs + realpath directly; EditGuard handles noop/duplicate; Bun polyfill only provides xxHash32.

CapabilityStatus
tree-sitter block / REM·MV✅ via engine
Noop / Duplicate / normalize✅ Phase 1
Stale auto-recovery / multi-version snapshot❌ Phase 2

Markdown fence protection (patch-package patch + Phase 2.5)

Problem

The upstream boundary-repair engine is designed for code (} / ) / JSX close and other structural edges) and mis-handles Markdown fenced code blocks. A ``` fence line is delimiter-neutral; when a SWAP range correctly includes the fence, the payload restates it, and the surviving line just outside the range happens to be the adjacent block's fence, the engine drops the payload's trailing fence as a "restated boundary" — leaving the block unclosed, shifting every later fence pair, silently.

Two-layer fix (see patches/@oh-my-pi+hashline+17.1.3.patch and hashline.ts):

  1. Fence-role guard (upstream patch): in countDuplicateLeading/TrailingBoundaryLines, reject an echo candidate when it contains a fence line and the range's own boundary line is itself a fence — the outside fence and the range fence belong to the same pairing sequence, so dropping the payload fence inevitably breaks pairing. Legitimate absorption still works: a range short of a fence (tight two-sided echo) or missing the closing fence (range's last line is content) still repairs normally. Code files are unaffected (no fence lines).
  2. Markdown fence balance check (Phase 2.5): after editing a .md / .markdown file, compare fence-line parity before/after; when an edit turns an even count odd, emit [WARN] with line numbers. Covers repair-misdeletes, SWAP-range miscounts, and model output that drops a fence — turning silent corruption into a visible warning.

Heuristic limit

An odd fence count ≈ a possibly-unclosed block; it does not model ``` vs ~~~ as independent streams, nor catch "even count but already mis-paired". Same pattern as the Phase 2.4 HTML tag check: only newly introduced imbalance is reported; a pre-existing odd count is not re-warned, and an odd → even repair stays silent.

Design notes

ProjectMechanismpiex choice
oh-my-pi hashlinewhole-file tag + tree-sitter block grammar + boundary repair engineAdopted: wrap @oh-my-pi/hashline, inherit tag, SWAP.BLK, REM/MV and the engine. Not adopted: in-tree integration, Bun FS, LSP writethrough; use Node + an outer EditGuard instead
pi-hashline-editper-line context hash + 3-way merge recovery + JSON DSLNot adopted core algorithm (route divergence), borrowed the fault-tolerance intent (noop/dup guard). ADRs kept as Phase 2 Stale-recovery reference
pi-hashline-edit-proper-line content hash + stable mappingNot adopted algorithm, borrowed "minimize re-reads" as a long-term goal

Core trade-off: whole-file tag (strict and simple) over per-line tag (locally usable), with a wrapper layer for fault tolerance. Three phases: Phase 1 tolerance (done) → Phase 2 recovery → Phase 3 undo/LSP integration.

Changelog

Roadmap

Conflict policy leans toward reject (safe but costly) → add 3-way merge / Recovery; snapshots are thin → multi-version LRU + anchored Grep; duplicate granularity per section; wrapper-layer unit tests and optional LSP integration.

phases
Phase 1 ✅  Noop Loop / Duplicate Edit / dialect normalize
Phase 2    Stale recovery · multi-version snapshot · anchored Grep
Phase 3    Undo · Auto/Raw Read · LSP integration · denser tests

Version history

VersionDateChanges
0.1.32026-08-02Auto-repair warnings now show the actual dropped lines (up to 3 lines, 80-char truncated preview); the update diff echo gains an "anchor on the new line numbers" next-edit hint; the tag mismatch message points back to this session's diff echo; WARN assertion tests added for every repair path (boundary echo, duplicate prefix/suffix)
0.1.22026-07-25Upgraded @oh-my-pi/hashline to ^17.1.3; auto-apply two upstream patches via patch-package + postinstall (findDuplicateSuffix/findDuplicatePrefix boundary echo false-positive fix, import.meta.dir Node 18 compatibility fix)
0.1.12026-07-14Initial release: wraps @oh-my-pi/hashline, overrides built-in edit; Phase 1 guards (Noop Loop / Duplicate Edit / dialect normalize); Node.js native FS + realpath path normalization

Appendix: comparison with other hashline implementations

Three industry routes: oh-my-pi whole-file tag, pi-hashline-edit context per-line hash, pi-hashline-edit-pro stable mapping. piex picks the first as engine, borrowing fault tolerance from the latter. Full comparison in the source.