同じ PR、2 つの異なるレビュー

私のネットワークにいるある開発者が、CI のコードレビュアーとして Claude Code を設定しました。「Claude に PR をチェックさせればいい」と彼は言いました。「自分が見落とすものも拾ってくれるから。」私は同じ PR を Claude に 2 回かけてみるよう頼みました。

1 回目のレビューは、エラーハンドリングが包括的だと評価しました。2 回目のレビューは不十分だと指摘し、3 つの変更を提案しました。彼は 5 分間画面を見つめ、どちらの Claude が正しいのか考え込みました。

どちらも正しくなかった。どちらも間違っていなかった。 LLM は決定論的なルールを適用しているのではなく、確率的な推測をしていたのです。これが AI を検証に使う根本的な問題です。検証には LLM が持たない 2 つの性質が必要です。決定性 — 同じ入力が常に同じ出力を生む。完全性 — すべての故障モードが毎回捕捉される。

決定性の問題

同じコードを Claude Code に 2 回通すと、2 つの異なるレビューが返ってくることがあります。Temperature、コンテキストウィンドウ、プロンプトの表現 — すべてが分散を生みます。ブレインストーミングでは分散はフィーチャーです。しかし検証では分散はバグです。確率的な品質ゲートの上に信頼できるソフトウェアを構築することはできません。

決定論的チェックにはこの問題がありません。tsc --noEmit は常に同じエラーを出します。同じ設定の ESLint は常に同じ問題をフラグします。これらのツールは確率ではなくルールを適用します。退屈です。だからこそ機能するのです。

O(1) ガードスタック

これが私たちの pre-commit スタックです。すべてのチェックは決定論的。すべてのチェックはミリ秒で実行。どのチェックも、コードを書いたのが人間か Claude Code かを気にしません:

# Pre-commit (< 1 second total)
TypeScript strict mode      # tsc --noEmit
ESLint boundary rules       # module isolation
Import restrictions         # dependency direction
dependency-cruiser          # graph analysis
Unit tests                  # contract verification

# CI (< 30 seconds)
Full test suite
Bundle size checks
Fresh clone launch test

重要な指標は**コードベースのサイズに対して O(1)**であること。型チェックはインクリメンタル。Lint はファイル単位。dependency-cruiser はインポートグラフを線形に分析します。どれも vibe coding で作ったアプリが大きくなっても遅くなりません。Claude Code のレビューはコードベースがコンテキストウィンドウを超えると遅くなり、精度も落ちます。

決定論的チェックが実際に何を捕捉するか

先週、dependency-cruiser があるPR を捕捉しました。screen コンポーネントが barrel ではなく service の実装から直接インポートしていたのです。その PR はコンパイルも通り、テストも通りました。Cursor が自動的にそのインポートを生成していました。しかし、それは私たちのアーキテクチャルールに違反していました:composition root だけが実装をインポートできる。

LLM レビューならこれを捕捉できたかもしれません。あるいは PR が大きく、インポートがファイルの途中に埋もれていたために見逃したかもしれません。dependency-cruiser は絶対に見逃しません。コードを「読んで」いるのではなく、グラフを分析しているからです:

// .dependency-cruiser.cjs
// 8 lines. 100ms. Catches this every time, forever.
{
  name: 'no-deep-service-imports',
  comment: 'Only service barrels are public.',
  severity: 'error',
  from: { pathNot: '^src/services/' },
  to: {
    path: '^src/services/[^/]+/',
    pathNot: '^src/services/[^/]+/index\.ts$',
  }
}

8 行。100 ミリ秒未満。すべての PR。永遠に。Claude Code のレビューはトークンを消費し、秒単位の時間がかかり、見逃す可能性もあります。計算の結果は明白です。

AI が本当に属する場所

私は反 AI ではありません。Cursor と Claude Code を毎日使っています — コードの下書き、アプローチの探索、テストの作成、デバッグに。しかし検証の役割には配置しません。検証は決定論的でなければなりません。退屈でなければなりません。毎回同じでなければなりません。

ガードレールは見えないが避けられないものであるべきです。誰かが実行を忘れても問題を捕捉すべきです。決定論的チェックが与えてくれるのはまさにそれです:眠らず、疲れず、コードベースが成長してもコンテキストを失わないセーフティネット。

Autotomy Expo Starter Pack には TypeScript strict mode、ESLint boundary rules、dependency-cruiser 設定、pre-commit hooks がすべて事前設定済みで同梱されています。生成には Cursor のスピードを。検証にはマシンの精度を。これが本番を壊さない vibe coding のやり方です。