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.
| Path | What |
|---|---|
index.js, lib/, test/, skills/, roles/, cordis.patch.yml, package.json |
the 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.py |
the c2g core (stdlib only): frame, error and the query tools — cache → merged store → binary |
core/pr_craft.py |
the 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.mjs |
regenerates docs/DRILL-PLUGIN-SOURCE-BUNDLE-*.md from the git revision |
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 --linkIt 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.6The 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-kitThe 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.
node --test test/*.test.js # 107 tests, 0 failures
node tools/build-source-bundle.mjs # refresh docs/DRILL-PLUGIN-SOURCE-BUNDLE-*.mdtest/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/schemasterydocs/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.
The drill stands on other people's work, and the c2g layer is theirs:
| Project | Who | What this kit uses |
|---|---|---|
| NodeDB-Lab/code2graph | farhan-syah (NodeDB founder), with CLI fixes by EnRaiha | the 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/nodedb | the NodeDB-Lab org | the repository the drill was built and exercised against; its shards are the merged store's nd_* repo keys |
| awesome-dsh-plugin | the catalog maintainers | the 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).
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.
MIT — see LICENSE.
The package documentation follows.
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.
| Stage | Gate | Evidence required |
|---|---|---|
| 1 Localize | localize |
one locate record naming file:line — from the failure signal (drill_error resolves each frame) or by symbol (drill_locate) |
| 2 Blast radius | blast |
one blast record with callers/callees/affected files (drill_blast), and/or the branch diff plus its dependents (drill_diff) |
| 3 Edge cases | edge |
one edge record stating the invariants that must fail — drill_diff proposes a checklist, you supply the invariants |
| 4 Surgical patch | red |
the latest test run with arm=base fails, with a captured log — a later base run that passes reopens it |
green |
the 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 |
|
hygiene |
the latest hygiene run (fmt/clippy/preflight) exited 0; a later dirty run reopens it |
|
| 5 Review | review |
a 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 PR | pr |
a 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.
| Tool | What it does |
|---|---|
drill_start |
open a task: ledger + active.json + gate report |
drill_error |
stage 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_locate |
stage 1 over the local c2g cache: symbol name, or file+line from a stack frame → exact definition. Records locate. |
drill_blast |
stage 2 over the same cache: call sites, transitive callers to a bounded depth, callees. Records blast. |
drill_diff |
stage 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_search |
text 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_index |
build 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_cache |
status / prune / clear the derived cache: sizes, entry counts, the idle TTL, and what pruning freed. |
drill_pr |
stage 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_run |
run 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_record |
record 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_gate |
which gates hold, which are open, and what the next step is |
drill_status |
active task, record count, last records, gate state |
drill_report |
render the report (gate table + test evidence with exit codes and log hashes) and write .drill/<task>/report.md |
drill_review |
spawn 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_setup |
install 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.
# 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/schemasteryA normal registry/tarball install does not need this: the package lands inside the profile tree, where the host fallback is reachable.
Every key has a schema default; override by re-stating the row's whole config in your profile patch.
| Key | Default | Meaning |
|---|---|---|
stateRoot |
.drill |
Per-task state directory, relative to the session workspace. |
reminder |
true |
Register the turn-stopping gate reminder. |
provider |
spawn |
Subagent 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. |
defaultRole |
drill-auditor |
Role id drill_review audits with. |
maxReviewToolCalls |
40 |
Fallback 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. |
runTimeoutMs |
900000 |
Default timeout for drill_run. |
errorMaxResolve |
12 |
Cap on frames drill_error resolves against the graph. |
requireLog |
true |
Refuse 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. |
prLint |
true |
Score 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. |
pythonBin |
python3 |
Interpreter used to call the PR-craft core. |
c2gEnabled |
true |
Enable the code2graph-backed stage 1–2 tools. |
c2gCacheDir |
~/.cache/code2graph/projects |
c2g cache root. |
sqliteBin |
sqlite3 |
sqlite3 executable used for read-only queries. |
c2gDepth |
3 |
Default transitive-caller depth for drill_blast. |
rippleDepth |
2 |
Default reverse-edge depth for drill_diff dependents. |
searchEngine |
auto |
auto 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>/tgrep |
Where out-of-tree tgrep indexes live (one directory per root). |
cacheDir |
~/.cache/dsh-drill |
Cache root for derived data. Never /tmp. |
cacheTtlDays |
7 |
Idle time before a cache entry is removed; 0 disables expiry. |
embedEnabled |
true |
Use the merged c2g store as the second resolution layer. |
embedStore |
~/Embed/c2g/graph_index.sqlite |
Path to the merged c2g graph store. |
embedRepoMap |
'' |
Override shard labels, e.g. nd_src=nodedb/src,nd_sql=nodedb-sql. |
autoIndex |
false |
When 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. |
rgBin |
rg |
ripgrep binary name or absolute path. |
tgrepBin |
tgrep |
tgrep binary name or absolute path; ~/.local/bin and ~/.cargo/bin are searched after PATH. |
searchMaxHits |
200 |
Cap on returned hits; the result reports when it truncated. |
.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.
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:
| Layer | Used for | Evidence 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 indexed | c2g <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 worktree | c2g-embed <store> (built <ts>) |
tgrep (trigram index under <cacheDir>/tgrep) |
fast substring/regex over an indexed root | tgrep <bin> (text-level) |
| rg (ripgrep) | everything else, and any root without a tgrep index | rg <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.
~/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:
| Shard | Worktree path | |
|---|---|---|
nd_src |
nodedb/src |
verified |
nd_tests |
nodedb/tests |
verified |
nd_sql |
nodedb-sql |
verified |
nd_cluster |
nodedb-cluster |
verified |
nd_types |
nodedb-types |
verified |
nd_vector |
nodedb-vector |
verified |
nd_rest |
already worktree-relative | verified |
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
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 (keepingcrate::module::fnas asymbol_hint), a compiler-->diagnostic, a PythonFile "x", line N, and barefile.ext:linementions in prose. Frames dedupe byfile:lineand cap at 40. - Marks toolchain and dependency frames (
/rustc/,~/.cargo/registry/,node_modules,site-packages) asexternalinstead 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
locateevidence, which is what closes the localize gate.
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.
That path is where the fix took four releases, and the sequence is worth keeping: v0.7.0 shipped the bug (a negative result closed the gate it was supposed to feed), v0.7.1 fixed drill_locate/drill_blast and added a regression test, v0.7.3 found that drill_search still wrote text: "no hits for <pattern>" on a zero-hit search, and the v0.8.0 end-to-end review found the same shape in two more places — drill_error on a backtrace with no repository frame, and the merged-store fallback when every hit lives in another worktree. Each test is two-sided where it can be: a miss stays open and a hit closes the gate, so "never closes" cannot pass either.
Each record is one synchronous O_APPEND write of a newline-terminated line; there is no lock file, so one writer per task is the rule. On read, an unterminated final line (the shape a crash mid-append leaves) is skipped, while a complete line that fails to parse refuses evaluation with its line number.
drill_pr renders the body from evidence rather than from prose: the verification table is built from test and hygiene records (command, exit code, log name, commit), the changed-file list comes from blast records, and the Review 2 line comes from the review record. A body without a red proof says so in a blockquote instead of implying one.
It is then scored by the existing PR-craft core (core/pr_craft.py lint-desc, shared with the Kilo and Hermes plugins — one logic core, no drift), and pr evidence is recorded only when the lint reports no blockers. When the lint could not run at all, the record says so: recorded without PR-craft lint (…), so an unscored body never looks scored. A blocked body is still written to disk so the author can fix it, while the pr gate stays open.
Everything the plugin can rebuild lives under one root — ~/.cache/dsh-drill — and never under /tmp, which the host may wipe between sessions:
~/.cache/dsh-drill/
c2g-discovery.json # repository path -> the c2g database that answered, with its snapshot id
tgrep/<root-slug>/ # out-of-tree trigram indexes
One week idle time to live. Three rules keep it honest:
- The active scope snapshot is picked deterministically. Upstream keys
active_snapshotsby(resolver_tier, completeness)withcompleteness IN (0,1), so a cache can hold a partial graph and a complete one at the same time. Every query reads… WHERE resolver_tier='scope' ORDER BY completeness DESC LIMIT 1: the complete snapshot wins, a partial one is used only when it is all the cache has, and — because all queries share that fragment — symbols and edges never come from two different snapshots. When only a partial snapshot answered, the record sayspartial scope snapshot, callers may be under-reportedinstead of presenting an incomplete graph as the graph. - The c2g cache schema version is re-read on every hit, not just when the entry was written: one
PRAGMA user_versionquery (~2 ms) per answer. Our stages read specific tables (graph_symbols,graph_edges,active_snapshots), so if upstream bumps the cache schema, every c2g-backed record carriesc2g cache schema v4, expected v3 — upstream changed the cache format; verify these queries before trusting the resultinstead of quietly reporting a graph it no longer understands. Reading it on the hit is what keeps the drift from hiding behind the seven-day TTL. - The TTL measures idleness, not age: a lookup touches the entry it used, so an index for a worktree still being drilled survives and one abandoned for a week is removed.
pruneruns wheneverdrill_indexbuilds, anddrill_cache pruneruns it on demand.cacheTtlDays: 0disables expiry. - A cached answer is re-validated on read: the database file must still exist, and the entry must carry a snapshot id. Coverage means can answer — a c2g cache without an active scope snapshot (for example one rooted at a home directory, or a
/tmpfixture) is not a candidate, because every query resolves through that snapshot.drill_starttherefore reports the engine that will actually answer.
drill_start reports which engine will answer, so the gap is visible before the first search:
stage 1–2: no c2g cache and no tgrep index — run drill_index to build one, otherwise searches scan with rg
stage 1–2: no c2g cache, tgrep index ready (text-level)
stage 1–2: c2g cache covers this repo
The fallback never pretends to be a graph: drill_locate searches for definition-shaped lines and says so, drill_blast reports occurrences, explicitly not resolved call sites, and nothing in the fallback path can produce a transitive impact list. A caller/callee claim still needs the graph or a read of the code.
drill_diff— the branch diff becomes blast evidence:--diff-filter=ACMRchanged files, deletions reported separately, dependents walked backwards over c2gCall/Read/TypeRefedges to a bounded depth, plus a proposed manual-test checklist (one happy-path and one refusal line per changed file, one line per dependent). The checklist is an input to stage 3 — theedgegate still requires your own invariants, so a diff cannot close it by itself.drill_reviewis role-driven — persona, tool policy and budget come fromroles/drill-auditor.mdresolved project → user → bundled, with unknown frontmatter keys reported. A role naming a tool the agent cannot see degrades to the visible subset instead of failing the delegation.FALLBACK_PERSONA/read-only defaults still apply when no role file exists.- Evidence is bound to a commit —
drill_runrecordshead+branch;drill_reviewrecordshead. Thereviewgate reopens when HEAD moved after the review, and the report names the commit evidence belongs to (or lists the commits it spans). A review that names no commit is as stale as one that names the wrong one — v0.8.0 closed the hole where a head-less record skipped the comparison entirely. lib/git.js— read-only git queries (rev-parse HEAD, branch,diff --name-only) that answer null/empty outside a repository instead of throwing.drill_search+lib/search.js— tgrep/rg text search as the honest fallback for stages 1–2, with engine auto-selection and text-level labelling.drill_error+drill_pr— the drill now starts from the failure signal (frames resolved to symbols, toolchain frames marked external) and ends with a PR body rendered from the ledger and scored by the shared PR-craft core, recorded only when the lint is clean.- merged c2g store as the second resolution layer —
~/Embed/c2g/graph_index.sqliteanswers for every worktree of a repository through a verified shard-path map, so a missing per-worktree cache no longer costs the drill its resolved call graph (it previously fell straight to text search). The store's build time is carried into every record it produces. drill_cache+ one expiring cache root — discovery results and indexes share~/.cache/dsh-drill, the TTL is idle-based with a week's default, stale entries are dropped on read (a database that vanished, or an entry recorded before coverage required a snapshot), anddrill_cache status|prune|clearmakes the whole thing inspectable. c2g discovery also stops re-probing every project directory with a sqlite3 process on each call.drill_index+ out-of-tree indexes —--index-pathsupport keeps the trigram index in~/.cache/tgrep-index/, so coverage is added without dirtying a worktree;drill_startnames the engine that will answer; c2g discovery now picks the most specific matching cache root (with an active snapshot preferred), so a stale cache indexed at a parent directory cannot shadow the real one.
npm test # node --test test/*.test.js — 107 tests- unit: task-id safety, entry validation, log hashing, gate logic (including commit binding), report rendering, runner exit codes/timeouts
- git/role: real repositories for
headSha/branchName/diffFiles(including the non-repo path); frontmatter parsing, project→user→bundled precedence, tool-filter expansion, budget reading - errors: panic headers, numbered backtraces with symbol hints, compiler diagnostics, tracebacks and bare mentions; external marking; dedupe and capping; message extraction
- pr: body rendering from the ledger (including the missing-red warning), blocker surfacing from a core, a crashing core, and a run against the real
pr_craft.py - embed store: manifest reading, shard↔worktree path mapping both ways (round trip, overrides, unconfirmed paths), definition/caller/callee/impact/dependent queries over
linkswith a relation filter, and a falseisUsablefor a non-database - cache: TTL by idleness with touch-extends-life, prune protecting the keep list, size accounting, discovery cache hit/miss/expiry/forget, an entry without a snapshot never being served, and index pruning that keeps roots in use
- search: binary resolution, engine auto-selection against an indexed vs unindexed root, out-of-tree index build/read-back/root matching (including that no
.tgrepappears inside the repo), ripgrep/tgrep JSON parsing, definition-shaped patterns, miss-vs-failure exit codes, unavailable-engine reporting - c2g: a synthetic cache exercises the SQL (JSON-encoded
kind/role, line from the symbol blob, duplicate names across crates, read-only refusal) - integration: loads the real
@deepseek-ai/dsh-toolsruntime, compiles every tool schema against the real DSL, drives a whole drill (start → locate/blast/diff → red → green → hygiene → review → report), asserts the gates open in order, asserts the reviewer request carries the role persona/read-only filter/budget/commit, and asserts stages 1–2 fall back to a labelled text search on a repo with no code graph
Loaded clean on DSH 0.1.6-alpha.2 in a scratch profile (drill, port 3999): the bundle row activates with no import error and no "entry did not activate" warning.
Peer ranges carry explicit prerelease branches (>=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0), because a plain >=0.1.0-rc.2 silently excludes every prerelease host — the trap documented in the awesome-list's own contributing guide.
Known host constraints on 0.1.6-alpha.2:
- No custom session events.
Session.appendhas noignorableenvelope there and the read path refuses unknown types, so this plugin keeps state in files and injects durable context as a plugin-sourced user message. Thekind: 'plugin'message source exists on 0.1.6 and was removed in 0.1.7-alpha.1; on a 0.1.7+ host, declare aMessageSourceMapmember instead. - Output schemas use the value-schema DSL:
required: truebelongs on each property, never as a top-levelrequired: [...]array.