この記事は、連載「AIの意見を聞かない技術」の第 5 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。
今回は、自動テストや hook を書いていない禁止事項の話です。禁止事項というのは、開発のときに許さないと決めたルールの一覧です。自動テストは書きます。書かないのは、プログラムが真偽を決められない禁止の側です。の一覧は 17 件あります。そのうち、自動テストが見ていると言い切れるのは 4 件で、残りの 13 件は、プログラムに真偽を決められません。その 13 件には、機械検証にしないと決めたことを隣に書くことにしました。
前作「読まない技術」の第 6 回で、ルールを、破られたら落ちる自動テストに置き換えると書きました。今回は、置き換えられなかったほうの禁止の話です。
まず一つだけ、確かめてみてください。
禁止事項を 1 つ選んで、それが今この瞬間に破られていたら、何が起きるかを答えます。
自動テストが落ちるでしょうか。
CI が止まるでしょうか。
誰かが気づくでしょうか。
どれでもなければ、その禁止は書いてあるだけです。
CC BY 4.0
「全 locale に翻訳を追加する」と書いてあったのに、5,525 件欠けていた
の手元の本番プロジェクト——typingtube という、YouTube の音楽動画でタイピング練習ができる Web サービスです——には、こういう行動ルールがありました。「fallback で済ませず、全 locale に翻訳を追加する」。十数言語で提供しているサービスなので、当然の方針です。文面に曖昧さはなく、AI にもずっと渡していました。
自動テストが無かったので、AI に破られていても誰も気づきませんでした。
が数えてみたら、5,525 件の翻訳が欠けていました(2026-08-31 実測)。755 ある翻訳ファイルのうち 241 に欠落があり、ファイルがまるごと無いものも 2 つ。そのうち 31 件は、翻訳が無いとキーの名前がそのまま画面に出る場所でした。
数え直して一番こたえたのは、内訳のほうです。日本語と英語だけ揃っている箇所が多かった。文言を追加するたびに 2 言語で止めた形が、何度も繰り返されていたということです。
ルールが AI に読まれていなかったわけではありません。読まれた上で、翻訳を足すたびに、ルールは負けていました。勝っていたのは、目の前の文言を日本語と英語で通すことです。
翻訳のルールを自動テストにできるかどうかを、はそれまで決めていませんでした。行動ルールには、自動テストがあるかどうかを書く場所がありません。決めていない禁止も、決めた禁止も、そこでは同じ一行です。
翻訳のこの 1 件は、が pre-commit hook に移しました。翻訳のキーが言語ごとに揃っているかどうかは、hook が数えられるからです。
全部を自動テストにしようとして、できませんでした
それなら、全部を自動テストにすればいい。
もそう思って、17 件の禁止事項を一つずつ見ていきました。
自動テストにできたのは 4 件でした。
残った 13 件の 1 つは、キャラクター機能のコスト引き下げ却下です。第 2 回で理由を 3 行書いたのが、これです。コストの数字が変わったことなら、プログラムに数えられます。でも禁止しているのは数字が変わることではなく、引き下げの提案を通すことです。「この経済バランスは意図どおりか」を決めるには、理由まで読む必要があります。初心者がなんとなく設定したキャラクターが、他の人の体験を壊すかどうかです。数字を比べても、そこは決まりません。
決められない禁止は、ほかにもあります。サービスの収益構造の方針、レベルの推薦をしないという判断、SEO のテキストを変えるときに根拠を要求すること。どれも同じで、数えれば決まる状態がありません。
つまり、半分以上の禁止は、人が判定する禁止です。
見終わって一覧に戻ると、もう一つ分かったことがあります。自動テストにできないとが決めた 13 件のうち、そう読める項目と、読めない項目がありました。読めない項目は、が見る前と同じ字面のままです。決めたことは、の頭の中にしかありません。冒頭の翻訳のルールが、決めていないまま 5,525 件を積んだのと、外からは同じに見えます。
理解したことは一文にできます。
自動テストにできない禁止は、そう決めても、書かなければ決めていない禁止と見分けが付かない。だから、自動テストにしないと決めたことを、禁止の隣に書いておく。
なぜ、自動テストにできない禁止は、書かなければ見分けが付かないのか
自動テストや hook が見ているのは、ファイルの中の状態です。翻訳の hook なら、日本語にあるキーが、他の言語のファイルにも有るか無いか。有るか無いかは数えられるので、プログラムが真偽を決められます。
書いてある一行を数えるプログラムはありません。従うかどうかを、AI が決めます。Claude Code のドキュメントは、指示ファイルもメモリも文脈であって、強制される設定ではないと書いています。同じページに、AI は読んで従おうとするが、厳密な遵守の保証は無い、とくに曖昧な指示や矛盾する指示ではそうだ、とあります。届いた一行に従うかどうかは、翻訳を足すその場で、手元にある他の全部と一緒に AI が決めます。一度の判定で負ける確率が小さくても、同じ判定は文言を足すたびに繰り返されます。毎回わずかに負けるものは、誰にも気づかれずに積み上がります。5,525 件は、そうして積み上がった数です。
hook に移すと、その判定が無くなります。hook のドキュメントは、hook が決定論的な制御を与えると書いています。LLM がそれを実行すると選ぶことに頼るのではなく、その動作が必ず起きる、と。翻訳の 1 件は、hook に移した時点で、従うかどうかの話から外れました。
コストの引き下げには、数えれば決まる状態がありません。数字が動いていないことと、引き下げの提案を通していないことは、別のことだからです。禁止を自動テストにするというのは、その禁止を、数えられる状態に置き換えることです。置き換えたあとに守られるのは、元の禁止ではなく、数えている状態のほうです。
第 3 回の色リテラルが、その実例でした。#fff のように色を直に書かない、という禁止に、は hook を置いて件数を数えていました。hook が数えていたのは CSS ファイルだけで、禁止の範囲もそこになりました。数えている状態が禁止の中身と重なっていれば、置き換えてかまいません。翻訳のキーが言語ごとに揃っているかは、重なります。コストの禁止を数字の比較に置き換えると、数字が動かなければ通る、という別の禁止になります。だからは、自動テストにしません。自動テストにしないと決めた禁止は、書いてある一行のまま、AI の判定に残ります。
そして、自動テストにしないと決めたこと自体は、書かなければどこにも残りません。Claude Code のドキュメントは、セッションは独立していて、新しいセッションは前のセッションの会話履歴を持たないと書いています。次のセッションへ渡るのは、ディスクにあるものだけです。が頭の中で決めたことは、次のセッションの AI には届きません。届くのは一覧の字面で、決めた項目と決めていない項目は、同じ字面です。半年後のが見るものも、同じ字面です。
仕組み: 「機械検証」の見出しに、「なし」と理由を書く
第 1 回の 1 枚には、項目ごとに「機械検証」の見出しがあります。自動テストや hook が、その禁止を見ているかどうかを書く場所です。今回書くのは、その中身です。
### NG3 ユーザー投稿物に名前フィールドを持たせない
**結論**: キャラクター・ドット絵・背景などの投稿物は、名前の列を持たない。
識別はビジュアルと設定で行う。
**理由**: (なぜ禁止なのか。ここは第 2 回の主題)
**適用範囲**: 対象 4 モデルへの `name` / `title` / `nickname` 等の列追加を禁止
**関連 memory**: `feedback_no_naming_for_ugc.md`
**機械検証**: あり。`a1_name_field_absence_test.rb` が、その 4 モデルに
該当する列が無いことをスキーマの側から検査する
最後の 1 行です。自動テストがあるものには、どの自動テストかを書きます。自動テストの無いものには、こう書きます。
**機械検証**: なし(推薦 UI の機械検知は実装ドメイン横断のためレビューで担保)
**機械検証**: なし(プロセスルール、レビューで担保)
**機械検証**: なし(条文の書き方なので、追加・改訂時のレビューで見る)
何も書かないことと、「なし」と書くことは違います。何も書いていない項目は、「まだ検討していない」と「検討して、プログラムには無理だと決めた」の見分けが付きません。見分けが付かないと、その項目は永久に宙に浮きます。誰かが思い出したときだけ「そういえば自動テストを書けるのでは」と再検討され、たいてい誰も思い出しません。「なし」と理由を書いた瞬間に、それは決着済みの項目になります。決着済みの項目は、人が見ると決めた項目です。書いてあるだけの禁止ではなくなります。
の一覧は 17 項目で、内訳はこうです。「あり」が 4 件、「なし」が 10 件。自動テストが部分的に見ているだけで、あり/なしを言い切れていないものが 2 件。そして 1 件は、見出しごとありません。
「なし」の 1 件には、テストの名前が書いてあります。第 3 回で扱った、判定軸が 3 つある禁止です。境界を見る自動テストはありますが、その禁止の全部を自動テストが持てているわけではないので、「なし」の側に置いています。
見出しごと無い 1 件は、この回を書きながら数えて初めて気づきました。書く場所を決めると、まだ書けていない所まで見えるようになります。
半分以上が「なし」になるのは、サボった結果ではありません。プログラムに持てるものをプログラムに持たせた結果、残ったのが判断だけになった。その状態を、見出しが見えるようにしているだけです。
「なし」と書くときに、もう一つ決めることがあります。その禁止を残すかどうかです。残す基準は、その失敗が今も再現するかどうかです。「AI がまだこの禁止を必要としているか」で考えると、どれも要りそうに見えて、何も消せません。指示ファイルのドキュメントが、指示を足す条件の最初に挙げているのも、AI が同じ誤りを二度目にしたときです。
機械検証の見出しがあるのは、一覧に載せた項目だけです。冒頭の翻訳のルールのように一覧の外にある禁止は、この見出しに数えられません。一覧へ上げるかどうかを決めているのは、いまもです。
CC BY 4.0 はここまで
読み直さなくなったもの
禁止事項の一覧そのものです。「今どこまでプログラムが持っていて、どこから人間が見ているのか」を知るために全文を読み直す作業が、その見出しを見るだけになりました。項目が増えても、読むのはその 1 行だけです。
CC BY 4.0
注意: 自動テストは「落ちない」方向に壊れる
両側に壊れ方があります。
「あり」は、空回りする方向に壊れます。走査対象をパスの指定でまとめて集める自動テストは、そのパスを打ち間違えると 0 件を走査して、永遠に通ったままになります。「走査対象が N 件以上あった」を必ず併置してください(詳しくは前作の第 6 回に書きました)。
「なし」は、逃げ道になる方向に壊れます。書くのが面倒な自動テストを「なし(レビューで担保)」で流せてしまう。線引きは単純で、プログラムが真偽を決められるかだけです。決められるのに「なし」と書いたなら、それは分類ではなく先送りで、半年後の自分にはもう見分けが付きません。その場で書いてしまうほうが、結局は安上がりでした。
検証手順: 破って、落ちることを確かめる
前提
- 禁止事項の一覧に「機械検証」の見出しを足し、「あり」と書いた項目が 1 つ以上あること
- 作業中の変更が無い状態(
git statusが空)から始めること
所要時間: 10 分
手順
- あなたが、「機械検証: あり」と書いた項目を 1 つ選び、そこに名前が書いてある自動テストを開きます。名前が書かれていないなら、確かめる前にそこが問題です。どの自動テストが見ているのかを、まず項目に書いてください
- あなたが、その禁止への違反をわざと 1 つ作ります。自動テストが走査している範囲の中に書きます
- あなたが、その自動テストを走らせます
合格条件(すべて満たすこと)
- 自動テストが落ちる
- メッセージに、手順 2 であなたが書いた違反の場所が出ている
合格しなかったとき
- 落ちなかった —— その項目の「あり」は嘘です。「機械検証: なし(レビューで担保)」へ直すか、自動テストの走査の範囲を広げるか、どちらかをその場で決めます
- 落ちたが、場所が出ない —— 直す側(AI でも、あなたでも)が、毎回探すことになります。メッセージに違反箇所を含める形へ直します
後始末
- 手順 2 で書いた違反を消し、合格に戻ることと
git diffが空になることを確かめます
破ったのに落ちない自動テストは、無い自動テストより悪いです。
AI は「機械検証: あり」の 1 行を、そこが自動テストに守られている証拠として読み、その禁止を自分で確かめ直しません。この回が置いたのは、一覧を読み直さずに済ませるための 1 行でした。その 1 行が嘘なら、読み直さないという判断のほうが先に壊れています。
CC BY 4.0 はここまで
次回は「AIの確認依頼を断る技術」。AI に「画面を開いて目視で確認してください」と言われても、は開きません。それでも、壊れたまま先へ進むことはありません。
連載「AIの意見を聞かない技術」
- ← 前回: 第 4 回 サブエージェントの入力だけは、確かめろ
- → 次回: 第 6 回 AIの確認依頼を断る技術
- 全回の一覧: 序論 断ったはずの提案が、また来る