按门禁流水线修复缺陷并产出可评审的 PR
29.7/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/9/29 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add EnRaiha/dsh-drill-kit该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:待复验
是什么
dsh 原生插件 · dev
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 36 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件
查看扫描证据(共 72 处,展示前 62 条)
- 读取本地文件index.js:238
return readdirSync(dir) - 读取本地文件index.js:247
if (!existsSync(path)) return null - 读取本地文件index.js:249
return JSON.parse(readFileSync(path, 'utf8')) - 写入本地文件index.js:317
mkdirSync(paths.artifacts, { recursive: true }) - 写入本地文件index.js:328
writeFileSync(join(stateRoot, 'active.json'), `${JSON.stringify({ task: args.task, repo: args.repo ?? cwd, base: args.base ?? 'origin/main', issue: ar - 写入本地文件index.js:1042
mkdirSync(paths.dir, { recursive: true }) - 写入本地文件index.js:1043
writeFileSync(out, body, 'utf8') - 读取本地文件index.js:1050
if (config.prLint && explicitCore !== undefined && !existsSync(explicitCore)) { - 写入本地文件index.js:1506
mkdirSync(paths.artifacts, { recursive: true }) - 写入本地文件index.js:1507
writeFileSync(artifact, `# Review 2 — ${task}\n\nverdict: ${verdict}\nrole: ${roleId} (${roleSource}) · ${role?.path ?? 'builtin persona'}\nprovider: - 读取本地文件lib/ledger.js:51
if (!existsSync(path) || !statSync(path).isFile()) return undefined - 读取本地文件lib/ledger.js:51
if (!existsSync(path) || !statSync(path).isFile()) return undefined - 读取本地文件lib/ledger.js:52
return `sha256:${createHash('sha256').update(readFileSync(path)).digest('hex')}` - 写入本地文件lib/ledger.js:145
mkdirSync(paths.dir, { recursive: true }) - 写入本地文件lib/ledger.js:146
appendFileSync(paths.ledger, `${JSON.stringify(normalized)}\n`, 'utf8') - 读取本地文件lib/ledger.js:156
if (!existsSync(paths.ledger)) return [] - 读取本地文件lib/ledger.js:157
const raw = readFileSync(paths.ledger, 'utf8') - 写入本地文件lib/ledger.js:184
mkdirSync(paths.dir, { recursive: true }) - 写入本地文件lib/ledger.js:185
writeFileSync(paths.report, markdown, 'utf8') - 写入本地文件lib/runner.js:28
mkdirSync(dirname(logPath), { recursive: true }) - 写入本地文件lib/runner.js:29
const stream = createWriteStream(logPath, { flags: 'w' }) - 执行外部命令来自配置/变量lib/runner.js:33
const child = spawn(shell, ['-lc', command], { cwd, detached: true, stdio: ['ignore', 'pipe', 'pipe'] }) - 读取本地文件lib/c2g.js:38
else if (value?.db === undefined || !existsSync(value.db)) delete entries[key] - 读取本地文件lib/c2g.js:103
if (cached?.db !== undefined && existsSync(cached.db)) { - 读取本地文件lib/c2g.js:126
if (!existsSync(cacheDir)) return null - 读取本地文件lib/c2g.js:128
for (const entry of readdirSync(cacheDir)) { - 读取本地文件lib/c2g.js:130
if (!existsSync(db)) continue - 执行外部命令来自配置/变量lib/c2g.js:196
const out = execFileSync(sqliteBin, ['-readonly', '-json', db, sql], { encoding: 'utf8', timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024 }) - 执行外部命令固定命令lib/git.js:20
return execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8', timeout: 30_000 }).trim() - 读取本地文件lib/role.js:120
if (!existsSync(candidate.path)) continue - 读取本地文件lib/role.js:121
const parsed = parseRoleFile(readFileSync(candidate.path, 'utf8')) - 读取本地文件lib/search.js:63
if (!existsSync(metaPath)) return null - 读取本地文件lib/search.js:65
const raw = JSON.parse(readFileSync(metaPath, 'utf8')) - 读取本地文件lib/search.js:115
return existsSync(baseDir) ? sizeOf(baseDir) : 0 - 读取本地文件lib/search.js:124
return existsSync(LEGACY_INDEX_DIR) ? LEGACY_INDEX_DIR : null - 写入本地文件lib/search.js:141
mkdirSync(dir, { recursive: true }) - 执行外部命令来自配置/变量lib/search.js:146
const stdout = execFileSync(bin, args, { encoding: 'utf8', timeout: options.timeoutMs ?? 600_000, maxBuffer: 8 * 1024 * 1024 }) - 读取本地文件lib/search.js:161
if (name.includes('/')) return existsSync(name) && statSync(name).isFile() ? name : null - 读取本地文件lib/search.js:161
if (name.includes('/')) return existsSync(name) && statSync(name).isFile() ? name : null - 执行外部命令来自配置/变量lib/search.js:263
raw = execFileSync(resolved.bin, args, { encoding: 'utf8', timeout: options.timeoutMs ?? 30_000, maxBuffer: 32 * 1024 * 1024 }) - 写入本地文件lib/cache.js:33
mkdirSync(dir, { recursive: true }) - 读取本地文件lib/cache.js:45
if (!existsSync(path)) return null - 读取本地文件lib/cache.js:47
const stat = statSync(path) - 读取本地文件lib/cache.js:49
return JSON.parse(readFileSync(path, 'utf8')) - 写入本地文件lib/cache.js:65
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, 'utf8') - 写入本地文件lib/cache.js:66
renameSync(tmp, path) - 写入本地文件lib/cache.js:79
utimesSync(path, now, now) - 读取本地文件lib/cache.js:96
if (!existsSync(dir)) return { removed: [], kept: [], freedBytes: 0 } - 读取本地文件lib/cache.js:101
for (const entry of readdirSync(dir)) { - 读取本地文件lib/cache.js:109
stat = statSync(path) - 删除本地文件递归lib/cache.js:116
rmSync(path, { recursive: true, force: true }) - 删除本地文件递归lib/cache.js:155
rmSync(path, { recursive: true, force: true }) - 读取本地文件lib/pr.js:28
?? (existsSync(BUNDLED_PR_CORE) ? BUNDLED_PR_CORE : `${process.env.HOME ?? ''}/scripts/pr_craft.py`) - 读取本地文件lib/pr.js:117
if (core === '' || !existsSync(core)) { - 执行外部命令来自配置/变量lib/pr.js:121
const stdout = execFileSync(python, [core, 'lint-desc', '--stdin'], { - 读取本地文件lib/embed.js:46
if (!existsSync(path) || !statSync(path).isFile()) return null - 读取本地文件lib/embed.js:46
if (!existsSync(path) || !statSync(path).isFile()) return null - 读取本地文件lib/embed.js:50
if (existsSync(manifestPath)) manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) - 读取本地文件lib/embed.js:50
if (existsSync(manifestPath)) manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) - 执行外部命令来自配置/变量lib/embed.js:72
const out = execFileSync(sqliteBin, ['-readonly', '-json', path, sql], { encoding: 'utf8', timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024 }) - 读取本地文件lib/embed.js:88
if (repo === REST_SHARD) return { path: storedFile, exists: existsSync(join(worktree, storedFile)) } - 读取本地文件lib/embed.js:90
if (prefix === undefined) return { path: storedFile, exists: existsSync(join(worktree, storedFile)) }
扫描于 2026/10/31(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/dsh-tools@deepseek-ai/dsh-llm@deepseek-ai/dsh-subagent给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 按门禁流水线修复缺陷
- 产出可评审PR
- 带关卡与证据
DeepSeek Harness 的演练:带关卡、证据和已审查 PR 的 bug 修复流水线——插件、共享 c2g/PR 内核、Kilo+Hermes 前端、文档
README
由 DeepSeek 最新模型翻译生成演练工具包
一个仓库承载整个演练:强制执行它的 DSH 插件、它借用的两个共享 Python 核心、这些核心的 Kilo/Hermes 前端,以及这项工作产出的每一份文档。
dsh = DeepSeek Harness。演练是一次以带门禁的流水线方式运行的 bug 修复——从失败信号定位,绘制影响范围,枚举边界情况,证明红(在基线上失败)然后绿(带修复通过),通过卫生检查,通过一次全新上下文的只读 Review 2,然后产出 PR——每一项主张都由台账中的工件支撑。“完成”不是一句话;它是一次门禁评估。
布局
| 路径 | 内容 |
| index.js、lib/、test/、skills/、roles/、cordis.patch.yml、package.json | DSH 插件——16 个工具、107 个测试、门禁实现和台账。这是根包,因此仓库本身即可作为插件安装。
展开完整文档(README 剩余 1 节)
演练工具包
一个仓库承载整个演练:强制执行它的 DSH 插件、它借用的两个共享 Python 核心、这些核心的 Kilo/Hermes 前端,以及这项工作产出的每一份文档。
dsh = DeepSeek Harness。演练是一次以带门禁的流水线方式运行的 bug 修复——从失败信号定位,绘制影响范围,枚举边界情况,证明红(在基线上失败)然后绿(带修复通过),通过卫生检查,通过一次全新上下文的只读 Review 2,然后产出 PR——每一项主张都由台账中的工件支撑。“完成”不是一句话;它是一次门禁评估。
布局
| 路径 | 内容 |
| index.js、lib/、test/、skills/、roles/、cordis.patch.yml、package.json | DSH 插件——16 个工具、107 个测试、门禁实现和台账。这是根包,因此仓库本身即可作为插件安装。 |
| core/c2g_tools.py | c2g 核心(仅标准库):frame、error 以及查询工具——缓存 → 合并存储 → 二进制 |
| core/pr_craft.py | PR-craft 核心(仅标准库):lint-desc、lint-comment、lint-diff、plan、checklist |
| frontends/kilo/、frontends/hermes/ | 轻量前端,将这两个核心作为插件暴露给那些运行时 |
| docs/ | 规范、评审,以及为评审者生成的源码包 |
| tools/build-source-bundle.mjs | 从 git 修订版重新生成 docs/DRILL-PLUGIN-SOURCE-BUNDLE-.md |
安装插件
脚本会完成安装,可以从检出目录或直接从 GitHub 管道安装——无需 npm registry,无需 API token:
./install.sh --profile web # 官方 dsh plugin add(需要 pnpm 在 PATH 中) ./install.sh --profile web --link # 符号链接安装:无需 pnpm,无需网络 ./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 # 打印每个操作,不写入任何内容
curl -fsSL https://raw.githubusercontent.com/EnRaiha/dsh-drill-kit/master/install.sh \
| bash -s -- --profile web --link
它会自行找到 DSH 检出目录(PATH 上的 dsh、$HOME/projects/deepseek-harness,或本仓库旁边的检出目录),当缺少 pnpm 或网络时从插件管理器回退到链接安装,在编辑某个 profile 的 package.json 之前将其备份为 .bak-drill,保留任何将被覆盖的 skill 或 role 文件的副本,除非你传入 --force,否则拒绝替换正在使用的前端目录,并且从不重启任何东西。--dry-run 打印每个操作而不写入。
标志:--profile (默认 web)、--dsh 、--link、--manager、--skills、--frontends、--verify、--uninstall、--force、--dry-run。
dsh plugin --profile add dsh-drill # 从 npm 安装
dsh plugin --profile add dsh-drill@0.8.4 # 固定版本sh
dsh plugin --profile add /path/to/dsh-drill-kit # 本地检出
dsh plugin --profile add github:EnRaiha/dsh-drill-kit#v0.8.6
该插件会导入 DSH 运行时提供的主机模块(@deepseek-ai/dsh-tools、dsh-llm、dsh-subagent)。这三者都被声明为 可选 的 peer 依赖:主机通过 profile 自身的解析机制提供它们,缺少主机时仍会在导入阶段明确报错,而全新安装则保持安静,不会为用户本就不应安装的包打印 missing peer。@deepseek-ai/schemastery 则不同——它是运行时校验器,而非主机服务——因此它作为常规的 dependencies 条目发布。
或者手动安装: sh 从检出目录
node /apps/cli/lib/bin.js plugin --profile add /path/to/dsh-drill-kit
或者仓库公开后直接从仓库安装
dsh plugin --profile add github:EnRaiha/dsh-drill-kit
插件行声明在 cordis.patch.yml(package.json 中的 dsh.bundle.patch)中,DSH profile 管理器和 awesome-dsh-plugin 目录都会读取它。添加后请重启运行时:主机侧的插件行在启动时组合。
运行测试 sh
node --test test/.test.js # 107 个测试,0 个失败
node tools/build-source-bundle.mjs # 刷新 docs/DRILL-PLUGIN-SOURCE-BUNDLE-.md
test/integration.test.js 会加载真实的 @deepseek-ai/dsh-tools 运行时,针对真实 DSL 编译每个工具 schema,并通过真实的 execute() 路径驱动整个 drill。它需要从该检出目录能够访问到主机包;当无法访问时,它会 跳过自身 而不是失败——因此独立使用者看到的测试数量会更少,但绝不会出现误报的红色。要接通它们: sh
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/README.md 为它们建立了索引。简而言之:DRILL-BUG-AND-PLUGIN-FULL-DOC 是权威规范(它合并了两次评审和风险登记册),而 DRILL-PLUGIN-SOURCE-BUNDLE 是生成的快照——包含每个被跟踪的文件、逐文件哈希以及一次记录的测试运行——因此评审者无需仓库访问权限即可审计该插件。
致谢与上游
该 drill 建立在他人的工作之上,c2g 层属于他们:
| 项目 | 作者 | 本套件使用的内容 |
| NodeDB-Lab/code2graph | farhan-syah(NodeDB 创始人),CLI 修复由 EnRaiha 完成 | c2g 库 + CLI,以及 drill_locate / drill_blast 查询的按项目 SQLite 缓存表。刻意保持存储中立——每个使用者自带存储。 |
| NodeDB-Lab/nodedb | NodeDB-Lab 组织 | 演练所针对并实际运行验证的仓库;其分片即合并存储的 nd_ 仓库键 |
| awesome-dsh-plugin | 目录维护者 | 本仓库遵循的插件约定(dsh.bundle.patch、dsh- 命名)以及插件可被收录的目录 |
本仓库自身的工作是:门禁实现与账本(index.js、lib/)、演练技能与审计员角色、lib/embed.js 中的合并存储/嵌入回退,以及两个 Python 核心面向演练的工具(c2g_error、c2g_frame)。
状态
dsh-drill v0.8.6 · 16 个工具 · 107 项测试 · 可在 DSH 0.1.6-alpha.2 上加载。文档中记录了两轮评审;它们发现的每个缺陷都已修复并附有回归测试,而无法确定的问题则列为未验证,而非假定已解决。
许可证
MIT — 见 LICENSE。
以下是包文档。
dsh-drill
面向 DeepSeek Harness(dsh)的证据门禁式缺陷修复演练。
一个问题,一个工作树,一条证据链。每个演练阶段都会在按任务划分的账本中留下记录;drill_gate 会拒绝没有证据支撑的完成声明。红绿证明通过在此处运行测试命令产生,绝不通过向文件写入断言来产生。
为 NodeDB 演练而构建(red → green → fmt/clippy → preflight → commit → Review 2 → PR),并源自对十一个现有 DSH 插件的源码评审——见 ../drill-plugins/REVIEW-20260925.md。
阶段与门禁
| 阶段 | 门禁 | 所需证据 |
| 1 定位 | localize | 一条 locate 记录,指明 file:line——来自失败信号(drill_error 解析每个栈帧)或按符号(drill_locate) |
| 2 影响范围 | blast | 一条 blast 记录,包含调用方/被调用方/受影响文件(drill_blast),和/或分支差异及其依赖项(drill_diff) |
| 3 边界情况 | edge | 一条 edge 记录,说明必须失败的不变量——drill_diff 提出检查清单,由你提供不变量 |
| 4 外科手术式补丁 | red | 最近一次 arm=base 的 test 运行失败,并捕获日志——之后一次通过的 base 运行会重新打开该门禁 |
| | green | 最近一次 arm=fix 的 test 运行通过,并捕获日志——在最后一次编辑之前记录的通过,无法在之后一次失败的运行后继续成立 |
| | hygiene | 最近一次 hygiene 运行(fmt/clippy/preflight)退出码为 0;之后一次不干净的运行会重新打开该门禁 |
| 5 评审 | review | 一条 review 记录,verdict=PASS、blockers=0,且与绿色证明为同一提交;之后的 FAIL 或 HEAD 移动会重新打开该门禁 |
| 6 PR | pr | 一条 pr 记录,指向为绿色证明的提交所渲染的 PR 正文文件;drill_pr 仅在 lint 运行无阻塞项时才记录一条,且新提交会重新打开 review 和 pr |
red、green、hygiene 和 review 是 drill_gate 报告 ready 之前的必需项。其余是说明工作停在哪里的建议性门禁。
工具
| 工具 | 作用 |
| drill_start | 开启一个任务:ledger + active.json + 门禁报告 |
| drill_error | 从信号开始的第 1 阶段:将 panic、backtrace、编译器诊断或 traceback 解析为 file:line 帧,并将每一帧解析到其符号(缓存 → 合并存储)。工具链和依赖帧——包括相对的 library/std/… panic 头——被标记为 external,而没有仓库帧的信号会记录一条 note,而不是 locate 证据。 |
| drill_locate | 在本地 c2g 缓存上执行第 1 阶段:符号名,或来自栈帧的 file+line → 精确定义。记录 locate。 |
| drill_blast | 在同一缓存上执行第 2 阶段:调用点、有界深度的传递调用者、被调用者。记录 blast。 |
| drill_diff | 从 git 开始的第 2b 阶段:相对于 base ref 的已更改文件、已删除文件、依赖它们的文件(c2g 反向边),以及一份建议的手动测试清单。记录 blast——如果无法比较 base ref,则回退到工作区 diff,记录回退命令和一条 note,而不是它从未使用过的 base。 |
| drill_search | 使用 tgrep(三元组索引)或 rg 进行文本搜索:用于图无法回答的问题——配置键、错误字符串、SQL 片段、文档声明。可选地将命中记录为 locate/blast/edge 证据,始终标记为文本级别。 |
| drill_index | 为 drill 仓库构建或刷新树外 tgrep 索引,使第 1–2 阶段停止扫描:索引落在缓存目录中,绝不在工作区内,且 git status 保持干净。顺带清理空闲索引。 |
| drill_cache | 对派生缓存执行 status / prune / clear:大小、条目数、空闲 TTL,以及清理释放了什么。 |
| drill_pr | 第 7 阶段:从 ledger 渲染 PR 正文(原因、已更改文件、带退出码和提交的 red/green/preflight 行、Review 2 裁决、门禁表),写入它,用 PR-craft 核心为其评分,并且仅当 lint 运行时没有阻塞项才记录 pr 证据(崩溃的 lint 不是干净的正文)。 |
| drill_run | 运行命令,将输出流式写入 .drill//logs/,对日志进行哈希,记录退出码以及它运行所在的提交。这是产生测试证明的唯一方式:test/hygiene 记录的日志若位于任务的 logs/ 目录之外,会被拒绝。被信号杀死的子进程记录为 128 + signo(SIGSEGV → 139),并在 note 中记录信号——“它在 base 上崩溃”是红色证明,而不是缺失记录。 |
| drill_record | 记录非命令证据:locate/blast/edge/review/pr/note。拒绝未武装的 test、没有日志的 test/hygiene 记录,以及位于任务自身 logs/ 目录之外的日志;接受显式的 head,以便手动记录的 review/pr 可以绑定到它所审查的提交。 |
| drill_gate | 哪些门禁通过、哪些开放,以及下一步是什么 |
| drill_status | 活动任务、记录数量、最近记录、门禁状态 |
| drill_report | 渲染报告(门禁表 + 带退出码和日志哈希的测试证据)并写入 .drill//report.md |
| drill_review | 启动 Review 2:由角色文件驱动(默认 drill-auditor——先项目 .dsh/roles,再 ~/.dsh/roles,最后内置)的全新上下文、只读审查者子代理,带有硬性工具调用预算;记录绑定到被审查提交的裁决 |
| drill_setup | 将内置技能和审查者角色安装到 ~/.dsh/skills/drill/ 和 ~/.dsh/roles/ |
该插件还注册了一个 agent/turn-stopping 监听器:当活动任务存在开放的必需门禁时,它会注入一行提醒并列出这些门禁。它是建议性的——它永远无法阻止回合结束。
安装 sh 从此检出目录(开发):链接到某个 profile,然后启动
dsh plugin --profile add ~/projects/dsh-drill-kit
node apps/cli/lib/cli.js --dump-config | grep -A2 'id: drill'
该插件行在 cordis.patch.yml 中作为单个条目提供(id: drill、name: dsh-drill);安装该 bundle 会自动将 dsh-drill 追加到 profile 的 dsh.profile.bundles。
开发模式解析。 link: 安装会从链接目标解析 @deepseek-ai/ 导入,这会脱离 $DSH_HOME/profiles/node_modules(宿主机的模块回退路径)——因此链接的检出目录需要能从该目录访问到宿主机包: sh
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
正常的 registry/tarball 安装不需要这样做:包会落在 profile 树内,宿主机回退路径在那里是可访问的。
配置
每个键都有 schema 默认值;通过在 profile 补丁中重新声明该行的整个配置来覆盖。
| 键 | 默认值 | 含义 |
| stateRoot | .drill | 每任务状态目录,相对于会话工作区。 |
| reminder | true | 注册回合停止时的门禁提醒。 |
| provider | spawn | drill_review 使用的子代理提供者(spawn = 全新上下文;fork 继承父前缀)。当角色文件同时存在 provider/model 时,它们优先。 |
| model | '' | 可选的审查者模型覆盖。 |
| defaultRole | drill-auditor | drill_review 用于审查的角色 id。 |
| maxReviewToolCalls | 40 | 当角色未设置时的回退预算;0 禁用上限。角色自身的 maxToolCalls 始终优先,包括 0——v0.8.1 修复了角色声明 0 却被此回退值静默覆盖的情况。超出预算会中止子进程并记录 FAIL。 |
| runTimeoutMs | 900000 | drill_run 的默认超时时间。 |
| errorMaxResolve | 12 | drill_error 针对图进行解析的帧数上限。 |
| requireLog | true | 拒绝日志缺失、不可读或位于任务自身 logs/ 目录之外的 test/hygiene 记录。设为 false 可接受来自其他位置的日志路径——此时账本会记录哈希值,而不检查其来源。 |
| prLint | true | 在记录渲染后的 PR 正文之前,用 PR-craft 核心对其评分。 |
| prCore | ''(自动) | PR-craft 核心的显式路径。留空时,插件会依次解析 PR_CRAFT_CORE、随包附带的副本(core/pr_craft.py),然后是 ~/scripts/pr_craft.py——因此全新安装无需任何配置即可进行 lint。若非空值对应的文件不存在,drill_pr 会拒绝,而不是未经 lint 就记录。 |
| pythonBin | python3 | 用于调用 PR-craft 核心的解释器。 |
| c2gEnabled | true | 启用由 code2graph 支持的 stage 1–2 工具。 |
| c2gCacheDir | ~/.cache/code2graph/projects | c2g 缓存根目录。 |
| sqliteBin | sqlite3 | 用于只读查询的 sqlite3 可执行文件。 |
| c2gDepth | 3 | drill_blast 的默认传递调用者深度。 |
| rippleDepth | 2 | drill_diff 依赖项的默认反向边深度。 |
| searchEngine | auto | auto 在索引覆盖根目录时选择 tgrep(先查树外缓存,再查 /.tgrep),否则选择 rg;可用 rg 或 tgrep 强制指定。 |
| tgrepIndexDir | /tgrep | 树外 tgrep 索引的存放位置(每个根目录一个目录)。 |
| cacheDir | ~/.cache/dsh-drill | 派生数据的缓存根目录。绝不能是 /tmp。 |
| cacheTtlDays | 7 | 缓存条目被移除前的空闲时间;0 表示禁用过期。 |
| embedEnabled | true | 使用合并后的 c2g 存储作为第二解析层。 |
| embedStore | ~/Embed/c2g/graph_index.sqlite | 合并后的 c2g 图存储的路径。 |
| embedRepoMap | '' | 覆盖分片标签,例如 nd_src=nodedb/src,nd_sql=nodedb-sql。 |
| autoIndex | false | 为 true 时,首次搜索会构建缺失的索引,而不是回退到 rg。默认关闭,因此搜索绝不会意外写入。 |
| rgBin | rg | ripgrep 二进制文件名或绝对路径。 |
| tgrepBin | tgrep | tgrep 二进制文件名或绝对路径;在 PATH 之后还会搜索 ~/.local/bin 和 ~/.cargo/bin。 |
| searchMaxHits | 200 | 返回命中数的上限;结果在截断时会报告。 |
证据布局
.drill/
active.json # 回合停止提醒所谈论的任务 / ledger.jsonl # 仅追加,每条记录一个 JSON 对象 logs/--.log artifacts/review-.md report.md 每条 test/hygiene 记录都携带 exit、其日志的 sha256,以及该命令运行所在的 head 提交。日志缺失的记录在写入时会被拒绝,因此账本无法累积无凭据的声明;若一次审查所指定的提交与绿色证明不同,则无法关闭审查门禁。
搜索引擎(第 1–2 阶段回退)
哪个“c2g”? 有三个不同的东西共用这个名字:上游提取器是 farhan-syah 的 NodeDB-Lab/code2graph(即 c2g 二进制文件,仅作为最后手段调用);按项目缓存 ~/.cache/code2graph/projects//cache.sqlite3 是下面的第 1 层;而 ~/Embed/c2g 是本机的合并存储,由 ~/scripts/merge_c2g_shards.py 构建,是第 2 层。
当图能回答时,它是权威的,但它并不总能回答:某个仓库可能没有 c2g 缓存,某个符号可能未被索引,或者问题可能根本不涉及符号。共三层,按调用选择,并且始终在证据中注明:
| 层 | 用途 | 证据标签 |
| c2g CLI 缓存(对 ~/.cache/code2graph/projects//cache.sqlite3 执行只读 SQL) | 针对被索引的确切工作树,解析定义、调用点、被调用者、传递影响、反向边依赖者 | c2g |
| 合并的 c2g 存储(~/Embed/c2g/graph_index.sqlite) | 针对该仓库的任意工作树的相同问题——一个合并图,分片路径映射到工作树 | c2g-embed (built ) |
| tgrep(/tgrep 下的三元组索引) | 在已索引根上的快速子串/正则匹配 | tgrep (text-level) |
| rg(ripgrep) | 其他所有情况,以及任何没有 tgrep 索引的根 | rg (text-level) |
auto 仅在根实际带有索引时才优先选择 tgrep——未索引的 tgrep 运行会扫描每个文件,并向证据日志写入一条警告,这比 ripgrep 更糟。两个引擎输出相同的 --json 匹配流,因此一个解析器即可读取它们。
索引位于仓库之外。 tgrep 索引通常位于 /.tgrep,这会给演练工作树添加未跟踪文件,并出现在 git status 中——而这正是演练自身预检检查所关注的内容。drill_index(以及 autoIndex)改为传入 --index-path,写入 ~/.cache/dsh-drill/tgrep//:实测每个 NodeDB 工作树约 1.5 秒、约 60 MB,峰值内存约 92 MB,而工作树不会增加任何内容。
合并的 c2g 存储(~/Embed)
~/Embed 是本机的嵌入与合并图存储(那里的 RULES.md 负责其布局)。它的 c2g/graph_index.sqlite 与按项目 CLI 缓存是不同的产物:一个文件,包含 nodes(id, name, kind, file, repo, line) 和 links(source, target, relation),由按 crate 的分片合并而来,并在导入时重建。由于它是合并的,它能针对该仓库的每一个工作树作答——而这正是按项目缓存所留下的缺口。
其中的路径是相对于 shard 的,因此插件会映射它们,并且只声明它能在磁盘上确认的映射:
| Shard | Worktree 路径 | |
| nd_src | nodedb/src | 已验证 |
| nd_tests | nodedb/tests | 已验证 |
| nd_sql | nodedb-sql | 已验证 |
| nd_cluster | nodedb-cluster | 已验证 |
| nd_types | nodedb-types | 已验证 |
| nd_vector | nodedb-vector | 已验证 |
| nd_rest | 已经是相对于 worktree 的 | 已验证 |
如果某一行的映射路径在 worktree 中不存在,则返回 path not in this worktree,而不是静默地报告为存在。
这是快照,而不是 worktree 的 HEAD。 从中记录的证据会说明这一点,并带有来自 manifest.json 的存储构建时间(c2g-embed … (built 2026-09-24T05:24:54+0800))。优先级保持不变:每个 worktree 的 CLI 缓存最新,合并存储次之,文本搜索最后。
在 nodedb-296 上测量(没有每个 worktree 的缓存,存在合并存储):
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
从失败信号到符号
流水线的第一阶段从“堆栈跟踪、错误日志、输入负载以及抛出错误的具体函数”开始——而不是从符号名称开始。drill_error 接收原始文本并完成这项工作:
- 解析 Rust panic 头部(panicked at path:line:col: msg)、带编号的回溯(将 crate::module::fn 保留为 symbol_hint)、编译器 --> 诊断、Python File "x", line N,以及散文中裸露的 file.ext:line 提及。帧按 file:line 去重,上限为 40。
- 标记工具链和依赖帧(/rustc/、~/.cargo/registry/、node_modules、site-packages)为 external,而不是解析或丢弃它们,这样读者就能看到为什么那里没有解析出任何内容。
- 解析每个仓库帧,通过与 drill_locate 相同的层——先是每个 worktree 的 c2g 缓存,然后是合并存储——并且当回溯命名的符号与图不一致时,同时显示两个答案(→ nextval_batch [Method] (c2g-embed) (backtrace names …::allocate))。
- 记录整条链作为 locate 证据,这正是关闭 localize 门禁的依据。
未命中是注释,不是证据
找不到任何内容的查找仍会追加一条记录——用于审计追踪——但不携带 files、symbols 或 text,因此它所属的门禁保持打开。
这条规则适用于所有能够写入上述类型之一的工具:当 drill_locate、drill_blast、drill_search 被要求记录时(kind: locate|blast|edge),当每一帧都是工具链或依赖时,drill_error,以及当存储知道该符号但没有任何命中解析到此工作树中的文件时,drill_locate 的合并存储回退。它们中的每一个都会写入它运行的命令以及 … — the gate stays open,而绝不会写入门禁会将其计入的 text 字段。
这条路径正是修复花费了四个版本的地方,其顺序值得保留:v0.7.0 引入了该缺陷(一个否定结果关闭了它本应供给的门禁),v0.7.1 修复了 drill_locate/drill_blast 并添加了一个回归测试,v0.7.3 发现 drill_search 在零命中搜索时仍然写入 text: "no hits for ",而 v0.8.0 端到端审查又在另外两个地方发现了同样的形态——drill_error 在没有任何仓库帧的回溯上,以及当所有命中都位于另一个工作树中时的合并存储回退。每个测试在可能的情况下都是双向的:未命中保持开放并且命中关闭门禁,因此“永不关闭”也无法通过。
账本并发与撕裂写入
每条记录都是一次同步的 O_APPEND 写入,写入一行以换行符结尾的内容;没有锁文件,因此规则是每个任务一个写入者。读取时,未终止的最后一行(追加中途崩溃所留下的形态)会被跳过,而一行完整但解析失败的内容会以其行号拒绝评估。
PR 正文来自账本
drill_pr 从证据而非文字渲染正文:验证表由 test 和 hygiene 记录构建(命令、退出码、日志名称、提交),变更文件列表来自 blast 记录,Review 2 行来自 review 记录。没有红色证明的正文会在引用块中说明这一点,而不是暗示有。
然后它由现有的 PR 工艺核心(core/pr_craft.py lint-desc,与 Kilo 和 Hermes 插件共享——一个逻辑核心,无漂移)评分,并且 pr 证据仅在 lint 报告无阻塞项时才被记录。当 lint 完全无法运行时,记录会说明这一点:recorded without PR-craft lint (…),因此未评分的正文永远不会看起来像已评分。被阻塞的正文仍会写入磁盘,以便作者可以修复它,同时 pr 门禁保持开放。
缓存与过期
插件能够重建的所有内容都位于一个根目录下——~/.cache/dsh-drill——而绝不会位于 /tmp 下,因为主机可能会在会话之间将其清除:
~/.cache/dsh-drill/
c2g-discovery.json # repository path -> the c2g database that answered, with its snapshot id
tgrep// # out-of-tree trigram indexes
空闲一周即过期。 三条规则使其保持诚实:
- 活动作用域快照是以确定性方式选取的。 上游用 (resolver_tier, completeness) 作为 active_snapshots 的键,其中 completeness IN (0,1),因此一个缓存可以同时保存部分图和完整图。每次查询都会读取 … WHERE resolver_tier='scope' ORDER BY completeness DESC LIMIT 1:完整快照优先,只有在缓存中只有部分快照时才会使用它,并且——由于所有查询共享该片段——符号和边绝不会来自两个不同的快照。当只有部分快照能够回答时,记录会写明 partial scope snapshot, callers may be under-reported,而不是把一个不完整的图当作完整的图来呈现。
- c2g 缓存 schema 版本在每次命中时都会重新读取,而不仅仅是在条目写入时:每个答案执行一次 PRAGMA user_version 查询(约 2 ms)。我们的各个阶段会读取特定的表(graph_symbols、graph_edges、active_snapshots),因此如果上游提升了缓存 schema,每个由 c2g 支持的记录都会带有 c2g cache schema v4, expected v3 — upstream changed the cache format; verify these queries before trusting the result,而不是悄悄报告一个它已不再理解的图。在命中时读取它,正是防止这种漂移隐藏在七天 TTL 背后的原因。
- TTL 衡量的是闲置时间,而不是存在时间:一次查找会触碰它使用过的条目,因此仍在被深入分析的工作树的索引会保留下来,而被弃置一周的索引则会被移除。prune 会在 drill_index 构建时运行,drill_cache prune 会按需运行它。cacheTtlDays: 0 会禁用过期。
- 缓存的答案在读取时会重新验证:数据库文件必须仍然存在,并且条目必须带有快照 id。覆盖意味着能够回答——没有活动作用域快照的 c2g 缓存(例如以主目录为根,或 /tmp 下的测试夹具)不是候选,因为每个查询都通过该快照解析。因此,drill_start 会报告实际将回答的引擎。
drill_start 会报告哪个引擎将回答,因此这个缺口在第一次搜索之前就可见:
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
回退路径从不假装自己是图:drill_locate 搜索定义形状的行,并明确说明这一点;drill_blast 报告的是出现位置,明确不是已解析的调用点;并且回退路径中没有任何东西能够产生传递性的 impact 列表。调用者/被调用者的断言仍然需要图或阅读代码。
v0.2 变更
- drill_diff — 分支差异成为爆炸半径证据:--diff-filter=ACMR 变更文件,删除单独报告,依赖项沿 c2g Call/Read/TypeRef 边反向遍历至有界深度,外加一份建议的手动测试清单(每个变更文件一行 happy-path 和一行 refusal,每个依赖项一行)。该清单是阶段 3 的输入——edge 门禁仍要求你自己的不变量,因此仅凭 diff 无法关闭它。
- drill_review 由角色驱动 — persona、工具策略和预算来自 roles/drill-auditor.md,按项目 → 用户 → 内置的顺序解析,未知的 frontmatter 键会被报告。角色指定的工具若 agent 不可见,会降级为可见子集,而不是让委派失败。当不存在角色文件时,FALLBACK_PERSONA/只读默认值仍然适用。
- 证据绑定到提交 — drill_run 记录 head + branch;drill_review 记录 head。当 HEAD 在审查后发生移动时,review 门禁会重新打开,报告会指明证据所属的提交(或列出它跨越的提交)。一个未指明提交的审查,与指明错误提交的审查一样陈旧——v0.8.0 堵上了无 head 记录完全跳过比较的漏洞。
- 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.sqlite answers 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), and drill_cache status|prune|clear makes 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-path support keeps the trigram index in ~/.cache/tgrep-index/, so coverage is added without dirtying a worktree; drill_start names 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.
Verification
sh
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 links with a relation filter, and a false isUsable for 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 .tgrep appears 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-tools runtime, 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.
Compatibility
Peer ranges carry explicit prerelease branches (>=0.1.2-rc.1 =0.1.5-alpha.1 =0.1.6-0 =0.1.7-0 =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.append has no ignorable envelope 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. The kind: 'plugin' message source exists on 0.1.6 and was removed in 0.1.7-alpha.1; on a 0.1.7+ host, declare a MessageSourceMap member instead.
- Output schemas use the value-schema DSL: required: true belongs on each property, never as a top-level required: [...] array.预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
EnRaiha/dsh-drill-kit
按门禁流水线修复缺陷并产出可评审的 PR
相关插件 · 开发 / 工程
隐私简历生成器reactive-resume/reactive-resume
Reactive Resume 已迁至 GitHub 上的…
★ 45,125已汉化MCP
71.7
AI 架构文档生成器sopaco/deepwiki-rs
🚀 Litho 已进化为 Terrain —— 给你的 AI 智能体一张会呼吸的代码库地图。
★ 3,157已汉化DSH
70.8
长时任务执行闭环AMAP-ML/LongHorizon-Harness
面向 Computer-Use Agent 的 Loop Engineering
★ 1,764已汉化DSH
70.5
AI 编程纪律方法包GanyuanRan/Aegis
让 AI 编程 agent 变得可信:少返工、更安全、说“完成”前先给证据。
★ 1,384已汉化DSH
69.6
多代理编排运行时Enderfga/claw-orchestrator
面向编程智能体的运行时。把 Claude Code、Codex、Antigravity、Grok…
★ 586已汉化MCP
69.4
记忆进化插件csyangwen/dsh-memory-evolve
dsh-memory-evolve 使用场景指南
★ 461已汉化DSH
67.9