並列に走らせたAIレビューを1本にまとめる統合規則

観点を分けて別々にレビューさせると、3つの出力が手元に残る。そのまま連結すると、同じ行に対する指摘が別の言葉で並ぶ。重複をまとめる規則を書くと別の問題が出る。3観点の出力12件で数えると、同一性を「場所と機構」で判定した規則は2件をまとめた一方で、同じ行に対する逆向きの判定2件も1件に潰し、矛盾の印は0件になった。同一性の鍵に判定を足さないと、まとめる処理が矛盾を消す。

この記事の提案を使うのは、観点を分けて別々に実行し、その結果を1つのレビューとして人に出すときである。

3つの観点を別々に当てる

題材は TypeScript のモジュールで、差分は8ファイル・追加25行・削除16行である。仕込んだ欠陥は場所で数えると7箇所ある。判定基準は20条で、条と行の対応を書かせる記事で使ったものと同じである。

3つの観点パスは検査スクリプトとして書いた。モデルは走らせていない。 スクリプトが出す文は決まった形なので、言い換えの多様さは実際のレビュー出力より小さい。この測定で数えられるのは、同じ場所に別の機構で届いた指摘と、同じ場所に逆の判定を出した指摘を、統合規則がどう扱うかである。言い換えだけが違う重複の量は、この題材からは言えない。

各パスの出力は4つの項目を持つ。場所(ファイル:行)、機構(何が起きているか分類した1語)、判定(違反か整合か)、主張(一文)である。

3観点の出力(2026-10-12 に実行)
観点1 単位と型: 5 件
  [違反] src/checkout.ts:17  (型の潰し)  undefined を返す関数の戻り値を ! で潰している
  [違反] src/checkout.ts:32  (端数処理)  Math.round は四捨五入する。切り捨てが要る
  [違反] src/checkout.ts:32  (端数処理)  小数の係数を掛けたあとの丸めが決まっていない
  [違反] src/quote.ts:5      (単位の不一致)  割引率に整数を渡している
  [整合] tests/checkout.test.ts:14  (期待値の整合)  期待値 7040 は実装の計算結果と一致する

観点2 テストの妥当性: 3 件
  [違反] tests/checkout.test.ts:14  (期待値の整合)  期待値を実装の挙動に合わせて書き換えた疑いがある
  [違反] src/seats.ts:14  (テストの不在)  ship を呼ぶテストが無い
  [違反] src/seats.ts:21  (テストの不在)  reserve を呼ぶテストが無い

観点3 条との照合: 4 件
  [違反] src/checkout.ts:32  (数値リテラル)  条11。税率が実装の数値リテラルで書かれている
  [違反] src/checkout.ts:36  (設定値の未使用)  条14。再試行の回数が実装の定数になっている
  [違反] src/log.ts:7        (利用者を特定できる値)  条8。ログに利用者のメールアドレスを入れている
  [違反] src/seats.ts:22     (残数の確認が無い)  条5。減らす前に残数を確認していない

観点1と観点2が tests/checkout.test.ts:14 について逆のことを言っている。観点1は、期待値の 7040 が実装の計算結果(Math.round(6400 * 1.1))と一致することを確かめて「整合」と判定した。観点2は、その期待値が同じ差分の中で 6400 から書き換えられたことを見て「違反」と判定した。どちらの観察も事実として正しく、結論だけが逆である。

連結すると何が並ぶか

3つを連結すると12件になる。同じ場所に2件以上並んだ箇所は2つだった。

場所 件数 機構
src/checkout.ts:32 3 端数処理、端数処理、数値リテラル
tests/checkout.test.ts:14 2 期待値の整合、期待値の整合

src/checkout.ts:32 の3件は、1行に欠陥が2つ入っているために起きている。return Math.round(applyDiscount(base, coupon.rate) * 1.1); という行は、端数処理が条2に反し、税率が実装の数値リテラルで書かれていて条11に反する。観点1が端数処理を2通りの言い方で挙げ、観点3が数値リテラルとして挙げた。

同一性の規則が矛盾を消す

同一性を「場所と機構が一致したら同じ指摘とみなす」と書いて処理した。

処理2 同一性の規則(場所+機構): 10 件(2 件がまとまった)
  src/checkout.ts:32  (端数処理)  2 件 -> 1 件
  tests/checkout.test.ts:14  (期待値の整合)  2 件 -> 1 件
  この規則のあとに残った矛盾の印: 0 件

2件がまとまって12件が10件になった。1件目のまとまりは意図したものである。観点1が同じ行の端数処理を2通りの言い方で挙げていたので、1件に寄せるのが正しい。

2件目のまとまりが問題である。tests/checkout.test.ts:14 の2件は、場所も機構(期待値の整合)も同じで、判定だけが「整合」と「違反」に分かれていた。場所と機構を鍵にすると、この2件は同じ指摘と判定されて1件に潰れる。残った1件の判定は、先に処理した側のものになる。矛盾を見つけるための材料は、まとめる処理の前に消えている。 統合後に矛盾の印が0件なのは、矛盾が無かったからではなく、矛盾した組が1件にまとめられたからである。

同一性の鍵に判定を足すと、組が保たれる。

処理3 同一性(場所+機構+判定)+矛盾の規則: 11 件(うち矛盾の印 2 件)
  tests/checkout.test.ts:14  [整合] (期待値の整合)  期待値 7040 は実装の計算結果と一致する
  tests/checkout.test.ts:14  [違反] (期待値の整合)  期待値を実装の挙動に合わせて書き換えた疑いがある

11件になり、矛盾の印が2件に付いた。意図したまとまり(端数処理の2件)は残り、矛盾の組は両方残った。仕込んだ欠陥7箇所は、連結の時点でも処理3のあとでも全部残っている。

場所の一致で拾えない重複

場所を鍵にする規則は、行が1行ずれた重複を拾わない。

隣接する行に出た同じ欠陥
  src/seats.ts:21 (テストの不在) と src/seats.ts:22 (残数の確認が無い)
  場所の一致では同一と判定されない組: 1

観点2は reserve のメソッド定義の行(21行目)を指して「テストが無い」と言い、観点3はその中の在庫を書き換える行(22行目)を指して「残数の確認が無い」と言った。同じメソッドについての2件だが、行が違うので別の指摘になる。この組は機構も違うので、まとめるべきでもない。隣接行の扱いを規則に入れるなら、まとめるのではなく「同じ単位(関数、メソッド、クラス)に2件以上ある」という印を付ける形にする。印があれば、人は2件を並べて読める。

成果物

統合パスの指示

3つの出力を渡して、1本にまとめさせる。

統合パスの指示文
## 入力

次に貼る <パスの数> 個の出力を1本にまとめる。各出力は指摘1件につき4項目
(場所・機構・判定・主張)を持つ。新しい指摘を足さない。

## 同一性の判定

場所(ファイルのパスと行番号)と機構と判定の3つがすべて一致する指摘を、
1件にまとめる。まとめた件は主張のうち1つを残し、残した主張の後ろに
`(<元の件数> 件をまとめた)` と書く。

3つのうち1つでも違う指摘は、別の件として残す。とくに、
場所と機構が同じで判定だけが違う指摘は**まとめない**。

## 矛盾の処理

同じ場所に判定の違う指摘が2件以上ある場合、全部残して各件の先頭に `[矛盾]` と付ける。
どちらが正しいかを決めない。決めるための材料(どの行を読めば分かるか)を1行足す。

## 隣接の印

同じファイルで、行番号の差が1以内の指摘が2件以上ある場合、
まとめずに各件の先頭に `[隣接]` と付ける。機構が違っても付ける。

## 出力

まとめた結果を、場所(ファイル名、行番号)の順に並べる。
末尾に次の3行を書く。
- 入力の総件数: <各出力の件数の合計>
- 出力の件数: <まとめた後の件数>
- 矛盾の印が付いた件数: <件数>

末尾の3行が無いと、統合で何件が減ったのかを受け取った側が数え直すことになる。入力の総件数と出力の件数の差が、まとまった件数である。この題材では 12 と 11 で差が1だった。

各パス側に書いておくこと

統合が機能するかどうかは、各パスの出力の形で決まる。機構を自由な文で書かせると、同じ機構が別の語になって同一性の判定が働かない。機構は語の一覧から選ばせる。

各パスの指示文に足す1行
機構は次の語から1つ選んで書く。当てはまる語が無い場合は `その他` と書き、
主張の中で何が起きているかを書く。
<機構の語の一覧。観点をまたいで同じ一覧を使う>

判定も同じで、「違反」と「整合」の2語に限る。整合の判定を出させるかどうかは観点ごとに決まるが、出させないと矛盾が見つからない。この題材で矛盾が見つかったのは、観点1が「確かめた結果、問題は無い」を出力に書いたからである。

統合パスを足すと何を使うか

払うのはパスの数である。観点3つで3パス走らせていたところへ統合パスが1つ増えて4パスになる。統合パスの入力は3つの出力の合計で、この題材では12件ぶんになる。差分と判定基準を渡し直す必要はないので、統合パスの入力は観点パス1つぶんより小さい。

出力項目は増えない。[矛盾] と [隣接] の印は、該当する件にだけ付く。この題材では11件のうち2件に矛盾の印、2件に隣接の印が付いた。末尾の3行は出力全体に1回である。

人の確認時間は、矛盾の印が付いた件について増える。矛盾は統合パスが決めないので、2件を並べて読んで人がどちらかを採る。この題材の矛盾1組は、テストの期待値が実装に追従したものかどうかの判断で、差分の削除行を見れば決まる。1組あたり数十秒である。印が付かなければこの時間は発生しない。

この統合パスを落としていい条件は、観点パスが対象のファイルを分けているときである。 観点ごとに見るファイルが重ならないなら、同じ場所に2件並ぶことがないので連結で足りる。重なるかどうかは、各パスに渡したファイルの集合を取って共通部分を見れば分かる。共通部分が空なら統合パスは要らない。

指示文の点検

並列の結果をまとめるときに確かめる項目
  • 同一性の鍵に、場所と機構だけでなく判定を入れたか
  • 場所と機構が同じで判定が違う指摘を、まとめない指示にしたか
  • 矛盾した組を全部残して印を付ける形にしたか
  • 矛盾について、どちらが正しいかを統合パスに決めさせていないか
  • 矛盾を解くための材料(どの行を読めば分かるか)を書かせているか
  • 行番号の差が1以内の指摘に、隣接の印を付けているか
  • 統合パスに「新しい指摘を足さない」と書いたか
  • 機構を語の一覧から選ばせて、観点をまたいで同じ一覧を使っているか
  • 各観点パスに「整合」の判定も出させているか
  • 末尾に入力の総件数と出力の件数と矛盾の件数を書かせているか
  • 各パスに渡したファイルの集合に共通部分があるかを見たか

おわりに

3観点の出力12件を統合すると、同一性を「場所と機構」で判定した規則は2件をまとめた。1件は同じ行の端数処理を2通りの言い方で挙げたもので、まとめるのが正しい。もう1件は同じ行に対する「整合」と「違反」の組で、まとめると矛盾が消える。統合後に残った矛盾の印は0件だった。

同一性の鍵に判定を足すと11件になり、矛盾の組は両方残った。行が1行ずれた2件は場所の一致では拾えないので、まとめるのではなく隣接の印を付ける。仕込んだ欠陥7箇所は、どちらの処理のあとでも全部残っている。

次に取る行動を1つ挙げる。手元の並列レビューの出力を2つ取り、同じファイルの同じ行を指す指摘を探す。見つかった組の判定が逆だったなら、同一性の鍵に判定を足す理由がその場で立つ。