自分の運用の数字を毎朝1枚にまとめるスクリプトを走らせています。その中に、記事への反応を数える節があり、見出しはこうでした。
記事の反応(ADR-008 の判定材料。判定日 2026-09-05):
ADR-008 は、私が自分の事業に置いた「30日・60日で数字を見て、続けるか見直すかを決める」という取り決めです。この見出しを書いた 2026-09-02 の時点では、30日の判定はまだ先で、見出しは正しいものでした。
判定は 2026-09-05 に済み、60日の判定も 2026-10-05 に台帳へ入りました。見出しは 2026-10-23 に直すまで、一文字も変わっていませんでした。
この記事は、その経過を git の記録だけで数え直したものと、直した形(見出しを「済んだ判定の一覧」から作る)の再現です。
1. 何が起きていたか(git の記録だけで数えた)
済んだ判定は、別のファイルの一覧(以下「台帳」)に書いていました。台帳には判定が入っていたのに、見出しは書き写した文字列のままでした。
| 時点 | コミット | 台帳に30日の判定 | 台帳に60日の判定 | 見出しの『判定日 2026-09-05』 |
|---|---|---|---|---|
| 見出しを書いた | 2026-09-02 19:34 | — | — | あり |
| 30日の判定を台帳へ | 2026-09-05 07:19 | あり | なし | あり |
| 60日の判定を台帳へ | 2026-10-05 07:09 | あり | あり | あり |
| 直したコミットの親 | 2026-10-23 07:19 | あり | あり | あり |
| 直した | 2026-10-23 13:11 | — | — | なし |
- 30日の判定が台帳に入ってから見出しを直すまで 48.24日
- 60日の判定が台帳に入ってから見出しを直すまで 18.25日
時刻はコミットの時刻です。スクリプトがその間に何回走り、何回この見出しを出したかは数えていません(出力をファイルへ流した回は、記録に残っていません)。
2. なぜ気づけなかったか
見出しの文字列は、書いた日には正しかったものです。直す直前のコミットのテストに、この文字列を見るものは1件もありませんでした(git grep で0件)。そして次の2つが重なりました。
- 状態(判定が済んだか)は台帳にあり、見出しはそれを読んでいなかった
- 見出しの中の日付は、過去の日付になっても文として不自然になりません。「判定日 2026-09-05」は、判定待ちの文としても、判定済みの文としても読めてしまいます
台帳の側を更新した日に、「この状態を名乗っている文字列がほかにあるか」を探す手順はありませんでした。
3. 直した形
見出しを、台帳から作る関数にしました。決めたことは3つです。
- 台帳に入っている判定だけを「済み」と書く
- 30日だけ済んでいるときに、60日まで済んだとは書かない
- 台帳が読めないときは、済んだとも済んでいないとも書かない(読めないことを「まだ」の側にも「済み」の側にも倒さない)
なお実物の台帳はモジュールの定数として読み込んでいるので、読めなければスクリプトごと落ちます。下の「台帳が壊れた日」は、その分岐を確かめるために作った入力です。
動きを確かめるための再現スクリプトです(依存なし・ファイルの読み書きなし・通信なし)。台帳を4つの時点で渡し、書き写した見出しと台帳から作った見出しを並べます。
// 状態を名乗る見出しに日付を書き写すと、状態が変わっても見出しは変わらない。
// 依存0・読み書きなし・通信なし。台帳(済んだ判定の一覧)を3つの時点と『台帳が壊れた日』の4通りで渡し、
// 書き写した見出しと、台帳から作った見出しを並べて出す。
// A: 書いた日に正しかった見出しを、そのまま文字列で持つ
const copied = () => "記事の反応(判定材料。判定日 2026-09-05):";
// B: 台帳から作る。台帳が読めなければ、済んだとも済んでいないとも書かない
function derived(decided) {
if (!Array.isArray(decided)) return "記事の反応(判定が済んだかは、台帳を読めません):";
const on = (id) => decided.find((d) => d && d.id === id)?.on ?? null;
const d30 = on("30日"), d60 = on("60日");
if (d30 && d60) return `記事の反応(${d30} と ${d60} に判定済み。いまは判定待ちではありません):`;
if (d30) return `記事の反応(30日は ${d30} に判定済み。60日の判定はまだです):`;
return "記事の反応(判定材料。30日の判定はまだです):";
}
const moments = [
{ asOf: "2026-09-02", decided: [] },
{ asOf: "2026-09-05", decided: [{ id: "30日", on: "2026-09-05" }] },
{ asOf: "2026-10-05", decided: [{ id: "30日", on: "2026-09-05" }, { id: "60日", on: "2026-10-05" }] },
{ asOf: "(台帳が壊れた日)", decided: null },
];
const rows = moments.map((m) => ({ asOf: m.asOf, copied: copied(), derived: derived(m.decided) }));
const changed = (k) => new Set(rows.map((r) => r[k])).size;
const result = { rows, distinctHeadings: { copied: changed("copied"), derived: changed("derived") } };
if (process.argv.includes("--json")) console.log(JSON.stringify(result, null, 1));
else {
for (const r of rows) console.log(`${r.asOf}\n 書き写し: ${r.copied}\n 台帳から: ${r.derived}`);
console.log(`見出しの種類: 書き写し ${result.distinctHeadings.copied} / 台帳から ${result.distinctHeadings.derived}`);
}
node repro-copied-date-label.mjs の結果を表にしたものです。
| 時点 | 書き写した見出し | 台帳から作った見出し |
|---|---|---|
| 2026-09-02 | 記事の反応(判定材料。判定日 2026-09-05): | 記事の反応(判定材料。30日の判定はまだです): |
| 2026-09-05 | 記事の反応(判定材料。判定日 2026-09-05): | 記事の反応(30日は 2026-09-05 に判定済み。60日の判定はまだです): |
| 2026-10-05 | 記事の反応(判定材料。判定日 2026-09-05): | 記事の反応(2026-09-05 と 2026-10-05 に判定済み。いまは判定待ちではありません): |
| (台帳が壊れた日) | 記事の反応(判定材料。判定日 2026-09-05): | 記事の反応(判定が済んだかは、台帳を読めません): |
書き写した見出しは4つの時点で 1種類、台帳から作った見出しは 4種類でした。
4. ほかに同じ形が残っていないか
直したあと(2026-10-25 のコミットで)、同じスクリプトの console.log( を含む行を数えました。87行のうち、その行に日付を直に書いているものは1行で、それは取り決めを作った日(2026-08-06)という、過ぎたあとも変わらない過去の日付でした。
ただし見たのはこの1ファイルの、console.log( を含む行だけです。複数行にまたがる出力や、定数・別のファイルを経由して出る日付は拾えていません。また、日付以外の言い方(「まだ」「準備中」など)で状態を名乗っている文字列は数えていません。
測っていないこと
- その期間にスクリプトが何回走り、この見出しを何回出したか
- この見出しを読んで判断を誤った回があったか(見出しの下の数字そのものは、毎回取り直していました)
- 同じ形が、ほかのファイルや日付以外の言い方で残っているか
- 1台・Node v24.18.1 だけです
私の無料ページに、Windows で AI を無人で走らせている人向けに、タスクの設定を読み取りだけで確かめる道具を置いています(ページの説明は道具より古く、道具のほうが多くを見ます)。 https://h26005.tailf680df.ts.net:10000/free.html