はじめに
テストの重複を検出する静的解析ツールを、2つひとりでメンテナンスしている。Vitest向けの vitest-dedup と、Cucumber/Gherkin向けの cuke-dedup だ。どちらもRustで書かれていて、npmとGitHub Actionで配布している。
PRをレビューしてくれる同僚はいない。それでも、出力が正しいことを前提に使ってくれるユーザーはいる。vitest-dedup のメインルールはデフォルトでエラーとして報告するので、偽陽性はただの「うるさい警告」では済まない。CIに組み込まれていれば、正しいコードを書いている他人のビルドを落とすことになる。
CodexとClaudeがコードの多くを書くようになり、AIによるレビューはおまけではなく開発プロセスの中心になった。全体を通している考え方はひとつだけだ。
開発はひとりでも、検証をひとつの視点に頼る必要はない。
2026年9月から11月までの数字を先に出しておく。2つのリポジトリで PRは571件、マージは548件。CodeRabbitによる200件の行コメントのうち、164件(82%) は、修正が確認されたか、自分が修正内容を返信したものだった。
この記事で扱う流れを1枚にまとめると、こうなる。層を下りるごとに残る問題は減り、最後に自分が判断する。
1. 個人プロジェクトでも、公開するなら基準はチームと同じ
OSSに契約やSLA、法的な保証がつくことは、まずない。それでも道義的な責任はある。ツールを公開する人は役に立ちたいと思っているはずで、動かないものを渡すくらいなら何も渡さないほうがましだ。品質のためのツールが間違った結果を出せば、時間を節約したかった人の時間を奪ってしまう。
ここから、プロセスについての考え方がいくつか出てくる。
- コントリビューターの有無で、プロセスの必要性は変わらない。誰かに価値を届けるのが目的なら、エンジニアリングのプロセスは明文化されているべきだ
- むしろ、コントリビューターのいないプロジェクトほど、プロセスは必要になる。チームなら判断に異を唱え、ズレを直してくれるもう一人がいる。ひとりだと書く人とレビューする人が同じになり、その解決策を選んだときと同じ前提で結果を評価してしまう。その不足を補う手段が、明文化されたプロセスと自動化だ
- AIはこの偏りを減らす助けになる。実装ではペアプログラミングの相手として、レビューではゲートとして、自分の解決策への思い入れを持たない評価をくれる
もうひとつ、明日誰かがプロジェクトに参加してもいいようにプロセスを設計している。自分の記憶に頼る部分を残さないためだ。push前に必ず動かすものはフックに入れ、レビュアーが知るべきことは指示ファイルに書き、PRで証明すべきことはテンプレートに書く。
PRテンプレートでいちばん大事な項目はこれだ。
- [ ] A focused regression test covers the change, and it fails without the change
修正前のコードでも通るテストは、修正の効果を何も証明しない。修正前に失敗し、修正後に通って初めて、そのテストが直したい振る舞いに反応していると言える。ほかの欠陥がないことまでは示せないが、客観的な証拠にはなる。このルールは、レビューコメントから生まれた修正にも同じように適用される。
2. 予防・検出・学習の3原則
予防:決定論的な品質ゲート
vitest-dedup のCIには、たとえば次のようなゲートがある。
| ゲート | 保証すること |
|---|---|
cargo fmt / clippy -D warnings / rustdoc
|
フォーマット、lint、ドキュメントに警告がない |
| Ubuntu・macOS・Windowsでのテスト | パス区切りの問題はWindowsでしか出ないので、あえて入れている |
| Fixture corpus gate | 各フィクスチャが「報告されるべきもの」と「報告されてはいけないもの」を宣言する |
| Recall corpus gate | 再現率の下限と、改善方向にしか動かない既知の見逃しリスト |
| Differential title harness | 生成したテスト名を本物のVitestとバイト単位で比較する |
| 重複率・カバレッジのラチェット | 後退できない指標 |
ほかにもローカルの pre-commit と pre-push、CodeQL、依存関係の監査、週1回のミューテーションテストを回している。
ゲートはトークンの節約にもなる。AIレビューはプロセスの中でもコストの高い工程なので、ゲートを通ったコードにだけ実行する。clippyが数秒で指摘することを、モデルにお金を払って見つけてもらう理由はない。だからツール群はプロジェクトと一緒に育てている。開発中のエージェントとの会話で同じ種類の問題が何度も出てきたら、scripts/check/ のスクリプトかlintルールに落とし込み、トークンを使わずに済ませる。
時間について
このプロセスには目に見えるコストがある。pre-push は最大で20分ほどブロックする。長く感じるが、見るべき指標はそこではない。比べるべきなのは、同じチェックを手で回す時間と、手順が抜け落ちるリスクのほうだ。「小さい変更だから」と人間が飛ばすこともあるし、AIエージェントが飛ばすこともある。
ゲートにしておけば、後者のリスクを防げる。狙いは、品質ゲートを通っていない変更をmainブランチにマージしないことだ。
もちろん、20分の待ち時間は開発のテンポを落とす。エージェントに複数の変更を並行して進めさせているときは、なおさらだ。だからゲートの構成も、ほかのツールと同じく見直しの対象にしている。
補完的な検出:視点の違うレビュアー
これは4章で扱う。
学習:指摘のたびにプロセスを良くする
レビューで何か見つかったら、「どう直すか」の次にもうひとつ問いを立てる。
同じ種類のミスを二度と起こさないにはどうするか。
2つの出口の重みは違う。lintルールや必須のテストは、その性質を表現できる限り、同じ種類の問題を機械的に塞ぐ。レビュアーへの指示は注意を向けるだけで、モデルの解釈に左右されることは変わらない。だから、できる限り左の道を選ぶ。
vitest-dedup の結合テストのうち2つはこうして生まれた。どちらもファイルの冒頭に、どの死角を塞ぐのかが書いてある。
//! The blind spot this file closes: two review rounds found documentation stating things the code
//! did not do (`--fail-on-incomplete` documented as exit 2 while returning 1; a reporter list
//! naming two of four).
tests/docs_contract.rs(2回のレビューで、ドキュメントとコードの食い違いを見つけたのがきっかけ)
//! The blind spot this file closes is adversarial input applied *unevenly*: hostile input was
//! tested thoroughly in the HTML reporter, partially in JSON and JSONL, and not at all on stderr
//! diagnostics or the stdout path echo
tests/adversarial_matrix.rs(悪意のある入力のテストが、出力形式ごとにばらついていたことがきっかけ)
1つ目は、ドキュメントそのものをアサーションに変える。実行時に .md の表を読み込み、実際のバイナリの挙動と比べる。2つ目は、17個の値(悪意のある値15個と対照用2個)を、すべての入力箇所とすべての出力形式に掛け合わせる。どちらのケースも、もうレビューで思い出してもらう必要はない。
右の道を担うCodeRabbitのLearnings
テストにできないものには、CodeRabbitのLearningsがある。コメントへの返信で、指摘が当てはまらない理由やプロジェクトの規約を説明すると、CodeRabbitはその好みを覚える。そして、関係する変更のレビューでそれを使う。Learningsはダッシュボードで確認・編集できる。
つまり、レビューのスレッドへの返信は、レビュアーを育てることにもなる。だから返信には「won't fix」だけでなく理由を書く(7章を参照)。
3. AIは実行役として、明確な制約の中で使う
AIを実行役にするのは手抜きではない。そのぶん、アーキテクチャや検出戦略、ゲートの設計、リリースに何を入れるかといった、エンジニアリングの判断に時間を回せる。ASTを走査するコードはエージェントが書く。そのルールに意味があるかは自分が決める。
実行を制御するために、次のことを守っている。
- 実装したエージェントを最後のゲートにしない。後ろに前章のゲートとレビューがある
- ローカルのレビューは隔離する。Claudeのレビューは読み取り専用のツールと隔離したチェックアウトで動かし、レビュー対象のツリーとパッチIDが変わっていないことを記録する
- ルールを階層で管理し、昇格の道筋を決めておく
| 階層 | ファイル | 用途 |
|---|---|---|
| 個人・ローカル |
AGENTS.override.md(Codex)、CLAUDE.local.md(Claude、git管理外) |
個人のスタイル、ショートカット、実験 |
| リポジトリ | AGENTS.md |
どのエージェントにも、どのコントリビューターにも当てはまるルール |
| 決定論的 | チェック用スクリプト、lintルール、テスト | 解釈なしで検証できるもの |
ルールはまずローカルで始める。やり取りを重ねて役に立つとわかったら AGENTS.md に昇格させる。機械的に表現できるならツールにして、解釈の領域から外す。
AGENTS.md への昇格には、もうひとつ効果がある。CodeRabbitは AGENTS.md や CLAUDE.md をCode Guidelinesとして読み込み、レビューの基準に使う。同じルールが、書く側とレビューする側の両方で効く。
cuke-dedup の AGENTS.md から実例を挙げる。コードのスタイルではなく、エージェントの動かし方についてのルールだ。
Never stream potentially high-volume commands directly to the terminal or tool output. This
includes fuzzers, mutation testing, benchmarks, full quality gates, recursive diagnostics, and
verbose builds.
ファザーやミューテーションテスト、ベンチマークの大量の出力をそのままターミナルに流さない、というルールだ。このあとに時間・メモリ・並列数の上限が具体的に書いてあり、タイムアウト後に子プロセスが残っていないか確認するよう指示している。
4. 3つのレビュアー、3つの視点
4.1 フローの実際
PRを出す前に、ローカルで次の順に回す。
- 決定論的なゲート(
pre-commit・pre-push) - Codexの
/review。指摘がなくなるまで繰り返す - Claudeの
/code-reviewで差分全体を見る - 大きな変更や慎重さが必要な変更では、CodeRabbit CLI(
coderabbit review)も使う
そのあと、GitHubのPRでCodeRabbitがレビューする。
ローカルのレビューを必須にはしていない。すべての変更が全部を通るわけでもない。ただ、大きな変更では効果が大きい。push前に問題が見つかるので、イテレーションの間隔が短くなり、PRもきれいな状態で出せる。こうした変更でCodeRabbitをローカルで回しておくと、PRで言われそうなことの一部を先に拾える。
レビューは逐次的に進む。どの段階も、前の段階で直したあとのコードを見る。これが示すのは運用上の補完性、つまり段階を経るごとにプロセスが良くなっていくことだ。ツールごとの検出能力を比べたいなら、修正前の同じコミットに対して独立したレビューを回す必要がある。
4.2 それぞれが重視するもの
下の表は、各社が公開している仕組みの説明であり、ベンチマークの結果ではない。
| Codex | Claude Code /code-review
|
CodeRabbit(PR) | |
|---|---|---|---|
| 出発点 | ベースブランチとの差分 | 差分とプロジェクトのルール | 差分とリポジトリの依存グラフ |
| 追加のコンテキスト | AGENTS.md |
変更された関数の周辺コード | 変更シンボルの定義元と参照元、履歴上で一緒に変わるファイル、関連issue・PR、linter、Learnings、Code Guidelines |
| 指摘を出す基準 | GitHub上ではP0/P1 | 検証ステップと信頼度80以上 | 指摘ごとの重大度(Major/Minor)とカテゴリ |
| 重視する視点 | 変更の意図と明示的なルール | 規約への準拠と、変更箇所のバグ | リポジトリ全体から見た変更 |
Codexの /review は、ベースブランチ(merge base)との差分、未コミットの変更、または特定のコミットをレビューする。
Claudeの /code-review は、4つのレビュアーを並列に動かす。2つはプロジェクトのルールへの準拠を、1つは差分だけを見てバグを、もう1つは変更された関数の周辺を読んでバグを探す。候補はすべて検証ステップと信頼度フィルタを通ってから出てくる。これは2026年11月時点の実装で、同じコマンド名のまま構成が変わることもある。自分はClaude Opus 5.5で使っている。
CodeRabbitは、レビューのたびに依存グラフを組み立て、3種類のファイルをコンテキストに入れる。差分で使われているシンボルを定義しているファイル、変更されたシンボルを参照しているファイル、使われ方とコミット履歴から関連すると判断したファイルだ。そこに関連issueやPR、Learnings、Code Guidelines、linterの出力が加わる。
4.3 ケース1:ローカルレビューが見落としたもの
vitest-dedup のあるPRで、各テストの「振る舞いのフィンガープリント」に new X() の呼び出しを記録するようにした。別々のオブジェクトを生成する2つのテストを、ほぼ重複として報告しないためだ。差分全体に対するClaudeのローカルレビューでは、正しさに関わるバグは見つからなかった。
PRでは、CodeRabbitがこう指摘した(🟡 Minor、Functional Correctness)。
forの初期化式にコンストラクタがあると、ConstructorScopeはそれを受け入れるが、walk_stmtはその初期化式を飛ばす。テンプレートリテラルの埋め込み式もwalk_exprで同じように抜けている。たとえば、ループの初期化式のnew Store()とnew Cart()だけが違うコールバックは同じ振る舞いトレースになり、誤ったnear-duplicate-testの指摘を受けうる。
新しいルールは、ASTの走査が一度も訪れない位置にあるコンストラクタまで受け入れていた。その結果は、まさにこのPRがなくそうとしていた偽陽性だ。少し珍しい位置で起きるというだけの違いしかない。
修正は同じ日に次のPRでマージした。新しい走査を外すと失敗する回帰テストを2つ付けている。
このケースが面白いのは、指摘の種類だ。変更したコードのバグではなく、システムの2つの部分の食い違いを突いている。何を数えるかを決めるスコープと、何を訪れるかを決めるウォーカーの食い違いだ。CodeRabbitがコンテキストに持ち込むのは、まさにこういう関係だ。
4.4 ケース2:Codexのレビューを2回通ったあとで
別のPRでは、継承元の tsconfig や node_modules がないときに、その原因を報告するようにした。このPRはCodexのレビューを2回通っていた。
CodeRabbitは、importがnpmパッケージかどうかを判定する関数が、チルダ付きのエイリアスやWindowsのパス、URIまでパッケージとして扱っていると指摘した。そのままだと、node_modules のないプロジェクトで、インストールとは無関係なimportまで「インストールされていない」せいにしてしまう。
修正では、~、@/、https:、data:、UNCパスなどを判定から除いた。ひとつだけ限界が残った。パッケージ名と同じ形のエイリアス(@app/missing)は、綴りだけでは本物のパッケージと区別できない。これはドキュメントに書いた。CodeRabbitはスレッドでこの限界を受け入れ、指摘を解決済みにした。
PRでのレビューのいちばん良い形が、これだ。技術的なやり取りが「fixed」で終わるのではなく、修正と、ドキュメント化された限界の両方で終わっている。
5. 公開されている参考例:Vue.jsとNuxt
CodeRabbitを知ったのは、vuejs/core のPRを追っていたときだった。VueやNuxtが自分と同じ戦略をとっているとは限らない。それでも、それぞれのコントリビューションガイドには、この記事の原則とはっきり重なる部分がある。
Vueは、ローカルのゲートで予防している。vuejs/core は simple-git-hooks を使っていて、コミットのたびに変更ファイルの整形、プロジェクト全体の型チェック、コミットメッセージの検証が走る。時間のコストはCIで発覚するのではなく、コミットの時点で払っている。ガイドには、バグ修正にはissueかPR自体での再現を付け、機能追加にはテストケースを付けるよう書かれている。「修正なしでは失敗するテスト」と同じ考え方だ。
Nuxtは逆のトレードオフを選んでいる。素早くコミットできるようにフックは入れず、PRを出す前にlintとテストを通すよう求めている。どちらの道も正しい。ただ、「PRの前に」を確認してくれるメンテナーがいないひとりのプロジェクトでは、自分はゲートを選ぶ。
NuxtのAI利用の方針も参考になる。AIの支援を受けたコントリビューションは歓迎するが、原則が2つある。LLMに自分の代わりに話させないこと、LLMに自分の代わりに考えさせないこと。自分で理解して説明できるものだけを出す。Nuxt UIはさらに、AIアシスタント向けのコントリビューションガイドとして AGENTS.md を公開している。
Vueでは、自動レビューが大規模に運用されている。2026年には、vuejs/core で作られたPRのおよそ88%にCodeRabbitがコメントしている。同じPRの中で、バンドルサイズのレポート、プレビュー用パッケージ、プレビューデプロイ、数十個のCIチェックと並び、マージの判断はメンテナーに残っている。
集計方法
2026年11月8日時点のGitHub検索による概算で、review skippedのPRも含む。
repo:vuejs/core is:pr commenter:coderabbitai[bot] created:>=2026-01-01 → 1,235件
repo:vuejs/core is:pr created:>=2026-01-01 → 1,400件
6. すべての層を通ったものを、人間がレビューする
冒頭の図の最後の層が、ここにあたる。
2つのリポジトリで、PR上のCodeRabbitの指摘は200件あった。
「修正の確認まで進んだ割合」は、CodeRabbitがaddressedとマークしたか、自分が修正内容を返信したもの。残りの多くは、指摘自体は正しいがPR以前からの問題、またはPRの範囲外と確認したもの。
マージを判断する時点では、些細な問題はもう消えていて、ツールに見える範囲の問題も議論が済んでいる。自分に残るのは、メンテナーとしての判断が必要なことだけだ。このPRは正しい問題を解いているか。この設計は1年後も通用するか。増えた複雑さに見合う価値があるか。
7. 実践で得たこと
- 反論するときは、意見ではなく計測で返す。あるとき、CodeRabbitが
if (import.meta /* c */ .vitest)というガードをVitestが認識しないと指摘した。Vitest 5.0.5とNode 24で試すと、ガードは動き、テストも通ったので、コードはそのままにした。理由はスレッドに残っていて、次のレビューのLearningsにもなる - 「正しい指摘」と「このPRの指摘」を分ける。指摘の中には、正しいけれどPR以前からある問題も多かった。そういうものはPRの範囲を広げずに、別のissueやPRにする(ケース1のように)
- 修正前のコードでは失敗する回帰テストを求める。レビューコメントを保証に変えるのはこれだ
- pushはまとめる。CodeRabbitにはプランごとに時間あたりのレビュー数の上限があり、短時間にpushを重ねると次の枠まで待つことがある
試す価値のあるCodeRabbitの設定
2つのリポジトリは、まだデフォルト設定のままだ。それでも上の200件の指摘が出ている。ただ、.coderabbit.yaml を使えば、プロジェクトの規約をレビューの基準にできる。自分とは違う視点を持つよう、レビュアーを調整してもいい。今回のような静的解析ツールなら、自分はこのあたりから始めたい。以下は、まだ適用していない設定案だ。
# .coderabbit.yaml
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
reviews:
profile: assertive # コメント多め。些細なものはゲートが落とす
auto_review:
ignore_usernames:
- "dependabot[bot]" # 依存関係の更新は監査とCIで見ている
auto_pause_after_reviewed_commits: 2 # pushの多いPRで利用枠を節約する
path_filters:
- "!fixtures/**/node_modules/**" # フィクスチャ用にコミットしているスタンドインのパッケージ
- "!Cargo.lock" # cargo-denyと監査で見ている
- "!package-lock.json"
path_instructions:
- path: "src/**"
instructions: |
A false positive fails other people's builds: treat any change that can make a rule
fire on code that is not duplicated as a correctness bug, not a style issue.
- path: "tests/**"
instructions: |
A regression test must fail without the production change. Flag tests that would pass
either way.
- path: "docs/**"
instructions: |
Check every documented flag, exit code and reporter against src/cli.rs.
- path: ".github/workflows/**"
instructions: |
Actions must be pinned to a full commit SHA with a version comment.
knowledge_base:
learnings:
scope: auto
profile はコメントの量を quiet、chill、assertive から選ぶ設定だ。assertive を選ぶ理由は、些細なノイズを決定論的なゲートが先に落としてくれるからだ。ゲートがなければ chill のほうが無難だ。
path_instructions は、どのlinterでも表現できないルールを書く場所だ。上の最初のルールは、コーパス系のゲートが存在する理由を一文にまとめたものだ。auto_pause_after_reviewed_commits は、指定した数のコミットをレビューしたら増分レビューを止める。PRの準備ができたら @coderabbitai review でもう一度レビューを頼めばいい。
新しい指示は、コードと同じように試してから信用する。指摘されるべき変更、指摘されてはいけない安全な反例、無関係な変更の3つを用意すると、検出できているかとノイズが出ていないかを両方確認できる。
これから測りたいこと
この記事の数字が表しているのはフローであって、各ツールの検出能力ではない。それを公平に比べるには、修正前の同じコミットに対してレビューを回し、データを長く貯める必要がある。プロジェクトが育つのに合わせて、この集計は続けている。
おわりに
ひとりのプロジェクトが大企業のプロセスを再現する必要はない。それでもプロセスは要る。時間の使い方、作業の継続性、届けるものの安定性を支えるのはプロセスで、それは自分の記憶やその日の気分に左右されない。
そのプロセスの中で、AIは道具だ。価値があるのは、能力を補い、広げる使い方だ。速く実行し、多くの角度からレビューし、エンジニアリングの判断に注意を回す。判断そのものを置き換える使い方ではない。
CodeRabbitが自分のフローで重要な役割を担っているのは、PRの差分だけでなく、依存関係や過去の変更、プロジェクト固有のルールまで踏まえてレビューするからだ。会話のたびに育つLearningsとパスごとのルールを使えば、自分とは違う視点でコードを見るように調整もできる。それは良いレビュアーがすることで、ひとりのプロジェクトにいちばん欠けていたものだった。
実際にやっていることは、3つの原則にまとめられる。
- 予防する。機械的に検証できることはゲートにする
- 検出する。視点の違うAIレビュアーを組み合わせる。CodeRabbitはリポジトリ全体のコンテキストをPRに持ち込む
- 学習する。大事な指摘は、修正前のコードでは失敗する回帰テストか、次のレビュアーがすでに知っているルールで終わらせる
最後に。「ひとりで作る」は、このプロジェクトの始まり方であって、続け方ではない。vitest-dedup や cuke-dedup が役に立ちそうなら、コントリビューションを歓迎している。コードでも、実際のプロジェクトで使ってみた感想でも、翻訳の手伝いでもうれしい。GitHubでスターを付けてもらえるだけでも、続ける励みになる。詳しくはvitestdedup.devとcukededup.devにある。



