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
@@ -703,6 +703,31 @@ Widget description.
703
703
  assert.deepStrictEqual(p.tasks[0].title, 'Build the widget', 'em-dash heading T01 title');
704
704
  });
705
705
 
706
+ test('parsePlan: filename subheadings do not become task ids', () => {
707
+ const content = `# S15: Filename Headings
708
+
709
+ **Goal:** Ignore file-reference subheadings inside task descriptions.
710
+ **Demo:** Only real task ids are parsed.
711
+
712
+ ## Tasks
713
+
714
+ - [ ] **T01: First task** \`est:10m\`
715
+ Implement the feature.
716
+
717
+ ### constraints.py — \`add_off_request_tiered()\`
718
+ - preserve behavior
719
+
720
+ ### annotations.py — \`annotate()\`
721
+ - keep metadata
722
+ `;
723
+
724
+ const p = parsePlan(content);
725
+ assert.deepStrictEqual(p.tasks.map((task) => task.id), ['T01'], 'filename subheadings should not create extra tasks');
726
+ assert.deepStrictEqual(p.tasks[0].title, 'First task', 'real task should still parse normally');
727
+ assert.ok(p.tasks[0].description.includes('preserve behavior'), 'detail lines under filename subheadings should remain attached to the task');
728
+ assert.ok(p.tasks[0].description.includes('keep metadata'), 'later detail lines should also remain attached to the task');
729
+ });
730
+
706
731
  test('parsePlan: mixed checkbox and heading-style tasks', () => {
707
732
  const content = `# S14: Mixed Format
708
733
 
@@ -17,6 +17,8 @@ import {
17
17
  parsePreferencesMarkdown,
18
18
  _resetParseWarningFlag,
19
19
  } from "../preferences.ts";
20
+ import { formatConfiguredModel, toPersistedModelId } from "../commands-prefs-wizard.ts";
21
+ import { _resetLogs, peekLogs } from "../workflow-logger.ts";
20
22
  import type { GSDPreferences, GSDModelConfigV2, GSDPhaseModelConfig } from "../preferences.ts";
21
23
 
22
24
  // ── Git preferences ──────────────────────────────────────────────────────────
@@ -346,6 +348,22 @@ test("handles model config with explicit provider field", () => {
346
348
  assert.equal(execution.provider, "bedrock");
347
349
  });
348
350
 
351
+ test("formatConfiguredModel renders provider-qualified object config", () => {
352
+ assert.equal(
353
+ formatConfiguredModel({ model: "claude-opus-4-6", provider: "bedrock" }),
354
+ "bedrock/claude-opus-4-6",
355
+ );
356
+ });
357
+
358
+ test("toPersistedModelId prefixes provider chosen in prefs wizard", () => {
359
+ assert.equal(toPersistedModelId("openai", "gpt-5.4"), "openai/gpt-5.4");
360
+ assert.equal(
361
+ toPersistedModelId("openai", "openai/gpt-5.4"),
362
+ "openai/gpt-5.4",
363
+ "already-qualified IDs should be preserved",
364
+ );
365
+ });
366
+
349
367
  test("handles empty models config", () => {
350
368
  const prefs = parsePreferencesMarkdown("---\nversion: 1\n---\n");
351
369
  assert.notEqual(prefs, null);
@@ -422,6 +440,25 @@ test("parsePreferencesMarkdown parses heading+list format without frontmatter (#
422
440
  assert.deepStrictEqual(result!.git, { isolation: "none" });
423
441
  });
424
442
 
443
+ test("section parse warning is emitted at most once for heading+list YAML failures (#3759)", () => {
444
+ _resetParseWarningFlag();
445
+ _resetLogs();
446
+
447
+ const content = `## Git
448
+ bad: [
449
+ `;
450
+
451
+ parsePreferencesMarkdown(content);
452
+ parsePreferencesMarkdown(content);
453
+ parsePreferencesMarkdown(content);
454
+
455
+ const warnings = peekLogs().filter((entry) => entry.component === "guided" && entry.message.includes("preferences section parse failed"));
456
+ assert.equal(warnings.length, 1, `expected exactly 1 guided warning, got ${warnings.length}`);
457
+
458
+ _resetParseWarningFlag();
459
+ _resetLogs();
460
+ });
461
+
425
462
  // ── Experimental preferences ─────────────────────────────────────────────────
426
463
 
427
464
  test("experimental.rtk: true is accepted and stored", () => {
@@ -51,6 +51,12 @@ test("guided discussion prompts avoid wrap-up prompts after every round", () =>
51
51
  assert.doesNotMatch(slicePrompt, /I think I have a solid picture of this slice\. Ready to wrap up/i);
52
52
  });
53
53
 
54
+ test("guided milestone discussion scopes depth verification to the milestone id", () => {
55
+ const prompt = readPrompt("guided-discuss-milestone");
56
+ assert.match(prompt, /depth_verification_\{\{milestoneId\}\}/, "depth verification id should include the milestone id");
57
+ assert.doesNotMatch(prompt, /depth_verification_confirm" — this enables the write-gate downstream/i, "legacy global depth gate wording should be gone");
58
+ });
59
+
54
60
  test("guided-resume-task prompt preserves recovery state until work is superseded", () => {
55
61
  const prompt = readPrompt("guided-resume-task");
56
62
  assert.match(prompt, /Do \*\*not\*\* delete the continue file immediately/i);
@@ -65,11 +71,13 @@ test("execute-task prompt references gsd_complete_task tool", () => {
65
71
  assert.match(prompt, /gsd_complete_task/);
66
72
  });
67
73
 
68
- test("execute-task prompt instructs writing task summary before tool call", () => {
74
+ test("execute-task prompt uses gsd_complete_task as canonical summary write path", () => {
69
75
  const prompt = readPrompt("execute-task");
70
- // The prompt instructs writing the summary file AND calling the tool
71
76
  assert.match(prompt, /\{\{taskSummaryPath\}\}/);
72
77
  assert.match(prompt, /gsd_complete_task/);
78
+ assert.match(prompt, /DB-backed tool is the canonical write path/i);
79
+ assert.match(prompt, /Do \*\*not\*\* manually write `?\{\{taskSummaryPath\}\}`?/i);
80
+ assert.doesNotMatch(prompt, /^\d+\.\s+Write `?\{\{taskSummaryPath\}\}`?\s*$/m);
73
81
  });
74
82
 
75
83
  test("execute-task prompt does not instruct LLM to toggle checkboxes manually", () => {
@@ -113,10 +121,14 @@ test("guided-complete-slice prompt references gsd_slice_complete tool", () => {
113
121
 
114
122
  test("complete-slice prompt instructs writing summary and UAT files before tool call", () => {
115
123
  const prompt = readPrompt("complete-slice");
116
- // The prompt instructs writing the summary AND UAT files, then calling the tool
117
124
  assert.match(prompt, /\{\{sliceSummaryPath\}\}/);
118
125
  assert.match(prompt, /\{\{sliceUatPath\}\}/);
119
126
  assert.match(prompt, /gsd_complete_slice/);
127
+ assert.match(prompt, /DB-backed tool is the canonical write path/i);
128
+ assert.match(prompt, /Do \*\*not\*\* manually write `?\{\{sliceSummaryPath\}\}`?/i);
129
+ assert.match(prompt, /Do \*\*not\*\* manually write `?\{\{sliceUatPath\}\}`?/i);
130
+ assert.doesNotMatch(prompt, /^\d+\.\s+Write `?\{\{sliceSummaryPath\}\}`?.*$/m);
131
+ assert.doesNotMatch(prompt, /^\d+\.\s+Write `?\{\{sliceUatPath\}\}`?.*$/m);
120
132
  });
121
133
 
122
134
  test("complete-slice prompt preserves decisions and knowledge review steps", () => {
@@ -125,6 +137,15 @@ test("complete-slice prompt preserves decisions and knowledge review steps", ()
125
137
  assert.match(prompt, /KNOWLEDGE\.md/);
126
138
  });
127
139
 
140
+ test("validate-milestone prompt uses gsd_validate_milestone as canonical validation write path", () => {
141
+ const prompt = readPrompt("validate-milestone");
142
+ assert.match(prompt, /gsd_validate_milestone/);
143
+ assert.match(prompt, /\{\{validationPath\}\}/);
144
+ assert.match(prompt, /DB-backed tool is the canonical write path/i);
145
+ assert.match(prompt, /Do \*\*not\*\* manually write `?\{\{validationPath\}\}`?/i);
146
+ assert.doesNotMatch(prompt, /Write to `?\{\{validationPath\}\}`?:/i);
147
+ });
148
+
128
149
  test("complete-slice prompt still contains template variables for context", () => {
129
150
  const prompt = readPrompt("complete-slice");
130
151
  assert.match(prompt, /\{\{sliceSummaryPath\}\}/);
@@ -188,7 +209,8 @@ test("validate-milestone prompt dispatches parallel reviewers", () => {
188
209
  assert.match(prompt, /Reviewer C/);
189
210
  assert.match(prompt, /Requirements Coverage/);
190
211
  assert.match(prompt, /Cross-Slice Integration/);
191
- assert.match(prompt, /UAT/);
212
+ assert.match(prompt, /Assessment & Acceptance Criteria/);
213
+ assert.match(prompt, /assessment evidence/i);
192
214
  });
193
215
 
194
216
  // ─── Prompt migration: replan-slice → gsd_replan_slice ────────────────
@@ -458,6 +458,66 @@ test("openai-codex-responses.ts extracts nested error fields", () => {
458
458
  );
459
459
  });
460
460
 
461
+ // ── Fix 1: resetTransientRetryState resets module-level singleton ────────────
462
+
463
+ test("resetTransientRetryState is exported from agent-end-recovery.ts", () => {
464
+ const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
465
+ assert.ok(
466
+ src.includes("export function resetTransientRetryState"),
467
+ "agent-end-recovery.ts must export resetTransientRetryState for provider-error-resume.ts",
468
+ );
469
+ });
470
+
471
+ test("provider-error-resume.ts calls resetTransientRetryState before startAuto", () => {
472
+ const src = readFileSync(join(__dirname, "..", "bootstrap", "provider-error-resume.ts"), "utf-8");
473
+ assert.ok(
474
+ src.includes("resetTransientRetryState"),
475
+ "provider-error-resume.ts must import and call resetTransientRetryState",
476
+ );
477
+ // Ensure reset is called BEFORE startAuto — order matters
478
+ const resetIdx = src.indexOf("resetTransientRetryState()");
479
+ const startIdx = src.indexOf("await deps.startAuto(");
480
+ assert.ok(
481
+ resetIdx !== -1 && startIdx !== -1 && resetIdx < startIdx,
482
+ "resetTransientRetryState() must be called before deps.startAuto()",
483
+ );
484
+ });
485
+
486
+ // ── Fix 2: Session creation timeout treated as transient in phases.ts ───────
487
+
488
+ test("phases.ts handles timeout session-creation failures with pause instead of stopAuto", () => {
489
+ const src = readFileSync(join(__dirname, "..", "auto", "phases.ts"), "utf-8");
490
+
491
+ // The cancelled + isTransient + category=timeout path must pause, not hard-stop
492
+ assert.ok(
493
+ src.includes('category === "timeout"'),
494
+ "phases.ts must check category === 'timeout' on transient cancelled unitResults",
495
+ );
496
+ // Must call pauseAuto (not stopAuto) for timeout cancellations
497
+ assert.ok(
498
+ /category === "timeout"[\s\S]{0,300}pauseAuto/.test(src),
499
+ "phases.ts must call pauseAuto for session-timeout failures (not stopAuto or continue)",
500
+ );
501
+ // Must NOT use action: "continue" for transient cancellations (causes infinite loops)
502
+ assert.ok(
503
+ !/isTransient[\s\S]{0,500}action:\s*"continue"/.test(src),
504
+ "phases.ts must NOT return action:continue for cancelled units — use break+pause instead",
505
+ );
506
+ });
507
+
508
+ // ── Fix 3: MAX_TRANSIENT_AUTO_RESUMES raised to 8 ───────────────────────────
509
+
510
+ test("MAX_TRANSIENT_AUTO_RESUMES is at least 8 for sustained overload resilience", () => {
511
+ const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
512
+ const match = src.match(/MAX_TRANSIENT_AUTO_RESUMES\s*=\s*(\d+)/);
513
+ assert.ok(match, "MAX_TRANSIENT_AUTO_RESUMES must be defined");
514
+ const value = Number(match![1]);
515
+ assert.ok(
516
+ value >= 8,
517
+ `MAX_TRANSIENT_AUTO_RESUMES must be >= 8 for sustained overload resilience, got ${value}`,
518
+ );
519
+ });
520
+
461
521
  // ── agent-session retryable regex handles server_error (#1166) ──────────────
462
522
 
463
523
  test("agent-session retryable error regex matches server_error (underscore)", () => {
@@ -13,6 +13,7 @@
13
13
  * (e) write/edit to source path → block
14
14
  * (f) bash command → block (could execute work)
15
15
  * (g) registered GSD tools (gsd_milestone_generate_id, gsd_summary_save) → pass
16
+ * (h) unknown custom tools → block
16
17
  */
17
18
 
18
19
  import test from 'node:test';
@@ -155,3 +156,11 @@ test('queue-guard: allows web search and library tools during queue mode', () =>
155
156
  const r4 = shouldBlockQueueExecution('fetch_page', '', true);
156
157
  assert.strictEqual(r4.block, false, 'fetch_page should pass');
157
158
  });
159
+
160
+ // ─── Scenario 10: Unknown custom tools are blocked during queue mode ──
161
+
162
+ test('queue-guard: blocks unknown custom tools during queue mode', () => {
163
+ const result = shouldBlockQueueExecution('custom_codegen_tool', '', true);
164
+ assert.strictEqual(result.block, true, 'unknown custom tools should be blocked');
165
+ assert.ok(result.reason, 'should explain the queue restriction');
166
+ });
@@ -101,6 +101,25 @@ test("parseTaskPlanIO handles multiple backtick tokens on one line", () => {
101
101
  assert.deepEqual(io.outputFiles, ["src/c.ts"]);
102
102
  });
103
103
 
104
+ test("parseTaskPlanIO strips inline descriptions from backtick-wrapped file references", () => {
105
+ const content = `# T01: Described Paths
106
+
107
+ ## Inputs
108
+
109
+ - \`src/config.ts — existing configuration\`
110
+ - \`src/flags.ts - feature flags\`
111
+
112
+ ## Expected Output
113
+
114
+ - \`definitions/ac-audit.md — current state of AC CRM\`
115
+ - \`docs/runbook.md - update deployment notes\`
116
+ `;
117
+
118
+ const io = parseTaskPlanIO(content);
119
+ assert.deepEqual(io.inputFiles, ["src/config.ts", "src/flags.ts"]);
120
+ assert.deepEqual(io.outputFiles, ["definitions/ac-audit.md", "docs/runbook.md"]);
121
+ });
122
+
104
123
  // ─── deriveTaskGraph ──────────────────────────────────────────────────────
105
124
 
106
125
  test("deriveTaskGraph: linear chain T01→T02→T03", () => {
@@ -0,0 +1,73 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { registerShortcuts } from "../bootstrap/register-shortcuts.ts";
8
+
9
+ function makeTempDir(prefix: string): string {
10
+ const dir = join(
11
+ tmpdir(),
12
+ `gsd-register-shortcuts-test-${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
13
+ );
14
+ mkdirSync(dir, { recursive: true });
15
+ return dir;
16
+ }
17
+
18
+ function cleanup(dir: string): void {
19
+ try {
20
+ rmSync(dir, { recursive: true, force: true });
21
+ } catch {
22
+ // best-effort
23
+ }
24
+ }
25
+
26
+ test("dashboard shortcut resolves the project root instead of the current worktree path", async (t) => {
27
+ const projectRoot = makeTempDir("project");
28
+ const worktreeRoot = join(projectRoot, ".gsd", "worktrees", "M001");
29
+ mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
30
+ mkdirSync(worktreeRoot, { recursive: true });
31
+
32
+ const originalCwd = process.cwd();
33
+ process.chdir(worktreeRoot);
34
+ t.after(() => {
35
+ process.chdir(originalCwd);
36
+ cleanup(projectRoot);
37
+ });
38
+
39
+ let capturedHandler: ((ctx: any) => Promise<void>) | null = null;
40
+ const shortcuts: Array<{ description: string; handler: (ctx: any) => Promise<void> }> = [];
41
+ const pi = {
42
+ registerShortcut: (_key: unknown, shortcut: { description: string; handler: (ctx: any) => Promise<void> }) => {
43
+ shortcuts.push(shortcut);
44
+ if (!capturedHandler) {
45
+ capturedHandler = shortcut.handler;
46
+ }
47
+ },
48
+ } as any;
49
+
50
+ registerShortcuts(pi);
51
+ assert.ok(capturedHandler, "dashboard shortcut is registered");
52
+ const dashboardShortcut = shortcuts[0];
53
+ assert.ok(dashboardShortcut, "dashboard shortcut is captured");
54
+
55
+ let customCalls = 0;
56
+ const notices: Array<{ message: string; type?: string }> = [];
57
+ await dashboardShortcut.handler({
58
+ hasUI: true,
59
+ ui: {
60
+ custom: async () => {
61
+ customCalls++;
62
+ return true;
63
+ },
64
+ notify: (message: string, type?: string) => {
65
+ notices.push({ message, type });
66
+ },
67
+ },
68
+ });
69
+
70
+ assert.ok(customCalls > 0, "shortcut opens the dashboard overlay when project root is resolved");
71
+ assert.equal(notices.length, 0, "shortcut does not fall back to the missing-.gsd warning");
72
+ assert.equal(shortcuts.length, 3, "all GSD shortcuts are still registered");
73
+ });
@@ -760,6 +760,104 @@ test("ask-user-questions source-level: tryRemoteQuestions is called before the h
760
760
  );
761
761
  });
762
762
 
763
+ // ═══════════════════════════════════════════════════════════════════════════
764
+ // Race model tests (#3810) — local TUI races against remote channel
765
+ // ═══════════════════════════════════════════════════════════════════════════
766
+
767
+ test("ask-user-questions source-level: raceRemoteAndLocal function exists", () => {
768
+ const src = readFileSync(
769
+ join(__dirname, "..", "..", "ask-user-questions.ts"),
770
+ "utf-8",
771
+ );
772
+ assert.ok(
773
+ src.includes("async function raceRemoteAndLocal("),
774
+ "raceRemoteAndLocal helper should exist for racing local TUI against remote channel",
775
+ );
776
+ });
777
+
778
+ test("ask-user-questions source-level: race path uses isRemoteConfigured for routing", () => {
779
+ const src = readFileSync(
780
+ join(__dirname, "..", "..", "ask-user-questions.ts"),
781
+ "utf-8",
782
+ );
783
+ assert.ok(
784
+ src.includes("isRemoteConfigured()"),
785
+ "execute() should call isRemoteConfigured() for lightweight routing decision",
786
+ );
787
+ });
788
+
789
+ test("ask-user-questions source-level: race path checks both hasRemote and ctx.hasUI", () => {
790
+ // Regression: #3810 — the race should only activate when BOTH remote and local UI
791
+ // are available. Headless mode should still use remote-only, and no-remote should
792
+ // use local-only.
793
+ const src = readFileSync(
794
+ join(__dirname, "..", "..", "ask-user-questions.ts"),
795
+ "utf-8",
796
+ );
797
+ assert.ok(
798
+ src.includes("hasRemote && ctx.hasUI"),
799
+ "Race path should require both remote configured and local UI available",
800
+ );
801
+ assert.ok(
802
+ src.includes("hasRemote && !ctx.hasUI"),
803
+ "Headless path should handle remote-only when no local UI",
804
+ );
805
+ });
806
+
807
+ test("ask-user-questions source-level: race treats remote timeout as non-win", () => {
808
+ // Regression: the whole point of the race is that a remote timeout should NOT
809
+ // block the local TUI. The race helper must filter out timed_out results.
810
+ const src = readFileSync(
811
+ join(__dirname, "..", "..", "ask-user-questions.ts"),
812
+ "utf-8",
813
+ );
814
+ const raceFnStart = src.indexOf("async function raceRemoteAndLocal(");
815
+ const raceFnEnd = src.indexOf("\n}", raceFnStart);
816
+ const raceFnBody = src.slice(raceFnStart, raceFnEnd);
817
+ assert.ok(
818
+ raceFnBody.includes("timed_out"),
819
+ "raceRemoteAndLocal should check for timed_out in remote results",
820
+ );
821
+ assert.ok(
822
+ raceFnBody.includes("details?.error"),
823
+ "raceRemoteAndLocal should check for error in remote results",
824
+ );
825
+ });
826
+
827
+ test("ask-user-questions source-level: race uses AbortController to cancel loser", () => {
828
+ const src = readFileSync(
829
+ join(__dirname, "..", "..", "ask-user-questions.ts"),
830
+ "utf-8",
831
+ );
832
+ assert.ok(
833
+ src.includes("new AbortController()"),
834
+ "Race path should create an AbortController for cancellation",
835
+ );
836
+ assert.ok(
837
+ src.includes("controller.abort()"),
838
+ "raceRemoteAndLocal should abort the controller to cancel the losing side",
839
+ );
840
+ });
841
+
842
+ test("manager source-level: isRemoteConfigured export exists", () => {
843
+ const src = readFileSync(
844
+ join(__dirname, "..", "..", "remote-questions", "manager.ts"),
845
+ "utf-8",
846
+ );
847
+ assert.ok(
848
+ src.includes("export function isRemoteConfigured()"),
849
+ "manager.ts should export isRemoteConfigured for lightweight config checking",
850
+ );
851
+ // Must delegate to resolveRemoteConfig — no separate config parsing
852
+ const fnStart = src.indexOf("export function isRemoteConfigured()");
853
+ const fnEnd = src.indexOf("\n}", fnStart);
854
+ const fnBody = src.slice(fnStart, fnEnd);
855
+ assert.ok(
856
+ fnBody.includes("resolveRemoteConfig()"),
857
+ "isRemoteConfigured should delegate to resolveRemoteConfig",
858
+ );
859
+ });
860
+
763
861
  test("config source-level: removeProviderToken uses auth.remove not auth.set with empty key", () => {
764
862
  const commandSrc = readFileSync(
765
863
  join(__dirname, "..", "..", "remote-questions", "remote-command.ts"),
@@ -6,7 +6,7 @@ import { tmpdir } from "node:os";
6
6
 
7
7
  const { deriveState } = await import("../state.js");
8
8
 
9
- test("deriveState reports complete when all milestone slices are done", async () => {
9
+ test("deriveState reports the last completed milestone when all milestone slices are done", async () => {
10
10
  const base = mkdtempSync(join(tmpdir(), "gsd-smart-entry-complete-"));
11
11
 
12
12
  try {
@@ -31,7 +31,7 @@ test("deriveState reports complete when all milestone slices are done", async ()
31
31
 
32
32
  const state = await deriveState(base);
33
33
  assert.equal(state.phase, "complete");
34
- assert.equal(state.activeMilestone?.id, "M001");
34
+ assert.equal(state.lastCompletedMilestone?.id, "M001");
35
35
  } finally {
36
36
  rmSync(base, { recursive: true, force: true });
37
37
  }
@@ -9,6 +9,7 @@ import { deriveState, isValidationTerminal } from "../state.ts";
9
9
  import { resolveExpectedArtifactPath, diagnoseExpectedArtifact } from "../auto-artifact-paths.ts";
10
10
  import { verifyExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.ts";
11
11
  import { resolveDispatch, type DispatchContext } from "../auto-dispatch.ts";
12
+ import { buildValidateMilestonePrompt } from "../auto-prompts.ts";
12
13
  import type { GSDState } from "../types.ts";
13
14
  import { clearPathCache } from "../paths.ts";
14
15
  import { clearParseCache } from "../files.ts";
@@ -57,6 +58,12 @@ function writeSliceSummary(base: string, mid: string, sid: string, content: stri
57
58
  writeFileSync(join(dir, `${sid}-SUMMARY.md`), content);
58
59
  }
59
60
 
61
+ function writeSliceAssessment(base: string, mid: string, sid: string, content: string): void {
62
+ const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
63
+ mkdirSync(dir, { recursive: true });
64
+ writeFileSync(join(dir, `${sid}-ASSESSMENT.md`), content);
65
+ }
66
+
60
67
  const ALL_DONE_ROADMAP = `# M001: Test Milestone
61
68
 
62
69
  ## Vision
@@ -192,6 +199,25 @@ test("deriveState returns complete when both VALIDATION and SUMMARY exist", asyn
192
199
  }
193
200
  });
194
201
 
202
+ test("buildValidateMilestonePrompt inlines ASSESSMENT evidence instead of UAT spec", async () => {
203
+ const base = makeTmpBase();
204
+ try {
205
+ writeRoadmap(base, "M001", ALL_DONE_ROADMAP);
206
+ const dir = join(base, ".gsd", "milestones", "M001");
207
+ writeFileSync(join(dir, "M001-CONTEXT.md"), CONTEXT_FILE);
208
+ writeSliceSummary(base, "M001", "S01", "# S01 Summary\nDelivered.");
209
+ writeFileSync(join(dir, "slices", "S01", "S01-UAT.md"), "# UAT Spec\nDo the thing.\n");
210
+ writeSliceAssessment(base, "M001", "S01", "---\nverdict: PASS\n---\n# Assessment\nEvidence captured.");
211
+
212
+ const prompt = await buildValidateMilestonePrompt("M001", "Test Milestone", base);
213
+ assert.match(prompt, /S01 Assessment/i, "prompt should inline assessment evidence");
214
+ assert.match(prompt, /verdict: PASS/i, "prompt should include the assessment verdict");
215
+ assert.doesNotMatch(prompt, /UAT Spec/i, "prompt should not inline the raw UAT spec as evidence");
216
+ } finally {
217
+ cleanup(base);
218
+ }
219
+ });
220
+
195
221
  // ─── Dispatch rule ────────────────────────────────────────────────────────
196
222
 
197
223
  test("dispatch rule matches validating-milestone phase", async () => {
@@ -233,3 +233,62 @@ assert.ok(
233
233
  overlaySrc.includes('from "../shared/mod.js"'),
234
234
  "imports from shared barrel",
235
235
  );
236
+
237
+ test("visualizer overlay closes on escape in filter and help submodes", async () => {
238
+ const mod = await import("../visualizer-overlay.js");
239
+
240
+ const mockTui = { requestRender: () => {} };
241
+ const mockTheme = {
242
+ fg: (_color: string, text: string) => text,
243
+ bold: (text: string) => text,
244
+ };
245
+
246
+ let closedFilter = false;
247
+ const filterOverlay = new mod.GSDVisualizerOverlay(
248
+ mockTui,
249
+ mockTheme as any,
250
+ () => { closedFilter = true; },
251
+ );
252
+ filterOverlay.filterMode = true;
253
+ filterOverlay.handleInput("\u0003");
254
+ assert.equal(closedFilter, true, "Ctrl+C closes while filter mode is active");
255
+ filterOverlay.dispose();
256
+
257
+ let closedHelp = false;
258
+ const helpOverlay = new mod.GSDVisualizerOverlay(
259
+ mockTui,
260
+ mockTheme as any,
261
+ () => { closedHelp = true; },
262
+ );
263
+ helpOverlay.showHelp = true;
264
+ helpOverlay.handleInput("\u001b");
265
+ assert.equal(closedHelp, true, "Escape closes while help overlay is visible");
266
+ helpOverlay.dispose();
267
+ });
268
+
269
+ test("visualizer overlay tab hitboxes include rendered badges", async () => {
270
+ const mod = await import("../visualizer-overlay.js");
271
+
272
+ const mockTui = { requestRender: () => {} };
273
+ const mockTheme = {
274
+ fg: (_color: string, text: string) => text,
275
+ bold: (text: string) => text,
276
+ };
277
+
278
+ const overlay = new mod.GSDVisualizerOverlay(
279
+ mockTui,
280
+ mockTheme as any,
281
+ () => {},
282
+ );
283
+ overlay.loading = true;
284
+ overlay.data = { captures: { pendingCount: 3 } } as any;
285
+
286
+ const lines = overlay.render(120);
287
+ const tabLine = lines.find((line: string) => line.includes("Captures") && line.includes("(3)"));
288
+ assert.ok(tabLine, "rendered tab bar includes captures badge");
289
+ const plain = tabLine!.replace(/\x1b\[[0-9;]*m/g, "");
290
+ const badgeColumn = plain.indexOf("(3)") + 2;
291
+ overlay.handleInput(`\x1b[<0;${badgeColumn};2M`);
292
+ assert.equal(overlay.activeTab, 8, "clicking the badge area selects the captures tab");
293
+ overlay.dispose();
294
+ });
@@ -0,0 +1,91 @@
1
+ import test, { afterEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, rmSync, existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { appendEvent, readEvents } from "../workflow-events.ts";
8
+ import { listConflicts, reconcileWorktreeLogs, resolveConflict } from "../workflow-reconcile.ts";
9
+ import { closeDatabase } from "../gsd-db.ts";
10
+
11
+ const tmpDirs: string[] = [];
12
+
13
+ function makeTmpRepo(): { main: string; worktree: string } {
14
+ const root = mkdtempSync(join(tmpdir(), "workflow-reconcile-"));
15
+ const main = join(root, "main");
16
+ const worktree = join(root, "worktree");
17
+ mkdirSync(main, { recursive: true });
18
+ mkdirSync(worktree, { recursive: true });
19
+ tmpDirs.push(root);
20
+ return { main, worktree };
21
+ }
22
+
23
+ afterEach(() => {
24
+ closeDatabase();
25
+ for (const dir of tmpDirs) {
26
+ try {
27
+ rmSync(dir, { recursive: true, force: true });
28
+ } catch {
29
+ // Best-effort cleanup on platforms that keep files open briefly.
30
+ }
31
+ }
32
+ tmpDirs.length = 0;
33
+ });
34
+
35
+ test("resolveConflict(pick=main) rewrites the worktree log durably", () => {
36
+ const { main, worktree } = makeTmpRepo();
37
+
38
+ appendEvent(main, {
39
+ cmd: "plan_milestone",
40
+ params: { milestoneId: "M001", title: "Base Milestone" },
41
+ ts: "2026-01-01T00:00:00.000Z",
42
+ actor: "agent",
43
+ });
44
+ appendEvent(worktree, {
45
+ cmd: "plan_milestone",
46
+ params: { milestoneId: "M001", title: "Base Milestone" },
47
+ ts: "2026-01-01T00:00:00.000Z",
48
+ actor: "agent",
49
+ });
50
+
51
+ appendEvent(main, {
52
+ cmd: "plan_milestone",
53
+ params: { milestoneId: "M001", title: "Main Choice" },
54
+ ts: "2026-01-01T00:01:00.000Z",
55
+ actor: "agent",
56
+ });
57
+
58
+ appendEvent(worktree, {
59
+ cmd: "plan_milestone",
60
+ params: { milestoneId: "M001", title: "Worktree Choice" },
61
+ ts: "2026-01-01T00:01:00.000Z",
62
+ actor: "agent",
63
+ });
64
+
65
+ const initial = reconcileWorktreeLogs(main, worktree);
66
+ assert.equal(initial.conflicts.length, 1, "expected one conflict before resolution");
67
+ assert.ok(listConflicts(main).length === 1, "CONFLICTS.md should exist after detection");
68
+
69
+ resolveConflict(main, worktree, "milestone:M001", "main");
70
+
71
+ assert.equal(listConflicts(main).length, 0, "conflict file should be cleared after resolving main");
72
+ const conflictsPath = join(main, ".gsd", "CONFLICTS.md");
73
+ assert.equal(
74
+ existsSync(conflictsPath),
75
+ false,
76
+ "CONFLICTS.md should be removed after the last conflict is resolved",
77
+ );
78
+
79
+ const wtEvents = readEvents(join(worktree, ".gsd", "event-log.jsonl"));
80
+ assert.ok(
81
+ wtEvents.some((e) => e.cmd === "plan_milestone" && e.params.title === "Main Choice"),
82
+ "worktree log should be rewritten to the main-side resolution",
83
+ );
84
+ assert.ok(
85
+ !wtEvents.some((e) => e.cmd === "plan_milestone" && e.params.title === "Worktree Choice"),
86
+ "worktree log should no longer contain the discarded conflict event",
87
+ );
88
+
89
+ const second = reconcileWorktreeLogs(main, worktree);
90
+ assert.equal(second.conflicts.length, 0, "reconcile should stay clean after choosing main");
91
+ });