dsh-better-editDeepSeek Harness plugin

Hash-anchored read/edit/undo_last_edit tools for DeepSeek Harness (dsh), fewer token consumption, lower cost.

Stars
21
Forks
4
License
MIT
Last commit
Sep 3, 2026
Latest release
v0.6.2

Overview

Hash-anchored read/edit/undo_last_edit tools for DeepSeek Harness (dsh), fewer token consumption, lower cost.

Original README

Cached from the project repository on Sep 3, 2026. This is source content, separate from the Agents.md review above.

View source

dsh-better-edit

dsh-better-edit

A better edit tool for DeepSeek Harness
Position-free hashes — one read, many edits, fewer tokens, more room for real work.

English · 简体中文

why hashline quick start 30s 23/23 battery

Quick StartWhy HashlineToolsBenchmarkHow Anchors WorkDevelopment

Version MIT License DeepSeek Harness Plugin npm version npm downloads GitHub Stars

file.ts → read → hashed lines → edit by hash → diff


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

This is the harness fix. Hashes replace line numbers — edits above don't shift anchors below. One read serves many edits; drift outside your range passes with a notice, true conflicts retry with fresh anchors — no full read needed.

3 calls vs 6 · -55.8% tokens · 23/23 correctness. Same external-drift refactor, same file (single stochastic run; method). Payload numbers are deterministic — see Benchmark.

Why you need this

If you've watched line 47 → 74 corrupt a file after an insert — this is for you.

Before: str_replace / line numbersAfter: hashline edit
Re-types old code (~5-6× billed)Two 3-char hashes, old text never echoed
One insert shifts every number → silent wrong lineContent addresses — edits above don't move anchors below
No check against what was shownEvery line verified; [E_RANGE_STALE]/[E_RANGE_UNSERVED] reject before write, then reject-and-serve returns fresh HASH│content

[!TIP] Shining points — honest:

  • Position-free. read 1..5insert @0edit 10..12 still lands at 10..12. Anchors are canon(line) hashes, not positions (ADR-0013). Exterior drift is a notice, not a re-read.
  • Fewer round-trips. Single-session 1 read → N edits — no ritual re-reads. Multi-session exterior A:10..12 / B:20..30 also passes; only overlapping A∩B≠∅ retries once via servedRows (no full read). Harness 9/9 green.
  • Fewer tokens. Compact payload {path, edits:[[from,to,text]]} + never echoing old_string; diff/echo/rejection rows count as serves. Envelope -40% pinned 12-edit corpus, session -55.8% on external-drift.
  • Concurrent-safe, not silent. tombstone per (session,path) epoch blocks re-bound S@3→@3; canon + hash + changed∩[L,R] makes pos-free single-thread and strict only on true overlap. One retry vs silent wrong-line.

Not for one-line touch-ups (near parity) or new files (write). Pays off in long sessions and structural edits.

Quick Start — install to verified edit in 30s

Install (pick one)

sh
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:

sh
dsh --profile <name> --dump-config   # shows "# == dsh-better-edit" layer
Requirement
Node^22.19.0 || >=24.0.0
Profiledsh profile (dsh plugin creates one)
Backendssandboxed / remote ctx.fs

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:

json
{ "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..5insert @0edit 10..12 still verifies 10..12 (resist mode). Multi-session honesty: A:10..12+1 shifts B:20..30→21..31B passes (drift notice); B:12..13 overlapping AE_RANGE_STALE + fresh rows, one retry.

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

json
{ "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/session-start, 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.

yaml
# $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 presettool:read / tool:edit / tool:undo_last_edit are plain markdown per preset at $DSH_HOME/plugins/dsh-better-edit/<preset>/<section>.md (orders 130/131/133). 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_RANGE_STALE]/[E_RANGE_UNSERVED]/[E_RANGE_UNVERIFIED] + 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_FILE_TOO_LARGE]).

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.

snapshotstr_replaceeditedit multiOMP per-editOMP batch
pinned 12-edit corpus1,015609 -40.0%582 -42.7%590 -41.9%480 -52.7%
local snapshot358272 -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.

enginecallstokenssavedok
OMP628,467
hashline edit312,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

ToolWhat it does
readHASH│content with offset/limit; [Showing N-M of T] paging; >200KB lines show marker
read_skillPlain 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

CodeMeaning
[E_BAD_SHAPE]/[E_BAD_REF]Bad tuple shape or not bare 3-char
[E_STALE_ANCHOR]/[E_AMBIGUOUS_ANCHOR]No line / multi-line → read
[E_EDIT_HASH_ECHO]/[E_WRITE_HASH_ECHO]Copied HASH│ from same session/path/line — strip and retry
[E_WOULD_EMPTY]/[E_NOT_FOUND]/[E_ACCESS]/[E_NOT_TEXT]/[E_FILE_TOO_LARGE]Empty guard / missing / access / binary / >238,328 lines
[E_BAD_OP]/[E_INVALID_PATCH]/[E_BARE_HASH_PREFIX]Swapped range / +HASH│ patch marker (auto-corrected)
[E_BAD_ENCODING]/[E_DECODE_FAILED]Encoding / decode failed
[E_NOT_OBSERVED]/[E_RANGE_STALE]/[E_RANGE_UNSERVED]/[E_RANGE_UNVERIFIED]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/hashlinestr_replace
AddressHASH│ 3-char canon[path#tag] + linetext match
Whitespace-insen.~ n/a
Duplicate lines✅ unique~ pos❌ first
Verified vs served✅ every line~ file tag
Blind edit✅ reject~
Batch atomic
Undo
Battery23/2310/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 → xxHash32A-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/session-start 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

sh
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.

Criterionhashlinestr_replaceseq / batch
old_string echoedneverevery editnever
12-edit saved31%0%42% / 53%
multi-line saved29–47%0%40–53%
5× output cost~1.4× less~1.7×/~2.1× less
Verified100%nonetag only
Scenariohashlinestr_replace
1×8309324
3–15×4393691
TOTAL ×127021015

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 0.6.1: pos-free resist/strict + tombstone/canons/epoch, per-session (session,path) store, 1222 tests, 9/9 harness.

Next
  • Keep benchmark/run.mjs in sync with ADR-0013 (reads/retries/tokens per session)
  • Re-check wiring vs next dsh (pinned 0.1.0-rc.6)
  • 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


⭐ If hashline made your agent edit better, give it a star!