throughline 0.3.22 → 0.3.24

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/README.ja.md ADDED
@@ -0,0 +1,255 @@
1
+ <p align="center">
2
+ <img src=".github/og.png" alt="Throughline — Claude Code のコンテキスト消費を約 90% 削減しつつ記憶はほぼ残す" width="100%">
3
+ </p>
4
+
5
+ # Throughline
6
+
7
+ [![npm version](https://img.shields.io/npm/v/throughline.svg?color=cb3837&logo=npm)](https://www.npmjs.com/package/throughline)
8
+ [![license](https://img.shields.io/npm/l/throughline.svg?color=blue)](LICENSE)
9
+ [![node](https://img.shields.io/node/v/throughline.svg?color=339933&logo=node.js&logoColor=white)](https://nodejs.org)
10
+ [![CI](https://github.com/kitepon-rgb/Throughline/actions/workflows/test.yml/badge.svg)](https://github.com/kitepon-rgb/Throughline/actions/workflows/test.yml)
11
+
12
+ [English](README.md) · **日本語**
13
+
14
+ > **Claude Code のコンテキスト消費を約 90% 削減しつつ、記憶はほぼそのまま残す。**
15
+ > 「時間の新旧」ではなく **「コンテンツの種類」** で会話を分離する。人間が読みたいテキストは残し、機械が出力したツール I/O は SQLite に退避する。同じ判断、同じ文脈、9 割軽量。
16
+
17
+ ## 30 秒で始める
18
+
19
+ ```bash
20
+ npm install -g throughline
21
+ throughline install # ~/.claude/settings.json に hook を登録
22
+ ```
23
+
24
+ これだけ。Claude Code のセッションを開けば、以後すべてのターンが
25
+ `~/.throughline/throughline.db` に自動で流れていく。50 ターン作業した後、
26
+ 次のセッションへ記憶を引き継ぎたければ `/clear` の前に `/tl` を打つ。新セッションは
27
+ ゼロからのスタートではなく、**思考の途中から再開** される。
28
+
29
+ ## 他の手段との比較
30
+
31
+ | | Throughline | MemGPT / SummaryBufferMemory | 素の Claude Code |
32
+ |---|---|---|---|
33
+ | **圧縮の軸** | コンテンツの **種類** (テキスト vs ツール I/O) | **新旧** (古い → 要約) | 無し |
34
+ | **コーディング用途への適合** | 高 — ツール I/O こそ重い 80% | 中 — 残したい部分まで圧縮される | — |
35
+ | **`/clear` 後の生存** | ✅ SQLite + `/tl` バトン | ホスト依存 | ❌ |
36
+ | **誤継承リスク** | ゼロ (明示的な `/tl`) | 高 | — |
37
+ | **ランタイム依存** | **ゼロ** (Node 22.5+ 同梱の `node:sqlite`) | 多数 | — |
38
+ | **マルチセッション トークン監視** | ✅ 実測 `message.usage`、`len/4` 推定なし | — | — |
39
+
40
+ <details>
41
+ <summary><b>なぜこれが効くのか — 80% ツール I/O 問題</b></summary>
42
+
43
+ 通常の Claude Code セッションでは、**コンテキストの 80% はツール I/O** です —
44
+ ファイル読み込み、Bash 出力、grep 結果。これらは Claude が即座に消費するデータですが、
45
+ コンテキスト上には永久に残り、ウィンドウ上限に向かって押し出されていきます。
46
+
47
+ Throughline はこの問題を、会話を **時間ではなく種類** で分離することで解決します:
48
+
49
+ ```
50
+ Throughline 無し (50 ターン、/clear なし):
51
+ コンテキスト = ユーザー文 + アシスタント文 + ツール I/O + システムメッセージ
52
+ ≈ 125,000 トークン (うち 80% は二度と読み返さないツール I/O)
53
+
54
+ Throughline 有り (50 ターン → /clear → 再開):
55
+ コンテキスト = 直近 20 ターンの会話本文 (L2)
56
+ + それ以前 30 ターンの一行要約 (L1)
57
+ + ツール I/O ゼロ (L3 — SQLite に退避、必要時にだけ取得)
58
+ ≈ 13,000 トークン — 同じ判断、同じ文脈、90% 軽量
59
+ ```
60
+
61
+ MemGPT や LangChain の SummaryBufferMemory が **新旧** で圧縮するのに対し、
62
+ Throughline は **コンテンツの種類** で分離します。人間が読むべき会話は残し、
63
+ 機械が生成した一過性のツール出力は退避する。コーディングアシスタント向けに
64
+ 特化した設計です。
65
+
66
+ 退避された L3 は失われていません。過去ターンのツール出力が再び必要になれば、
67
+ Claude は `throughline detail <時刻>` で取り戻せます。
68
+
69
+ Throughline は加えて、トランスクリプト JSONL から実測 API 使用量を読む
70
+ **マルチセッション トークン監視ツール** も同梱しています (`length / 4` 推定は使いません)。
71
+
72
+ </details>
73
+
74
+ ---
75
+
76
+ ## 3 層メモリーモデル (schema v7)
77
+
78
+ ```mermaid
79
+ flowchart LR
80
+ T["1 ターン<br/>ユーザー · アシスタント · ツール · 思考"]
81
+ T --> H["Stop hook"]
82
+ H --> L2[("L2 · bodies<br/>本文そのまま")]
83
+ H --> L3[("L3 · details<br/>ツール I/O · 思考")]
84
+ H -. "非同期<br/>Haiku" .-> L1[("L1 · skeletons<br/>一行要約")]
85
+
86
+ L2 -- "直近 20 ターン" --> S["次セッション<br/>SessionStart 注入"]
87
+ L1 -- "それ以前" --> S
88
+ L3 -. "オンデマンド · throughline detail" .-> S
89
+
90
+ classDef l1 fill:#3aa0ff,stroke:#1a1f2e,color:#fff
91
+ classDef l2 fill:#7c5cff,stroke:#1a1f2e,color:#fff
92
+ classDef l3 fill:#4a5568,stroke:#1a1f2e,color:#fff
93
+ class L1 l1
94
+ class L2 l2
95
+ class L3 l3
96
+ ```
97
+
98
+ | 層 | 名称 | 保存先 | 内容 | ターンあたりコスト |
99
+ | --- | --- | --- | --- | --- |
100
+ | **L1** | スケルトン | 古いターンとして注入 | Haiku が生成する一行要約 | 約 10 トークン |
101
+ | **L2** | ボディ | 直近ターンとして注入 | ユーザー本文 + アシスタント返答そのまま | 自然なフルサイズ |
102
+ | **L3** | ディテール | SQLite のみ | ツール I/O、システムメッセージ、画像、**拡張思考** (オンデマンド) | 重い、退避済 |
103
+
104
+ 3 層は **互いに補完的かつ排他的** で、重複保存はありません。
105
+ 拡張思考ブロックは L3 (`kind='thinking'`) に格納されるので、次セッションは
106
+ **前セッションの Claude が中断時に何を考えていたか** を、発話だけでなく
107
+ 内省レベルで参照できます。`SessionStart` では **最終ターンの思考** が L2 履歴の
108
+ 直上にインライン注入され、それ以前の思考は `throughline detail <時刻>` で取得できます。
109
+
110
+ `SessionStart` 時、Throughline は SQLite からコンテキストを再構築し、
111
+ プレーンテキストとして注入します:
112
+
113
+ - **直近 20 ターン** は L2 (`bodies`) のフル本文として注入
114
+ - **それ以前** は L1 (`skeletons`) の一行要約として注入
115
+ - L3 は SQLite に残り、`/sc-detail <時刻>` でオンデマンド取得
116
+
117
+ L1 要約は `claude -p --model claude-haiku-4-5-*` サブプロセスで
118
+ **Claude Haiku 4.5** が生成します。Claude Max のログイン認証を流用するため
119
+ API キー不要です。要約は遅延実行で、20 ターン未満で終わるセッションでは
120
+ Haiku は一度も呼ばれず、短いタスクの要約コストはゼロです。
121
+
122
+ 3 層 (L1/L2/L3) の書き込みパスは schema v5 から動作しています。
123
+ `/sc-detail HH:MM:SS` はユーザー / アシスタント本文 (L2) と、そのターンで
124
+ L3 に保存された `kind` 別 (ツール入力 / ツール出力 / hook 出力) を返します。
125
+
126
+ ---
127
+
128
+ ## 明示的引き継ぎ — `/tl` (in-flight メモ付き)
129
+
130
+ 引き継ぎは **明示的** であり、自動ではありません。次セッションへ「ここから続けてほしい」と
131
+ 渡したいときは、現セッションで `/clear` または新規チャットを開く **前に** `/tl` を打ちます。
132
+ `/tl` 無しの場合、新セッションはまっさらな状態で始まり、過去メモリは引き継がれません。
133
+
134
+ `/tl` は 2 つの仕事をします:
135
+
136
+ 1. **引き継ぎバトンの書き込み**。現在の `session_id` を `handoff_batons` テーブルへ、
137
+ `UserPromptSubmit` hook 経由で記録します。
138
+ 2. **現 Claude に in-flight メモを書かせる**。`/tl` は Claude に対し、
139
+ *次に何をしようとしていたか、現在の仮説、未解決の問い、進行中 TODO* を Markdown で
140
+ 要約し、`throughline save-inflight` 経由でバトンの `memo_text` カラムに添付するよう
141
+ 指示します。これにより、トランスクリプト再生だけでは保てない「いま考え中だった内容」を保存できます。
142
+
143
+ 次の `SessionStart` では、hook がバトンを読み、**1 時間以内** であれば
144
+ そのセッションのメモリを `BEGIN IMMEDIATE` トランザクション内で
145
+ `UPDATE session_id = ?` を使って決定論的にマージします。バトンはマージと
146
+ 原子的に消費 (削除) されるため、二重発火しません。注入される再開コンテキストは
147
+ **「中断されたタスクの再開」** として再フレーミングされ、in-flight メモと最終ターンの
148
+ 拡張思考が先頭に来ることで、新 Claude は思考の途中から拾えます。
149
+
150
+ ```
151
+ Session A (/tl を打つ) -----------> バトン書き込み
152
+ |
153
+ /clear |
154
+ | ▼
155
+ Session B ---- バトン読込 → A を B にマージ → バトン削除 ---->
156
+ |
157
+ (もう一度 /tl で更に渡せる)
158
+ ```
159
+
160
+ 明示的バトン方式を選んだ理由:
161
+
162
+ - **誤継承ゼロ**。並行ウィンドウや VSCode 再起動、同じリポジトリでの新規タスクが、
163
+ 前セッションのメモリを誤って引き継ぐことはありません。`/tl` 明示時のみ発火。
164
+ - **VSCode 拡張対応**。`SessionStart` hook の `source` フィールドは VSCode 拡張で
165
+ `/clear` 後も `"startup"` に書き換えられてしまう
166
+ ([issue #49937](https://github.com/anthropics/claude-code/issues/49937))。
167
+ source 判定は信用できないため、ユーザー駆動のバトンで回避。
168
+ - **決定論的**。時間窓ヒューリスティック、PID 推測、祖先プロセス追跡なし。
169
+ ユーザーが意思を宣言し、hook が実行する。それだけ。
170
+
171
+ 各マージ行は `origin_session_id` を保持するので、`/tl` を繰り返すと
172
+ 記憶がチェーン状に蓄積します:
173
+
174
+ ```
175
+ S1 (4 ターン) --/tl,/clear--> S2 (S1 をマージ + 3 ターン追加) --/tl,/clear--> S3 (S2 をマージ + 5 ターン追加)
176
+ origin=S1×4 origin=S1×4, S2×3, S3×5
177
+ ```
178
+
179
+ ---
180
+
181
+ ## マルチセッション トークン監視
182
+
183
+ 実行:
184
+
185
+ ```bash
186
+ throughline monitor # 現プロジェクトのアクティブな全セッション
187
+ throughline monitor --all # 全プロジェクト、全セッション
188
+ throughline monitor --session <id-prefix>
189
+ ```
190
+
191
+ 実機出力例 (1M context Opus セッション稼働中):
192
+
193
+ ```
194
+ [Throughline] 1 セッション
195
+ ▶ Throughline 2ed5039c ████░░░░░░░░░░░░░░░░ 205.1k / 21% 残 794.9k claude-opus-4-6
196
+ ```
197
+
198
+ 詳細仕様 (resize 追従、1M context 検出、ステイル隠し、Stop hook の非同期化など) は
199
+ [英語版 README](README.md#multi-session-token-monitor) を参照してください。
200
+
201
+ ---
202
+
203
+ ## コマンド早見表
204
+
205
+ | コマンド | 役割 |
206
+ | --- | --- |
207
+ | `throughline install` | `~/.claude/settings.json` (ユーザー全体) に hook を登録 |
208
+ | `throughline install --project` | 現リポジトリの `.claude/settings.json` だけに hook を登録 |
209
+ | `throughline uninstall` | hook を削除 |
210
+ | `throughline monitor` | マルチセッション監視を起動 |
211
+ | `throughline monitor --diag` | TTY/columns/env 診断ダンプ (描画バグ切り分け用) |
212
+ | `throughline detail <時刻>` | あるターンの L2 本文と L3 ツール I/O を取得 (Claude が使う) |
213
+ | `throughline save-inflight` | `/tl` から呼ばれ、現バトンに in-flight メモを添付 (stdin 経由) |
214
+ | `throughline doctor` | Node バージョン、hook 登録状況、DB、PATH をチェック |
215
+ | `throughline doctor --session <id-prefix>` | 特定セッションの state/transcript ズレを診断 |
216
+ | `throughline status` | DB 統計表示 (sessions / skeletons / bodies / details) |
217
+ | `throughline --version` | インストール済みバージョンを表示 |
218
+
219
+ スラッシュコマンド (Claude Code 内でユーザーが叩く):
220
+
221
+ | コマンド | 役割 |
222
+ | --- | --- |
223
+ | `/tl` | 引き継ぎバトンを書き込み + Claude に in-flight メモを書かせる |
224
+ | `/sc-detail <時刻>` | 過去ターンの L2 本文と L3 ツール I/O を取得 |
225
+
226
+ > `/tl` 発火時、Claude は Bash 経由で `throughline save-inflight` を呼びます。
227
+ > 初回は許可確認が出るので、`Bash(throughline save-inflight:*)` を allowlist に
228
+ > 追加すると以後の確認はスキップできます。
229
+
230
+ ---
231
+
232
+ ## 動作要件
233
+
234
+ - **Node.js 22.5 以上** (組み込み `node:sqlite` モジュール使用、ネイティブビルド不要)
235
+ - **Claude Code** (`SessionStart`, `Stop`, `UserPromptSubmit` hooks 対応版)
236
+ - **Claude Max サブスクリプション** (Haiku ベース L1 要約のため `claude -p` 経由)
237
+ - 対応 OS: **Windows / macOS / Linux**
238
+
239
+ ランタイム依存 **ゼロ**。npm パッケージは純 `.mjs` ファイルのみで構成されています。
240
+
241
+ ---
242
+
243
+ ## 設計ドキュメント
244
+
245
+ - [`docs/L1_L2_L3_REDESIGN.md`](docs/L1_L2_L3_REDESIGN.md) — L1/L2/L3 差分階層モデルの **設計仕様書** (schema v4 ベース + v5 L3 分類拡張)。記憶階層化ルールの正典
246
+ - [`docs/INHERITANCE_ON_CLEAR_ONLY.md`](docs/INHERITANCE_ON_CLEAR_ONLY.md) — `/tl` バトン引き継ぎ方式の設計判断記録 (schema v6–v7)
247
+ - [`docs/PUBLIC_RELEASE_PLAN.md`](docs/PUBLIC_RELEASE_PLAN.md) — 公開配布化プラン、§ 0 フォールバック禁止ルール、バージョン別実装ステータス
248
+ - [`CHANGELOG.md`](CHANGELOG.md) — リリース履歴
249
+ - [`docs/archive/`](docs/archive/) — 破棄済み旧設計 (CONCEPT 初期案、session-linking 実験記録など)
250
+
251
+ ---
252
+
253
+ ## ライセンス
254
+
255
+ MIT — [LICENSE](LICENSE) 参照。
package/README.md CHANGED
@@ -1,6 +1,45 @@
1
+ <p align="center">
2
+ <img src=".github/og.png" alt="Throughline — cut ~90% of Claude Code's context usage while keeping nearly all the memory" width="100%">
3
+ </p>
4
+
1
5
  # Throughline
2
6
 
3
- **Cut ~90% of Claude Code's context usage while keeping nearly all the memory.**
7
+ [![npm version](https://img.shields.io/npm/v/throughline.svg?color=cb3837&logo=npm)](https://www.npmjs.com/package/throughline)
8
+ [![license](https://img.shields.io/npm/l/throughline.svg?color=blue)](LICENSE)
9
+ [![node](https://img.shields.io/node/v/throughline.svg?color=339933&logo=node.js&logoColor=white)](https://nodejs.org)
10
+ [![CI](https://github.com/kitepon-rgb/Throughline/actions/workflows/test.yml/badge.svg)](https://github.com/kitepon-rgb/Throughline/actions/workflows/test.yml)
11
+
12
+ **English** · [日本語](README.ja.md)
13
+
14
+ > **Cut ~90% of Claude Code's context usage while keeping nearly all the memory.**
15
+ > Throughline separates conversation by **type, not time** — humans-readable text stays, machine-generated tool output retires to SQLite. Same decisions, same context, 90% lighter.
16
+
17
+ ## In 30 seconds
18
+
19
+ ```bash
20
+ npm install -g throughline
21
+ throughline install # registers hooks in ~/.claude/settings.json
22
+ ```
23
+
24
+ That's it. Open any Claude Code session and your turns flow into
25
+ `~/.throughline/throughline.db` automatically. After 50 turns of work, type
26
+ `/clear` (or just open a new chat), then `/tl` first if you want the next
27
+ session to inherit the memory — the new session resumes mid-thought instead
28
+ of starting from zero.
29
+
30
+ ## How it compares
31
+
32
+ | | Throughline | MemGPT / SummaryBufferMemory | Plain Claude Code |
33
+ |---|---|---|---|
34
+ | **Compression axis** | content **type** (text vs tool I/O) | **recency** (old → summarized) | none |
35
+ | **Coding-assistant fit** | high — tool I/O is the heavy 80% | medium — also compresses what you want to keep | — |
36
+ | **`/clear` survival** | ✅ via SQLite + `/tl` baton | depends on host | ❌ |
37
+ | **Auto-inheritance risk** | zero (explicit `/tl`) | high | — |
38
+ | **Runtime deps** | **zero** (Node 22.5+ built-in `node:sqlite`) | many | — |
39
+ | **Multi-session token monitor** | ✅ real `message.usage`, no `len/4` | — | — |
40
+
41
+ <details>
42
+ <summary><b>Why this matters — the 80% tool-I/O problem</b></summary>
4
43
 
5
44
  In a typical Claude Code session, **80% of the context window is tool I/O** —
6
45
  file reads, Bash output, grep results. This data is consumed the moment Claude
@@ -33,26 +72,32 @@ again.
33
72
  Throughline also ships a multi-session **token monitor** that reads real
34
73
  Anthropic API usage from the transcript JSONL (no `length / 4` heuristics).
35
74
 
36
- ---
37
-
38
- ## Quick Start
39
-
40
- ```bash
41
- npm install -g throughline
42
- throughline install
43
- ```
44
-
45
- That's it. `install` registers Throughline's hooks in `~/.claude/settings.json`
46
- (user scope), so every Claude Code project on your machine picks it up
47
- automatically. No per-project wiring required.
48
-
49
- Start any Claude Code session and your turns will begin flowing into
50
- `~/.throughline/throughline.db` in the background.
75
+ </details>
51
76
 
52
77
  ---
53
78
 
54
79
  ## Three-layer memory model (schema v7)
55
80
 
81
+ ```mermaid
82
+ flowchart LR
83
+ T["Live turn<br/>user · assistant · tools · thinking"]
84
+ T --> H["Stop hook"]
85
+ H --> L2[("L2 · bodies<br/>verbatim text")]
86
+ H --> L3[("L3 · details<br/>tool I/O · thinking")]
87
+ H -. "async<br/>Haiku" .-> L1[("L1 · skeletons<br/>one-liners")]
88
+
89
+ L2 -- "recent 20 turns" --> S["Next SessionStart<br/>injection"]
90
+ L1 -- "older turns" --> S
91
+ L3 -. "on demand · throughline detail" .-> S
92
+
93
+ classDef l1 fill:#3aa0ff,stroke:#1a1f2e,color:#fff
94
+ classDef l2 fill:#7c5cff,stroke:#1a1f2e,color:#fff
95
+ classDef l3 fill:#4a5568,stroke:#1a1f2e,color:#fff
96
+ class L1 l1
97
+ class L2 l2
98
+ class L3 l3
99
+ ```
100
+
56
101
  | Layer | Name | Where it lives | Content | Cost per turn |
57
102
  | ----- | ---------- | --------------------- | --------------------------------------------------------------------- | ------------- |
58
103
  | **L1** | Skeleton | injected when old | one-line Haiku-generated summary of the turn | ~10 tok |
@@ -201,6 +246,15 @@ Example output (real values from a running 1M-context Opus session):
201
246
  file. The monitor prefers this snapshot over re-reading the JSONL, which
202
247
  removes a source of flicker when the transcript path in state drifts from
203
248
  the one Claude Code is currently appending to.
249
+ - **Non-blocking Stop hook (v0.3.22+).** The Stop hook is registered with
250
+ `"async": true` so `throughline process-turn` runs in the background and
251
+ does not delay Claude's reply from reaching you. L1 Haiku summarization
252
+ (`claude -p` subprocess + inference, seconds to tens of seconds) would
253
+ otherwise stall the user-facing response of every turn; since L1 is only
254
+ needed for the *next* session's SessionStart injection, there is no reason
255
+ to block the current turn on it. Existing installs need
256
+ `throughline uninstall && throughline install` to promote the flag (the
257
+ dedup logic skips entries that match by command string).
204
258
 
205
259
  ### VS Code auto-start (automatic)
206
260
 
@@ -414,6 +468,74 @@ Run `throughline doctor` — it checks Node version, hook registration, DB
414
468
  writability, and PATH resolution. If the binary is not on PATH, reinstall with
415
469
  `npm install -g throughline`.
416
470
 
471
+ The most common cause is **the npm global `bin/` directory is not on PATH**.
472
+ This happens when you set `npm config set prefix ~/.npm-global` (the sudoless
473
+ recommended setup) but only added the export to `~/.profile`, not `~/.bashrc`.
474
+ VSCode's integrated terminal launches an *interactive non-login* bash, which
475
+ reads `.bashrc` only — so the export is silently skipped and `throughline` is
476
+ not found. Fix:
477
+
478
+ ```bash
479
+ # bash: add to ~/.bashrc
480
+ if [ -d "$HOME/.npm-global/bin" ] ; then
481
+ PATH="$HOME/.npm-global/bin:$PATH"
482
+ fi
483
+ ```
484
+
485
+ Then reload the shell (`source ~/.bashrc`) and verify with `which throughline`.
486
+
487
+ `throughline install` itself prints a warning to stderr if PATH resolution
488
+ fails at install time, but if you missed it, run `throughline doctor` again.
489
+
490
+ **Using Throughline across Windows ↔ WSL2 (or Linux ↔ macOS)**
491
+
492
+ Two things to know:
493
+
494
+ 1. **The DB lives under `os.homedir()`**, so each environment has its own
495
+ database. `C:\Users\<you>\.throughline\throughline.db` (Windows) and
496
+ `/home/<you>/.throughline/throughline.db` (WSL2) are unrelated. Memory does
497
+ **not** roam. To migrate:
498
+ ```bash
499
+ # From WSL2, copy Windows-side DB into WSL2 home
500
+ cp /mnt/c/Users/<you>/.throughline/throughline.db ~/.throughline/throughline.db
501
+ ```
502
+ Or vice versa. Do this with no Claude Code sessions running so WAL/SHM are
503
+ clean.
504
+
505
+ 2. **WSL2 sees Windows npm shims via `/mnt/c/...AppData/Roaming/npm`**, which
506
+ may be earlier on PATH than your WSL2-native npm-global bin. Symptom:
507
+ `which throughline` returns a `/mnt/c/...` path even though you ran
508
+ `npm install -g throughline` inside WSL2. Fix: ensure
509
+ `~/.npm-global/bin` is **before** the Windows npm path in `PATH` (the
510
+ `.bashrc` snippet above prepends, so it does this naturally).
511
+
512
+ **`.vscode/tasks.json` should not be committed to git (recommended)**
513
+
514
+ The `Throughline Monitor` task that gets auto-generated contains absolute
515
+ paths specific to your machine (`process.execPath` and the install location
516
+ of `throughline.mjs`). For shared repositories, add one of these to
517
+ `.gitignore`:
518
+
519
+ ```gitignore
520
+ .vscode/tasks.json # only ignore tasks.json (settings.json etc. stay shared)
521
+ .vscode/ # ignore the whole .vscode directory
522
+ ```
523
+
524
+ Throughline detects this and prints a one-time recommendation when it first
525
+ creates / merges / repairs the file. (You don't have to follow the advice —
526
+ v0.3.23+ auto-repairs stale absolute paths anyway, so committing is not
527
+ catastrophic.)
528
+
529
+ **Cross-environment `.vscode/tasks.json` errors after switching machines**
530
+
531
+ If you commit `.vscode/tasks.json` to git and pull it on a different machine
532
+ (Windows ↔ WSL2, Linux ↔ macOS, etc.), the `command` and `args` paths inside
533
+ were absolutized to the original machine. Throughline auto-detects this:
534
+ on the next hook fire it rewrites just the `command` / `args` fields to the
535
+ current environment, preserving any `label` / `presentation` customization
536
+ you made. You'll see a one-time `Reload Window` notice in Claude Code when
537
+ the repair happens.
538
+
417
539
  **`node:sqlite` warning on startup**
418
540
  Node.js prints `ExperimentalWarning: SQLite is an experimental feature` on stderr.
419
541
  This is cosmetic — the module is stable enough for production and is used
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "throughline",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "type": "module",
5
5
  "description": "Claude Code hooks plugin for structured context compression (/clear-safe persistent memory)",
6
6
  "keywords": [
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, copyFileSync, unlinkSync } from 'node:fs';
14
- import { join, dirname, resolve } from 'node:path';
14
+ import { join, dirname, resolve, delimiter } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
16
  import { homedir } from 'node:os';
17
17
 
@@ -89,6 +89,54 @@ function uninstallSlashCommands(commandsDir) {
89
89
  return removed;
90
90
  }
91
91
 
92
+ /**
93
+ * PATH 上で 'throughline' (Windows なら .cmd / .ps1 / .exe) が解決できるかを確認する。
94
+ *
95
+ * 解決できない場合 hook (`throughline session-start` 等) は command not found で
96
+ * silent fail する。npm の global prefix bin が PATH に通っていない (Linux/WSL2 の
97
+ * `~/.npm-global` 設定派が `.profile` だけに PATH を書いて `.bashrc` に書き忘れる
98
+ * パターンなど) と、ユーザーは「入れたのに何も起きない」状態に陥る。
99
+ *
100
+ * 戻り値: 解決できた絶対パス、見つからなければ null。
101
+ */
102
+ export function resolveThroughlineOnPath(env = process.env) {
103
+ const pathEnv = env.PATH || env.Path || '';
104
+ const dirs = pathEnv.split(delimiter).filter(Boolean);
105
+ const exts = process.platform === 'win32'
106
+ ? (env.PATHEXT || '.EXE;.CMD;.BAT').split(';').filter(Boolean)
107
+ : [''];
108
+ for (const dir of dirs) {
109
+ for (const ext of exts) {
110
+ const candidate = join(dir, `throughline${ext}`);
111
+ if (existsSync(candidate)) return candidate;
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+
117
+ function emitPathWarning() {
118
+ const lines = [
119
+ '',
120
+ '警告: PATH 上で `throughline` コマンドが解決できません。',
121
+ ' hooks は PATH 経由で `throughline` を呼び出すため、このままでは静かに失敗します。',
122
+ '',
123
+ '対処:',
124
+ ' 1) npm の global prefix を確認: npm prefix -g',
125
+ ' 2) その bin ディレクトリを PATH に追加',
126
+ '',
127
+ ];
128
+ if (process.platform === 'win32') {
129
+ lines.push(' Windows: 環境変数 PATH に %APPDATA%\\npm を追加');
130
+ } else {
131
+ lines.push(' bash: ~/.bashrc に export PATH="$(npm prefix -g)/bin:$PATH" を追記');
132
+ lines.push(' zsh: ~/.zshrc に同じ行を追記');
133
+ }
134
+ lines.push('');
135
+ lines.push(' 3) シェルを開き直して `throughline doctor` で確認');
136
+ lines.push('');
137
+ process.stderr.write(lines.join('\n'));
138
+ }
139
+
92
140
  function readSettings(settingsPath) {
93
141
  if (!existsSync(settingsPath)) return {};
94
142
  return JSON.parse(readFileSync(settingsPath, 'utf8'));
@@ -166,4 +214,8 @@ export async function run(args = []) {
166
214
  console.log('');
167
215
  }
168
216
  console.log(' アンインストール: throughline uninstall');
217
+
218
+ if (!resolveThroughlineOnPath()) {
219
+ emitPathWarning();
220
+ }
169
221
  }
@@ -4,7 +4,7 @@ import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync, mkdirSync
4
4
  import { tmpdir, homedir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
 
7
- import { run } from './install.mjs';
7
+ import { run, resolveThroughlineOnPath } from './install.mjs';
8
8
 
9
9
  function makeTempHome() {
10
10
  const dir = mkdtempSync(join(tmpdir(), 'tl-install-test-'));
@@ -29,11 +29,14 @@ function makeTempHome() {
29
29
  function silence() {
30
30
  const origLog = console.log;
31
31
  const origErr = console.error;
32
+ const origStderrWrite = process.stderr.write.bind(process.stderr);
32
33
  console.log = () => {};
33
34
  console.error = () => {};
35
+ process.stderr.write = () => true;
34
36
  return () => {
35
37
  console.log = origLog;
36
38
  console.error = origErr;
39
+ process.stderr.write = origStderrWrite;
37
40
  };
38
41
  }
39
42
 
@@ -153,6 +156,45 @@ test('Stop hook is registered with async:true so it does not block ターン完
153
156
  }
154
157
  });
155
158
 
159
+ // --- resolveThroughlineOnPath (地雷 1: PATH 解決チェック) ---
160
+
161
+ test('resolveThroughlineOnPath: returns null when PATH is empty', () => {
162
+ assert.equal(resolveThroughlineOnPath({ PATH: '' }), null);
163
+ assert.equal(resolveThroughlineOnPath({}), null);
164
+ });
165
+
166
+ test('resolveThroughlineOnPath: returns null when not in any PATH directory', () => {
167
+ const dir = mkdtempSync(join(tmpdir(), 'tl-path-empty-'));
168
+ try {
169
+ assert.equal(resolveThroughlineOnPath({ PATH: dir }), null);
170
+ } finally {
171
+ rmSync(dir, { recursive: true, force: true });
172
+ }
173
+ });
174
+
175
+ test('resolveThroughlineOnPath: finds throughline binary in PATH directory', () => {
176
+ // Windows は PATHEXT 経由でしか見つからないので分岐
177
+ const dir = mkdtempSync(join(tmpdir(), 'tl-path-found-'));
178
+ try {
179
+ if (process.platform === 'win32') {
180
+ const binPath = join(dir, 'throughline.cmd');
181
+ writeFileSync(binPath, '@echo off\n');
182
+ const result = resolveThroughlineOnPath({
183
+ PATH: dir,
184
+ PATHEXT: '.CMD',
185
+ });
186
+ assert.equal(result, binPath);
187
+ } else {
188
+ const binPath = join(dir, 'throughline');
189
+ writeFileSync(binPath, '#!/bin/sh\n');
190
+ const result = resolveThroughlineOnPath({ PATH: dir });
191
+ assert.equal(result, binPath);
192
+ }
193
+ } finally {
194
+ rmSync(dir, { recursive: true, force: true });
195
+ }
196
+ });
197
+
156
198
  test('install is idempotent: second run keeps exactly one tl.md and one hook entry', async () => {
157
199
  const home = makeTempHome();
158
200
  if (home.resolved !== home.dir) {