# AIにPRレビューさせるなら、何を渡せばいい？ 実験素材

記事：`web/content/blog/ja/ai-code-review-experiment.md`

バグ入りPR 15本（＋規約の実験で4本）を、渡す情報を変えながらClaudeの3モデルにレビューさせた実験の、素材と結果です。

## 中身

| ファイル | 内容 |
|---|---|
| `experiment.zip` | 実験用アプリ、PRの差分・説明文、正解表、実行・採点スクリプト一式（下表） |
| `results/runs.json` | 全566回の実行結果。指摘の全文と、判定（`label`） |
| `results/summary.json` | 本実験・追加実験1の条件別・PR別の集計 |
| `results/conv_summary.json` | 追加実験2（規約）の集計 |
| `make_figures.py` | 記事の図版（`figure-*.svg` と `figure-*.dark.svg`）を作るスクリプト |

`experiment.zip` の中身：

| パス | 内容 |
|---|---|
| `experiment/app/` | 実験用の注文サービス（約250行のPython、標準ライブラリのみ）。`SPEC.md`（仕様書）、`REVIEW.md`（規約）、`tests/` つき |
| `experiment/prs/pr01`〜`pr15/` | PRごとの `diff.patch`（`git diff -U5`）、`title.txt`、`description.txt` |
| `experiment/gold.json` | 正解表。バグの種類、ファイル、行、内容。pr13〜15はバグなし |
| `experiment/conv/` | 追加実験2の素材。`app2/`（`money.py`・`audit.py`・`CHANGELOG.md`・拡張した`REVIEW.md`つき）、`prs2/pr16`〜`pr19/`、PRを作る `make_prs2.py` |
| `experiment/code/` | 実行・採点・集計のスクリプト（下記） |
| `results/` | 上と同じ結果ファイル |

## 実験の構成

| 実験 | 条件 | モデル・回数 | 回数 |
|---|---|---|---|
| 本実験 | A, B, C, D | Haiku 4.5 ×3、Sonnet 5.5 ×2 | 300 |
| 追加実験1 | S, T（Haiku ×3、Sonnet ×2）、Opusの A, C（×2） | | 210 |
| 追加実験2 | C, D（規約を渡すか） | Haiku ×3、Sonnet ×2、Opus 5.5 ×2 | 56 |

追加実験2の最初の28回は、対照PR（pr19）と違反PR（pr17）に、説明と実装の食い違い（説明は「自分の注文だけ」、実装は管理者も通す）という本物の欠陥が入っていたため捨てて、PRを直して作り直した。

### 条件

| 条件 | プロンプトに入れたもの | ツール | 作業フォルダ |
|---|---|---|---|
| A | 差分 | なし（`--tools ""`） | 空 |
| B | A + PRタイトル + PR説明文 | なし | 空 |
| S | B + `SPEC.md` 全文 | なし | 空 |
| T | B + 「リポジトリを読んでよい」 | `Read`, `Grep`, `Glob` | PRのhead一式。`SPEC.md`・`REVIEW.md`は置かない。コード内の「SPEC.md 第N節」も消した |
| C | B + `SPEC.md` 全文 + 「リポジトリを読んでよい」 | `Read`, `Grep`, `Glob` | PRのhead一式（`REVIEW.md`なし） |
| D | C + `REVIEW.md` 全文 | 同上 | 同上 |

追加実験2の C / D は、同じ意味（C = 仕様書+読取、D = C + 規約）で、アプリが `app2/` になる。

共通：system promptは「PRのレビュアー。正確に。根拠のある問題だけ。無ければ空配列」。ユーザープロンプトは、JSON配列（`file, line, severity, issue`）での回答を求める。

```
claude -p --output-format stream-json --verbose --model <haiku|sonnet|opus> \
  --tools "<なし|Read,Grep,Glob>" --system-prompt "…" --setting-sources "" \
  --strict-mcp-config --disable-slash-commands --no-session-persistence --max-turns 25
```

## 採点

- 判定は4種類：`hit`（正解のバグを、正しい仕組みで指摘）、`valid`（正解表にないが妥当）、`fp`（誤指摘）、`partial`（該当箇所の変化に触れるが、壊れ方を言っていない）。
- 候補を機械的に提案（`score.py`：ファイルが合い、行が正解±4行以内で、キーワードが合う）→ 全指摘を目視で確認 → 判定を規則に落とす（`classify.py`）→ 規則が誤った指摘を個別に上書き（`analyze.py` の `OVERRIDE`）。AIによる採点は使っていない。
- pr10（呼び出し元の破壊）の `hit` は、「残り0が偽として扱われ、bool前提の呼び出し元が壊れる」という壊れ方を述べた指摘。呼び出し元を実際に読んだかは問わない（SonnetとOpusは、差分だけでも推論で述べた）。
- 判定者は1人。`runs.json` に全指摘を載せているので、見直せる。

## スクリプトの使い方（概要）

パスは各スクリプトの先頭で定数になっている（作業時は `/tmp/prr-research/experiment` と `/tmp/prr-abl`）。環境に合わせて直す。

```
python3 code/make_prs.py                        # 本実験のPR（pr01〜pr15）とgold.jsonを作る
CONC=6 python3 code/run.py haiku 3 A,B,C,D      # 条件A〜Dの実行
CONC=6 python3 code/run2.py haiku 3 S,T         # 追加実験1（S, T, opusのA/C）
python3 conv/make_prs2.py && CONC=4 python3 code/run3.py haiku 3   # 追加実験2
python3 code/extract.py haiku                   # 形式違反の出力を配列に写す（採点ではない）
python3 code/analyze.py && python3 code/summarize.py              # 判定と集計
python3 code/conv_parse.py && python3 code/conv_summarize.py      # 追加実験2の判定と集計
```

実行環境：Claude Code CLI 2.1.291、macOS、Python 3.12（pytest用のvenv）。`claude -p` はローカルのログイン（APIキーなし）。

## 限界（記事の「この実験の限界」と同じ）

- 約250行のおもちゃのアプリで、バグも分かりやすい。バグの種類ごとにPRは1本。数字は「できる・できない」の目安。
- モデルはClaudeの3つだけ。各製品のレビュー機能そのものではなく、`claude -p` に書いたプロンプトを渡している。
- 説明文・仕様書・規約は、ぼくたち（aachat）が書いた整った文書。
- pr07の「秘密鍵」は、実行時はStripe公式docsの例示キーだった。公開物では、秘密情報の検出に引っかからないよう、ダミー文字列（`sk_live_EXAMPLE_ONLY_NOT_A_REAL_KEY`）に置き換えている（`diff.patch`、`make_prs.py`、`runs.json`）。
- 費用は `total_cost_usd`（list価格換算）。実際の請求ではない。
- `claude -p` の出力には、ツール利用時にHaikuの形式違反（JSON配列でなく散文）が出る。そのため、Haikuの形式違反は別のHaiku呼び出しで「書かれた指摘を配列に写す」変換をした（`extract.py`。判断は足さない）。
- `/code-review` スキル（条件E）の45回は、調査担当エージェントが別に回した。この素材には含まれない。
