throughline 0.8.0 → 0.8.2

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.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,26 @@ shipped to npm but were not individually tagged on GitHub.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.8.2] — 2026-07-20
14
+
15
+ ### Fixed
16
+
17
+ - Windows native Codex hook commands now prefix quoted Node executables with the
18
+ PowerShell call operator `&`. POSIX command strings are unchanged, and the
19
+ existing managed-hook detector continues to recognize the canonical commands.
20
+
21
+ ## [0.8.1] — 2026-07-19
22
+
23
+ ### Fixed
24
+
25
+ - **Native factory diagnostics now report the database compatibility label from
26
+ the canonical schema version.** Throughline schema v9 previously emitted the
27
+ stale `throughline.database.v8` label alongside numeric versions `9`/`9`, so
28
+ exact factory reporters correctly classified the installation as
29
+ incompatible. The label is now derived from the DB migration version and a
30
+ regression test requires the label, actual version, and supported version to
31
+ stay aligned on future schema bumps.
32
+
13
33
  ## [0.8.0] — 2026-07-18
14
34
 
15
35
  ### Changed (breaking behavior)
package/README.ja.md CHANGED
@@ -29,10 +29,14 @@ throughline install # hook / Codex skill / VS Code monitor task を登録
29
29
  <details>
30
30
  <summary><b>Codex も併用する場合</b> Codex hooks も登録される — クリックで詳細</summary>
31
31
 
32
- Codex では `UserPromptSubmit` / `PostToolUse` / `Stop` hook と `$throughline`
33
- skill も登録する。75% 自動発火は token-monitor 依存ではなく、当該 Codex
34
- セッションの rollout `token_count` hook が読み、prompt 送信時または tool loop
35
- 途中の閾値到達時に同じセッションへ `$throughline` 実行指示を注入する。
32
+ global install は Codex `UserPromptSubmit` / `PostToolUse` / `Stop` hook
33
+ (絶対 node パス登録)と `$throughline` skill も登録する。これらの hook は
34
+ rollout capture monitor state 書き込みだけを行い、**使用量閾値での
35
+ `$throughline` 自動注入はしない**(token-monitor は表示専用)。bare
36
+ `$throughline` は app-server 経由で新規 Codex thread を開始し、Throughline DB
37
+ の handoff memory を developer item として注入する。current-thread rollback
38
+ 診断が要る時だけ明示的に `trim --execute --host codex` を使う。既存の
39
+ 非 Throughline Codex hook は保持される。
36
40
 
37
41
  </details>
38
42
 
@@ -42,7 +46,7 @@ skill も登録する。75% 自動発火は token-monitor 依存ではなく、
42
46
  |---|---|---|---|---|
43
47
  | **何をする** | ツール I/O を SQLite に退避、本文は残す | ウィンドウを全消去 | ウィンドウ全体を LLM 要約 | 新旧で要約 |
44
48
  | **圧縮の軸** | コンテンツの **種類** (テキスト vs ツール I/O) | 無し — 全消去 | **新旧** (一律) | **新旧** (一律) |
45
- | **境界後に残る記憶** | ✅ 直近 20 ターン本文 + それ以前 L1 + L3 オンデマンド | ❌ ゼロ | △ 一個の要約 (情報欠落) | △ 要約 (情報欠落) |
49
+ | **境界後に残る記憶** | ✅ 直近ターン本文そのまま (予算内ターン原子詰め) + それ以前は `recall` で pull + L3 オンデマンド | ❌ ゼロ | △ 一個の要約 (情報欠落) | △ 要約 (情報欠落) |
46
50
  | **ツール I/O の扱い** | L3 に退避、`/sc-detail HH:MM:SS` で取り戻せる | 消える | 要約に溶けて読めない | 要約に溶ける |
47
51
  | **コーディング用途への適合** | 高 — ツール I/O こそ重い 80% | 低 — 文脈が切れる | 中 — ただし不可逆 | 中 |
48
52
  | **誤継承リスク** | 低 (typed `/clear` / `/tl` が前任を指名) | n/a | n/a | 高 |
@@ -73,10 +77,10 @@ Throughline 無し (50 ターン、/clear なし):
73
77
  ≈ 125,000 tok 合計
74
78
 
75
79
  Throughline 有り (50 ターン → /clear → 再開):
76
- 直近 20 ターン L2 ~10,000 tok ██
77
- それ以前 30 ターン L1 ~3,000 tok ▌
78
- ツール I/O 0 tok (SQLite 退避、オンデマンド取得)
79
- ≈ 13,000 tok — 90% 軽量
80
+ 再開注入 ~3,000 tok ▌ (直近 L2 ターン丸ごと、≤9,500 字)
81
+ それ以前の記憶 0〜10,000 tok (必要な時だけ recall --l2 / --l1 で pull)
82
+ ツール I/O 0 tok (SQLite 退避、detail でオンデマンド取得)
83
+ 最大 13,000 tok — 90% 軽量
80
84
  ```
81
85
 
82
86
  MemGPT や LangChain の SummaryBufferMemory が **新旧** で圧縮するのに対し、
@@ -94,7 +98,7 @@ Throughline は加えて、トランスクリプト JSONL から実測 API 使
94
98
 
95
99
  ---
96
100
 
97
- ## 3 層メモリーモデル (schema v7)
101
+ ## 3 層メモリーモデル (schema v9)
98
102
 
99
103
  ```mermaid
100
104
  flowchart LR
@@ -102,10 +106,11 @@ flowchart LR
102
106
  T --> H["Stop hook"]
103
107
  H --> L2[("L2 · bodies<br/>本文そのまま")]
104
108
  H --> L3[("L3 · details<br/>ツール I/O · 思考")]
105
- H -. "非同期<br/>Haiku" .-> L1[("L1 · skeletons<br/>一行要約")]
109
+ H -. "非同期<br/>要約器" .-> L1[("L1 · skeletons<br/>一行要約")]
106
110
 
107
- L2 -- "直近 20 ターン" --> S["次セッション<br/>SessionStart 注入"]
108
- L1 -- "それ以前" --> S
111
+ L2 -- "直近ターン丸ごと<br/>(9,500 字予算)" --> S["次セッション<br/>初回プロンプト注入"]
112
+ L2 -. "窓の残り<br/>throughline recall --l2" .-> S
113
+ L1 -. "それ以前すべて<br/>throughline recall --l1" .-> S
109
114
  L3 -. "オンデマンド · throughline detail" .-> S
110
115
 
111
116
  classDef l1 fill:#3aa0ff,stroke:#1a1f2e,color:#fff
@@ -118,28 +123,35 @@ flowchart LR
118
123
 
119
124
  | 層 | 名称 | 保存先 | 内容 | ターンあたりコスト |
120
125
  | --- | --- | --- | --- | --- |
121
- | **L1** | スケルトン | 古いターンとして注入 | ターンの一行要約(既定は Claude Haiku、設定時は Codex sidecar も可) | 約 10 トークン |
122
- | **L2** | ボディ | 直近ターンとして注入 | ユーザー本文 + アシスタント返答そのまま | 自然なフルサイズ |
126
+ | **L1** | スケルトン | `recall --l1` でオンデマンド pull | ターンの一行要約(既定 backend Codex CLI `gpt-5.6-luna`、fallback は ADR 0015 参照) | 約 10 トークン |
127
+ | **L2** | ボディ | 予算内は注入、残りは `recall --l2` | ユーザー本文 + アシスタント返答そのまま | 自然なフルサイズ |
123
128
  | **L3** | ディテール | SQLite のみ | ツール I/O、システムメッセージ、画像、**拡張思考** (オンデマンド) | 重い、退避済 |
124
129
 
125
130
  3 層は **互いに補完的かつ排他的** で、重複保存はありません。
126
131
  拡張思考ブロックは L3 (`kind='thinking'`) に格納されるので、次セッションは
127
132
  **前セッションの Claude が中断時に何を考えていたか** を、発話だけでなく
128
- 内省レベルで参照できます。`SessionStart` では **最終ターンの思考** が L2 履歴の
129
- 直上にインライン注入され、それ以前の思考は `throughline detail <時刻>` で取得できます。
130
-
131
- `SessionStart` 時、Throughline SQLite からコンテキストを再構築し、
132
- プレーンテキストとして注入します:
133
-
134
- - **直近 20 ターン** は L2 (`bodies`) のフル本文として注入
135
- - **それ以前** L1 (`skeletons`) の一行要約として注入
133
+ 内省レベルで参照できます。思考は注入されず、`throughline detail <時刻>`
134
+ いつでも取得できます。
135
+
136
+ 次セッションの **初回ユーザープロンプト** 時(二相ハンドオフ、ADR 0014)、
137
+ Throughline は SQLite からコンテキストを再構築し、プレーンテキストとして
138
+ 注入します(push/pull 設計、ADR 0016):
139
+
140
+ - **直近ターン** L2 (`bodies`) のフル本文として注入 — 新しい順に
141
+ ターン丸ごと(user + assistant 原子)、約 9,500 字の予算に入るだけ
142
+ - **L1 (`skeletons`) の一行要約は注入しません** — 代わりに、そのまま実行
143
+ できる `throughline recall --l2|--l1` コマンド入りの案内セクションを注入し、
144
+ 古い記憶は必要になった時だけ pull します
136
145
  - L3 は SQLite に残り、`/sc-detail <時刻>` でオンデマンド取得
137
146
 
138
147
  L1 要約は遅延実行で、20 ターン未満で終わるセッションでは外部要約器を呼ばず、
139
- 短いタスクの要約コストはゼロです。既定では
140
- `claude -p --model claude-haiku-4-5-*` サブプロセスで **Claude Haiku 4.5** が生成します。
141
- Claude Max のログイン認証を流用するため API キー不要です。
142
- `codex-sidecar` `summarize-l1` preset で明示設定されている場合は、そちらを使えます。
148
+ 短いタスクの要約コストはゼロです。要約は **削減割合**(既定 1/5、
149
+ `THROUGHLINE_L1_RATIO` で変更可。不正値は明示エラー)を目標にします。
150
+ Claude-primary 経路の backend 順は codex-sidecar(`summarize-l1` preset 明示
151
+ 設定時)→ **Codex CLI**(既定 `gpt-5.6-luna`@`low`、実測評価で選定 ADR 0015。
152
+ `THROUGHLINE_L1_MODEL` / `THROUGHLINE_L1_EFFORT` で変更可)→ **Claude Haiku 4.5**
153
+ (`claude -p` サブプロセス、Claude Max のログイン認証流用)で、API キーは
154
+ 不要です。各段の失敗理由は記録されます。
143
155
 
144
156
  3 層 (L1/L2/L3) の書き込みパスは schema v5 から動作しています。
145
157
  `/sc-detail HH:MM:SS` はユーザー / アシスタント本文 (L2) と、そのターンで
@@ -158,13 +170,14 @@ flowchart LR
158
170
  U["ユーザーが入力<br/>/clear または /tl"] -->|UserPromptSubmit| W["writeBaton<br/>(session_id + TTL 1h)"]
159
171
  W --> B[("handoff_batons<br/>SQLite")]
160
172
  M["VS Code メニュー<br/>clear"] -->|UserPromptSubmit に届かない| X["baton 無し"]
161
- NS["次の SessionStart"] --> C{"baton<br/>あり?"}
173
+ NS["次の SessionStart<br/>(intent 登録のみ)"] --> FP["初回ユーザープロンプト<br/>(実セッションの証明)"]
174
+ FP --> C{"baton<br/>あり?"}
162
175
  B -.-> C
163
176
  X -.-> C
164
177
  C -->|あり| P1["baton path<br/>(主経路)<br/>指名された前任を merge"]
165
- C -->|無し / source='clear'| P2["auto path<br/>(補助)<br/>findLatestClaudePredecessor"]
178
+ C -->|無し / source='clear'| P2["auto path<br/>(補助)<br/>前任は SessionStart 時点で凍結"]
166
179
  C -->|無し / source!='clear'| P3["新規セッション<br/>merge 無し"]
167
- P1 --> INJ["L1 + L2 + L3 references を注入"]
180
+ P1 --> INJ["L2 ターン + recall 案内を注入<br/>(予算 9,500 字)"]
168
181
  P2 --> INJ
169
182
 
170
183
  classDef primary fill:#7c5cff,stroke:#1a1f2e,color:#fff
@@ -178,16 +191,26 @@ flowchart LR
178
191
  ### baton path (primary): typed `/clear` または `/tl`
179
192
 
180
193
  ユーザーが prompt に `/clear` または `/tl` を打つと、UserPromptSubmit hook が
181
- **そのセッションの** `session_id` を `handoff_batons` に書きます。次の
182
- SessionStart 1 時間以内の baton を消費し、その前任を確定的に merge します。
194
+ **そのセッションの** `session_id` を `handoff_batons` に書きます。次の新セッション
195
+ **初回ユーザープロンプト時** baton を消費し(適格性: セッション誕生が baton
196
+ 書き込みから TTL 1 時間以内)、その前任を確定的に merge します。
183
197
  複数ウィンドウで「最新更新セッション」と「今 `/clear` したセッション」が違っても、
184
198
  指名された前任だけを引き継ぎます。
185
199
 
200
+ なぜ SessionStart でなく初回プロンプトか: Claude Code は同一 project に数百 ms の
201
+ 間隔で複数の SessionStart を発火させることがあり、その一部は transcript を一切
202
+ 作らない**幽霊セッション**になります。SessionStart 時点では実体と幽霊を判別
203
+ できないため、baton を SessionStart で消費すると幽霊が記憶を飲み込み、実セッション
204
+ が空で始まる事故が起きます。幽霊はプロンプトを発火しないので、消費を初回
205
+ プロンプトへ遅延させればこの事故は構造的に起きません(二相ハンドオフ、ADR 0014)。
206
+
186
207
  ### auto path (fallback): `source='clear'`
187
208
 
188
209
  baton が無く、SessionStart の `source='clear'` が届いた場合だけ、同 project の
189
- 最新 Claude predecessor を選んで merge します。これは VS Code 拡張メニューなど、
190
- typed `/clear` が UserPromptSubmit hook に届かない経路のための補助です。
210
+ 最新 Claude predecessor **SessionStart 時点で** 解決・凍結し(transcript
211
+ 無い幽霊は候補から除外)、merge + 注入は初回プロンプト時に行います。これは
212
+ VS Code 拡張メニューなど、typed `/clear` が UserPromptSubmit hook に届かない
213
+ 経路のための補助です。
191
214
 
192
215
  `THROUGHLINE_DISABLE_AUTO_HANDOFF=1` はこの fallback path だけを OFF にします。
193
216
  typed `/clear` と `/tl` はユーザーの明示意思なので、この env に関係なく baton を
@@ -201,11 +224,18 @@ fallback: baton 無し + source='clear' → latest predecessor を merge
201
224
 
202
225
  ### 注入されるもの
203
226
 
204
- 両経路で同じ curated memory が注入されます:
227
+ 両経路で同じ curated memory が注入されます(push/pull 設計、ADR 0016):
205
228
 
206
- - L1 サマリー (古い turn の一行要約)
207
- - L2 verbatim (直近 20 turn の本文)
208
- - L3 references (`throughline detail <時刻>` で引き出すコマンド一覧、本文は SQLite に残置)
229
+ - **「現在地」アンカー** (v0.4.12〜) — 最新のユーザー指示と最新の assistant turn
230
+ ヘッダ直下に再掲(各 600 字で切り詰め)
231
+ - **pull 案内セクション**(無条件表示)— そのまま実行できる `throughline recall`
232
+ コマンド。session id・ISO ms 境界・件数は注入時に焼き込み済み
233
+ - L2 verbatim — 約 9,500 字の注入予算に入るだけ、新しい turn からターン丸ごと
234
+ 詰める(典型 7〜8 turn、軽い会話ならもっと多い)。**L1 サマリーは注入しません**
235
+ — 20 turn 窓の残りは `recall --l2` で全文、それより古い全 turn は `recall --l1`
236
+ で取得(要約済みは L1 行、未要約は `throughline detail` への誘導付きで明示)
237
+ - L3 references (`throughline detail <時刻>` で引き出すコマンド、各 L2 行末尾に
238
+ inline 付記。本文は SQLite に残置)
209
239
 
210
240
  注入は **「中断されたタスクの再開」** として再フレーミングされます。L2 verbatim に
211
241
  最終 assistant turn (= 次に何をしようとしていたか) が含まれるため、別途 memo /
@@ -231,14 +261,14 @@ slash command / transcript / baton / resume behavior を置き換えるもので
231
261
  adapter / projection として追加されます。
232
262
 
233
263
  現時点で core Throughline が外部モデルを呼ぶのは L2→L1 要約だけです。
234
- `codex-sidecar` が `summarize-l1` preset で設定されている場合はその要約に
235
- Codex sidecar を使えます。使えない場合は、従来どおり Claude Haiku 経路を使います。
264
+ backend 順は codex-sidecar(`summarize-l1` preset 明示設定時)→ Codex CLI
265
+ (既定 `gpt-5.6-luna`)→ Claude Haiku です(ADR 0015)。
236
266
 
237
267
  Codex 側 trim (= same-thread context trim) は `throughline trim --execute --host codex`
238
268
  で発火します。Codex の bare `$throughline` skill もこの scripted rollback + DB
239
269
  memory inject を直接実行します。Claude 側は `/clear` での auto path 引継ぎが本線になったため、
240
270
  `/tl-trim` slash command は v0.4.0 で廃止されました。current-work framing は
241
- SessionStart 注入の Reading Contract / Continuation Instruction で同じ意図を
271
+ 再開注入の Reading Contract / Continuation Instruction で同じ意図を
242
272
  継承しています。
243
273
 
244
274
  </details>
@@ -285,6 +315,7 @@ Throughline state をまだ書いていない現在セッションも表示で
285
315
  | `throughline monitor` | マルチセッション監視を起動 |
286
316
  | `throughline monitor --diag` | TTY/columns/env 診断ダンプ (描画バグ切り分け用) |
287
317
  | `throughline detail <時刻>` | あるターンの L2 本文と L3 ツール I/O を取得 (Claude が使う) |
318
+ | `throughline recall --l2\|--l1 --session <id> --before <ISO> ...` | 注入の案内セクションが指す古い記憶を pull (read-only、正確なコマンドは注入に焼き込み済み) |
288
319
  | `throughline doctor` | Node バージョン、hook 登録状況、DB、PATH をチェック |
289
320
  | `throughline doctor --trim --host claude` | trim boundary と手動手順を診断 |
290
321
  | `throughline handoff-preview --session <id>` | Codex 向け `throughline_handoff` JSON projection を表示 |
@@ -313,7 +344,8 @@ Throughline state をまだ書いていない現在セッションも表示で
313
344
 
314
345
  - **Node.js 22.5 以上** (組み込み `node:sqlite` モジュール使用、ネイティブビルド不要)
315
346
  - **Claude Code** (`SessionStart`, `Stop`, `UserPromptSubmit` hooks 対応版)
316
- - **Claude Max サブスクリプション** (Haiku ベース L1 要約のため `claude -p` 経由)
347
+ - **Codex CLI ログイン**(既定の L1 要約 backend、`gpt-5.6-luna`)または
348
+ **Claude Max サブスクリプション**(`claude -p` 経由の Haiku fallback)— どちらも API キー不要
317
349
  - 対応 OS: **Windows / macOS / Linux**
318
350
 
319
351
  ランタイム依存 **ゼロ**。npm パッケージは純 `.mjs` ファイルのみで構成されています。
package/README.md CHANGED
@@ -54,7 +54,7 @@ guarded `trim --execute --host codex` surface.
54
54
  |---|---|---|---|---|
55
55
  | **What it does** | retire tool I/O to SQLite, keep text in-context | wipe the whole window | LLM-summarize the whole window | recency-based summarize |
56
56
  | **Compression axis** | content **type** (text vs tool I/O) | none — full wipe | **recency** (uniform) | **recency** (uniform) |
57
- | **Memory after the boundary** | ✅ recent 20 turns verbatim + older as L1 + L3 on demand | ❌ zero | △ lossy single summary | △ lossy summary |
57
+ | **Memory after the boundary** | ✅ recent turns verbatim (whole, budget-packed) + everything older via `recall` pull + L3 on demand | ❌ zero | △ lossy single summary | △ lossy summary |
58
58
  | **Tool I/O handling** | retired to L3, retrievable by `/sc-detail HH:MM:SS` | gone | folded into summary, unreadable | folded into summary |
59
59
  | **Coding-assistant fit** | high — tool I/O is the heavy 80% | low — you lose the thread | medium — but irreversible | medium |
60
60
  | **Auto-inheritance risk** | low (typed `/clear` / `/tl` names the predecessor) | n/a | n/a | high |
@@ -86,10 +86,10 @@ Without Throughline (50 turns, no /clear):
86
86
  ≈ 125,000 tok total
87
87
 
88
88
  With Throughline (50 turns → /clear → resume):
89
- recent 20 turns L2 ~10,000 tok ██
90
- older 30 turns L1 ~3,000 tok
91
- tool I/O 0 tok (retired to SQLite, on-demand)
92
- ≈ 13,000 tok total — 90% lighter
89
+ resume injection ~3,000 tok ▌ (recent L2 turns, whole, ≤ 9,500 chars)
90
+ older memory 0–10,000 tok (pulled only when needed: recall --l2 / --l1)
91
+ tool I/O 0 tok (retired to SQLite, on-demand via detail)
92
+ ≈ 13,000 tok worst case — 90% lighter
93
93
  ```
94
94
 
95
95
  Throughline separates conversation content by **type, not time**: human-readable
@@ -117,10 +117,11 @@ flowchart LR
117
117
  T --> H["Stop hook"]
118
118
  H --> L2[("L2 · bodies<br/>verbatim text")]
119
119
  H --> L3[("L3 · details<br/>tool I/O · thinking")]
120
- H -. "async<br/>Haiku" .-> L1[("L1 · skeletons<br/>one-liners")]
120
+ H -. "async<br/>summarizer" .-> L1[("L1 · skeletons<br/>one-liners")]
121
121
 
122
- L2 -- "recent 20 turns" --> S["Next session's first prompt<br/>injection"]
123
- L1 -- "older turns" --> S
122
+ L2 -- "recent turns, whole<br/>(9,500-char budget)" --> S["Next session's first prompt<br/>injection"]
123
+ L2 -. "rest of the window<br/>throughline recall --l2" .-> S
124
+ L1 -. "everything older<br/>throughline recall --l1" .-> S
124
125
  L3 -. "on demand · throughline detail" .-> S
125
126
 
126
127
  classDef l1 fill:#3aa0ff,stroke:#1a1f2e,color:#fff
@@ -133,22 +134,25 @@ flowchart LR
133
134
 
134
135
  | Layer | Name | Where it lives | Content | Cost per turn |
135
136
  | ----- | ---------- | --------------------- | --------------------------------------------------------------------- | ------------- |
136
- | **L1** | Skeleton | injected when old | one-line summary of the turn (current Claude-primary path: Claude Haiku, optional Codex sidecar) | ~10 tok |
137
- | **L2** | Body | injected when recent | user text + assistant reply, verbatim | full natural |
137
+ | **L1** | Skeleton | pulled on demand (`recall --l1`) | one-line summary of the turn (default backend: Codex CLI `gpt-5.6-luna`, fallbacks per ADR 0015) | ~10 tok |
138
+ | **L2** | Body | injected while the budget lasts; rest via `recall --l2` | user text + assistant reply, verbatim | full natural |
138
139
  | **L3** | Detail | SQLite only | tool I/O, system messages, images, **extended thinking** (on-demand) | heavy, retired |
139
140
 
140
141
  The layers are **complementary and disjoint** — nothing is duplicated across
141
142
  them. Extended thinking blocks are stored at L3 (`kind='thinking'`) so the
142
143
  next session can see *what the previous Claude was thinking* at the moment it
143
- was interrupted, not just what it said aloud. On `SessionStart` the thinking
144
- of the **final turn** is injected inline above the L2 history; older thinking
145
- remains retrievable via `throughline detail <time>`.
146
-
147
- On `SessionStart`, Throughline rebuilds the context from SQLite and
148
- injects it as plain text:
149
-
150
- - The **most recent 20 turns** are injected as full L2 (`bodies`) text
151
- - **Older turns** are injected as L1 (`skeletons`) one-liners
144
+ was interrupted, not just what it said aloud. Thinking is never injected —
145
+ it stays retrievable via `throughline detail <time>`.
146
+
147
+ At the **first user prompt** of the next session (two-phase handoff, ADR
148
+ 0014), Throughline rebuilds the context from SQLite and injects it as plain
149
+ text (push/pull design, ADR 0016):
150
+
151
+ - The **most recent turns** are injected as full L2 (`bodies`) text — packed
152
+ whole-turn, newest-first, as many as fit the ~9,500-char budget
153
+ - **L1 (`skeletons`) one-liners are not injected** — a guidance section with
154
+ ready-to-run `throughline recall --l2|--l1` commands is injected instead,
155
+ so older memory is pulled only when needed
152
156
  - L3 stays in SQLite and is retrieved on demand via `/sc-detail <time>`
153
157
 
154
158
  L1 summaries are generated lazily: for sessions that stay under 20 turns, no
@@ -189,7 +193,7 @@ flowchart LR
189
193
  C -->|yes| P1["baton path<br/>(primary)<br/>merge that exact predecessor"]
190
194
  C -->|no, source='clear'| P2["auto path<br/>(fallback)<br/>predecessor frozen at SessionStart"]
191
195
  C -->|no, source!='clear'| P3["fresh session<br/>no merge"]
192
- P1 --> INJ["inject L1 + L2 + L3 refs<br/>(budgeted ≤ 9,500 chars)"]
196
+ P1 --> INJ["inject L2 turns + recall guidance<br/>(budgeted ≤ 9,500 chars)"]
193
197
  P2 --> INJ
194
198
 
195
199
  classDef primary fill:#7c5cff,stroke:#1a1f2e,color:#fff
@@ -895,7 +899,9 @@ throughline auditor-context --session claude-session-id --project "$PWD" \
895
899
  - **Node.js >= 22.13** (for the stable, flag-free built-in `node:sqlite` module — no native build
896
900
  required, no `npm install` of SQLite bindings)
897
901
  - **Claude Code** with hooks support (`SessionStart`, `Stop`)
898
- - **Claude Max subscription** (for Haiku-based L1 summarization via `claude -p`)
902
+ - **Codex CLI login** (default L1 summarization backend, `gpt-5.6-luna`) or a
903
+ **Claude Max subscription** (Haiku fallback via `claude -p`) — no API key
904
+ either way
899
905
  - Works on **Windows, macOS, Linux**
900
906
 
901
907
  Throughline has **zero runtime dependencies**. The published tarball is just
@@ -73,6 +73,18 @@ assistant 行だけ入り user 行が pull 側に半身で現れ、境界の算
73
73
  - 焼き込まれた案内コマンドをそのまま実行して、`--l2` が境界ぴったりから 11 ターン
74
74
  (33k 字)、`--l1` が全 171 ターン一覧(118k 字、`--last` で部分取得可)を返すことを確認
75
75
 
76
+ ### 実機 E2E(2026-07-18、global 0.8.0 / baton path)
77
+
78
+ - `/tl` → 新セッションの初回プロンプトで新形式注入が発火(47 ターン引き継ぎ)。
79
+ 注入は L1 非注入・現在地アンカー・L2 ターン原子詰め(11 ターン全文)・案内セクション
80
+ 焼き込みで、設計どおりの形を目視確認
81
+ - 焼き込まれた `recall --l2 --session <id> --before <ISO ms> --last 11` を新セッション側から
82
+ そのまま実行し、境界より古い 11 ターン(11:02:27〜11:38:57)が古い順・全文・L3 suffix
83
+ 付きで返り、注入分の最古ターン(11:48:57)と連続・重複欠落なしを確認
84
+ - 焼き込まれた `recall --l1 ... --skip 11` が窓外の全 9 ターンを「全 9 / 要約済み 7」の
85
+ 正直表示で返し、未要約 2 件は `throughline detail <時刻>` への誘導付きで明示された
86
+ - これをもって v0.8.0 の残タスク(新注入形式の実機 E2E)は完了
87
+
76
88
  ### multi-hook 10k 突破の実測(2026-07-18、Claude Code 2.1.211 / 不採用)
77
89
 
78
90
  - 同一 UserPromptSubmit に hook を 3 本登録 → それぞれ独立の `hook_success` attachment
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "throughline",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "type": "module",
5
5
  "description": "Claude Code hooks plugin for structured context compression (/clear-safe persistent memory)",
6
6
  "keywords": [
@@ -5,6 +5,7 @@ import { existsSync, mkdtempSync, rmSync, statSync } from 'node:fs';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { join } from 'node:path';
7
7
 
8
+ import { CURRENT_VERSION } from '../db.mjs';
8
9
  import {
9
10
  collectFactoryDiagnostics,
10
11
  inspectFactoryDatabase,
@@ -19,7 +20,12 @@ test('factory-diagnostics CLI: JSON-only contract and schema fixture', () => {
19
20
  stdout: { write(value) { output.push(value); } },
20
21
  version: '0.6.1',
21
22
  inspectThread: () => ({ status: 'ready', rolloutAvailable: true }),
22
- inspectDatabase: () => ({ status: 'ready', schemaVersion: 8, handoffMemory: true }),
23
+ inspectDatabase: () => ({
24
+ status: 'ready',
25
+ schemaVersion: CURRENT_VERSION,
26
+ supportedSchemaVersion: CURRENT_VERSION,
27
+ handoffMemory: true,
28
+ }),
23
29
  inspectHooks: () => ({
24
30
  status: 'ready',
25
31
  claudeStatus: 'ready',
@@ -32,6 +38,9 @@ test('factory-diagnostics CLI: JSON-only contract and schema fixture', () => {
32
38
  const parsed = JSON.parse(output[0]);
33
39
  assert.equal(parsed.schema, 'throughline.native_factory_diagnostics.v1');
34
40
  assert.equal(parsed.version, '0.6.1');
41
+ assert.equal(parsed.databaseSchema.schema, `throughline.database.v${CURRENT_VERSION}`);
42
+ assert.equal(parsed.databaseSchema.databaseSchemaVersion, CURRENT_VERSION);
43
+ assert.equal(parsed.databaseSchema.supportedDatabaseSchemaVersion, CURRENT_VERSION);
35
44
  assert.equal(parsed.readiness.restore.status, 'ready');
36
45
  assert.equal(parsed.evidence.restoreSmoke.status, 'unverified');
37
46
  });
@@ -101,22 +101,30 @@ export function resolveCodexHookNodePath({
101
101
  export function buildCodexStopHookCommand({
102
102
  nodePath = resolveCodexHookNodePath(),
103
103
  cliScriptPath = join(PACKAGE_ROOT, 'bin', 'throughline.mjs'),
104
+ platform = process.platform,
104
105
  } = {}) {
105
- return `${quoteCommandPath(nodePath)} ${quoteCommandPath(cliScriptPath)} codex-hook stop`;
106
+ return buildCodexHookCommand('stop', { nodePath, cliScriptPath, platform });
106
107
  }
107
108
 
108
109
  export function buildCodexUserPromptSubmitHookCommand({
109
110
  nodePath = resolveCodexHookNodePath(),
110
111
  cliScriptPath = join(PACKAGE_ROOT, 'bin', 'throughline.mjs'),
112
+ platform = process.platform,
111
113
  } = {}) {
112
- return `${quoteCommandPath(nodePath)} ${quoteCommandPath(cliScriptPath)} codex-hook user-prompt-submit`;
114
+ return buildCodexHookCommand('user-prompt-submit', { nodePath, cliScriptPath, platform });
113
115
  }
114
116
 
115
117
  export function buildCodexPostToolUseHookCommand({
116
118
  nodePath = resolveCodexHookNodePath(),
117
119
  cliScriptPath = join(PACKAGE_ROOT, 'bin', 'throughline.mjs'),
120
+ platform = process.platform,
118
121
  } = {}) {
119
- return `${quoteCommandPath(nodePath)} ${quoteCommandPath(cliScriptPath)} codex-hook post-tool-use`;
122
+ return buildCodexHookCommand('post-tool-use', { nodePath, cliScriptPath, platform });
123
+ }
124
+
125
+ function buildCodexHookCommand(event, { nodePath, cliScriptPath, platform }) {
126
+ const prefix = platform === 'win32' ? '& ' : '';
127
+ return `${prefix}${quoteCommandPath(nodePath)} ${quoteCommandPath(cliScriptPath)} codex-hook ${event}`;
120
128
  }
121
129
 
122
130
  export function isThroughlineCodexHookCommand(command) {
@@ -8,6 +8,7 @@ import {
8
8
  buildCodexPostToolUseHookCommand,
9
9
  buildCodexStopHookCommand,
10
10
  buildCodexUserPromptSubmitHookCommand,
11
+ isThroughlineCodexHookCommand,
11
12
  resolveCodexHookNodePath,
12
13
  run,
13
14
  resolveThroughlineOnPath,
@@ -141,6 +142,27 @@ test('global install registers Codex session hooks and enables hooks features',
141
142
  }
142
143
  });
143
144
 
145
+ test('Codex hook builders use the PowerShell call operator on Windows only', () => {
146
+ const options = {
147
+ nodePath: String.raw`C:\Program Files\nodejs\node.exe`,
148
+ cliScriptPath: String.raw`C:\Users\Kite\App Data\Roaming\npm\node_modules\throughline\bin\throughline.mjs`,
149
+ };
150
+ assert.equal(
151
+ buildCodexStopHookCommand({ ...options, platform: 'win32' }),
152
+ String.raw`& "C:\Program Files\nodejs\node.exe" "C:\Users\Kite\App Data\Roaming\npm\node_modules\throughline\bin\throughline.mjs" codex-hook stop`,
153
+ );
154
+ assert.equal(
155
+ buildCodexUserPromptSubmitHookCommand({ ...options, platform: 'win32' }),
156
+ String.raw`& "C:\Program Files\nodejs\node.exe" "C:\Users\Kite\App Data\Roaming\npm\node_modules\throughline\bin\throughline.mjs" codex-hook user-prompt-submit`,
157
+ );
158
+ assert.equal(
159
+ buildCodexPostToolUseHookCommand({ ...options, platform: 'win32' }),
160
+ String.raw`& "C:\Program Files\nodejs\node.exe" "C:\Users\Kite\App Data\Roaming\npm\node_modules\throughline\bin\throughline.mjs" codex-hook post-tool-use`,
161
+ );
162
+ assert.equal(buildCodexStopHookCommand({ ...options, platform: 'linux' }).startsWith('& '), false);
163
+ assert.equal(isThroughlineCodexHookCommand(buildCodexStopHookCommand({ ...options, platform: 'win32' })), true);
164
+ });
165
+
144
166
  test('global install copies Throughline Codex skill to ~/.codex/skills/', async () => {
145
167
  const home = makeTempHome();
146
168
  if (home.resolved !== home.dir) {
@@ -1,5 +1,7 @@
1
+ import { CURRENT_VERSION } from './db.mjs';
2
+
1
3
  export const FACTORY_DIAGNOSTICS_SCHEMA = 'throughline.native_factory_diagnostics.v1';
2
- export const DATABASE_SCHEMA = 'throughline.database.v8';
4
+ export const DATABASE_SCHEMA = `throughline.database.v${CURRENT_VERSION}`;
3
5
 
4
6
  const STATUSES = new Set(['ready', 'not_ready', 'not_applicable', 'unverified']);
5
7
 
@@ -2,9 +2,26 @@ import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
 
4
4
  import {
5
+ DATABASE_SCHEMA,
5
6
  FACTORY_DIAGNOSTICS_SCHEMA,
6
7
  buildFactoryDiagnostics,
7
8
  } from './factory-diagnostics.mjs';
9
+ import { CURRENT_VERSION } from './db.mjs';
10
+
11
+ test('factory diagnostics: database label は DB 実体・対応 version と一致する', () => {
12
+ const result = buildFactoryDiagnostics({
13
+ database: {
14
+ status: 'ready',
15
+ schemaVersion: CURRENT_VERSION,
16
+ supportedSchemaVersion: CURRENT_VERSION,
17
+ handoffMemory: true,
18
+ },
19
+ });
20
+
21
+ assert.equal(DATABASE_SCHEMA, `throughline.database.v${CURRENT_VERSION}`);
22
+ assert.equal(result.databaseSchema.schema, `throughline.database.v${result.databaseSchema.databaseSchemaVersion}`);
23
+ assert.equal(result.databaseSchema.databaseSchemaVersion, result.databaseSchema.supportedDatabaseSchemaVersion);
24
+ });
8
25
 
9
26
  test('factory diagnostics: 未設定の native factory は not_applicable を成功へ丸めない', () => {
10
27
  const result = buildFactoryDiagnostics({