dshbase

插件目录 / Developer / dsh-tool-hashline

dsh-tool-hashline

已验证 · 实测可装 InklingYoshi584

✓ 持续维护 纯 TypeScript

查看 GitHub ↗ ← 返回插件目录

2Stars
0Forks
0未关闭 issue
TypeScript语言
2026-08-15最近推送
跨平台平台

功能简介

哈希锚定的读/编辑/搜索工具,拒绝过期锚点

✅
我们的评价
可用 — 实测通过,早期项目

哈希锚定的读/编辑/搜索工具,拒绝过期锚点 实测能干净安装、正常启动。早期项目,但功能可用。

「已验证」表示我们的自动化 CI 在干净 profile 里实际执行了 dsh plugin add 并启动成功——仅此而已。功能描述与版本兼容性均为作者声明。这不是安全审计,也不代表对第三方代码的背书。

README

dsh-tool-hashline

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.


How to install

Prerequisites

  • Node.js ≥ 20 (for running dsh and the plugin).
  • dsh. Two ways to have it:
    • No install: 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".
    • Global install (gives you the bare dsh command on PATH, plus pnpm in one go): npm i -g pnpm @deepseek-ai/dsh.
  • pnpm — the dsh plugin command forwards to it (installed by the global command above).
  • A DeepSeek API key for live sessions (configure it in the Web UI at Settings → Models, or export DEEPSEEK_API_KEY).

Step 1 — get the plugin

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.

Step 2 — author the hashline preset

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

Step 3 — select the preset

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.

Step 4 — verify it works

  1. In a session: read any text file — output lines are LINE#HASH: tagged ( 1#PK:alpha).
  2. Ask the agent to edit something — the call carries edits: [{op, pos: "N#HASH", …}] and the result returns an --- Anchors --- block.
  3. Settings → Agent presets should show "In use: Hashline".

What the swap changes

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.

Headless / no-roster deployments

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"

Why use it

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:

  • Literal string-replace (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.
  • Line-number editing is worse: numbers silently point at the wrong line after any shift, and the edit corrupts code the model never looked at.
  • The problem is real enough that Cursor trained a dedicated 70B model just to apply edits correctly.

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.

Demo

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

Tools

read

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

Errors

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

How the protocol works

  • Hash alphabet 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.
  • Context invalidation: editing line N changes hashes for N−1, N, N+1 only — exactly the region re-anchored after each edit.
  • Strictness: an anchor that doesn't match fails the whole call. No relocation to a "close enough" line, ever — the tool trades convenience for correctness.
  • File-level safety net: every mutation still goes through DSH's fs/edit-intent → version-CAS write, so concurrent modification between validation and write is caught as FS_STALE_VERSION.

Configuration

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

Composition gotchas (verified against the published dsh)

  • Id-targeted overrides in a profile 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.
  • Absolute plugin paths in patch rows must be file:/// URLs on Windows; a bare drive path fails with ERR_UNSUPPORTED_ESM_URL_SCHEME.
  • The registry throws on same-name tools within one scope layer — the preset-plane shadowing is what makes replacement possible, not re-registration.

Repository layout

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

Limits

  • Stale anchors fail, they don't self-heal. pi-hashline-edit's 3-way snapshot-merge recovery (ADRs 0004/0005) is deliberately deferred; the tool returns clear re-read guidance instead. Snapshots and merge recovery are the natural v2.
  • 2-char hashes are 256 buckets. Context hashing plus ambiguity rejection make collisions rare and safe (they error, never misapply), but hashLength: 3 or 4 is there for very large or very uniform files.
  • Dev preview churn. Built against DSH v0.1 (source @ 47f943, npm @deepseek-ai/dsh-* rc.1/rc.5). DeepSeek warns the preview will break compatibility — pin and re-verify on upgrade.

Development

npm install
npm run check    # typecheck + 105 tests
npm run build    # lib/index.js — what the npm package ships

Credits

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.

License

MIT

安装

🧩 让 Agent 自动装(推荐)

装一次目录插件,之后本站所有插件都能让 DeepSeek Harness 自动找、自动装:

dsh plugin add dshbase-catalog

然后对 agent 说「帮我装 dsh-tool-hashline」,它会在目录里找到并自动安装。文档:dshbase-catalog · 已验证场景包。

Web profile:

dsh plugin --profile web add dsh-tool-hashline

Headless(CLI)profile:

dsh plugin --profile headless add dsh-tool-hashline

包信息

npm:dsh-tool-hashline · 版本 — · 实测环境 dsh 0.1.0-rc.6

实测报告

端到端验证通过:dsh 0.1.0-rc.6 上 L1 安装 + L2 加载 + L3 运行问答。

使用场景

扩展 agent 的编码能力面——给它一个新工具、工作流或集成,让它接手以前做不了的开发任务。

适合谁

想让 dsh 在真实代码库上像队友一样干活的开发者——能改、能跑、能验证,而不只是回答问题。

二次开发建议

工具/命令面就是缝:暴露更多 SDK 能力、加更聪明的上下文接线,或收紧改代码与验证之间的循环。

安全:尚未扫描——我们的每日静态扫描将很快覆盖它。

分享徽章

Developer 里更多

浏览全部 7797 个插件 →