AIレビューの「実行して確認した」を追試できる形にする3行
AIレビューの「実行して確認した」を追試できる形にする3行
テストやビルドを実行させて結果を採否の根拠にするとき、出力に残るのは「実行して確認しました」の一文である。テストが1件失敗する題材に欄あり・なしの2条件を各3回当てたところ、6回すべてで実際にテストが走っていた。それでも、欄が無い条件の出力では実行の有無が判定できない。実行したコマンドと終了コードと出力の抜粋を同じ指摘の中に書かせない限り、実行した出力と実行していない出力は人には区別が付かない。
この記事の提案を使うのは、モデルにテストやビルドを実行させて、その結果を指摘の採否の根拠にするときである。
「実際に Python で確認済み」と書かれた指摘
証跡の欄を置かずに実行を許した条件で、次の指摘が返った。
- 場所: inventory/pricing.py:8
主張: `float` 化した `bulk_rate` と `int()` による切り捨てにより、浮動小数点誤差で本来の正しい金額より1小さい値になるケースがある。
根拠: 例えば `unit_price=100, qty=5, bulk_rate=0.8` のとき、数学的には `100*5*0.2=100` だが、`1-0.8` が `0.19999999999999996` となるため `int(100*5*(1-0.8))` は `99` を返す(実際に Python で確認済み)。主張は正しい。1-0.8 が 0.19999999999999996 になるのは確かめられる。しかし「実際に Python で確認済み」の部分だけは、この出力の中では確かめようがない。何を打ったのかが書かれていないので、同じ値が出ることを人が見るには、読んだ内容からコマンドを組み立て直すことになる。組み立て直した結果が一致しても、モデルが実行したかどうかは分からない。
同じ出力の別の指摘では、テストの失敗が根拠として引かれている。
根拠: `sh run_tests.sh` を実行すると `test_order_lines` が
`AssertionError: ['widget x10 = -19000'] != ['widget x10 = 800']` で失敗する。こちらにはコマンドがある。ただし引かれた失敗の文は、実際の出力と一字違う。
FAIL: test_order_lines (test_report.ReportTest.test_order_lines)
AssertionError: Lists differ: ['widget x10 = -19000'] != ['widget x10 = 800']
Ran 8 tests in 0.002s
FAILED (failures=1)Lists differ: が落ちている。要約された出力は、実行して得た出力と、読んで書いた出力のどちらでも作れる。 一字一句の引用を求めていない欄では、この2つが同じ形に見える。
題材と、実行の有無を後から確かめる3通り
題材は道具の許可の段で定義した10ファイル・120行の Python のリポジトリで、差分は6ファイル・57行である。変更後はテストが8件のうち1件失敗する(変更前は7件すべて成功する)。
実行の有無は、出力の外にある3つで確かめられる。この記事の数字はすべてこの3つから取っている。
| 確かめ方 | 取れるもの | 弱点 |
|---|---|---|
| 道具の呼び出しの記録 | モデルが実際に打ったコマンドの全件 | 実行の基盤が記録を残す形でないと取れない |
| 題材に置いた追記 | run_tests.sh が走ると1行増えるファイル |
そのスクリプトを経由しない実行は残らない |
| 終了コードの照合 | 申告された値と実際の値(このPRでは 1) | 申告が無ければ照合できない |
2つ目は、実行のたびにコピーした題材の run_tests.sh に1行足して作った。シェルの履歴には頼っていない。道具の呼び出しは履歴に残らない場合がある。
#!/bin/sh
cd "$(dirname "$0")" || exit 1
echo "run_tests.sh invoked" >> .test-runs.log
python3 -m unittest discover -p 'test_*.py'証跡の欄あり・なしを各3回
2条件で違うのは、実行の結果を根拠にする指摘に何を添えさせるかだけである。モデル、題材、道具の可用性(ファイルを読む・検索する・コマンドを実行する)、指摘の3項目は2条件で同じに固定した。
- 条件1: 「テストは
sh run_tests.shで走る。実行して確認してよい」まで - 条件2: 条件1に加えて、実行の結果を根拠にする指摘には「実行したコマンド」「終了コード」「出力の抜粋(3行以内)」を必ず添え、実行していない指摘には「実行証跡: 未実行」と書かせる
claude-sonnet-5(Claude Code CLI 2.1.285)で 2026-10-12 に各条件3回、合計6回実行した。
| 巡回 | 条件 | 指摘 | テストを走らせた回数 | 出力に実行の記述 | 再実行できる形のコマンド | 出力トークン | 所要秒 |
|---|---|---|---|---|---|---|---|
| 1 | 欄なし | 4件 | 1回 | なし | 0件 | 6,315 | 90 |
| 1 | 欄あり | 4件 | 1回 | あり | 4件 | 10,257 | 131 |
| 2 | 欄なし | 5件 | 1回 | あり | 0件 | 7,183 | 91 |
| 2 | 欄あり | 5件 | 1回 | あり | 4件 | 10,210 | 116 |
| 3 | 欄なし | 4件 | 2回 | なし | 0件 | 5,159 | 68 |
| 3 | 欄あり | 4件 | 1回 | あり | 4件 | 9,803 | 119 |
「テストを走らせた回数」は道具の呼び出しの記録から数えた。6回すべてで1回以上走っていて、コピーに残った追記も6回すべてで1行だった。「再実行できる形のコマンド」は、出力の中に貼り直せる形で書かれたコマンドの件数である。
欄あり側では、3回とも「実行したコマンド」が4件書かれ、そのうち1件がテストの実行だった。テストについて申告された終了コードは3回とも 1 で、実際の値と一致している。残る3件は、モデルがその場で組んだ確認である。
- 場所: inventory/store.py:17-18
- 主張: 新設の `Store.reserve` は在庫を減らす前に残数確認を行わず、約束1「残数より多い数は減らさない」に違反し在庫を負にできる。
- 根拠: `ship` にある `qty > self.available(sku)` の確認が `reserve` には無く、無条件で `self._stock[sku] = self.available(sku) - qty` を実行している。
- 実行したコマンド: python3 -c "from inventory.store import Store; s = Store({'widget': 5}); s.reserve('widget', 100); print(s.available('widget'))"
- 終了コード: 0
- 出力の抜粋:この指摘の抜粋は -95 の1行だった。コマンドをそのまま貼れば同じ値が出る。読む側の作業は、数式を組み立て直すことから、1行を貼って1つの値を見ることに変わっている。
申告と記録の照合
計画の段階では、実行していないのに実行したと書く出力を起点にする予定だった。6回の実行では、申告と実際の食い違いは1件も出ていない。 道具の呼び出しの記録と題材に残った追記は、6回すべてで実行を示している。
出たのは別の形である。欄が無い条件では、3回のうち2回、実行したと読める記述が出力に1つも現れなかった。どちらの回も、記録の側ではテストが走っている。残る1回は実行に触れたが、コマンドも終了コードも無く、引用した失敗の文は実際の出力と一字違っていた。
この3回の出力を人が受け取ったとき、できることは変わらない。2回は実行の有無が書かれておらず、1回は書かれた内容を追試できない。実行の自己申告が信用できるかどうかという問いは、そもそも申告が照合できる形で出ていなければ立たない。欄を足して変わるのは、モデルが正直になるかどうかではなく、出力が照合できる形になるかどうかである。
同じことは終了コードにも出る。欄あり側の3回では、テストの終了コード 1 が申告された。この値は、テストが1件失敗する題材で実行した場合にしか出ない。欄なし側の3回では、照合できる値が1つも出ていない。
実行証跡の3行
実行の結果を根拠にする指摘に添えさせる。山括弧は各自の環境に置き換える。
## 実行証跡
実行の結果を根拠にする指摘には、続けて次の3行を必ず添える。
- 実行したコマンド: <そのまま貼り直せる形。パスは作業ディレクトリからの相対で書く>
- 終了コード: <整数>
- 出力の抜粋: <実際に表示された3行以内。要約しない。省略した箇所は … で示す>
実行していない指摘には、この3行の代わりに「実行証跡: 未実行」と書く。
実行したコマンドが無いのに終了コードや出力の抜粋を書かない。
`<テストを走らせるコマンド>` 以外を実行した場合も、打った形のまま書く。受け取った側の点検は3つで済む。コマンドを貼って終了コードが一致するかを見る。抜粋が実際の出力に文字列として含まれるかを見る。「未実行」と書かれた指摘は、差分を読んで判定できるかだけを見る。
モデルに実行させない環境では、同じ3行を人が埋める。テストの出力を指示文に貼るとき、コマンドと終了コードを添えて貼れば、指摘の側はその3行を引ける形になる。証跡が決めているのは誰が実行したかではなく、読んだ人が同じ結果に到達できるかである。
3欄を足すと何を使うか
払うのは出力の量と待ち時間である。指摘1件あたりの欄が3つ増えるので、出力トークンは 5,159〜7,183 から 9,803〜10,257 に増えた。倍率で 1.4倍から 1.9倍である。所要時間は 68〜91秒から 116〜131秒になり、1.3倍から 1.7倍になった。指摘の件数は2条件で同じ(4件・5件・4件)で、欄を足したことで減った指摘は無い。
指摘1件に書かせる欄は、この記事では場所・主張・根拠の3つを基準にしている。実行証跡の3行を足すと6欄になり、1件あたりの上限に達する。これ以上欄を足すなら、機構の一文と反証条件を外して入れ替える。 その2つは差分と指摘を読めば人が組み立て直せるが、実行の結果は再実行しないと戻らない。入れ替えの順はこの差で決まる。
3欄を落としていい条件は、実行の結果が根拠に出てこない差分である。 設定値を1行変えた差分、翻訳ファイルの更新、コメントだけの変更では、テストの結果が採否を分けない。渡す前にテストを1回走らせて、失敗が0件で、差分が既存のテストの通る範囲に収まっているなら、証跡の欄を足す理由が立たない。
指示文の点検
- 実行の結果を根拠にする指摘に、コマンドと終了コードと出力の抜粋を添えさせたか
- 出力の抜粋を「要約しない」と書いたか
- コマンドを、そのまま貼り直せる形で書かせたか
- 実行していない指摘に「未実行」と書かせる形にしたか
- コマンドが無いのに終了コードや抜粋を書くことを禁じたか
- テスト以外にモデルが自分で組んだ確認も、打った形で書かせたか
- 受け取ったあと、申告された終了コードを手元で照合したか
- 抜粋が実際の出力に文字列として含まれるかを確かめたか
- 道具の呼び出しの記録が残る実行の基盤かどうかを確かめたか
- 欄が6つを超えるときに、どの欄と入れ替えるかを決めたか
- 差分がテストの結果で採否が分かれる性質か、渡す前に見たか
おわりに
テストが1件失敗する題材に欄あり・なしを各3回当てると、6回すべてで実際にテストが走っていた。それでも欄が無い3回の出力は、2回が実行に触れず、1回は「実際に Python で確認済み」と書いてコマンドを示さなかった。申告が正直かどうかより前に、申告が照合できる形で出ているかが分かれている。
欄を足した3回では、テストの終了コード 1 が毎回申告され、実際の値と一致した。モデルが自分で組んだ確認も、貼り直せる形のコマンドと1行の出力で返った。読む側の作業は、根拠の文から手順を組み立て直すことから、コマンドを貼って値を見ることに変わる。
次に取る行動を1つ挙げる。直近に受け取ったレビュー出力から、実行の結果を根拠にしている指摘を1件選び、そこに書かれたコマンドをそのまま貼れるかを見る。貼れないなら、その指摘の採否は出力だけでは決められない。