dsh-review
English | 中文
代码评审不该绑死在一家身上。把改动交给一个 CLI 编码智能体,或交给一个跑 DSH 配置里模型的
子智能体——最后读一份合并好的结论。
它能做什么
- 两种评审者,都是真智能体。
codex/claude/gemini/opencode/cursor,或你
自己拼命令行的任意 CLI;或者一个跑 DSH 配置里模型的子智能体。 - 多个评审者,一份报告。 并行跑;每个评审者一节,按你勾选的顺序排列。
- 评审模式。 强度与提示词起个名字(
quick、balanced、deep……)配一次就能复用,
不必每次重写评审要求。 - 在 GUI 里配置,而且自动保存。 勾选要用的评审者,其余都收在「高级配置」里;停止输入约一秒半
后自动落盘,顶部会显示保存状态,最下面仍留一个「立即保存」按钮作手动重试。 - 评审状态一眼可见。 对话开始后,对话与输入框之间会出现一条很轻的状态:还没评审、第几轮正在跑、
已经评审过几轮、用了哪些评审者;点开是每一轮的时间、范围与结果。对话为空时不出现,当前这一轮
没有文件改动时也会自动隐藏。数据跟着会话持久化,重开会话也还在。 - 不碰密钥、不碰端点。 provider 与凭据都留在你的 DSH 配置里,插件只用已经有的。
安装
clone 到一个长期保留的位置,然后装进 profile:
git clone git@github.com:yutingzhao1991/dsh-review.git ~/code/github/dsh-review
dsh plugin --profile web add ~/code/github/dsh-review # web 换成你的 profile 名
也可以让智能体调 plugin_manager 的 install_bundle,target 传上面的绝对路径。
装完重启宿主。升级同理:git pull 之后重启。
需要 DSH 0.2.1 或更新版本。插件在 manifest 里用 engines.dsh 声明了这一点,旧宿主会直接拒绝
安装或激活,而不是等到真跑评审时才在子智能体启动那一步失败。
怎么用
review() // 用你启用的评审者,评审未提交的改动
review({ reviewers: ["codex", "deep-dive"] }) // 这次指定评审者
review({ mode: "deep" }) // 深度模式:多个评审者
review({ base: "main" }) // 工作区相对某个分支的改动
review({ commit: "abc1234" }) // 某个提交引入的改动
review({ instructions: "只看并发与错误处理" })
review({ timeoutMs: 600000 }) // 本次最多跑 10 分钟
评审要跑几分钟,所以调用默认立即把 job id 交回(不等):用 job_output 读结论、
job_kill 终止,或者就等着——跑完会自动通知。
评审者是显式启用的:一个都没启用时,不带 reviewers 的调用会直接报错,而不是
悄悄把能用的都跑一遍。timeoutMs 是单次调用的墙钟上限,与配置里的 hardTimeoutMs 取较小值;
两者都设时报告按实际生效的那个归因。
评审状态
对话与输入框之间有一条常驻的评审状态,不需要打开任何面板:
| 状态 | 显示 |
|---|---|
| 还没评审 | 评审 · 尚未评审 |
| 正在跑 | 评审 · 第 2 轮评审中 · 1 分 05 秒 + 正在跑的评审者 |
| 已结束 | 评审 · 已评审 3 轮 + 评审者胶囊 |
| 最后一轮失败/中断 | 评审 · 第 3 轮失败 / 第 3 轮已中断 |
点一下状态条会展开最近 30 轮(外加仍在进行中的轮次)的记录:每轮的序号、时间、范围
(未提交改动 / 相对某个分支 / 某个提交)、评审者、完成情况。轮数统计不受保留条数影响。
状态来自会话级 projection:review 工具启动的 job 注册与结算各写一次,随会话日志持久化。
所以切换会话、刷新页面、重启宿主之后,历史轮次仍然在;上次进程中途退出的 running 会显示为
「已中断」,不会永远停在计时中。
什么时候不显示:
- 对话还是空的(没有消息,也没有正在跑的首轮)——不显示。
- 当前这一轮没有文件改动——不显示。这里直接复用了官方的
@deepseek-ai/dsh-workspace-changes
的workspace/changes事件:正常情况下它只在确实有改动时才追加事件,所以不需要自己再跑一次git status。
轮次进行中、或者有评审正在跑时仍然显示,避免刚触发 review 就消失。 - 宿主没有装
workspace-changes时,只保留「对话为空不显示」这一条,不会因为拿不到能力而误藏;
子智能体会话也不套用「无改动隐藏」,因为上游明确不记录 subagent 的改动。 - 只看这一轮的文件改动,不包含用户手工改的、或者 shell 在快照覆盖范围外改的文件——这是上游
workspace-changes的边界,不是本插件另做了一套判断。
评审者
| 类型 | 跑什么 | 自己会读代码吗 | 依赖 |
|---|---|---|---|
cli |
你已经信得过的编码智能体 | 看预设(codex 会自己读仓库) | 本机装了那个 CLI |
subagent |
DSH 子智能体 | 会,它有工具 | 一个 subagent 服务(默认 spawn) |
CLI 预设:codex、claude、gemini、opencode、cursor,或 custom 自定义命令模板。
CLI 装在哪:插件按这个顺序找——executable(PATH 上的名字或路径)→ ~/.local/bin、
~/.bun/bin、/opt/homebrew/bin、/usr/local/bin → 当前 profile 的依赖(node_modules/.bin/<名字>,
以及预设声明的包内 bin,如 @openai/codex 的 bin/codex.js)。
最后那条是给你的建议路径:把 CLI 装成 profile 的直接依赖(dsh plugin --profile add <包名>),
定位会一直有效。不要再把绝对路径写进 patch——本机就踩过这个坑:依赖重装把
node_modules/@openai/codex 裁掉后,钉死的绝对路径静默失效,工具描述还宣告它可用,
模型照着调用一路白跑。绝对路径仍然支持(优先级最高),但它的生命周期跟着依赖图走。
配置看着不对时先跑一次自检,别发起真评审:
review({ doctor: true })
它逐个检查可执行文件能否定位、来源是哪条候选、--version 能否跑起来、子智能体的 provider
是否注册,以及宿主有没有本插件依赖的 startActivation() 接口;还会报告 GUI 配置 schema
(@deepseek-ai/schemastery)是否解析到、来自哪里。不评审任何改动、不起评审者,也不需要 git 仓库。
子智能体的模型连通性不在自检范围内(那要真发一次请求)。
有一点值得知道:subagent 不写 agentModel 就会继承父会话的模型,所以它不自动等于「第二
意见」——想要独立意见就显式指定模型。
子智能体默认带 30 分钟墙钟上限(timeoutMs 按评审者覆盖;hardTimeoutMs 仍然限制整次调用)。
递归由宿主的委派深度预算兜底(默认 1),所以评审者通常无法再起子智能体。运行期间会报进度,
跑得久也不像卡住。
DSH 0.2.1 起,persona 与工具限制不再由子智能体启动请求传入:把它们配进一个 Agent Preset,
再用 agentPreset 选中。旧的 persona / toolFilter 会给出迁移告警,而不是静默失效。
这里刻意没有「把 diff 丢给一个裸模型」这种类型:能自己打开仓库的评审者,比只看到手头材料的
更有价值。
让 codex 也收「范围 + 关注点」
codex 原生 exec review 的范围开关与 PROMPT 互斥,所以默认 codex 评审者只能二选一。如果确实
需要带着关注点评审一个已提交范围,可以再加一个走 codex exec 的自定义评审者——它有 diff 包,
instructions 会进提示词:
reviewers:
- id: codex-diff
kind: cli
preset: custom
executable: codex
label: Codex (diff)
command: ["{exe}", "exec", "--sandbox", "read-only", "{model}", "{extra}", "{prompt}"]
promptVia: argv
needsPacket: true
promptReplacesRange: false
它走 codex exec,不是原生 exec review,评审口径与原生路径不同;只在需要「范围 + 关注点」时用。
command 里不要写 {promptArg}(否则会被当成 PROMPT 与范围互斥),也不要写 {range}(codex exec
不认 --base / --commit,范围由插件收集的 diff 包提供)。
- id: review
name: dsh-review
config:
agents:
- preset: codex
model: gpt-5.6-sol
- preset: claude
reviewers:
- id: deep-dive
kind: subagent
provider: spawn # subagent 服务实现,默认就是 spawn
agentProvider: bailian # 你 DSH 配置里的任意 provider
agentModel: deepseek-v4.1-flash
子智能体的模型同样来自你的 DSH 配置(provider、密钥、上下文窗口),GUI 里用一个合并的模型选择器
按 provider 分组列出已注册的那些,选中即同时写入 agentProvider 与 agentModel;插件里不复制任何一份。
provider 是 subagent 服务实现(默认 spawn),不是模型 provider——GUI 默认把它藏起来,
只有配成非 spawn 的值时才显示。
要指定 persona 或工具集,给该评审者加 agentPreset::这些组合归 Agent Preset 管。
spawn 支持独立 preset;fork 会继承父会话的组合。
评审模式
一个模式就是一束「盯多严、额外说什么、用谁」。
| 字段 | 含义 |
|---|---|
strength |
light / standard / deep——内置的评审措辞 |
prompt |
这个模式自己的评审指令 |
reviewers |
这个模式用哪些评审者 |
when |
always / complex / never——给智能体看的评审策略 |
config:
defaultMode: balanced
modes:
balanced:
strength: standard
reviewers: [ codex ]
deep:
strength: deep
reviewers: [ codex, deep-dive ]
一个模式都不配时,插件不追加任何约束——你继续用自己的提示词管智能体就行。when 只有在同时
打开 injectPolicy: true 时才生效,而且它只是写进系统提示词的要求,不是强制闸门。
配置项
所有配置都能在 GUI 里改;下面是同一批键在 YAML 里的样子,只列值得知道的:
| 键 | 默认 | 说明 |
|---|---|---|
defaultReviewers |
一个都没有 | 调用没给 reviewers 时用哪些;空 = 不启用任何评审者 |
defaultMode |
不设 | 调用没给 mode 时套用的模式 |
language |
auto |
zh / en / auto 报告语言(auto 跟随你的指令) |
injectPolicy |
false |
把各模式的 when 规则写进系统提示词 |
agents / reviewers |
[] |
定义 CLI 与子智能体评审者(agents 是预设简写,reviewers 是完整写法) |
modes |
{} |
命名评审模式 |
waitMs |
0 |
调用阻塞多久后交回 job id。0 = 立即交回(评审要跑几分钟,等着基本是白等) |
hardTimeoutMs |
0 |
墙钟上限;0 = 不限制——什么时候停由你决定 |
maxResultChars |
80000 |
报告上限;超出会截断并给出完整报告路径 |
maxDiffChars |
160000 |
需要 diff 的评审者能看到的变更正文上限 |
已知限制
- 评审始终在会话工作目录里跑,不能指向别的仓库。
- 一次调用只评一种范围(未提交 / 某个分支 / 某个提交),与底层 CLI 一致;
base与commit
互斥。范围 +instructions对子智能体可用,但 codex 这类 CLI 不行(范围开关与 PROMPT 互斥):
选中的评审者全都是这种 CLI 时调用会被直接拒绝,否则在报告脚注里点名没收到关注点的那几个。
想同时保留范围和关注点,要加一个 subagent 评审者;把关注点写进 mode 的prompt并不能绕过
——有范围时模式提示词同样会被跳过(见下一条)。 - 模式的提示词只在没给范围时才能到达 codex(它的 CLI 不允许两者同时出现);范围永远生效,
提示词被跳过时报告里会写明。 - codex 只在它信任的目录里能跑(
~/.codex/config.toml)。 base/commit写了不存在的分支/提交会在起评审者之前直接报错,而不是跑几分钟再报一个
「什么都没评」的批次。- 报告骨架(标题、脚注、错误信息)目前固定中文;
language只影响评审者自己的指令。 - 被截断的报告留在
$TMPDIR/dsh-review-*/report.md;未截断的运行会自己清理。 - merge 提交按第一父提交的 diff 评审(
git show -m --first-parent):这是“这次 merge 落到当前分支”的口径,不是 combined diff。 - 未跟踪的符号链接不会被跟随读取,避免把仓库外的内容交给评审者;报告只列名字并说明“未跟随”。
- 报告头的“范围内变更文件预览”来自 git 文件列表,只说明范围,不代表评审者已逐一审查。
- 工具描述里的评审者列表是注册时的快照:宿主不会因为 profile 的依赖变化而重载插件代码
(实测patchReload: live只重载 patch 层),所以描述可能滞后于磁盘现状。要准的当前状态
用review({ doctor: true })。同一原因,改完插件代码需要重启宿主才生效。 - CLI 的结论取自 stdout 且 stdout 被截断(超过
collectMaxBytes)时,报告脚注会写明可能缺尾部
并给出 spill 路径;找不到可执行文件时会列出所有尝试过的候选路径与各自失败原因。
开发
node selftest.mjs # 用假宿主驱动插件:不需要 DSH、不跑 codex、不调模型
License
MIT