throughline 0.14.1 → 0.14.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,20 @@ shipped to npm but were not individually tagged on GitHub.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.14.2] — 2026-10-05
14
+
15
+ ### 修正
16
+
17
+ - WindowsのClaude Codeの自動継続で、後継へ記憶が入らない不具合を直す。Claude CodeはWindowsのhookをGit Bashで走らせ、起動したprojectの場所を`C:/Users/…`の形で渡す。0.14.1までは、圧縮を止めるhookがこの形のまま印(baton)を残していた。後継の最初の指示は`C:\Users\…`の形で印を探すので見つからず、記憶を注入しなかった。継続の指示は届くので、後継は記憶の無いまま動いた。配送の結果は`unknown`(`handoff_delivery_timeout`)になった。
18
+ - 印と引き継ぎの記録に残すprojectの場所を、そのOSの書き方にそろえる。macOSとLinuxの動作は変わらない。
19
+ - 「ここまでにしたこと」の道具の対象を作業フォルダからの相対で書く処理も、Windowsで効くようになる。
20
+
21
+ ### 確認した範囲
22
+
23
+ - Windows 11(10.0.26200)のClaude Code 2.1.289、Haiku 4.5。対話の会話から始めて、4回連続で引き継いで作業を完了した。読み直しも読み飛ばしも無く、どの会話でも自動圧縮は走っていない。0.14.1では、同じ条件で1回目の後継に記憶が入らなかった。
24
+ - 圧縮を止める・道具を止める・`claude --bg`で後継を立てる・受け口(named pipe)へ継続の指示を送る、の4つは、0.14.1の時点からWindowsで動いていた。
25
+ - Claude Desktopの画面から始めた会話、WindowsでのHaiku以外のモデル、subagentが動いている最中の引き継ぎは確かめていない。
26
+
13
27
  ## [0.14.1] — 2026-10-05
14
28
 
15
29
  ### 修正
@@ -1696,7 +1710,8 @@ two attempts, instrument first instead of patching again.
1696
1710
 
1697
1711
  ---
1698
1712
 
1699
- [Unreleased]: https://github.com/kitepon/Throughline/compare/v0.14.1...HEAD
1713
+ [Unreleased]: https://github.com/kitepon/Throughline/compare/v0.14.2...HEAD
1714
+ [0.14.2]: https://github.com/kitepon/Throughline/compare/v0.14.1...v0.14.2
1700
1715
  [0.14.1]: https://github.com/kitepon/Throughline/compare/v0.14.0...v0.14.1
1701
1716
  [0.14.0]: https://github.com/kitepon/Throughline/compare/v0.13.0...v0.14.0
1702
1717
  [0.13.0]: https://github.com/kitepon/Throughline/compare/v0.12.9...v0.13.0
package/README.ja.md CHANGED
@@ -69,7 +69,7 @@ throughline auto-handoff status --host claude --operation <handoff-id> --json
69
69
 
70
70
  後継の立ち上げや配送に失敗した時は、固定の理由を記録して止まります。結果が不明な指示は再送しません。圧縮を止めた後にモデルが道具を呼ばずにターンを終えた時は、後継を立てません。印は残るので、1時間以内に開いた新しい会話が記憶を引き継ぎます。
71
71
 
72
- 有効にした端末では、Claudeの道具の呼び出しのたびに`PreToolUse`フックが1回走ります。LinuxとmacOSのClaude Code(2.1.289、Claude Desktop同梱の2.1.286)で実機確認済みです。Windowsと、Claude Desktopの画面から始めた会話は未検証です。[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-05-claude-auto-handoff.md)を参照してください。
72
+ 有効にした端末では、Claudeの道具の呼び出しのたびに`PreToolUse`フックが1回走ります。Linux・macOS・WindowsのClaude Code 2.1.289(macOSはClaude Desktop同梱の2.1.286も)で、端末から始めた会話を実機確認済みです。Claude Desktopの画面から始めた会話は未検証です。[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-05-claude-auto-handoff.md)を参照してください。
73
73
 
74
74
  ## 所有境界
75
75
 
package/README.md CHANGED
@@ -68,7 +68,7 @@ throughline auto-handoff status --host claude --operation <handoff-id> --json
68
68
 
69
69
  後継の立ち上げや配送に失敗した時は、固定の理由を記録して止まります。結果が不明な指示は再送しません。圧縮を止めた後にモデルが道具を呼ばずにターンを終えた時は、後継を立てません。印は残るので、1時間以内に開いた新しい会話が記憶を引き継ぎます。
70
70
 
71
- 有効にした端末では、Claudeの道具の呼び出しのたびに`PreToolUse`フックが1回走ります。LinuxとmacOSのClaude Code(2.1.289、Claude Desktop同梱の2.1.286)で実機確認済みです。Windowsと、Claude Desktopの画面から始めた会話は未検証です。[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-05-claude-auto-handoff.md)を参照してください。
71
+ 有効にした端末では、Claudeの道具の呼び出しのたびに`PreToolUse`フックが1回走ります。Linux・macOS・WindowsのClaude Code 2.1.289(macOSはClaude Desktop同梱の2.1.286も)で、端末から始めた会話を実機確認済みです。Claude Desktopの画面から始めた会話は未検証です。[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-05-claude-auto-handoff.md)を参照してください。
72
72
 
73
73
  ## Ownership boundary
74
74
 
@@ -30,6 +30,8 @@ Claude Desktop の新しい会話は、画面を開いただけでは process
30
30
 
31
31
  1. **圧縮を止める**: `PreCompact` hook(`throughline pre-compact`、matcher 無し)が、`trigger: "auto"` で自動継続が
32
32
  有効な project の時だけ、`/tl` と同じ印(baton)と引き継ぎの記録を残し、exit code 2 で圧縮を止める。
33
+ 印の project は、会話を起動した場所(`CLAUDE_PROJECT_DIR`)を、その OS の書き方にそろえたもの(0.14.2)。
34
+ hook の cwd は Bash の `cd` に追従するので使わない。後継は起動した場所で立ち、その cwd で印を探す。
33
35
  手動の `/compact`、無効な project、subagent の中の圧縮(payload に `agent_id`)、script から起動した会話
34
36
  (`CLAUDE_CODE_ENTRYPOINT` が `sdk-` で始まる)では止めない。
35
37
  2. **旧い会話を止める**: `PreToolUse` hook(`throughline pre-tool-use`、全ての道具)が、記録のある会話の次の道具を、
@@ -101,4 +103,7 @@ Claude Desktop の新しい会話は、画面を開いただけでは process
101
103
  - macOS 27.0。端末の会話(2.1.289)と、Claude Desktop が同梱する本体(2.1.286)を端末から起動した会話から始めた。
102
104
  0.14.1 で、Haiku 4.5 は4回、Opus 5.5 は7回、連続で引き継いで完了した。読み直しも読み飛ばしも無い。
103
105
  - どの会話にも圧縮の記録は無く、止めた道具は実行されていなかった。
104
- - Claude Desktop の画面から始めた会話、Windows、Fable、subagent が動いている最中の引き継ぎは確かめていない。
106
+ - Windows 11。端末の会話(2.1.289)から始めた。0.14.2 で、Haiku 4.5 は4回、連続で引き継いで完了した。
107
+ 0.14.1 までは、印(baton)に残す project の場所が `C:/…` の形で、後継が `C:\…` の形で探して見つけられなかった。
108
+ 印と記録に残す場所は、その OS の書き方にそろえる(0.14.2)。
109
+ - Claude Desktop の画面から始めた会話、Fable、subagent が動いている最中の引き継ぎは確かめていない。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "throughline",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "type": "module",
5
5
  "description": "Persistent memory hooks for Claude Code, Codex, Grok, and Cursor (/clear-safe context compression)",
6
6
  "keywords": [
@@ -18,7 +18,7 @@
18
18
 
19
19
  import { closeSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
20
20
  import { homedir } from 'node:os';
21
- import { basename, dirname, join, resolve } from 'node:path';
21
+ import { basename, dirname, join, posix, resolve, win32 } from 'node:path';
22
22
  import { randomUUID } from 'node:crypto';
23
23
  import { fileURLToPath } from 'node:url';
24
24
  import { setTimeout as delay } from 'node:timers/promises';
@@ -159,6 +159,17 @@ function unsupportedEntrypoint(env) {
159
159
  return typeof env.CLAUDE_CODE_ENTRYPOINT === 'string' && env.CLAUDE_CODE_ENTRYPOINT.startsWith('sdk-');
160
160
  }
161
161
 
162
+ /**
163
+ * 会話を起動した project の場所を、その OS の書き方にそろえる。
164
+ *
165
+ * Windows の Claude Code は hook を Git Bash で走らせ、CLAUDE_PROJECT_DIR を `C:/Users/…` の形で渡す。
166
+ * hook の payload の cwd は `C:\Users\…` で、後継の最初の指示はこちらで印(baton)を探す。
167
+ * 書き方が違うと、後継が印を見つけられず、記憶が入らない。
168
+ */
169
+ function nativeProjectPath(projectPath, platform) {
170
+ return (platform === 'win32' ? win32 : posix).resolve(projectPath);
171
+ }
172
+
162
173
  /**
163
174
  * PreCompact hook の本体。自動圧縮で、その project が有効な時だけ、印と記録を残して圧縮を止める。
164
175
  *
@@ -177,6 +188,7 @@ export function requestClaudeAutoHandoff({
177
188
  config = readClaudeAutoHandoffConfig(),
178
189
  now = Date.now(),
179
190
  dir = claudeAutoHandoffDir(),
191
+ platform = process.platform,
180
192
  }) {
181
193
  const sessionId = payload?.session_id;
182
194
  if (typeof sessionId !== 'string' || !sessionId) throw new Error('Missing session_id in PreCompact payload');
@@ -185,7 +197,8 @@ export function requestClaudeAutoHandoff({
185
197
  const path = recordPath(sessionId, dir);
186
198
  const existing = readClaudeAutoHandoff(sessionId, dir);
187
199
  // 有効判定は会話を起動した project で行う。hook の cwd は Bash の cd に追従する。
188
- const projectPath = claudeHostAdapter.completionProjectPath({ cwd: payload.cwd ?? process.cwd(), env });
200
+ const projectPath = nativeProjectPath(
201
+ claudeHostAdapter.completionProjectPath({ cwd: payload.cwd ?? process.cwd(), env }), platform);
189
202
 
190
203
  if (payload.trigger !== 'auto') {
191
204
  // 人が /compact を選んだ。まだ止めていない記録は取り下げる。
@@ -57,6 +57,8 @@ function request(dir, overrides = {}) {
57
57
  payload: { session_id: SESSION, trigger: 'auto', cwd: '/work/app', transcript_path: overrides.transcriptPath ?? null,
58
58
  ...overrides.payload },
59
59
  env: overrides.env ?? {}, config: overrides.config ?? ENABLED, openDb: () => db, dir, now: overrides.now ?? 1_000,
60
+ // 試験の project は POSIX の書き方(/work/app)。Windows の CI でも同じ値で比べる
61
+ platform: 'linux',
60
62
  });
61
63
  return { result, db };
62
64
  }
@@ -84,6 +86,20 @@ test('PreCompact: 自動圧縮で有効な時だけ、印と記録を残して
84
86
  assert.deepEqual([started.status, started.projectPath], ['requested', '/work/app']);
85
87
  }));
86
88
 
89
+ test('PreCompact: Windows の `C:/…` の形の project は、後継が印を探す時の書き方(`C:\\…`)にそろえて残す', () => withDir(dir => {
90
+ const db = makeBatonDb();
91
+ const result = requestClaudeAutoHandoff({
92
+ // Windows の Git Bash で走る hook には、project の場所が `C:/…` の形で渡る(実機では CLAUDE_PROJECT_DIR)
93
+ payload: { session_id: SESSION, trigger: 'auto', cwd: 'C:/Users/k/proj/' },
94
+ env: {}, platform: 'win32',
95
+ config: { enabled: true, projects: [] }, openDb: () => db, dir, now: 1_000,
96
+ });
97
+ assert.equal(result.status, 'requested');
98
+ assert.equal(result.projectPath, 'C:\\Users\\k\\proj');
99
+ assert.equal(db.prepare('SELECT project_path FROM handoff_batons').get().project_path, 'C:\\Users\\k\\proj');
100
+ assert.equal(readClaudeAutoHandoff(SESSION, dir).project_path, 'C:\\Users\\k\\proj');
101
+ }));
102
+
87
103
  test('PreCompact: 手動・無効・対象外のproject・scriptから起動した会話・subagent では圧縮を止めない', () => withDir(dir => {
88
104
  for (const [overrides, reason] of [
89
105
  [{ payload: { trigger: 'manual' } }, 'manual_compact'],
@@ -272,7 +288,7 @@ test('worker: 止めた時点の依頼とモデルを transcript から写し、
272
288
  const run = async (sessionId, transcriptPath, toolUseId) => {
273
289
  const db = makeBatonDb();
274
290
  requestClaudeAutoHandoff({ payload: { session_id: sessionId, trigger: 'auto', cwd: '/work/app', transcript_path: transcriptPath },
275
- env: {}, config: ENABLED, openDb: () => db, dir });
291
+ env: {}, config: ENABLED, openDb: () => db, dir, platform: 'linux' });
276
292
  await stopClaudeTurnForHandoff({ payload: { session_id: sessionId, cwd: '/work/app', transcript_path: transcriptPath,
277
293
  tool_use_id: toolUseId }, dir, launchWorker: async () => {} });
278
294
  const calls = [];
@@ -381,7 +397,7 @@ test('worker: 止めたターンを DB へ取り込み、ここまでにした
381
397
  ]));
382
398
  const db = makeMemoryDb();
383
399
  requestClaudeAutoHandoff({ payload: { session_id: SESSION, trigger: 'auto', cwd: '/work/app', transcript_path: transcript },
384
- env: {}, config: ENABLED, openDb: () => db, dir });
400
+ env: {}, config: ENABLED, openDb: () => db, dir, platform: 'linux' });
385
401
  // hook は並んで走る。記録に残る止めた道具は、応答の中の2つ目のこともある
386
402
  await stopClaudeTurnForHandoff({ payload: { session_id: SESSION, cwd: '/work/app', transcript_path: transcript, tool_use_id: 't5' },
387
403
  dir, launchWorker: async () => {} });
@@ -428,7 +444,7 @@ test('worker: 止めたターンを取り込めなくても後継を立て、取
428
444
  writeFileSync(transcript, jsonl([user('依頼', '2026-10-04T03:00:00Z'), assistant('途中です。', '2026-10-04T03:00:02Z'), toolUse('toolu_1', 'Read')]));
429
445
  const db = makeBatonDb(); // bodies も sessions も無い
430
446
  requestClaudeAutoHandoff({ payload: { session_id: SESSION, trigger: 'auto', cwd: '/work/app', transcript_path: transcript },
431
- env: {}, config: ENABLED, openDb: () => db, dir });
447
+ env: {}, config: ENABLED, openDb: () => db, dir, platform: 'linux' });
432
448
  await stopClaudeTurnForHandoff({ payload: { session_id: SESSION, cwd: '/work/app', transcript_path: transcript, tool_use_id: 'toolu_1' },
433
449
  dir, launchWorker: async () => {} });
434
450
  const record = await runClaudeAutoHandoffWorker(SESSION, { dir, env: {}, pollMs: 10, targetTimeoutMs: 300, transcriptTimeoutMs: 300,