dsh-plugin-verified-search
f0909172434
Verified current-source search workflow for DeepSeek Harness
PROJECT TOPICS
PROJECT README
Hash-anchored read, edit, and grep tools for DeepSeek Harness (DSH). Every line carries a short content hash; edits reference LINE#HASH anchors that are verified against the file's current content before anything is written. Stale anchors fail the whole call — never relocated, never fuzzy-matched, never silently applied to the wrong line.
Protocol adopted from pi-hashline-edit (MIT), inspired by oh-my-pi.
npx @deepseek-ai/dsh <command> — note the bare command always needs a profile: npx @deepseek-ai/dsh web or npx @deepseek-ai/dsh --profile headless "task".dsh command on PATH, plus pnpm in one go): npm i -g pnpm @deepseek-ai/dsh.dsh plugin command forwards to it (installed by the global command above).DEEPSEEK_API_KEY).Option A: npm (recommended) — published on the registry, installs the prebuilt lib/ bundle:
dsh plugin --profile web add dsh-tool-hashline
# …or without a global dsh:
npx @deepseek-ai/dsh plugin --profile web add dsh-tool-hashline
Option B: from source (development):
git clone https://github.com/InklingYoshi584/dsh-tool-hashline.git
cd dsh-tool-hashline && npm install
The @deepseek-ai/* harness packages are dev dependencies here on purpose: at runtime the plugin resolves them from the dsh installation through the profile's maintained flat fallback ($DSH_HOME/profiles/node_modules symlinks), so no duplicate harness core ever ships with the plugin. npm install only needs to succeed for the tests; running the plugin needs nothing but the preset row.
hashline presetThe plugin replaces the stock tools by preset-plane shadowing: a preset that mounts this plugin instead of tool-fs gives its sessions the hashline read/edit/grep while everything else keeps working from the host composition. Create the preset by dropping the shipped files into the user preset root (hand-created presets are discovered live):
mkdir -p "$DSH_HOME/.agent-presets/hashline"
cp preset/agent.cordis.yml "$DSH_HOME/.agent-presets/hashline/"
cp preset/preset.yml "$DSH_HOME/.agent-presets/hashline/"
…or use the Web UI (Settings → presets → copy standard as hashline) and replace the copied composition with the shipped template.
Then edit the plugin row in $DSH_HOME/.agent-presets/hashline/agent.cordis.yml to point at your install:
# npm install:
- id: tool-hashline
name: 'dsh-tool-hashline'
# from source (Windows needs the file:/// URL form):
- id: tool-hashline
name: 'file:///C:/path/to/dsh-tool-hashline/src/index.ts'
To also enable grep, add config: { grep: true } to that row.
Settings → presets → hashline, or set the default in $DSH_HOME/settings.yaml:
agent-presets:
default: hashline
New sessions now run on hashline. Sessions already running keep their composition — only new sessions pick up the preset.
LINE#HASH: tagged (1#PK:alpha).edits: [{op, pos: "N#HASH", …}] and the result returns an --- Anchors --- block.On the hashline preset, read/edit (and grep when enabled) are the hashline versions — the preset's scope layer shadows the global tool-fs/tool-fs-search tools by name. write, read_image, glob, bash, and everything else keep working from the host composition, and subagents inherit the preset.
The headless profile composes no preset roster. Use a --patch overlay with a host-plane swap instead:
# hashline.patch.yml
- id: tool-fs
disabled: true
- id: tool-fs-search
disabled: true
- insert:
- id: tool-hashline
name: 'file:///C:/…/src/index.ts'
config:
grep: true
npx @deepseek-ai/dsh --profile headless --patch ./hashline.patch.yml "your task"
AI coding agents edit files by quoting what they saw on screen. Two things can be true at edit time: the file changed since the read (a concurrent write, a drift, an earlier edit in the same turn), and the quoted text appears in more than one place. Stock edit tools handle this badly:
old_string/new_string, what DSH ships) requires the quoted text to match exactly once. Any drift means a retry loop; ambiguity means "make it more specific" busywork.The evidence that the harness — not the model — is the lever: Can Balioglu's benchmark kept 15 models fixed and swapped only the edit format. Hashline beat patch for 14 of 16 configurations, and the weakest models gained the most. Better anchoring rescues the long tail of models you'd otherwise give up on.
What hashline changes:
| Property | Stock edit |
hashline |
|---|---|---|
| Anchor | literal old_string text |
LINE#HASH content hash |
| File drifted since read | error, retry with new text | error naming the stale anchor, re-read, retry |
| Same line text appears twice | ambiguity, "be more specific" | context hashing makes collisions rare; ambiguity lists candidates |
| Multiple hunks | one call per hunk | N ops in one call, one snapshot, one atomic write |
| After an edit | re-read the file | fresh --- Anchors --- block → chained edits with no re-read |
| Search-to-edit | grep, then open, then quote | grep returns the same anchors → edit with no read at all |
On top of that, DSH's own observation policy stays in force: read-before-write and a file-level version CAS still guard the whole file, while the anchors guard the lines. You get line-level correctness and file-level freshness together.
$ read e2e/probe/notes.txt
<path>…/notes.txt</path>
<type>file</type>
<content>
1#PK:alpha
2#YB:beta
3#VZ:gamma
4#WX:delta
5#XJ:epsilon
6#BK:zeta
7#XP:eta
8#KR:theta
(End of file - total 8 lines)
</content>
One edit call, four ops, one snapshot:
{
"file_path": "e2e/probe/notes.txt",
"edits": [
{ "op": "replace", "pos": "2#YB", "lines": [] },
{ "op": "append", "pos": "3#VZ", "lines": ["BETA-NEW"] },
{ "op": "replace", "pos": "4#WX", "lines": ["DELTA"] },
{ "op": "replace", "pos": "5#XJ", "end": "6#BK", "lines": ["EPSILON-ZETA"] }
]
}
The file …/notes.txt has been updated: 4 edit(s) applied.
--- Anchors 1-6 ---
1#ZP
2#XZ
3#BY
4#RJ
Deletes beta, inserts BETA-NEW after gamma, rewrites delta, and collapses the two-line range epsilon…zeta into one — all validated against the pre-edit content and committed in one write. The fresh anchor block drives the next edit without a re-read.
readLine-numbered UTF-8 content with a hash per line. The hash covers the line's context triple (prev + curr + next), so identical lines in different contexts hash differently, and editing line N invalidates anchors only for N−1, N, N+1.
| Arg | Meaning |
|---|---|
file_path |
Path, resolved against the session workspace |
offset, limit |
1-based window (default 2000 lines, byte-capped) |
raw |
Plain content without tags |
edit| Op | Anchor | Effect |
|---|---|---|
replace |
pos (or pos + end) |
Replace a line or inclusive range with lines; lines: [] deletes |
append |
pos (omit → EOF) |
Insert lines after pos |
prepend |
pos (omit → BOF) |
Insert lines before pos |
replace_text |
unique old_text |
Literal substring replace (off by default: anchor-only) |
Rules: every op validates against the same pre-edit snapshot; overlapping ops are rejected (HASHLINE_INVALID_PATCH); lines must be literal content — hashline prefixes or diff markers are refused; a successful edit returns fresh anchors for the changed region; a no-op warns, three consecutive identical no-ops raise HASHLINE_NOOP_LOOP.
grep (opt-in, grep: true)Searches with the packaged ripgrep binary — no system rg required. Matched lines return as the same LINE#HASH anchors, and matched files are recorded as read, so anchors feed edit directly with no prior read. Args: pattern (regex unless literal: true), path, glob, ignore_case, context (0–5), limit (default 50, max 200). Respects .gitignore.
Stable {name, code} metadata on failures:
| Code | Trigger |
|---|---|
HASHLINE_STALE_ANCHOR |
Anchor hash no longer matches its line |
HASHLINE_AMBIGUOUS |
Hash matches multiple lines (candidates listed) |
HASHLINE_INVALID_PATCH |
Overlapping ops, or non-literal lines |
HASHLINE_NOOP_LOOP |
Three consecutive identical no-op edits |
FS_NOT_OBSERVED / FS_STALE_VERSION |
DSH policy gate, unchanged from stock, with re-read remedies |
ZPMQVRWSNKTXJBYH (16 visually distinct chars, 4 bits each), default length 2 (configurable 2–4). FNV-1a over the UTF-8 context triple, deterministic across platforms.fs/edit-intent → version-CAS write, so concurrent modification between validation and write is caught as FS_STALE_VERSION.All optional (on the preset row's config):
| Key | Default | Meaning |
|---|---|---|
hashLength |
2 | Hash chars per line (2–4); longer → fewer collisions, more tokens |
replaceText |
false | Allow the literal replace_text op |
grep |
false | Register the hash-anchored grep tool |
readLimit |
2000 | Max lines per read |
readMaxLineLength |
2000 | Chars per line before truncation |
readMaxBytes |
51200 | Byte cap per read window |
readStreamMinSize |
10485760 | Files at/above this size stream |
cordis.patch.yml are bare top-level entries (- id: tool-fs + disabled: true, no name); restating name inside an insert: list creates a NEW row and the loader fails with duplicate loader entry id.file:/// URLs on Windows; a bare drive path fails with ERR_UNSUPPORTED_ESM_URL_SCHEME.src/hash.ts Hash core: FNV-1a, context triples, anchor format/parse (no deps)
src/render.ts Read windowing, byte caps, tagged envelope (no deps)
src/edit-engine.ts Op resolution, strict validation, bottom-up apply (no deps)
src/grep-engine.ts rg argv, NDJSON parsing, region rendering (no deps)
src/read.ts read tool (ctx.fs + fs/observed)
src/edit.ts edit tool (fs/edit-intent + version-CAS write)
src/grep.ts grep tool (ctx.subprocess + packaged rg)
src/prompts/*.ts Model guidance sections (the "prompt injection")
src/index.ts Plugin contract: name/inject/Config/apply
preset/ Drop-in `hashline` preset template
tests/ 105 tests: unit + integration over real fs-local, policy, and ripgrep
hashLength: 3 or 4 is there for very large or very uniform files.47f943, npm @deepseek-ai/dsh-* rc.1/rc.5). DeepSeek warns the preview will break compatibility — pin and re-verify on upgrade.npm install
npm run check # typecheck + 105 tests
npm run build # lib/index.js — what the npm package ships
pi-hashline-edit for the protocol (context hashing, op set, strict anchors, grep-anchor loop), oh-my-pi for the hashline technique, Can Balioglu for proving the harness problem.
MIT
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。