この記事は Zenn に投稿したもの の再掲です。内容は同じで、更新も両方に反映します。
はじめに
近年、Claude Code に代表される AI エージェントを開発ワークフローに組み込むことは、エンジニアにとって極めて日常的な光景となりました。私たちは、プルリクエスト(PR)を作成した直後に AI エージェントを起動し、変更内容に対する「最初のレビュー」を依頼しています。
しかし、このプロセスには致命的な課題がありました。それは、**「同じ差分(diff)を投げているにもかかわらず、実行するたびに指摘内容の質や種類がブレる」**という問題です。ある時はセキュリティの脆弱性を鋭く指摘する一方で、別の時は命名規則の些細なミスばかりを指摘し、肝心のロジックの不備を見逃してしまう。この「不確実性」は、AI レビューを自動化された CI プロセスに組み込む際の大きな障壁となります。
本記事では、なぜ AI によるレビュー精度が不安定になるのかという原因分析から、そのブレを最小化するために開発した独自の CLI ツール「Revi」の設計思想、および実装の詳細について解説します。
なぜ AI のレビューは「ブレる」のか
分析の結果、指摘のブレは単なるモデルの確率的な性質(Temperature)だけでなく、以下の 4 つの要因が複合的に絡み合っていることが判明しました。
1. 直前の会話履歴によるバイアス
AI エージェントは、対話型のコンテキストを保持しています。例えば、レビューを開始する直前まで「リファクタリングによるコードの整理」について対話していた場合、エージェントの注意(Attention)は「コードの綺麗さ」に強く引き寄せられます。その結果、ロジックの正当性よりも、変数名の適切さといった「見た目」の指摘に偏る現象が発生します。
2. コンテキストウィンドウの枯渇と情報の忘却
大規模な PR をレビューさせる際、エージェントが参照すべき周辺ファイルや過去の設計ドキュメントがコンテキストウィンドウを圧迫します。ウィンドウの限界に達すると、古い情報や「前提となる制約事項」が動的に破棄され、結果として以前は見逃さなかったはずの制約違反を見逃すようになります。
3. 暗黙の前提への依存
「前のターンで合意した仕様」を、新しいセッションで再度明示的にプロンプトへ含めない限り、エージェントはそれを「自明なもの」として扱いません。このため、プロンプトのわずかな構成の違いが、レビューの観点に決定的な差を生んでしまいます。
4. 「自分のコード」への甘さ
エージェントに「直前に修正したコード」のレビューを継続して依頼すると、エージェントはそのコードを「既知の、修正済みのもの」と認識しやすくなります。これにより、修正プロセスで発生した新たなデグレに対して、人間と同様に「修正したのだから大丈夫だろう」という認知バイアス(甘さ)が生じます。
精度を安定させるための設計原則
これらの課題を解決するためには、AI エージェントを「対話相手」としてではなく、「決定論的なパイプラインの構成要素」として扱う必要があります。私たちは、以下の 5 つの設計原則を策定しました。
- セッションの分離 (Isolation): 毎回、以前の履歴を一切持たないクリーンなセッション(Headless モード)で起動する。
-
観点の固定 (Standardized Criteria): システムプロンプトに、
Correctness(正当性),Security(セキュリティ),Performance(性能),Maintainability(保守性) の 4 つの観点を強制的に組み込む。 -
入力スコープの限定 (Scoped Input): 全ファイルを読み込ませるのではなく、diff を主軸とし、依存関係の解消に必要なファイルのみを明示的に
Readさせる多段パイプラインを構築する。 -
出力スキーマの構造化 (Structured Output): 自然文ではなく、必ず JSON 形式で出力させる(
file,line,severity,category,confidenceを含む)。 - ステージ分割 (Pipeline Decomposition): 「差分の解析 → 背景の理解 → レビューの実行 → 結果の集約」というプロセスを、単一のプロンプトではなく、段階的なステップとして分離する。
解決策:CLI ツール「Revi」の開発
これらの原則を運用ルールとしてエンジニアに強いるのは不可能です。そこで、私たちは上記の設計原則を技術的に強制する OSS ベースの CLI ツールである 「Revi」 を開発しました。
リポジトリは GitHub で公開しています: https://github.com/ben-saito/revi
Revi は bun をランタイムとして使用し、既存の AI エージェント(Claude Code 等)をサブプロセスとして制御するツールです。
実装のポイント
Revi は、エージェントの headless モード(-p / prompt)を利用して、バックグラウンドで実行されます。プロンプトの入力と出力の制御をプログラム的に行うことで、人間による介入の余地を排除しています。
// Revi のコアロジックのイメージ
import { spawn } from 'child_process';
async function runReview(diff: string) {
// Claude Code を headless モードで起動
const agent = spawn('claude', ['-p', 'Review the following diff according to the system prompt...']);
const prompt = `
Analyze this diff strictly.
Output format: JSON
Criteria: [Correctness, Security, Performance, Maintainability]
DIFF:
${diff}
`;
// プロンプトを流し込み、出力を解析するプロセス...
}
特徴的な機能
- 認証の再利用: 既存の AI エージェントの認証情報をそのまま利用するため、個別の API キー管理が不要です。
- レジリエンス: 指数バックオフを用いたリトライメカニズムと、API 制限に達した際のサーキットブレーカーを実装し、CI 環境での安定稼働を実現しています。
- マルチフォーマット出力: ターミナルでの人間向けの要約、GitHub PR に投稿するための Markdown、後続の解析用の JSON という 3 つの形式を同時に生成します。
利用例は非常にシンプルです。
# main ブランチに対する差分をレビューし、結果を PR コメントとして構成
revi review --base main --format github
安定性をどう測る設計にしているか
「決定論的に制御した」と言うだけでは意味がありません。効いているかを測れる形にしておく必要があります。Revi では次の 2 つを測定の型として設計しています。
- 対称差分(Symmetric Difference)テスト: 同じ差分に対して Revi を N 回連続で実行し、出力された指摘事項の集合の「対称差分」を取ります。見るのは Severity が High / Critical のものだけです。正解データを用意する必要がないのがこの手法の利点で、自己一致だけを見るため導入コストがほぼゼロです。指摘がブレたときは、モデルを疑う前に入力を疑います。原因はたいてい「会話履歴が混入している」「読み込むファイルが実行ごとに変わっている」「プロンプトに時刻やランダム要素が入っている」のいずれかです。
- ゴールデンセットによる定点観測: 既知の脆弱なコミット(SQL インジェクション、不適切な権限設定など)を固定セットとして用意し、CI 内で検知できるかを継続的に確認する設計です。対称差分が「ブレていないこと」しか見ないのに対し、こちらは「正しく当てられていること」を見ます。
なお現時点の Revi は主にローカル差分(--base HEAD)に対して回しており、上記のうち CI 上でのゴールデンセット定点観測はまだ設計段階です。実測値が出たら別途書きます。
まとめ
AI によるコードレビューの成否は、モデルのパラメータ(Temperature)やモデル自体の知能よりも、**「いかに実行環境を決定論的に制御できるか」**というエンジニアリングの力に依存します。
運用ルールで「意識してレビューしてください」と依頼するだけでは、人間の不注意や文脈の揺らぎを排除できません。課題を特定し、それを解決するための仕組みをツールとしてコードに焼き付ける。このアプローチこそが、AI エージェントを真に信頼できるチームメンバーへと昇華させる鍵となります。
「指摘がブレたときは、モデルを疑う前に入力を疑う」と書きました。同じ理屈で、「指摘0件」を「安定した」と読んではいけないという問題が残ります。入力が届いていないだけでも0件にはなるからです。
測定結果をどう読むか、規約が効かなくなる4つのパターンの見分け方、構成パターン別の rules.yml を本にまとめました。
→ AIコンテキスト設計ガイド ── 規約を配ったのに守られない理由(2,000円)
第3章「規約が効かなくなる4つのパターン」まで無料で読めます。
/compact の後もルートの CLAUDE.md は再注入されるが、サブディレクトリと paths: 付きは再注入されない ── 長いセッションで静かに落ちるのはどちらか、という章です。