機能を封鎖したら相乗りの検証も死ぬ — 未検証のゼロを画面から追い出す
危険な機能を後から封鎖すると、同じフラグにぶら下がっていた検証処理が道連れで死に、ツールは未検証のゼロを『確認した結果ゼロ』の顔で出し続ける。実例では1週間、12件ぶんの嘘の警報を出していた。フラグ参照の仕分け方、未確認を空集合で表現しない持ち方、原因バケツの合計を不変条件にする数え方、0/1/2 の終了コード設計、修正前後を本番データで並べる検証手順、委譲実装の受け入れ基準まで。
約1.2万トークンの節約 (API料金換算で約18円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「機能を封鎖したら相乗りの検証も死ぬ — 未検証のゼロを画面から追い出す」は、開発プロセスカテゴリのAI指示書(MDファイル)です。危険な機能を後から封鎖すると、同じフラグにぶら下がっていた検証処理が道連れで死に、ツールは未検証のゼロを『確認した結果ゼロ』の顔で出し続ける。実例では1週間、12件ぶんの嘘の警報を出していた。フラグ参照の仕分け方、未確認を空集合で表現しない持ち方、原因バケツの合計を不変条件にする数え方、0/1/2 の終了コード設計、修正前後を本番データで並べる検証手順、委譲実装の受け入れ基準まで。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約1.2万トークン(API料金換算で約18円)・74%のトークンを節約できます。
- カテゴリ
- 開発プロセス
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約1.6万トークン
- この巻物使用時
- 約4,200トークン
- 節約量
- 約1.2万トークン (約18円)
- 更新日
- 2026-09-03
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/md-7d1723de/raw を読み込んで、この指示書どおりに実装して"
中身
機能を封鎖したら「相乗りしていた検証」も死ぬ — 未検証のゼロを画面から追い出す型
危険な機能(本番への書き込み、外部への送信、破壊的な一括処理)を後から封鎖するのは正しい判断だ。 だがそのフラグの下でしか走っていなかった検証処理が、封鎖と同時に道連れで死ぬことがある。
死んだことは画面に出ない。出るのは 未検証のまま計算された 0 で、これは 「調べた結果ゼロだった」と字面で見分けがつかない。ツールは平然と嘘の警報を出し続ける。
この指示書は、その事故の作り方・見つけ方・直し方と、二度と起きない数え方を示す。 言語は任意(例は Node.js)。対象は「本番の状態を読み、異常を数えて報告する」CLI・バッチ全般。
1. 事故の形(実例・1週間気付かれなかった)
在庫の穴(売れる状態になっていない日)を数えて報告する CLI があった。
書き込み機能 --open は、外部 API 側の制約が判明したため後から封鎖された。
if (OPEN) {
console.error('--open は使えません(API 側の制約。管理画面から操作すること)');
process.exit(2); // ← 封鎖。判断としては正しい
}
ところが、ダブルブッキング防止のための実予約の突き合わせが、同じフラグにぶら下がっていた。
// 「開けるときだけ重い照合をすればいい」という、封鎖前は妥当だった最適化
const occupied = OPEN ? await occupiedSet() : new Set();
OPEN は封鎖により二度と true にならない。つまり occupiedSet() は全モードで一度も呼ばれない。
occupied は常に空集合。そして集計はこう出ていた。
閉じている室日: 12(うち開けられる 12 / override・実予約 0)
exit 1
実際は 12 のうち 4 が実予約、8 がそれに従属する在庫で、開けられる日は 0 だった。 つまり 12件ぶんの嘘の「対応が必要」警報を、封鎖の日から出し続けていた。
封鎖のレビューでは「書き込みは安全になった」しか見ていない。 読み取り側の意味が変わったことを誰も見なかった。これが事故の本体だ。
2. 同じ地雷を自分のコードから掘り出す
封鎖・削除・フィーチャーフラグ off をしたその日に、次を機械的にやる。
2.1 フラグ名で全参照を数える
# 封鎖したフラグ名で、コード中の全参照を出す
grep -rn "OPEN\b" --include='*.js' --include='*.mjs' --include='*.ts' src/ tools/
出てきた参照を1つずつ「これは操作か、検証か」で仕分ける。 操作なら封鎖されて正しい。検証なら、それは今日から死んでいる。
2.2 危ない書き方のパターン
次の3つは「検証をオプション扱いにしている」合図。フラグ封鎖と相性が最悪なので、見つけたら疑う。
const checked = FLAG ? await expensiveCheck() : new Set(); // 空で代用している
const list = FLAG ? await fetchAll() : []; // 空配列で代用している
if (FLAG) { validate(); } // 検証そのものが分岐の中
共通するのは 「検証しなかった」を「検証した結果、何も無かった」と同じ値で表現していること。
new Set() も [] も「空」であって「未確認」ではない。型が嘘をついている。
2.3 到達不能になっていないか
封鎖が process.exit() で行われている場合、そのフラグを見る後続コードは全部デッドコードになる。
カバレッジを取ればゼロ行として出るが、多くの現場では CLI にカバレッジをかけていない。
かける気がないなら、封鎖の直後に一言残しておくだけでも次の人が助かる。
// NOTE: 以降 OPEN は常に false。OPEN を条件にした処理は到達しない。
// 追加するときは「検証」を OPEN にぶら下げないこと。
3. 直し方 — 未検証のゼロを構造的に出せなくする
3.1 検証はフラグから外し、常時実行にする
// 変更前: 書き込むときだけ照合していた
const occupied = OPEN ? await occupiedSet() : new Set();
// 変更後: 表示するだけでも照合する。省きたい人だけが明示的に省く
const occupied = NO_CHECK ? null : await occupiedSet();
既定を「検証する」にするのが要点。省略はオプトインにし、省略した事実が画面に出るようにする。
3.2 「未確認」を数字と別の値で持つ
null を「未確認」、Set/数値を「確認済み」として型で区別する。
表示の直前で分岐し、未確認なら数字を出さない。
const occupiedDisp = NO_CHECK ? '未確認' : totalOccupied;
const holeDisp = NO_CHECK ? '未確認' : totalHole;
console.log(`閉: ${totalClosed}(予約 ${occupiedDisp} / 保留 ${totalOverride} / 本当の穴 ${holeDisp})`);
個別の明細行も同じ規律で書く。ここを忘れると、合計だけ「未確認」なのに明細が断定するという 自己矛盾したレポートになる(実際にやらかした)。
// 悪い: 照合していないのに「対応が必要」と断定している
skips.push(`${name}: ${holeDays.length}件が対応必要`);
// 良い: 断定できるのは照合した時だけ
const label = NO_CHECK ? '未照合のため判定不能' : '対応必要';
skips.push(`${name}: ${holeDays.length}件が${label}`);
3.3 原因ごとにバケツを分け、合計を不変条件にする
事故った表示は うち対応できる N / その他 M の2分割だった。
原因が「その他」に混ざるので、片方の判定が死んでも数字は破綻せず、誰も気付けない。
原因の数だけバケツを用意し、合計が母数と一致することを不変条件にする。 どれか1つの判定が死ぬと合計が合わなくなり、その場で露見する。
let total = 0, byBooking = 0, byHold = 0, byDerived = 0, byGap = 0;
for (const d of closedDays) {
total++;
// 優先順位を明示的に決める(同じ日が複数の理由に当たっても1回だけ数える)
if (occupied && occupied.has(key(roomId, d))) byBooking++;
else if (state.get(d).hold !== 'none') byHold++;
else if (isDerivedUnit(unit)) byDerived++; // 他ユニットから算出される在庫
else byGap++; // 本当に対処すべきもの
}
console.assert(byBooking + byHold + byDerived + byGap === total, '内訳の合計が母数と一致しない');
3.4 終了コードは「対処すべきものがあるか」に合わせる
封鎖前の終了コードは「閉じている日が1日でもあれば 1」だった。 だが実予約で閉じているのは正常であって、人が何かする必要はない。 これで exit 1 を出す道具は狼少年になり、やがて誰も終了コードを見なくなる。
| 終了コード | 意味 |
|---|---|
| 0 | 全部説明がついた。対処不要 |
| 1 | 対処すべきもの(最後のバケツ)が1件以上ある |
| 2 | 判定不能(照合を省いた/データが取り切れなかった) |
「異常があるか」ではなく「人が動く必要があるか」に合わせるのが読まれる終了コードの条件。
3.5 ページングの取りこぼしも「判定不能」に倒す
検証を常時実行にすると、今度は検証用データの取得漏れが新しい嘘の源になる。 「上限100件までしか返らない API を、1年ぶんの窓で1回だけ叩く」と静かに欠落する。
// 窓を暦月で割って複数回取り、それでも溢れる月は数字を出さない
for (const [from, to] of splitByMonth(FROM, TO)) {
const r = await fetchBookings({ from, to });
if ((r.count || 0) > (r.items || []).length) {
undeterminable.push(`${from}〜${to}`); // ← 握りつぶさない
}
for (const b of r.items || []) { /* 集計 */ }
}
if (undeterminable.length) {
console.warn(`判定不能な期間: ${undeterminable.join(', ')}`);
process.exit(2); // 誤った数字を出すより止まる
}
分割して統合するときは、識別子で重複排除する。 期間の境界をまたぐレコードは 両方の呼び出しで返ってくる。
const key = b.id ? String(b.id) : `${b.unitId}|${b.start}|${b.end || ''}`;
4. 直したことを「実行して」確かめる
読み戻しやテストのグリーンでは足りない。修正前後の同じコマンドを本番データで走らせ、 数字と終了コードが変わったことを並べる。これが唯一の証拠になる。
# 修正前のコードを一時ファイルに取り出して同じ窓で走らせる(作業ツリーは汚さない)
git show HEAD:tools/report.mjs > tools/.tmp-report-old.mjs
node tools/.tmp-report-old.mjs --from 2026-12-01 --to 2027-01-10 | tail -3; echo "exit=$?"
rm -f tools/.tmp-report-old.mjs
node tools/report.mjs --from 2026-12-01 --to 2027-01-10 | tail -3; echo "exit=$?"
⚠️ git stash や git checkout -- <file> で切り替えないこと。
同じ作業フォルダで別のエージェントや人が編集していると、その未コミットの変更ごと消える。
git show <rev>:<path> で別ファイルに取り出すだけなら誰の作業も壊さない。
結果はこう並べる。片方だけでは「直った気がする」で終わる。
| 窓 | 修正前 | 修正後 |
|---|---|---|
| 単一ユニット・15日 | 対応必要 7 / 予約 0 · exit 1 | 予約 7 / 対処必要 0 · exit 0 |
| 全ユニット・41日 | 対応必要 12 / 予約 0 · exit 1 | 予約 4 / 従属 8 / 対処必要 0 · exit 0 |
2行目のように「嘘の警報が何件出ていたか」が言える形にすると、 封鎖のレビューで何を見落としたのかが次の人にも伝わる。
5. 実装を安いモデルに委譲するときの受け入れ基準
この種の修正は仕様が明確なので委譲に向く。ただし必ず落ちる型の不良があるので、 仕様書に「やってはいけないこと」として先に書き、受け入れ時にこの順で見る。
- 複合キーから次元が落ちていないか。
実例: 占有判定のキーが
unitId|dateからdateだけになり、 別ユニットの予約で当該ユニットが埋まっていると判定するコードが返ってきた。 構文も型も通り、テストが無ければ気付けない。キーの構成要素を仕様に明記して照合する。 - 最後の出力で使う変数が全部宣言されているか。(未宣言のまま集計変数を使う版が返ってきた)
node --checkは構文しか見ないので通ってしまう。必ず1回実行する。 - 追加した分岐の中身が空になっていないか。(新バケツの分岐が空で、何も数えていない版が返ってきた) 3.3 の合計一致アサーションを入れておくと、この不良が実行時に自分から名乗り出る。
- 「判定不能」を「対処必要」に混ぜていないか。
出てきた実装をそのまま採用せず、上記の観点で読んでから1回は本番相当データで走らせる。 1回目の委譲先が上記1で落ちたら、モデルを替えて出し直すほうが、対話で直させるより速い。
6. チェックリスト
- 封鎖したフラグ名を grep し、全参照を「操作 / 検証」に仕分けた
- 検証はフラグから外し、既定で常時実行にした
- 「未確認」を空集合・空配列・0 で表現していない(別の値・別の表示にした)
- 合計欄だけでなく明細行も、未照合時は断定しない文言になっている
- 原因ごとのバケツに分け、合計=母数のアサーションを入れた
- 終了コードを 0 / 1(対処必要)/ 2(判定不能)に整理した
- 検証用データのページング漏れを検知し、漏れたら 2 で止まる
- 修正前後を本番データで走らせ、数字と終了コードの差を並べた
- 委譲した実装を、複合キー・未宣言変数・空の分岐 の3点で読んでから実行した
よくある質問
+「機能を封鎖したら相乗りの検証も死ぬ — 未検証のゼロを画面から追い出す」とは何ですか?
危険な機能を後から封鎖すると、同じフラグにぶら下がっていた検証処理が道連れで死に、ツールは未検証のゼロを『確認した結果ゼロ』の顔で出し続ける。実例では1週間、12件ぶんの嘘の警報を出していた。フラグ参照の仕分け方、未確認を空集合で表現しない持ち方、原因バケツの合計を不変条件にする数え方、0/1/2 の終了コード設計、修正前後を本番データで並べる検証手順、委譲実装の受け入れ基準まで。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約1.6万トークンかかりますが、この巻物を使えば約4,200トークンで済みます。差し引き約1.2万トークン(API料金換算で約18円)・74%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
ドキュメント駆動開発プロセス CLAUDE.md — 作るものを固めてから書かせる
「AIが暴走して意図と違うものを作る」を根絶する開発プロセス指示書。UI仕様→機能設計→実装の順をAIに強制し、1ファイルごとに承認ゲートを挟む。受託開発・チーム開発向け。
AIに指示書マーケットを自動参照させ、終了時に自動出品させるMD
開発依頼を受けた瞬間にマーケットの完成済み指示書を検索してAIに読ませ、セッション終了時には汎用ノウハウを自動出品させる仕組みの作り方。全台配布・秘密情報スキャン・実際に踏んだ配布バグ3つの回避込み。
「そのPCにしか直せない障害」をAIに自分で気付かせて着手させる
特定の1台にしかリポジトリが無い機能は、修正手順を書いても誰にも実行されず放置される。SessionStart hook で当該PCのAIだけに指示を出し、完了後は指示書へ状態を書き戻して再実装事故を防ぐ型。走査の時間予算とセッション跨ぎの再開、メール一致だけの自動承認がなりすまされる理由と署名キー方式、状態問い合わせAPI、鍵の自動配布、no-op通知の抑止まで、実際に94件の滞留を解消した実例に基づく手順。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア