gsd-pi 2.19.0 → 2.20.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 (249) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/onboarding.d.ts +3 -1
  4. package/dist/onboarding.js +77 -3
  5. package/dist/remote-questions-config.d.ts +1 -1
  6. package/dist/resources/extensions/google-search/index.ts +164 -47
  7. package/dist/resources/extensions/gsd/auto-prompts.ts +103 -24
  8. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  9. package/dist/resources/extensions/gsd/auto.ts +424 -30
  10. package/dist/resources/extensions/gsd/commands.ts +518 -36
  11. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  12. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  13. package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  14. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  15. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  16. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  17. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  18. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  19. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  20. package/dist/resources/extensions/gsd/export.ts +81 -2
  21. package/dist/resources/extensions/gsd/files.ts +39 -9
  22. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  23. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  24. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  25. package/dist/resources/extensions/gsd/history.ts +0 -1
  26. package/dist/resources/extensions/gsd/index.ts +277 -1
  27. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  28. package/dist/resources/extensions/gsd/metrics.ts +39 -3
  29. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  30. package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  31. package/dist/resources/extensions/gsd/preferences.ts +125 -150
  32. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  33. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  34. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  35. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  36. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  37. package/dist/resources/extensions/gsd/quick.ts +156 -0
  38. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  39. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  40. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  41. package/dist/resources/extensions/gsd/state.ts +30 -0
  42. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  43. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  44. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  45. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  46. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  47. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  48. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  49. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  50. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  51. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  52. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  53. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  54. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  55. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  56. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  57. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  58. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  59. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  60. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  61. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  62. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  63. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  64. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  65. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  66. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  67. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  68. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  69. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  70. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  71. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  72. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  73. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  74. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  75. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  76. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  77. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  78. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  79. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  80. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  81. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  82. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  83. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  84. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  85. package/dist/resources/extensions/gsd/types.ts +29 -0
  86. package/dist/resources/extensions/gsd/undo.ts +0 -1
  87. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  88. package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
  89. package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  90. package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
  91. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  92. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  93. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  94. package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  95. package/dist/resources/extensions/remote-questions/format.ts +154 -8
  96. package/dist/resources/extensions/remote-questions/manager.ts +9 -7
  97. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  98. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  99. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  100. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  101. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  102. package/dist/resources/extensions/voice/index.ts +4 -3
  103. package/package.json +1 -1
  104. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  106. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  109. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  111. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  113. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  117. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  119. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  120. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  122. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  124. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  126. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  128. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  130. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  133. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  136. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  138. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  141. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  142. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  143. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  144. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  145. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  146. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  147. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  148. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  149. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  150. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  151. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  152. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  153. package/src/resources/extensions/google-search/index.ts +164 -47
  154. package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
  155. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  156. package/src/resources/extensions/gsd/auto.ts +424 -30
  157. package/src/resources/extensions/gsd/commands.ts +518 -36
  158. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  159. package/src/resources/extensions/gsd/context-store.ts +195 -0
  160. package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  161. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  162. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  163. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  164. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  165. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  166. package/src/resources/extensions/gsd/doctor.ts +283 -2
  167. package/src/resources/extensions/gsd/export.ts +81 -2
  168. package/src/resources/extensions/gsd/files.ts +39 -9
  169. package/src/resources/extensions/gsd/git-service.ts +6 -0
  170. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  171. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  172. package/src/resources/extensions/gsd/history.ts +0 -1
  173. package/src/resources/extensions/gsd/index.ts +277 -1
  174. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  175. package/src/resources/extensions/gsd/metrics.ts +39 -3
  176. package/src/resources/extensions/gsd/notifications.ts +0 -1
  177. package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  178. package/src/resources/extensions/gsd/preferences.ts +125 -150
  179. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  180. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  181. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  182. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  183. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  184. package/src/resources/extensions/gsd/quick.ts +156 -0
  185. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  186. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  187. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  188. package/src/resources/extensions/gsd/state.ts +30 -0
  189. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  190. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  191. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  192. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  193. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  194. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  195. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  196. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  197. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  198. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  199. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  200. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  201. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  202. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  203. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  204. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  205. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  206. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  207. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  208. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  209. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  210. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  211. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  212. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  213. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  214. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  215. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  216. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  217. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  218. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  219. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  220. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  221. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  222. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  223. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  224. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  225. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  226. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  227. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  228. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  229. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  230. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  231. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  232. package/src/resources/extensions/gsd/types.ts +29 -0
  233. package/src/resources/extensions/gsd/undo.ts +0 -1
  234. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  235. package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
  236. package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  237. package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
  238. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  239. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  240. package/src/resources/extensions/remote-questions/config.ts +4 -2
  241. package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  242. package/src/resources/extensions/remote-questions/format.ts +154 -8
  243. package/src/resources/extensions/remote-questions/manager.ts +9 -7
  244. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  245. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  246. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  247. package/src/resources/extensions/remote-questions/types.ts +2 -1
  248. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  249. package/src/resources/extensions/voice/index.ts +4 -3
@@ -0,0 +1,442 @@
1
+ import { createTestContext } from './test-helpers.ts';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import * as os from 'node:os';
5
+ import {
6
+ openDatabase,
7
+ closeDatabase,
8
+ isDbAvailable,
9
+ insertDecision,
10
+ insertRequirement,
11
+ insertArtifact,
12
+ getDecisionById,
13
+ getRequirementById,
14
+ _getAdapter,
15
+ copyWorktreeDb,
16
+ reconcileWorktreeDb,
17
+ } from '../gsd-db.ts';
18
+
19
+ const { assertEq, assertTrue, report } = createTestContext();
20
+
21
+ // ═══════════════════════════════════════════════════════════════════════════
22
+ // Helpers
23
+ // ═══════════════════════════════════════════════════════════════════════════
24
+
25
+ function tempDir(): string {
26
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-wt-test-'));
27
+ }
28
+
29
+ function cleanup(...dirs: string[]): void {
30
+ closeDatabase();
31
+ for (const dir of dirs) {
32
+ try {
33
+ fs.rmSync(dir, { recursive: true, force: true });
34
+ } catch {
35
+ // best effort
36
+ }
37
+ }
38
+ }
39
+
40
+ function seedMainDb(dbPath: string): void {
41
+ openDatabase(dbPath);
42
+ insertDecision({
43
+ id: 'D001',
44
+ when_context: '2025-01-01',
45
+ scope: 'M001/S01',
46
+ decision: 'Use SQLite',
47
+ choice: 'node:sqlite',
48
+ rationale: 'Built-in',
49
+ revisable: 'yes',
50
+ superseded_by: null,
51
+ });
52
+ insertRequirement({
53
+ id: 'R001',
54
+ class: 'functional',
55
+ status: 'active',
56
+ description: 'Must store decisions',
57
+ why: 'Core feature',
58
+ source: 'design',
59
+ primary_owner: 'S01',
60
+ supporting_slices: '',
61
+ validation: 'test',
62
+ notes: '',
63
+ full_content: 'Full requirement text',
64
+ superseded_by: null,
65
+ });
66
+ insertArtifact({
67
+ path: 'docs/arch.md',
68
+ artifact_type: 'plan',
69
+ milestone_id: 'M001',
70
+ slice_id: null,
71
+ task_id: null,
72
+ full_content: 'Architecture document',
73
+ });
74
+ }
75
+
76
+ // ═══════════════════════════════════════════════════════════════════════════
77
+ // copyWorktreeDb tests
78
+ // ═══════════════════════════════════════════════════════════════════════════
79
+
80
+ console.log('\n=== worktree-db: copyWorktreeDb ===');
81
+
82
+ // Test: copies DB file and data is queryable
83
+ {
84
+ const srcDir = tempDir();
85
+ const destDir = tempDir();
86
+ const srcDb = path.join(srcDir, 'gsd.db');
87
+ const destDb = path.join(destDir, 'nested', 'gsd.db');
88
+
89
+ seedMainDb(srcDb);
90
+ closeDatabase();
91
+
92
+ const result = copyWorktreeDb(srcDb, destDb);
93
+ assertTrue(result === true, 'copyWorktreeDb returns true on success');
94
+ assertTrue(fs.existsSync(destDb), 'dest DB file exists after copy');
95
+
96
+ // Open the copy and verify data is queryable
97
+ openDatabase(destDb);
98
+ const d = getDecisionById('D001');
99
+ assertTrue(d !== null, 'decision queryable in copied DB');
100
+ assertEq(d?.choice, 'node:sqlite', 'decision data preserved in copy');
101
+
102
+ const r = getRequirementById('R001');
103
+ assertTrue(r !== null, 'requirement queryable in copied DB');
104
+ assertEq(r?.description, 'Must store decisions', 'requirement data preserved in copy');
105
+
106
+ cleanup(srcDir, destDir);
107
+ }
108
+
109
+ // Test: skips -wal and -shm files
110
+ {
111
+ const srcDir = tempDir();
112
+ const destDir = tempDir();
113
+ const srcDb = path.join(srcDir, 'gsd.db');
114
+ const destDb = path.join(destDir, 'gsd.db');
115
+
116
+ seedMainDb(srcDb);
117
+ closeDatabase();
118
+
119
+ // Create fake WAL/SHM files
120
+ fs.writeFileSync(srcDb + '-wal', 'fake wal data');
121
+ fs.writeFileSync(srcDb + '-shm', 'fake shm data');
122
+
123
+ copyWorktreeDb(srcDb, destDb);
124
+
125
+ assertTrue(fs.existsSync(destDb), 'DB file copied');
126
+ assertTrue(!fs.existsSync(destDb + '-wal'), 'WAL file NOT copied');
127
+ assertTrue(!fs.existsSync(destDb + '-shm'), 'SHM file NOT copied');
128
+
129
+ cleanup(srcDir, destDir);
130
+ }
131
+
132
+ // Test: returns false when source doesn't exist (no throw)
133
+ {
134
+ const destDir = tempDir();
135
+ const result = copyWorktreeDb('/nonexistent/path/gsd.db', path.join(destDir, 'gsd.db'));
136
+ assertEq(result, false, 'returns false for missing source');
137
+ cleanup(destDir);
138
+ }
139
+
140
+ // Test: creates dest directory if needed
141
+ {
142
+ const srcDir = tempDir();
143
+ const destDir = tempDir();
144
+ const srcDb = path.join(srcDir, 'gsd.db');
145
+ const deepDest = path.join(destDir, 'a', 'b', 'c', 'gsd.db');
146
+
147
+ seedMainDb(srcDb);
148
+ closeDatabase();
149
+
150
+ const result = copyWorktreeDb(srcDb, deepDest);
151
+ assertTrue(result === true, 'copyWorktreeDb succeeds with nested dest');
152
+ assertTrue(fs.existsSync(deepDest), 'DB file created at deeply nested path');
153
+
154
+ cleanup(srcDir, destDir);
155
+ }
156
+
157
+ // ═══════════════════════════════════════════════════════════════════════════
158
+ // reconcileWorktreeDb tests
159
+ // ═══════════════════════════════════════════════════════════════════════════
160
+
161
+ console.log('\n=== worktree-db: reconcileWorktreeDb ===');
162
+
163
+ // Test: merges new decisions from worktree into main
164
+ {
165
+ const mainDir = tempDir();
166
+ const wtDir = tempDir();
167
+ const mainDb = path.join(mainDir, 'gsd.db');
168
+ const wtDb = path.join(wtDir, 'gsd.db');
169
+
170
+ // Seed main with D001
171
+ seedMainDb(mainDb);
172
+ closeDatabase();
173
+
174
+ // Copy to worktree, add D002 in worktree
175
+ copyWorktreeDb(mainDb, wtDb);
176
+ openDatabase(wtDb);
177
+ insertDecision({
178
+ id: 'D002',
179
+ when_context: '2025-02-01',
180
+ scope: 'M001/S02',
181
+ decision: 'Use WAL mode',
182
+ choice: 'WAL',
183
+ rationale: 'Performance',
184
+ revisable: 'yes',
185
+ superseded_by: null,
186
+ });
187
+ closeDatabase();
188
+
189
+ // Re-open main and reconcile
190
+ openDatabase(mainDb);
191
+ const result = reconcileWorktreeDb(mainDb, wtDb);
192
+
193
+ assertTrue(result.decisions > 0, 'decisions merged count > 0');
194
+ const d2 = getDecisionById('D002');
195
+ assertTrue(d2 !== null, 'D002 from worktree now in main');
196
+ assertEq(d2?.choice, 'WAL', 'D002 data correct after merge');
197
+
198
+ cleanup(mainDir, wtDir);
199
+ }
200
+
201
+ // Test: merges new requirements from worktree into main
202
+ {
203
+ const mainDir = tempDir();
204
+ const wtDir = tempDir();
205
+ const mainDb = path.join(mainDir, 'gsd.db');
206
+ const wtDb = path.join(wtDir, 'gsd.db');
207
+
208
+ seedMainDb(mainDb);
209
+ closeDatabase();
210
+ copyWorktreeDb(mainDb, wtDb);
211
+
212
+ openDatabase(wtDb);
213
+ insertRequirement({
214
+ id: 'R002',
215
+ class: 'non-functional',
216
+ status: 'active',
217
+ description: 'Must be fast',
218
+ why: 'UX',
219
+ source: 'design',
220
+ primary_owner: 'S02',
221
+ supporting_slices: '',
222
+ validation: 'benchmark',
223
+ notes: '',
224
+ full_content: 'Performance requirement',
225
+ superseded_by: null,
226
+ });
227
+ closeDatabase();
228
+
229
+ openDatabase(mainDb);
230
+ const result = reconcileWorktreeDb(mainDb, wtDb);
231
+
232
+ assertTrue(result.requirements > 0, 'requirements merged count > 0');
233
+ const r2 = getRequirementById('R002');
234
+ assertTrue(r2 !== null, 'R002 from worktree now in main');
235
+ assertEq(r2?.description, 'Must be fast', 'R002 data correct after merge');
236
+
237
+ cleanup(mainDir, wtDir);
238
+ }
239
+
240
+ // Test: merges new artifacts from worktree into main
241
+ {
242
+ const mainDir = tempDir();
243
+ const wtDir = tempDir();
244
+ const mainDb = path.join(mainDir, 'gsd.db');
245
+ const wtDb = path.join(wtDir, 'gsd.db');
246
+
247
+ seedMainDb(mainDb);
248
+ closeDatabase();
249
+ copyWorktreeDb(mainDb, wtDb);
250
+
251
+ openDatabase(wtDb);
252
+ insertArtifact({
253
+ path: 'docs/api.md',
254
+ artifact_type: 'reference',
255
+ milestone_id: 'M001',
256
+ slice_id: 'S01',
257
+ task_id: 'T01',
258
+ full_content: 'API documentation',
259
+ });
260
+ closeDatabase();
261
+
262
+ openDatabase(mainDb);
263
+ const result = reconcileWorktreeDb(mainDb, wtDb);
264
+
265
+ assertTrue(result.artifacts > 0, 'artifacts merged count > 0');
266
+ const adapter = _getAdapter()!;
267
+ const row = adapter.prepare('SELECT * FROM artifacts WHERE path = ?').get('docs/api.md');
268
+ assertTrue(row !== null, 'artifact from worktree now in main');
269
+ assertEq(row?.['artifact_type'], 'reference', 'artifact data correct after merge');
270
+
271
+ cleanup(mainDir, wtDir);
272
+ }
273
+
274
+ // Test: detects conflicts (same PK, different content in both DBs)
275
+ {
276
+ const mainDir = tempDir();
277
+ const wtDir = tempDir();
278
+ const mainDb = path.join(mainDir, 'gsd.db');
279
+ const wtDb = path.join(wtDir, 'gsd.db');
280
+
281
+ // Seed main with D001
282
+ seedMainDb(mainDb);
283
+ closeDatabase();
284
+ copyWorktreeDb(mainDb, wtDb);
285
+
286
+ // Modify D001 in main
287
+ openDatabase(mainDb);
288
+ const mainAdapter = _getAdapter()!;
289
+ mainAdapter.prepare(
290
+ `UPDATE decisions SET choice = 'better-sqlite3' WHERE id = 'D001'`,
291
+ ).run();
292
+ closeDatabase();
293
+
294
+ // Modify D001 in worktree differently
295
+ openDatabase(wtDb);
296
+ const wtAdapter = _getAdapter()!;
297
+ wtAdapter.prepare(
298
+ `UPDATE decisions SET choice = 'sql.js' WHERE id = 'D001'`,
299
+ ).run();
300
+ closeDatabase();
301
+
302
+ // Reconcile
303
+ openDatabase(mainDb);
304
+ const result = reconcileWorktreeDb(mainDb, wtDb);
305
+
306
+ assertTrue(result.conflicts.length > 0, 'conflicts detected');
307
+ assertTrue(
308
+ result.conflicts.some(c => c.includes('D001')),
309
+ 'conflict mentions D001',
310
+ );
311
+
312
+ // Worktree-wins: D001 should now have worktree's value
313
+ const d1 = getDecisionById('D001');
314
+ assertEq(d1?.choice, 'sql.js', 'worktree wins on conflict (INSERT OR REPLACE)');
315
+
316
+ cleanup(mainDir, wtDir);
317
+ }
318
+
319
+ // Test: handles missing worktree DB gracefully
320
+ {
321
+ const mainDir = tempDir();
322
+ const mainDb = path.join(mainDir, 'gsd.db');
323
+
324
+ seedMainDb(mainDb);
325
+
326
+ const result = reconcileWorktreeDb(mainDb, '/nonexistent/worktree.db');
327
+ assertEq(result.decisions, 0, 'no decisions merged for missing worktree DB');
328
+ assertEq(result.requirements, 0, 'no requirements merged for missing worktree DB');
329
+ assertEq(result.artifacts, 0, 'no artifacts merged for missing worktree DB');
330
+ assertEq(result.conflicts.length, 0, 'no conflicts for missing worktree DB');
331
+
332
+ cleanup(mainDir);
333
+ }
334
+
335
+ // Test: path with spaces works
336
+ {
337
+ const baseDir = tempDir();
338
+ const mainDir = path.join(baseDir, 'main dir');
339
+ const wtDir = path.join(baseDir, 'worktree dir');
340
+ fs.mkdirSync(mainDir, { recursive: true });
341
+ fs.mkdirSync(wtDir, { recursive: true });
342
+
343
+ const mainDb = path.join(mainDir, 'gsd.db');
344
+ const wtDb = path.join(wtDir, 'gsd.db');
345
+
346
+ seedMainDb(mainDb);
347
+ closeDatabase();
348
+ copyWorktreeDb(mainDb, wtDb);
349
+
350
+ // Add a decision in worktree
351
+ openDatabase(wtDb);
352
+ insertDecision({
353
+ id: 'D003',
354
+ when_context: '2025-03-01',
355
+ scope: 'M001/S03',
356
+ decision: 'Path spaces test',
357
+ choice: 'yes',
358
+ rationale: 'Robustness',
359
+ revisable: 'no',
360
+ superseded_by: null,
361
+ });
362
+ closeDatabase();
363
+
364
+ openDatabase(mainDb);
365
+ const result = reconcileWorktreeDb(mainDb, wtDb);
366
+ assertTrue(result.decisions > 0, 'reconciliation works with spaces in path');
367
+ const d3 = getDecisionById('D003');
368
+ assertTrue(d3 !== null, 'D003 merged from worktree with spaces in path');
369
+
370
+ cleanup(baseDir);
371
+ }
372
+
373
+ // Test: main DB is usable after reconciliation (DETACH cleanup verified)
374
+ {
375
+ const mainDir = tempDir();
376
+ const wtDir = tempDir();
377
+ const mainDb = path.join(mainDir, 'gsd.db');
378
+ const wtDb = path.join(wtDir, 'gsd.db');
379
+
380
+ seedMainDb(mainDb);
381
+ closeDatabase();
382
+ copyWorktreeDb(mainDb, wtDb);
383
+
384
+ openDatabase(mainDb);
385
+ reconcileWorktreeDb(mainDb, wtDb);
386
+
387
+ // Verify main DB is still fully usable after DETACH
388
+ assertTrue(isDbAvailable(), 'DB still available after reconciliation');
389
+
390
+ insertDecision({
391
+ id: 'D099',
392
+ when_context: '2025-12-01',
393
+ scope: 'test',
394
+ decision: 'Post-reconcile insert',
395
+ choice: 'works',
396
+ rationale: 'Verify DETACH cleanup',
397
+ revisable: 'no',
398
+ superseded_by: null,
399
+ });
400
+
401
+ const d99 = getDecisionById('D099');
402
+ assertTrue(d99 !== null, 'can insert and query after reconciliation');
403
+ assertEq(d99?.choice, 'works', 'post-reconcile data correct');
404
+
405
+ // Verify no "wt" database still attached
406
+ const adapter = _getAdapter()!;
407
+ let wtAccessible = false;
408
+ try {
409
+ adapter.prepare('SELECT count(*) FROM wt.decisions').get();
410
+ wtAccessible = true;
411
+ } catch {
412
+ // Expected — wt should be detached
413
+ }
414
+ assertTrue(!wtAccessible, 'wt database is detached after reconciliation');
415
+
416
+ cleanup(mainDir, wtDir);
417
+ }
418
+
419
+ // Test: reconcile with empty worktree DB (no new rows, no conflicts)
420
+ {
421
+ const mainDir = tempDir();
422
+ const wtDir = tempDir();
423
+ const mainDb = path.join(mainDir, 'gsd.db');
424
+ const wtDb = path.join(wtDir, 'gsd.db');
425
+
426
+ seedMainDb(mainDb);
427
+ closeDatabase();
428
+ copyWorktreeDb(mainDb, wtDb);
429
+
430
+ // Don't modify the worktree DB at all — reconcile the identical copy
431
+ openDatabase(mainDb);
432
+ const result = reconcileWorktreeDb(mainDb, wtDb);
433
+
434
+ // Should still report counts for the existing rows (INSERT OR REPLACE touches them)
435
+ assertTrue(result.conflicts.length === 0, 'no conflicts when DBs are identical');
436
+ assertTrue(isDbAvailable(), 'DB usable after no-change reconciliation');
437
+
438
+ cleanup(mainDir, wtDir);
439
+ }
440
+
441
+ // ─── Final Report ──────────────────────────────────────────────────────────
442
+ report();
@@ -0,0 +1,165 @@
1
+ /**
2
+ * worktree-post-create-hook.test.ts — Tests for #597 worktree post-create hook.
3
+ *
4
+ * Verifies that runWorktreePostCreateHook correctly executes user scripts
5
+ * with SOURCE_DIR and WORKTREE_DIR environment variables.
6
+ *
7
+ * Uses Node.js scripts instead of bash for Windows compatibility.
8
+ */
9
+
10
+ import test from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { mkdtempSync, mkdirSync, rmSync, existsSync, writeFileSync, readFileSync, chmodSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+
16
+ import { runWorktreePostCreateHook } from "../auto-worktree.ts";
17
+
18
+ function makeTmpDir(): string {
19
+ return mkdtempSync(join(tmpdir(), "gsd-wt-hook-test-"));
20
+ }
21
+
22
+ const isWin = process.platform === "win32";
23
+
24
+ /** Return the platform-appropriate hook file path (adds .bat on Windows). */
25
+ function hookPath(base: string): string {
26
+ return isWin ? `${base}.bat` : base;
27
+ }
28
+
29
+ /** Create a cross-platform Node.js hook script. */
30
+ function writeNodeHookScript(filePath: string, code: string): void {
31
+ if (isWin) {
32
+ // Write the JS code to a companion .js file and have the .bat invoke it.
33
+ // node -e with multi-line code breaks on Windows because cmd.exe splits on newlines.
34
+ const jsPath = filePath.replace(/\.bat$/, ".js");
35
+ writeFileSync(jsPath, code);
36
+ writeFileSync(filePath, `@echo off\nnode "%~dp0${jsPath.split("\\").pop()}" %*\n`);
37
+ } else {
38
+ writeFileSync(filePath, `#!/usr/bin/env node\n${code}\n`);
39
+ chmodSync(filePath, 0o755);
40
+ }
41
+ }
42
+
43
+ // ─── runWorktreePostCreateHook ──────────────────────────────────────────────
44
+
45
+ test("returns null when no hook path is provided", () => {
46
+ const src = makeTmpDir();
47
+ const wt = makeTmpDir();
48
+ try {
49
+ const result = runWorktreePostCreateHook(src, wt, undefined);
50
+ assert.equal(result, null);
51
+ } finally {
52
+ rmSync(src, { recursive: true, force: true });
53
+ rmSync(wt, { recursive: true, force: true });
54
+ }
55
+ });
56
+
57
+ test("returns error when hook script does not exist", () => {
58
+ const src = makeTmpDir();
59
+ const wt = makeTmpDir();
60
+ try {
61
+ const result = runWorktreePostCreateHook(src, wt, ".gsd/hooks/nonexistent");
62
+ assert.ok(result !== null, "should return error string");
63
+ assert.ok(result!.includes("not found"), "error should mention 'not found'");
64
+ } finally {
65
+ rmSync(src, { recursive: true, force: true });
66
+ rmSync(wt, { recursive: true, force: true });
67
+ }
68
+ });
69
+
70
+ test("executes hook script with correct SOURCE_DIR and WORKTREE_DIR env vars", () => {
71
+ const src = makeTmpDir();
72
+ const wt = makeTmpDir();
73
+ try {
74
+ const hooksDir = join(src, ".gsd", "hooks");
75
+ mkdirSync(hooksDir, { recursive: true });
76
+ const hookFile = hookPath(join(hooksDir, "post-create"));
77
+ const code = [
78
+ `const fs = require("fs");`,
79
+ `const path = require("path");`,
80
+ `const out = path.join(process.env.WORKTREE_DIR, "hook-output.txt");`,
81
+ `fs.writeFileSync(out, "SOURCE=" + process.env.SOURCE_DIR + "\\n" + "WORKTREE=" + process.env.WORKTREE_DIR + "\\n");`,
82
+ ].join("\n");
83
+ writeNodeHookScript(hookFile, code);
84
+
85
+ const result = runWorktreePostCreateHook(src, wt, hookPath(".gsd/hooks/post-create"));
86
+ assert.equal(result, null, "should succeed");
87
+
88
+ const outputFile = join(wt, "hook-output.txt");
89
+ assert.ok(existsSync(outputFile), "hook should have created output file");
90
+
91
+ const output = readFileSync(outputFile, "utf-8");
92
+ assert.ok(output.includes(`SOURCE=${src}`), "SOURCE_DIR should match source dir");
93
+ assert.ok(output.includes(`WORKTREE=${wt}`), "WORKTREE_DIR should match worktree dir");
94
+ } finally {
95
+ rmSync(src, { recursive: true, force: true });
96
+ rmSync(wt, { recursive: true, force: true });
97
+ }
98
+ });
99
+
100
+ test("returns error message when hook script fails", () => {
101
+ const src = makeTmpDir();
102
+ const wt = makeTmpDir();
103
+ try {
104
+ const hooksDir = join(src, ".gsd", "hooks");
105
+ mkdirSync(hooksDir, { recursive: true });
106
+ const hookFile = hookPath(join(hooksDir, "failing-hook"));
107
+ writeNodeHookScript(hookFile, `process.exit(1);`);
108
+
109
+ const result = runWorktreePostCreateHook(src, wt, hookPath(".gsd/hooks/failing-hook"));
110
+ assert.ok(result !== null, "should return error string");
111
+ assert.ok(result!.includes("hook failed"), "error should mention 'hook failed'");
112
+ } finally {
113
+ rmSync(src, { recursive: true, force: true });
114
+ rmSync(wt, { recursive: true, force: true });
115
+ }
116
+ });
117
+
118
+ test("supports absolute hook paths", () => {
119
+ const src = makeTmpDir();
120
+ const wt = makeTmpDir();
121
+ try {
122
+ const hookFile = hookPath(join(src, "absolute-hook"));
123
+ const code = [
124
+ `const fs = require("fs");`,
125
+ `const path = require("path");`,
126
+ `fs.writeFileSync(path.join(process.env.WORKTREE_DIR, "absolute-hook-ran"), "");`,
127
+ ].join("\n");
128
+ writeNodeHookScript(hookFile, code);
129
+
130
+ const result = runWorktreePostCreateHook(src, wt, hookFile);
131
+ assert.equal(result, null, "absolute path hook should succeed");
132
+ assert.ok(existsSync(join(wt, "absolute-hook-ran")), "hook should have run");
133
+ } finally {
134
+ rmSync(src, { recursive: true, force: true });
135
+ rmSync(wt, { recursive: true, force: true });
136
+ }
137
+ });
138
+
139
+ test("hook can copy files from source to worktree", () => {
140
+ const src = makeTmpDir();
141
+ const wt = makeTmpDir();
142
+ try {
143
+ writeFileSync(join(src, ".env"), "DB_HOST=localhost\nAPI_KEY=secret123\n");
144
+
145
+ const hookFile = hookPath(join(src, "setup-hook"));
146
+ const code = [
147
+ `const fs = require("fs");`,
148
+ `const path = require("path");`,
149
+ `const envSrc = path.join(process.env.SOURCE_DIR, ".env");`,
150
+ `const envDst = path.join(process.env.WORKTREE_DIR, ".env");`,
151
+ `fs.copyFileSync(envSrc, envDst);`,
152
+ ].join("\n");
153
+ writeNodeHookScript(hookFile, code);
154
+
155
+ const result = runWorktreePostCreateHook(src, wt, hookFile);
156
+ assert.equal(result, null, "hook should succeed");
157
+
158
+ assert.ok(existsSync(join(wt, ".env")), ".env should be copied to worktree");
159
+ const envContent = readFileSync(join(wt, ".env"), "utf-8");
160
+ assert.ok(envContent.includes("API_KEY=secret123"), ".env content should match");
161
+ } finally {
162
+ rmSync(src, { recursive: true, force: true });
163
+ rmSync(wt, { recursive: true, force: true });
164
+ }
165
+ });
@@ -334,3 +334,32 @@ export interface HookStatusEntry {
334
334
  /** Current cycle counts for active triggers. */
335
335
  activeCycles: Record<string, number>;
336
336
  }
337
+
338
+ // ─── Database Types (Decisions & Requirements) ────────────────────────────
339
+
340
+ export interface Decision {
341
+ seq: number; // auto-increment primary key
342
+ id: string; // e.g. "D001"
343
+ when_context: string; // when/context of the decision
344
+ scope: string; // scope (milestone, slice, global, etc.)
345
+ decision: string; // what was decided
346
+ choice: string; // the specific choice made
347
+ rationale: string; // why this choice
348
+ revisable: string; // whether/when revisable
349
+ superseded_by: string | null; // ID of superseding decision, or null
350
+ }
351
+
352
+ export interface Requirement {
353
+ id: string; // e.g. "R001"
354
+ class: string; // requirement class (functional, non-functional, etc.)
355
+ status: string; // active, validated, deferred, etc.
356
+ description: string; // short description
357
+ why: string; // rationale
358
+ source: string; // origin (milestone, user, etc.)
359
+ primary_owner: string; // owning slice/milestone
360
+ supporting_slices: string; // other slices that touch this
361
+ validation: string; // how to validate
362
+ notes: string; // additional notes
363
+ full_content: string; // full requirement text
364
+ superseded_by: string | null; // ID of superseding requirement, or null
365
+ }
@@ -1,6 +1,5 @@
1
1
  // GSD Extension — Undo Last Unit
2
2
  // Rollback the most recent completed unit: revert git, remove state, uncheck plans.
3
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
4
3
 
5
4
  import type { ExtensionCommandContext, ExtensionAPI } from "@gsd/pi-coding-agent";
6
5
  import { existsSync, readFileSync, writeFileSync, unlinkSync, readdirSync } from "node:fs";
@@ -36,6 +36,7 @@ export interface AutoUnitRuntimeRecord {
36
36
  updatedAt: number;
37
37
  phase: UnitRuntimePhase;
38
38
  wrapupWarningSent: boolean;
39
+ continueHereFired: boolean;
39
40
  timeoutAt: number | null;
40
41
  lastProgressAt: number;
41
42
  progressCount: number;
@@ -50,7 +51,9 @@ function runtimeDir(basePath: string): string {
50
51
  }
51
52
 
52
53
  function runtimePath(basePath: string, unitType: string, unitId: string): string {
53
- return join(runtimeDir(basePath), `${unitType}-${unitId.replace(/[\/]/g, "-")}.json`);
54
+ const sanitizedUnitType = unitType.replace(/[\/]/g, "-");
55
+ const sanitizedUnitId = unitId.replace(/[\/]/g, "-");
56
+ return join(runtimeDir(basePath), `${sanitizedUnitType}-${sanitizedUnitId}.json`);
54
57
  }
55
58
 
56
59
  export function writeUnitRuntimeRecord(
@@ -72,6 +75,7 @@ export function writeUnitRuntimeRecord(
72
75
  updatedAt: Date.now(),
73
76
  phase: updates.phase ?? prev?.phase ?? "dispatched",
74
77
  wrapupWarningSent: updates.wrapupWarningSent ?? prev?.wrapupWarningSent ?? false,
78
+ continueHereFired: updates.continueHereFired ?? prev?.continueHereFired ?? false,
75
79
  timeoutAt: updates.timeoutAt ?? prev?.timeoutAt ?? null,
76
80
  lastProgressAt: updates.lastProgressAt ?? prev?.lastProgressAt ?? Date.now(),
77
81
  progressCount: updates.progressCount ?? prev?.progressCount ?? 0,