review-deep について
review-deepは、3つのモデルで9つの観点から並行してコードレビューを行い、別途ファイル単位の網羅性スキャンも実行するスキルです。リポジトリのレビュー向けに、実行状況、対象範囲、引用、指摘を検証してから統合する手順を備えています。
主な機能
- 013モデルによる9つのレビュー観点
- 0227人のレビュアーを並行実行
- 03ファイル単位の網羅性スキャン
- 04実行状況、範囲、引用の確認
- 05レビュアー間の指摘の統合
活用シーン
- 01複数の観点からのリポジトリレビュー
- 02レビュー対象領域とファイルの網羅確認
- 03指摘の引用を統合前に検証
MCP 対応
レビュアーのナビゲーション用MCPサーバーとしてSerenaの有効化が必要です。また、スキーマ非互換のためGeminiのレビューでは`researcher` MCPサーバーを無効にすると説明されています。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jimweller/ clanker-skills --skill review-deepインストール後、Claude Code で/review-deepを入力して読み込まれたか確認します。
詳細
STARTER_CHARACTER = 🕵️♂️
コードレビューコマンド
3 つのモデルに対して、9 つの視点から一度にコードベースをレビューします。各レビュアーは独立した opencode run プロセスで実行されるため、27 個すべてが並行して動作します。それぞれが Serena を使って作業ツリーの現在の状態を調べ、独自の指摘ファイルを書き出します。
何もパッケージ化されません。オーケストレータープロセスも、task による振り分けもありません。レビュアーはディスク上のコードを読み取るため、引用箇所は実在するファイルの実在する行を指します。
これらと並行して、1 つの 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 ファイルのリポジトリに対するセキュリティレビュー 1 件で測定したところ、同じエージェントとプロンプトで、flash は pro の 6 件に対して 11 件の指摘を返し、トークン数はおよそ 2 倍でした。これは比較 1 件の結果であり、ベンチマークではありません。
学ぶのに 2 日かかったルール
すべての opencode run で、標準入力を /dev/null からリダイレクトしてください。これをしないと、セッションが作成される前にプロセスが停止し、0 バイトを書き込み、message=init 以降は何もログに残さず、何かに終了させられるまで動き続けます。opencode run はほかの処理を始める前に標準入力を EOF まで読み取るため、標準入力を開いたままにするランチャーはすべてハングします。バックグラウンドジョブ、CI ステップ、エージェントハーネスも同様です。これは anomalyco/opencode issue #38723 で、1.18.25 を対象に報告されており、ここでは 1.18.20 で再現しました。
このハングは断続的に見えますが、実際はそうではありません。ランチャーが標準入力を閉じるかどうかで決まります。3 人の報告者が macOS arm64、Linux aarch64、Windows で確実に再現することを測定しており、リダイレクトなしでは 5 回中 5 回ハングし、リダイレクトありでは 10 回中 5~10 回成功しました。
このファイルの以前のバージョンでは、opencode run を timeout でラップすることを禁じ、失敗率 44% の原因をラッパーのせいにしていました。これは誤りでした。timeout がどこにもなくても停止は再現し、upstream 独自の再現スクリプトでも timeout 120 を使っています。制限をすべて取り除いたことで、1 行の標準入力のバグが数日がかりの調査になりました。そのため、Step 2b では各レビュアーにセッション行があるか確認するようになりました。
レビュアーにトークン上限もターン上限も設けないでください。大規模なリポジトリのレビューには、必要なだけ時間がかかります。
Serena は ~/.config/opencode/opencode.json で有効にし、--project-from-cwd を付けて起動する必要があります。レビュアーが使えるナビゲーション手段はこれだけです。--dir で対象を指定し、Serena がプロジェクトを見つけられるようにします。
researcher が無効化されている理由
configs/opencode/opencode.json では researcher MCP サーバーに "enabled": false が設定されており、レビュアーエージェントもそのツールを再度無効化しています。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 イベントを 1 件、NDJSON を 5712 バイト出力します。
同じパターンを使用する MCP サーバーは、Google アームを同じように破損させます。gemini アームが field predicate failed: $type == Type.ARRAY で失敗し始めたら、名前が示されたプロパティを所有するサーバーを特定して無効化してください。
手順
Step 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 は、各レビュアーが確認する必要のあるファイルの一覧であり、Step 3 でカバレッジを算出する際の分母です。
rm -f ではなく find -delete を使用してください。PATH 上の safe-rm シムは、パスをゴミ箱に移動し、-f を指定していてもパスが存在しない場合はゼロ以外の値で終了します。そのため、初回実行時に消去処理が失敗します。
Step 2: 27 人のレビュアーとカバレッジ確認を実行する
1 つの bash ブロックで、27 人のレビュアーと ocr スキャンを起動し、その後に待機します。Step 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 リポジトリでの測定では、1 人のレビュアーに 148 秒かかりました。
レビュアーは apply_patch を使って自身のファイルを書き込みます。レビュアーエージェントにはその操作を許可し、bash、webfetch、context7_*、repomix_*、researcher_*、および Serena の 4 つの書き込み系ファミリーを禁止します。--agent でエージェントを選択した場合、opencode はサブエージェントの tools ブロックを無視するため、mode: primary として実行します。
レビュアーは task を呼び出してはいけません。1070 ファイルのリポジトリで、27 人中 2 人のレビュアーが応答しないサブエージェントを起動しました。TaskTool はブロッキングする yield を伴うフォアグラウンドのサブエージェントを実行するため、親が停止し、Step 2 の wait も同時に停止しました。エージェントには tools.task: false と permission.task: {"*": deny} を設定します。この修正後、両方のレビュアーは task の呼び出しゼロで完了しました。
同時に 2 つの実行を防ぐ仕組みはありません。Step 2 はロックなしで固定パスに書き込むため、同じリポジトリに対する 2 回目の実行は、同じ NDJSON に交互に書き込み、同じ出力ファイルを上書きします。2026-09-22 に、この問題で 2 回の試行が破損しました。実行を開始する前に、進行中の実行がないか確認してください。
制約となるのはプロバイダーの上限ではありません。OpenAI は毎分 40,000,000 トークン、Azure Foundry は 15,000,000 トークンを報告しています。
Step 2b: すべてのレビュアーが起動したことを確認する
Step 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 です。1 回再試行しても対象領域が未カバーのままなら、モデル名と理由を挙げて、ステップ 4 で Not reviewed と報告します。
レビュアーは H2 の前に、つなぎの文を書くことがあります。その場合は、再ディスパッチせず、最初の ## より前のすべてを削除してください。プロンプトの調整では解決しません。削除すれば必ず解決します。
cited と findings を比較してください。最初のバッククォートで囲まれたフィールドに :line が含まれていない指摘は、ファイル全体を読み直さなければ誰も確認できないため、使用できません。2026-09-21 の測定では、openai は 16 件中 16 件、claude は 28 件中 5 件を引用しました。
レビュアーが末尾に line unconfirmed を付けたかどうかにかかわらず、cited は path:line を含む指摘を数えます。引用は行の先頭に、マーカーは末尾にあるため、互いに除外し合うことはありません。レビュアーが確証を持っている件数を得るには、cited から unconfirmed を引いてください。2026-09-22 の gemini セキュリティプローブでの測定では、38 件が引用され、マーカーが付いていたのは 0 件でした。
レビュアーが指摘を破棄する代わりに line unconfirmed と記載できるようになったため、欠落が必ずしも不具合とは限りません。再ディスパッチする前に、未引用の指摘を読んでください。シンボルも付いていない場合に再ディスパッチします。
未検証の指摘を除外する指示は、2026-09-22 に削除されました。gemini で 1 つの領域を対象に測定したところ、この指示がある場合に残った指摘は 6 件、ない場合は 1 件でした。指示なしの場合には、抑制された実行で調査されたものの報告されなかった、パストラバーサルの不具合 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 を返します。検出があった場合は、ルールごとに <ruleId> x<count> の行を 1 行出力し、終了コード 2 を返します。|| true によって、終了コードが 0 以外でもステップが終了しないようにします。検出があったら、何が検出されたかを示し、オペレーター自身のプロンプトに [allow-pii] を付けて再実行すればブロックが解除されることを伝えてください。
次に、27 個のファイルと ocr-scan.json を読み、モデル間で比較します。ocr のコメントには path、start_line、severity、category が含まれ、2 つのモデルで一致する指摘がある場合は、第 4 の情報源として数えます。
- クォーラムでの指摘、3/3。3 つすべてのモデルが指摘した問題を、領域、重大度、指摘内容とともに挙げます。
- クォーラムでの指摘、2/3。一致した 2 つのモデルと、一致しなかったモデルを示します。
- 単一モデルの指摘。すべてを列挙し、それぞれを指摘したモデルを記します。
- 評価の食い違い。あるモデルがリスクを指摘し、別のモデルが問題なしとする場合など、モデル間で見解が異なる領域を示します。
- カバレッジのギャップ。ステップ 3 で
Not reviewedとされたすべての領域について、モデル名と理由を示します。クォーラム数が意味を持つのは、その領域をカバーしたモデルを基準にした場合のみです。どのレビュアーも開かなかった ledger ファイルをすべて挙げます。これらのファイルで受けたレビューは 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 リファレンス
1 行につき JSON オブジェクトを 1 つ記述します。解析に失敗した行はスキップします。
| 種類 | 意味 | 主なフィールド |
|---|---|---|
step_start | LLM のターンが開始します | |
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 件を担いました。ファイルを支える言語サーバーがない場合、シンボルナビゲーションの価値はほとんどありません。
ルール
- 呼び出し元のエージェントは起動役兼統合役です。独自のレビュー分析は行いません。分析を行うのはステップ 4 のみです。
opencode runでは、必ず標準入力を/dev/nullからリダイレクトしてください。これを省略すると、セッションの作成前にプロセスがハングし、出力もエラーも表示されません。これは anomalyco/opencode の issue #38723 です。- レビュアーにトークン数の上限もターン数の上限も設定しないでください。完了するまで実行させます。
- ディスパッチから約 30 秒後にステップ 2b を実行してください。セッション行がないレビュアーは停止しており、再起動が必要です。経過時間だけでは、低速なレビューと区別できません。
- ステップ 2 のブロックは、バックグラウンドの Bash 呼び出しとして実行してください。
waitは 27 個すべてのプロセスが終了するまでブロックします。バックグラウンドで実行することで、メインセッションをブロックせずに済みます。 - 27 個すべてのレビュアーと ocr スキャンを、1 つの bash ブロックで起動してください。これらは互いに独立したプロセスです。
--commandではなく、通常のメッセージ呼び出しを使用してください。--commandフラグには context7 MCP サーバーとの既知の問題があります。- 実行中は、エリアごとのファイル、NDJSON、ログを削除しないでください。次回の実行開始時にステップ 1 が削除します。
- レビュアーが失敗しても、ほかのレビュアーを待ち、その結果を報告してください。
- レビュアーエージェントは
configs/opencode/agents/にあり、./installによって glob リンクされます。変更後に install を実行していないマシンでは、エージェントが不明というエラーになります。