DeepSeek Harness 的 dsh-code-review skill 是怎么维护的
很多团队都干过同一件事:给 AI 写一份「评审本仓库 PR 时要注意什么」的提示词,第一个月很好用,第三个月它还在说着半年前的目录结构。问题不在写,在维护——没人知道该在什么时机、依据什么证据去改它。
DeepSeek Harness 这个仓库把这件事写成了流程文档。它自己用的评审 skill 叫 dsh-code-review,维护方式记在 docs/cookbook/maintaining-dsh-code-review.md(同目录有官方中文版 .zh.md),流程规格记在 .agents/notes/proposed/process/2026-07-13-human-review-skill-maintenance.md。下面沿着这两份文档和相关源码走一遍。
先把限定说在前面:该仓库 README 里有一节标题就叫 Developer preview,正文自述项目处于开发者预览阶段、迭代很快,并用大写强调「会有破坏兼容性的变更」。本文提到的命令、脚本名、默认值随时可能变,以仓库最新内容为准。
一、这份文件在哪,它怎么被读进去
dsh-code-review 的本体只有一个文件:.agents/skills/dsh-code-review/SKILL.md。我们数了一下,49 行、按空白切分约 1073 个词,里面 6 条编号的 Blocking requirements、15 条 Manual checks 项目符号,另有 19 处指向仓库内其它文件的相对链接。它不是清单,开头第一句就写明「本 skill 是指导,不是完整清单」。
它被加载的路径写在 packages/skill/skill-filesystem/README.md 的 Discovery 表里。该 provider 按 rank 顺序扫描五个根目录,rank 100 是 <projectRoot>/.dsh/skills,rank 200 是 <projectRoot>/.agents/skills——dsh-code-review 就在这一层。项目根的判定规则是「最近的、包含 .git 的祖先目录」,找不到就退回当前 cwd。发现只有一层深:只认 <root>/<name>/SKILL.md 和 <root>/<name>.md,README 明写嵌套的 **/SKILL.md 是「刻意排除」的。
frontmatter 这边,README 写明 provider 解析必需的 name 和 description,外加可选的 whenToUse、metadata、disable-model-invocation、user-invocable;名字必须是 kebab-case。回头看 dsh-code-review 的 frontmatter,只有 name 和 description 两行,两个调用开关都缺省——按 README 的说法,省略即允许对应的调用面。作为对照,同目录下的 dsh-translate-docs 显式写了 disable-model-invocation: true 与 user-invocable: true。
还有一条对维护方式有直接影响:README 的 Skill Format 一节写明 catalog 和 body 生命周期分开,每次 skill(name) 加载都会重新读取并重新解析当前文件,所以改正文不需要哈希、修订号、缓存失效或主动通知模型。维护这件事因此可以被压缩成「改一个 Markdown 文件」。
二、真正干活的工具不在这个仓库里
翻遍仓库你也找不到那套维护工具。cookbook 写得很直白:skill 由一名指定操作员通过私有的周期维护工具持续更新,工具源码、评审适配器、提供方凭据和调度器都是操作员的私有基础设施,按设计位于仓库之外。Agent Note 的「Where the mechanism lives」一节给了文档自述的理由:这个机制只服务一个 skill、只有一名操作员,把工具本身也纳入仓库评审的持续成本超过收益;如果将来交接给第二名维护者,需要另写一篇 Agent Note 来修订这个决定。
所以这个仓库里能看到的,是「工作流保证什么」,不是「怎么实现」。这也是我要提醒的第一处口径:你能抄的是规则,不是代码。
三、跟着一次运行走一遍
cookbook 写明操作员每天手动调用包装脚本,使用 2 个 UTC 日的重叠窗口;每周的手动恢复运行用 7 日窗口;漏跑更久时通过 DSH_CODE_REVIEW_SINCE=<Nd> 手动补。重叠窗口是幂等的——当前 skill 里已经有的指导会被归类为 covered,不会再次冒出来当候选。
选 PR。 唯一的准入条件是合并 commit 可从 origin/master 到达。父分支被 squash 掉、合并 commit 不可达的堆叠分支,以及超过 250 个 commit 获取上限的 PR,都会记进 skipped-pulls.json 然后跳过,而不是让整次运行失败。Agent Note 另外写明,当窗口会超过 GitHub 1000 条搜索结果上限时,搜索阶段会显式失败,避免静默漏掉已合并的 PR。
收反馈。 只收合并前带 commit 锚点的人工评审反馈,也就是行内评论和评审提交。PR 会话评论不采集,Agent Note 给的理由是:force-push 之后,GitHub 的当前状态无法证明哪个存活 commit 在该评论之前,所以拿不到抗 force-push 的反馈时基线。作者过滤也很死板:GitHub 报告的 actor type 必须是 User,并且创建时间与最后编辑时间都要严格早于 PR 合并时间——时间戳相等的编辑按合并后处理;评审提交的编辑时间取 GraphQL 的 lastEditedAt,因为 REST 表示里没有这个字段。
采纳证据。 这一段是整套流程里最值得抄的部分。文档明确拒绝把「PR 合并了」「线程被 resolve 了」「作者回了一句 fixed」当成采纳证明,也拒绝直接拿反馈基线和落地 merge 去 diff——那个差异里混着目标分支自己前进带来的无关变更。它给评审者的是两份 PR 自身的快照:设 B 为反馈基线、T 为落地 merge 的目标分支父提交、M 为落地 merge,反馈时快照是 merge-base(B, T) 到 B 的树差异,最终快照是 T 到 M 的树差异。只存在于目标分支的改动在两份快照里都不会出现,而反馈之后才加进 PR 的改动只出现在最终快照里。另外,基线不是评审者点的那个 commit(那可能是个旧 commit),而是提交者时间戳严格早于反馈的最新 PR commit。
双适配器。 两个独立配置的评审适配器先各自判断每条反馈的作者类别(human-authored / forwarded-automation / unclear)和是否被采纳(adopted / rejected / unclear),只有两边都判为「人写的且被采纳」才继续;然后对这批条目再做一次独立分类:候选、已覆盖、实现相关、不算反馈。Agent Note 写明单例也可以成为候选,不要求同类反馈复现。之后由主适配器起草完整的修订版 SKILL.md——注意是返回整份文件内容而不是补丁,由工具校验后写入唯一目标文件;两个适配器再评审同一份 diff,只要还有阻塞性问题就进入有界修订循环,必须双方批准同一版本。
四、门禁到底挡住了什么
cookbook 写明工具声明成功前会对候选跑 pnpm run doc-sync 和 pnpm run lint。这两行值得往下挖一层。
doc-sync 在 package.json 里是 tsx scripts/run-gates.ts doc-sync,在 scripts/run-gates.ts 的 docSyncLeafGates() 里展开——我们数出 28 个子门禁(含默认包含的 doc-typecheck)。但真正会读到 .agents/skills/**/*.md 的只有两个:scripts/verify-md-links.ts 的 PATTERNS 里有这条 glob,scripts/verify-mermaid.ts 的 PATTERNS 里也有。相邻的 scripts/verify-md-wrap.ts,PATTERNS 只到 .agents/notes/**/*.md,没有 skills。
verify-md-links 干的事对这份文件很实在:它不只检查相对链接的目标文件存在,还要求 #fragment 对应真实的标题 slug。SKILL.md 里就有 ../../../AGENTS.md#conventions 和 #run-relevant-checks-locally 这样的锚点,对应根 AGENTS.md 里的 ## Conventions 与 ### Run relevant checks locally 两个标题(后者是三级标题)。谁改了 AGENTS.md 的标题文字,这个候选就会在门禁上翻车。
再看两处没有覆盖它的地方,只陈述事实:一是 scripts/doc-budgets.manifest.json 里只有 9 个文件的词数上限(AGENTS.md、docs/AGENTS.md、docs/architecture.md 等),.agents/skills/ 下的任何文件都不在其中;二是 scripts/verify-skill-invocation-metadata.ts 的 skillDirectories() 只挑出含 agents/openai.yaml 的 skill 目录,而 .agents/skills/ 下 11 个 skill 里有 6 个带这个文件,dsh-code-review 不带。把这两处和 cookbook 第 1 步并排看就很清楚:cookbook 让操作员亲自去找「清单膨胀、历史叙述、由单次事件外推、与现有 skill 或权威文档重复」——这些恰好是没有门禁在管的部分。说到这里就停,我不去推断作者为什么这么划分。
lint 那行是 npm run build:lib:host && npm run lint:contracts-ready,后者是 tsx scripts/run-oxlint.ts .。也就是说即使候选只改了一个 Markdown 文件,跑的仍是整仓的 oxlint,前面还挂着一次 host 侧构建。
顺带一句 Windows 侧:上面这些都是 pnpm 脚本;但 cookbook 给操作员的示例用的是 ls / less / rm,日志路径写的是 ~/Library/Logs/dsh-code-review-maintainer/,通知描述的是 macOS 通知。官方文档没有说明这套机制在 Windows 上的等价路径与通知方式。
五、操作员拿到候选之后
每次运行的产物落在操作员机器上的 ~/dsh-code-review-outputs/,按时间戳命名,分别是 .diff、.SKILL.md、.manifest.json。manifest 记录源 master commit 与 skill blob、源反馈 ID 和 URL、已落地证据范围、适配器裁决和门禁结果;每个适配器的原始 I/O 留在私有临时目录,路径写进通知和每日日志。维护 worktree 每次运行后都恢复干净,文档自述的理由是避免操作员直接在维护副本里改。
cookbook 给出三种处理:丢弃(下次运行会依据届时的 skill 重新考虑同一份反馈)、留待成批、提升。提升走 dsh-code-review-promote <timestamp>,前置条件是仓库处在干净的 master checkout;它会刷新 master、核对当前 skill blob 与 bundle 记录的源 blob 是否一致,不一致就停下而不是用陈旧的完整文件覆盖更新过的指导,然后开一份 draft PR,正文列出源反馈 URL 或 ID、作为采纳证据的落地 commit 范围、发起这次更改的运行、门禁结果和操作员编辑。
还有两条纪律写得很硬:第一,「不要因为评审者已经批准就直接接受」,维护者约定规定最终决定权在操作员;至少要抽查一条——链接的人工评论是否真的支撑这条新增规则,链接的 PR 是否真的采纳了它。第二,不要逐字提交适配器输出,提升过程中收紧措辞、删掉离开源 PR 上下文就讲不通的例子、把新规则并进已有规则,都是预期动作。
另外,运行没产出候选是常态。工具在每日日志里记一句「无候选版本」,不发通知(文档自述是为了避免提醒疲劳)。文档明确写道:某天没有 skill 更新说明工作流运行正常,而不是停滞。
六、文档自己标出来的边界
这一节全部照读,不加判断。
那篇 Agent Note 的 Status 是 proposed,文件也确实放在 .agents/notes/proposed/process/ 下。它的验收标准列了 6 条,其中只有 2 条带 Observed on 2026-07-15 的记录:一次扫描 62 个已合并 PR、跳过 5 个(合并 commit 不可达或超过 250 commit 获取上限)、考虑 426 条人工反馈、产出 0 个候选;以及两个独立配置的适配器完成了一轮采纳与分析。最后一条验收标准——「至少一份由本工作流产出的候选 diff 经操作员检视并通过正常仓库 PR 评审合并到 master」——没有标注观察记录。
安全侧也要照实说。Agent Note 写明适配器子进程以清洗过的环境启动,cwd 设在私有运行目录而不是仓库根,反馈被包在带 128 位 nonce 的 <untrusted-feedback nonce="…"> 块里、每个 prompt 都指示模型把它当数据处理,nonce 用来防止不可信正文伪造闭合标签;生产环境的 git / gh / 门禁子进程同样使用清洗过的环境。但同一段里紧接着写明:access 和 tools 字段是对适配器作者的契约标记,不是 OS 沙箱。这套机制会在本机拉起子进程执行外部程序,边界就是文档写明的这些,不多也不少。
Risks 一节里官方还自己列了五点:用提交者时间戳推断因果存在残余的误判窗口;两个分类器都说「不是候选」但理由不同(例如 covered 与 specific)时直接归入 excluded,不再走争议轮;工具只能拒绝两个评审命令解析到逐字节相同的可执行文件,无法验证两个 wrapper 背后是不是真的不同提供方或模型,这一点是部署契约;候选写入与回滚用的是 best-effort 的 compare-and-swap,POSIX 上并非真正原子,窗口是一个 event-loop tick。最后是单维护者的关键人风险——机制跑在一台机器上,它一停,skill 维护就整体停摆。
七、如果你也想维护一份团队评审 skill
不谈优劣,只说这套文档把哪几个判断固定成了规则,值得你在自己的流程里对照:
一是采纳的定义。合并、resolve、一句「已修复」都不算,要拿两份 PR 内的快照说话,并且刻意把目标分支自己前进的改动排除在证据之外。
二是起草者和裁决者分开。主适配器只负责写出完整候选,判断作者类别、是否采纳、是否与现有内容重复的是两次独立分类,分歧只给一轮有界重评。
三是产物不直接进仓库。工具从不 commit、push、开 PR 或合并;提升要从干净 master 出发、核对 blob、开 draft PR,走正常评审。
最后回到那份 49 行的 SKILL.md:它值得一读的地方不在条目多,而在它明确说自己是指导不是清单,并且要求评审者先跑 pnpm --silent run change-scope --base <verified-base-ref> --head <verified-head-ref> 拿到变更范围报告再去读 diff。这个脚本在 scripts/change-scope.ts,报告结构里带 formatVersion、解析后的 baseSha / headSha / mergeBaseSha,以及分成 committed / staged / unstaged / untracked 四层的路径清单。skill 里同时写明:这份报告识别路径和脏层,但不替代语义评审,retarget 或 merge 之后要重新确定 base 并重跑。
本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的
架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-17,对应仓库快照 47f9438(版本 0.1.0-rc.5)。
本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目,
因此不涉及界面外观、操作手感与运行速度的任何描述。
该仓库 README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更,
文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。