Skip to content

quangdang46/hashline

Repository files navigation

hashline

hashline — Hash-anchored file editing for AI coding agents

Release CI License Rust

Hash-anchored file editing for Claude Code, AI coding agents, and patch-safe automation.
Every line gets a stable xxh32 hash (42:a3). Patch by anchor, not by fragile text match. Stale reads are caught and rejected before they corrupt your work.

curl -fsSL "https://raw.githubusercontent.com/quangdang46/hashline/main/install.sh?$(date +%s)" | bash

🤖 Agent Quickstart (MCP / Robot Mode)

hashline ships a 6-tool MCP server that works with Claude Code, Codex, Cursor, Windsurf, Gemini CLI, and OpenCode. The installer auto-configures it.

# MCP stdio server (auto-wired by installer)
hashline mcp

# Read a file with hashes — agents copy anchors, not lines
hashline read src/auth.js --json

# Patch by anchor — survives nearby edits
hashline patch src/auth.js 'SWAP 2:b2:'
+  const decoded = jwt.verify(token, env.SECRET)

# Dry-run before applying
hashline patch src/auth.js 'DEL 3' --dry-run --json

Output conventions

  • stdout = data only (file content, patch result, JSON)
  • stderr = diagnostics, warnings
  • exit 0 = success, exit 1 = stale-read rejection or no-op

TL;DR

The Problem

AI coding agents (str_replace, sed, bespoke edit LLM tools) routinely botch file edits. The pattern is always the same: whitespace mismatch, stale context, a } that was supposed to close a block but grabbed the wrong one instead. Each failure costs 10–60 seconds in retry round-trips, and after the first successful edit, every remaining line number shifts — so targeting by number alone is fragile.

The Solution

hashline replaces fragile text-matching with content-hashed line anchors (42:a3). Read a file once and every line comes with a stable xxh32 hash. Patch using those anchors — insertions, deletions, swaps, and block replacements all reference hashes, not line text or numbers. If the file changed between read and apply, hashline rejects the patch with a clear error. No silent corruption, no wasted retries.

Why hashline?

Feature What it does
Stable anchors xxh32 hashes survive nearby edits; re-targeting is one anchor change
Stale-read detection Hard error if file changed between read and patch
Block-aware ops SWAP.BLK / DEL.BLK / INS.BLK.POST for brace-delimited, indent-based, and Ruby def…end blocks
Atomic writes Temp file + rename. No partial writes, no torn edits
Multi-op patches Several SWAP/DEL/INS in one pass via stdin pipe
MCP server 6-tool stdio MCP for Claude Code, Codex, Cursor, and friends
Daemon mode Background JSON-RPC over Unix socket or HTTP
Dry-run preview --dry-run shows diff before applying

How hashline Compares

Dimension hashline str_replace (built-in) sed
Stable anchors ✅ xxh32 hash 42:a3 ❌ Exact text match ❌ Fragile regex
Stale-read detection ✅ Hard error on mismatch ❌ Applies blindly ❌ Applies blindly
Block replacement ✅ SWAP.BLK / DEL.BLK / INS.BLK.POST ❌ Line-granularity ❌ Line-granularity
Atomic writes ✅ Temp file + rename ✅ Temp file + rename ❌ In-place (torn writes possible)
Multi-op batches ✅ stdin *** Begin Patch ❌ One replacement per call -e flag chaining
Dry-run preview --dry-run with diff ❌ Not supported ❌ Not supported
MCP server hashline mcp (6 tools) N/A N/A
Setup Single Rust binary ~280 µs anchor resolution Built into agent POSIX standard

Quick Example

# 1. Read a file — every line gets a hash
hashline read src/app.ts

# ──[src/app.ts#1A2B]─────────────────────────────
# 1:a1| import { verify } from 'jwt'
# 2:b2| const token = req.headers.authorization
# 3:c3| if (!verify(token, SECRET)) throw 401
# 4:d4| return decode(token)

# 2. Build a patch using the anchor
hashline patch src/app.ts 'SWAP 3:c3:
+  if (!token) throw new AuthError("missing token")'

# 3. Apply — atomic write, no partial file
#    File is updated in-place with a temp-file+rename

Design Philosophy

Principle Rationale
Anchors over content matching xxh32 hashes are stable, short, and easy for agents to copy. Re-targeting after an edit is a single anchor change.
Stale-read is a hard error If the file changed between read and patch, hashline refuses — the agent must re-read and re-anchor. Better fail-fast than corrupt.
Block awareness Brace-delimited, indentation-based, and Ruby def…end block ops eliminate the "find the closing brace" problem that LLMs struggle with.
Atomic writes only Temp file + rename. No partial writes, no torn edits.

Limitation vs Alternatives

Why hashline is not a drop-in for sed or str_replace:

Edge case Reality
Text-search edits hashline does not support sed s/old/new/g — use sed when you need regex replacement across non-hashable text
Line-number targeting hashline accepts line-number targets as fallback, but the design is anchor-first
Interactive editing hashline is batch-oriented (read → patch) — for interactive editing use your editor

Installation

# macOS / Linux — curl pipe
curl -fsSL "https://raw.githubusercontent.com/quangdang46/hashline/main/install.sh?$(date +%s)" | bash

# Windows PowerShell
irm "https://raw.githubusercontent.com/quangdang46/hashline/main/install.ps1" | iex

# From source
cargo install --path crates/core

The installers auto-detect your platform, fetch the matching binary from GitHub Releases, verify the SHA-256, and atomically install to ~/.local/bin/hashline. They also auto-detect supported MCP hosts (claude-code, codex, cursor, windsurf, vscode, gemini, opencode) and upsert a hashline MCP server entry for each.


Quick Start

# 1. Read a file with snapshot hashes
hashline read src/auth.js

# 2. Apply a single-line patch
hashline patch src/auth.js 'SWAP 2:
+  const decoded = jwt.verify(token, env.SECRET)'

# 3. Apply a range
hashline patch src/auth.js 'SWAP 2..4:
+  return decoded'

# 4. Delete a line
hashline patch src/auth.js 'DEL 3'

# 5. Dry-run first
hashline patch src/auth.js 'DEL 3' --dry-run

# 6. Block operations
hashline patch src/mod.rs 'SWAP.BLK 12:
+fn replaced() {
+    // new body
+}'

# 7. Multi-op via stdin (no intermediate file)
hashline patch src/auth.js - <<'EOF'
*** Begin Patch
SWAP 5:1a2b:
+  const decoded = jwt.verify(token, env.SECRET)
DEL 9
*** End Patch
EOF

Commands

Command Description See also
read Read file with [path#HASH] + `LINE:hash content`
patch Apply SWAP/DEL/INS/BLK edits hashline guidePatch Operations
write Write content to a new file (--force overwrites)
find-block Find enclosing brace/indent/Ruby block around anchor
remove Delete a file
rename Rename (move) a file
remove Delete a file
guide Interactive user guide — always matches your binary built-in
serve daemon over Unix socket or HTTP hashline guideDaemon Mode
mcp MCP stdio server (6 tools) hashline guideMCP Mode

Architecture

                    ┌─────────────────┐
                    │  hashline read   │
                    │  [file#1A2B]     │
                    │  1:a1|content    │
                    └────────┬────────┘
                             │ copy anchor
                             ▼
              ┌──────────────────────────┐
              │  Build patch string       │
              │  SWAP 2:b2:               │
              │  +new content             │
              └────────┬─────────────────┘
                       │
              ┌────────▼─────────┐   ┌──────────────┐
              │  hashline patch  │──│  --dry-run    │
              │  file.patch      │   │  preview      │
              └────────┬─────────┘   └──────────────┘
                       │
              ┌────────▼─────────┐
              │  File updated     │
              │  (atomic write)   │
              └──────────────────┘

Block-aware resolution by extension:

.rs .js .ts .go .java .c .cpp .h .cs → brace-balanced { }
.py .verse                         → indentation-based
.rb                               → def … end matching

Limitations

Edge case Reality
Not a sed replacement hashline does not support regex find-and-replace across text — use sed for that
Anchor-first design Line-number targeting works as fallback, but the tool is optimized for hash-based edits
Batch-oriented read → patch workflow, not interactive editing
No tree-sitter Block resolution is syntactic (brace depth, indent, end), not AST-based
Error Likely Cause Fix
I/O error: No such file or directory Path does not exist Check path + permissions
line 2 content changed since last read File modified after read hashline read <file> retry patch
hash 'ff' not found in demo.txt Anchor copied from wrong read Re-read + copy fresh hash
hash 'ab' matches 3 lines 4-hex hash is ambiguous Use line-qualified 2:ab

FAQ

Does hashline work with Claude Code's built-in tools? Yes — hashline mcp exposes a stdio MCP server with 6 tools (read, patch, write, find_block, remove_file, rename_file) that any MCP-capable agent can call. The install script auto-configures it.

Can I use hashline as a daemon? Yes — hashline serve runs a background daemon that accepts JSON-RPC over Unix socket (default: ~/.hashline/daemon.sock) or HTTP (--http 17300). Set HASHLINE_URL to route CLI calls through it.

Is it fast? Anchor resolution on a 10k-line file takes ~280 µs. Full patch (parse + apply) is ~297 µs. File I/O dominates at scale, not hashing.

What about tree-sitter? hashline does not use tree-sitter. Block resolution is purely syntactic (brace depth, indentation, end keyword). This keeps the binary small and startup instant.

About

Stable line-addressed file editing for Claude Code, Codex using content-hashed anchors instead of fragile string replacement.

Topics

Resources

License

Stars

11 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors