AIエージェントに読ませるコーディングルールは、増えるほどルール同士が矛盾し、人間のレビューだけでは遵守を保証できなくなる。ZOZOはMarkdownの自然文ルールをArchUnitの実行可能なテストへ変換し、CIの必須ゲートにした。同じ物差しで自分のCLAUDE.md・AGENTS.mdの運用ルールを仕分けてみたら、姉妹メディアの片方にしかない機械チェックを見つけた。
自然文ルールを実行可能なテストへ変換する際にZOZOが踏んだ具体的な罠と、その解決策。そして自分の運用ルールを「自動組み込み/スクリプトはあるが手動依存/自然文のみ」の3層で仕分けたら、片方のメディアで直した再発防止策が、もう片方にはまだ無いまま残っていたという自己点検の実例。
AIエージェントにコードを書かせる開発では、「こう書いてほしい」というルールをMarkdownやCLAUDE.md的なファイルに積み上げていくのが定番になっている。だがZOZOのテックブログが報告しているのは、そのルールが増えていったときに起きる2つの問題だ。
ひとつは、ルール同士が矛盾したり重複したりすること。もうひとつは、生成AIがコードを吐き出す速度に対して、人間のレビュアーがルール遵守を確認する速度がまったく追いつかないこと。「レビューでちゃんとチェックする」という運用は、ルールの数とコード生成量が少ないうちしか成立しない。
レビュー依存から抜け出すために選ばれたのが、Javaのアーキテクチャテストライブラリ「ArchUnit」だった。
パッケージ間の依存関係やメソッド呼び出しの制約を、テストコードとしてDSLで記述し、ビルド時に検証できるライブラリ。「UseCase層はRepository層に直接依存してはいけない」のような設計ルールを、コメントではなくassertで書ける。
最初の実装は「特定のクラスへの依存」をパッケージ単位・型参照ベースで検出していた。ところがこれだと、生成AIが書いたコードが単にそのクラスをフィールドとして持っているだけのケースまで違反として誤検出してしまう。ルールが本当に禁じたいのは「そのクラスの禁止メソッドを実際に呼び出すこと」であって、型を知っていること自体ではない。検証対象を実際のメソッド呼び出しに絞り込むことで、生成コードを不当に弾かずに済むようになった。自然文の「〜してはいけない」を機械的な条件に落とし込む作業そのものに、こうした解像度のズレが潜む。
ルール文書は2つに分けられた。背景や実装パターンを説明する知識用のファイルと、AIエージェントが読み込む強制基準用のファイル。後者には「@Transactional必須はArchUnitで検証済み。メソッド公開範囲の妥当性はレビュー依存」というように、どこまでが機械的に保証されていて、どこからが人間のレビュー任せかを明記した。そのうえで検証をプルリクエストの必須ステータスチェックにし、失敗したらマージできないようにしている。新しいルールを足すときは、わざと違反するコードでテストが落ちることを確認してから運用に入る。
ここが要点。ArchUnitという道具そのものより、「このルールは機械が保証しているか、人間の目に頼っているか」を1行で言えるようにしたことの方が価値が大きい。保証範囲が言語化されていないルールは、増えれば増えるほど「守られているつもり」が積み重なっていく。
このブログを含む副業ポートフォリオにも、AIエージェント(ClaudeとCodex)に読ませる運用ルール文書AGENTS.mdがあり、「行動原則」として12項目が並んでいる。ZOZOと同じ物差しで、それぞれが機械的に保証されているか、人間(実質はAIエージェント自身)の遵守に頼っているかを仕分けてみた。
投稿の自動化パイプライン自体に検証・修復コードが埋め込まれていて、誰も気にしなくても毎回実行される層。姉妹メディア「SURPLUS」の予約投稿スクリプトpublish_next.ps1は、キューの記事を本番番号へ採番し直した直後にfix_internal_links.py --applyを自動実行し、記事間リンクの不整合を修復してからslug_ledger.jsonに記録を1行足す。これは過去に102記事中53本のリンクが本番404を返していた事故の再発防止策として組み込まれたものだ。
検証スクリプト自体は存在するが、それを「実行してPASSを確認する」かどうかはAIエージェントがルール文書を読んで思い出すかどうかに懸かっている層。FLOW/SURPLUSへの新規記事投入前にcheck_article_accessibility.pyを実行しPASSを確認する規約や、facts.jsonを編集した後にpython -c "import json"でパース確認する規約がこれにあたる。ツールは機械的だが、呼び出し自体は自然文の指示頼みという意味で、A層と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リンク分踏んでいる(本サイトの別記事で扱った事故そのものだ)。にもかかわらず、その再発防止策は片方のメディアにしか横展開されていなかった。
Javaのバイトコード解析ほど厳密な道具がなくても、「自然文ルールを機械的な条件に変換し、実行される場所に埋め込む」という発想自体は、個人開発の軽量なスクリプトでも再現できる。実際、check_article_accessibility.pyは正規表現とHTMLパーサだけで書かれたB層の検証ツールで、ArchUnitのDSLに相当するものは無くても機能している。
今回の点検で分かれ目になったのは、道具の高度さではなく「どこに置いたか」だった。B層のスクリプトはAIエージェントへの指示文の中に置かれていて、指示を読み飛ばせば実行されない。A層のスクリプトはパイプラインのコードそのものに置かれていて、読み飛ばしようがない。同じ検証ロジックでも、置き場所を変えるだけでC層寄りのB層からA層へ引き上げられる。
publish_next.ps1にも、SURPLUS側と同じfix_internal_links.py --applyの呼び出しを追加すれば、この1点はA層に引き上げられる。この記事はその点検結果を書いたところまでで、実装そのものは別作業として残している——検証と実装を同じ勢いで混同しないための切り分けでもある。複数のプロジェクト・複数のAIエージェントにルールを読ませて運用している個人開発者なら、同じ点検が刺さるはずだ。