throughline 0.3.24 → 0.4.0

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 (116) hide show
  1. package/.claude/commands/tl.md +6 -21
  2. package/.codex-sidecar.yml +62 -0
  3. package/CHANGELOG.md +632 -0
  4. package/README.ja.md +71 -46
  5. package/README.md +420 -76
  6. package/bin/throughline.mjs +169 -7
  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 +159 -0
  10. package/docs/L1_L2_L3_REDESIGN.md +415 -0
  11. package/docs/PUBLIC_RELEASE_PLAN.md +185 -0
  12. package/docs/THROUGHLINE_CLEAR_AUTO_HANDOFF_PLAN.md +286 -0
  13. package/docs/THROUGHLINE_CODEX_DUAL_SUPPORT.md +249 -0
  14. package/docs/THROUGHLINE_CODEX_FIRST_ROADMAP.md +555 -0
  15. package/docs/THROUGHLINE_CODEX_MONITOR_IMPLEMENTATION_PLAN.md +220 -0
  16. package/docs/THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md +528 -0
  17. package/docs/THROUGHLINE_CODEX_TRIM_ROLLBACK_FIX_PLAN.md +672 -0
  18. package/docs/archive/CONCEPT.md +476 -0
  19. package/docs/archive/EXPERIMENT.md +371 -0
  20. package/docs/archive/README.md +22 -0
  21. package/docs/archive/SESSION_LINKING_DESIGN.md +231 -0
  22. package/docs/archive/THROUGHLINE_NEXT_STEPS.md +134 -0
  23. package/docs/throughline-codex-trim-rollback-incident-report.md +306 -0
  24. package/docs/throughline-handoff-context.example.json +57 -0
  25. package/docs/throughline-rollback-context-trim-insight.md +455 -0
  26. package/package.json +6 -2
  27. package/src/baton.mjs +17 -45
  28. package/src/baton.test.mjs +4 -41
  29. package/src/cli/codex-capture.mjs +95 -0
  30. package/src/cli/codex-handoff-model-smoke.mjs +292 -0
  31. package/src/cli/codex-handoff-model-smoke.test.mjs +262 -0
  32. package/src/cli/codex-handoff-smoke.mjs +163 -0
  33. package/src/cli/codex-handoff-smoke.test.mjs +149 -0
  34. package/src/cli/codex-handoff-start.mjs +291 -0
  35. package/src/cli/codex-handoff-start.test.mjs +194 -0
  36. package/src/cli/codex-hook.mjs +276 -0
  37. package/src/cli/codex-hook.test.mjs +293 -0
  38. package/src/cli/codex-host-primitive-audit.mjs +110 -0
  39. package/src/cli/codex-host-primitive-audit.test.mjs +75 -0
  40. package/src/cli/codex-restore-smoke.mjs +357 -0
  41. package/src/cli/codex-restore-source-audit.mjs +304 -0
  42. package/src/cli/codex-resume.mjs +138 -0
  43. package/src/cli/codex-rollback-model-visible-smoke.mjs +373 -0
  44. package/src/cli/codex-rollback-model-visible-smoke.test.mjs +255 -0
  45. package/src/cli/codex-sidecar-diagnostics.mjs +48 -0
  46. package/src/cli/codex-sidecar-dry-run.mjs +85 -0
  47. package/src/cli/codex-summarize.mjs +224 -0
  48. package/src/cli/codex-threads.mjs +89 -0
  49. package/src/cli/codex-visibility-smoke.mjs +196 -0
  50. package/src/cli/codex-vscode-restore-smoke.mjs +226 -0
  51. package/src/cli/codex-vscode-rollback-smoke.mjs +114 -0
  52. package/src/cli/doctor.mjs +503 -1
  53. package/src/cli/doctor.test.mjs +542 -3
  54. package/src/cli/handoff-preview.mjs +78 -0
  55. package/src/cli/help.test.mjs +64 -0
  56. package/src/cli/install.mjs +226 -3
  57. package/src/cli/install.test.mjs +205 -4
  58. package/src/cli/trim.mjs +564 -0
  59. package/src/codex-app-server.mjs +1816 -0
  60. package/src/codex-app-server.test.mjs +512 -0
  61. package/src/codex-auto-refresh.mjs +194 -0
  62. package/src/codex-auto-refresh.test.mjs +182 -0
  63. package/src/codex-capture.mjs +235 -0
  64. package/src/codex-capture.test.mjs +393 -0
  65. package/src/codex-handoff-model-smoke.mjs +114 -0
  66. package/src/codex-handoff-model-smoke.test.mjs +89 -0
  67. package/src/codex-handoff-smoke.mjs +124 -0
  68. package/src/codex-handoff-smoke.test.mjs +103 -0
  69. package/src/codex-handoff.mjs +331 -0
  70. package/src/codex-handoff.test.mjs +220 -0
  71. package/src/codex-host-primitive-audit.mjs +374 -0
  72. package/src/codex-host-primitive-audit.test.mjs +208 -0
  73. package/src/codex-restore-smoke.test.mjs +639 -0
  74. package/src/codex-restore-source-audit.mjs +1348 -0
  75. package/src/codex-restore-source-audit.test.mjs +623 -0
  76. package/src/codex-resume.test.mjs +242 -0
  77. package/src/codex-rollout-memory.mjs +711 -0
  78. package/src/codex-rollout-memory.test.mjs +610 -0
  79. package/src/codex-sidecar-cli.test.mjs +75 -0
  80. package/src/codex-sidecar.mjs +246 -0
  81. package/src/codex-sidecar.test.mjs +172 -0
  82. package/src/codex-summarize.test.mjs +143 -0
  83. package/src/codex-thread-identity.mjs +23 -0
  84. package/src/codex-thread-index.mjs +173 -0
  85. package/src/codex-thread-index.test.mjs +164 -0
  86. package/src/codex-usage.mjs +110 -0
  87. package/src/codex-usage.test.mjs +140 -0
  88. package/src/codex-visibility-smoke.test.mjs +222 -0
  89. package/src/codex-vscode-restore-smoke.mjs +206 -0
  90. package/src/codex-vscode-restore-smoke.test.mjs +325 -0
  91. package/src/codex-vscode-rollback-smoke.mjs +90 -0
  92. package/src/codex-vscode-rollback-smoke.test.mjs +290 -0
  93. package/src/db-schema.test.mjs +96 -0
  94. package/src/db.mjs +14 -1
  95. package/src/haiku-summarizer.mjs +267 -26
  96. package/src/haiku-summarizer.test.mjs +282 -0
  97. package/src/handoff-preview.test.mjs +108 -0
  98. package/src/handoff-record.mjs +294 -0
  99. package/src/handoff-record.test.mjs +226 -0
  100. package/src/hook-entrypoints.test.mjs +286 -0
  101. package/src/package-files.test.mjs +19 -0
  102. package/src/prompt-submit.mjs +9 -6
  103. package/src/resume-context.mjs +58 -171
  104. package/src/resume-context.test.mjs +177 -0
  105. package/src/session-start.mjs +85 -26
  106. package/src/state-file.mjs +50 -6
  107. package/src/state-file.test.mjs +50 -0
  108. package/src/token-monitor.mjs +14 -10
  109. package/src/token-monitor.test.mjs +27 -0
  110. package/src/trim-cli.test.mjs +1584 -0
  111. package/src/trim-model.mjs +584 -0
  112. package/src/trim-model.test.mjs +568 -0
  113. package/src/turn-processor.mjs +17 -10
  114. package/src/vscode-task.mjs +33 -10
  115. package/src/vscode-task.test.mjs +19 -9
  116. package/src/cli/save-inflight.mjs +0 -81
@@ -0,0 +1,208 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ import {
8
+ classifyCodexHostPrimitiveSchema,
9
+ extractJsonRpcMethods,
10
+ runCodexHostPrimitiveAudit,
11
+ } from './codex-host-primitive-audit.mjs';
12
+
13
+ function makeSchemaDir({
14
+ methods,
15
+ resumeHistoryDescription = '[UNSTABLE] FOR CODEX CLOUD - DO NOT USE.',
16
+ resumeRootDescription =
17
+ 'There are three ways to resume a thread. The precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.',
18
+ }) {
19
+ const dir = mkdtempSync(join(tmpdir(), 'tl-codex-host-primitive-test-'));
20
+ mkdirSync(join(dir, 'v2'), { recursive: true });
21
+ writeFileSync(
22
+ join(dir, 'ClientRequest.json'),
23
+ JSON.stringify({
24
+ oneOf: methods.map((method) => ({
25
+ type: 'object',
26
+ properties: {
27
+ method: {
28
+ const: method,
29
+ },
30
+ },
31
+ })),
32
+ }),
33
+ );
34
+ writeFileSync(
35
+ join(dir, 'v2', 'ThreadResumeParams.json'),
36
+ JSON.stringify({
37
+ description: resumeRootDescription,
38
+ properties: {
39
+ history: {
40
+ description: resumeHistoryDescription,
41
+ },
42
+ },
43
+ }),
44
+ );
45
+ return dir;
46
+ }
47
+
48
+ test('extractJsonRpcMethods collects method const and enum values', () => {
49
+ const methods = extractJsonRpcMethods({
50
+ oneOf: [
51
+ { properties: { method: { const: 'thread/rollback' } } },
52
+ { properties: { method: { enum: ['turn/start', 'plain'] } } },
53
+ ],
54
+ });
55
+
56
+ assert.deepEqual([...methods].sort(), ['thread/rollback', 'turn/start']);
57
+ });
58
+
59
+ test('classifyCodexHostPrimitiveSchema blocks when only rollback/inject/new-thread primitives exist', () => {
60
+ const dir = makeSchemaDir({
61
+ methods: [
62
+ 'thread/start',
63
+ 'thread/resume',
64
+ 'thread/fork',
65
+ 'thread/archive',
66
+ 'thread/compact/start',
67
+ 'thread/rollback',
68
+ 'thread/inject_items',
69
+ 'thread/turns/list',
70
+ ],
71
+ });
72
+ try {
73
+ const result = classifyCodexHostPrimitiveSchema({ schemaDir: dir });
74
+
75
+ assert.equal(result.status, 'host-primitive-audit-blocked');
76
+ assert.equal(result.reason, 'no_current_thread_restore_non_resurrection_primitive');
77
+ assert.equal(result.restartSafePrimitive, false);
78
+ assert.equal(result.facts.threadRollback, true);
79
+ assert.equal(result.facts.threadInjectItems, true);
80
+ assert.equal(result.facts.threadResumeHistory.supportedForThroughline, false);
81
+ assert.equal(result.facts.threadResumeHistory.reason, 'thread_resume_history_is_marked_do_not_use');
82
+ assert.equal(result.facts.hasCurrentThreadRemediationPrimitive, false);
83
+ assert.equal(result.facts.hasCurrentThreadNonResurrectionPrimitive, false);
84
+ assert.equal(result.facts.threadTurnsList, true);
85
+ assert.equal(
86
+ result.repairContract.status,
87
+ 'blocked-missing-current-thread-non-resurrection-guarantee',
88
+ );
89
+ assert.equal(
90
+ result.repairContract.criteria.find((entry) => entry.id === 'same_current_thread_repair_primitive')
91
+ ?.status,
92
+ 'missing',
93
+ );
94
+ assert.equal(
95
+ result.repairContract.criteria.find((entry) => entry.id === 'post_repair_host_read_verification')
96
+ ?.status,
97
+ 'present',
98
+ );
99
+ assert.equal(
100
+ result.decisions.find((entry) => entry.id === 'non_resurrection_guarantee_absent')?.status,
101
+ 'blocking',
102
+ );
103
+ } finally {
104
+ rmSync(dir, { recursive: true, force: true });
105
+ }
106
+ });
107
+
108
+ test('classifyCodexHostPrimitiveSchema surfaces a future current-thread rewrite candidate', () => {
109
+ const dir = makeSchemaDir({
110
+ methods: ['thread/rollback', 'thread/inject_items', 'thread/read', 'thread/history/replace'],
111
+ });
112
+ try {
113
+ const result = classifyCodexHostPrimitiveSchema({ schemaDir: dir });
114
+
115
+ assert.equal(result.status, 'host-primitive-audit-needs-live-validation');
116
+ assert.deepEqual(result.facts.inPlaceHistoryRewriteMethods, ['thread/history/replace']);
117
+ assert.equal(result.repairContract.status, 'candidate-requires-live-validation');
118
+ assert.deepEqual(result.repairContract.currentThreadRepairCandidates, ['thread/history/replace']);
119
+ assert.deepEqual(result.repairContract.restoreSourceDeletionCandidates, ['thread/history/replace']);
120
+ assert.deepEqual(result.repairContract.restoreSourceIsolationCandidates, []);
121
+ assert.equal(
122
+ result.repairContract.criteria.find((entry) => entry.id === 'same_current_thread_repair_primitive')
123
+ ?.status,
124
+ 'candidate',
125
+ );
126
+ assert.equal(
127
+ result.repairContract.criteria.find((entry) => entry.id === 'restart_reconnect_non_resurrection_verification')
128
+ ?.status,
129
+ 'requires-live-smoke',
130
+ );
131
+ assert.equal(result.recommendation.status, 'diagnostic-only');
132
+ } finally {
133
+ rmSync(dir, { recursive: true, force: true });
134
+ }
135
+ });
136
+
137
+ test('classifyCodexHostPrimitiveSchema accepts a future restore projection candidate', () => {
138
+ const dir = makeSchemaDir({
139
+ methods: [
140
+ 'thread/rollback',
141
+ 'thread/inject_items',
142
+ 'thread/read',
143
+ 'thread/restore/filter',
144
+ ],
145
+ });
146
+ try {
147
+ const result = classifyCodexHostPrimitiveSchema({ schemaDir: dir });
148
+
149
+ assert.equal(result.status, 'host-primitive-audit-needs-live-validation');
150
+ assert.deepEqual(result.facts.inPlaceRestoreIsolationMethods, ['thread/restore/filter']);
151
+ assert.equal(result.repairContract.status, 'candidate-requires-live-validation');
152
+ assert.deepEqual(result.repairContract.currentThreadRepairCandidates, ['thread/restore/filter']);
153
+ assert.deepEqual(result.repairContract.restoreSourceDeletionCandidates, []);
154
+ assert.deepEqual(result.repairContract.restoreSourceIsolationCandidates, ['thread/restore/filter']);
155
+ assert.equal(
156
+ result.repairContract.criteria.find(
157
+ (entry) => entry.id === 'restore_source_non_resurrection_guarantee',
158
+ )?.status,
159
+ 'candidate',
160
+ );
161
+ } finally {
162
+ rmSync(dir, { recursive: true, force: true });
163
+ }
164
+ });
165
+
166
+ test('classifyCodexHostPrimitiveSchema keeps resume history out of the repair contract', () => {
167
+ const dir = makeSchemaDir({
168
+ methods: ['thread/resume', 'thread/inject_items', 'thread/read'],
169
+ resumeHistoryDescription: 'test-only history candidate',
170
+ resumeRootDescription: 'test-only thread resume params',
171
+ });
172
+ try {
173
+ const result = classifyCodexHostPrimitiveSchema({ schemaDir: dir });
174
+
175
+ assert.equal(result.status, 'host-primitive-audit-needs-live-validation');
176
+ assert.equal(result.facts.threadResumeHistory.supportedForThroughline, true);
177
+ assert.deepEqual(result.repairContract.resumeHistoryCandidates, ['thread/resume(history)']);
178
+ assert.equal(
179
+ result.repairContract.status,
180
+ 'blocked-missing-current-thread-non-resurrection-guarantee',
181
+ );
182
+ assert.deepEqual(result.repairContract.currentThreadRepairCandidates, []);
183
+ assert.equal(
184
+ result.repairContract.criteria.find((entry) => entry.id === 'same_current_thread_repair_primitive')
185
+ ?.status,
186
+ 'missing',
187
+ );
188
+ } finally {
189
+ rmSync(dir, { recursive: true, force: true });
190
+ }
191
+ });
192
+
193
+ test('runCodexHostPrimitiveAudit can read a provided schema directory without spawning codex', () => {
194
+ const dir = makeSchemaDir({
195
+ methods: ['thread/rollback', 'thread/inject_items'],
196
+ });
197
+ try {
198
+ const result = runCodexHostPrimitiveAudit({
199
+ command: '/missing/codex',
200
+ schemaDir: dir,
201
+ });
202
+
203
+ assert.equal(result.status, 'host-primitive-audit-blocked');
204
+ assert.equal(result.schemaSource, 'provided-schema-dir');
205
+ } finally {
206
+ rmSync(dir, { recursive: true, force: true });
207
+ }
208
+ });