自分が管理していない上流ファイルを差し込みで維持する型 — 死んだパッチを検知して retired に移す
上流が更新し続けるスクリプトに自分の修正を当て続ける仕組み。find/marker/replace の4状態エンジン、anchor-missing に専用 exit code を与える警報設計、差し込みの連鎖、死んだ差し込みの retired 化。テストが足りない値を注入していて壊れたコードが緑だった実害と、その塞ぎ方まで。
約3.4万トークンの節約 (API料金換算で約51円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「自分が管理していない上流ファイルを差し込みで維持する型 — 死んだパッチを検知して retired に移す」は、開発プロセスカテゴリのAI指示書(MDファイル)です。上流が更新し続けるスクリプトに自分の修正を当て続ける仕組み。find/marker/replace の4状態エンジン、anchor-missing に専用 exit code を与える警報設計、差し込みの連鎖、死んだ差し込みの retired 化。テストが足りない値を注入していて壊れたコードが緑だった実害と、その塞ぎ方まで。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約3.4万トークン(API料金換算で約51円)・85%のトークンを節約できます。
- カテゴリ
- 開発プロセス
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約4万トークン
- この巻物使用時
- 約6,000トークン
- 節約量
- 約3.4万トークン (約51円)
- 更新日
- 2026-09-18
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/retired/raw を読み込んで、この指示書どおりに実装して"
中身
自分が管理していない上流ファイルを「差し込み」で維持する型 — 死んだパッチを検知して retired に移す
これは何のための指示書か
あなたの手元には、他の誰か(上流)が更新し続けるスクリプトがあり、そこに自分専用の修正を当て続けたい。
典型例:
- 共有リポを
git pullで受け取り、自分の機体だけに必要な修正を足している - 社内共通ツールの配布物に、1台だけの回避策を当てている
- OSS を fork せずに、手元で数行だけ書き換えて使っている
素朴にやると必ずこうなる:
- 上流を取り直すと自分の修正が消える
- 消えたことに気付かない
- 気付かないまま「直したはず」の機能が死に、その機能が支えていた検査も一緒に死ぬ
この指示書は、それを「当て直しを自動化し、当たらなくなった差し込みを警報に変える」形で解く。 実測ベース: この型で維持している差し込みは29本、うち4本が上流の変更で死亡し、警報経由で発見・処理された。
全体像
正本(あなたが管理するリポ)に 差し込み定義の配列 を1つ置く。 それを当てるスクリプトを毎日1回走らせる。当たらなくなったら警報を出して落ちる。
正本リポ
patches.mjs … 差し込み定義(PATCHES 配列)+ 適用エンジン
test/patches.test.mjs … 差し込みが「上流の実物」に当たることのテスト
上流のファイル(live)
<upstream-tool>.mjs … 毎日の同期で上書きされる。ここを直接直しても翌日消える
差し込み1本の形
{
file: '<upstream-tool>.mjs',
// marker: 「もう当たっている」ことを見分ける文字列。当てた後のコードにだけ現れる語を選ぶ。
marker: 'const launchStarted = Date.now();',
// find: 上流の「いまの実物」と完全一致する文字列(前後の空白も1文字も違えてはいけない)
find: [
'async function launchCodex(args) {',
' let result;',
].join('\n'),
// replace: 置き換え後。find の全行を保持し、足す行だけ増やす。
replace: [
'async function launchCodex(args) {',
' const launchStarted = Date.now();',
' let result;',
].join('\n'),
}
適用エンジンはこれだけ:
export function applyPatch(text, patch) {
if (patch.retired) return { text, changed: false, reason: 'retired' };
if (text.includes(patch.marker)) return { text, changed: false, reason: 'already' };
if (!text.includes(patch.find)) return { text, changed: false, reason: 'anchor-missing' };
return { text: text.replace(patch.find, patch.replace), changed: true, reason: 'patched' };
}
返る4状態がこの型の全てである。already と anchor-missing を区別することが核心。
anchor-missing を「もう当たってるんだろう」で黙らせた瞬間、この仕組みは無意味になる。
🔴 落とし穴1: marker を replace 後のコードに無い文字列にしてしまう
marker は当てた後のテキストに必ず含まれていること。含まれないと毎回 find を探しにいき、
2回目以降は anchor-missing になって偽の警報が出る。逆に marker が当てる前にも含まれていると、
一度も当たっていないのに already と報告する(最悪の故障モード。黙って何もしない)。
marker は replace から選び、find に含まれていない語にする。
🔴 落とし穴2: find を構文の途中で切る
find は必ず構文単位(文の先頭から終端まで、対応する括弧・引用符ごと)で切る。
引数リストの途中や、開き括弧だけを含む形で切ると、置換後に構文エラーになる。
# 適用後に必ず走らせる。これが唯一の検出器。
node --check <live-file>
この1行を省くと、壊れたファイルを配って初めて気付く。
🔴 落とし穴3: 差し込みは「連鎖」する
後の差し込みが、前の差し込みが置いた行を狙うことがある。これは避けるのではなく設計として認める。
差し込みA: 上流の素の2行 → Aの形
差し込みB: Aの形 → Bの形(find が A の replace そのもの)
こうすると、まっさらな上流(A→B の順に当たる)でも、すでに A が当たっている手元(B だけ当たる)でも、 同じ最終形に着地する。片方しか成立しない設計にすると、機体ごとに状態が分かれる。
ただし連鎖させたら、テストでもその順に当てて検証すること(後述)。
🔴 落とし穴4: 既存の差し込みの replace を書き換えても届かない
すでに already になっている差し込みの replace をあとから直しても、live には永遠に届かない
(marker が見つかるので置換が走らない)。挙動を変えたい時は:
- 新しい差し込みを1本足す(find = 現在 live にある壊れた形、marker = 直した後の形にだけ在る語)
これを知らずに replace を直して「直した」と報告する事故が起きやすい。
🔴 落とし穴5: 死んだ差し込みを消すと、上流が戻った時に気付けない
上流が変わって find が二度と当たらなくなった差し込みは、消さずに retired へ移し、理由を書く。
{
file: '<upstream-tool>.mjs',
retired: '上流の関数シグネチャが (…, provider, attempts = 1) へ変わり、この find は二度と当たらない。'
+ '目的は marker const abortLedger の差し込みへ移した',
marker: '…', find: '…', replace: '…', // 元の内容はそのまま残す
}
retired は「当てにいかないし、警報も出さない」状態。消してしまうと、
上流が元に戻った時や、別の機体で古い上流が残っていた時に、履歴が何も語らない。
毎日の実行と警報
# 書かずに検める(毎日1回・cron / スケジューラ)
node patches.mjs --check
# 当てる
node patches.mjs
--check の出力と終了コードをこう設計する:
| 状態 | 出力 | exit |
|---|---|---|
| 全部 already / retired | 未適用 0 件 | 0 |
| 未適用がある | 未適用 N 件 | 1 |
| anchor-missing がある | 🚨 差し込みが二度と当たらない: <file> [<marker>] | 3 |
| ファイルが無い | 見つからない: … | 2 |
anchor-missing に専用の終了コードを与えるのが要点。「未適用」と同じ扱いにすると、
「まだ当てていないだけ」と「上流が変わって安全網が死んだ」が混ざって埋もれる。
警報文には必ず「どうすればよいか」を書く:
🚨 N 件の差し込みが anchor-missing。上流が変わってこの安全網は死んでいる。
上流の現物に合わせて貼り直すか、retired にして理由を書くこと(放置すると巻き戻りに気付けない)。
テストの書き方(ここで一番事故る)
期待件数を手書きの数値で固定しない
// ✗ 差し込みを1本足した翌日に偽 fail する
assert.equal(result.stale.length, 2);
// ○ PATCHES から導出する。数ではなく「どのファイルか」で見る
const active = PATCHES.filter((p) => !p.retired && p.file === '<upstream-tool>.mjs').length;
assert.ok(active >= 2);
assert.equal(result.stale.length, active);
fixture は「上流の実物」から作り、patch.find を fixture に使わない
applyPatch(patch.find, patch) は必ず通る(find で find を探すのだから)。
これは何も検証していない。fixture は上流の実物を写したものを別に持ち、そこに当てる。
const LIVE_SOURCE = [ /* 上流ファイルから実際に写した行 */ ].join('\n');
const applied = applyPatch(LIVE_SOURCE, getPatch(MARKER));
assert.equal(applied.reason, 'patched'); // 当たること
assert.deepEqual(applyPatch(applied.text, getPatch(MARKER)), {
text: applied.text, changed: false, reason: 'already',
}); // 冪等であること
連鎖する差し込みは、連鎖の順に当てた結果を検証する
const afterA = applyPatch(LIVE_SOURCE, getPatch(MARKER_A));
const afterB = applyPatch(afterA.text, getPatch(MARKER_B));
assert.equal(afterB.reason, 'patched');
fixture を手写しで「B が当たった後の形」として持つと、上流が動いた時に嘘の緑になる。必ず導出する。
🔴 最大の落とし穴: テストが足りない値を注入していると、壊れたコードが緑になる
実害の記録。ある差し込みが、別の関数で宣言された変数を参照するコードを置いてしまった
(スコープ外参照 → 実行すれば ReferenceError)。ところがテストは、置いたコードを
サンドボックスで走らせる際にその変数をコンテキストに注入していたため、ずっと緑だった。
結果、この差し込みが守るはずだった記録は1件も書かれないまま数日が過ぎた。
対策は「注入をやめる」ではなく「注入していないことを assert する」:
const context = makeContext(); // 足りない変数は入れない
assert.equal(Object.hasOwn(context, 'someVar'), false, '注入せずに走ること');
vm.runInNewContext(`${patchedSource}\nglobalThis.__run = () => target();`, context);
await assert.rejects(context.__run, (e) => e === context.expectedExit);
さらに、コメント行を除いた実コードに参照が残っていないことも見る (説明コメントに変数名を書くと、素朴な文字列検索は当たってしまう):
const code = source.split('\n').filter((l) => !l.trim().startsWith('//')).join('\n');
assert.equal(code.includes('someVar'), false);
retired であること自体をテストで固定する
function getRetiredPatch(marker) {
const [p] = PATCHES.filter((x) => x.marker === marker);
assert.ok(p.retired, `retired patch: ${marker}`);
return p;
}
test('死んだ差し込みは retired で、上流の実物にも当たらない', () => {
const p = getRetiredPatch(MARKER);
assert.equal(LIVE_SOURCE.includes(p.find), false); // 本当に当たらない
assert.deepEqual(applyPatch(LIVE_SOURCE, p), {
text: LIVE_SOURCE, changed: false, reason: 'retired',
});
assert.ok(String(p.retired).length > 10, 'retired には理由を書くこと');
});
これを入れておくと、誰かが「もう要らないだろう」と retired を消した時に落ちる。
上流の関数シグネチャが変わった時の判断
一番厄介なのは、呼び出し先の引数の意味が変わるケース。
実例: 上流の記録関数が f(a, b, c, provider = 'x', extra = {}) から
f(a, b, c, provider = 'x', attempts = 1) に変わった。第5引数は「追加フィールドのオブジェクト」から
「数値の試行回数」になった。呼び出し側はエラーも警告も出さずに、オブジェクトを
attempts としてそのまま記録し続けた。記録は増えるが、狙ったキーは一生生まれない。
このとき「引数の渡し方を直す」は解にならない。その関数を通す道が存在しない。
判断: 上流の関数を経由せず、自分の差し込みの中で目的を完結させる。
上の例では、記録関数を呼ぶのをやめ、差し込みの中で1行だけ直接書き出す形にした。 上流が何をしようと壊れない。上流のAPIに依存する量を減らすのが、差し込みの寿命を伸ばす唯一の方法。
完了判定のチェックリスト
差し込みを足した/直した時、次の全部が揃って初めて「直した」と言える:
node --check <live-file>が通る(構文単位で切れている)--checkの🚨 anchor-missingが 0 件- テストスイートの pass 数が減っていない(「fail 0」だけ見ると、テストが消えても緑になる)
- 実際に当該経路を1回走らせ、期待する副作用(ログ1行・ファイル1件)が 「修正より後の時刻で」増えている
4 が一番飛ばされる。終了コードが 0 でも、成果物が無ければ何も起きていない。 逆に終了コードが 0 以外でも、成果物があれば成功していることがある。判定は常に成果物で行う。
上流への還元
手元の差し込みが3本を超えたら、上流に PR を出すことを検討する。 差し込みは「上流が受け取るまでの橋」であって、住む場所ではない。 retired が増え続けるなら、それは上流と設計が食い違っている信号である。
よくある質問
+「自分が管理していない上流ファイルを差し込みで維持する型 — 死んだパッチを検知して retired に移す」とは何ですか?
上流が更新し続けるスクリプトに自分の修正を当て続ける仕組み。find/marker/replace の4状態エンジン、anchor-missing に専用 exit code を与える警報設計、差し込みの連鎖、死んだ差し込みの retired 化。テストが足りない値を注入していて壊れたコードが緑だった実害と、その塞ぎ方まで。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約4万トークンかかりますが、この巻物を使えば約6,000トークンで済みます。差し引き約3.4万トークン(API料金換算で約51円)・85%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
スマホ(Remote Control)から即相談できる Claude Code タブを VS Code に毎朝自動で用意する
自作VS Code拡張で公式Claude Codeのコマンド(editor.openLast/newConversation/renameSessionTab)を叩き、名前付きタブをN本自動補充。夜間はWM_CLOSE→再起動で毎朝揃える。--bg/ターミナル経路・タブ0でのnewConversation・SendKeys再読み込みが失敗する実測付き
夜間ジョブ異常を通知で終わらせず自動修復→AI修理PR→人へ引き渡す閉ループ
監視の『検知して通知』の後段に、決定的Playbook→AIコーダーの隔離worktree修理PR→持ち越し→人への3要素引き渡し、を足す実装指示書。argvで指示を渡すな等の実測の落とし穴つき
ドキュメント駆動開発プロセス CLAUDE.md — 作るものを固めてから書かせる
「AIが暴走して意図と違うものを作る」を根絶する開発プロセス指示書。UI仕様→機能設計→実装の順をAIに強制し、1ファイルごとに承認ゲートを挟む。受託開発・チーム開発向け。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア