关于 review-deep
review-deep 是一项代码审查技能,使用三个模型从九个视角并行审查,并另行扫描文件覆盖情况。它面向代码库审查,提供核验审查运行、文件覆盖、引用和问题的流程。
主要功能
- 01三个模型覆盖九个审查视角
- 02并行运行 27 个审查者
- 03单独执行逐文件覆盖扫描
- 04核验运行状态、覆盖率与引用
- 05汇总不同审查者的发现
适用场景
- 01从多个视角审查代码库
- 02检查审查区域和文件是否覆盖
- 03汇总前验证问题引用
MCP 相关
该技能要求启用 Serena 作为审查者的导航 MCP 服务。文档还说明,因架构不兼容,Gemini 审查分支会禁用 `researcher` MCP 服务。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jimweller/ clanker-skills --skill review-deep安装后在 Claude Code 中输入/review-deep确认已加载。
详细介绍
STARTER_CHARACTER = 🕵️♂️
代码审查命令
从 9 个角度,同时对照 3 个模型审查代码库。每位审查者都是独立的 opencode run 进程,因此 27 个进程会并发运行。每位审查者都通过 Serena 浏览实时工作树,并写入自己的发现文件。
不会打包任何内容。没有编排器进程,也没有 task 扇出。审查者读取磁盘上的代码,因此引用指向真实文件中的真实行。
一个 ocr scan 会同时运行。它会在自己的对话中审查每个可审查文件,因此覆盖整个仓库,但无法看到跨越两个文件的内容。审查者能够跨文件查看,并自行选择要读取的内容。两者各自覆盖对方的盲点。
参数
如果用户在调用时提供了路径,则将其视为相对于仓库根目录的目标目录。否则审查整个仓库。
模型
| 标签 | 模型 ID |
|---|---|
| openai | openai/gpt-6-sol |
| gemini | google/gemini-3.8-flash |
| claude | az-anthropic/claude-opus-5-5 |
OCR 覆盖检查使用 ~/.opencodereview/config.json 中的提供商和模型。
每个 ID 都必须存在于 configs/opencode/opencode.json 中对应的提供商下,并且也必须出现在该提供商的 whitelist 中。任一处缺少 ID,都会导致对应分支报错 Model not found,且只会以 NDJSON 中的 error 事件呈现。
jq -r '.provider | to_entries[] | .key as $p | .value.models | keys[] | "\($p)/\(.)"' ~/.config/opencode/opencode.json
gemini 分支有意使用 Flash 模型。Google 没有发布高于 gemini-3.1-pro-preview 的 pro 模型,因此目前最新的 Google 模型是 gemini-3.8-flash,比最新的 pro 高出 7 个次版本。在一个包含 1070 个文件的仓库上进行的一次安全审查中,flash 在相同的 Agent 和提示词下,给出了 11 条发现,而 pro 给出了 6 条,消耗的 token 大约是 pro 的两倍。这只是一次比较,并非基准测试。
花了两天才学会的规则
每次运行 opencode run 时,都要将标准输入重定向到 /dev/null。否则,进程会在创建会话前阻塞,写入 0 字节,日志也只会记录到 message=init,并且只有被终止后才会退出。opencode run 会先读取标准输入直至 EOF,然后才执行其他操作,因此任何让标准输入保持打开状态的启动器都会使它挂起;后台作业、CI 步骤和 Agent 测试框架也都如此。这是 anomalyco/opencode 的问题 #38723,针对 1.18.25 提出,并在此处的 1.18.20 上复现。
这种挂起看起来像是间歇性问题,实际上并非如此。是否挂起取决于启动器是否关闭标准输入。3 位报告者在 macOS arm64、Linux aarch64 和 Windows 上都进行了确定性测试:不重定向时,5 次中有 5 次挂起;重定向时,10 次中有 5 至 10 次成功。
此文件的早期版本禁止使用 timeout 包装 opencode run,并将 44% 的失败率归咎于该包装方式。这是错误的。即使完全不使用 timeout,也能复现卡顿;上游自己的复现脚本也使用了 timeout 120。移除所有限制,才让一个单行的标准输入问题演变成持续数日的调查,因此现在第 2b 步会检查每位审查者是否有会话记录。
不要对审查者设置 token 上限或轮次上限。审查大型仓库需要多久,就审查多久。
必须在 ~/.config/opencode/opencode.json 中启用 Serena,并使用 --project-from-cwd 启动。审查者只能通过它浏览代码。--dir 指向目标目录,以便 Serena 遍历并找到项目。
为什么 researcher 被禁用
configs/opencode/opencode.json 将 researcher MCP 服务设为 "enabled": false,审核 Agent 也会再次禁用它的工具。Google 会拒绝其架构。
researcher 将可选数组声明为联合类型 ["null","array"],这符合 JSON Schema。Anthropic 和 OpenAI 都接受这种写法。Google 会将 JSON Schema 转换成自己的 OpenAPI 子集 proto,把联合类型转换为 any_of,从数组分支中丢弃 items,然后又因 any_of[0].items: missing field 拒绝自己的输出。gemini 分支会在 5 秒内失败,只产生一个 error 事件和 5712 字节的 NDJSON。
任何使用这种模式的 MCP 服务都会以同样的方式导致 Google 分支失败。如果 gemini 分支开始报错 field predicate failed: $type == Type.ARRAY,请找到拥有所命名属性的服务并将其禁用。
流程
步骤 1:确定目标并做好准备
PROJECT_ROOT=$(git rev-parse --show-toplevel)
PROJECT_ROOT=$(cd -P "$PROJECT_ROOT" && pwd -P)
TARGET_PATH="<user-provided path or empty>"
[ -z "$TARGET_PATH" ] && TARGET_PATH="$PROJECT_ROOT"
TARGET_PATH=$(cd -P "$TARGET_PATH" 2>/dev/null && pwd -P) || { echo "TARGET_PATH does not exist"; exit 1; }
case "$TARGET_PATH" in
"$PROJECT_ROOT"|"$PROJECT_ROOT"/*) ;;
*) echo "TARGET_PATH escapes PROJECT_ROOT"; exit 1 ;;
esac
TARGET_NAME=$(basename "$TARGET_PATH")
[ "$TARGET_PATH" = "$PROJECT_ROOT" ] && TARGET_NAME="repo"
STATE_DIR="$PROJECT_ROOT/.llmtmp/review-deep"
mkdir -p "$STATE_DIR"
find "$STATE_DIR" -maxdepth 1 -name '*.md' -delete
find "$STATE_DIR" -maxdepth 1 -name 'raw-*.ndjson' -delete
find "$STATE_DIR" -maxdepth 1 \( -name 'ocr-scan.*' -o -name 'ledger.txt' -o -name 'opened.txt' \) -delete
SCOPE=""
[ "$TARGET_PATH" != "$PROJECT_ROOT" ] && SCOPE="--path ${TARGET_PATH#"$PROJECT_ROOT"/}"
ocr scan --preview --format json --repo "$PROJECT_ROOT" $SCOPE 2>/dev/null \
| jq -r '.files[] | select(.will_review) | .path' > "$STATE_DIR/ledger.txt"
echo "PROJECT_ROOT=$PROJECT_ROOT"
echo "TARGET_PATH=$TARGET_PATH"
echo "TARGET_NAME=$TARGET_NAME"
echo "STATE_DIR=$STATE_DIR"
echo "LEDGER=$(wc -l < "$STATE_DIR/ledger.txt" | tr -d ' ') files"
ocr scan --preview 会列出所有可审核文件,且不会调用模型。ledger.txt 是每位审核者必须检查的文件清单,也是步骤 3 中计算覆盖率的分母。
使用 find -delete,而不是 rm -f。PATH 上的 safe-rm 替代程序会将路径移至废纸篓;即使使用 -f,路径不存在时也会以非零状态退出,这会导致首次运行时清理失败。
步骤 2:派发 27 位审核者并执行覆盖率检查
一个 bash 代码块会启动全部 27 位审核者和 ocr scan,然后等待它们完成。替换为步骤 1 输出的值。
AREAS="security architecture solid correctness testing ops performance quality data"
MODELS="openai:openai/gpt-6-sol gemini:google/gemini-3.8-flash claude:az-anthropic/claude-opus-5-5"
SCOPE=""
[ "$TARGET_PATH" != "$PROJECT_ROOT" ] && SCOPE="--path ${TARGET_PATH#"$PROJECT_ROOT"/}"
PIDS=""
ocr scan --audience agent --repo "$PROJECT_ROOT" $SCOPE --max-tokens 1000000 \
--format json --output "$STATE_DIR/ocr-scan.json" \
< /dev/null > /dev/null 2> "$STATE_DIR/ocr-scan.log" &
PIDS="$PIDS $!"
for entry in $MODELS; do
label="${entry%%:*}"
model="${entry#*:}"
for area in $AREAS; do
out="$STATE_DIR/$label-$area.md"
prompt="Review every file under $TARGET_PATH.
Navigate with serena symbolic tools: get_symbols_overview to map a file,
find_symbol to read a definition, find_referencing_symbols to find callers,
find_declaration and find_implementations to resolve a usage. Read a whole
file only when symbolic navigation cannot answer the question.
Cite path:line from the live file and name the enclosing function, method,
or type. A line you saw counts as verified, and find_symbol returns a symbol's
line range. When you cannot verify the exact line, cite the nearest line you
saw and end the finding with `line unconfirmed`. Never drop a real defect
because its line number is uncertain.
LEDGER_PATH: $STATE_DIR/ledger.txt
LEDGER_PATH lists the reviewable source files, one path per line. Read it
first and cover every file it lists.
OUTPUT_PATH: $out
Write your findings to OUTPUT_PATH. Writing that file is mandatory and is
how your work is delivered. Do not return findings as your response."
opencode run \
--agent "reviewer-$area" \
-m "$model" \
--format json \
--dir "$TARGET_PATH" \
--title "Review $label $area" \
"$prompt" \
< /dev/null \
> "$STATE_DIR/raw-$label-$area.ndjson" 2>"$STATE_DIR/$label-$area.log" &
PIDS="$PIDS $!"
done
done
echo "launched $(echo $PIDS | wc -w | tr -d ' ') processes, 27 reviewers and 1 ocr scan"
wait
echo "done, $(ls "$STATE_DIR"/*-*.md 2>/dev/null | wc -l | tr -d ' ') files written"
预计总耗时会接近最慢的单个审核者,而不是所有审核者耗时的总和。在一个有 22 个文件的 TypeScript 仓库中,单个审核者耗时 148 秒。
审核者使用 apply_patch 写入自己的文件。审核者 Agent 允许使用该工具,并禁止 bash、webfetch、context7_*、repomix_*、researcher_* 以及 Serena 的四类写入工具。它们以 mode: primary 运行,因为通过 --agent 选择子 Agent 时,opencode 会忽略其 tools 配置块。
审核者不得调用 task。在一个有 1070 个文件的仓库中,27 位审核者里有两位启动了一个始终没有返回的子 Agent;TaskTool 会以前台方式运行子 Agent,并使用阻塞式 yield,因此父级进程会挂起,步骤 2 的 wait 也会随之挂起。这些 Agent 会设置 tools.task: false 和 permission.task: {"*": deny}。修复后,这两位审核者都顺利完成,且没有调用 task。
没有任何机制能阻止同时运行两次。步骤 2 会写入固定路径,且没有锁,因此同一仓库中若启动第二次运行,就会交错写入同一个 NDJSON 文件,并覆盖同一个输出文件。2026-09-22,这导致两次尝试的结果损坏。派发任务前,请检查是否已有正在运行的任务。
提供商的限制不是瓶颈。OpenAI 报告的每分钟 token 数为 40,000,000,Azure Foundry 为 15,000,000。
步骤 2b:确认每位审核者都已启动
在步骤 2 仍在等待时,单独以前台调用运行此命令。未创建会话的审核者不会产生任何内容,之后也不会产生。
sleep 30
DB="$HOME/.local/share/opencode/opencode.db"
for label in openai gemini claude; do
for area in security architecture solid correctness testing ops performance quality data; do
n=$(sqlite3 -readonly "$DB" "SELECT count(*) FROM session WHERE title='Review $label $area';")
[ "$n" -eq 0 ] && echo "NO SESSION $label $area"
done
done
echo "checked 27"
正常运行的审核者会在启动后约 3 秒内写入自己的会话行。如果此处列出了停滞的审核者,请终止并重新启动,而不是任由其停滞。2026-09-22,有一次运行停滞了 55 分钟,输出字节数始终为零,却一直无人查看。
第 3 步:验证
某个模型有时会直接在响应中返回审查结果,而不是写入 OUTPUT_PATH。gpt-6-sol 就是如此。结果位于 NDJSON 中,因此请先将其复制出来,再进行验证。
for f in "$STATE_DIR"/raw-*.ndjson; do
b=$(basename "$f" .ndjson); out="$STATE_DIR/${b#raw-}.md"
[ -s "$out" ] && continue
jq -r 'select(.type=="text") | .part.text' "$f" | awk '/^## /{p=1} p' > "$out"
done
for label in openai gemini claude; do
echo "== $label =="
for area in security architecture solid correctness testing ops performance quality data; do
f="$STATE_DIR/$label-$area.md"
if [ ! -s "$f" ]; then
echo " $area MISSING errors=$(grep -c '\"type\":\"error\"' "$STATE_DIR/raw-$label-$area.ndjson" 2>/dev/null)"
else
n=$(grep -c '^- \*\*' "$f")
cited=$(grep -oE '^- \*\*(High|Medium|Low)\*\* `[^`]+`' "$f" | grep -c ':[0-9]')
unconf=$(grep -c 'line unconfirmed' "$f")
echo " $area ok findings=$n cited=$cited unconfirmed=$unconf"
fi
done
done
然后按已打开的文件数而非已引用的文件数衡量覆盖情况。打开但未引用的文件表示已阅读且未发现问题。
jq -r 'select(.type=="tool_use") | .part.state.input | (.filePath // .relative_path // .path // empty)' \
"$STATE_DIR"/raw-*.ndjson 2>/dev/null | sed "s|^$PROJECT_ROOT/||" | sort -u > "$STATE_DIR/opened.txt"
echo "ledger files opened=$(comm -12 <(sort "$STATE_DIR/ledger.txt") "$STATE_DIR/opened.txt" | wc -l | tr -d ' ') of $(wc -l < "$STATE_DIR/ledger.txt" | tr -d ' ')"
comm -23 <(sort "$STATE_DIR/ledger.txt") "$STATE_DIR/opened.txt" | sed 's/^/ never opened: /'
jq -r '"ocr status=\(.status) files=\(.summary.files_reviewed) findings=\(.comments|length)"' "$STATE_DIR/ocr-scan.json"
如果某个区域的文件缺失、为空、没有 H2,或有 H2 但既没有问题行,也没有准确的 No findings.,则视为未审查。切勿将上述任何情况记为 No findings.。
重新派发给该审查者,这只需运行一次 opencode run。重试一次后仍未覆盖的区域,应在第 4 步中报告为 Not reviewed,并注明模型和原因。
审查者有时会在 H2 前写一条过渡句。请删除第一个 ## 之前的所有内容,而不要为此重新派发。调整提示词无法解决这个问题;删除内容始终有效。
比较 cited 和 findings。如果某条问题的首个反引号字段没有 :line,则该问题不可用,因为无人能在不重新阅读整个文件的情况下核实它。2026-09-21 的测量结果:openai 引用了 16 条问题中的 16 条,claude 引用了 28 条中的 5 条。
无论审查者是否在末尾标注 line unconfirmed,只要一条问题包含 path:line,cited 就会计入它。引用位于行首,标记位于行尾,因此二者不会互相排除。用 cited 减去 unconfirmed,即可得到审查者愿意背书的问题数。2026-09-22 对 gemini 安全探测的测量结果:38 条问题有引用,0 条带有该标记。
既然审查者现在可以将问题标记为 line unconfirmed,而不必将其舍弃,那么存在差距并不一定意味着有缺陷。重新派发前,请先阅读未引用的问题。如果它们也没有符号,再重新派发。
删除未经验证的问题这一指示已于 2026-09-22 移除。对 gemini 的一个区域进行测量后发现,保留它要付出 6 条问题的代价:有该条款时为 1 条,没有时为 6 条,其中包括被抑制的运行检查过却从未报告的 6 个路径遍历缺陷。移除 Ownership 表却只找回了 1 条,因此该表予以保留。
第 4 步:扫描并综合
阅读文件前,先扫描每个文件。审查者若引用了秘密信息,敏感数据钩子会阻止该文件,而阅读过程中无法请求绕过。
SCAN="$HOME/.config/dotfiles/scripts/canary-scan.sh"
if [ -x "$SCAN" ]; then
for f in "$STATE_DIR"/*-*.md "$STATE_DIR/ocr-scan.json"; do
[ -s "$f" ] && { echo "== $(basename "$f") =="; "$SCAN" "$f" || true; }
done
fi
文件无问题或插件不存在时,canary-scan.sh 会以 0 退出且不输出内容;发现命中时,则以 2 退出,并为每条规则打印一行 <ruleId> x<count>。|| true 可避免非零退出导致此步骤结束。若有命中,请指出触发了什么规则,并告知操作员:在自己的提示词中加入 [allow-pii] 后重新调用即可解除阻止。
然后阅读这 27 个文件和 ocr-scan.json,并跨模型进行比较。ocr 的评论包含 path、start_line、severity 和 category;当两处结果一致时,可将其作为第四个来源计入。
- 多数一致的问题,3 个模型全部确认。列出所有 3 个模型都标记的问题,并注明区域、严重性和问题描述。
- 多数一致的问题,3 个模型中有 2 个确认。注明意见一致的两个模型,以及意见不同的模型。
- 单个模型发现的问题。全部列出,并注明各自由哪个模型提出。
- 评估冲突。列出模型意见不一致的区域,例如一个模型标记了风险,而另一个认为没有问题。
- 覆盖缺口。列出第 3 步中标记为
Not reviewed的每个区域,并注明模型和原因。多数一致的问题数只有结合覆盖该区域的模型数才有意义。列出所有没有审查者打开过的账本文件。这些文件只有 ocr 的结果可供审查。
包含每一条问题。不要跳过或概括任何项目。
预期输出文件
.llmtmp/review-deep/ 保存完整运行结果,并会在下次运行开始时清空:
<label>-<area>.md,每个区域的问题文件,共 27 个ocr-scan.json和ocr-scan.log,覆盖检查ledger.txt,可审查文件;以及opened.txt,审查者打开的文件raw-<label>-<area>.ndjson,每位审查者的完整事件流<label>-<area>.log,每位审查者的标准错误
NDJSON 参考
每行一个 JSON 对象。跳过解析失败的行。
| 类型 | 含义 | 关键字段 |
|---|---|---|
step_start | 一个大模型轮次开始 | |
text | 模型输出的文本 | part.text |
tool_use | 模型调用了工具 | part.tool、part.state.status、part.state.input |
step_finish | 一个轮次完成 | part.reason、part.cost、part.tokens |
error | 会话级错误 | error.data.message |
step_finish 的原因是 stop 表示已完成,tool-calls 表示仍在继续。
jq -s '[.[] | select(.type=="step_finish") | .part.cost] | add' "$NDJSON"
tac "$NDJSON" | jq -s 'first(.[] | select(.type=="step_finish")) | .part.tokens.total'
jq -r 'select(.type=="tool_use") | .part.tool' "$NDJSON" | sort | uniq -c | sort -rn
jq -r 'select(.type=="error") | .error.data.message' "$NDJSON"
Serena 在 TypeScript 仓库中承担了 181 次工具调用里的 119 次,在包含 HTML、CSS 和 Dockerfile 的仓库中承担了 28 次里的 2 次。没有语言服务器支持文件时,符号导航的价值微乎其微。
规则
- 调用 Agent 负责启动和汇总,不执行任何自身的审查分析。只有第 4 步进行分析。
- 在
opencode run中始终将标准输入重定向到/dev/null。省略此项会导致进程在创建会话前挂起,且没有任何输出或错误,这是 anomalyco/opencode issue #38723。 - 不要为审查者设置 token 上限或轮次上限。让它完成任务。
- 调度后约 30 秒运行第 2b 步。没有会话记录的审查者已失效,需要重新启动;仅凭耗时无法区分这种情况与审查较慢。
- 将第 2 步作为后台 Bash 调用运行。
wait会阻塞,直到全部 27 个进程退出;放到后台可避免其阻塞主会话。 - 在一个 bash 代码块中启动全部 27 个审查者和 ocr 扫描。它们是相互独立的进程。
- 使用普通消息调用,不要使用
--command。--command标志与 context7 MCP 服务存在已知问题。 - 运行期间不要清理各区域文件、NDJSON 或日志。第 1 步会在下一次运行开始时清理它们。
- 如果某个审查者失败,仍要等待其他审查者完成并报告结果。
- 审查者 Agent 位于
configs/opencode/agents/中,并通过./install使用 glob 链接。若某台机器在这些文件变更后尚未运行 install,则会报未知 Agent 错误。