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

今回は、AI から来る目視確認の依頼の話です。確認は、なくなっていません。なくなったのは、私が画面を開いて目で見ることです。AI の作業は毎回、残るのは実機の目視だけです、で終わります。私は、その依頼に応えていません。代わりに置いたのは、合否はプログラムが決めるという一本のスモークテストです。スモークテストというのは、実際のブラウザで主要な操作を最後まで通して、壊れていないかを見るテストのことです。

前作「読まない技術」の第 12 回で、AI が仕上げた作業結果そのものを、読まずに済ませる形を置きました。今回は、その作業結果を画面で確かめるよう頼まれたときの話です。

まず一つだけ、数えてみてください。

直近のセッションで、AI が画面での確認を残して作業を終えた回数です。

そのうち、変更した行に単体テストが付いていたのは、何回でしたか。

CC BY 4.0

AI に確かめ方を任せると、人の目に流れた

私の手元の本番プロジェクト——typingtube という、YouTube の音楽動画でタイピング練習ができる Web サービスです——では、AI の作業の締めは「残るのは実機の目視 2 点だけ」です。AI が次のセッションへ渡すメモにも、「残るのは実画面の目視(人)だけ」と残っています。私が一度も開かなくても、この一言は毎回来ます。

流れる先は、人の目だけではありません。色を変えたとき、AI が向かったのは、画面を撮って画像から色を読むことでした。配色を洗い直した日は、私が先に釘を刺しました。「全パターンスクリーンショットを撮影して画像から配色を抽出して計算し直して点検するなどコストのかかりすぎる対応はしないで、直接カラーコードを使って計算するなどでテストしてください」。

画面の演出を作ったとき、ロジックを確かめていたのはスモークテスト 26 シナリオで、関数の単体テストは一つもありませんでした。26 本が全部通り、実機では演出が動きませんでした。単体テストを 101 件書いてから見直すと、26 本のうち 17 本は、その単体テストが既に見ている論理の確認でした。

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

目視確認の依頼は、AI が自分のトークンを使わない確かめ方で、合否の判断を人に渡している。だから応えず、合否をプログラムが決める形に置き換える。

なぜ、AI は自分のトークンを使わない確かめ方へ流れるのか

Claude Code のドキュメントは、ツールを使うたびに返ってくる情報がループへ戻り、次の判断の材料になると書いています。人が画面を開いて目で見たものは、どのツールも返しません。AI の側で読むものが無いので、人の目に渡した確認は、AI のトークンを 1 つも使いません。

Claude Code のドキュメントは、モデルが各ステップで、考えるかどうかと、どれだけ考えるかを自分で決めるとも書いています。同じページに、考える度合いの設定は、トークンの消費と能力を天秤にかける、とあります。確かめ方を選ぶのも、その一手の一つです。人の目は 0、スモークテスト 1 回は返ってくる数行、変更した行ごとの単体テストは、書く分と走らせる分と読む分です。

Claude Code のベストプラクティスは、こう書いています。Claude は、仕事が終わったように見えたところで止まる。走らせられる自動テストが無ければ、「終わったように見える」が唯一の信号で、あなたが検証のループになる、と。目視確認の依頼は、この形で出てきます。AI の側に、走らせて読める合否がありません。Claude のプロンプトの指針も、自律的な作業が長くなるほど、人の継続的なフィードバック無しに正しさを確かめる必要があると書いています。

Claude Code のドキュメントは、セッションは独立していて、前のセッションの会話履歴を持たないと書いています。私が画面を開いたかどうかは、ディスクに何も残しません。次のセッションの AI に届くのは、前のセッションの AI が書き残した、残るのは目視だけ、のほうです。

ベストプラクティスのページは、走らせる自動テストの中身も書いています。テストスイート、ビルドの終了コード、lint、出力を期待値と比べるスクリプト、設計図と比べたブラウザのスクリーンショット。条件は一つで、AI が会話の中で読める信号を返すことです。AI が自分で走らせ、自分で読み、通るまで直す、と。

仕組み: スモークテストを 1 本書き、合否は DB で決める

変更した行を見る単体テストと、それを壊して確かめる変異は、前作の第 3 回で置いてあります。それでも決まらないものが残ります。ここに置くのがスモークテスト 1 本です。例は、単語帳の対戦です。2 人が同じ部屋に入り、同じ順番で単語を打ちます。「2 人に同じ順番で出題されているか」は、私が画面を目で見ても分かりません。2 人の画面はそれぞれ独立して進むので、表示中の単語は簡単にずれます。実際、最初のスモークテストは 2 つの画面に出ている単語を突き合わせていて、出題順は同じなのに、不一致を返しました。先に画面を開いた側が 1 語目を時間切れで失い、表示が 1 語ずれていたからです。表示は制限時間で先へ進みますが、打った順は変わりません。

スモークテストは、Playwright を Docker で動かし、データを作り、2 人ぶんのブラウザで対戦を最後まで打ち、終わったらデータを消します。

# lib/tasks/dev_smoke.rake(手元のコードを短くしたもの)— スモークテストが終わったあと、画面ではなく DB を見る
orders = VocabularyPlayLog.where(battle_room_id: room.id).map do |log|
  log.entry_play_logs.in_play_order.pluck(:vocabulary_entry_id)
end
puts "entry_order_match=#{orders.uniq.size == 1}"      # 2 人に同じ順で出たか

plan = VocabularyBattleEntryOrder.call(version: record.vocabulary_deck_version,
                                       play_mode: "shuffle", shuffle_seed: record.shuffle_seed)
server_ids = plan.entries.map(&:id)
puts "server_order_match=#{server_ids == orders.first}" # サーバーが決めた順と同じか

結果は「Smoke FAILED (playwright=1 verify=1)」のように、2 つの数で返ります。Playwright が最後まで通ったかと、DB の判定が揃ったか。どちらかが落ちれば失敗です。スモークテストは AI が自分で走らせます。走らせるかどうかも、AI が決めます。この 2 つの数を読むのは、AI です。スモークテストを置くと、AI が流れる先は、人の目からスモークテストへ変わります。この 2 つの数を決めている行は、AI が編集できるファイルの中にあります。DB に何も残らない場面もあります。同じ対戦でもデータを残さないモードが一つあり、そこだけは、いまもスモークテストが画面を見ています。

漏れたものは、本番で踏んだ人から届く

スモークテストが見るのは、書いた場面だけです。書かなかった場面は本番に出ます。そこは私が確かめに行かず、踏んだ人から届く経路で受けています。不具合報告のフォームです。報告は、領域(タイピング / 対戦 / 歌詞表示…)と種別(不具合 / 要望 / 歌詞の間違い)で分類され、未読・既読・対応済みの状態を持ちます。私が見るのは、溜まった一覧だけです。

CC BY 4.0 はここまで

確認しなくなったもの

画面です。ローカル環境も本番環境も、私は開いて回ることをやめました。AI から目視の依頼が来ても、私が返すのはいつもの「次の作業を進めてください」です。代わりに合否を決めているのは、単体テストと変異とスモークテストです。どれもプログラムが決め、読むのは AI で、落ちたら AI が直します。

CC BY 4.0

検証手順: 判定を 1 つ反転して、失敗になることを確かめる

前提

  • スモークテストを 1 本書き、合否をデータで決める判定(この回の例では、対戦の後に DB に残ったデータを見る部分)が入っていること
  • 手元でスモークテストが最後まで通ること

所要時間: 20 分(スモークテストの実行時間を含みます)

手順

  1. あなたが、データを見ている判定を 1 つ反転します(本文の例なら、出題順の一致を見る行を「不一致なら合格」に書き換えます)。反転するのは判定の行だけです。テストの操作の側は触りません
  2. あなたが、スモークテストを走らせます
  3. あなたが、本番の経路も一度だけ確かめます。フォームの送信先をわざと壊し、報告の件数を見ます

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

  • 手順 2 で、結果が失敗になる
  • 手順 2 の失敗が、画面の操作の側ではなく、データの判定の側で立っている(この回の例では playwright=0 verify=1 に当たる形)
  • 手順 3 で、報告が 0 件になる

合格しなかったとき

  • 画面の操作の側だけが失敗している —— あなたが反転した判定は、合否に効いていません
  • 手順 3 で報告が届き続ける —— 壊したつもりの送信先が、実際の経路ではありません

手順 3 が本体です。報告 0 件は「不具合が無い」とも「経路が切れている」とも読めて、数字だけでは区別が付きません。一度壊して 0 になるのを見ておくと、次に 0 が続いたときに何を疑えばよいかが分かります。

後始末

  • 反転した判定の行を戻し、もう一度走らせて合格に戻ることを確かめます
  • 手順 3 で壊した送信先を戻し、報告が届くことを確かめます

CC BY 4.0 はここまで

次回は「AIの話を聞かない技術」。AI が少しズレたことを言っても、私は説明していません。それでも、AI は正しい向きに戻ります。

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