diff --git a/docs/todo-summary.md b/docs/todo-summary.md
index 1202302e..03e287cf 100644
--- a/docs/todo-summary.md
+++ b/docs/todo-summary.md
@@ -2,7 +2,7 @@
> **本ファイルの位置付け**: `docs/todo.md` から「推奨実行順序サマリー」section を切り出した index 専用ファイル。各タスクの詳細は「ファイル」列に示された `docs/todoN.md` を参照する。`docs/todo.md` のサイズが 50KB を超え Claude Code 読み取り安定性に影響したため分離 (2026-05-09)。
>
-> **更新方針**: table への新規行追加・既存行の削除・順位の再採番は本 index (todo-summary.md + [docs/todo-summary2.md](todo-summary2.md)) で実施する (2026-07-20 に順位 220 以降を todo-summary2.md へ物理分割、docs 50KB 超過解消。新規行は末尾 = todo-summary2.md 側に追加)。詳細エントリは現行の追加先ファイル (= **`docs/todo22.md`**、2026-08-11 の post-merge feedback 採否バッチで新設。直前の追加先 `docs/todo21.md` が約 57KB = 50KB 安定読み取り閾値超過に達したため移行) に記録する。追加先は 50KB 到達のたびに移っており、`docs/todo13.md` → `todo14.md` (2026-07-19、WR-2026-07-19-T02) → `todo20.md` → `todo21.md` (2026-08-08) → `todo22.md` (2026-08-11) と辿ってきた。**移行済みの旧ファイルは既存エントリの編集・完了削除専用**で、新規追加先ではない。なお `docs/todo10.md` は 2026-06-29 PR #224 セッションで todo13.md へ追加先が移行するまでの旧追加先 (以降 既存エントリの編集・完了削除専用)、`docs/todo11.md` は 2026-06-06 todo9.md 分割で新設された専用ファイル (順位 157, 160-173 を収容)、`docs/todo12.md` は 2026-06-12 PR #204 で todo10.md 分割により新設された専用ファイル (順位 176/178/179/180/181/182/193/194 = PR #185 〜 PR #196 era を収容) で、いずれも新規追加先ではない。
+> **更新方針**: table への新規行追加・既存行の削除・順位の再採番は本 index (todo-summary.md + [docs/todo-summary2.md](todo-summary2.md)) で実施する (2026-07-20 に順位 220 以降を todo-summary2.md へ物理分割、docs 50KB 超過解消。新規行は末尾 = todo-summary2.md 側に追加)。詳細エントリは現行の追加先ファイル (= **`docs/todo23.md`**、2026-08-13 に週次レビュー WR-2026-08-13-M01 採用で新設。直前の追加先 `docs/todo22.md` が約 66KB = 50KB 安定読み取り閾値超過に達したため移行) に記録する。追加先は 50KB 到達のたびに移っており、`docs/todo13.md` → `todo14.md` (2026-07-19、WR-2026-07-19-T02) → `todo20.md` → `todo21.md` (2026-08-08) → `todo22.md` (2026-08-11) → `todo23.md` (2026-08-13) と辿ってきた。**移行済みの旧ファイルは既存エントリの編集・完了削除専用**で、新規追加先ではない。なお `docs/todo10.md` は 2026-06-29 PR #224 セッションで todo13.md へ追加先が移行するまでの旧追加先 (以降 既存エントリの編集・完了削除専用)、`docs/todo11.md` は 2026-06-06 todo9.md 分割で新設された専用ファイル (順位 157, 160-173 を収容)、`docs/todo12.md` は 2026-06-12 PR #204 で todo10.md 分割により新設された専用ファイル (順位 176/178/179/180/181/182/193/194 = PR #185 〜 PR #196 era を収容) で、いずれも新規追加先ではない。
diff --git a/docs/todo.md b/docs/todo.md
index c9c9f214..17aeb559 100644
--- a/docs/todo.md
+++ b/docs/todo.md
@@ -2,7 +2,7 @@
> **運用ルール**: 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
>
-> **本ファイル + [docs/todo3.md](todo3.md) 〜 [docs/todo22.md](todo22.md) + [docs/todo-summary.md](todo-summary.md) + [docs/todo-summary2.md](todo-summary2.md) の使い分け** (todo2.md は 2026-08-12 退役) (PR #83 T3-2 で恒久化、2026-04-28 強化、PR #88 で todo3.md 追加、PR #96 セッションで todo4.md 追加、PR #101 セッションで todo5.md 追加、PR #123 セッションで todo6.md 追加、2026-05-09 に todo-summary.md 切り出し + todo5.md 分割で todo7.md 追加、PR #143 = 2026-05-11 で todo8.md 追加、PR #172 仕組み化方針切替 = 2026-05-25 で todo9.md 追加、PR #185 land 後 2026-05-29 で todo10.md 追加、2026-06-06 todo9.md 分割で todo11.md 追加、2026-06-12 PR #204 で todo10.md 分割により todo12.md 追加、2026-06-29 PR #224 セッションで todo13.md 追加、2026-07-19 週次レビュー WR-2026-07-19-T02 採用で todo14.md 追加、2026-07-20 docs 50KB 超過解消で todo13.md を todo15/16/17・todo10.md を todo18/19 へ物理分割、2026-08-04 todo14.md の 50KB 超過で todo20.md 追加、2026-08-08 todo20.md の 50KB 超過で todo21.md 追加、2026-08-11 todo21.md の 50KB 超過で todo22.md 追加):
+> **本ファイル + [docs/todo3.md](todo3.md) 〜 [docs/todo23.md](todo23.md) + [docs/todo-summary.md](todo-summary.md) + [docs/todo-summary2.md](todo-summary2.md) の使い分け** (todo2.md は 2026-08-12 退役) (PR #83 T3-2 で恒久化、2026-04-28 強化、PR #88 で todo3.md 追加、PR #96 セッションで todo4.md 追加、PR #101 セッションで todo5.md 追加、PR #123 セッションで todo6.md 追加、2026-05-09 に todo-summary.md 切り出し + todo5.md 分割で todo7.md 追加、PR #143 = 2026-05-11 で todo8.md 追加、PR #172 仕組み化方針切替 = 2026-05-25 で todo9.md 追加、PR #185 land 後 2026-05-29 で todo10.md 追加、2026-06-06 todo9.md 分割で todo11.md 追加、2026-06-12 PR #204 で todo10.md 分割により todo12.md 追加、2026-06-29 PR #224 セッションで todo13.md 追加、2026-07-19 週次レビュー WR-2026-07-19-T02 採用で todo14.md 追加、2026-07-20 docs 50KB 超過解消で todo13.md を todo15/16/17・todo10.md を todo18/19 へ物理分割、2026-08-04 todo14.md の 50KB 超過で todo20.md 追加、2026-08-08 todo20.md の 50KB 超過で todo21.md 追加、2026-08-11 todo21.md の 50KB 超過で todo22.md 追加、2026-08-13 todo22.md の 50KB 超過で todo23.md 追加):
>
> - **docs/todo-summary.md**: 推奨実行順序サマリー table 専用 (旧 todo.md から切り出し)、順位 6-219 を収容。既存行編集・順位再採番はここで行う。
> - **docs/todo-summary2.md**: todo-summary.md の table を 2026-07-20 に docs 50KB 超過解消で分割した後半 (順位 220 以降を収容)。新規行追加は末尾 = 本ファイルで行う。cli-docs-lint の priority-inversion / preamble は両 summary を統合検査。
@@ -27,9 +27,10 @@
> - **docs/todo19.md**: 既存タスクの編集・完了削除専用 (2026-07-20 todo10.md 分割で新設、順位 220-224 収容)。**新規タスクは追加しない**
> - **docs/todo20.md**: 既存タスクの編集・完了削除専用。**新規タスクは追加しない** (2026-08-04 todo14.md の 50KB 超過で新設・順位 365-388 を収容、2026-08-08 に本ファイルも 50KB 超過で新規追加先は todo21.md へ移行)
> - **docs/todo21.md**: 既存タスクの編集・完了削除専用。**新規タスクは追加しない** (約57KB に到達したため、2026-08-11 以降の新規エントリは todo22.md へ。2026-08-08 todo20.md の 50KB 超過で新設、順位 385 以降を収容)
-> - **docs/todo22.md**: 新規タスクの追加先。50KB に到達するまでは本ファイルへ追加 (2026-08-11 todo21.md の 50KB 超過で新設)
-> - 例外: 既存 todo.md / todo3.md 〜 todo22.md タスクと **同一ファイル / 同一コンポーネント** を編集する密結合タスクは該当ファイルに追加可 (例: `~/.claude/rules/common/git-workflow.md` 配下のグローバルルール群)
-> - **新セッションでは全 todo ファイルを確認すること** (todo.md / todo3-22.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)
+> - **docs/todo22.md**: 既存タスクの編集・完了削除専用。**新規タスクは追加しない** (約 66KB に到達したため、2026-08-13 以降の新規エントリは todo23.md へ。2026-08-11 todo21.md の 50KB 超過で新設)
+> - **docs/todo23.md**: 新規タスクの追加先。50KB に到達するまでは本ファイルへ追加 (2026-08-13 todo22.md の 50KB 超過で新設、週次レビュー WR-2026-08-13-M01 採用)
+> - 例外: 既存 todo.md / todo3.md 〜 todo23.md タスクと **同一ファイル / 同一コンポーネント** を編集する密結合タスクは該当ファイルに追加可 (例: `~/.claude/rules/common/git-workflow.md` 配下のグローバルルール群)
+> - **新セッションでは全 todo ファイルを確認すること** (todo.md / todo3-23.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)
---
diff --git a/docs/todo10.md b/docs/todo10.md
index 51fe7a5d..e04a6dcf 100644
--- a/docs/todo10.md
+++ b/docs/todo10.md
@@ -2,7 +2,7 @@
> **運用ルール** ([docs/todo.md](todo.md) と同一): 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
>
-> **本ファイルの位置付け**: docs/todo9.md がファイルサイズ 50KB を超え行数 1100+ 行に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して新規エントリは本ファイルに記録する (PR #185 = Bundle CR-RL land 後、2026-05-29 ユーザー判断)。**本ファイルは既存タスクの編集・完了削除専用** (新規エントリの追加先は PR #224 セッション = 2026-06-29 で [docs/todo13.md](todo13.md) へ移行し、その後 todo14.md → todo20.md → todo21.md を経て、**現在は [docs/todo22.md](todo22.md)** (2026-08-11 に todo21.md が 50KB 超過で移行)。2026-06-12 PR #204 で PR #185 〜 PR #196 era の 8 エントリを [docs/todo12.md](todo12.md) に分離して file_size_check 50KB threshold 内に収めた、todo12.md は新規追加先ではない)。todo.md / todo3.md 〜 todo9.md / todo11.md / todo12.md の既存エントリ (todo2.md は 2026-08-12 退役)は引き続き有効、相互に独立。**2026-07-20 に順位 215-224 を todo18.md/todo19.md へ物理分割し、本ファイルは順位 198-214 のみ収容 (docs 50KB 超過解消、39KB 台に縮小)。**新セッションでは23つすべてを確認すること (todo.md / todo3-22.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
+> **本ファイルの位置付け**: docs/todo9.md がファイルサイズ 50KB を超え行数 1100+ 行に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して新規エントリは本ファイルに記録する (PR #185 = Bundle CR-RL land 後、2026-05-29 ユーザー判断)。**本ファイルは既存タスクの編集・完了削除専用** (新規エントリの追加先は PR #224 セッション = 2026-06-29 で [docs/todo13.md](todo13.md) へ移行し、その後 todo14.md → todo20.md → todo21.md を経て、**現在は [docs/todo23.md](todo23.md)** (2026-08-13 に todo22.md が 50KB 超過で移行)。2026-06-12 PR #204 で PR #185 〜 PR #196 era の 8 エントリを [docs/todo12.md](todo12.md) に分離して file_size_check 50KB threshold 内に収めた、todo12.md は新規追加先ではない)。todo.md / todo3.md 〜 todo9.md / todo11.md / todo12.md の既存エントリ (todo2.md は 2026-08-12 退役)は引き続き有効、相互に独立。**2026-07-20 に順位 215-224 を todo18.md/todo19.md へ物理分割し、本ファイルは順位 198-214 のみ収容 (docs 50KB 超過解消、39KB 台に縮小)。**新セッションでは24つすべてを確認すること (todo.md / todo3-23.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
>
> **推奨実行順序**: 全タスク横断のサマリーは [docs/todo-summary.md](todo-summary.md#recommended-order-summary) を参照。
diff --git a/docs/todo11.md b/docs/todo11.md
index 8082a354..1ff33885 100644
--- a/docs/todo11.md
+++ b/docs/todo11.md
@@ -2,7 +2,7 @@
> **運用ルール** ([docs/todo.md](todo.md) と同一): 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
>
-> **本ファイルの位置付け**: docs/todo9.md がファイルサイズ 75KB 超 (890 行) に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して、PR-specific follow-up entries (PR #174 以降の post-merge-feedback 採用 entry) を本ファイルに分離 (2026-06-06)。todo9.md には「既存ルール仕組み化バンドル + 週次レビュー拡張」themed entries が残る。todo.md / todo3.md 〜 todo10.md の既存エントリ (todo2.md は 2026-08-12 退役)は引き続き有効、相互に独立。新セッションでは23つすべてを確認すること (todo.md / todo3-22.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
+> **本ファイルの位置付け**: docs/todo9.md がファイルサイズ 75KB 超 (890 行) に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して、PR-specific follow-up entries (PR #174 以降の post-merge-feedback 採用 entry) を本ファイルに分離 (2026-06-06)。todo9.md には「既存ルール仕組み化バンドル + 週次レビュー拡張」themed entries が残る。todo.md / todo3.md 〜 todo10.md の既存エントリ (todo2.md は 2026-08-12 退役)は引き続き有効、相互に独立。新セッションでは24つすべてを確認すること (todo.md / todo3-23.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
>
> **推奨実行順序**: 全タスク横断のサマリーは [docs/todo-summary.md](todo-summary.md#recommended-order-summary) を参照。
diff --git a/docs/todo23.md b/docs/todo23.md
new file mode 100644
index 00000000..833e5853
--- /dev/null
+++ b/docs/todo23.md
@@ -0,0 +1,13 @@
+# TODO (Part 23)
+
+> **運用ルール** ([docs/todo.md](todo.md) と同一): 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
+>
+> **本ファイルの位置付け**: docs/todo22.md がファイルサイズ 66528 B (2026-08-13 時点、50KB = 51200 B の安定読み取り閾値超過) に到達したため、新規エントリは本ファイルに記録する (2026-08-13 新設、週次レビュー WR-2026-08-13-M01 採用)。**新規エントリの追加先は本ファイル**。todo.md / todo3.md 〜 todo22.md の既存エントリは引き続き有効、相互に独立。
+>
+> **サイズ表記について**: todo22.md のサイズは記録時点で異なる (週次レビュー検出時 54203 B → 本ファイル新設時 66528 B → その後の追記でさらに増加)。各記載は**その時点の計測値**であり、現在値と一致しないことがある。現在値が必要なら計測すること。
+>
+> **推奨実行順序**: 全タスク横断のサマリーは [docs/todo-summary.md](todo-summary.md#recommended-order-summary) を参照。
+
+---
+
+(新規エントリはここに追加する)
diff --git a/docs/todo3.md b/docs/todo3.md
index f66ea129..d227e2d4 100644
--- a/docs/todo3.md
+++ b/docs/todo3.md
@@ -2,7 +2,7 @@
> **運用ルール** ([docs/todo.md](todo.md) と同一): 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
>
-> **本ファイルの位置付け**: docs/todo2.md がファイルサイズ約 50KB に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して PR #88 以降の新規エントリは本ファイルに記録した。本ファイルも PR #96 セッションで 50KB 接近のため、それ以降の新規エントリは [docs/todo4.md](todo4.md) へ。todo.md / todo3-22.md の既存エントリは引き続き有効、相互に独立。新セッションでは23つすべてを確認すること (todo.md / todo3-22.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
+> **本ファイルの位置付け**: docs/todo2.md がファイルサイズ約 50KB に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して PR #88 以降の新規エントリは本ファイルに記録した。本ファイルも PR #96 セッションで 50KB 接近のため、それ以降の新規エントリは [docs/todo4.md](todo4.md) へ。todo.md / todo3-23.md の既存エントリは引き続き有効、相互に独立。新セッションでは24つすべてを確認すること (todo.md / todo3-23.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
>
> **推奨実行順序**: 全タスク横断のサマリーは [docs/todo-summary.md](todo-summary.md#recommended-order-summary) を参照。
diff --git a/docs/todo4.md b/docs/todo4.md
index 0c8845ec..3d102d5e 100644
--- a/docs/todo4.md
+++ b/docs/todo4.md
@@ -2,7 +2,7 @@
> **運用ルール** ([docs/todo.md](todo.md) と同一): 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
>
-> **本ファイルの位置付け**: docs/todo3.md がファイルサイズ約 50KB に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して新規エントリは本ファイルに記録していた。**本ファイルも 50KB に到達したため、PR #101 セッション以降の新規エントリは [docs/todo5.md](todo5.md) へ**。本ファイルは既存タスクの編集・完了削除専用。todo.md / todo3-22.md の既存エントリは引き続き有効、相互に独立。新セッションでは23つすべてを確認すること (todo.md / todo3-22.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
+> **本ファイルの位置付け**: docs/todo3.md がファイルサイズ約 50KB に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して新規エントリは本ファイルに記録していた。**本ファイルも 50KB に到達したため、PR #101 セッション以降の新規エントリは [docs/todo5.md](todo5.md) へ**。本ファイルは既存タスクの編集・完了削除専用。todo.md / todo3-23.md の既存エントリは引き続き有効、相互に独立。新セッションでは24つすべてを確認すること (todo.md / todo3-23.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
>
> **推奨実行順序**: 全タスク横断のサマリーは [docs/todo-summary.md](todo-summary.md#recommended-order-summary) を参照。
diff --git a/docs/todo5.md b/docs/todo5.md
index ce9871eb..cd926b3e 100644
--- a/docs/todo5.md
+++ b/docs/todo5.md
@@ -2,7 +2,7 @@
> **運用ルール** ([docs/todo.md](todo.md) と同一): 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
>
-> **本ファイルの位置付け**: docs/todo4.md がファイルサイズ約 50KB に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して PR #101 セッション以降の新規エントリは本ファイルに記録していた。**本ファイルも 67KB に到達したため、2026-05-09 に PR #101〜#109 由来の古い半分を [docs/todo7.md](todo7.md) へ分離した**。本ファイル残存は PR #110 以降のエントリのみ。新規エントリは [docs/todo6.md](todo6.md) へ。todo.md / todo3-22.md の既存エントリは引き続き有効、相互に独立。新セッションでは23つすべてを確認すること (todo.md / todo3-22.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
+> **本ファイルの位置付け**: docs/todo4.md がファイルサイズ約 50KB に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して PR #101 セッション以降の新規エントリは本ファイルに記録していた。**本ファイルも 67KB に到達したため、2026-05-09 に PR #101〜#109 由来の古い半分を [docs/todo7.md](todo7.md) へ分離した**。本ファイル残存は PR #110 以降のエントリのみ。新規エントリは [docs/todo6.md](todo6.md) へ。todo.md / todo3-23.md の既存エントリは引き続き有効、相互に独立。新セッションでは24つすべてを確認すること (todo.md / todo3-23.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
>
> **推奨実行順序**: 全タスク横断のサマリーは [docs/todo-summary.md](todo-summary.md#recommended-order-summary) を参照。
diff --git a/docs/todo6.md b/docs/todo6.md
index 055e5bce..c5e3d98e 100644
--- a/docs/todo6.md
+++ b/docs/todo6.md
@@ -2,7 +2,7 @@
> **運用ルール** ([docs/todo.md](todo.md) と同一): 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
>
-> **本ファイルの位置付け**: docs/todo5.md / 本ファイルが 50KB に到達 (PR #143 T3-#1) のため **新規エントリは [docs/todo8.md](todo8.md) へ移行**。本ファイルは既存タスクの編集・完了削除専用。todo.md / todo3-22.md の既存エントリは引き続き有効、相互に独立。新セッションでは23つすべてを確認すること (todo.md / todo3-22.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
+> **本ファイルの位置付け**: docs/todo5.md / 本ファイルが 50KB に到達 (PR #143 T3-#1) のため **新規エントリは [docs/todo8.md](todo8.md) へ移行**。本ファイルは既存タスクの編集・完了削除専用。todo.md / todo3-23.md の既存エントリは引き続き有効、相互に独立。新セッションでは24つすべてを確認すること (todo.md / todo3-23.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
>
> **推奨実行順序**: 全タスク横断のサマリーは [docs/todo-summary.md](todo-summary.md#recommended-order-summary) を参照。
diff --git a/docs/todo7.md b/docs/todo7.md
index 34190622..18771e7f 100644
--- a/docs/todo7.md
+++ b/docs/todo7.md
@@ -2,7 +2,7 @@
> **運用ルール** ([docs/todo.md](todo.md) と同一): 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
>
-> **本ファイルの位置付け**: docs/todo5.md がファイルサイズ 67KB に到達して Claude Code の読み取り安定性 (50KB 超で不安定化) を損なったため、2026-05-09 に **PR #101〜#109 由来の古い半分のタスクを本ファイルへ分離** した。todo5.md には PR #110 以降のタスクが残存。本ファイルは既存タスクの編集・完了削除専用、新規タスクは追加しない (新規エントリは [docs/todo6.md](todo6.md) へ)。todo.md / todo3-22.md の既存エントリは引き続き有効、相互に独立。新セッションでは23つすべてを確認すること (todo.md / todo3-22.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
+> **本ファイルの位置付け**: docs/todo5.md がファイルサイズ 67KB に到達して Claude Code の読み取り安定性 (50KB 超で不安定化) を損なったため、2026-05-09 に **PR #101〜#109 由来の古い半分のタスクを本ファイルへ分離** した。todo5.md には PR #110 以降のタスクが残存。本ファイルは既存タスクの編集・完了削除専用、新規タスクは追加しない (新規エントリは [docs/todo6.md](todo6.md) へ)。todo.md / todo3-23.md の既存エントリは引き続き有効、相互に独立。新セッションでは24つすべてを確認すること (todo.md / todo3-23.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
>
> **推奨実行順序**: 全タスク横断のサマリーは [docs/todo-summary.md](todo-summary.md#recommended-order-summary) を参照。
diff --git a/docs/todo8.md b/docs/todo8.md
index c066aeec..26366d83 100644
--- a/docs/todo8.md
+++ b/docs/todo8.md
@@ -2,7 +2,7 @@
> **運用ルール** ([docs/todo.md](todo.md) と同一): 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
>
-> **本ファイルの位置付け**: docs/todo6.md がファイルサイズ 50KB に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して PR #143 T3-#1 採用時 = 2026-05-11 から新規エントリは本ファイルに記録していた。**本ファイルも 60KB に到達したため、PR #172 仕組み化方針切替セッション = 2026-05-25 以降の新規エントリは [docs/todo9.md](todo9.md) へ移行**。本ファイルは既存タスクの編集・完了削除専用。**新規エントリの現在の追加先は [docs/todo22.md](todo22.md)** (todo9 → todo10 → todo13 → todo14 → todo20 → todo21 → todo22 と移動してきた)。todo.md / todo3.md 〜 todo7.md / todo9.md 〜 todo22.md の既存エントリは引き続き有効、相互に独立。新セッションでは23つすべてを確認すること (todo.md / todo3-22.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
+> **本ファイルの位置付け**: docs/todo6.md がファイルサイズ 50KB に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して PR #143 T3-#1 採用時 = 2026-05-11 から新規エントリは本ファイルに記録していた。**本ファイルも 60KB に到達したため、PR #172 仕組み化方針切替セッション = 2026-05-25 以降の新規エントリは [docs/todo9.md](todo9.md) へ移行**。本ファイルは既存タスクの編集・完了削除専用。**新規エントリの現在の追加先は [docs/todo23.md](todo23.md)** (todo9 → todo10 → todo13 → todo14 → todo20 → todo21 → todo22 と移動してきた)。todo.md / todo3.md 〜 todo7.md / todo9.md 〜 todo23.md の既存エントリは引き続き有効、相互に独立。新セッションでは24つすべてを確認すること (todo.md / todo3-23.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
>
> **推奨実行順序**: 全タスク横断のサマリーは [docs/todo-summary.md](todo-summary.md#recommended-order-summary) を参照。
diff --git a/docs/todo9.md b/docs/todo9.md
index c4153875..cea8dea8 100644
--- a/docs/todo9.md
+++ b/docs/todo9.md
@@ -2,7 +2,7 @@
> **運用ルール** ([docs/todo.md](todo.md) と同一): 各タスクには **やろうとしたこと / 現在地 / 詰まっている箇所** を必ず書く。完了タスクは ADR か仕組みに反映後、このファイルから削除する。過去の経緯は git log で追跡可能。
>
-> **本ファイルの位置付け**: docs/todo8.md がファイルサイズ 60KB に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して新規エントリは本ファイルに記録する (PR #172 仕組み化方針切替セッション = 2026-05-25)。todo.md / todo2.md 〜 todo8.md の既存エントリは引き続き有効、相互に独立。**2026-06-06 分割**: 本ファイルが 75KB / 890 行に到達したため、PR-specific follow-up entries (順位 157, 160-173) を [docs/todo11.md](todo11.md) に分離。残置: 既存ルール仕組み化バンドル (順位 146-151) + 週次レビュー拡張 (順位 152-154)。新セッションでは23つすべてを確認すること (todo.md / todo3-22.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
+> **本ファイルの位置付け**: docs/todo8.md がファイルサイズ 60KB に到達したため、Claude Code の読み取り安定性 (50KB 超で不安定化) を考慮して新規エントリは本ファイルに記録する (PR #172 仕組み化方針切替セッション = 2026-05-25)。todo.md / todo2.md 〜 todo8.md の既存エントリは引き続き有効、相互に独立。**2026-06-06 分割**: 本ファイルが 75KB / 890 行に到達したため、PR-specific follow-up entries (順位 157, 160-173) を [docs/todo11.md](todo11.md) に分離。残置: 既存ルール仕組み化バンドル (順位 146-151) + 週次レビュー拡張 (順位 152-154)。新セッションでは24つすべてを確認すること (todo.md / todo3-23.md / todo-summary.md / todo-summary2.md。todo2.md は 2026-08-12 退役)。
>
> **推奨実行順序**: 全タスク横断のサマリーは [docs/todo-summary.md](todo-summary.md#recommended-order-summary) を参照。
diff --git a/src/hooks-post-tool-linter/src/custom_rules/engine.rs b/src/hooks-post-tool-linter/src/custom_rules/engine.rs
index f3e40779..54308284 100644
--- a/src/hooks-post-tool-linter/src/custom_rules/engine.rs
+++ b/src/hooks-post-tool-linter/src/custom_rules/engine.rs
@@ -9,6 +9,7 @@ use crate::violation::{
emit_feedback, LintViolation, ViolationExample, ViolationFix, ViolationLocation,
MAX_CUSTOM_VIOLATIONS,
};
+use crate::repo_path::to_repo_relative;
use globset::{Glob, GlobSet, GlobSetBuilder};
use regex::Regex;
use std::path::{Path, PathBuf};
@@ -127,12 +128,15 @@ pub(crate) fn rule_matches_ext(rule: &CustomRule, file: &str) -> bool {
}
/// `compiled.paths_glob` が `None` (filter なし) または `Some(GlobSet)` で file path がマッチする場合 true。
+///
+/// 照合前に [`to_repo_relative`] を通すこと。rule の `paths` はリポジトリ相対 glob で書かれる
+/// のに hook 入力は絶対パスなので、素の文字列で照合すると**常に不一致 = rule が無言で
+/// 死ぬ** (2026-08-13 に `takt-workflow-persona-without-model` で実測)。
pub(crate) fn rule_matches_path(compiled: &CompiledRule, file: &str) -> bool {
let Some(globset) = compiled.paths_glob.as_ref() else {
return true;
};
- let normalized = file.replace('\\', "/");
- globset.is_match(&normalized)
+ globset.is_match(to_repo_relative(file))
}
/// 1 件の regex match と rule 定義から `LintViolation` の JSON 文字列を構築する。
diff --git a/src/hooks-post-tool-linter/src/custom_rules/engine_tests.rs b/src/hooks-post-tool-linter/src/custom_rules/engine_tests.rs
index 044997c8..3372bdf3 100644
--- a/src/hooks-post-tool-linter/src/custom_rules/engine_tests.rs
+++ b/src/hooks-post-tool-linter/src/custom_rules/engine_tests.rs
@@ -517,3 +517,36 @@ fn powershell_validation_returns_multiple_violators() {
let missing = find_powershell_rules_missing_case_insensitive_flag(&rules);
assert_eq!(missing, vec!["ps-a".to_string(), "ps-b".to_string()]);
}
+
+/// **hook が実際に渡すのは絶対パス**なので、`paths` 付き rule がその形で一致することを固定する。
+///
+/// 既存テストは相対パスのみを渡しており、`paths` を持つ rule が実運用で一度も発火しない
+/// 状態を通していた (2026-08-13 に `takt-workflow-persona-without-model` で実測確認)。
+#[test]
+fn paths_filter_matches_absolute_path_as_delivered_by_the_hook() {
+ let rule = make_test_rule_with_paths("test", "x", &["md"], &["docs/**/*.md"]);
+ let compiled = compile_rule(rule).expect("rule must compile");
+ let cwd = std::env::current_dir().expect("cwd");
+ let abs = format!("{}/docs/spec.md", cwd.to_string_lossy().replace('\\', "/"));
+ assert!(
+ rule_matches_path(&compiled, &abs),
+ "絶対パスで一致しないと paths 付き rule は無言で死ぬ: {}",
+ abs
+ );
+}
+
+/// リポジトリ外の絶対パスは `paths` に一致させない。
+///
+/// 固定パス (`/other-repo` 等) は実行環境の cwd とたまたま一致すると正しい実装でも落ちる。
+/// cwd 由来の sibling path なら cwd 自身とは必ず別ディレクトリになる。
+#[test]
+fn paths_filter_rejects_absolute_path_outside_repo() {
+ let rule = make_test_rule_with_paths("test", "x", &["md"], &["docs/**/*.md"]);
+ let compiled = compile_rule(rule).expect("rule must compile");
+ let cwd = std::env::current_dir().expect("cwd");
+ let outside = format!(
+ "{}-outside/docs/spec.md",
+ cwd.to_string_lossy().replace('\\', "/")
+ );
+ assert!(!rule_matches_path(&compiled, &outside));
+}
diff --git a/src/hooks-post-tool-linter/src/file_size_check.rs b/src/hooks-post-tool-linter/src/file_size_check.rs
index 59636b0c..8d2d33c0 100644
--- a/src/hooks-post-tool-linter/src/file_size_check.rs
+++ b/src/hooks-post-tool-linter/src/file_size_check.rs
@@ -7,6 +7,7 @@
//! ADR-039 § 3 Bounded lifetime: 3-5 PR の dogfood 後に default-ON 昇格 or 却下を判定。
use crate::config::{Config, FileSizeCheckConfig};
+use crate::repo_path::to_repo_relative;
use crate::violation::emit_feedback;
use globset::{Glob, GlobSet, GlobSetBuilder};
@@ -57,7 +58,7 @@ pub(crate) fn check_file_size_threshold(
return None;
}
};
- let normalized = file.replace('\\', "/");
+ let normalized = to_repo_relative(file);
if !glob_set.is_match(&normalized) {
return None;
}
@@ -120,6 +121,51 @@ mod tests {
);
}
+ /// **hook が実際に渡すのは絶対パス**なので、その形で glob に一致することを固定する。
+ ///
+ /// 既存テストは相対パスしか渡しておらず、`paths` が相対 glob であるために実運用では
+ /// 常に不一致 = 本検査が一度も発火しない状態を通してしまった (2026-08-13 に docs 配下の
+ /// 50KB 超 6 件が無警告で蓄積していたことで発覚)。入力の形まで再現しないテストは
+ /// 「動いているつもり」を固定する。
+ #[test]
+ fn file_size_check_matches_absolute_path_as_delivered_by_the_hook() {
+ let config = FileSizeCheckConfig {
+ enabled: true,
+ threshold_bytes: 51_200,
+ paths: vec!["docs/**/*.md".to_string()],
+ touch_trigger: true,
+ };
+ let cwd = std::env::current_dir().expect("cwd");
+ let abs = format!("{}/docs/oversized.md", cwd.to_string_lossy().replace('\\', "/"));
+ let result = check_file_size_threshold(&abs, 60_000, &config);
+ assert!(
+ result.is_some(),
+ "絶対パス入力で発火しないと実運用では無言の no-op になる: {}",
+ abs
+ );
+ }
+
+ /// リポジトリ外の絶対パスは対象外 (誤って検査しない)。
+ ///
+ /// 固定パス (`/other-repo` 等) を使うと、実行環境の cwd がたまたま一致した場合に
+ /// 正しい実装でも落ちる。cwd から派生させた sibling path なら、cwd 自身とは必ず
+ /// 別ディレクトリであることが構造的に保証される。
+ #[test]
+ fn file_size_check_skips_absolute_path_outside_repo() {
+ let config = FileSizeCheckConfig {
+ enabled: true,
+ threshold_bytes: 51_200,
+ paths: vec!["docs/**/*.md".to_string()],
+ touch_trigger: true,
+ };
+ let cwd = std::env::current_dir().expect("cwd");
+ let outside = format!(
+ "{}-outside/docs/x.md",
+ cwd.to_string_lossy().replace('\\', "/")
+ );
+ assert!(check_file_size_threshold(&outside, 60_000, &config).is_none());
+ }
+
#[test]
fn file_size_check_skips_when_path_does_not_match_glob() {
let config = FileSizeCheckConfig {
diff --git a/src/hooks-post-tool-linter/src/main.rs b/src/hooks-post-tool-linter/src/main.rs
index 6dd7784e..fd77ee19 100644
--- a/src/hooks-post-tool-linter/src/main.rs
+++ b/src/hooks-post-tool-linter/src/main.rs
@@ -17,6 +17,7 @@ mod config;
mod custom_rules;
mod file_size_check;
mod pipeline_runner;
+mod repo_path;
mod utf8_integrity;
mod violation;
diff --git a/src/hooks-post-tool-linter/src/repo_path.rs b/src/hooks-post-tool-linter/src/repo_path.rs
new file mode 100644
index 00000000..78e644fb
--- /dev/null
+++ b/src/hooks-post-tool-linter/src/repo_path.rs
@@ -0,0 +1,112 @@
+//! hook が受け取るファイルパスを、config の glob と同じ土俵 (リポジトリ相対) へ揃える層。
+//!
+//! # なぜ必要か
+//!
+//! `.claude/hooks-config.toml` の `paths` や `.claude/custom-lint-rules.toml` の `paths` は
+//! **リポジトリ相対の glob** (`docs/**/*.md` / `.takt/workflows/*.yaml`) で書く。一方
+//! Claude Code が PostToolUse hook に渡す `tool_input.file_path` は **絶対パス**
+//! (`C:/Users/.../repo/docs/guide.md`) である。
+//!
+//! 両者をそのまま照合すると `docs/` 始まりの glob は `C:/` 始まりの文字列に一致せず、
+//! **フィルタが常に不一致 = 検査が無言で no-op になる**。2026-08-13 の調査で、
+//! `file_size_check` (50KB 閾値) と `paths` 付き custom rule の両方がこの理由で一度も
+//! 発火していなかったことが実測で判明した (docs 配下に 50KB 超が 6 件蓄積していた)。
+//!
+//! # 正規化の方針
+//!
+//! 現在の作業ディレクトリ (hook はリポジトリ root で起動される) を prefix として剥がす。
+//! 剥がせない場合は**元の文字列をそのまま返す** — 判定を「マッチしない」側へ倒すことで、
+//! リポジトリ外のファイルを誤って検査対象に含めない (fail-safe)。
+
+/// 区切りを `/` に揃え、可能なら現在の作業ディレクトリからの相対パスへ変換する。
+///
+/// Windows ではドライブレターの大小が経路によって揺れる (`C:` / `c:`) ため、prefix 比較は
+/// 大小無視で行う。返す値は元の文字列から切り出すので、パス自体の大小は保たれる。
+pub(crate) fn to_repo_relative(file: &str) -> String {
+ let normalized = file.replace('\\', "/");
+ let Ok(cwd) = std::env::current_dir() else {
+ return normalized;
+ };
+ let root = cwd.to_string_lossy().replace('\\', "/");
+ let root = root.strip_suffix('/').unwrap_or(&root);
+ if root.is_empty() {
+ return normalized;
+ }
+ strip_root_prefix(&normalized, root).unwrap_or(normalized)
+}
+
+/// `normalized` が `root` 配下なら、root を剥がした相対パスを返す。
+///
+/// `root` の直後が `/` であることまで確認する。これが無いと `/repo` と `/repo-backup` の
+/// ように prefix を共有する別ディレクトリを同一視してしまう。
+fn strip_root_prefix(normalized: &str, root: &str) -> Option {
+ let head = normalized.get(..root.len())?;
+ if !head.eq_ignore_ascii_case(root) {
+ return None;
+ }
+ let rest = normalized.get(root.len()..)?;
+ let rest = rest.strip_prefix('/')?;
+ if rest.is_empty() {
+ return None;
+ }
+ Some(rest.to_string())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn backslashes_become_forward_slashes() {
+ assert_eq!(
+ to_repo_relative("docs\\adr\\adr-001.md"),
+ "docs/adr/adr-001.md"
+ );
+ }
+
+ /// リポジトリ外のパスは相対化できないのでそのまま返る (= glob に一致せず対象外)。
+ #[test]
+ fn path_outside_repo_is_returned_as_is() {
+ let outside = if cfg!(windows) {
+ "D:/elsewhere/docs/x.md"
+ } else {
+ "/elsewhere/docs/x.md"
+ };
+ assert_eq!(to_repo_relative(outside), outside);
+ }
+
+ /// 実際の hook 入力形式 (cwd 配下の絶対パス) が相対へ落ちること。
+ ///
+ /// **本 crate の検査層はすべてこの形の入力を受け取る。** 相対パスだけを渡すテストでは
+ /// 正規化の欠落を検出できず、実際に `file_size_check` と `paths` 付き custom rule が
+ /// 無言で no-op になっていた (2026-08-13)。
+ #[test]
+ fn absolute_path_under_cwd_becomes_repo_relative() {
+ let cwd = std::env::current_dir().expect("cwd");
+ let abs = format!("{}/docs/guide.md", cwd.to_string_lossy().replace('\\', "/"));
+ assert_eq!(to_repo_relative(&abs), "docs/guide.md");
+ }
+
+ #[test]
+ fn absolute_path_with_backslashes_becomes_repo_relative() {
+ let cwd = std::env::current_dir().expect("cwd");
+ let abs = format!("{}\\src\\main.rs", cwd.to_string_lossy());
+ assert_eq!(to_repo_relative(&abs), "src/main.rs");
+ }
+
+ /// prefix を共有する別ディレクトリ (`-backup`) を repo 配下と誤認しない。
+ #[test]
+ fn sibling_directory_sharing_the_prefix_is_not_stripped() {
+ let cwd = std::env::current_dir().expect("cwd");
+ let sibling = format!("{}-backup/docs/x.md", cwd.to_string_lossy().replace('\\', "/"));
+ assert_eq!(to_repo_relative(&sibling), sibling);
+ }
+
+ /// cwd 自身 (末尾にファイル名が無い) は相対化しない。
+ #[test]
+ fn cwd_itself_is_returned_as_is() {
+ let cwd = std::env::current_dir().expect("cwd");
+ let root = cwd.to_string_lossy().replace('\\', "/");
+ assert_eq!(to_repo_relative(&root), root);
+ }
+}