チームの出発点は、多くのチームに共通するものだった。命名規則やレイヤー間の依存関係の制約を Markdown のルール文書として書き、コーディングエージェント(主に Claude Code)に読ませるというものである。ルールが増えるにつれて 2 つの問題が現れた。1 つは、ルール同士が矛盾したり重複したりし始めたことで、数が少ないうちは目で見て気づけるが、増えるにつれて気づけなくなっていく。もう 1 つは、遵守がレビューに依存していたことである。エージェントが生成したコードを毎回目視で確認していると、レビュアーの負荷は際限なく増え、レビューで見逃されたものはそのまま実装に残る。

最初の転換は、手法ではなく前提の転換である。チームはそれまで、人間向けのドキュメントを書くようにルールを書いていた。ルール間で相互参照し、背景説明を加えていたのである。人間の読み手は関連する文書を行き来しながら理解を組み立てるからだ。しかしエージェントは実装やレビューのたびにルールを読み、参照をひとつたどるごとに見落としや誤読の機会が増える。読み手はエージェントであると捉え直し、チームは各ルールが適用に必要なすべてを備えて単体で完結するようにルールを書き直した。著者によれば、懸念していた重複は目立つほどには生じず、人間のレビュアーにとっての文書の読みやすさも変わらなかったという。

2 つ目の転換は 1 つ目から導かれる。ルールの書き方を変えても、ルールが守られているかどうかは依然として分からなかったため、構造として表現できるルールを、ビルド時に実行される ArchUnit のテストに落とし込み始めた。記事はこれが部分的な対応であることを率直に認めている。自然言語のルールがすべて機械的に検査できるわけではなく、この手法でカバーできるのは、パッケージ構造やクラス間の関係の観点で表現できる制約だというのが記事の立場である。

要点

  • 明言されている前提の転換は、エージェント向けのルールは相互参照ではなく単体で完結させるべきだということである。エージェントは実装やレビューのたびにルールを読み直し、参照をひとつたどるごとに誤読の余地が増えるからだ。予想されたコスト、つまり同じ説明が複数のルール文書で繰り返されることは、実際には目立つほど現れなかったと著者は報告している。
  • 機械化の理由として論じられているのは、原理的な正しさではなく負荷である。エージェントは大量のコードを素早く生成するので、違反も同じ速度で生じる。目視で確認していると、レビュアーの負担は量とともに増え、いずれ追いつけなくなる。著者のまとめの主張は、ルールを実行可能な形で表現すれば、検証の速度をコードの生成速度に合わせられるというものである。
  • 実例を通じて、素直なエンコードが誤りになりうる理由が示される。ドメイン層でのリフレクションを禁止するルールを、リフレクションパッケージへの依存の禁止として書いたところ、リフレクション型のフィールドを宣言しているだけのコードまで検出してしまった。その中には、自身ではリフレクション API を一切呼び出さない O/R マッパーの生成コードも含まれていた。チームはこれを、型の参照ではなく実際のメソッド呼び出しを検査する条件に置き換え、リフレクションパッケージの外にあるクラスの動的ルックアップ用メソッドを手作業で列挙し、さらにコンストラクタやフィールド初期化子もカバーされるよう対象範囲をすべてのコード単位に広げた。
  • 同じ方法で変換された他のルールとして、次のものが挙げられている。テストで時刻を固定できるよう、日付・時刻クラスの now() をクロック引数なしで直接呼び出すことを禁止する。特定のインターフェースを実装するレコードのコンパクトコンストラクタで null チェックを必須にする。トランザクション境界の付け忘れを防ぐため、ユースケース層の特定のメソッドにトランザクションアノテーションを必須にする。
  • 記事は、機械化によって生じる三者間の整合性の問題を挙げている。ルール文書、テスト、実際のコードは別々のファイルにあり、互いにずれていく。その具体例が例外の扱いで、ある 1 つのユースケースクラスには分離レベルの引き上げが許可されており、これは文書には例外として、テストには除外条件として、両方に書かなければならない。チームの現時点での対応は、例外を追加する人にテストの除外条件も更新するよう促す注記をルール文書に置くことである。著者は、これは依然として人が読んで対応しなければならないものであり、例外の宣言をコード側に持たせてテストから直接参照できるようにすれば注記は不要になると率直に述べている。これは今後の改善課題として記録されている。
  • 同じ問題に対するもう 1 つの対応は、保証の範囲をルール文書の中に明記することである。ルールファイルには、どの制約がすでにテストで検査されていて、どれがまだレビューに依存しているかが書かれている。その利点として、エージェントも人間のレビュアーも、文書だけを見て機械による保証がどこまで及ぶかを判断できる点が挙げられている。
  • ナレッジベースはルールから切り離された。背景説明と実装パターン(なぜそのアーキテクチャを選んだのか、各レイヤーのパターンは何か)は一方のディレクトリへ、守るべき制約はもう一方のディレクトリへ移された。両方が 1 つの文書に同居していると、どの記述が検証の対象候補なのか判別できないというのがその理由である。著者は、ルールのディレクトリは Claude Code がデフォルトで読み込むものである一方、ナレッジのディレクトリはそのプロジェクト独自の慣習であると記している。また、その結果として生じるルールファイルから対応するナレッジへの参照は、単体完結という前提に反しないとしている。強制される基準はルール側で完結しており、背景は任意の参照だからである。
  • 計画していた展開は断念され、その計画を黙って取り下げるのではなく、理由が報告されている。チームは、自作のマーケットプレイス風の仕組みと管理ツールを使って他のリポジトリにルールを配布するつもりだったが、障害はツールではなく採用にあることが分かった。他のリポジトリのルールを取り入れたチームはほとんどなかったのである。原因として 2 つが挙げられているが、著者はこれが計測によるものではなく、チーム自身の事後的な推測であると明言している。1 つは、リポジトリをまたいで本当に持ち運べるルールが想定より少なかったこと、もう 1 つは、ルールを取り込むと、それをテストや手元のコードと整合させ続けるという継続的なコストも一緒に持ち込むことになることである。
  • その判断を見直す条件として挙げられているのは、共有可能なルールがたまたま増えることではなく、一般化のための材料が揃うことである。つまり、各チームがルールの背景を記録し、それらの記録をチーム間で比較する仕組みがあることだ。著者の述べる教訓は、複数チームの事例が蓄積される前に、ルールを先に一般化してから展開するやり方はうまくいかない、というものである。
  • 検証は単に存在するだけでなく、必須のゲートにされている。チームは、あるルールがビルドの check タスクには組み込まれていたが test には組み込まれておらず、ローカルでテストを実行すると通るのに、違反が CI でしか表面化しないという事例に遭遇した。そこでプッシュ前に check を実行することを標準とし、それをルール文書に書き込んだ。さらに、ルールを検証する CI ジョブを必須のステータスチェックに設定し、違反を含むプルリクエストはマージできないようにした。ルールを追加する際には、意図的に違反するダミークラスを置いて失敗することを確かめ、検査が実際に発動することも確認する。また、マージ前に既存コードの違反がゼロであることも確認する。
  • 締めくくりの立場は、整合性を保つ作業そのものもエージェントに委ねるというものである。チームはエージェントに、ルール文書、テストコード、実際のコードを突き合わせ、矛盾、記述と実装の食い違い、まだレビューに依存している抜けを洗い出させている。ルールに従うエージェントが、そのルールの維持にも協力するわけである。これらは著者による 1 チームの実践の記述であり、前後比較の計測値は報告されていない。

背景

この記事は、このウィキが AI IDE ルール として記録しているものの具体例であり、その考え方を特定の方向へ押し進めている。ルールファイルそのものを成果物として扱うのではなく、ルールファイルを仕様として扱い、その強制可能な部分をテストに落とし込むべきだとしているのである。検証は生成に追いつかなければならないという捉え方は、このウィキの レビューのボトルネック や決定論的な ガードレール に関する資料と並ぶものであり、機械化できるルールとできないルールの区別は、記事が暗黙のままにせず明示している境界である。

著者は、まだ目視で確認している規約にも、ArchUnit に限らない手段で機械的な検証を広げていく意向を示している。