Tools & Capabilities

dsh-better-edit

Rianico/dsh-better-edit · ★ 43

RianicoCurated

Hash-anchored read / edit / batch_edit / undo_last_edit tools: every line gets a unique 3-character content hash, edits target hashes instead of line numbers, and served-state verification rejects stale ranges with fresh anchors.

License
MIT
Added
2026-08-16
Maintenance
active
Install kind
github
npm
dsh-better-edit
Manifest details
Manifest status
Declared
Manifest path
package.json#dsh
Manifest checked
2026-08-19
Lifecycle scripts
None found

Static manifest checks report declarations only; they are not compatibility or security review.

安装

dsh plugin --profile web add github:Rianico/dsh-better-edit

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

For install and environment setup, plus plugin development tutorials, see DSH community docs ↗

README

Source README · body: en · Read Chinese README

Table of contents
  1. Why You Need It
  2. The 3 Fatal Editing Traps of Autonomous Coding Agents
  3. Core Pillars
  4. 1. 🪙 Token Economics (40–60% Context Savings)
  5. 2. 🛡️ Resistance to External Writes (Drift & Concurrency)
  6. 3. 🎯 Zero Silent Miswrites
  7. Quick Start — install to verified edit in 30s
  8. Install (pick one)
  9. See it work
  10. Configuration
  11. Why Hashline
  12. Token economics
  13. Tools
  14. Error codes
  15. Comparison
  16. How Anchors Work
  17. How It Replaces Built-ins
  18. Project Structure
  19. Development
  20. Benchmark
  21. Roadmap
  22. Contributing
  23. License
  24. Acknowledgments
  25. Star History

What is dsh-better-edit? A high-precision file editing plugin for DeepSeek Harness (dsh) that replaces volatile line numbers and token-wasting code echoes with immutable, content-addressed 3-character line hashes (szJ│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, dsh-better-edit slashes output tokens by 40–60%, 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 dsh-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 3-char hashes (anchor_from, anchor_to) + replacement. Cuts output tokens by 40–60%.
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. Content-Addressed Line Verification: Unique anchors via coprime probing; format-tolerant whitespace hashing; fail-closed reject-and-serve.

Core Pillars

1. 🪙 Token Economics (40–60% Context Savings)

  • $O(R)$ Edit Payloads: The model emits only { "path": "...", "edits": [["a1b", "c3d", "..."]] }, never echoing existing code.
  • Self-Serving Diffs: Every applied edit returns fresh anchors in the post-edit diff — zero re-read roundtrips to chain edits.
  • Zero-Token Auto-Rebase: Non-conflicting exterior shifts resolve locally without agent intervention — 0 tokens, 0 retries.
  • Atomic Multi-Item Batches: Apply up to 32 same-file edits in one tool call; overlapping spans abort atomically ([E_BATCH_ABORT]) before touching disk.

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

  • Auto-Formatter Immunity: Strips ASCII whitespace before hashing. Prettier, Black, and ESLint format-on-save passes never rotate anchors.
  • 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_UNSERVED_RANGE]) — recovering in exactly 1 turn.
  • Session-Keyed Leases: Leases are isolated per session, preventing cross-agent race conditions or state pollution.

3. 🎯 Zero Silent Miswrites

  • Decoupled Line Identity: Lines are verified against served snapshot lineage, not ephemeral line coordinates.
  • Collision-Free Anchors: Coprime bitset probing ensures duplicate lines in a file receive distinct, unambiguous 3-character hashes.
  • No Heuristic Guessing: Retires fuzzy matching. If an anchor cannot be unambiguously resolved, it fails closed safely.
  • Persisted Undo: undo_last_edit restores exact file content, BOM, line endings, and original anchors, persisting across session restarts.

"The harness — not the model — is the bottleneck." — Can Bölük, The Harness Problem

3 calls vs 6 · -55.8% tokens · 23/23 correctness. Tested on realistic external-drift refactoring against OMP wrapper. Payload numbers are deterministic — see Benchmark.

Quick Start — install to verified edit in 30s

Install (pick one)

npx @deepseek-ai/dsh plugin --profile web add github:Rianico/dsh-better-edit   # from github
npx @deepseek-ai/dsh plugin --profile web add dsh-better-edit                 # from npm
npx @deepseek-ai/dsh plugin --profile web add /path/to/dsh-better-edit       # local

No config. Next session runs with hashline tools. Verify:

dsh --profile <name> --dump-config   # shows "# == dsh-better-edit" layer
Requirement
Node ^22.19.0 || >=24.0.0
Profile dsh profile (dsh plugin creates one)
Backends sandboxed / remote ctx.fs
dsh 0.2.1-rc.2 (verified end-to-end; 0.1.6-alpha.2 was the last pre-0.2 baseline)

dsh 0.2.0+ is supported. Registration uses agent/created, path resolution follows the session's live working directory (workingDirectory, with the session header as the pre-0.2.1 fallback), and tool-guidance order comes from systemPrompt.getSectionOrder. There is no peer range and no runtime version probing: compatibility comes from that stable surface plus optional-service fallbacks, so a composition that omits workingDirectory or agentPresets keeps working.

See it work

read serves HASH│content — the hash is the address:

ve7│function hello() {
szJ│  console.log("world");
kQm│}

edit by hashes — always lands where you meant:

{ "path": "src/main.ts", "edits": [["szJ", "szJ", "  console.log('hi');"]] }

Returns a diff with fresh anchors — next edit needs no read:

- szJ │   console.log("world");
+ a3m │   console.log('hi');
  kQm │ }

Position-free in one line: read 1..5 → insert @0 → edit 10..12 still verifies 10..12 (resist mode). Multi-session honesty: A:10..12+1 shifts B:20..30→21..31 → B passes (drift notice); B:12..13 overlapping A → E_STALE_RANGE + fresh rows, one retry.

Batch atomically — one edit, up to 32 same-file ranges:

{
  "path": "src/main.ts",
  "edits": [
    ["a1b", "a1b", "new line 1\n"],
    ["c3d", "c3d", "new line 2"]
  ]
}

One fails → none write ([E_BATCH_ABORT]).

[!TIP] Want proof before you install? Upstream 23/23 battery runs no LLM — stale edits are rejected every run. Same algorithm.

Configuration

Tenancy and prompt guidance declare once, read at agent/created, no code change.

Store central by default $DSH_HOME/plugins/dsh-better-edit/runtime/<name>-<hash8>/ (ls-readable + .wsPath sidecar). DBs are disposable caches — rm -rf runtime/<name>-<hash8>/ is safe, rebuilt on next read.

# $DSH_HOME/plugins/dsh-better-edit/config.yaml
storeDir: central # central | workspace | /abs
autoGitignore: false
undo_ttl_s: 604800 # 7d, -1 forever
storeMaxAgeS: 2592000 # 30d janitor
storeMaxTotalBytes: 524288000 # 500 MB LRU

Env overrides yaml (DSH_BETTER_EDIT_STORE_DIR, DSH_BETTER_EDIT_AUTO_GITIGNORE).

Guidance per preset — tool:read / tool:edit / tool:undo_last_edit are plain markdown per preset at $DSH_HOME/plugins/dsh-better-edit/<preset>/<section>.md. The default order is the harness's own tool slot (systemPrompt.getSectionOrder: 1100 read, 1300 edit, 1310 undo), so a band change upstream moves the sections with it; front-matter pins a custom order. The seeded roster is the shipped presets — standard, ptc, minimal, cordis. Delete or empty a file → default re-seeds at next boot; keep a --- fence to blank on purpose.

Why Hashline

Verified against what was served. Every resolved line checked against read/diff/rejection rows. Stale or unseen → [E_STALE_RANGE]/[E_UNSERVED_RANGE] + fresh HASH│content, retry needs no read. Session-scoped — sub-agent serves never validate main edits.

Content-addressed. canon(line) strips ASCII whitespace, xxh32 → 62³=238,328 anchors. Re-inserting identical text keeps its hash; prettier/eslint --fix between edits doesn't invalidate. Unique by bitset probing — }/import repeats never collide; cap 238,328 lines ([E_LARGE_FILE]).

No loop, no ritual. No-op → No changes made; same no-op ×3 → [E_NOOP_LOOP]. Diff/echo/rejection rows count as serves — read is recovery, not ritual.

Token economics

Envelope change: hoist path, edits:[[from,to,text]], never repeat old_string.

snapshot str_replace edit edit multi OMP per-edit OMP batch
pinned 12-edit corpus 1,015 609 -40.0% 582 -42.7% 590 -41.9% 480 -52.7%
local snapshot 358 272 -24.0% 241 -32.7% 268 -25.1% 180 -49.7%

Percent vs str_replace. External row pinned corpus, cl100k_base; local npm run benchmark in upstream.

engine calls tokens saved ok
OMP 6 28,467 — ✅
hashline edit 3 12,593 -55.8% ✅

Single stochastic run, opencode-go/gpt-5.6-luna high. Artifact.

Scope & honesty. Payload deterministic; practical run stochastic. We measure payload + round-trips, not throughput. Retries are where the gap is largest — see edge cases.

Tools

Tool What it does
read HASH│content with offset/limit; [Showing N-M of T] paging; >200KB lines show marker
read_skill Plain text, no hashes, no serves — editing after it needs a serve
edit {path, edits:[[from,to,text]]} path:string|null inference, "" deletes, atomic ≤32, verify-then-write
undo_last_edit {path} restores last edit (BOM/line endings/anchors), persisted

write stays, but refuses an exact HASH│ echo for same session/path/line before dispatch.

Error codes

Code Meaning
[E_BAD_PAYLOAD] Bad tuple shape (payload must be {path, edits} with 3-position tuples)
[E_STALE_ANCHOR] No line (hash/retired/canon miss) / multi-line → read
[E_BAD_ANCHOR] Not bare 3-char, or replacement_text carries HASH│/diff-preview prefixes — refused, remove and retry
[E_SERVED_ECHO] Copied HASH│ from same session/path/line — refused, remove and retry
[E_EMPTY_RANGE]/[E_NOT_FOUND]/[E_ACCESS]/[E_UNSUPPORTED_FILE]/[E_LARGE_FILE] Empty guard / missing / access / binary / >238,328 lines
[E_REVERSED_ANCHORS] Swapped range — healed with dimmed [USER] notice on success, otherwise refused
[E_BAD_ENCODING]/[E_DECODE_FAILED] Encoding / decode failed
[E_NOT_OBSERVED]/[E_STALE_RANGE]/[E_UNSERVED_RANGE] Served-state miss — echoed fresh HASH│content
[E_UNDO_STALE]/[E_UNDO_UNAVAILABLE] Undo stale / unavailable
[E_NOOP_LOOP]/[E_BATCH_ABORT] 3× same no-op / atomic batch fail → nothing written

Full list in src/ — every rejection echoes fresh rows, no read needed.

Comparison

dsh-better-edit @oh-my-pi/hashline str_replace
Address HASH│ 3-char canon [path#tag] + line text match
Whitespace-insen. ✅ ~ n/a ❌
Duplicate lines ✅ unique ~ pos ❌ first
Verified vs served ✅ every line ~ file tag ❌
Blind edit ✅ reject ~ ❌
Batch atomic ✅ ✅ ❌
Undo ✅ ❌ ❌
Battery 23/23 10/10 —

~ partial, — n/a. Same lineage — patch library vs dsh tool pair; pick by seam.

Edge cases: wrong anchor impossible (verified), disk drift → reject+serve, shift above → nothing moves, repeats → unique/ambiguous, unseen → reject, batch → atomic. See upstream benchmarks.

Battery: 23/23 tool, 10/10 library (upstream npm run eval, same algorithm).

How Anchors Work

canon(line) strips ASCII whitespace → xxHash32 → A-Za-z0-9 3-char (62³). Stable across prettier; Unicode/strings stay significant except ASCII whitespace inside strings (linter-only). stride=62²+62+1 probes bitset → unique; cap 238,328. Store hash-store.sqlite per workspace (central, honoring XDG_CONFIG_HOME); 7-day served TTL, janitor storeMaxAgeS/LRU + wal_checkpoint.

How It Replaces Built-ins

dsh resolves agent → preset → global; built-ins live on preset. Plugin via cordis.patch.yml: at agent/created registers read/edit on agent layer (shadows, auto-unwinds); write stays with pre-execute guard + post-execute auto-read.

Project Structure

dsh-better-edit/
├── src/hashline/     # hash + served core
├── src/tool-*.ts     # read / edit / undo
├── src/served-store.ts # SQLite store
├── benchmark/corpus/ # 103-line fixture
├── test/             # 108 files, 1222 tests
├── assets/           # logo + banner
└── cordis.patch.yml

Development

pnpm install
pnpm typecheck   # tsc --noEmit
pnpm test        # vitest run
pnpm benchmark   # hash probe + session envelope (reads/retries/tokens)

Benchmark

103-line file, 12 replacements (8×1 + 4×3/6/10/15), cl100k_base. hashline vs str_replace vs oh-my-pi seq/batch. Upstream is source of truth — same algorithm byte-for-byte.

Criterion hashline str_replace seq / batch
old_string echoed never every edit never
12-edit saved 31% 0% 42% / 53%
multi-line saved 29–47% 0% 40–53%
5× output cost ~1.4× less 1× ~1.7×/~2.1× less
Verified 100% none tag only
Scenario hashline str_replace
1×8 309 324
3–15×4 393 691
TOTAL ×12 702 1015

Saved 313 (31%). Reproduce: upstream npm run benchmark. See pi-better-edit/benchmark/README.md.

Scope & honesty. Benchmark is request-payload tokens (reads cancel, replacement text identical). No transcription-failure model — real gap larger; see edge cases.

Roadmap

Current 1.0.0: the v2 line-identity architecture, per-session (session,path) store, 1648 tests, dsh 0.2.1-rc.2.

  • Keep benchmark/run.mjs in sync with ADR-0013 (reads/retries/tokens per session)
  • Re-check wiring vs next dsh (pinned 0.2.1-rc.2)
  • README.zh.md parity

Contributing

See CONTRIBUTING.md. Most valuable: more served-state edge-case tests.

License

MIT — see LICENSE.

Acknowledgments

From Can Bölük's The Harness Problem. Thanks to pi-hashline-edit, pi-hashline-edit-pro, pi-better-edit, @oh-my-pi/hashline. Reading: hash-anchors.


Star History

Star History Chart