この記事は、連載「AIの意見を聞かない技術」の第 3 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。

今回は、禁止事項の範囲の話です。禁止事項とは、「この機能は作らない」「この列は足さない」のように、開発でやらないと決めたことを書き、AIに守らせるルールです。禁止事項そのものは、私はいまも作ります。やめたのは、あるケースが禁止に入るかどうかの判定に迷うたびに、禁止事項の一覧へ新しい禁止事項を 1 つ足すことです。前回、私は禁止事項に理由を添えました。それでも私の手元には、何が禁止に入るかが決まらない禁止事項が残っていました。文面は書いてあるのに、範囲が決まっていないのです。範囲が決まっているかどうかは、境界のケース(禁止に入るかどうかの境目にあって、判定に迷うケース)を一つ作ったときに初めて分かります。

まず一つだけ、試してみてください。

禁止事項を 1 つ選んで、「これは禁止に入るのかどうか、自分でも一瞬迷う」というケースを 1 つ作ってみます。

作ることができたでしょうか。

作ることができたなら、次の問いです。AIの作業がそのケースに当たったとき、それが禁止に入るのか入らないのかを、あなたは答えることができますか。

CC BY 4.0

「色リテラルを書かない」と決めて 0 件にしたのに、254 件残っていた

私の手元には、typingtube という本番プロジェクトがあります。YouTube の音楽動画でタイピング練習ができる Web サービスです。このプロジェクトの禁止事項の一つが、「色はテーマの変数で書く。色リテラルを書かない」です。色リテラルとは、#fff のように、色の値をコードに直に書いたものです。このプロジェクトでは、ユーザーが明るいものから暗いものまで 10 のテーマを切り替えることができます。そのため、文字の色を #fff と直に書くと、その文字はどれかのテーマで必ず背景と同じような色になり、読むことができなくなります。この禁止事項は、理由もはっきりしていて、譲ることのできないものです。

4 月に私は、CSS ファイルの中の色リテラルの件数を数える pre-commit hook を置き、1 週間で 1,775 件を 0 件にしました。「達成済」と設計書に書いて、終わったつもりでした。

8 月に私が、hook の数える範囲の外を初めて数えると、色リテラルが 254 件ありました。HTML テンプレートの style="color:#fff" と、JavaScript の el.style.color = "#fff" です。CSS ファイルの中は、0 件のままでした。文字色を直に書いていた箇所のコントラスト比を 10 テーマで測ると、5 つのテーマで、白い面に白い文字が出ているトースト(画面の隅に短く出る通知。コントラスト比 1.00)が見つかりました。

同じ月にAIが書いた計画書には、次の一文があります。

CSS ファイルに色リテラルを増やさないよう、inline style で流す

AIは、色リテラルという禁止事項の語を書いたうえで、色を書く場所を、hook が数える範囲の外にある inline style(style 属性と、JavaScript の .style)へ移していました。

禁止事項の文面は正しく、「色リテラルを書かない」と書いてありました。決まっていなかったのは、style 属性や、JavaScript の .style に書いた色が、禁止事項の「色リテラル」に入るかどうかです。そこを決めないまま、私もAIも作業を進めていました。

「どちらの判定軸で数えるのか」が決まっていなかった

もう一つ、範囲の決まっていない禁止事項があります。このプロジェクトの「歌詞タイピングと単語帳の実績・ミッション・統計は完全に分離する」という禁止事項です。歌詞タイピングと単語帳は、難易度もプレイコストも違います。混ぜて数えると、片方の実績の価値が下がります。この禁止事項も、理由のはっきりしたものです。

9 月に、このプロジェクトに、歌詞タイピングと単語帳に続く 3 つ目の機能として記事が加わったとき、私が答えに迷う問いが一つ出てきました。記事から入って単語帳をプレイしたユーザーを、記事と単語帳のどちらの判定軸(実績・ミッション・統計を数える系統)で数えるのか、という問いです。

ユーザーがどこから入ってきたか(流入元)で数えるなら、記事の判定軸(記事軸)に入ります。ユーザーが何をしたか(行為)で数えるなら、単語帳の判定軸(単語帳軸)に入ります。どちらの数え方も、「完全に分離する」という文面に沿ったものです。禁止事項は正しく書いてあるのに、このプレイがどちらの判定軸に入るかは、文面からは決まりません。

私が決めた境界は、次の 1 行です。

判定軸は行為で決まる(打った = 単語帳 / 読んだ = 記事)のであって、どこから来たかでは決まらない。流入元は導線クリックとして記事軸が別に数える。

「色リテラルを書かない」という禁止事項どおりに CSS ファイルの中を 0 件にしたのに、AIは、同じ色を style 属性へ移していました。このとき、あなたなら次のどちらを選びますか。

  • 禁止事項の文面を、もっと厳密な言い方に書き直す
  • 禁止事項の適用範囲に、style 属性の色が入るかどうかを、境界のケースとして 1 行書く

答えは、境界のケースを書くほうです。AIが色を style 属性へ移したのは、禁止事項の一行に書いてあったのが結論だけで、足りない範囲を、AIが手元の材料で補ったからです。手元にあった材料は、hook の件数で、hook が数えていたのは CSS ファイルだけでした。禁止事項の文面は正しかったので、言い方を厳密にしても、style 属性の色が禁止に入るかどうかは決まりません。決まっていなかったのは境界のケースで、そのケースを適用範囲に 1 行書けば、どちらに入るかが決まります。

理解したことは一文にできます。

理由を書いても、何が禁止に入るかは決まらない。決まるのは、境界のケースを一つ作って、そのケースが禁止に入るかどうかを書いたときです。

なぜ、境界のケースを書くまで決まらないのか

計画書に「CSS ファイルに色リテラルを増やさないよう」と書いたAIの手元には、2 つのものがありました。禁止事項の一行と、hook が出す件数です。禁止事項の一行に書いてあるのは、結論だけです。件数のほうは、hook が app/assets/stylesheets の下の CSS ファイルだけを数えたものでした。

禁止事項の一行は、指示ファイルやメモリと同じく、文脈としてAIに届きます。Claude Code のドキュメントは、指示ファイルもメモリも文脈であって、強制される設定ではないと書いています。届いた一行が、いま書いている style 属性に当たるかどうかを判定するのは、そのつどAIです。

禁止事項の一行に書いてあるのが結論だけなら、足りない範囲は、AIがその場で補うことになります。色リテラルの禁止事項では、作業中のAIの手元に、範囲を補う材料がありました。hook が出す件数です。hook が数えているのは CSS ファイルだけで、その件数は 0 件のままでした。AIは、hook が数えている範囲を、そのまま禁止事項の範囲として読みました。

もう一つの、「完全に分離する」と書いた禁止事項には、範囲を補う材料が一つもありませんでした。この文面は、歌詞タイピングと単語帳の 2 つの機能の間のことを決めたものです。3 つ目の機能として記事が加わっても、文面が決めているのは、もとの 2 つの機能の組み合わせだけです。人が読んでも決まらない範囲は、AIが読んでも決まりません。

Anthropic がスキルの書き方について出しているガイダンスは、指示の具体度を、その操作の壊れやすさと振れ幅に合わせよと書いています。このガイダンスは、判断が要る操作を「開けた野原」に、一つの手順しか安全でない操作を「狭い橋」にたとえています。野原には散文の目安を書き、狭い橋には正確なコマンドを書く、という勧めです。禁止事項のうち、何を禁じるかを書く「結論」は、判断が要るので、散文で書いてかまいません。どこまでが禁止に入るかを書く「適用範囲」は、狭い橋のほうです。つまり、禁止事項の同じ 1 項目の中でも、結論と適用範囲とで書き方を変えてよいということです。同じページが例について「望ましい書き方と詳しさの水準は、説明だけよりも、例のほうが Claude に明確に伝わる」と続けているので、適用範囲は、境界のケースを並べて書きます。

仕組み: 禁止事項の適用範囲に、境界のケースを 1 行ずつ書き足す

私の禁止事項の一覧は、1 つのファイルです。一覧の各項目には、結論と理由の下に、「適用範囲」の見出しがあります。今回書くのは、その見出しの中身です。

### NG13 歌詞タイピング・単語帳・記事の実績・ミッション・統計の相互乗り入れ禁止(3 軸)

**適用範囲**:

- 単語帳のプレイを歌詞側の集計テーブルに計上しない(単語帳は独立系統)
- ⚠️ 記事から入った単語帳プレイは**単語帳軸**に計上する(記事軸ではない)。
  軸は行為で決まる(打った = 単語帳 / 読んだ = 記事)のであって、どこから来たかでは決まらない
- ⚠️ 記事軸に実績・ミッションは当面作らない。持つのはアクセス分析だけ。
  ★ 作りたくなったときは**この行を書き換えてから**作る(黙って足さない)

結論の文面は、結論の見出しに一度だけ書きます。適用範囲に繰り返し書いても、範囲は同じままだからです。明らかに禁止に入るケースと、明らかに禁止の外にあるケースは、適用範囲に書きません。適用範囲に書いても書かなくても、判定は同じだからです。適用範囲に残るのは、私が一瞬迷ったケースだけになります。

適用範囲の 3 つ目の箇条にある「作りたくなったときは」の行は、この禁止事項を解除するときの条件と手順です。解除の条件が書いてあると、AIには、「作る」か「作らない」かの二択のほかに、条件を満たしたかどうかを確かめるという道ができます。第 1 回では、「ユーザー理解が追いつかないと確認できた場合のみ再検討」という解除の条件が添えてある項目に行き当たったAIが、作る前にまず確かめるよう返してきました。

判定軸は、あとから増えます。この禁止事項は 2 つの判定軸で始まり、記事が加わって 3 つの判定軸になりました。判定軸が増えるたびに、新しい境界のケースが出てきます。そのため私は、判定軸を増やす作業と、境界のケースを書いた行(境界の行)を適用範囲に 1 行書き足す作業を、一緒に行います。

境界の行を書いて変わるのは、そのあとの判定です。色リテラルの 254 件のように、すでにコードの中にある違反は、境界の行を書いても、そのまま残ります。

CC BY 4.0 はここまで

相談されなくなったもの

「これは禁止に入りますか」です。以前は、その都度私が判定していました。AIが私に相談してくることもあれば、相談せずに禁止に入る実装をしていることもありました。後者に私が気づくのは、実装が終わった後です。境界の行を書いてからは、迷うケースが先に適用範囲に書いてあるので、AIが私にこの相談をすることはなくなりました。

CC BY 4.0

注意: 判定軸を増やすと、組み合わせが増える

判定軸が 2 つなら境界は 1 本ですが、3 つなら 3 本になります。判定軸を増やすコストは、判定軸の数よりも、判定軸どうしの組み合わせの数に応じて増えます。私の場合、判定軸を 3 つに増やした日には、記事軸そのものを定義するよりも、「記事 × 単語帳」の境界を決めるほうに時間を使いました。

検証手順: 境界を、2 つの向きに 1 度ずつ破る

前提

禁止事項に「適用範囲」を書き、境界の行を 1 つ以上置いておきます。その境界を確かめる自動テストがあり、いまは合格していることも確かめます。始めるのは、作業中の変更が無い状態(git status が空)からです。

所要時間: 20 分

この検証では、禁止に入る実装をわざと 1 行書いて、境界を破ります。境界を越える向きは 2 つあります。本文の例なら、単語帳のプレイが記事の判定軸に入る向きと、記事を読んだ記録が単語帳の判定軸に入る向きです。片方の向きだけを破って自動テストが落ちると、あなたは、その自動テストがもう片方の向きも確かめていると思い込みます。

手順

  1. あなたが、境界の行を 1 つ選びます(本文の例なら「記事から入って単語帳をプレイしたユーザーを、どちらの判定軸で数えるか」)
  2. あなたが、境界を一方の向きに破ります。本文の例なら、記事から入った単語帳のプレイを、記事の判定軸で数える実装をわざと 1 行書きます。あなたが自動テストを走らせ、結果を控えます
  3. あなたが、その 1 行を戻し、境界をもう一方の向きに破ります。本文の例なら、記事を読んだ記録を、単語帳の判定軸で数える実装を 1 行書きます。あなたが自動テストを走らせ、結果を控えます

合格条件(すべて満たすこと)

手順 2 でも手順 3 でも自動テストが落ち、どちらの失敗のメッセージにも、どちらの向きで破ったかが出ていれば合格です。

合格しなかったとき

  • 手順 2 と手順 3 のどちらか一方でしか落ちない —— その自動テストは、落ちたほうの向きしか確かめていません。その自動テストを見つけたことが、この検証の成果です。あなたが、落ちなかったほうの向きを確かめる表明(assert)を、その自動テストに 1 つ足します
  • 手順 2 でも手順 3 でも落ちない —— その自動テストは、選んだ境界を確かめていません

後始末

手順 3 で書いた 1 行を戻し、自動テストが合格に戻ることと、git diff が空になることを確かめます。

私の環境で、この禁止事項を確かめている自動テストには、「どちらの向きにも漏れない」と書いてあります。以前、私が片方の向きだけを確かめて、安心していたことがあるからです。


CC BY 4.0 はここまで

次回は「サブエージェントの入力だけは、確かめろ」。サブエージェントの入力だけは、確かめないととんでもないことになります。

連載「AIの意見を聞かない技術」