---
title: "確かめない技術 第2回：宣言を信じない技術"
author: garplab
publisher: TypingTube
license: CC BY 4.0
license_url: https://creativecommons.org/licenses/by/4.0/
license_scope: 「CC BY 4.0」の印から始まる節（仕組み・検証手順・コード）。印の無い本文は著作権を留保
canonical: https://typing-tube.net/articles/tashikamenai-02-declaration-guard
series: "確かめない技術"
language: ja
---


今回は、宣言の話です。宣言というのは、前回置いた 2 問——何を検証したいのか、なぜ安い手段では済まないのか——を、実行する前に文章で書かせることです。私はそれを置いたあと、書かずに走った実行が何回あったのかを、一度も数えられませんでした。

1 作目「読まない技術」の[第 4 回](https://typing-tube.net/articles/8f9ae8791b448d)で、**読んだかを測らない。書いていなければ動けない状態だけを見る**、と書きました。今回は、その物差しを前回の宣言そのものに当てます。

まず一つだけ、探してみてください。直近のセッションで、AI が撮影やスモークを走らせた実行を 1 つ選びます。その実行の**直前**に、なぜそれが要るのかを書いた文は、どこかに残っているでしょうか。

前回は「撮影の前に Q1（検証したい問題）と Q2（なぜ安い手段で済まないか）を書かせる」仕組みでした。書こうとした瞬間に、ルールを破りかけていることへ AI 自身が気づく——ガードレールとして効いています。残る課題は一点、**宣言の工程を飛ばした実行は素通りする**ことです。素通りは記録に残らないので、何回起きているかも推測のままでした。

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

> **宣言は効く。ただし、書かれたときだけ効く。だから宣言が書かれた形跡を、実行の前提にする。**

## 仕組み: 宣言ファイルの存在を、実行の前提にする

置いたのは PreToolUse hook 1 本です（手元の運用からの簡約版）。

```bash
# 撮影・スモークの入口だけを拾う（SKIP_VISUAL_GUARD=1 は明示の逃げ道として通す）
case "$command" in *SKIP_VISUAL_GUARD=*) exit 0 ;; esac
printf '%s' "$command" | grep -qE 'screenshot\.sh|smoke_[a-z_]+\.js' || exit 0

# 1. 宣言ファイルが無ければ動けない
[ -f tmp/visual_verification.md ] || { guide; exit 2; }

# 2. 禁句は機械的に弾く（機械に決められるのはここだけ）
grep -qE '念のため|とりあえず|確認のため' tmp/visual_verification.md && exit 2

# 3. Q1 / Q2 が 1 文ずつ埋まっているか（空欄・テンプレのままを弾く）
# 4. 鮮度 30 分（前の検証の宣言の使い回しを弾く）
```

線引きは前回の二分法のままです。「単体テストで済むか」の真偽は機械に決められないので、hook は中身の妥当性を測りません。測るのは、判断が実行より先に行われた**形跡**だけ——ファイルの存在、2 問の充足、禁句、鮮度。前回の言い方では、宣言はルールに従う確率を上げる策、hook はルールをなくす策。ここでなくしたのは「宣言を書いてから実行する」の 1 行だけで、宣言型はやめていません。禁句リストが言い換えに抜かれるのも前回に書いたとおりで、ここで塞いだのは一番安易な経路だけです。

## 置き換え先のない禁止は、検証の放棄を生む

作ってみて、止めるだけでは足りないと分かりました。ブロックの瞬間、AI の TODO には「スクリーンショットで確認」が積まれています。実行だけ止めると、タスクが宙に浮くか、**検証ごと放棄されます**。「確認できませんでした」で閉じる作業は、撮影が走るより悪い結末です。

だから案内文に置き換え先を明示しました。「TODO の『撮影で確認』を『grep / 単体テストで確認』に**書き換えて**消化する。本当に画面が要るなら Q1/Q2 を書いて実行し直す」。1 作目 [第 8 回](https://typing-tube.net/articles/f7c9c30f567aee)のパターン——方針は問題の瞬間に差し込まれたときだけ読まれる——の変奏です。禁止を置くなら、止めたその画面に次の一手を書いておく。

## 実測: 導入した日に、2 つ獲れた

**1 件目、自動テストが hook 自身のバグを見つけました。** bash の `${#var}` はロケール次第で文字数ではなく**バイト数**を返します。「見たい」は 3 文字ですが 9 バイト。「8 文字未満は空欄扱い」の判定を、3 文字の手抜き宣言が素通りしていました。空欄チェックの自動テスト項目を書いたから見つかり、文字数は python3 で数える形に直しました。

**2 件目、hook が導入者である私自身を止めました。** 動作を確かめる検証コマンドが、`screenshot.sh` という**文字列を含んでいただけ**で止まる偽陽性です。案内文に書いておいた逃げ道の環境変数で、私は通過できました。ガードレールは必ず誤作動します。大事なのは、誤作動した瞬間に読める場所に出口が書いてあることです。

hook はガードレールであると同時に計測器です。判定のたびに記録ファイル（日時・止めた理由・コマンド先頭 120 字）へ 1 行追記するので、前回まで推測のままだった「宣言なしの実行が何回あるか」を数え始めました。4 日で 22 回この hook を通り、止まったのは 7 回です。**7 回とも、2 件目と同じ偽陽性でした。** コマンド文字列に入口の名前が入っていただけで、撮影の実行は 1 件もありません。うち 2 件は、**宣言ファイルを書くコマンド自身**です。記録に残った理由は「前の宣言の使い回し」ですが、それは判定の名前であって、止めた相手ではありません。宣言なしの撮影も、禁句も、この 4 日では 1 度も来ていません。

## 読まなくなったもの

宣言の中身です。**私は、AI が書いた「なぜ確かめるか」を一度も読んでいません。** hook は中身の妥当性を測らないと決めてあるので、読んで良し悪しを決める役が、どこにもいません。私が読むのは、止まったときに記録へ 1 行増える「いつ・何を止めたか」だけです。それでも、理由を書かずに走った実行はありません——書いていなければ、実行そのものが始まらないからです。

## 注意: 形は見ていますが、中身は見ていません

この hook を通った宣言が、正しい宣言だとは限りません。Q1 と Q2 にそれらしい文が 1 文ずつ入っていて、禁句が無く、30 分以内に書かれていれば通ります。**「単体テストからは呼べない画面なので」と書けば通ります。** それが本当かどうかは、機械には決められません。

塞いでいません。塞ごうとすると、中身の妥当性を判定する側をもう一段作ることになり、そこは結局また誰かの判断になります。ここで手に入れたのは「判断が実行より先に行われた」という形跡だけで、判断そのものの質は、前回のまま AI の側に残っています。

もう一つ。この hook が拾えるのは、**拾う対象として書いた入口の名前だけ**です。⚠️ 手元がまさにそれで、hook に書いたのは内側で動く道具のファイル名でした。私が普段叩いているラッパーのほうは 14 本あって、そちらは置いた日から一度も見られていません。**入口を足したら、hook の側にも足す。** 名前の取り違えも、足し忘れも、何も言わずに通ります。

## 検証手順: 止まるべきものが止まり、通るべきものが通る

**前提**

- この hook を登録し、宣言ファイルの置き場と禁句の一覧を決め終えていること
- 撮影のコマンドが手元にあること

**所要時間**: 20 分（9 項目を一度に自動テストへ書くなら、もう少しかかります）

**手順**

**あなたが**、下の 6 通りを 1 つずつ AI に渡して、hook の反応を控えます。止める側だけを確かめて終わらせないことが、この節の全部です。**止まる 4 つと、通る 2 つを併置します。**

| # | 何を渡すか | 期待する結果 |
|---|---|---|
| 1 | 宣言ファイルを**置かずに**撮影のコマンドを実行させる | 止まる。案内に「Q1 と Q2 を書け」と、**書き換え先**（撮影 → grep / 単体テスト）が出る |
| 2 | Q2 に**禁句だけ**を書いた宣言（「念のため」） | 止まる。**どの語が禁句か**が出る |
| 3 | Q1 か Q2 が**空**の宣言 | 止まる |
| 4 | **古い**宣言（前の作業のものを残したまま） | 止まる。**いつの宣言か**が出る |
| 5 | Q1 と Q2 が具体的に埋まった宣言 | **通る**（撮影が走る） |
| 6 | 案内文に書いておいた逃げ道（環境変数など） | **通る** |

**合格条件**（すべて満たすこと）

- 1〜4 が、**期待する結果のとおりに止まる**
- 5 と 6 が、**止まらずに通る**
- **禁句の判定を hook から 1 行消すと、2 が通ってしまう**（= その項目を見ている自動テストが、消したことに気づく）

**合格しなかったとき**

- **5 と 6 が止まった** —— ⚠️ この hook は「撮影を全部止める道具」になっています。止まることしか確かめない自動テストは、hook がすべてを止める壊れ方を映せません——1 作目 [第 6 回](https://typing-tube.net/articles/569d598941c00f)で見た「落ちたことのない自動テスト」と同じ落とし穴です
- **判定を 1 行消しても、自動テストが気づかない** —— その項目は、置いた日から何も見ていません

**後始末**

- 消した判定の 1 行を戻し、置いた宣言ファイルを片付けます

---

次回は「テストを再実行しない技術」。**私は、確認のためにテストを走らせ直すのをやめました。**それでも、いま通っているかどうかは毎回わかります。
