throughline 0.3.24 → 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 +383 -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 +33 -10
  111. package/src/vscode-task.test.mjs +19 -9
@@ -0,0 +1,242 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { mkdtempSync, rmSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const REPO_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
10
+
11
+ function makeTempHome() {
12
+ return mkdtempSync(join(tmpdir(), 'tl-codex-resume-home-'));
13
+ }
14
+
15
+ function makeTempProject() {
16
+ return mkdtempSync(join(tmpdir(), 'tl-codex-resume-project-'));
17
+ }
18
+
19
+ async function seedDb(home, project) {
20
+ const originalHome = process.env.HOME;
21
+ const originalUserProfile = process.env.USERPROFILE;
22
+ process.env.HOME = home;
23
+ process.env.USERPROFILE = home;
24
+ try {
25
+ const mod = await import(`./db.mjs?codexResume=${Date.now()}-${Math.random()}`);
26
+ const db = mod.getDb();
27
+ db.prepare(
28
+ `INSERT INTO sessions (session_id, project_path, status, created_at, updated_at)
29
+ VALUES ('codex:thread-resume', ?, 'active', 1, 2)`,
30
+ ).run(project);
31
+ db.prepare(
32
+ `INSERT INTO skeletons
33
+ (session_id, origin_session_id, turn_number, role, summary, created_at)
34
+ VALUES ('codex:thread-resume', 'codex:thread-resume', 1, 'assistant',
35
+ 'older codex summary', 1000)`,
36
+ ).run();
37
+ db.prepare(
38
+ `INSERT INTO bodies
39
+ (session_id, origin_session_id, turn_number, role, text, token_count, created_at)
40
+ VALUES ('codex:thread-resume', 'codex:thread-resume', 2, 'assistant',
41
+ 'latest codex body', 3, 2000)`,
42
+ ).run();
43
+ db.prepare(
44
+ `INSERT INTO details
45
+ (session_id, origin_session_id, turn_number, tool_name, input_text, output_text,
46
+ token_count, created_at, kind, source_id)
47
+ VALUES ('codex:thread-resume', 'codex:thread-resume', 2, 'exec_command',
48
+ '{"cmd":"pwd"}', NULL, 3, 2000, 'tool_input', 'codex-tool-1')`,
49
+ ).run();
50
+ db.close();
51
+ } finally {
52
+ if (originalHome === undefined) delete process.env.HOME;
53
+ else process.env.HOME = originalHome;
54
+ if (originalUserProfile === undefined) delete process.env.USERPROFILE;
55
+ else process.env.USERPROFILE = originalUserProfile;
56
+ }
57
+ }
58
+
59
+ function runResume(home, project, args = [], input = undefined) {
60
+ return spawnSync(
61
+ process.execPath,
62
+ [join(REPO_ROOT, 'bin/throughline.mjs'), 'codex-resume', ...args],
63
+ {
64
+ cwd: project,
65
+ env: {
66
+ ...process.env,
67
+ HOME: home,
68
+ USERPROFILE: home,
69
+ },
70
+ encoding: 'utf8',
71
+ input,
72
+ },
73
+ );
74
+ }
75
+
76
+ test('codex-resume prints active-work context text for explicit Codex session', async () => {
77
+ const home = makeTempHome();
78
+ const project = makeTempProject();
79
+ try {
80
+ await seedDb(home, project);
81
+ const result = runResume(home, project, ['--session', 'codex:thread-resume']);
82
+
83
+ assert.equal(result.status, 0, result.stderr);
84
+ assert.match(result.stdout, /Throughline: Active Work Context/);
85
+ assert.match(result.stdout, /Source agent: codex/);
86
+ assert.match(result.stdout, /older codex summary/);
87
+ assert.match(result.stdout, /latest codex body/);
88
+ assert.match(result.stdout, /throughline detail \d\d:\d\d:\d\d/);
89
+ } finally {
90
+ rmSync(project, { recursive: true, force: true });
91
+ rmSync(home, { recursive: true, force: true });
92
+ }
93
+ });
94
+
95
+ test('codex-resume can print a Codex developer message item JSON', async () => {
96
+ const home = makeTempHome();
97
+ const project = makeTempProject();
98
+ try {
99
+ await seedDb(home, project);
100
+ const result = runResume(home, project, [
101
+ '--session',
102
+ 'codex:thread-resume',
103
+ '--format',
104
+ 'item-json',
105
+ ]);
106
+
107
+ assert.equal(result.status, 0, result.stderr);
108
+ const item = JSON.parse(result.stdout);
109
+ assert.equal(item.type, 'message');
110
+ assert.equal(item.role, 'developer');
111
+ assert.equal(item.content[0].type, 'input_text');
112
+ assert.match(item.content[0].text, /current-task context for continuation/);
113
+ assert.match(item.content[0].text, /latest codex body/);
114
+ } finally {
115
+ rmSync(project, { recursive: true, force: true });
116
+ rmSync(home, { recursive: true, force: true });
117
+ }
118
+ });
119
+
120
+ test('codex-resume can print a fresh-thread handoff prompt', async () => {
121
+ const home = makeTempHome();
122
+ const project = makeTempProject();
123
+ try {
124
+ await seedDb(home, project);
125
+ const result = runResume(home, project, [
126
+ '--session',
127
+ 'codex:thread-resume',
128
+ '--format',
129
+ 'handoff',
130
+ '--max-detail-refs',
131
+ '0',
132
+ '--max-recent-bodies',
133
+ '1',
134
+ '--max-body-chars',
135
+ '6',
136
+ ]);
137
+
138
+ assert.equal(result.status, 0, result.stderr);
139
+ assert.match(result.stdout, /Throughline: New Codex Thread Handoff/);
140
+ assert.match(result.stdout, /fresh Codex thread without mutating the risky current thread/);
141
+ assert.match(result.stdout, /older codex summary/);
142
+ assert.match(result.stdout, /latest/);
143
+ assert.match(result.stdout, /\[entry truncated to 6 chars\]/);
144
+ assert.doesNotMatch(result.stdout, /codex body/);
145
+ assert.match(result.stdout, /1 detail commands available; omitted from this fresh-thread handoff/);
146
+ assert.doesNotMatch(result.stdout, /^\{/);
147
+ } finally {
148
+ rmSync(project, { recursive: true, force: true });
149
+ rmSync(home, { recursive: true, force: true });
150
+ }
151
+ });
152
+
153
+ test('codex-resume accepts Codex-primary in-flight memo on stdin', async () => {
154
+ const home = makeTempHome();
155
+ const project = makeTempProject();
156
+ try {
157
+ await seedDb(home, project);
158
+ const result = runResume(
159
+ home,
160
+ project,
161
+ ['--session', 'codex:thread-resume', '--memo-stdin'],
162
+ 'Next: continue Codex memo surface',
163
+ );
164
+
165
+ assert.equal(result.status, 0, result.stderr);
166
+ assert.match(result.stdout, /### In-flight Memo/);
167
+ assert.match(result.stdout, /Next: continue Codex memo surface/);
168
+ assert.match(result.stdout, /latest codex body/);
169
+ } finally {
170
+ rmSync(project, { recursive: true, force: true });
171
+ rmSync(home, { recursive: true, force: true });
172
+ }
173
+ });
174
+
175
+ test('codex-resume rejects invalid handoff detail reference limit', async () => {
176
+ const home = makeTempHome();
177
+ const project = makeTempProject();
178
+ try {
179
+ await seedDb(home, project);
180
+ const result = runResume(home, project, [
181
+ '--session',
182
+ 'codex:thread-resume',
183
+ '--format',
184
+ 'handoff',
185
+ '--max-detail-refs',
186
+ '-1',
187
+ ]);
188
+
189
+ assert.equal(result.status, 1);
190
+ assert.match(result.stderr, /--max-detail-refs must be a non-negative integer/);
191
+ } finally {
192
+ rmSync(project, { recursive: true, force: true });
193
+ rmSync(home, { recursive: true, force: true });
194
+ }
195
+ });
196
+
197
+ test('codex-resume rejects invalid handoff body limits', async () => {
198
+ const home = makeTempHome();
199
+ const project = makeTempProject();
200
+ try {
201
+ await seedDb(home, project);
202
+ const recentBodies = runResume(home, project, [
203
+ '--session',
204
+ 'codex:thread-resume',
205
+ '--format',
206
+ 'handoff',
207
+ '--max-recent-bodies',
208
+ '-1',
209
+ ]);
210
+ assert.equal(recentBodies.status, 1);
211
+ assert.match(recentBodies.stderr, /--max-recent-bodies must be a non-negative integer/);
212
+
213
+ const bodyChars = runResume(home, project, [
214
+ '--session',
215
+ 'codex:thread-resume',
216
+ '--format',
217
+ 'handoff',
218
+ '--max-body-chars',
219
+ '-1',
220
+ ]);
221
+ assert.equal(bodyChars.status, 1);
222
+ assert.match(bodyChars.stderr, /--max-body-chars must be a non-negative integer/);
223
+ } finally {
224
+ rmSync(project, { recursive: true, force: true });
225
+ rmSync(home, { recursive: true, force: true });
226
+ }
227
+ });
228
+
229
+ test('codex-resume uses latest Codex session for cwd when --session is omitted', async () => {
230
+ const home = makeTempHome();
231
+ const project = makeTempProject();
232
+ try {
233
+ await seedDb(home, project);
234
+ const result = runResume(home, project);
235
+
236
+ assert.equal(result.status, 0, result.stderr);
237
+ assert.match(result.stdout, /Throughline session: codex:thread-resume/);
238
+ } finally {
239
+ rmSync(project, { recursive: true, force: true });
240
+ rmSync(home, { recursive: true, force: true });
241
+ }
242
+ });