throughline 0.3.23 → 0.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.claude/commands/tl-trim.md +42 -0
  2. package/.codex-sidecar.yml +62 -0
  3. package/CHANGELOG.md +583 -0
  4. package/README.ja.md +42 -5
  5. package/README.md +400 -23
  6. package/bin/throughline.mjs +168 -4
  7. package/codex/skills/throughline/SKILL.md +157 -0
  8. package/codex/skills/throughline/agents/openai.yaml +7 -0
  9. package/docs/INHERITANCE_ON_CLEAR_ONLY.md +146 -0
  10. package/docs/L1_L2_L3_REDESIGN.md +415 -0
  11. package/docs/PUBLIC_RELEASE_PLAN.md +184 -0
  12. package/docs/THROUGHLINE_CODEX_DUAL_SUPPORT.md +249 -0
  13. package/docs/THROUGHLINE_CODEX_FIRST_ROADMAP.md +555 -0
  14. package/docs/THROUGHLINE_CODEX_MONITOR_IMPLEMENTATION_PLAN.md +220 -0
  15. package/docs/THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md +528 -0
  16. package/docs/THROUGHLINE_CODEX_TRIM_ROLLBACK_FIX_PLAN.md +672 -0
  17. package/docs/archive/CONCEPT.md +476 -0
  18. package/docs/archive/EXPERIMENT.md +371 -0
  19. package/docs/archive/README.md +22 -0
  20. package/docs/archive/SESSION_LINKING_DESIGN.md +231 -0
  21. package/docs/archive/THROUGHLINE_NEXT_STEPS.md +134 -0
  22. package/docs/throughline-codex-trim-rollback-incident-report.md +306 -0
  23. package/docs/throughline-handoff-context.example.json +57 -0
  24. package/docs/throughline-rollback-context-trim-insight.md +455 -0
  25. package/package.json +6 -2
  26. package/src/cli/codex-capture.mjs +95 -0
  27. package/src/cli/codex-handoff-model-smoke.mjs +292 -0
  28. package/src/cli/codex-handoff-model-smoke.test.mjs +262 -0
  29. package/src/cli/codex-handoff-smoke.mjs +163 -0
  30. package/src/cli/codex-handoff-smoke.test.mjs +149 -0
  31. package/src/cli/codex-handoff-start.mjs +291 -0
  32. package/src/cli/codex-handoff-start.test.mjs +194 -0
  33. package/src/cli/codex-hook.mjs +276 -0
  34. package/src/cli/codex-hook.test.mjs +293 -0
  35. package/src/cli/codex-host-primitive-audit.mjs +110 -0
  36. package/src/cli/codex-host-primitive-audit.test.mjs +75 -0
  37. package/src/cli/codex-restore-smoke.mjs +357 -0
  38. package/src/cli/codex-restore-source-audit.mjs +304 -0
  39. package/src/cli/codex-resume.mjs +138 -0
  40. package/src/cli/codex-rollback-model-visible-smoke.mjs +373 -0
  41. package/src/cli/codex-rollback-model-visible-smoke.test.mjs +255 -0
  42. package/src/cli/codex-sidecar-diagnostics.mjs +48 -0
  43. package/src/cli/codex-sidecar-dry-run.mjs +85 -0
  44. package/src/cli/codex-summarize.mjs +224 -0
  45. package/src/cli/codex-threads.mjs +89 -0
  46. package/src/cli/codex-visibility-smoke.mjs +196 -0
  47. package/src/cli/codex-vscode-restore-smoke.mjs +226 -0
  48. package/src/cli/codex-vscode-rollback-smoke.mjs +114 -0
  49. package/src/cli/doctor.mjs +503 -1
  50. package/src/cli/doctor.test.mjs +542 -3
  51. package/src/cli/handoff-preview.mjs +78 -0
  52. package/src/cli/help.test.mjs +64 -0
  53. package/src/cli/install.mjs +227 -4
  54. package/src/cli/install.test.mjs +207 -4
  55. package/src/cli/trim.mjs +564 -0
  56. package/src/codex-app-server.mjs +1816 -0
  57. package/src/codex-app-server.test.mjs +512 -0
  58. package/src/codex-auto-refresh.mjs +194 -0
  59. package/src/codex-auto-refresh.test.mjs +182 -0
  60. package/src/codex-capture.mjs +235 -0
  61. package/src/codex-capture.test.mjs +393 -0
  62. package/src/codex-handoff-model-smoke.mjs +114 -0
  63. package/src/codex-handoff-model-smoke.test.mjs +89 -0
  64. package/src/codex-handoff-smoke.mjs +124 -0
  65. package/src/codex-handoff-smoke.test.mjs +103 -0
  66. package/src/codex-handoff.mjs +331 -0
  67. package/src/codex-handoff.test.mjs +220 -0
  68. package/src/codex-host-primitive-audit.mjs +374 -0
  69. package/src/codex-host-primitive-audit.test.mjs +208 -0
  70. package/src/codex-restore-smoke.test.mjs +639 -0
  71. package/src/codex-restore-source-audit.mjs +1348 -0
  72. package/src/codex-restore-source-audit.test.mjs +623 -0
  73. package/src/codex-resume.test.mjs +242 -0
  74. package/src/codex-rollout-memory.mjs +711 -0
  75. package/src/codex-rollout-memory.test.mjs +610 -0
  76. package/src/codex-sidecar-cli.test.mjs +75 -0
  77. package/src/codex-sidecar.mjs +246 -0
  78. package/src/codex-sidecar.test.mjs +172 -0
  79. package/src/codex-summarize.test.mjs +143 -0
  80. package/src/codex-thread-identity.mjs +23 -0
  81. package/src/codex-thread-index.mjs +173 -0
  82. package/src/codex-thread-index.test.mjs +164 -0
  83. package/src/codex-usage.mjs +110 -0
  84. package/src/codex-usage.test.mjs +140 -0
  85. package/src/codex-visibility-smoke.test.mjs +222 -0
  86. package/src/codex-vscode-restore-smoke.mjs +206 -0
  87. package/src/codex-vscode-restore-smoke.test.mjs +325 -0
  88. package/src/codex-vscode-rollback-smoke.mjs +90 -0
  89. package/src/codex-vscode-rollback-smoke.test.mjs +290 -0
  90. package/src/db-schema.test.mjs +97 -0
  91. package/src/haiku-summarizer.mjs +267 -26
  92. package/src/haiku-summarizer.test.mjs +282 -0
  93. package/src/handoff-preview.test.mjs +108 -0
  94. package/src/handoff-record.mjs +294 -0
  95. package/src/handoff-record.test.mjs +226 -0
  96. package/src/hook-entrypoints.test.mjs +326 -0
  97. package/src/package-files.test.mjs +19 -0
  98. package/src/prompt-submit.mjs +9 -6
  99. package/src/resume-context.mjs +44 -140
  100. package/src/resume-context.test.mjs +172 -0
  101. package/src/session-start.mjs +8 -5
  102. package/src/state-file.mjs +50 -6
  103. package/src/state-file.test.mjs +50 -0
  104. package/src/token-monitor.mjs +14 -10
  105. package/src/token-monitor.test.mjs +27 -0
  106. package/src/trim-cli.test.mjs +1584 -0
  107. package/src/trim-model.mjs +584 -0
  108. package/src/trim-model.test.mjs +568 -0
  109. package/src/turn-processor.mjs +17 -10
  110. package/src/vscode-task.mjs +94 -6
  111. package/src/vscode-task.test.mjs +186 -6
@@ -0,0 +1,371 @@
1
+ # Throughline 実験シート
2
+
3
+ このファイルは「session_id と `/clear` の挙動」について、推論ではなく**生データだけ**から事実を確定するための記録用紙です。
4
+
5
+ **ルール:**
6
+ - 私(Claude)の口頭での結論は信用しない
7
+ - すべての命題は「誰でも追跡できる生データの参照」で裏付ける
8
+ - 結果は「確定 / 反証 / 未検証」の 3 状態のみ
9
+ - 曖昧な場合は未検証のまま
10
+
11
+ ---
12
+
13
+ > **実装設計**: 命題 X の具体的な実装設計と実験プロトコルは [SESSION_LINKING_DESIGN.md](SESSION_LINKING_DESIGN.md) を参照。
14
+
15
+ ## 最終目標(Goal)
16
+
17
+ **旧 session_id と新 session_id を `/clear` を跨いで 1 対 1 で紐づける。**
18
+
19
+ 紐づけができれば:
20
+ - 記憶は `/clear` を跨いで連鎖する(A → A2 → A3 …)
21
+ - 並行して走る別作業(セッション B)の記憶は混ざらない
22
+ - project_path + 時間窓のような曖昧推測は不要
23
+
24
+ この目標に対する現行の課題:`/clear` 直後の UserPromptSubmit には新 session_id だけが届き、旧 session_id は含まれていない。旧と新を繋ぐ「糊」がどこにあるのかが不明。
25
+
26
+ ---
27
+
28
+ ## 命題一覧
29
+
30
+ ### 命題 A: `/clear` を実行すると session_id は変わる
31
+
32
+ - **状態**: 未検証
33
+ - **検証方法**:
34
+ 1. 現在の session_id を記録
35
+ 2. ユーザーが `/clear` を実行
36
+ 3. `/clear` 後に何かメッセージを送信
37
+ 4. 新しいメッセージの UserPromptSubmit ログに記録された session_id を読む
38
+ 5. 変化していれば確定、同じなら反証
39
+ - **必要なログ**: `~/.throughline/spike/hooks.log`(UserPromptSubmit を含む全イベント)
40
+ - **生データ参照**:
41
+ - Before: hooks.log 9493 行、`2026-04-15T05:51:38.277Z`、session_id = `a2335bc7-c354-4172-ab89-abff7c7b6ee6`、prompt = `"確認"`
42
+ - After: hooks.log 9656 行、`2026-04-15T05:53:34.961Z`、session_id = `42065160-337c-40c7-8066-2807aa312270`、prompt = 実験続きプロンプト
43
+ - **結果**: **確定**(a2335bc7 → 42065160 に変化)
44
+
45
+ ---
46
+
47
+ ### 命題 B: Stop フックは `/clear` の直前に必ず発火する
48
+
49
+ - **状態**: **反証**(`/clear` そのものでは Stop が発火しない)
50
+ - **検証方法**:
51
+ 1. `/clear` 実行直前の session_id を記録
52
+ 2. `/clear` 実行
53
+ 3. hooks.log を読み、最後の Stop エントリのタイムスタンプと session_id が「`/clear` 直前の session_id」と一致し、タイムスタンプが `/clear` 直前であるか確認
54
+ - **生データ参照**:
55
+ - `/clear` 直前の session_id: `a2335bc7-c354-4172-ab89-abff7c7b6ee6`
56
+ - `/clear` 前の最後の Stop エントリ: hooks.log 9614 行、`2026-04-15T05:52:47.751Z`、session_id = `a2335bc7...`(`last_assistant_message` = 「以下を `/clear` 後にそのまま貼ってください…」= 応答完了時の Stop)
57
+ - `/clear` 前の最後の UserPromptSubmit: hooks.log 9574 行、`2026-04-15T05:52:29.516Z`、prompt = 「clear 後に君は記憶を失うかもしれない…コピペ案をください」
58
+ - `/clear` 後の最初の UserPromptSubmit: hooks.log 9656 行、`2026-04-15T05:53:34.961Z`、新 session_id = `42065160...`
59
+ - **9614(Stop, 05:52:47)と 9656(UserPromptSubmit, 05:53:34)の間に Stop エントリは存在しない**
60
+ - **結果**: 反証。`/clear` 操作そのものでは Stop フックは発火していない。9614 の Stop は「コピペ案をください」への応答完了時の Stop であり、`/clear` トリガーではない。
61
+
62
+ ---
63
+
64
+ ### 命題 C: Stop フックの stdin には「そのセッションの」session_id が含まれる
65
+
66
+ - **状態**: **確定**
67
+ - **検証方法**: 命題 B の Stop エントリの `stdin.session_id` を確認
68
+ - **生データ参照**: hooks.log 9614 行の Stop エントリ、`stdin.parsed.session_id = "a2335bc7-c354-4172-ab89-abff7c7b6ee6"`(`/clear` 前のセッションと一致)
69
+ - **結果**: 確定(Stop の stdin には発火したセッションの session_id が含まれる)
70
+
71
+ ---
72
+
73
+ ### 命題 D: `/clear` 後の最初の UserPromptSubmit で新 session_id が届く
74
+
75
+ - **状態**: **確定**
76
+ - **検証方法**: 命題 A と同じデータで判定
77
+ - **生データ参照**: hooks.log 9656 行、session_id = `42065160-337c-40c7-8066-2807aa312270`(`/clear` 前の a2335bc7 と異なる新 ID)
78
+ - **結果**: 確定
79
+
80
+ ---
81
+
82
+ ### 命題 G: Stop フックが発火するタイミングは何か?
83
+
84
+ - **状態**: **確定** — (c) アシスタントが応答を返し終えるたびに発火する
85
+ - **過去に私が言った候補:**
86
+ - (a)「Stop は `/clear` の直前に発火する」 → **反証**(命題 B 参照)
87
+ - (b)「Stop はセッション終了時に発火する」 → **反証**(1 セッションで複数回発火している)
88
+ - (c)「Stop はアシスタントが応答を返し終えるたびに発火する」 → **確定**
89
+ - **生データ参照**:
90
+ - hooks.log 9493 UserPromptSubmit「確認」→ 9532 Stop(`last_assistant_message` = 「前提整いました。生データ:…」)
91
+ - hooks.log 9574 UserPromptSubmit「コピペ案をください」→ 9614 Stop(`last_assistant_message` = 「以下を /clear 後にそのまま…」)
92
+ - UserPromptSubmit と Stop が 1 対 1 で対応
93
+ - Stop の stdin には `last_assistant_message` フィールドがあり、直前に完了したアシスタント応答の全文が入っている
94
+ - **結果**: 確定(1 ターン分のアシスタント応答が完全に終わったタイミングで発火)
95
+
96
+ ---
97
+
98
+ ### 命題 E: SessionStart フックは `/clear` 時に発火しない
99
+
100
+ - **状態**: **反証**
101
+ - **生データ参照**:
102
+ - session-start.log 4 件目: `2026-04-15T05:53:31.674Z`、session_id = `42065160-337c-40c7-8066-2807aa312270`、`source: "startup"`
103
+ - この 42065160 は `/clear`(05:52:47 の Stop のあと、05:53:34 の UserPromptSubmit より前)で発生した新セッション
104
+ - つまり `/clear` 後にも SessionStart が発火している(ただし source は `"startup"`)
105
+ - **結果**: 反証。`/clear` 後も SessionStart は発火する。VSCode 拡張では /clear が内部的に新プロセス扱いになっている可能性。
106
+
107
+ ---
108
+
109
+ ### 命題 F: SessionStart フックの source 値として現時点で観測されたのは "startup" のみ
110
+
111
+ - **状態**: 確定
112
+ - **生データ参照**: `~/.throughline/spike/session-start.log` 4 件すべて `source: "startup"`(75a05214, 977cfe3a, a2335bc7, 42065160)
113
+ - **結果**: 確定。`/clear` 後の新セッションでも `source` は `"startup"`。`"clear"` / `"resume"` は未観測。
114
+
115
+ ---
116
+
117
+ ### 命題 H: SessionStart フックは新セッションの最初の UserPromptSubmit より前に発火する
118
+
119
+ - **状態**: **確定**
120
+ - **生データ参照**:
121
+ - session-start.log 4 件目: `2026-04-15T05:53:31.674Z`、session_id = `42065160...`
122
+ - hooks.log 9656 行、最初の UserPromptSubmit: `2026-04-15T05:53:34.961Z`、session_id = `42065160...`
123
+ - 差分: SessionStart が UserPromptSubmit の **約 3 秒前**に発火
124
+ - **結果**: 確定。ユーザー発言を待たずに新 session_id を取得できる。
125
+
126
+ ---
127
+
128
+ ## 核心命題: 旧 → 新 session_id の紐づけ
129
+
130
+ ### 命題 X: Stop フックが旧 session_id をファイルに書き、次の SessionStart or UserPromptSubmit がそれを読めば、旧 → 新の紐づけが成立する
131
+
132
+ - **状態**: **前提条件すべて確定。実装可能。**
133
+ - **前提条件(更新版):**
134
+ - ~~命題 B: Stop が `/clear` 直前に必ず発火する~~ → 反証。ただし命題 G で代替:**毎ターン末尾で Stop が発火するので、常に最新の旧 session_id がファイルに上書きされ続ける**。`/clear` 直前の Stop は「直前のアシスタント応答完了時の Stop」で十分。
135
+ - 命題 C: Stop の stdin に旧 session_id が含まれる ✅ 確定
136
+ - 命題 D: `/clear` 後の最初の UserPromptSubmit で新 session_id が届く ✅ 確定
137
+ - 命題 G: Stop は応答完了ごとに発火 ✅ 確定
138
+ - 命題 H: SessionStart は UserPromptSubmit より前に発火し、新 session_id が届く ✅ 確定(UserPromptSubmit より 3 秒早い)
139
+ - **実装案(改訂):**
140
+ 1. Stop フック: `~/.throughline/last-session-by-project/<project_hash>` に旧 session_id を書き込む(毎ターン上書き)
141
+ 2. SessionStart フック: 新 session_id を受け取ったとき、上記ファイルを読んで旧 session_id を取得 → DB で記憶を新 session_id に移管(UserPromptSubmit より前に完了するので再注入に間に合う)
142
+ - **既知の課題(サブ命題 X-1):** 並行セッション B が同一 project_path で動いている場合、B の Stop がファイルを上書きして A の紐づけを破壊する(未解決)
143
+
144
+ #### 命題 X の実測検証 #1(2026-04-15 07:07〜07:08)
145
+
146
+ - **状態**: **反証**(10 秒窓の設定では紐づけ不成立)
147
+ - **実装**: `spike/session-link-writer.mjs`(Stop), `spike/session-link-reader.mjs`(SessionStart), 窓 = 10000ms
148
+ - **手順**: /clear 直前の最終応答 Stop でファイル書き込み → /clear 実行 → 新セッションで SessionStart が読む
149
+ - **生データ参照**:
150
+ - project_hash: `c86cce038550be3b`(ドキュメント記載の `b0b9520facad6366` は誤り)
151
+ - state file (`~/.throughline/session-link/c86cce038550be3b.json`):
152
+ ```json
153
+ {"old_session_id":"42065160-337c-40c7-8066-2807aa312270","ts":1776236862765,"state":"open"}
154
+ ```
155
+ - link.log 末尾:
156
+ - (a) `2026-04-15T07:07:42.766Z` op=write, old_session_id=`42065160-337c-40c7-8066-2807aa312270`(/clear 直前の Stop)
157
+ - (b) `2026-04-15T07:08:02.952Z` op=**read-miss-stale**, new=`34cf32a9-cc54-4710-a799-135e818176d0`, source=startup, **elapsed_ms=20187**, old_session_id=42065160...
158
+ - link-success エントリ **無し**、state-closed エントリ **無し**
159
+ - session-start.log 5 件目: `2026-04-15T07:08:02.933Z`, session_id=`34cf32a9-cc54-4710-a799-135e818176d0`, source=startup
160
+ - SessionStart(07:08:02.933Z)→ reader 処理(07:08:02.952Z)の順序は確定(差 19ms)
161
+ - **判定:**
162
+ - ❌ state="open" のまま(closed になっていない)
163
+ - ❌ link-success が記録されていない
164
+ - ❌ elapsed_ms=20187 > 10000ms 窓(stale 判定)
165
+ - ✅ old=42065160 / new=34cf32a9 は期待どおり
166
+ - ✅ 書き込み → SessionStart → 読み取りの順序は正しく動作
167
+ - **失敗原因の生データ**: Stop (07:07:42.766Z) と SessionStart (07:08:02.933Z) の間隔 = 20.167 秒。10 秒窓では収まらない。
168
+ - **次の判断材料**: 窓を 60 秒または 300 秒に拡張して再試行するか、窓そのものを廃止して「紐づけ完了後のみ close」方式で運用するか要検討。cwd の大小文字違い(write="C:\\..." vs read="c:\\...")も観測されたが、ハッシュは一致しておりこの要因では失敗していない。
169
+
170
+ #### 命題 X の次アプローチ計画
171
+
172
+ 10 秒窓を拡大する以外に、根本的に別ルートがないかを調べる。優先度順:
173
+
174
+ - **アプローチ 1: `/clear` 操作に関連して発火し、旧 session_id を取得できる hook/メカニズムが他に存在しないか調査**
175
+ - 既知の hook: PostToolUse, Stop, PreCompact, UserPromptSubmit, SessionStart
176
+ - 未確認: SessionEnd, Notification, その他ドキュメント化されていない hook
177
+ - `/clear` に対応する専用 hook(`OnClear` 的なもの)の有無
178
+ - PreCompact hook が `/clear` でも発火するか(manual trigger の挙動)
179
+ - SessionStart の stdin に旧 session_id が含まれる隠しフィールドの有無
180
+ - **検証方法**: 公式ドキュメント調査 + spike/hook-logger.mjs で全 hook を記録しながら `/clear` を実行、差分観測
181
+
182
+ - **アプローチ 2: スキル/hook から UI セッションを操作する手段の調査**
183
+ - スキルや hook の中から Claude Code の UI アクション(`/clear`、新セッション起動)を発動する API の有無
184
+ - Bash から `claude` CLI を起動した場合の挙動(独立プロセスになるか、UI 置換できるか)
185
+ - VSCode 拡張のコマンドパレット項目を外部から叩く手段の有無
186
+ - **検証方法**: 公式ドキュメント調査 + claude-code-guide エージェント経由での確認
187
+
188
+ 現在アプローチ 1 を実行中。
189
+
190
+ ##### アプローチ 1 調査ログ(2026-04-15)
191
+
192
+ ###### ドキュメント調査結果(未検証・公式ドキュメント記載ベース)
193
+
194
+ claude-code-guide エージェント経由で [code.claude.com/docs/en/hooks](https://code.claude.com/docs/en/hooks) を参照した結果:
195
+
196
+ - **公式 hook 全 12 種**: SessionStart, UserPromptSubmit, PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, Stop, StopFailure, SubagentStart, SubagentStop, PreCompact, SessionEnd
197
+ - **`/clear` 専用 hook は存在しない**
198
+ - **SessionStart の source 値は 4 種**: `startup` / `resume` / `clear` / `compact`(ドキュメント上)
199
+ - **SessionEnd hook が存在**: stdin に `session_id`, `transcript_path`, `reason` を含む。ただしドキュメントは「`/clear` では発火しない」と主張
200
+ - **PreCompact は `/clear` では発火しない**(ドキュメント主張)
201
+ - **SessionStart stdin の前セッション ID 隠しフィールドは記載なし**
202
+
203
+ ###### ドキュメントと命題 F の矛盾点
204
+
205
+ | 項目 | ドキュメント | 命題 F(実機 5 件) |
206
+ |---|---|---|
207
+ | SessionStart の source | "clear" が存在 | 全て "startup" |
208
+
209
+ この食い違いは以下のいずれかで説明される(未確定):
210
+ - (a) Windows + VSCode 拡張環境ではドキュメントと挙動が異なる
211
+ - (b) ドキュメントが一部誤記または未実装機能を記載している
212
+ - (c) 特定バージョンからの新機能で、手元環境が未対応
213
+
214
+ ###### 実機検証プロトコル(次に実行)
215
+
216
+ 1. `.claude/settings.json` に spike/hook-logger.mjs を以下の hook 全てに登録 ✅ 済
217
+ - SessionEnd, StopFailure, SubagentStart, SubagentStop, PostToolUseFailure, PermissionRequest, PreCompact, Notification
218
+ 2. ユーザーが `/clear` を実行(現セッション終了)
219
+ 3. 新セッションで任意のメッセージ送信
220
+ 4. hooks.log と session-start.log の差分から以下を観測:
221
+ - (i) SessionEnd が発火したか(`/clear` のタイミングで)
222
+ - (ii) SessionEnd stdin に旧 session_id が含まれるか
223
+ - (iii) SessionStart の source が "clear" に変わるか
224
+ - (iv) SessionStart stdin に隠しフィールド(previous_session_id 等)があるか
225
+ - (v) PreCompact, Notification など他の hook が発火するか
226
+ 5. 結果を命題 F・命題 E の再検証として記録
227
+ 6. SessionEnd が /clear で発火 + 旧 session_id 取得可能なら、**命題 X の時間窓問題は解決**(SessionEnd → SessionStart の間隔は Claude Code 内部処理のみで、ユーザー操作時間を含まない)
228
+
229
+ ###### /clear 実行前の記録(Before)
230
+
231
+ - 現セッション session_id: `34cf32a9-cc54-4710-a799-135e818176d0`
232
+ - hooks.log 行数: 11047(これ以降が After 差分)
233
+ - session-start.log 行数: 180
234
+ - session-link/link.log 行数: 15
235
+ - state ファイル: `c86cce038550be3b.json`, state="open", old=42065160(古い、次の Stop で 34cf32a9 に上書きされる想定)
236
+
237
+ ###### 実機検証結果(2026-04-15 07:44〜07:46)
238
+
239
+ - **状態**: アプローチ 1 **反証**(/clear で追加 hook は 1 つも発火せず)
240
+ - **新 session_id**: `0129aeb8-b83d-44fe-9d4f-46a8d1adbb02`
241
+
242
+ **After 差分で発火した hook 集計**(`hooks.log` 11048 行目以降、`grep -oE '"hook_event_name": "[^"]*"' | uniq -c`):
243
+
244
+ | hook | 発火回数 |
245
+ |---|---|
246
+ | Stop | 3 |
247
+ | UserPromptSubmit | 3 |
248
+ | PermissionRequest | 7 |
249
+ | SessionEnd | **0** |
250
+ | PreCompact | **0** |
251
+ | Notification | **0** |
252
+ | StopFailure | **0** |
253
+ | SubagentStart | **0** |
254
+ | SubagentStop | **0** |
255
+ | PostToolUseFailure | **0** |
256
+
257
+ (SessionStart は hooks.log ではなく session-start.log に記録される別経路。1 件発火)
258
+
259
+ **個別観測:**
260
+
261
+ - (a) **SessionEnd: 未発火**。`.claude/settings.json` には `{ "SessionEnd": [{ "command": "node spike/hook-logger.mjs SessionEnd" }] }` を登録済み。PermissionRequest が 7 回正しく発火していることから、追加 hook 登録そのものは有効。にもかかわらず /clear 経路で SessionEnd は一度も呼ばれなかった。
262
+ - (b) **SessionStart の source**: `"startup"` のまま(session-start.log 181 行目、`parsed.source = "startup"`、新 session_id `0129aeb8-b83d-44fe-9d4f-46a8d1adbb02`)。命題 F 再確認。
263
+ - (c) **SessionStart stdin の隠しフィールド**: **無し**。parsed キーは `session_id`, `transcript_path`, `cwd`, `hook_event_name`, `source` の 5 つのみ。`previous_session_id` / `parent_session_id` / `resumed_from` 類のフィールドは存在しない。
264
+ - (d) **PreCompact / Notification / SubagentStart/Stop / StopFailure / PostToolUseFailure**: 全て発火回数 0。
265
+ - (e) **PermissionRequest は発火した**(7 回、全て Bash tool の許可要求)— これは登録が有効であることの動作証明になる。
266
+
267
+ **link.log 観測:**
268
+
269
+ ```
270
+ 2026-04-15T07:44:34.928Z op=write old=34cf32a9... (旧セッションの /clear 直前 Stop)
271
+ 2026-04-15T07:45:05.233Z op=read-miss-stale new=0129aeb8... source=startup elapsed_ms=30305 old=34cf32a9...
272
+ 2026-04-15T07:45:16.445Z op=write old=0129aeb8... (新セッション最初の Stop)
273
+ 2026-04-15T07:46:02.901Z op=write old=34cf32a9... (**並行セッションからの Stop**)
274
+ ```
275
+
276
+ - **elapsed_ms = 30305** > 10000ms → stale。命題 X 実測 #1(20.167 秒)より更に長くなった。
277
+ - link-success / state-closed のエントリは依然として無し。
278
+
279
+ **命題 X-1(並行セッション問題)の実機発現:**
280
+
281
+ `07:46:02.901Z` の write は、現セッション `0129aeb8` が動作中であるにも拘らず旧セッション `34cf32a9` から発火している。これは hooks.log でも確認できる:
282
+ - 11183 行: `UserPromptSubmit` session_id=`34cf32a9`, prompt="次のセッションのClaudeが何やったらいいかわかんないみたい…"
283
+ - 11223 行: `Stop` session_id=`34cf32a9`, last_assistant_message="以下を `/clear` 後にそのまま貼ってください…"
284
+
285
+ つまり、ユーザーは `/clear` して `0129aeb8` を開いた後も、旧セッション `34cf32a9` の別ウィンドウを生かして追加の指示を出していた。旧セッションの Stop が state ファイルを上書きし、現セッション (`0129aeb8`) の視点からは「自分でない session_id が state に書かれている」状況になっている(state.json は old=`34cf32a9`, 現在は `0129aeb8`)。命題 X-1 の破綻シナリオが机上ではなく実機で起きた。
286
+
287
+ ###### 判定
288
+
289
+ | 観測項目 | 結果 |
290
+ |---|---|
291
+ | SessionEnd が /clear で発火 → 時間窓問題解決 | ❌ 発火せず |
292
+ | SessionStart source が "clear" | ❌ "startup" のまま |
293
+ | SessionStart stdin に隠しフィールド | ❌ 無し |
294
+ | 他の追加 hook(PreCompact 等)で旧 session_id 取得 | ❌ 全て発火せず |
295
+
296
+ **アプローチ 1 は「追加 hook ルートなし」で確定(反証)。** Windows + VSCode 拡張環境では、ドキュメント記載の SessionEnd(`reason: "clear"`), SessionStart source `"clear"`, PreCompact 等はいずれも実機で確認できなかった。
297
+
298
+ **次の方針候補:**
299
+
300
+ 1. **アプローチ 2 に移行**: スキル/hook から UI セッション操作を試みる路線の調査
301
+ 2. **時間窓の大幅拡張 + X-1 対策**: 窓を 300 秒以上に拡張した上で、並行セッション衝突を transcript_path などで弁別するキー設計に変更
302
+ 3. **窓廃止 + 使い捨てファイル方式**: Stop が書くファイル名に session_id を含め、SessionStart 時点では「最新の変更時刻を持つ *自分でない* session_id のファイル」を選ぶ方式。ただし並行セッション B の影響は残る
303
+
304
+
305
+ ---
306
+
307
+ ### 命題 X-1: 並行セッション問題 — 同一 project_path で複数セッションが並行するとファイル方式は破綻するか
308
+
309
+ - **状態**: 論理的に確定(未実装のため机上)
310
+ - **破綻シナリオ:**
311
+ 1. セッション A が作業中、session_id = A
312
+ 2. セッション B を別ウィンドウで開始、session_id = B
313
+ 3. A が応答 → Stop 発火 → ファイルに A を書き込む
314
+ 4. B がユーザー発言 → UserPromptSubmit → ファイルから A を読む → **B を A の継続と誤認**
315
+ - **回避案(未検証):**
316
+ - (a) project_path + transcript_path で一意化する
317
+ - (b) Stop が書くファイルのキーを「親プロセス PID」などセッション固有の値にする
318
+ - (c) Claude Code 起動時のウィンドウ ID / ターミナル TTY を使う
319
+ - (d) 諦める(ユーザーに「並行セッションは非推奨」と明示する)
320
+ - **結果**: 未検証
321
+
322
+ ---
323
+
324
+ ## 次にやること(優先順)
325
+
326
+ 1. **命題 A・B・C・D・G を 1 回の `/clear` 操作で同時に検証する**
327
+ - 手順:
328
+ 1. 現在の session_id を記録
329
+ 2. hooks.log の Stop エントリ数を記録(before)
330
+ 3. ユーザーが `/clear` を実行
331
+ 4. ユーザーが任意のメッセージを送信(例: 「確認」)
332
+ 5. hooks.log を読み、差分から以下を埋める:
333
+ - `/clear` 直前の最後の Stop エントリ(命題 B・C)
334
+ - `/clear` 後の最初の UserPromptSubmit エントリ(命題 A・D)
335
+ 6. 命題 G は既存ログだけで判定可能(Stop 発火回数 vs ターン数 vs セッション数)
336
+ 2. 結果をこのファイルに書き込む
337
+ 3. 命題 X の実装可能性を判定
338
+ 4. 命題 X-1(並行セッション問題)の回避案を検討
339
+
340
+ ---
341
+
342
+ ## ppid 実機検証(2026-04-15)
343
+
344
+ ### 仮説
345
+ 同一 Claude Code プロセスから生まれたセッションは hook の `process.ppid` が共通のはず。`/clear` 跨ぎで同 ppid を見て前任セッションを紐づけられないか。
346
+
347
+ ### 生データ(hooks.log 12104 行目以降)
348
+
349
+ | 時刻 | hook | session_id | pid | ppid |
350
+ |---|---|---|---|---|
351
+ | 07:57:37 | Stop | `0129aeb8` (旧) | 70008 | **29456** |
352
+ | 07:57:54 | UserPromptSubmit | `f67ce28b` (新) | 33356 | **75600** |
353
+ | 07:58:04 | PermissionRequest | `f67ce28b` (新) | 60908 | **132140** |
354
+
355
+ 並行セッション `34cf32a9` の hook 発火はこの窓内に **無し**(grep 該当は引用文字列のみ)。
356
+
357
+ ### Windows プロセス照会
358
+ `powershell Get-CimInstance Win32_Process -Filter "ProcessId=<N>"` を 29456 / 75600 / 132140 / 70008 / 33356 / 60908 全てに実行 → **全て結果空(既に消滅)**。hook プロセスは短命で、WMI 照会時点でプロセスツリーから消えている。親プロセス名確認不能。
359
+
360
+ ### 判定
361
+ - **ppid 仮説は不成立**。
362
+ - 決定的事実: **同一セッション `f67ce28b` 内の 2 つの hook 呼び出しで ppid が異なる**(75600 vs 132140)。セッション単位でも ppid は安定していないので、「同 ppid = 同 Claude Code プロセス = 前任紐付け候補」という前提が成立しない。
363
+ - `/clear` 跨ぎ(29456 → 75600)も当然不一致。
364
+ - hook は毎回 `node spike/hook-logger.mjs` を新規 spawn しており、その親プロセス(おそらく cmd.exe や Claude Code の中間 spawner)も呼び出しごとに使い捨てされている可能性が高い。Claude Code 本体プロセスまで辿るには ppid を連鎖的に parent‑walk する必要があるが、そのどこかの世代が hook 実行終了後に即死するため WMI で追跡不能。
365
+ - 並行 34cf32a9 のデータは取れなかった(差分窓で発火なし)。X-1 への効果は今回の実験では判定材料なし。
366
+
367
+ ### 含意
368
+ - process.ppid は Windows + VSCode 拡張環境では **セッション相関キーとして使えない**。
369
+ - 追加調査の価値があるとすれば「ppid を parent-walk して Claude Code 本体 PID まで辿り、その PID をキャッシュする」方式だが、中間世代の寿命問題でブラックボックス化しており筋が悪い。
370
+ - アプローチ 1(hook 側で /clear を観測・相関)は ppid でも救えず、これで追加ルート全滅。
371
+
@@ -0,0 +1,22 @@
1
+ # docs/archive/
2
+
3
+ このフォルダの内容は **歴史的資料**。現行実装を説明していない。
4
+
5
+ 現行の設計仕様は一つ上のディレクトリの [L1_L2_L3_REDESIGN.md](../L1_L2_L3_REDESIGN.md) を参照。
6
+
7
+ ## このフォルダにあるもの
8
+
9
+ | ファイル | 当時の位置づけ | 現在 |
10
+ |---|---|---|
11
+ | CONCEPT.md | Throughline の初期コンセプト文書。L2 = 判断 (judgment) 抽出という構造化方式を想定していた | schema v4 で judgments テーブル廃止、L2 は「会話本文そのまま」に再定義。本文書の L2 節以降は実装と乖離している |
12
+ | EXPERIMENT.md | `/clear` 跨ぎで旧/新 session_id を紐づけるための命題 A〜X と実機検証の記録 | 結論として記憶張り替え方式 (merged_into + origin_session_id) が採用され、本実験結果は歴史記録としてのみ価値がある |
13
+ | SESSION_LINKING_DESIGN.md | 命題 X(ファイルベース紐付け)の実装設計書。時間窓+ state ファイル方式 | 同上。最終的に記憶張り替え方式に置き換えられ、spike コードも破棄済み |
14
+ | THROUGHLINE_NEXT_STEPS.md | 2026-04-17、npm publish 直前の優先順位メモ(publish 済ませろ / awesome-claude-code 登録 / HN 投稿)| npm publish は v0.1.0 〜 v0.3.x まで複数回実施、awesome-claude-code は未提出。現在の未完タスクは [../PUBLIC_RELEASE_PLAN.md](../PUBLIC_RELEASE_PLAN.md) に集約 |
15
+
16
+ ## なぜアーカイブするか
17
+
18
+ - コンセプトの根幹(3 層メモリ、SQLite 退避、/clear-safe)は生きている
19
+ - 具体的なスキーマ、層の中身、フック構成はすべて実装中に改訂された
20
+ - 最新の正と歴史が同じフォルダに並ぶと読み手が混乱する
21
+
22
+ 新規に仕様を読む場合は必ず [../L1_L2_L3_REDESIGN.md](../L1_L2_L3_REDESIGN.md) から始めること。
@@ -0,0 +1,231 @@
1
+ # Session Linking Design(命題 X 実装設計)
2
+
3
+ このドキュメントは「`/clear` を跨いで旧 session_id と新 session_id を紐づける」機構の設計と実験プロトコルを定義する。
4
+
5
+ 関連: [EXPERIMENT.md](EXPERIMENT.md) — 命題 A〜H、X、X-1 の生データと判定。
6
+
7
+ ---
8
+
9
+ ## 目的
10
+
11
+ `/clear` の前後で:
12
+ - 旧セッション A の session_id
13
+ - 新セッション A2 の session_id
14
+
15
+ を **1 対 1 で紐づける**。紐づけは「新セッションのユーザー最初の発言より前」に完了していること。
16
+
17
+ ---
18
+
19
+ ## 確定済みの前提(生データあり)
20
+
21
+ | 命題 | 内容 | 参照 |
22
+ |---|---|---|
23
+ | C | Stop フックの stdin に旧 session_id が含まれる | hooks.log 9614 行 |
24
+ | D | `/clear` 後の最初の UserPromptSubmit で新 session_id が届く | hooks.log 9656 行 |
25
+ | G | Stop はアシスタント応答完了ごとに毎ターン発火する | hooks.log 9493/9532, 9574/9614 |
26
+ | H | SessionStart は UserPromptSubmit より約 3 秒前に発火し、新 session_id が届く | session-start.log 05:53:31.674Z / hooks.log 05:53:34.961Z |
27
+
28
+ これらから、**Stop で旧 ID をファイルに書き、SessionStart で読む**方式が成立する見込み。
29
+
30
+ ---
31
+
32
+ ## 設計
33
+
34
+ ### 全体フロー
35
+
36
+ ```
37
+ [セッション A]
38
+ ユーザー発言 → アシスタント応答 → Stop 発火
39
+ → session-link-writer.mjs が
40
+ ~/.throughline/session-link/<project_hash>.json に
41
+ { old_session_id: A, ts: now, state: "open" } を書き込む(毎ターン上書き)
42
+
43
+ ユーザーが /clear を実行
44
+
45
+ [セッション A2 開始]
46
+ SessionStart 発火(UserPromptSubmit より約 3 秒前)
47
+ → session-link-reader.mjs が上記ファイルを読む
48
+ 判定:
49
+ - state != "open" なら skip(既に消費済み)
50
+ - now - ts > 10_000ms なら skip(窓切れ)
51
+ - old_session_id == new_session_id なら skip(自己参照)
52
+ - それ以外: リンク実行 → state = "closed" に書き戻す
53
+
54
+ UserPromptSubmit 発火
55
+ → context-injector.mjs が新 session_id の記憶を取り出す
56
+ (リンク済みなので旧 A の記憶は A2 に紐づいている)
57
+ ```
58
+
59
+ ### データ構造
60
+
61
+ **リンク状態ファイル**: `~/.throughline/session-link/<project_hash>.json`
62
+
63
+ `project_hash` = `sha256(cwd).slice(0, 16)`。cwd は Stop/SessionStart 双方で届くため一意な鍵になる。
64
+
65
+ ```json
66
+ {
67
+ "old_session_id": "a2335bc7-c354-4172-ab89-abff7c7b6ee6",
68
+ "transcript_path": "C:\\Users\\kite_\\.claude\\projects\\...",
69
+ "cwd": "c:\\Users\\kite_\\Documents\\Program\\Throughline",
70
+ "ts": 1712345678901,
71
+ "state": "open"
72
+ }
73
+ ```
74
+
75
+ **監査ログ**: `~/.throughline/session-link/link.log`(append-only JSONL)
76
+
77
+ 各操作(write / read-hit / read-miss-closed / read-miss-stale / read-miss-self / link-success)を 1 行 JSON で記録。実験後に grep で検証可能。
78
+
79
+ ### 10 秒受付窓
80
+
81
+ - **目的**: 古いファイル(Claude Code を長時間閉じて再開した等)を無視する
82
+ - **判定**: `Date.now() - entry.ts > 10_000` で stale 扱い
83
+ - **例外**: なし。10 秒を過ぎたら問答無用でスキップ
84
+
85
+ ### 紐づけ完了後の「受付終了」
86
+
87
+ - **目的**: 同じ旧 ID を二重にリンクしない・紐づけ成立後の上書きで既成リンクを壊さない
88
+ - **方法**: リンク成功後に `state = "closed"` をファイルに書き戻す
89
+ - **自動再開**: 次の Stop(次ターンのアシスタント応答完了時)が `state = "open"` で新しい {old, ts} を書き込むので、次の `/clear` に備える
90
+
91
+ ### 並行セッション問題(X-1)の扱い
92
+
93
+ **残る穴**: 「A の最後の Stop」と「A の /clear」の間に「B の Stop」が挟まると、A2 SessionStart が B を旧 ID と誤認する可能性がある。
94
+
95
+ **今回の実装での方針**: 既知の穴として許容。実用上、同一 project_path で並行セッションを走らせるケースは少ないと想定。監査ログから誤リンクが検出できるようにする(cwd・transcript_path の整合チェック)。
96
+
97
+ ---
98
+
99
+ ## 実装ファイル
100
+
101
+ ```
102
+ spike/
103
+ ├── session-link-common.mjs 共通(project_hash 計算、ファイル読み書き、ログ)
104
+ ├── session-link-writer.mjs Stop フック追加(旧 ID 書き込み)
105
+ └── session-link-reader.mjs SessionStart フック追加(読み取り・リンク・クローズ)
106
+ ```
107
+
108
+ spike 配下に置くのは、本実装ではなく**命題 X の実測検証**が目的のため。実測で成立が確認できたら `src/session-linker.mjs` に昇格する。
109
+
110
+ DB への実リンク処理(sessions テーブル更新)は**この実験では行わない**。まずは「読み書きとタイミングが期待通り動くか」だけを確認する。リンク成功時は `link.log` に記録するだけ。
111
+
112
+ ---
113
+
114
+ ## 実験プロトコル
115
+
116
+ ### 準備
117
+
118
+ 1. `.claude/settings.json` の Stop と SessionStart に spike フックを追加
119
+ 2. ユーザーに「次の応答を待って `/clear` → 任意のメッセージ」と依頼
120
+
121
+ ### 検証項目
122
+
123
+ | 項目 | 期待 | 確認方法 |
124
+ |---|---|---|
125
+ | 1. Stop で書き込みが起きる | `link.log` に `op: "write"` エントリ、対応する `<project_hash>.json` が `state: "open"` で存在 | ファイル確認 |
126
+ | 2. `/clear` 後の SessionStart で読み取りが起きる | `link.log` に `op: "read-hit"` または類似エントリ、elapsed < 10000ms | ログ確認 |
127
+ | 3. 旧 ID と新 ID が異なることを記録できる | `link.log` に `old`, `new` が別 UUID で記録される | ログ確認 |
128
+ | 4. SessionStart が UserPromptSubmit より前に実行される | `link.log` のタイムスタンプと hooks.log の UserPromptSubmit を比較 | 突き合わせ |
129
+ | 5. リンク成功後 `state: "closed"` になっている | ファイル確認 | 直接 cat |
130
+ | 6. 次の Stop で再び `state: "open"` に戻る | 次ターン末尾で確認 | 直接 cat |
131
+
132
+ ### 成功条件
133
+
134
+ 項目 1〜5 がすべてログとファイルで確認できれば命題 X は実測確定。
135
+
136
+ ### 失敗時の切り分け
137
+
138
+ - 1 が無い → Stop フックに spike が登録されていない / パス間違い
139
+ - 2 が無い → SessionStart が `/clear` で発火していない(命題 E 再検証)
140
+ - 2 の elapsed > 10s → /clear から SessionStart まで 10 秒以上かかっている(窓拡張 or 起点変更を検討)
141
+ - 3 で old == new → そもそも /clear が session_id を変えていない(命題 A 再検証)
142
+ - 4 で SessionStart が後なら → タイミング前提が崩壊(命題 H 再検証)
143
+
144
+ ---
145
+
146
+ ## 次ステップ
147
+
148
+ 1. このドキュメントに従って spike 実装を作成
149
+ 2. settings.json に登録
150
+ 3. ユーザーに `/clear` 実験を依頼
151
+ 4. `link.log` と hooks.log を突き合わせて命題 X を判定
152
+ 5. 成立すれば `src/session-linker.mjs` に昇格し、DB リンク処理を追加
153
+
154
+ ---
155
+
156
+ ## 最終決定 (2026-04-15)
157
+
158
+ ### 採用されなかった案
159
+
160
+ **命題 X のファイルベース紐付け**: spike/session-link-*.mjs で実装したが、命題 G(並行セッション)のケースで曖昧性が残ったため本番採用見送り。10 秒窓に複数 Stop が入ったときの同定ができない。
161
+
162
+ **ppid 相関仮説**: 「同じ Claude Code プロセスから生まれた hook プロセスは `process.ppid` が共通のはず」という仮説を実機検証 (`docs/EXPERIMENT.md` 参照)。結果は**不成立**:
163
+ - 同一セッション内の 2 つの hook 呼び出しでも ppid が異なる (75600 vs 132140)
164
+ - Windows + VSCode 拡張環境では hook プロセスの親世代が呼び出しごとに使い捨てされる
165
+ - WMI 照会時点で既に死亡しており親プロセス追跡も不可
166
+
167
+ **`/preclear` コマンド案**: ユーザーに `/preclear` を明示的に打たせて「この次に clear するぞ」というハンドシェイクを取る案。実装シンプルだがユーザー規律依存で頑健性に欠けるため不採用。
168
+
169
+ ### 採用した案: 記憶張り替え (Relabel) 方式
170
+
171
+ `SessionStart` フックで以下を行う:
172
+
173
+ 1. `sessions` テーブルに新 session_id を INSERT OR IGNORE
174
+ 2. 同 `project_path` で `merged_into IS NULL` の最新 `updated_at` セッションを前任候補として SELECT
175
+ 3. 前任候補があれば、その skeletons / judgments / details の `session_id` を新 session_id に UPDATE(張り替え)
176
+ - `origin_session_id` は既存値を保持 → 系譜追跡
177
+ 4. 前任 `sessions.merged_into = 新 session_id`、新 `sessions.updated_at = now`
178
+ 5. `BEGIN IMMEDIATE` トランザクションで原子性確保
179
+ 6. 合流成立なら `buildResumeContext(isInheritance=true)` で引き継ぎヘッダ付き L1+L2 を stdout 注入
180
+
181
+ ### 実機確認 (2026-04-15)
182
+
183
+ `SessionStart` は `/clear` 後も `source="startup"` で発火する(`~/.throughline/spike/session-start.log` 実機ログ)。プラン時点の誤認「SessionStart は /clear 後には発火しない」は `source="clear"` が来ないだけで hook 自体は発火していたという事実。stdout の生テキスト注入も spike マーカーで動作確認済み。
184
+
185
+ ### 張り替え方式の利点
186
+
187
+ - **チェーン蓄積**: 複数回の /clear を跨いでも記憶が同じ session_id 配下に集約される。1 ホップ制限なし
188
+ - **並行セッション誤認 (X-1) の扱い**: 「同 project_path で最後に Claude が反応したセッション」を前任候補とする単純ルール。受容し、注入ヘッダで明示
189
+ - **時間窓撤廃**: `CLEAR_CONTINUATION_MS` を削除。任意の古さの前任でも引き継ぎ可
190
+ - **SessionStart での早期注入**: ユーザーの初発言を待たずに記憶が戻る
191
+
192
+ ### 既知の制約
193
+
194
+ 1. **並行セッション誤認 (X-1)**: 受容、注入ヘッダに注意書き明示
195
+ 2. **非常に古いセッションの復活**: 時間窓撤廃の副作用、許容
196
+ 3. **張り替えの一方向性**: 合流後、元の session_id に戻す undo は提供しない
197
+ 4. **初回ターンのコンテキスト冗長**: SessionStart 注入と UserPromptSubmit 注入の両方が会話先頭に並ぶ。合計トークン消費がやや増える
198
+ 5. **mid-turn /clear で残留する NULL details**: 前任 PostToolUse が Stop 前に /clear された場合、`details.turn_number=NULL` のまま合流先に移る。L3 参照時に NULL 除外で対応
199
+
200
+ ### スキーマ (v3)
201
+
202
+ ```sql
203
+ -- 新規列
204
+ ALTER TABLE skeletons ADD COLUMN origin_session_id TEXT;
205
+ ALTER TABLE judgments ADD COLUMN origin_session_id TEXT;
206
+ ALTER TABLE details ADD COLUMN origin_session_id TEXT;
207
+ ALTER TABLE sessions ADD COLUMN merged_into TEXT;
208
+
209
+ -- UNIQUE 制約張り替え
210
+ DROP INDEX uq_skeletons_turn;
211
+ DROP INDEX uq_judgments_hash;
212
+ CREATE UNIQUE INDEX uq_skeletons_turn_v3
213
+ ON skeletons(session_id, origin_session_id, turn_number, role);
214
+ CREATE UNIQUE INDEX uq_judgments_hash_v3
215
+ ON judgments(session_id, origin_session_id, content_hash);
216
+
217
+ -- 検索用副次インデックス
218
+ CREATE INDEX idx_skeletons_session ON skeletons(session_id, created_at);
219
+ CREATE INDEX idx_judgments_session ON judgments(session_id, resolved, created_at);
220
+ ```
221
+
222
+ ### 主要モジュール
223
+
224
+ | ファイル | 役割 |
225
+ |---|---|
226
+ | `src/session-merger.mjs` | `resolveMergeTarget` (merged_into チェーン解決) / `mergePredecessorInto` (張り替え本体) |
227
+ | `src/resume-context.mjs` | L1+L2 レンダリング共有モジュール (isInheritance フラグで引き継ぎ/通常ヘッダ切替) |
228
+ | `src/session-start.mjs` | SessionStart hook: INSERT + merge + 引き継ぎ注入 |
229
+ | `src/context-injector.mjs` | UserPromptSubmit hook: 毎ターン通常注入 + merge 追従 |
230
+ | `src/turn-processor.mjs` | Stop hook: merge target に origin 記録付きで L1/L2 書き込み |
231
+ | `src/detail-capture.mjs` | PostToolUse hook: 同様に merge target 追従で L3 書き込み |