EnRaiha/dsh-drill-kit

dsh-drill-kit

The drill for DeepSeek Harness: a bug-fix pipeline with gates, evidence and a reviewed PR — plugin, shared c2g/PR cores, Kilo+Hermes frontends, docs

待分类bug-fix-pipelinecode-reviewcode2graph

The drill kit

One repository for the whole drill: the DSH plugin that enforces it, the two shared Python cores it borrows, the Kilo/Hermes frontends for those cores, and every document the work produced.

dsh = DeepSeek Harness. The drill is one bug fix run as a pipeline with gates — localize from the failure signal, map the blast radius, enumerate the edge cases, prove red (fails on base) then green (passes with the fix), pass hygiene, pass a fresh-context read-only Review 2, then produce the PR — with every claim backed by an artifact in a ledger. "Done" is not a sentence; it is a gate evaluation.

Layout

PathWhat
index.js, lib/, test/, skills/, roles/, cordis.patch.yml, package.jsonthe DSH plugin — 16 tools, 107 tests, the gate implementation and the ledger. This is the root package, so the repo itself installs as a plugin.
core/c2g_tools.pythe c2g core (stdlib only): frame, error and the query tools — cache → merged store → binary
core/pr_craft.pythe PR-craft core (stdlib only): lint-desc, lint-comment, lint-diff, plan, checklist
frontends/kilo/, frontends/hermes/thin frontends that expose the two cores as plugins in those runtimes
docs/the specification, the reviews, and a generated source bundle for reviewers
tools/build-source-bundle.mjsregenerates docs/DRILL-PLUGIN-SOURCE-BUNDLE-*.md from the git revision

Install the plugin

The script does it, from a checkout or piped straight from GitHub — no npm registry, no API token:

./install.sh --profile web                 # official `dsh plugin add` (needs pnpm on PATH)
./install.sh --profile web --link          # symlink install: no pnpm, no network
./install.sh --profile web --skills --frontends --verify
./install.sh --profile web --uninstall

curl -fsSL https://raw.githubusercontent.com/EnRaiha/dsh-drill-kit/master/install.sh \
  | bash -s -- --profile web --dry-run      # prints every action, writes nothing
curl -fsSL https://raw.githubusercontent.com/EnRaiha/dsh-drill-kit/master/install.sh \
  | bash -s -- --profile web --link

It finds the DSH checkout itself (dsh on PATH, $HOME/projects/deepseek-harness, or a checkout beside this repo), falls back from the plugin manager to a link install when pnpm or the network is missing, backs up a profile's package.json as .bak-drill before editing it, keeps a copy of any skill or role file it would overwrite, refuses to replace a live frontend directory unless you pass --force, and never restarts anything. --dry-run prints every action without writing.

Flags: --profile <name> (default web), --dsh <checkout|bin.js>, --link, --manager, --skills, --frontends, --verify, --uninstall, --force, --dry-run.

dsh plugin --profile <profile> add dsh-drill              # from npm
dsh plugin --profile <profile> add dsh-drill@0.8.4        # pinned
dsh plugin --profile <profile> add /path/to/dsh-drill-kit # local checkout
dsh plugin --profile <profile> add github:EnRaiha/dsh-drill-kit#v0.8.6

The plugin imports host modules that the DSH runtime provides (@deepseek-ai/dsh-tools, dsh-llm, dsh-subagent). All three are declared as optional peers: the host supplies them through the profile's own resolution, a missing host still fails loudly at import, and a fresh install stays quiet instead of printing missing peer for packages the user must not install. @deepseek-ai/schemastery is different — a runtime validator, not a host service — so it ships as a regular dependencies entry.

Or by hand:

# from a checkout
node <dsh-checkout>/apps/cli/lib/bin.js plugin --profile <profile> add /path/to/dsh-drill-kit
# or straight from the repo once it is public
dsh plugin --profile <profile> add github:EnRaiha/dsh-drill-kit

The plugin row is declared in cordis.patch.yml (dsh.bundle.patch in package.json), which is what both the DSH profile manager and the awesome-dsh-plugin catalog read. Restart the runtime after adding it: host-side plugin rows are composed at boot.

Run the tests

node --test test/*.test.js           # 107 tests, 0 failures
node tools/build-source-bundle.mjs   # refresh docs/DRILL-PLUGIN-SOURCE-BUNDLE-*.md

test/integration.test.js loads the real @deepseek-ai/dsh-tools runtime, compiles every tool schema against the real DSL, and drives whole drills through the real execute() path. It needs the host packages reachable from this checkout; when they are not, it skips itself instead of failing — so a standalone consumer sees a smaller count, never a false red. To wire them up:

mkdir -p node_modules/@deepseek-ai
for p in llm/llm subagent/subagent core/tools; do
  ln -sfn "$DSH_CHECKOUT/packages/$p" "node_modules/@deepseek-ai/dsh-$(basename $p)"
done
ln -sfn "$DSH_CHECKOUT/vendor/schemastery" node_modules/@deepseek-ai/schemastery

Docs

docs/README.md indexes them. The short version: DRILL-BUG-AND-PLUGIN-FULL-DOC is the canonical specification (it folds in both reviews and the risk register), and DRILL-PLUGIN-SOURCE-BUNDLE is a generated snapshot — every tracked file plus per-file hashes and a recorded test run — so a reviewer can audit the plugin without repo access.

Credits and upstream

The drill stands on other people's work, and the c2g layer is theirs:

ProjectWhoWhat this kit uses
NodeDB-Lab/code2graphfarhan-syah (NodeDB founder), with CLI fixes by EnRaihathe c2g library + CLI, and the per-project SQLite cache whose tables drill_locate / drill_blast query. Deliberately storage-neutral — every consumer brings its own store.
NodeDB-Lab/nodedbthe NodeDB-Lab orgthe repository the drill was built and exercised against; its shards are the merged store's nd_* repo keys
awesome-dsh-pluginthe catalog maintainersthe plugin conventions this repo follows (dsh.bundle.patch, dsh- naming) and the catalog a plugin can be listed in

What is this repository's own work: the gate implementation and ledger (index.js, lib/), the drill skill and auditor role, the merged-store/embedding fallback in lib/embed.js, and the two Python cores' drill-facing tools (c2g_error, c2g_frame).

Status

dsh-drill v0.8.6 · 16 tools · 107 tests · loads on DSH 0.1.6-alpha.2. Two reviews are recorded in the docs; every defect they found is fixed with a regression test, and the ones that could not be settled are listed as unverified rather than assumed.

Licence

MIT — see LICENSE.


The package documentation follows.

dsh-drill

Evidence-gated bug-fix drill for DeepSeek Harness (dsh).

One issue, one worktree, one evidence chain. Each drill stage leaves a record in a per-task ledger; drill_gate refuses a completion claim that has no proof behind it. Red and green proofs are produced by running the test command here, never by writing an assertion into a file.

Built for the NodeDB drill (red → green → fmt/clippy → preflight → commit → Review 2 → PR), and derived from a source review of eleven existing DSH plugins — see ../drill-plugins/REVIEW-20260925.md.

Stages and gates

StageGateEvidence required
1 Localizelocalizeone locate record naming file:line — from the failure signal (drill_error resolves each frame) or by symbol (drill_locate)
2 Blast radiusblastone blast record with callers/callees/affected files (drill_blast), and/or the branch diff plus its dependents (drill_diff)
3 Edge casesedgeone edge record stating the invariants that must fail — drill_diff proposes a checklist, you supply the invariants
4 Surgical patchredthe latest test run with arm=base fails, with a captured log — a later base run that passes reopens it
greenthe latest test run with arm=fix passes, with a captured log — a pass recorded before the last edit does not survive a later failing run
hygienethe latest hygiene run (fmt/clippy/preflight) exited 0; a later dirty run reopens it
5 Reviewreviewa review record with verdict=PASS, blockers=0, and the same commit as the green proof; a later FAIL or a moved HEAD reopens the gate
6 PRpra pr record pointing at the PR body file rendered for the green proof's commit; drill_pr records one only when the lint ran without blockers, and new commits reopen both review and pr

red, green, hygiene and review are required before drill_gate reports ready. The rest are advisory gates that describe where the work stopped.

Tools

ToolWhat it does
drill_startopen a task: ledger + active.json + gate report
drill_errorstage 1 from the signal: parse a panic, backtrace, compiler diagnostic or traceback into file:line frames and resolve each frame to its symbol (cache → merged store). Toolchain and dependency frames — including a relative library/std/… panic header — are marked external, and a signal with no repository frame records a note instead of locate evidence.
drill_locatestage 1 over the local c2g cache: symbol name, or file+line from a stack frame → exact definition. Records locate.
drill_blaststage 2 over the same cache: call sites, transitive callers to a bounded depth, callees. Records blast.
drill_diffstage 2b from git: changed files against the base ref, deleted files, the files that depend on them (c2g reverse edges), and a proposed manual-test checklist. Records blast — and if the base ref cannot be compared it falls back to the worktree diff, recording the fallback command and a note instead of a base it never used.
drill_searchtext search with tgrep (trigram index) or rg: for questions the graph cannot answer — a config key, an error string, a SQL fragment, a doc claim. Optionally records the hits as locate/blast/edge evidence, always labelled text-level.
drill_indexbuild or refresh the out-of-tree tgrep index for the drill repository, so stages 1–2 stop scanning: the index lands in the cache directory, never inside the worktree, and git status stays clean. Prunes idle indexes on the way.
drill_cachestatus / prune / clear the derived cache: sizes, entry counts, the idle TTL, and what pruning freed.
drill_prstage 7: render the PR body from the ledger (why, changed files, red/green/preflight rows with exit codes and commits, Review 2 verdict, gate table), write it, score it with the PR-craft core, and record pr evidence only when the lint ran without blockers (a crashed lint is not a clean body).
drill_runrun a command, stream output into .drill/<task>/logs/, hash the log, record the exit code and the commit it ran on. This is the only way to produce a test proof: a test/hygiene record whose log sits outside the task's logs/ directory is refused. A child killed by a signal is recorded as 128 + signo (SIGSEGV → 139) with the signal in note — "it crashes on base" is a red proof, not a missing record.
drill_recordrecord non-command evidence: locate/blast/edge/review/pr/note. Refuses an unarmed test, a test/hygiene record without a log, and a log outside the task's own logs/ directory; takes an explicit head so a hand-recorded review/pr can be bound to the commit it reviewed.
drill_gatewhich gates hold, which are open, and what the next step is
drill_statusactive task, record count, last records, gate state
drill_reportrender the report (gate table + test evidence with exit codes and log hashes) and write .drill/<task>/report.md
drill_reviewspawn Review 2: fresh-context, read-only reviewer subagent driven by a role file (drill-auditor by default — project .dsh/roles, then ~/.dsh/roles, then bundled), with a hard tool-call budget; records the verdict bound to the reviewed commit
drill_setupinstall the bundled skill and auditor role into ~/.dsh/skills/drill/ and ~/.dsh/roles/

The plugin also registers one agent/turn-stopping listener: while an active task has open required gates, it injects a one-line reminder naming them. It is advisory — it can never block a turn close.

Install

# from this checkout (dev): link into a profile, then boot
dsh plugin --profile <profile> add ~/projects/dsh-drill-kit
node apps/cli/lib/cli.js <profile> --dump-config | grep -A2 'id: drill'

The plugin row ships in cordis.patch.yml as one entry (id: drill, name: dsh-drill); installing the bundle appends dsh-drill to the profile's dsh.profile.bundles automatically.

Dev-mode resolution. A link: install resolves @deepseek-ai/* imports from the link target, which escapes $DSH_HOME/profiles/node_modules (the host's module fallback) — so a linked checkout needs the host packages reachable from this directory:

mkdir -p node_modules/@deepseek-ai
H=${DSH_CHECKOUT:-$HOME/projects/deepseek-harness}
ln -sfn $H/packages/core/tools       node_modules/@deepseek-ai/dsh-tools
ln -sfn $H/packages/llm/llm          node_modules/@deepseek-ai/dsh-llm
ln -sfn $H/packages/subagent/subagent node_modules/@deepseek-ai/dsh-subagent
ln -sfn $H/vendor/schemastery        node_modules/@deepseek-ai/schemastery

A normal registry/tarball install does not need this: the package lands inside the profile tree, where the host fallback is reachable.

Configuration

Every key has a schema default; override by re-stating the row's whole config in your profile patch.

KeyDefaultMeaning
stateRoot.drillPer-task state directory, relative to the session workspace.
remindertrueRegister the turn-stopping gate reminder.
providerspawnSubagent provider used by drill_review (spawn = fresh context; fork inherits the parent prefix). A role file's provider/model win when both are present.
model''Optional reviewer model override.
defaultRoledrill-auditorRole id drill_review audits with.
maxReviewToolCalls40Fallback budget when the role sets none; 0 disables the cap. A role's own maxToolCalls always wins, including 0 — v0.8.1 fixed a case where a role saying 0 was silently overridden by this fallback. Exceeding the budget aborts the child and records FAIL.
runTimeoutMs900000Default timeout for drill_run.
errorMaxResolve12Cap on frames drill_error resolves against the graph.
requireLogtrueRefuse a test/hygiene record whose log is missing, unreadable, or outside the task's own logs/ directory. Set false to accept a log path from elsewhere — the ledger then records the hash without checking where it came from.
prLinttrueScore the rendered PR body with the PR-craft core before recording it.
prCore'' (auto)Explicit path to the PR-craft core. Left empty, the plugin resolves PR_CRAFT_CORE, then the copy that ships with the package (core/pr_craft.py), then ~/scripts/pr_craft.py — so a fresh install lints without configuring anything. A non-empty value that does not exist is refused by drill_pr rather than recorded unlinted.
pythonBinpython3Interpreter used to call the PR-craft core.
c2gEnabledtrueEnable the code2graph-backed stage 1–2 tools.
c2gCacheDir~/.cache/code2graph/projectsc2g cache root.
sqliteBinsqlite3sqlite3 executable used for read-only queries.
c2gDepth3Default transitive-caller depth for drill_blast.
rippleDepth2Default reverse-edge depth for drill_diff dependents.
searchEngineautoauto picks tgrep when an index covers the root (out-of-tree cache first, then <root>/.tgrep), otherwise rg; force with rg or tgrep.
tgrepIndexDir<cacheDir>/tgrepWhere out-of-tree tgrep indexes live (one directory per root).
cacheDir~/.cache/dsh-drillCache root for derived data. Never /tmp.
cacheTtlDays7Idle time before a cache entry is removed; 0 disables expiry.
embedEnabledtrueUse the merged c2g store as the second resolution layer.
embedStore~/Embed/c2g/graph_index.sqlitePath to the merged c2g graph store.
embedRepoMap''Override shard labels, e.g. nd_src=nodedb/src,nd_sql=nodedb-sql.
autoIndexfalseWhen true, a missing index is built on the first search instead of falling back to rg. Off by default so a search never writes unexpectedly.
rgBinrgripgrep binary name or absolute path.
tgrepBintgreptgrep binary name or absolute path; ~/.local/bin and ~/.cargo/bin are searched after PATH.
searchMaxHits200Cap on returned hits; the result reports when it truncated.

Evidence layout

.drill/
  active.json                  # the task a turn-stopping reminder talks about
  <task>/
    ledger.jsonl               # append-only, one JSON object per record
    logs/<stamp>-<label>-<arm>.log
    artifacts/review-<stamp>.md
    report.md

Every test/hygiene record carries exit, sha256 of its log, and the head commit the command ran on. A record whose log is missing is refused at write time, so the ledger cannot accumulate unbacked claims; a review that names a different commit than the green proof cannot close the review gate.

Search engines (stage 1–2 fallback)

Which "c2g"? Three different things share the name: the upstream extractor is farhan-syah's NodeDB-Lab/code2graph (the c2g binary, called only as a last resort); the per-project cache ~/.cache/code2graph/projects/<key>/cache.sqlite3 is layer 1 below; and ~/Embed/c2g is this machine's merged store, built by ~/scripts/merge_c2g_shards.py, layer 2.

The graph is authoritative when it answers, but it does not always: a repository may have no c2g cache, the symbol may not be indexed, or the question may not be about a symbol at all. Three layers, chosen per call and always named in the evidence:

LayerUsed forEvidence label
c2g CLI cache (read-only SQL over ~/.cache/code2graph/projects/<key>/cache.sqlite3)resolved definitions, call sites, callees, transitive impact, reverse-edge dependents, for the exact worktree that was indexedc2g <db>
merged c2g store (~/Embed/c2g/graph_index.sqlite)the same questions for any worktree of the repository — one merged graph, shard paths mapped to the worktreec2g-embed <store> (built <ts>)
tgrep (trigram index under <cacheDir>/tgrep)fast substring/regex over an indexed roottgrep <bin> (text-level)
rg (ripgrep)everything else, and any root without a tgrep indexrg <bin> (text-level)

auto prefers tgrep only when the root actually carries an index — an unindexed tgrep run scans every file and writes a warning into the evidence log, which is worse than ripgrep. Both engines emit the same --json match stream, so one parser reads them.

Indexes live outside the repository. A tgrep index normally sits at <root>/.tgrep, which would add untracked files to a drill worktree and surface in git status — exactly what the drill's own preflight checks look at. drill_index (and autoIndex) instead pass --index-path, writing to ~/.cache/dsh-drill/tgrep/<root-slug>/: measured at ~1.5 s and ~60 MB per NodeDB worktree, ~92 MB peak RAM, and the worktree gains nothing.

Merged c2g store (~/Embed)

~/Embed is the machine's embedding and merged-graph store (RULES.md there owns the layout). Its c2g/graph_index.sqlite is a different artifact from the per-project CLI cache: one file with nodes(id, name, kind, file, repo, line) and links(source, target, relation), merged from per-crate shards and rebuilt on import. Because it is merged, it answers for every worktree of the repository — which is exactly the gap the per-project cache leaves.

Paths inside it are shard-relative, so the plugin maps them and only claims a mapping it can confirm on disk:

ShardWorktree path
nd_srcnodedb/srcverified
nd_testsnodedb/testsverified
nd_sqlnodedb-sqlverified
nd_clusternodedb-clusterverified
nd_typesnodedb-typesverified
nd_vectornodedb-vectorverified
nd_restalready worktree-relativeverified

A row whose mapped path is absent from the worktree is returned with path not in this worktree rather than silently reported as present.

This is a snapshot, not the worktree's HEAD. Evidence recorded from it says so, and carries the store's build time from manifest.json (c2g-embed … (built 2026-09-24T05:24:54+0800)). Precedence stays: the per-worktree CLI cache is freshest, the merged store answers next, and text search is last.

Measured on nodedb-296 (no per-worktree cache, merged store present):

drill_start  → stage 1–2: no per-worktree c2g cache, merged c2g store available
               (~/Embed/c2g/graph_index.sqlite (built 2026-09-24T05:24:54+0800)); tgrep index ready
drill_locate nextval_batch → nodedb/src/control/sequence/registry.rs:227 (nd_src)
                             nodedb/src/control/sequence/types.rs:91   (nd_src)
drill_blast  catalog_err   → 40 resolved call sites, 60 transitive callers in 24 files

From a failure signal to symbols

The pipeline's first stage starts from "the stack trace, error logs, input payload, and the specific function throwing the error" — not from a symbol name. drill_error takes the raw text and does that work:

  • Parses a Rust panic header (panicked at path:line:col: msg), a numbered backtrace (keeping crate::module::fn as a symbol_hint), a compiler --> diagnostic, a Python File "x", line N, and bare file.ext:line mentions in prose. Frames dedupe by file:line and cap at 40.
  • Marks toolchain and dependency frames (/rustc/, ~/.cargo/registry/, node_modules, site-packages) as external instead of resolving or dropping them, so the reader can see why nothing was resolved there.
  • Resolves each repository frame through the same layers as drill_locate — per-worktree c2g cache, then the merged store — and shows both answers when the backtrace names a symbol the graph disagrees with (→ nextval_batch [Method] (c2g-embed) (backtrace names …::allocate)).
  • Records the whole chain as locate evidence, which is what closes the localize gate.

A miss is a note, not evidence

A lookup that finds nothing still appends a record — for the audit trail — but carries no files, symbols or text, so the gate it belongs to stays open.

The rule reaches every tool that can write one of those kinds: drill_locate, drill_blast, drill_search when it is asked to record (kind: locate|blast|edge), drill_error when every frame is toolchain or dependency, and drill_locate's merged-store fallback when the store knows the symbol but no hit resolves to a file in this worktree. Each of them writes the command it ran and … — the <gate> gate stays open, never a text field that the gate would count.