ほとんどの人が口にしないことについて正直に話そう。私たちはAI生成コードをほとんどレビューしていない。
diffは600行。エージェントは14ファイルに変更を加えた。Pull requestを開き、変更をスクロールし、いくつかの関数に目を細め、マージする。先にテストを実行するかもしれない。しないかもしれない。コードはまともに見える。エージェントは完了と言った。シップしよう。
これは怠慢ではない。構造的な問題だ。従来のコードレビューは、人間がコードを書き、聞かれれば推論を説明できる世界のために設計された。diffが50-200行だったのは、一人の人間が集中セッションで書く量がそれくらいだからだ。PRの説明に「Yの理由でアプローチXを選んだ」と書かれていて、そのコンテキストを信頼できた。
AIエージェントはそうは動かない。Claude Codeは2分で500行の動作するコードを生成できる。PRの説明はたいてい「feature Xを実装」だけだ。diffは何が変わったかは教えてくれるが、なぜについては何も教えてくれない。エージェントがどの代替案を検討したかの記録がない。どのトレードオフをしたかの説明がない。実際に何かをテストした証拠がない。ブラックボックスの出力をレビューしていて、レビューツールは出力しか見せてくれない。
この記事では、diff基盤のレビューがエージェント出力に対してなぜ失敗するのか、実際に欠けている層は何か、そしてレビュー時間を3倍にすることなくAI生成コードをレビュー可能にする具体的なパターンを解説する。
レビューのギャップ
開発者はプライベートではこの問題に正直だ。コミュニティのスレッドでパターンは繰り返される。「エージェントのdiffはだいたい流し見するだけ。」「テストが通ることを確認してマージ。」「だいたい合ってそうなら承認。」
制約を考えればこれは合理的な行動だ。人間の同僚がPRを出す時はコンテキストがある。何に取り組んでいたか知っている、チケットを見た、スタンドアップでアプローチを議論したかもしれない。diffは補足だ。本当のレビューは共有コンテキストを通じて行われた。
エージェントの場合、そのどれもない。エージェントはタスクを引き取り、3分間沈黙し、diffを生成した。唯一のコンテキストはエージェントがPRに残した1行の説明だ。そのdiffを意図や推論のコンテキストなしにゼロからレビューするのは、同じサイズの人間のPRをレビューする5-10倍の時間がかかる。だから人々はやらない。いくつかの重要な箇所をスポットチェックして承認する。
結果は予測可能だ。コンテキストがあれば発見できたバグがすり抜ける。エージェントが小さな決定を重ねることでアーキテクチャのドリフトが蓄積する。コード品質が微妙に劣化する。壊れてはいないが、完全に正しくもない。あるファイルで一貫性のないエラーハンドリング。動くがスケールしないデータベースクエリ。エージェントが存在を知らなかったために既存のものを複製した新しいユーティリティ関数。
これらの失敗はどれもdiffレビューでは見えない。エージェントが何をしようとしていたかを理解し、その意図を実行と比較できて初めて見える。
diffが十分でない理由
diffはテキスト変更の記録だ。それだけだ。人間のコードレビューでは、レビュアーがパターン認識と共有コンテキストから意図を推測できるのでdiffは機能する。同僚がtry/catchブロックを追加するのを見て、先週のバグレポートのエラーケースを処理していると分かる。関数のリネームを見て、チームが合意した命名規則に従っていると分かる。
エージェント生成コードでは、推論プロセスの一部でなかったため、意図を推測できない。500行のエージェントdiffが実際に教えてくれること:
- どのファイルが変更されたか
- どの行が追加、変更、削除されたか
- 新しいコードの構文構造
教えてくれないこと:
なぜこのアプローチが選ばれたか。 エージェントは3つの異なる実装を検討したかもしれない。1つを選んだ。なぜかは分からない。選んだものが最適かもしれない。最初に試したもので十分に機能しただけかもしれない。diffからは判断できない。
どの代替案が破棄されたか。 エージェントがWebSocketサブスクリプションではなくポーリング戦略を選んだ場合、それは意図的なアーキテクチャ決定だったのか、偶然だったのか。diffは教えてくれない。
実装がspecに合致しているかどうか。 specを一つのウィンドウで、diffを別のウィンドウで開き、各受け入れ基準を手動でクロスリファレンスする必要がある。ほとんどの人はやらない。
何がどのようにテストされたか。 diffに新しいテストファイルが含まれるかもしれない。しかしエージェントは実行したのか? パスしたのか? specのエッジケースをカバーしているのか? 知るにはブランチをチェックアウトして自分で実行する必要がある。
エージェントがスコープ内に留まったかどうか。 タスクが「ログインバグの修正」だったのに、エージェントはauthミドルウェアもリファクタリングし、2つのユーティリティ関数をリネームし、設定スキーマを更新したかもしれない。これらの変更はすべて単独では問題なく見える。しかし要求されていない、仕様化されていない、元の受け入れ基準に対してテストされていない。
これは特定のdiffツールの問題ではない。GitHubのレビューインターフェース、GitLabのマージリクエスト、Gerrit、ターミナルのgit diff。すべて同じことを見せる: 何が変わったか。エージェント出力に対して、「何が変わったか」は最も重要でない質問だ。重要な質問は: この変更はやるべきことをやっていて、それ以外のことはしていないか?
欠けている層: 実装ナラティブ
レビュアーが本当に必要としているのは、エージェントの推論の軌跡だ。diffではない。実装のストーリー: エージェントが何をする計画だったか、実際に何をしたか、結果をどう検証したか。これを実装ナラティブと呼ぼう。
良い実装ナラティブは5つの質問に答える:
- 計画は何だったか? コードを書く前に、エージェントは何をするつもりだったか? どのファイル、どのアプローチ、どの操作順序?
- 実装中に何が起きたか? 計画はコードベースとの接触を乗り越えたか? サプライズ、ピボット、スコープ変更はあったか?
- 最終結果は何だったか? diffではない。何が変わりなぜ変わったかのプレーンテキストの要約。
- どのように検証されたか? 実装が機能することを確認するためにエージェントが行った具体的なステップ。「テストがパスした」ではなく「受け入れ基準#3をXを実行して検証し、Yを観察した。」
- レビュアーは何をチェックすべきか? 人間の注意に値するものについてのエージェント自身の推奨。どちらの方向にも行き得る設計決定や、セカンドオピニオンに値するパフォーマンストレードオフがあるかもしれない。
これのどれも標準的なPRワークフローには存在しない。PR説明フィールドは誰も強制しないフリーテキストだ。エージェントのPRはデフォルトで最小限の説明になる。エージェントは実装するよう指示されたのであって、推論を文書化するよう指示されていないからだ。
ギャップはツーリングではない。プロセスだ。レビューインフラストラクチャは存在する。欠けているのは、レビュアーがdiffに対してチェックできるエージェントの意図の構造化された記録だ。
ここに、大きなオーバーヘッドなしにギャップを埋めるパターンがある。3つのパートから成る: エージェントがコードを書く前にplanをコメントし、実装し、完了時に構造化されたdoneレポートをコメントする。
ステップ1: Planコメント
エージェントがファイルを開く前に、何をするつもりかを書き出す。番号付きのステップ、触れるファイル、取るアプローチ。
PLAN: Fix WebSocket reconnection dropping messages during
server restart.
1. Add a message buffer to hooks/use-websocket.ts that queues
outbound messages while the connection is in CONNECTING state
2. On successful reconnection, flush the buffer in order
3. Add a 30-second timeout: if reconnection hasn't succeeded,
surface an error to the user via the toast system
4. Update the existing reconnection test to verify buffer
behavior
Files: hooks/use-websocket.ts, components/connection-status.tsx
Test: Unit test for buffer queueing/flushing, manual test by
killing the WS server mid-operation and verifying no messages
are lost on reconnect.
エージェントにかかる時間は約30秒。レビュアーは2分以内に読める。その2分で、diffレビューなら20分かかるであろう問題を発見できる:
- スコープは正しいか? (計画は2ファイルと言っている。diffが6ファイル触れていたら、何かがドリフトした。)
- アプローチは妥当か? (flush-on-reconnect付きのメッセージバッファは合理的だ。サーバーから全状態を再読み込みする提案だったら、先に議論したいだろう。)
- 欠けているステップはないか? (タイムアウトウィンドウ中に送信されたメッセージはどうなる? ドロップするのか、リトライするのか?)
計画は軽量なチェックポイントだ。レビューし、承認し、エージェントは意図の共有理解のもと進む。
ステップ2: 実装
エージェントがコードを書く。ここは何も変わらない。しかし計画があるため、エージェントは場当たり的な決定ではなく、従うべきロードマップを持っている。
ステップ3: Doneコメント
エージェントが完了したら、構造化された完了レポートを投稿する。「完了」ではない。「featureを実装した」ではない。実際に何をしたかの詳細な説明。
DONE: WebSocket reconnection now buffers outbound messages
during CONNECTING state and flushes on successful reconnect.
Changes:
- hooks/use-websocket.ts: Added messageBuffer ref, queue
messages when readyState !== OPEN, flush in FIFO order on
'open' event. Added 30s reconnection timeout.
- components/connection-status.tsx: Shows "Reconnecting..."
badge during buffer state, switches to error toast after
timeout.
QA Verification:
1. Start dev server and WebSocket server
2. Open app, verify connection dot is green
3. Kill WebSocket server process
4. Trigger an action that sends a WS message (e.g., update
a bead status)
5. Restart WebSocket server within 30 seconds
6. Verify: the buffered message is delivered, bead status
updates in the UI
7. Repeat step 3, but wait >30 seconds before restart
8. Verify: error toast appears after timeout
Acceptance criteria:
- [x] Messages sent during reconnection are not lost (step 6)
- [x] Timeout surfaces user-visible error (step 8)
- [x] No behavior change when connection is stable (step 2)
Commit: f4e2a1b
これでレビュアーは必要なものがすべて揃った。計画を読み(意図)、doneコメントを読み(実際に構築されたものと検証方法)、そしてフルコンテキストでdiffを確認する。diffレビューは「これは一体何?」から「エージェントが言ったことと一致しているか確認しよう」に変わる。
エージェント出力のレビューチェックリスト
実装ナラティブがあっても、体系的なアプローチが必要だ。Claude Code出力をレビューする際に使っているチェックリストを紹介する。1回のレビューに5-10分かかり、diffだけでは見逃すカテゴリのバグを発見する。
Specとの整合:
- 実装はspecの各受け入れ基準に対応しているか?
- specが要求した以上の変更はないか?
- doneコメントは各基準を検証ステップにマッピングしているか?
スコープの制限:
- エージェントは計画に記載されたファイルのみを変更したか?
- 追加ファイルに触れた場合、理由は明記されているか?
- タスクの一部ではない「クリーンアップ」変更(リネーム、再フォーマット、リファクタリング)はないか?
テストカバレッジ:
- 新しい動作に対する新しいテストは存在するか?
- テストは本当に正しいものをテストしているか? (エージェントは実装ではなくモックをテストするため自明にパスするテストを書くことがある。)
- エージェントはテストを実行したと主張しているか? 証拠はあるか?
アーキテクチャの一貫性:
- 変更はコードベースの既存パターンに従っているか?
- 既存のものを複製する新しい抽象化はないか?
- エラーハンドリング戦略はプロジェクトの他の部分と一致しているか?
依存関係の認識:
- エージェントが依存関係を追加した場合、正当化されているか?
- 変更は既存の機能を壊していないか? (変更されたモジュールをインポートしているファイルを確認。)
- タスクが他のタスクへの依存関係を持つ場合、それらは解決されているか?
このチェックリストはどのコードレビューツールでも機能する。付箋に印刷するか、PRテンプレートに入れるか、CLAUDE.mdに組み込んでエージェントが測定される基準を知れるようにする。重要なのは個々の項目ではない。「良さそう」の代わりに構造化されたプロトコルを持つことだ。
レビューサーフェスとしてのBeads
plan-comment-doneパターンには住む場所が必要だ。計画とdoneコメントがSlackメッセージ、PR説明、ターミナル出力に散在していると、spec、計画、実装、検証の間のつながりが失われる。
これがbeadsが解決する問題だ。Beadsはオープンソースのgitネイティブissueトラッカーで、各「bead」がタスクのライフサイクル全体を持つ。説明としてのspec、コメントとしてのエージェント計画、コメントとしてのdoneレポート、コメントとしてのQA結果。すべて1つのエンティティに紐付けられ、検索可能で永続的。
bd CLIでのレビューワークフロー:
specでタスクを作成:
bd create --title "Fix WebSocket reconnection message loss" \
--description "## Problem
Messages sent during WebSocket reconnection are silently
dropped...
## Acceptance Criteria
1. Messages queued during CONNECTING state are delivered
on reconnect
2. 30-second timeout surfaces error to user
3. No behavior change when connection is stable" \
--type bug --priority p1
エージェントが作業をクレームし計画を投稿:
bd update bb-f4e2 --claim --actor eng1
bd comments add bb-f4e2 --author eng1 "PLAN: Add message
buffer to WebSocket hook...
1. Queue outbound messages when readyState !== OPEN
2. Flush buffer in FIFO order on 'open' event
3. Add 30s timeout with error toast
4. Update reconnection test
Files: hooks/use-websocket.ts, components/connection-status.tsx"
2分で計画をレビュー:
bd show bb-f4e2 # Spec + planコメントを読む
計画が正しければ、エージェントは進む。正しくなければ、コードが書かれる前に修正をコメントする。
エージェントが完了しdoneレポートを投稿:
bd comments add bb-f4e2 --author eng1 "DONE: WebSocket
reconnection now buffers outbound messages...
QA Verification:
1. Kill WS server, trigger action, restart within 30s...
Acceptance criteria:
- [x] Buffered messages delivered on reconnect
- [x] Timeout error visible
- [x] No regression on stable connection
Commit: f4e2a1b"
bd update bb-f4e2 --status ready_for_qa
QAが独立して検証:
bd show bb-f4e2 # Doneコメントの検証ステップを読む
# 各ステップを実行
bd comments add bb-f4e2 --author qa1 "QA PASS: All 3 criteria
verified. Buffer flushes correctly, timeout fires at 30s,
stable connections unaffected."
レビューの軌跡全体が1か所にある。6か月後、誰かが「なぜWebSocketは再接続中にメッセージをバッファリングするのか?」と聞いた時、答えはbeadにある。specが問題を説明し、計画がアプローチを説明し、doneコメントが何が構築されたかを説明し、QAコメントが機能することを確認する。
ターミナルレビューが限界に達する時
1つのタスクでbd showを実行すればすべてが得られる。しかし複数のエージェントの出力を複数の並行ワークストリームにわたってレビューしている場合、CLIワークフローは線形にスケールする: タスクごとに1回のbd show、レビュー準備ができたものを見るためのbd list、承認が必要な計画ごとに1回のbd show。
ここにBeadboxが収まる。Beadboxはリアルタイムダッシュボードで、ワークスペース内のすべてのタスクを現在のステータス、最新コメント、レビューパイプラインでの位置とともに表示する。どのエージェントが承認を必要とする計画を投稿したか。どれがレビュー準備完了のdoneレポートを投稿したか。どれがまだ進行中か。エージェントがbd CLIでコメントを書きステータスを変更するのに合わせてすべてがライブで更新される。
plan-comment-doneパターンを使うためにBeadboxは不要だ。CLIがワークフロー全体を処理する。しかし5つのエージェントが同時にレビュー可能な出力を生成している場合、各タスクを個別にポーリングする代わりにレビューキューを一目で見られることが、パイプラインを進む速度を変える。
Beadboxはベータ期間中は無料で、その基盤となるbeads CLIはオープンソースだ。
レビューの問題は自然には解決しない
AI生成コードは私たちのレビュー能力より速く増加している。私たちが持つツールは異なるスケールと異なるワークフローのために構築された。GitHub PR、IDEのdiff、洗練された静的解析でさえ、根本的な問題に対処していない。著者の意図を知らずにコードをレビューすることは、意図を知ってレビューすることよりも劇的に困難だということだ。
解決策はより良いdiffツールではない。構造化された意図だ。エージェントが何を計画したか、実際に何をしたか、結果をどう検証したかの記録。plan-comment-doneパターンは大きなオーバーヘッドなしにその記録を提供する。エージェントは計画の作成に30秒。あなたはレビューに2分。エージェントはdoneレポートの作成に60秒。あなたはゼロからではなくフルコンテキストでdiffをレビューする。
持ち帰るべき5つの原則:
-
コードの前に計画を要求する。 30秒のplanコメントが20分のレビューセッションを節約する。計画が間違っていたら、コードが存在する前に修正する。
-
構造化されたdoneレポートを要求する。 「完了」はdoneレポートではない。検証ステップ、受け入れ基準のマッピング、コミットハッシュがdoneレポートだ。
-
diffに対してではなく、specに対してレビューする。 diffは何が変わったかを示す。specは何が変わるべきだったかを示す。クロスリファレンスする。
-
スコープ境界を強制する。 エージェントが計画外のファイルに触れたなら、それはフラグだ。計画外の変更はレビューされていない変更だ。
-
レビューを判断ではなくプロトコルとして扱う。 チェックリストは直感よりも多くのバグを発見する。「良さそう」はレビューではない。
エージェントはさらに速くなり続ける。diffはさらに大きくなり続ける。問題は、あなたのレビュープロセスがそれに追いつくか、それとも600行のdiffに目を細めて最善を祈り続けるかだ。
このようなワークフローを構築しているなら、GitHubでBeadboxにスターを。