DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Hashline Edittool — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins

dsh-hashline-edittool

Hashline Edittool

Line-anchored read/edit/batch_edit/grep/undo_last_edit tools for DeepSeek Harness (dsh). Every line is addressed by `<line>:<anchor>` so chained edits skip a re-read; stale or ambiguous anchors are rejected and a fresh `<line>:<anchor>` is served back. Un

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add dsh-hashline-edittool@0.8.0
READMECompatibilityVersions
hashline cards: read / edit / grep / LSP / settings

Compatibility and provenance

Hashline Edittool is published as dsh-hashline-edittool and currently resolves to version 0.8.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
npm
Registry updated
9/20/2026

Versions

0.8.0stable
9/19/2026
0.7.3stable
9/18/2026
0.7.2stable
9/17/2026
Show 12 more versionsCollapse versions
0.7.1stable
9/16/2026
0.7.0stable
9/16/2026
0.6.2stable
9/16/2026
0.6.1stable
9/15/2026
0.6.0stable
9/14/2026
0.5.2stable
9/10/2026
0.5.1stable
9/10/2026
0.5.0stable
9/10/2026
0.4.3stable
9/7/2026
0.4.2stable
9/6/2026
0.4.1stable
8/30/2026
0.4.0stable
8/22/2026

Related plugins

Loading related plugins…

Latest
0.8.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
1.6 MB
Files
196
Surface
web
License
MIT
Source
npm
GitHub
★ 1
Weekly downloads
971
Security scan
✓ v0.8.0 scan passed
Last push
9/20/2026
View source ↗Project homepage ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in developer-tools.

Web App@deepseek-ai/dsh-web-appThe dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)Sdk Minimal@deepseek-ai/dsh-sdk-minimalThe standalone minimal SDK profile bundle: JSON-RPC, one DeepSeek adapter, persistent shell, and JSONL sessionsSdk App@deepseek-ai/dsh-sdk-appThe dsh SDK profile bundle: stdio JSON-RPC serving and process lifecycle over dsh-baseSubagent Codex@deepseek-ai/dsh-subagent-codexOne-shot Codex subagent provider over the official app-server protocol

README

dsh-hashline-edittool

hashline cards: read / edit / grep / LSP / settings

Line-anchored editing for DeepSeek Harness
Every line gets a variable-length content anchor — no line numbers, no echoing old code, fewer tokens, more context for real work.

English · 简体中文

Quick Start • The Anchor Contract • Tools • Settings • Error Codes • Architecture • Acknowledgments


What it is

A DeepSeek Harness plugin that replaces the built-in read / edit / grep tools with hash-anchored versions and adds undo_last_edit, ast_grep, ast_edit, and an lsp tool on top:

  • Every line carries a content anchor — a variable-length Base62 marker (2 characters covers the first 3,844 lines; the encoding grows only as the file demands). The model edits by marker, so it never echoes the code it is replacing.
  • Edits are verified against what the model actually saw. Each resolved range is checked against the served mirror (anchor + content). A line that changed under the agent is rejected with [E_STALE] — and the rejection echoes the current lines with fresh, immediately usable anchors (reject-and-serve).
  • One call = one atomic batch. All anchors in one edit resolve against the original snapshot; any failure rejects the whole call and writes nothing. Multi-file batches are grouped per file, each file all-or-nothing, partial success reported.
  • Everything is a card. The bundled client plugin renders read / diff / grep / undo / write / structural / LSP cards in the dsh web UI from structured presentationMeta — the model text and the UI never have to agree by string parsing.

Ships as one npm package (dsh-hashline-edittool): host plugin + web card plugin + prompt sections, mounted by a single bundle patch.

Highlights

Self-rendering cards. The bundled client plugin ships its own React components — HashlineReadRow, HashlineEditRow, HashlineGrepRow (file tabs + match highlighting), HashlineUndoRow, HashlineWriteRow, HashlineAstGrepRow, HashlineAstEditRow, HashlineLspRow — registered straight into the dsh web UI's slots. Every card renders from the tool's structured presentationMeta, with anchor gutters, diff rows, and highlight spans drawn natively. No generic tool-output cards, no string parsing, no upstream web changes.

Dynamic-length anchors. Anchors are not fixed-width hashes. Allocation is shortest-first: 2 characters cover the first 3,844 lines, and a new layer grows only when the file demands it (up to 62⁸ lines — practically unbounded). All-digit encodings are skipped so a marker can never be confused with a line number, collisions are probed and resolved at allocation time, and surviving lines keep their anchors across edits within the session.

AST + LSP, two semantic backends. ast_grep / ast_edit answer "where does the syntax match" through a sandboxed tree-sitter worker, with a curated grammar catalog (SHA-256-pinned downloads, install/uninstall routes) and folded editable outlines for long files. The lsp tool answers "what does this symbol mean" through a real language server per language — started on demand, shared with dsh's own lsp service — and falls back to a heuristic backend when none can be had. Both serve their rows, so structural and semantic results are directly editable.

Diagnostics on write. After an edit/write lands, the plugin baselines the language server with the pre-write text, pulls diagnostics, and delivers them per written file — an inline, severity-tinted capsule under the card within a short window, with bounded async delivery at the model's next natural step, riding both the JSON envelope and the text channel. One switch (lsp.auto_diagnostics) turns it off.

A settings panel rendered by the plugin itself. The bundled HashlineSettingsCard is a full settings UI in the web: separator, output format, context lines, require_line_content, the AST master switch plus per-language toggles, named LSP servers, auto-diagnostics. Edit, commit, done — no YAML editing required.

Hot switching, everywhere. A committed settings change takes effect on the next tool call — output format, separator, context lines, AST/LSP toggles (verified live: flipping output_format mid-session immediately changes what the model receives). And the switch with the biggest blast radius is handled too: flipping require_line_content disposes and re-registers the edit tool's schema, so the model's very next step sees the new { anchor, line } parameter set — no restart anywhere.

Quick Start

npx @deepseek-ai/dsh plugin --profile web add github:hyperion2144/dsh-hashline-edittool   # from github
npx @deepseek-ai/dsh plugin --profile web add dsh-hashline-edittool                       # from npm
npx @deepseek-ai/dsh plugin --profile web add /path/to/dsh-hashline-edittool              # local checkout

The profile's next session runs with the hashline tools installed. Verify the layer is active:

dsh --profile <name> --dump-config   # shows a "# == dsh-hashline-edittool" layer
Requirement
Node^22.19.0 || >=24.0.0 (dsh's requirement; the store uses node:sqlite)
Profilea dsh profile (dsh plugin initializes one on first use)
Backendssandboxed / remote filesystems supported (writes go through ctx.fs)

The Anchor Contract

Markers

  • An anchor is a variable-length Base62 marker, unique per line (identical content lines get distinct anchors — the anchor is a row identity, not a content hash you can guess). Digits-only encodings are skipped, so a marker is never all digits.
  • A marker is written <anchor> or <anchor>:<line>; line is a positional hint only — the anchor is authoritative, and a disagreeing hint is a warning ([E_LINE_HINT]), not an error. The legacy <line>:<anchor> order is still accepted.
  • read output opens with an ANCHOR:FILELINE header separating the marker column from verbatim content, using the configured separator (| in the examples below):
ANCHOR:FILELINE
G8:1|// UI demo file
ur:2|export const APP = "hashline";
D0:4|export function greet(name: string): string {

Served-state verification (reject-and-serve)

A row becomes served when a tool result shows it to the model (read, grep, edit diffs, structural results, LSP rows). edit verifies each resolved range against that mirror before writing:

  • anchor unknown or row never served → [E_RANGE_UNSERVED] / [E_RANGE_UNVERIFIED];
  • served content differs from disk → [E_STALE] / [E_RANGE_STALE];
  • every rejection echoes the current lines as served rows with fresh anchors, so the fix is: take the marker from the echo and resubmit. Served rows are also emitted as fs/observed, so they can be written with immediately.

There is no Shift: block — after an edit, take anchors from the diff rows the response just gave you, or re-read.

Batch semantics

  • edits[] apply in order against one snapshot; overlapping ranges are [E_BATCH_CONFLICT]; any failure is [E_BATCH_ABORT] — nothing is written.
  • With per-item path (or every item carrying path), items are grouped per file and each file is all-or-nothing independently; results aggregate as success[] / fail[] (multi-file form). item.path === topLevelPath is auto-folded to absent.
  • Up to 32 edits per call.

op semantics

opanchor fieldbehavior
replaceanchor_start (+ optional anchor_end)swap the range for lines (non-empty; [""] clears a line to empty, distinct from del). anchor_end omitted = single-line replace; required when lines spans multiple lines.
insanchor_afterinsert lines below that line — the anchor line is kept, lines holds only what is new. anchor_start/anchor_end are refused. May anchor on another hunk's range end line, never its start/interior.
delanchor_start (+ optional anchor_end)delete the range (or the single anchor_start line); lines is ignored.
sedanchor_start (+ optional anchor_end)rewrite the range line by line with pattern + replacement + optional flags (gims), no lines, no newline in replacement; sed's \1/& and JS's $1/$& both accepted.

Mixing anchor fields with the wrong op is [E_BAD_SHAPE].

require_line_content (optional hardening)

When hashline.require_line_content is on, every anchor becomes a { anchor, line } pair — line is your declaration of that row's current full text. Declarations are verified after the stale-anchor check; a mismatch rejects the call with [E_CONTENT_MISMATCH] and echoes where your declared content actually lives.

Tools

ToolWhat it does
readFile as served rows: ANCHOR:FILELINE header + <anchor>:<line> markers (set line_numbers: false for bare anchors). offset (1-based) / limit paging; oversize lines (>200 KB) become a marker + sed hint — anchors need full lines.
editOne or more range edits via { path?, edits: [{ op, … }, …] } — the full contract is above. Replaces the legacy batch_edit.
writeFully shadowed: creates/overwrites a file and returns the write plus an auto-read preview with fresh anchors, so the next edit never needs a separate read.
grepJavaScript-flavre regex search (or regex: false for literal) across a path tree, one section per file under the same header, full lines only. -C N echoes context rows; hits are served → directly editable.
undo_last_edit{ path } reverts the file's last hashline edit — only while the file still matches the stored post-edit content; survives restarts.
ast_grepStructural search by syntax shape (pat with $NAME / $$$ARGS / $_ metavariables). Refuses instead of guessing when a pattern does not parse as one node. Long files come back as a folded, editable outline.
ast_editStructural rewrite: finds places by shape and hands the change to the same engine edit uses — served-state check, undo entry, diff and syntax gate all apply.
lspSymbol-aware work through a real language server when one can be had (started per language, shared with dsh's lsp service); falls back to a heuristic backend otherwise. Serves its rows, so LSP output is directly editable.

Output modes

hashline.output_format switches the model-facing text between:

  • text (default) — the ANCHOR:FILELINE row format shown above;
  • json — pure JSON envelopes (e.g. edit returns { ok, path, diff, hints, warnings } where diff is a {"<anchor>:<line>": content} dict). Structured, for models that prefer parsing over row formats.

The web cards are unaffected — they render from presentationMeta, which is always structured.

Settings

All keys live under the hashline namespace in dsh settings (~/.dsh/settings.yaml), are optional, and hot-reload: a committed change takes effect on the next tool call, no restart.

hashline:
  separator: "|"           # marker/content column separator (default ":")
  output_format: text      # "text" | "json"
  context_lines: 3         # context rows around stale echoes / diffs (0..20)
  require_line_content: false
  ast:
    enabled: true
    languages: {}          # per-language { <id>: { enabled: false } } narrowing
  lsp:
    servers: {}            # named servers: { <languageId>: <command> }
    auto_diagnostics: true # deliver server diagnostics inline after writes

Every one of these keys is editable in the web through the plugin's own settings card (HashlineSettingsCard) — change, commit, done; see Highlights.

Per-preset guidance

The tool:read / tool:edit / tool:grep / tool:undo_last_edit guidance sections are plain-markdown override files in the plugin's shared home, keyed by preset id — see docs/adr/0001. Emptying a file resets to the compiled default; a broken front-matter fence is fast-failed with a warning.

Error Codes

CodeMeaning
[E_ACCESS]File exists but is not readable/writable.
[E_ANCHOR_AMBIGUOUS]The anchor is live on multiple lines (a freed anchor was re-allocated while the model still held the old binding) — refused; re-read. Nothing was written.
[E_ANCHOR_STATE_DUP]The file's anchor state has duplicate anchors (corruption). The state is rebuilt; the edit is refused — re-read to get fresh anchors. Nothing was written.
[E_AST_DISABLED] / [E_AST_PATTERN] / [E_AST_TOO_LARGE]AST capability off for the language / pattern did not parse as one node / file exceeds the AST size cap.
[E_AST_WORKER_ABORTED] / [E_AST_WORKER_FAILED]The tree-sitter worker was aborted / failed.
[E_BAD_OP]Range end precedes range start (autocorrected when reversed).
[E_BAD_REF]Anchor field is not a marker copied from a row's leftmost column.
[E_BAD_SHAPE]Request/field shape wrong (unknown fields, wrong anchor field for the op, …).
[E_BATCH_ABORT]A batch item failed; nothing was written.
[E_BATCH_CONFLICT]Two items' ranges overlap on the same snapshot.
[E_BARE_HASH_PREFIX]An anchor-prefixed row was pasted into lines; stripped with a warning.
[E_CONTENT_MISMATCH]A declared line (require_line_content) does not match.
[E_ELISION_IN_PAYLOAD]Payload carries the outline marker …; warning, edit proceeds.
[E_GRAMMAR_BUILTIN] / [E_GRAMMAR_NO_DESCRIPTOR] / [E_GRAMMAR_UNKNOWN]Grammar catalog: built-in name collision / no descriptor for the language / unknown language.
[E_GRAMMAR_FETCH_FAILED] / [E_GRAMMAR_HASH_MISMATCH] / [E_GRAMMAR_NOT_IN_TARBALL]Grammar download failed / SHA-256 mismatch / entry missing from the tarball.
[E_HASH_SPACE]

Store

Anchor identity, served rows, and undo history live in one SQLite store keyed by the workspace being edited:

$DSH_HOME/plugins/dsh-hashline-edittool/<projectKey>/hash-store.sqlite

<projectKey> is a human-navigable encoding of the session cwd, so parallel workspaces never share anchors or undo history. Callers outside a workspace fall back to the shared-home store. A 7-day TTL prunes served rows; corrupt stores are quarantined and rebuilt automatically.

Architecture

One plugin, three planes:

src/
├── index.ts              # entry: mounts tools, settings, LSP, grammar routes
├── config.ts             # settings schema + wiring (hot-reload)
├── tools/                # the 8 tool entry points — thin, no IO of their own
├── domain/
│   ├── edit/             # edit engine, mutation transaction, contract, prompts
│   └── session/          # served state, hash store, file views
├── render/               # per-card projections: read / edit / grep cards, diff renderer
├── contract/             # request shapes + validation (schema is the single authority)
├── hashline/             # the anchor core: allocation, resolve/apply engine
├── infra/                # fs bridge, sandbox, paths, settings snapshot, workspace scope
├── lsp/                  # language-server sessions, auto-diagnostics
├── ast/                  # tree-sitter worker, grammar registry
└── guidance/             # per-preset override resolution + materialization
client/                   # the web card plugin (same package)
test/                     # 1,210 tests

Dependencies point one way: tools → domain → render/contract → hashline/infra. Cards render from structured presentationMeta; the model text and the UI never parse each other. See CONTEXT.md for the domain glossary and docs/adr/ for the decisions behind the contract.

DSH Version Support

Compatibility is declared through a settings-service peer dependency (@deepseek-ai/dsh-settings >=0.1.2-rc.0, enforced by npm) and verified against the harness this repository is actually run on:

dsh versionplugin versionsnotes
0.1.6-alpha.1 (current env)0.7.1+dual event registration (agent/created + legacy agent/session-start); systemPrompt resolved as a scoped service
0.1.5-rc.2 (prior env, live-verified)0.7.0unified anchor lifecycle, AST/LSP split, settings card, card gallery
≥ 0.1.2-rc.00.6.x – 0.7.1self-rendered cards, per-workspace store, settings panel
0.1.20.4.x – 0.5.xv2 dynamic anchors; dsh 0.1.2 web-card adaptation completed (#69)
0.1.2 (early)0.1.x – 0.3.xlegacy line#hash anchors, batch_edit
  • Build/test SDK line: 0.1.6-alpha.1 (#134); the 0.7.0 line was verified on dsh 0.1.5-rc.2.
  • Newer dsh 0.1.x/rc lines are expected to work; report regressions as issues.

Development

npm run typecheck   # tsc --noEmit (src + test projects)
npm test            # vitest
npm run build       # clean lib/ + tsc + client workspace

Releases are tag-first: npm run release -- X.Y.Z bumps the version, moves the changelog, tags vX.Y.Z, and pushes — the tag triggers the GitHub Actions release workflow. npm publish is blocked until the tag exists. PRs first; Closes #NN in the body. See .agents/skills/git-std.md.

License

MIT

Acknowledgments

This project is a fork of Rianico/dsh-better-edit, now maintained independently — thank you, @Rianico, for the foundation and for putting hash-anchored editing in front of DeepSeek Harness users.

That fork itself stands on the hashline lineage, and this project is grateful to:

  • pi-hashline-edit by RimuruW — the original pi-coding-agent extension that introduced content hashes and collision resolution;
  • pi-hashline-edit-pro by YuGiMob — the hardened fork the hashline core here is ported from;
  • Can Bölük's The Harness Problem — the post that showed the harness, not the model, is the bottleneck.

Related reading: Hash anchors + Myers diff + single-token anchors (dirac.run) and an independent hashline-vs-replace benchmark.


Star History

Star History Chart


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

Anchor space exhausted (> 62⁸ lines).
[E_INS_ANCHOR_DUP]ins lines[0] duplicates the anchor line; warning, proceeds.
[E_INVALID_PATCH]Diff-preview markers pasted into lines; stripped with a warning.
[E_LINE_HINT]A <line>:<anchor> hint disagreed with the anchor; anchor wins.
[E_LINE_REF]A bare number was passed as an anchor; resolved via served state when safe.
[E_LSP_NO_SERVER] / [E_LSP_BAD_OPERATION] / [E_LSP_UNAVAILABLE]LSP: no server for the language / unknown operation / server unusable.
[E_LSP_ABORTED] / [E_LSP_CLOSED] / [E_LSP_NOT_READY] / [E_LSP_TIMEOUT]LSP request aborted / channel closed / server still starting / timed out.
[E_NOOP_LOOP]The same edit keeps producing no change; resubmission rejected.
[E_NOT_FOUND] / [E_NOT_TEXT]Missing file / directory-binary-non-UTF-8.
[E_NOT_OBSERVED]File never observed this session (read-before-write policy).
[E_OP_INS]Informational: ins placed lines after the anchor.
[E_PASTE_DUP]Replacement line matches an adjacent file line; kept verbatim.
[E_SERVED_RECORD]Diagnostics: served state could not be persisted (storage failure); the response carries a re-read notice instead of silently losing the rows.
[E_RANGE_STALE] / [E_RANGE_UNSERVED] / [E_RANGE_UNVERIFIED]Served-state verification failed; the range is echoed fresh.
[E_STALE]Anchor no longer matches served content; re-read.
[E_SYNTAX_AFTER_EDIT]ast_edit's replacement would leave the file unparsable; not written.
[E_UNDO_STALE] / [E_UNDO_UNAVAILABLE]File changed after the edit / undo history could not persist.
[E_WOULD_EMPTY]Edit would empty a non-empty file; use write.
[E_WIN_REPLACE]Windows atomic replace held open by another process.
[E_PARSE_FAILED]AST worker could not parse the document.