はじめに — 同じPRなのに、レビューの質が全然違う理由
ちょっと想像してほしいんですが、2つのチームがあります。
どちらも同じAIコードレビューツールを使っている。同じLLMモデル。同じPR。なのに、チームAは「AIレビューめっちゃ助かる、人間が見落としてたバグ見つけてくれた」って言ってて、チームBは「的外れすぎて使えない、結局人間がやり直してる」って言ってる。
この差、なんでだと思います?
答えから言います。 「プロンプトの書き方」じゃなくて、「コンテキストの設計」が違う んです。
具体的に見せます。
Before: コンテキストなしのAIレビュー
// PRの差分コードだけを渡した場合のAIレビュー結果
// 指摘1: 「変数名 `d` は意味が不明確です。より説明的な名前にしましょう」
// 指摘2: 「マジックナンバー 30 を定数に切り出してください」
// 指摘3: 「console.log を本番コードに残さないでください」
...うん、間違ってはいない。でも、こんなのはLintツールでできる。わざわざLLMを使う意味がない。
After: コンテキスト設計済みのAIレビュー
// 3層コンテキストを渡した場合のAIレビュー結果
// 指摘1: 「fetchUserProfile() のレスポンスに対する null チェックが
// 抜けています。チームのエラーハンドリング方針(errors.ts の
// AppError クラス)に沿って、Optional チェーンと早期リターンを
// 使うのが適切です」
// 指摘2: 「この変更で UserService に直接 DB クエリが追加されていますが、
// 既存の Repository パターン(UserRepository)を経由すべきです。
// architecture-decisions/ADR-003.md の方針に反しています」
// 指摘3: 「PR の目的が『ユーザープロフィール取得の高速化』ですが、
// N+1 クエリが発生しています。UserRepository.findWithRelations()
// を使えば、1クエリで取得できます」
全然違いますよね。 チームの規約を知っていて、PRの目的を理解していて、既存コードとの整合性まで見ている 。
こういう世界、作れます。しかもそんなに難しくない。
大事なのは「AIにどう聞くか」じゃなくて、 「AIに何を渡すか」を設計すること 。これがコンテキスト設計です。
3層コンテキスト設計フレームワーク
じゃあ具体的にどう設計するのか。
僕がたどり着いたのは、 コンテキストを3つの層に分ける というフレームワークです。なぜ3層かというと、情報の「更新頻度」が違うから。
| 層 | 名前 | 中身 | 更新頻度 |
|---|---|---|---|
| 第1層 | 常設コンテキスト(Persistent) | コーディング規約、アーキテクチャ方針、禁止パターン | 月1回程度 |
| 第2層 | PR コンテキスト | 変更の目的、関連Issue、影響範囲 | PR毎 |
| 第3層 | 差分コンテキスト(Diff) | 差分コード、変更前後のファイル、依存関係 | コミット毎 |
この3層を 「前提条件 → 目的 → 対象」 の順で渡す。LLMは前から順に読んでいくので、まず「このチームのルール」を理解し、次に「今回のPRで何をしたいのか」を把握し、最後に「実際のコードの差分」を評価する。この順序が大事なんです。
第1層: 常設コンテキスト — チームの「当たり前」を言語化する
ここが一番見落とされてる層です。
チームの中で「暗黙の了解」になっているルール、ありますよね。「うちはRepository パターンで統一してる」「エラーハンドリングは AppError クラスを使う」「環境変数は .env じゃなくて AWS Parameter Store から取る」みたいなやつ。
人間のレビュアーは長年の経験でこれを知ってる。でもAIは知らない。 だから的外れな指摘をする 。
解決策はシンプルで、Markdownファイルに書き出すだけです。
<!-- .github/review-context/coding-standards.md -->
# コーディング規約(AIレビュー用)
## アーキテクチャ
- Repository パターンを採用。Service から直接 DB クエリを書かない
- エラーハンドリングは src/lib/errors.ts の AppError を使用
- API レスポンスは src/lib/response.ts の formatResponse() で統一
## 命名規則
- React コンポーネント: PascalCase(UserProfile.tsx)
- ユーティリティ関数: camelCase(formatDate.ts)
- 定数: UPPER_SNAKE_CASE(MAX_RETRY_COUNT)
- DB カラム: snake_case
## 禁止パターン
- any 型の使用(必ず型を定義する)
- console.log の本番コード残留
- Service 層での直接 SQL / MongoDB クエリ
- 環境変数の直接参照(config モジュール経由のみ)
## セキュリティ
- ユーザー入力は必ず Zod でバリデーション
- SQL / NoSQL インジェクション対策: パラメータバインディング必須
- 認証トークンはリクエストヘッダー経由のみ(クエリパラメータ禁止)
これだけで、AIレビューが「このチームの流儀」を理解した上で指摘してくれるようになる。
ポイントは 「なぜそのルールがあるか」まで書く必要はない ということ。AIに必要なのは「何が正しいか」の基準だけ。理由は人間が知っていればいい。
第2層: PR コンテキスト — 「なぜこの変更をするのか」を渡す
PRの差分コードだけ見ても、「なぜこの変更が必要なのか」はわからない。
例えば、パフォーマンス改善のPRなのに「このコードは可読性が低い」と指摘されても的外れですよね。意図的にインライン化して速度を稼いでるかもしれない。
だからPRテンプレートに 「変更の目的」「影響範囲」「テスト方針」 を必須項目として入れて、それをAIレビューのコンテキストに含める。
<!-- .github/PULL_REQUEST_TEMPLATE.md -->
## 変更の目的
<!-- このPRで何を達成したいか。関連Issueがあればリンク -->
## 変更の種類
<!-- feature / bugfix / refactor / performance / security -->
## 影響範囲
<!-- 変更が影響するモジュール・機能を列挙 -->
## テスト方針
<!-- どんなテストで品質を担保するか -->
## AIレビューへの補足
<!-- AIレビューに知っておいてほしい背景情報。
例: 「パフォーマンス優先のため、意図的にN+1を許容している」等 -->
最後の 「AIレビューへの補足」 が地味に重要で、これがあると「意図的な設計判断」をAIが誤って指摘するのを防げます。
第3層: 差分コンテキスト — ツールが自動収集する層
ここは基本的にツールが自動でやってくれます。PRの差分コード、変更されたファイルの全体、依存関係のあるファイル。
ただし1つ注意点があって、 差分だけでなく「変更前後のファイル全体」を渡す こと。差分だけだと、既存コードとの整合性をチェックできない。
CodeRabbitやGitHub Copilot Code Reviewは、リポジトリ全体にアクセスできるので自動的にやってくれます。自作する場合は、GitHub APIで関連ファイルも取得する必要がある。
3層を組み立てるシステムプロンプト
この3層を実際にどう組み立ててLLMに渡すのか。構造はこうなります。
あなたは経験豊富なシニアエンジニアとして、以下のPRをレビューしてください。
## チームのコーディング規約(必ず準拠してください)
{第1層: coding-standards.md の内容}
## PR の目的と背景
{第2層: PR description の内容}
## レビュー対象のコード差分
{第3層: diff の内容}
## レビューの出力形式
以下の形式で、重要度の高い順に指摘してください:
- 🔴 Critical: セキュリティ脆弱性、データ損失リスク
- 🟡 Warning: バグの可能性、パフォーマンス問題
- 🔵 Suggestion: 可読性向上、ベストプラクティス
各指摘には以下を含めてください:
1. 該当箇所(ファイル名と行番号)
2. 問題の説明
3. 具体的な修正案(コード例)
この構造を守るだけで、レビューの質が大きく変わります。
5つのプロンプトパターン — そのまま使えるテンプレート
ここからは、用途別のプロンプトパターンを紹介します。 効果が高い順 にランク付けしてます。全部使う必要はない。自分のチームに刺さるものから1つ試してみてください。
パターン1: 規約準拠チェック(効果: ★★★★★)
一番効果が出やすいパターン。第1層(常設コンテキスト)をフル活用します。
あなたはコードレビュアーです。
以下のチーム規約に準拠しているかをチェックしてください。
# チーム規約
{coding-standards.md の内容}
# コード差分
{diff}
# 出力ルール
- 規約に違反している箇所のみ指摘してください
- 違反していない箇所へのコメントは不要です
- 各指摘に、規約のどの項目に違反しているかを明記してください
- 修正案をコードで示してください
このパターンを試すと、「変数名を分かりやすく」みたいなぼんやりした指摘が消えて、「チーム規約のXXに違反しています」という明確な指摘に変わります。
パターン2: バグ・エッジケース検出(効果: ★★★★☆)
PRの目的を理解した上で、潜在バグを探すパターン。
あなたは品質保証エンジニアです。
以下のPRの目的を理解した上で、バグやエッジケースを検出してください。
# PRの目的
{PR description}
# コード差分
{diff}
# 特に注意してほしい観点
- null / undefined の未処理
- 配列の空チェック漏れ
- 非同期処理のエラーハンドリング漏れ
- 境界値(0, 空文字, 最大値)での挙動
- 既存機能へのリグレッション
# 出力ルール
- 確信度の高い問題のみ指摘してください
- 「念のため確認」レベルの指摘は不要です
- 再現手順または発生条件を必ず付けてください
ポイントは 「確信度の高い問題のみ」 という制約。これがないと、AIは「もしかしたら問題かも」レベルの指摘を大量に出してきて、ノイズになる。
パターン3: パフォーマンス問題検出(効果: ★★★★☆)
N+1問題やメモリリークなど、パフォーマンス観点に特化したパターン。
あなたはパフォーマンスエンジニアです。
以下のコード変更にパフォーマンス上の問題がないかレビューしてください。
# データベース構成の概要
{DB schema の概要、主要テーブルとリレーション}
# コード差分
{diff}
# 特に注意してほしい観点
- N+1 クエリ
- 不要な全件取得(LIMIT なしの SELECT)
- ループ内での DB / API 呼び出し
- 大量データのメモリ上展開
- インデックスが効かないクエリパターン
# 出力ルール
- 問題の影響度(レコード数が増えた時にどう悪化するか)を説明してください
- 改善案をコード例で示してください
パターン4: セキュリティ脆弱性スキャン(効果: ★★★☆☆)
これは大事なんですが、 注意点があります 。
あなたはセキュリティエンジニアです。
以下のコード変更にセキュリティ上の脆弱性がないかレビューしてください。
# セキュリティポリシー
{チームのセキュリティ方針}
# 認証・認可フローの概要
{認証の仕組みの概要}
# コード差分
{diff}
# チェック観点
- インジェクション(SQL, NoSQL, XSS, コマンド)
- 認証・認可の抜け穴
- 機密情報の露出(ログ出力、レスポンスへの混入)
- CSRF / SSRF
- 安全でないデシリアライゼーション
# 出力ルール
- OWASP Top 10 に基づいて分類してください
- 各脆弱性の悪用シナリオを説明してください
- 修正案をコード例で示してください
ただし、正直に言いましょう。 このプロンプトだけでセキュリティ対策が万全になることはない です。LLMのセキュリティスキャンは、SASTツール(Snyk, SonarQube等)の代替にはなりません。あくまで「人間のセキュリティレビューの補助」として使ってください。LLMは既知のパターンには強いですが、 見逃し(偽陰性)のリスクは常にある 。
パターン5: 設計整合性チェック(効果: ★★★☆☆)
3層全てを統合した、最も包括的なパターン。
あなたはテックリードとして、以下のPRをレビューしてください。
コードの品質だけでなく、チームの設計方針との整合性もチェックしてください。
# チームのコーディング規約とアーキテクチャ方針
{第1層: coding-standards.md}
# PRの目的と背景
{第2層: PR description}
# コード差分
{第3層: diff}
# レビュー観点(優先順位順)
1. アーキテクチャ方針との整合性
2. セキュリティ上の問題
3. バグ・エッジケース
4. パフォーマンス
5. 可読性・保守性
# 出力形式
重要度別に分類して出力してください:
- 🔴 Must Fix: マージ前に必ず修正が必要
- 🟡 Should Fix: 修正を推奨(別PRでもOK)
- 🔵 Nice to Have: 改善提案
設定ファイルの実装例 — CI/CD パイプラインへの組み込み
プロンプトパターンがわかったところで、実際にどうCI/CDに組み込むのか。3つの方法を紹介します。
方法1: CodeRabbit の設定( .coderabbit.yaml )
CodeRabbitを使っている場合、 .coderabbit.yaml でレビューの振る舞いを細かく制御できます。
# .coderabbit.yaml
language: ja-JP
reviews:
profile: assertive # 積極的に指摘する
path_instructions:
- path: "src/services/**"
instructions: |
Service層のレビューでは以下を重点的にチェック:
- Repository パターン経由のDB アクセスか(直接クエリ禁止)
- エラーハンドリングは AppError を使っているか
- トランザクション管理は適切か
- path: "src/api/**"
instructions: |
API層のレビューでは以下を重点的にチェック:
- リクエストバリデーション(Zod schema)があるか
- 認証・認可チェックが適切か
- レスポンス形式は formatResponse() を使っているか
- path: "src/components/**"
instructions: |
Reactコンポーネントのレビューでは以下をチェック:
- Props の型定義があるか
- useEffect の依存配列は正しいか
- メモ化(useMemo, useCallback)が適切か
auto_review:
enabled: true
drafts: false # ドラフトPRはスキップ
ポイントは path_instructions 。ディレクトリごとにレビュー観点を変えられるので、第1層(常設コンテキスト)をパスベースで渡せます。
方法2: GitHub Copilot 用の指示ファイル
GitHub Copilot Code Review を使っている場合は、 .github/copilot-instructions.md にレビュー指示を書けます。
<!-- .github/copilot-instructions.md -->
# コードレビューの指示
## 重点チェック項目
1. TypeScript の any 型が使われていないか
2. エラーハンドリングが AppError クラスを使っているか
3. Service 層から直接 DB クエリを発行していないか
4. 環境変数は config モジュール経由で参照しているか
5. ユーザー入力に対する Zod バリデーションがあるか
## レビューしなくてよい項目
- テストファイルの命名規則
- コメントの有無(JSDoc は任意)
- import の並び順(ESLint で自動修正済み)
## コンテキスト
- フレームワーク: Next.js 15 (App Router)
- ORM: Prisma
- DB: MongoDB
- テスト: Vitest + Testing Library
方法3: 自作LLMレビューBot(GitHub Actions + OpenAI API)
既存ツールに依存したくない場合や、完全にカスタマイズしたい場合は自作する方法もあります。最小限のコードで動くものを示します。
# .github/workflows/ai-review.yml
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Get diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > diff.txt
- name: Run AI Review
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_BODY: ${{ github.event.pull_request.body }}
run: node .github/scripts/ai-review.mjs
// .github/scripts/ai-review.mjs
import { readFileSync } from "fs";
import { execSync } from "child_process";
const diff = readFileSync("diff.txt", "utf-8").slice(0, 12000);
const standards = readFileSync(
".github/review-context/coding-standards.md", "utf-8"
);
const prBody = process.env.PR_BODY || "(説明なし)";
const prompt = `あなたはシニアエンジニアです。以下のPRをレビューしてください。
## チーム規約
${standards}
## PRの目的
${prBody}
## コード差分
\`\`\`diff
${diff}
\`\`\`
重要度別(🔴 Must Fix / 🟡 Should Fix / 🔵 Nice to Have)で指摘してください。
各指摘にファイル名・行番号・修正案を含めてください。
問題がなければ「LGTM」とだけ返してください。`;
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: prompt }],
max_tokens: 2000,
}),
});
const data = await res.json();
const review = data.choices[0].message.content;
// PRにコメントとして投稿
execSync(`gh pr comment ${process.env.PR_NUMBER} --body '## 🤖 AI Review\n\n${review.replace(/'/g, "'\\''")}'`);
30行ちょっとで、3層コンテキスト設計を組み込んだAIレビューBotが動きます。もちろんこのまま使うのではなく、チームの規約に合わせてカスタマイズしてください。
人間とAIの役割分担マトリクス
ここが一番伝えたいところかもしれない。
AIにレビューを任せるって言っても、 全部任せたらダメ なんです。AIが得意なこと、人間じゃないと判断できないこと。この線引きができてるチームは強い。
AIが得意な6観点
| 観点 | なぜAIが得意か |
|---|---|
| 構文・型チェック | パターンマッチングの得意分野 |
| 規約準拠 | 明文化されたルールとの照合は正確 |
| パフォーマンス | N+1やメモリリーク等の既知パターンの検出 |
| テスト漏れ | カバレッジの穴を機械的に発見できる |
| 命名の一貫性 | リポジトリ全体の命名パターンと照合できる |
| 重複コードの検出 | 大量のファイルを横断して類似コードを見つける |
人間が必要な4観点
| 観点 | なぜ人間が必要か |
|---|---|
| ビジネスロジックの妥当性 | 「この仕様で本当に正しいか」はドメイン知識が必要 |
| アーキテクチャ判断 | 長期的な保守性・拡張性の判断はチームの文脈依存 |
| UX への影響 | ユーザー体験への影響はコードだけではわからない |
| チーム方針との整合性 | 暗黙知や「今後こうしていきたい」という方向性 |
判断フローの考え方
迷った時の考え方はシンプルです。
「この指摘は、明文化されたルールに基づくか?」
- YES → AIに任せる(規約に書いてあることをチェックするのはAIの仕事)
- NO → 人間が見る(判断が必要なことは人間の仕事)
これ、開発に限った話じゃないんです。あきらパパの哲学で言うと、 「What(何を作るか)と Why(なぜ作るか)は人間が決める。How(どう作るか)はAIに任せる」 。レビューでも同じ構造。「何を基準にレビューするか」は人間が設計する。「その基準に沿ってコードをチェックする」のはAIがやる。
失敗パターンと対策 — AIレビューで事故らないために
正直に言います。AIレビューで事故るパターンも、ちゃんとある。知っておけば防げるので、隠さずに書きます。
失敗1: AIの指摘を鵜呑みにしてリグレッション
シナリオ: AIが「このメソッドは不要です、削除してください」と指摘。確かに今回のPRでは使ってないように見えたので削除したら、別の機能でインポートされていてバグが出た。
対策: AIの「削除」「変更」系の指摘は、 必ず影響範囲を確認してから適用する 。特に「このコードは不要」系は要注意。AIはリポジトリの全ファイルを見ているとは限らない。
# プロンプトに追加するセーフガード
- コード削除を提案する場合は、
他のファイルからの参照がないことを確認した上で指摘してください
- 確認できない場合は「削除可能か要確認」と明記してください
失敗2: セキュリティ脆弱性をAI任せにして本番事故
シナリオ: AIレビューで「セキュリティ問題なし」と出たので安心してマージ。しかし実際にはパストラバーサルの脆弱性があり、本番で悪用された。
対策: AIのセキュリティレビューは 「第一パス」 。最終判断は必ず人間が行う。SASTツール(Snyk, SonarQube等)との併用が必須。特にセキュリティクリティカルな変更(認証・認可・決済・個人情報)は、AIレビューの結果に関わらず、セキュリティ知見のある人間がレビューする。
失敗3: AIレビューに慣れすぎて人間レビューが形骸化
シナリオ: AIが細かく見てくれるので、人間レビュアーが「AI がOK出してるし大丈夫でしょ」と流すようになった。結果、ビジネスロジックの間違いに誰も気づかず、リリース後に発覚。
対策: AIレビューと人間レビューの 責任範囲を明確に分ける 。AIは「規約・構文・パフォーマンス」、人間は「ビジネスロジック・設計判断・UX影響」。人間レビュアーがチェックすべき観点をPRテンプレートにチェックリストとして入れておく。
<!-- PRテンプレートに追加 -->
## 人間レビュアー向けチェックリスト
- [ ] ビジネスロジックは仕様通りか
- [ ] 既存機能へのリグレッションリスクはないか
- [ ] アーキテクチャ方針と整合しているか
- [ ] ユーザー体験に悪影響を与えないか
共通の原則 : AIレビューは「第一パス」。人間レビューは「最終判断」。この棲み分けを崩さないこと。
締め — 明日からできる3ステップ
全部いきなりやる必要はないです。まず1つ。
Step 1: チームのコーディング規約をMarkdownファイルに書き出す
.github/review-context/coding-standards.md を1つ作る。完璧じゃなくていい。「うちのチームではこれが当たり前」を10個書き出すだけで、AIレビューの精度は変わり始めます。
Step 2: PRテンプレートに「変更の目的」と「影響範囲」を必須化する
.github/PULL_REQUEST_TEMPLATE.md に3つの必須項目を追加する。これだけで、AIが「なぜこの変更をしたのか」を理解した上でレビューしてくれるようになる。人間のレビュアーにとっても助かる。一石二鳥です。
Step 3: 1つのプロンプトパターンを試す
パターン1(規約準拠チェック)がおすすめ。一番シンプルで、効果が出やすい。CodeRabbitの path_instructions でもいいし、GitHub Copilotの copilot-instructions.md でもいい。自作Botでもいい。手段は何でもいい。
大事なのは、AIに「良いレビューをして」とお願いすることじゃなくて、 AIが良いレビューをするための「文脈」を設計すること 。
コンテキスト設計って、コードレビューに限った話じゃないと思ってます。AIに何かを任せるとき、結果の質を決めるのは常に「どんな文脈を渡したか」。自分のチームのルール、今回の目的、対象の詳細。この3つを整理して渡す。
それだけで、AIは「的外れな汎用コメントマシン」から「チームの一員としてレビューしてくれる存在」に変わる。
ちょっと考えてみてください。あなたのチームの「暗黙の了解」、いくつ言語化できてますか?
...そこが、コンテキスト設計の出発点です。