gsd-pi 2.66.1-dev.ed243f2 → 2.67.0-dev.43b0159

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 (287) hide show
  1. package/dist/claude-cli-check.d.ts +8 -0
  2. package/dist/claude-cli-check.js +36 -0
  3. package/dist/cli.js +40 -0
  4. package/dist/onboarding.js +19 -2
  5. package/dist/resources/extensions/ask-user-questions.js +79 -11
  6. package/dist/resources/extensions/claude-code-cli/partial-builder.js +4 -3
  7. package/dist/resources/extensions/claude-code-cli/readiness.js +63 -12
  8. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -3
  9. package/dist/resources/extensions/gsd/auto/loop.js +13 -1
  10. package/dist/resources/extensions/gsd/auto/phases.js +22 -3
  11. package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
  12. package/dist/resources/extensions/gsd/auto/session.js +1 -1
  13. package/dist/resources/extensions/gsd/auto-dashboard.js +65 -15
  14. package/dist/resources/extensions/gsd/auto-dispatch.js +30 -28
  15. package/dist/resources/extensions/gsd/auto-model-selection.js +12 -3
  16. package/dist/resources/extensions/gsd/auto-prompts.js +173 -25
  17. package/dist/resources/extensions/gsd/auto-recovery.js +11 -12
  18. package/dist/resources/extensions/gsd/auto.js +13 -1
  19. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +32 -1
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +18 -6
  21. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +59 -5
  23. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +8 -5
  24. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +186 -14
  25. package/dist/resources/extensions/gsd/codebase-generator.js +4 -0
  26. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  27. package/dist/resources/extensions/gsd/commands/dispatcher.js +1 -1
  28. package/dist/resources/extensions/gsd/commands/handlers/core.js +94 -4
  29. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +49 -9
  30. package/dist/resources/extensions/gsd/context-store.js +134 -2
  31. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -1
  32. package/dist/resources/extensions/gsd/detection.js +6 -0
  33. package/dist/resources/extensions/gsd/files.js +19 -2
  34. package/dist/resources/extensions/gsd/guided-flow.js +12 -8
  35. package/dist/resources/extensions/gsd/index.js +1 -1
  36. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +2 -0
  37. package/dist/resources/extensions/gsd/parsers-legacy.js +3 -1
  38. package/dist/resources/extensions/gsd/preferences.js +6 -1
  39. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  40. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  41. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
  42. package/dist/resources/extensions/gsd/prompts/discuss.md +3 -3
  43. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  44. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
  45. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  46. package/dist/resources/extensions/gsd/prompts/rethink.md +6 -2
  47. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  48. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  49. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +11 -9
  50. package/dist/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
  51. package/dist/resources/extensions/gsd/safety/file-change-validator.js +2 -1
  52. package/dist/resources/extensions/gsd/state.js +2 -1
  53. package/dist/resources/extensions/gsd/visualizer-overlay.js +27 -26
  54. package/dist/resources/extensions/gsd/workflow-reconcile.js +46 -7
  55. package/dist/resources/extensions/remote-questions/manager.js +8 -0
  56. package/dist/resources/extensions/shared/interview-ui.js +10 -0
  57. package/dist/web/standalone/.next/BUILD_ID +1 -1
  58. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  59. package/dist/web/standalone/.next/build-manifest.json +3 -3
  60. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  61. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  63. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/index.html +1 -1
  80. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  87. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  90. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  91. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  92. package/dist/web/standalone/.next/static/chunks/{6502.8874bcae249c02e1.js → 6502.b804e48b7919f55e.js} +3 -3
  93. package/dist/web/standalone/.next/static/chunks/{webpack-9fed74684e1c5bb1.js → webpack-65f0501b197d1c49.js} +1 -1
  94. package/package.json +1 -1
  95. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  96. package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -3
  97. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  98. package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
  99. package/packages/pi-ai/dist/utils/json-parse.js +11 -1
  100. package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
  101. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  102. package/packages/pi-ai/dist/utils/repair-tool-json.js +60 -1
  103. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  104. package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts +2 -0
  105. package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts.map +1 -0
  106. package/packages/pi-ai/dist/utils/tests/json-parse.test.js +14 -0
  107. package/packages/pi-ai/dist/utils/tests/json-parse.test.js.map +1 -0
  108. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +10 -0
  109. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  110. package/packages/pi-ai/src/providers/anthropic-shared.ts +4 -3
  111. package/packages/pi-ai/src/utils/json-parse.ts +11 -1
  112. package/packages/pi-ai/src/utils/repair-tool-json.ts +69 -1
  113. package/packages/pi-ai/src/utils/tests/json-parse.test.ts +17 -0
  114. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +13 -0
  115. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -0
  116. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -0
  118. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +16 -0
  120. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/retry-handler.js +58 -1
  122. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +58 -0
  124. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/sdk.d.ts +3 -0
  126. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/sdk.js +1 -0
  128. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts +2 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +17 -0
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +2 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +1 -0
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +2 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
  147. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  148. package/packages/pi-coding-agent/package.json +1 -1
  149. package/packages/pi-coding-agent/src/core/agent-session.ts +4 -0
  150. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +69 -0
  151. package/packages/pi-coding-agent/src/core/retry-handler.ts +66 -1
  152. package/packages/pi-coding-agent/src/core/sdk.ts +5 -0
  153. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +18 -0
  154. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +2 -1
  155. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +11 -2
  156. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +2 -1
  157. package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -1
  158. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -2
  159. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +13 -0
  160. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  161. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts +2 -0
  162. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts.map +1 -0
  163. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +35 -0
  164. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -0
  165. package/packages/pi-tui/dist/__tests__/tui.test.d.ts +2 -0
  166. package/packages/pi-tui/dist/__tests__/tui.test.d.ts.map +1 -0
  167. package/packages/pi-tui/dist/__tests__/tui.test.js +43 -0
  168. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -0
  169. package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
  170. package/packages/pi-tui/dist/autocomplete.js +9 -7
  171. package/packages/pi-tui/dist/autocomplete.js.map +1 -1
  172. package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts +2 -0
  173. package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts.map +1 -0
  174. package/packages/pi-tui/dist/components/__tests__/editor.test.js +54 -0
  175. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -0
  176. package/packages/pi-tui/dist/components/editor.d.ts +3 -1
  177. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  178. package/packages/pi-tui/dist/components/editor.js +14 -3
  179. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  180. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  181. package/packages/pi-tui/dist/stdin-buffer.js +6 -0
  182. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  183. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  184. package/packages/pi-tui/dist/tui.js +8 -0
  185. package/packages/pi-tui/dist/tui.js.map +1 -1
  186. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +15 -0
  187. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +43 -0
  188. package/packages/pi-tui/src/__tests__/tui.test.ts +50 -0
  189. package/packages/pi-tui/src/autocomplete.ts +9 -7
  190. package/packages/pi-tui/src/components/__tests__/editor.test.ts +64 -0
  191. package/packages/pi-tui/src/components/editor.ts +14 -3
  192. package/packages/pi-tui/src/stdin-buffer.ts +7 -0
  193. package/packages/pi-tui/src/tui.ts +9 -0
  194. package/pkg/package.json +1 -1
  195. package/src/resources/extensions/ask-user-questions.ts +103 -11
  196. package/src/resources/extensions/claude-code-cli/partial-builder.ts +4 -3
  197. package/src/resources/extensions/claude-code-cli/readiness.ts +67 -12
  198. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -3
  199. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +17 -0
  200. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +18 -0
  201. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -1
  202. package/src/resources/extensions/gsd/auto/loop.ts +14 -1
  203. package/src/resources/extensions/gsd/auto/phases.ts +27 -4
  204. package/src/resources/extensions/gsd/auto/run-unit.ts +14 -2
  205. package/src/resources/extensions/gsd/auto/session.ts +1 -1
  206. package/src/resources/extensions/gsd/auto-dashboard.ts +76 -16
  207. package/src/resources/extensions/gsd/auto-dispatch.ts +36 -35
  208. package/src/resources/extensions/gsd/auto-model-selection.ts +12 -3
  209. package/src/resources/extensions/gsd/auto-prompts.ts +195 -25
  210. package/src/resources/extensions/gsd/auto-recovery.ts +15 -15
  211. package/src/resources/extensions/gsd/auto.ts +12 -1
  212. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -1
  213. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +27 -6
  214. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -0
  215. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +67 -6
  216. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +11 -8
  217. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +209 -16
  218. package/src/resources/extensions/gsd/codebase-generator.ts +4 -0
  219. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  220. package/src/resources/extensions/gsd/commands/dispatcher.ts +1 -2
  221. package/src/resources/extensions/gsd/commands/handlers/core.ts +113 -8
  222. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +49 -11
  223. package/src/resources/extensions/gsd/context-store.ts +167 -2
  224. package/src/resources/extensions/gsd/custom-workflow-engine.ts +3 -1
  225. package/src/resources/extensions/gsd/detection.ts +6 -0
  226. package/src/resources/extensions/gsd/files.ts +21 -2
  227. package/src/resources/extensions/gsd/guided-flow.ts +15 -8
  228. package/src/resources/extensions/gsd/index.ts +6 -0
  229. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +2 -0
  230. package/src/resources/extensions/gsd/parsers-legacy.ts +3 -1
  231. package/src/resources/extensions/gsd/preferences.ts +6 -1
  232. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  233. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  234. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
  235. package/src/resources/extensions/gsd/prompts/discuss.md +3 -3
  236. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  237. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
  238. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  239. package/src/resources/extensions/gsd/prompts/rethink.md +6 -2
  240. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  241. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  242. package/src/resources/extensions/gsd/prompts/validate-milestone.md +11 -9
  243. package/src/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
  244. package/src/resources/extensions/gsd/safety/file-change-validator.ts +4 -1
  245. package/src/resources/extensions/gsd/state.ts +2 -1
  246. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +52 -1
  247. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +50 -2
  248. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +21 -7
  249. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +48 -0
  250. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  251. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +12 -0
  252. package/src/resources/extensions/gsd/tests/context-store.test.ts +176 -0
  253. package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +76 -0
  254. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +7 -1
  255. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +31 -0
  256. package/src/resources/extensions/gsd/tests/decision-scope-cascade.test.ts +370 -0
  257. package/src/resources/extensions/gsd/tests/detection.test.ts +37 -0
  258. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +50 -0
  259. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +35 -0
  260. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +34 -0
  261. package/src/resources/extensions/gsd/tests/health-widget.test.ts +45 -0
  262. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +53 -13
  263. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -2
  264. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +3 -3
  265. package/src/resources/extensions/gsd/tests/measurement.test.ts +531 -0
  266. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +3 -4
  267. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +21 -0
  268. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +71 -2
  269. package/src/resources/extensions/gsd/tests/parsers.test.ts +25 -0
  270. package/src/resources/extensions/gsd/tests/preferences.test.ts +37 -0
  271. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +26 -4
  272. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +60 -0
  273. package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +9 -0
  274. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +19 -0
  275. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +73 -0
  276. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +98 -0
  277. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +2 -2
  278. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +26 -0
  279. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +59 -0
  280. package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +91 -0
  281. package/src/resources/extensions/gsd/tests/write-gate.test.ts +210 -35
  282. package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -27
  283. package/src/resources/extensions/gsd/workflow-reconcile.ts +59 -8
  284. package/src/resources/extensions/remote-questions/manager.ts +9 -0
  285. package/src/resources/extensions/shared/interview-ui.ts +13 -0
  286. /package/dist/web/standalone/.next/static/{HAq0VE4k68rhRvJbQL1VW → CrKrzIIxk55witDF1eS0L}/_buildManifest.js +0 -0
  287. /package/dist/web/standalone/.next/static/{HAq0VE4k68rhRvJbQL1VW → CrKrzIIxk55witDF1eS0L}/_ssgManifest.js +0 -0
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Exercises shouldBlockContextWrite() — a pure function that implements:
5
5
  * (a) toolName !== "write" → pass
6
- * (b) milestoneId null pass (not in discussion)
6
+ * (b) milestone context must resolve to a verified milestone
7
7
  * (c) path doesn't match /M\d+-CONTEXT\.md$/ → pass
8
- * (d) depthVerified → pass
8
+ * (d) non-context files → pass
9
9
  * (e) else → block with actionable reason
10
10
  */
11
11
 
@@ -14,12 +14,12 @@ import assert from 'node:assert/strict';
14
14
  import {
15
15
  isDepthConfirmationAnswer,
16
16
  shouldBlockContextWrite,
17
- isDepthVerified,
18
- isQueuePhaseActive,
19
17
  setQueuePhaseActive,
20
18
  } from '../index.ts';
21
19
  import {
22
20
  markDepthVerified,
21
+ isMilestoneDepthVerified,
22
+ shouldBlockContextArtifactSave,
23
23
  clearDiscussionFlowState,
24
24
  resetWriteGateState,
25
25
  } from '../bootstrap/write-gate.ts';
@@ -53,26 +53,27 @@ test('write-gate: blocks CONTEXT.md write during discussion without depth verifi
53
53
  // ─── Scenario 3: Allows CONTEXT.md write after depth verification ──
54
54
 
55
55
  test('write-gate: allows CONTEXT.md write after depth verification', () => {
56
+ clearDiscussionFlowState();
57
+ markDepthVerified('M001');
56
58
  const result = shouldBlockContextWrite(
57
59
  'write',
58
60
  '/Users/dev/project/.gsd/milestones/M001/M001-CONTEXT.md',
59
61
  'M001',
60
- true,
61
62
  );
62
63
  assert.strictEqual(result.block, false, 'should not block after depth verification');
63
64
  assert.strictEqual(result.reason, undefined, 'should have no reason');
65
+ clearDiscussionFlowState();
64
66
  });
65
67
 
66
- // ─── Scenario 4: Allows CONTEXT.md write outside discussion phase (milestoneId null) ──
68
+ // ─── Scenario 4: Ambiguous session context no longer bypasses the gate ──
67
69
 
68
- test('write-gate: allows CONTEXT.md write outside discussion phase', () => {
70
+ test('write-gate: blocks CONTEXT.md write when milestoneId is ambiguous', () => {
69
71
  const result = shouldBlockContextWrite(
70
72
  'write',
71
73
  '.gsd/milestones/M001/M001-CONTEXT.md',
72
74
  null,
73
- false,
74
75
  );
75
- assert.strictEqual(result.block, false, 'should not block outside discussion phase');
76
+ assert.strictEqual(result.block, true, 'should block when milestone context is ambiguous');
76
77
  });
77
78
 
78
79
  // ─── Scenario 5: Allows non-CONTEXT.md writes during discussion ──
@@ -83,7 +84,6 @@ test('write-gate: allows non-CONTEXT.md writes during discussion', () => {
83
84
  'write',
84
85
  '.gsd/milestones/M001/M001-DISCUSSION.md',
85
86
  'M001',
86
- false,
87
87
  );
88
88
  assert.strictEqual(r1.block, false, 'DISCUSSION.md should pass');
89
89
 
@@ -92,7 +92,6 @@ test('write-gate: allows non-CONTEXT.md writes during discussion', () => {
92
92
  'write',
93
93
  '.gsd/milestones/M001/slices/S01/S01-PLAN.md',
94
94
  'M001',
95
- false,
96
95
  );
97
96
  assert.strictEqual(r2.block, false, 'slice plan should pass');
98
97
 
@@ -101,7 +100,6 @@ test('write-gate: allows non-CONTEXT.md writes during discussion', () => {
101
100
  'write',
102
101
  'src/index.ts',
103
102
  'M001',
104
- false,
105
103
  );
106
104
  assert.strictEqual(r3.block, false, 'regular code file should pass');
107
105
  });
@@ -113,7 +111,6 @@ test('write-gate: regex does not match slice context files (S01-CONTEXT.md)', ()
113
111
  'write',
114
112
  '.gsd/milestones/M001/slices/S01/S01-CONTEXT.md',
115
113
  'M001',
116
- false,
117
114
  );
118
115
  assert.strictEqual(result.block, false, 'S01-CONTEXT.md should not be blocked');
119
116
  });
@@ -125,7 +122,6 @@ test('write-gate: blocked reason contains depth_verification keyword and anti-by
125
122
  'write',
126
123
  '.gsd/milestones/M999/M999-CONTEXT.md',
127
124
  'M999',
128
- false,
129
125
  );
130
126
  assert.strictEqual(result.block, true);
131
127
  assert.ok(result.reason!.includes('depth_verification'), 'reason should mention depth_verification question id');
@@ -141,7 +137,6 @@ test('write-gate: blocks CONTEXT.md write in queue mode without depth verificati
141
137
  'write',
142
138
  '.gsd/milestones/M001/M001-CONTEXT.md',
143
139
  null, // no milestoneId in queue mode
144
- false, // not depth-verified
145
140
  true, // queue phase active
146
141
  );
147
142
  assert.strictEqual(result.block, true, 'should block in queue mode without depth verification');
@@ -151,48 +146,228 @@ test('write-gate: blocks CONTEXT.md write in queue mode without depth verificati
151
146
  // ─── Scenario 9: Queue mode allows CONTEXT.md write after depth verification ──
152
147
 
153
148
  test('write-gate: allows CONTEXT.md write in queue mode after depth verification', () => {
149
+ clearDiscussionFlowState();
150
+ markDepthVerified('M001');
154
151
  const result = shouldBlockContextWrite(
155
152
  'write',
156
153
  '.gsd/milestones/M001/M001-CONTEXT.md',
157
154
  null, // no milestoneId in queue mode
158
- true, // depth-verified
159
155
  true, // queue phase active
160
156
  );
161
157
  assert.strictEqual(result.block, false, 'should not block in queue mode after depth verification');
158
+ clearDiscussionFlowState();
162
159
  });
163
160
 
164
- // ─── Scenario 10: markDepthVerified works in queue-only mode (no milestoneId) ──
165
- // This is the core regression for #1812: in queue mode, the tool_result handler
166
- // must call markDepthVerified() even when getDiscussionMilestoneId() is null.
161
+ // ─── Scenario 10: depth verification is scoped per milestone, not global ──
167
162
 
168
- test('write-gate: markDepthVerified unblocks queue-mode writes when milestoneId is null', () => {
163
+ test('write-gate: markDepthVerified unlocks only the matching milestone', () => {
169
164
  clearDiscussionFlowState();
170
- setQueuePhaseActive(true);
165
+ markDepthVerified('M001');
171
166
 
172
- // Before marking: should block
173
- const blocked = shouldBlockContextWrite(
167
+ const allowed = shouldBlockContextWrite(
174
168
  'write',
175
169
  '.gsd/milestones/M001/M001-CONTEXT.md',
176
170
  null,
177
- isDepthVerified(),
178
- isQueuePhaseActive(),
179
171
  );
180
- assert.strictEqual(blocked.block, true, 'should block before markDepthVerified');
172
+ assert.strictEqual(allowed.block, false, 'should allow the verified milestone');
181
173
 
182
- // Simulate what the fixed tool_result handler does
183
- markDepthVerified();
184
-
185
- // After marking: should pass
186
- const allowed = shouldBlockContextWrite(
174
+ const blockedOther = shouldBlockContextWrite(
187
175
  'write',
188
- '.gsd/milestones/M001/M001-CONTEXT.md',
176
+ '.gsd/milestones/M002/M002-CONTEXT.md',
189
177
  null,
190
- isDepthVerified(),
191
- isQueuePhaseActive(),
192
178
  );
193
- assert.strictEqual(allowed.block, false, 'should allow after markDepthVerified in queue mode');
179
+ assert.strictEqual(blockedOther.block, true, 'other milestones should remain blocked');
180
+ assert.strictEqual(isMilestoneDepthVerified('M001'), true);
181
+ assert.strictEqual(isMilestoneDepthVerified('M002'), false);
182
+
183
+ clearDiscussionFlowState();
184
+ });
185
+
186
+ // ─── Scenario 11: gsd_summary_save CONTEXT contract is milestone-scoped ──
187
+
188
+ test('write-gate: gsd_summary_save only blocks final milestone CONTEXT writes', () => {
189
+ clearDiscussionFlowState();
190
+
191
+ assert.strictEqual(
192
+ shouldBlockContextArtifactSave('CONTEXT-DRAFT', 'M001').block,
193
+ false,
194
+ 'draft CONTEXT should be allowed',
195
+ );
196
+ assert.strictEqual(
197
+ shouldBlockContextArtifactSave('CONTEXT', 'M001', 'S01').block,
198
+ false,
199
+ 'slice CONTEXT should be allowed',
200
+ );
201
+ assert.strictEqual(
202
+ shouldBlockContextArtifactSave('CONTEXT', 'M001').block,
203
+ true,
204
+ 'final milestone CONTEXT should block before verification',
205
+ );
206
+
207
+ markDepthVerified('M001');
208
+ assert.strictEqual(
209
+ shouldBlockContextArtifactSave('CONTEXT', 'M001').block,
210
+ false,
211
+ 'final milestone CONTEXT should pass after verification',
212
+ );
213
+
214
+ clearDiscussionFlowState();
215
+ });
194
216
 
217
+ // ═══════════════════════════════════════════════════════════════════════
218
+ // Discussion gate enforcement tests (pending gate mechanism)
219
+ // ═══════════════════════════════════════════════════════════════════════
220
+
221
+ import {
222
+ isGateQuestionId,
223
+ shouldBlockPendingGate,
224
+ shouldBlockPendingGateBash,
225
+ setPendingGate,
226
+ clearPendingGate,
227
+ getPendingGate,
228
+ } from '../bootstrap/write-gate.ts';
229
+
230
+ // ─── Scenario 19: isGateQuestionId recognizes all gate patterns ──
231
+
232
+ test('write-gate: isGateQuestionId recognizes all gate patterns', () => {
233
+ assert.strictEqual(isGateQuestionId('layer1_scope_gate'), true);
234
+ assert.strictEqual(isGateQuestionId('layer2_architecture_gate'), true);
235
+ assert.strictEqual(isGateQuestionId('layer3_error_gate'), true);
236
+ assert.strictEqual(isGateQuestionId('layer4_quality_gate'), true);
237
+ assert.strictEqual(isGateQuestionId('depth_verification'), true);
238
+ assert.strictEqual(isGateQuestionId('depth_verification_M002'), true);
239
+ assert.strictEqual(isGateQuestionId('my_layer1_scope_gate_question'), true);
240
+ // Non-gate question IDs
241
+ assert.strictEqual(isGateQuestionId('project_intent'), false);
242
+ assert.strictEqual(isGateQuestionId('feature_priority'), false);
243
+ assert.strictEqual(isGateQuestionId(''), false);
244
+ });
245
+
246
+ // ─── Scenario 20: setPendingGate / getPendingGate / clearPendingGate lifecycle ──
247
+
248
+ test('write-gate: pending gate lifecycle (set, get, clear)', () => {
195
249
  clearDiscussionFlowState();
250
+ assert.strictEqual(getPendingGate(), null, 'starts null');
251
+
252
+ setPendingGate('layer1_scope_gate');
253
+ assert.strictEqual(getPendingGate(), 'layer1_scope_gate', 'set correctly');
254
+
255
+ clearPendingGate();
256
+ assert.strictEqual(getPendingGate(), null, 'cleared correctly');
257
+
258
+ // clearDiscussionFlowState also clears pending gate
259
+ setPendingGate('layer2_architecture_gate');
260
+ clearDiscussionFlowState();
261
+ assert.strictEqual(getPendingGate(), null, 'clearDiscussionFlowState clears pending gate');
262
+ });
263
+
264
+ // ─── Scenario 21: shouldBlockPendingGate blocks non-safe tools when gate is pending ──
265
+
266
+ test('write-gate: shouldBlockPendingGate blocks write/edit during pending gate', () => {
267
+ clearDiscussionFlowState();
268
+ setPendingGate('layer1_scope_gate');
269
+
270
+ // write should be blocked during discussion
271
+ const writeResult = shouldBlockPendingGate('write', 'M001', false);
272
+ assert.strictEqual(writeResult.block, true, 'write should be blocked');
273
+ assert.ok(writeResult.reason!.includes('layer1_scope_gate'), 'reason mentions the gate');
274
+
275
+ // edit should be blocked
276
+ const editResult = shouldBlockPendingGate('edit', 'M001', false);
277
+ assert.strictEqual(editResult.block, true, 'edit should be blocked');
278
+
279
+ // gsd tools should be blocked
280
+ const gsdResult = shouldBlockPendingGate('gsd_plan_milestone', 'M001', false);
281
+ assert.strictEqual(gsdResult.block, true, 'gsd tools should be blocked');
282
+
283
+ clearDiscussionFlowState();
284
+ });
285
+
286
+ // ─── Scenario 22: shouldBlockPendingGate allows safe tools when gate is pending ──
287
+
288
+ test('write-gate: shouldBlockPendingGate allows read-only and ask_user_questions during pending gate', () => {
289
+ clearDiscussionFlowState();
290
+ setPendingGate('layer1_scope_gate');
291
+
292
+ // ask_user_questions is always safe (model needs to re-ask)
293
+ assert.strictEqual(shouldBlockPendingGate('ask_user_questions', 'M001').block, false);
294
+ // read-only tools are safe
295
+ assert.strictEqual(shouldBlockPendingGate('read', 'M001').block, false);
296
+ assert.strictEqual(shouldBlockPendingGate('grep', 'M001').block, false);
297
+ assert.strictEqual(shouldBlockPendingGate('glob', 'M001').block, false);
298
+ assert.strictEqual(shouldBlockPendingGate('ls', 'M001').block, false);
299
+
300
+ clearDiscussionFlowState();
301
+ });
302
+
303
+ // ─── Scenario 23: shouldBlockPendingGate still blocks when the session is ambiguous ──
304
+
305
+ test('write-gate: shouldBlockPendingGate blocks outside discussion when a gate is pending', () => {
306
+ clearDiscussionFlowState();
307
+ setPendingGate('layer1_scope_gate');
308
+
309
+ // No milestoneId and no queue phase — still block because the gate is pending
310
+ const result = shouldBlockPendingGate('write', null, false);
311
+ assert.strictEqual(result.block, true, 'should block even when milestoneId is null');
312
+
313
+ clearDiscussionFlowState();
314
+ });
315
+
316
+ // ─── Scenario 24: shouldBlockPendingGate blocks in queue mode ──
317
+
318
+ test('write-gate: shouldBlockPendingGate blocks in queue mode when gate is pending', () => {
319
+ clearDiscussionFlowState();
320
+ setQueuePhaseActive(true);
321
+ setPendingGate('depth_verification');
322
+
323
+ const result = shouldBlockPendingGate('write', null, true);
324
+ assert.strictEqual(result.block, true, 'should block in queue mode');
325
+
326
+ clearDiscussionFlowState();
327
+ });
328
+
329
+ // ─── Scenario 25: shouldBlockPendingGateBash allows read-only commands ──
330
+
331
+ test('write-gate: shouldBlockPendingGateBash allows read-only commands during pending gate', () => {
332
+ clearDiscussionFlowState();
333
+ setPendingGate('layer2_architecture_gate');
334
+
335
+ assert.strictEqual(shouldBlockPendingGateBash('cat file.txt', 'M001').block, false);
336
+ assert.strictEqual(shouldBlockPendingGateBash('git log --oneline', 'M001').block, false);
337
+ assert.strictEqual(shouldBlockPendingGateBash('grep -r pattern .', 'M001').block, false);
338
+ assert.strictEqual(shouldBlockPendingGateBash('ls -la', 'M001').block, false);
339
+
340
+ clearDiscussionFlowState();
341
+ });
342
+
343
+ // ─── Scenario 26: shouldBlockPendingGateBash blocks mutating commands ──
344
+
345
+ test('write-gate: shouldBlockPendingGateBash blocks mutating commands during pending gate', () => {
346
+ clearDiscussionFlowState();
347
+ setPendingGate('layer2_architecture_gate');
348
+
349
+ const result = shouldBlockPendingGateBash('npm run build', 'M001');
350
+ assert.strictEqual(result.block, true, 'mutating bash should be blocked');
351
+ assert.ok(result.reason!.includes('layer2_architecture_gate'));
352
+
353
+ clearDiscussionFlowState();
354
+ });
355
+
356
+ // ─── Scenario 27: no pending gate means no blocking ──
357
+
358
+ test('write-gate: no pending gate means no blocking', () => {
359
+ clearDiscussionFlowState();
360
+
361
+ assert.strictEqual(shouldBlockPendingGate('write', 'M001').block, false);
362
+ assert.strictEqual(shouldBlockPendingGateBash('npm run build', 'M001').block, false);
363
+ });
364
+
365
+ // ─── Scenario 28: resetWriteGateState clears pending gate ──
366
+
367
+ test('write-gate: resetWriteGateState clears pending gate', () => {
368
+ setPendingGate('layer3_error_gate');
369
+ resetWriteGateState();
370
+ assert.strictEqual(getPendingGate(), null);
196
371
  });
197
372
 
198
373
  // ─── Standard options fixture used across depth confirmation tests ──
@@ -34,6 +34,24 @@ const TAB_LABELS = [
34
34
  "0 Export",
35
35
  ];
36
36
 
37
+ type TabBarEntry = { label: string; width: number };
38
+
39
+ function buildTabBarEntries(activeTab: number, filterText: string, capturesPendingCount?: number): TabBarEntry[] {
40
+ return TAB_LABELS.map((label, i) => {
41
+ let displayLabel = label;
42
+ if (i === activeTab && filterText) {
43
+ displayLabel += " \u2731";
44
+ }
45
+ if (i === 8 && capturesPendingCount) {
46
+ displayLabel += ` (${capturesPendingCount})`;
47
+ }
48
+ return {
49
+ label: displayLabel,
50
+ width: visibleWidth(displayLabel) + 2,
51
+ };
52
+ });
53
+ }
54
+
37
55
  export class GSDVisualizerOverlay {
38
56
  private tui: { requestRender: () => void };
39
57
  private theme: Theme;
@@ -116,15 +134,14 @@ export class GSDVisualizerOverlay {
116
134
  }
117
135
 
118
136
  handleInput(data: string): void {
137
+ if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
138
+ this.dispose();
139
+ this.onClose();
140
+ return;
141
+ }
142
+
119
143
  // Filter mode input routing
120
144
  if (this.filterMode) {
121
- if (matchesKey(data, Key.escape)) {
122
- this.filterMode = false;
123
- this.filterText = "";
124
- this.invalidate();
125
- this.tui.requestRender();
126
- return;
127
- }
128
145
  if (matchesKey(data, Key.enter)) {
129
146
  this.filterMode = false;
130
147
  this.invalidate();
@@ -179,8 +196,9 @@ export class GSDVisualizerOverlay {
179
196
  // Left click — check if on tab bar row
180
197
  if (mouse.y === 2) {
181
198
  let xPos = 3;
182
- for (let i = 0; i < TAB_LABELS.length; i++) {
183
- const tabWidth = TAB_LABELS[i].length + 2;
199
+ const tabs = buildTabBarEntries(this.activeTab, this.filterText, this.data?.captures?.pendingCount);
200
+ for (let i = 0; i < tabs.length; i++) {
201
+ const tabWidth = tabs[i]!.width;
184
202
  if (mouse.x >= xPos && mouse.x < xPos + tabWidth) {
185
203
  this.activeTab = i;
186
204
  this.invalidate();
@@ -194,12 +212,6 @@ export class GSDVisualizerOverlay {
194
212
  return;
195
213
  }
196
214
 
197
- if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
198
- this.dispose();
199
- this.onClose();
200
- return;
201
- }
202
-
203
215
  if (matchesKey(data, Key.shift("tab"))) {
204
216
  this.activeTab = (this.activeTab - 1 + TAB_COUNT) % TAB_COUNT;
205
217
  this.invalidate();
@@ -442,20 +454,12 @@ export class GSDVisualizerOverlay {
442
454
  const content: string[] = [];
443
455
 
444
456
  // Tab bar
445
- const tabs = TAB_LABELS.map((label, i) => {
446
- let displayLabel = label;
447
- // Show filter indicator on active tab with filter
448
- if (i === this.activeTab && this.filterText) {
449
- displayLabel += " \u2731";
450
- }
451
- // Show captures badge
452
- if (i === 8 && this.data?.captures?.pendingCount) {
453
- displayLabel += ` (${this.data.captures.pendingCount})`;
454
- }
457
+ const tabEntries = buildTabBarEntries(this.activeTab, this.filterText, this.data?.captures?.pendingCount);
458
+ const tabs = tabEntries.map((entry, i) => {
455
459
  if (i === this.activeTab) {
456
- return th.fg("accent", `[${displayLabel}]`);
460
+ return th.fg("accent", `[${entry.label}]`);
457
461
  }
458
- return th.fg("dim", `[${displayLabel}]`);
462
+ return th.fg("dim", `[${entry.label}]`);
459
463
  });
460
464
  content.push(" " + tabs.join(" "));
461
465
  content.push("");
@@ -1,7 +1,7 @@
1
1
  import { join } from "node:path";
2
2
  import { mkdirSync, existsSync, readFileSync, unlinkSync } from "node:fs";
3
3
  import { logWarning, logError } from "./workflow-logger.js";
4
- import { readEvents, findForkPoint, appendEvent, getSessionId } from "./workflow-events.js";
4
+ import { readEvents, findForkPoint, getSessionId } from "./workflow-events.js";
5
5
  import type { WorkflowEvent } from "./workflow-events.js";
6
6
  import {
7
7
  transaction,
@@ -329,6 +329,41 @@ export function detectConflicts(
329
329
  return conflicts;
330
330
  }
331
331
 
332
+ function rewriteDivergedEventsForEntity(
333
+ divergedEvents: WorkflowEvent[],
334
+ entityType: string,
335
+ entityId: string,
336
+ replacementEvents: WorkflowEvent[],
337
+ ): WorkflowEvent[] {
338
+ const rewritten: WorkflowEvent[] = [];
339
+ let inserted = false;
340
+
341
+ for (const event of divergedEvents) {
342
+ const key = extractEntityKey(event);
343
+ if (key?.type === entityType && key.id === entityId) {
344
+ if (!inserted) {
345
+ rewritten.push(...replacementEvents);
346
+ inserted = true;
347
+ }
348
+ continue;
349
+ }
350
+ rewritten.push(event);
351
+ }
352
+
353
+ if (!inserted) {
354
+ rewritten.push(...replacementEvents);
355
+ }
356
+
357
+ return rewritten;
358
+ }
359
+
360
+ function writeEventLog(basePath: string, events: WorkflowEvent[]): void {
361
+ const dir = join(basePath, ".gsd");
362
+ mkdirSync(dir, { recursive: true });
363
+ const content = events.map((e) => JSON.stringify(e)).join("\n") + (events.length > 0 ? "\n" : "");
364
+ atomicWriteSync(join(dir, "event-log.jsonl"), content);
365
+ }
366
+
332
367
  // ─── writeConflictsFile ───────────────────────────────────────────────────────
333
368
 
334
369
  /**
@@ -575,8 +610,8 @@ function parseEventBlock(block: string): WorkflowEvent[] {
575
610
 
576
611
  /**
577
612
  * Resolve a single conflict by picking one side's events.
578
- * Replays the picked events through the DB helpers, appends them to the event log,
579
- * and updates or removes CONFLICTS.md.
613
+ * Replays the picked events through the DB helpers, rewrites the chosen side's
614
+ * event log so the conflict is durable, and updates or removes CONFLICTS.md.
580
615
  *
581
616
  * When the last conflict is resolved, non-conflicting events from both sides
582
617
  * are also replayed (they were blocked by the all-or-nothing D-04 rule).
@@ -598,14 +633,30 @@ export function resolveConflict(
598
633
  const conflict = conflicts[idx]!;
599
634
  const eventsToReplay = pick === "main" ? conflict.mainSideEvents : conflict.worktreeSideEvents;
600
635
 
636
+ const mainLogPath = join(basePath, ".gsd", "event-log.jsonl");
637
+ const wtLogPath = join(worktreeBasePath, ".gsd", "event-log.jsonl");
638
+ const mainEvents = readEvents(mainLogPath);
639
+ const wtEvents = readEvents(wtLogPath);
640
+ const forkPoint = findForkPoint(mainEvents, wtEvents);
641
+ const mainBaseEvents = mainEvents.slice(0, forkPoint + 1);
642
+ const wtBaseEvents = wtEvents.slice(0, forkPoint + 1);
643
+ const mainDiverged = mainEvents.slice(forkPoint + 1);
644
+ const wtDiverged = wtEvents.slice(forkPoint + 1);
645
+
646
+ const rewrittenTargetEvents = pick === "main"
647
+ ? rewriteDivergedEventsForEntity(wtDiverged, entityType, entityId, eventsToReplay)
648
+ : rewriteDivergedEventsForEntity(mainDiverged, entityType, entityId, eventsToReplay);
649
+
650
+ const targetBasePath = pick === "main" ? worktreeBasePath : basePath;
651
+ const targetBaseEvents = pick === "main" ? wtBaseEvents : mainBaseEvents;
652
+ writeEventLog(targetBasePath, targetBaseEvents.concat(rewrittenTargetEvents));
653
+
601
654
  // Replay resolved events through the DB (updates DB state)
602
655
  openDatabase(join(basePath, ".gsd", "gsd.db"));
603
656
  replayEvents(eventsToReplay);
604
-
605
- // Append resolved events to the event log
606
- for (const event of eventsToReplay) {
607
- appendEvent(basePath, { cmd: event.cmd, params: event.params, ts: event.ts, actor: event.actor });
608
- }
657
+ invalidateStateCache();
658
+ clearPathCache();
659
+ clearParseCache();
609
660
 
610
661
  // Remove resolved conflict from list
611
662
  conflicts.splice(idx, 1);
@@ -24,6 +24,15 @@ interface QuestionInput {
24
24
  allowMultiple?: boolean;
25
25
  }
26
26
 
27
+ /**
28
+ * Check whether a remote channel is configured without triggering any
29
+ * side effects (no HTTP requests, no prompt records). Used by the race
30
+ * logic to decide routing before committing to a remote dispatch.
31
+ */
32
+ export function isRemoteConfigured(): boolean {
33
+ return resolveRemoteConfig() !== null;
34
+ }
35
+
27
36
  export async function tryRemoteQuestions(
28
37
  questions: QuestionInput[],
29
38
  signal?: AbortSignal,
@@ -80,6 +80,12 @@ export interface InterviewRoundOptions {
80
80
  * Label for the Esc-confirm overlay header. Defaults to "End interview?".
81
81
  */
82
82
  exitHeadline?: string;
83
+ /**
84
+ * Optional AbortSignal to cancel the interview externally (e.g. when racing
85
+ * against a remote question channel). When aborted, the TUI closes and the
86
+ * promise resolves with an empty answers object.
87
+ */
88
+ signal?: AbortSignal;
83
89
  /**
84
90
  * Text for the "exit" hint shown in the review screen footer and exit confirm overlay.
85
91
  * Defaults to "end interview".
@@ -207,6 +213,13 @@ export async function showInterviewRound(
207
213
  let exitCursor = 0; // 0 = keep going (default), 1 = end interview
208
214
  let cachedLines: string[] | undefined;
209
215
 
216
+ // External cancellation (e.g. remote channel won the race)
217
+ if (opts.signal) {
218
+ const onAbort = () => done({ endInterview: false, answers: {} });
219
+ if (opts.signal.aborted) { onAbort(); }
220
+ else { opts.signal.addEventListener("abort", onAbort, { once: true }); }
221
+ }
222
+
210
223
  // Editor is created once; editorTheme comes from the design system
211
224
  const editorRef = { current: null as Editor | null };
212
225