← AIバイブコーディングTips 一覧へ AI Agent Rules / Mechanical Verification

ルールは増えるほど、守られなくなる

AIエージェントに読ませるコーディングルールは、増えるほどルール同士が矛盾し、人間のレビューだけでは遵守を保証できなくなる。ZOZOはMarkdownの自然文ルールをArchUnitの実行可能なテストへ変換し、CIの必須ゲートにした。同じ物差しで自分のCLAUDE.md・AGENTS.mdの運用ルールを仕分けてみたら、姉妹メディアの片方にしかない機械チェックを見つけた。

この記事で手に入るもの

自然文ルールを実行可能なテストへ変換する際にZOZOが踏んだ具体的な罠と、その解決策。そして自分の運用ルールを「自動組み込み/スクリプトはあるが手動依存/自然文のみ」の3層で仕分けたら、片方のメディアで直した再発防止策が、もう片方にはまだ無いまま残っていたという自己点検の実例。

01 — 何が起きたか

ルールが増えるほど、レビューが追いつかなくなる

AIエージェントにコードを書かせる開発では、「こう書いてほしい」というルールをMarkdownやCLAUDE.md的なファイルに積み上げていくのが定番になっている。だがZOZOのテックブログが報告しているのは、そのルールが増えていったときに起きる2つの問題だ。

ひとつは、ルール同士が矛盾したり重複したりすること。もうひとつは、生成AIがコードを吐き出す速度に対して、人間のレビュアーがルール遵守を確認する速度がまったく追いつかないこと。「レビューでちゃんとチェックする」という運用は、ルールの数とコード生成量が少ないうちしか成立しない。


02 — ZOZOの解法

自然文のルールを、実行可能なテストに変換する

レビュー依存から抜け出すために選ばれたのが、Javaのアーキテクチャテストライブラリ「ArchUnit」だった。

01

ArchUnitとは何か

パッケージ間の依存関係やメソッド呼び出しの制約を、テストコードとしてDSLで記述し、ビルド時に検証できるライブラリ。「UseCase層はRepository層に直接依存してはいけない」のような設計ルールを、コメントではなくassertで書ける。

02

変換で踏んだ罠——型参照と呼び出しの取り違え

最初の実装は「特定のクラスへの依存」をパッケージ単位・型参照ベースで検出していた。ところがこれだと、生成AIが書いたコードが単にそのクラスをフィールドとして持っているだけのケースまで違反として誤検出してしまう。ルールが本当に禁じたいのは「そのクラスの禁止メソッドを実際に呼び出すこと」であって、型を知っていること自体ではない。検証対象を実際のメソッド呼び出しに絞り込むことで、生成コードを不当に弾かずに済むようになった。自然文の「〜してはいけない」を機械的な条件に落とし込む作業そのものに、こうした解像度のズレが潜む。

03

「保証範囲」を正直に書き、CIの必須ゲートにする

ルール文書は2つに分けられた。背景や実装パターンを説明する知識用のファイルと、AIエージェントが読み込む強制基準用のファイル。後者には「@Transactional必須はArchUnitで検証済み。メソッド公開範囲の妥当性はレビュー依存」というように、どこまでが機械的に保証されていて、どこからが人間のレビュー任せかを明記した。そのうえで検証をプルリクエストの必須ステータスチェックにし、失敗したらマージできないようにしている。新しいルールを足すときは、わざと違反するコードでテストが落ちることを確認してから運用に入る。

ここが要点。ArchUnitという道具そのものより、「このルールは機械が保証しているか、人間の目に頼っているか」を1行で言えるようにしたことの方が価値が大きい。保証範囲が言語化されていないルールは、増えれば増えるほど「守られているつもり」が積み重なっていく。


03 — 自分の運用に当てはめる

このポートフォリオの行動原則を、3層に仕分けてみた

このブログを含む副業ポートフォリオにも、AIエージェント(ClaudeとCodex)に読ませる運用ルール文書AGENTS.mdがあり、「行動原則」として12項目が並んでいる。ZOZOと同じ物差しで、それぞれが機械的に保証されているか、人間(実質はAIエージェント自身)の遵守に頼っているかを仕分けてみた。

A

自動組み込み——エージェントの記憶に依存しない

投稿の自動化パイプライン自体に検証・修復コードが埋め込まれていて、誰も気にしなくても毎回実行される層。姉妹メディア「SURPLUS」の予約投稿スクリプトpublish_next.ps1は、キューの記事を本番番号へ採番し直した直後にfix_internal_links.py --applyを自動実行し、記事間リンクの不整合を修復してからslug_ledger.jsonに記録を1行足す。これは過去に102記事中53本のリンクが本番404を返していた事故の再発防止策として組み込まれたものだ。

B

スクリプトはあるが、実行はルール文書の指示に依存

検証スクリプト自体は存在するが、それを「実行してPASSを確認する」かどうかはAIエージェントがルール文書を読んで思い出すかどうかに懸かっている層。FLOW/SURPLUSへの新規記事投入前にcheck_article_accessibility.pyを実行しPASSを確認する規約や、facts.jsonを編集した後にpython -c "import json"でパース確認する規約がこれにあたる。ツールは機械的だが、呼び出し自体は自然文の指示頼みという意味で、A層とC層の中間にある。

C

自然文のみ——検証コードが存在しない

「publish_next.batはいかなる場合も手動実行しない」「FTP公開・SNS投稿・課金・アカウント設定変更は事前承認を得る」「YouTubeの状態確認は必ず在庫台帳経由で行う」——このポートフォリオで最も踏み外すと被害が大きい規約の多くが、実はこの層にある。守られているかどうかは、AIエージェントがルール文書を正しく読み、正しく思い出せるかにすべて懸かっている。

実際に見つかった見落とし。SURPLUS側のpublish_next.ps1がA層の対策(自動リンク修復)を持っているのを確認したついでに、同じ設計のFLOW側のpublish_next.ps1を読み返してみたら、そこにはfix_internal_links.pyを呼ぶ行が1行も無かった。FLOWも「キュー時点の仮スラッグが投稿後に採番し直されてリンクが壊れる」という、SURPLUSとまったく同じ構造の不具合を過去に31記事・58リンク分踏んでいる(本サイトの別記事で扱った事故そのものだ)。にもかかわらず、その再発防止策は片方のメディアにしか横展開されていなかった。


04 — 個人開発者への翻訳

ArchUnitが無くても、同じ発想は降ろせる

Javaのバイトコード解析ほど厳密な道具がなくても、「自然文ルールを機械的な条件に変換し、実行される場所に埋め込む」という発想自体は、個人開発の軽量なスクリプトでも再現できる。実際、check_article_accessibility.pyは正規表現とHTMLパーサだけで書かれたB層の検証ツールで、ArchUnitのDSLに相当するものは無くても機能している。

今回の点検で分かれ目になったのは、道具の高度さではなく「どこに置いたか」だった。B層のスクリプトはAIエージェントへの指示文の中に置かれていて、指示を読み飛ばせば実行されない。A層のスクリプトはパイプラインのコードそのものに置かれていて、読み飛ばしようがない。同じ検証ロジックでも、置き場所を変えるだけでC層寄りのB層からA層へ引き上げられる。

✓
次にやるべきことは明確だが、まだ手をつけていないFLOW側のpublish_next.ps1にも、SURPLUS側と同じfix_internal_links.py --applyの呼び出しを追加すれば、この1点はA層に引き上げられる。この記事はその点検結果を書いたところまでで、実装そのものは別作業として残している——検証と実装を同じ勢いで混同しないための切り分けでもある。
✓
新しいルールを1つ足すときは、検証できるか一緒に自問するできなければ「機械的チェックの現状:無し、レビュー依存」とルール文書に正直に書いておくだけでも、C層に紛れ込んでいる項目を後から見つけやすくなる。

05 — まとめ

持ち帰れる3点

複数のプロジェクト・複数のAIエージェントにルールを読ませて運用している個人開発者なら、同じ点検が刺さるはずだ。

✓
ルールを「自動組み込み/スクリプトはあるが手動依存/自然文のみ」の3層で仕分ける層が分かるだけで、どのルールが最も脆いかが一目で見える。最も被害の大きい承認ゲート系のルールほどC層に留まりやすい。
✓
「片方で直した再発防止策」は、もう片方に横展開されているか必ず確認する同じ構造の複数プロジェクトを運用していると、片方の事故対応がもう片方に届かないまま残ることがある。実際に自分のポートフォリオで見つかった。
✓
ArchUnitのような専用の道具は必須ではない本質は「自然文を機械的な条件に翻訳し、読み飛ばせない場所に置く」ことで、正規表現ベースの軽量スクリプトでも同じ効果を狙える。