gsd-pi 2.65.0-dev.5c8557b → 2.65.0-dev.d0517ff

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 (316) hide show
  1. package/dist/mcp-server.js +6 -2
  2. package/dist/resources/extensions/browser-tools/capture.js +20 -1
  3. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
  4. package/dist/resources/extensions/gsd/auto/run-unit.js +13 -2
  5. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  6. package/dist/resources/extensions/gsd/auto-dispatch.js +99 -9
  7. package/dist/resources/extensions/gsd/auto-model-selection.js +7 -5
  8. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -6
  9. package/dist/resources/extensions/gsd/auto-prompts.js +24 -0
  10. package/dist/resources/extensions/gsd/auto-recovery.js +40 -22
  11. package/dist/resources/extensions/gsd/auto-start.js +42 -11
  12. package/dist/resources/extensions/gsd/auto-tool-tracking.js +10 -0
  13. package/dist/resources/extensions/gsd/auto-worktree.js +29 -7
  14. package/dist/resources/extensions/gsd/auto.js +21 -15
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -4
  16. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
  17. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +6 -4
  18. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -1
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -3
  20. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +31 -1
  21. package/dist/resources/extensions/gsd/commands/context.js +8 -1
  22. package/dist/resources/extensions/gsd/commands/handlers/core.js +20 -0
  23. package/dist/resources/extensions/gsd/commands-extensions.js +1 -1
  24. package/dist/resources/extensions/gsd/config-overlay.js +312 -0
  25. package/dist/resources/extensions/gsd/db-writer.js +13 -3
  26. package/dist/resources/extensions/gsd/detection.js +1 -1
  27. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -1
  28. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  29. package/dist/resources/extensions/gsd/doctor.js +2 -1
  30. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  31. package/dist/resources/extensions/gsd/gsd-db.js +47 -4
  32. package/dist/resources/extensions/gsd/guided-flow.js +220 -29
  33. package/dist/resources/extensions/gsd/index.js +1 -1
  34. package/dist/resources/extensions/gsd/json-persistence.js +5 -2
  35. package/dist/resources/extensions/gsd/md-importer.js +14 -7
  36. package/dist/resources/extensions/gsd/parallel-orchestrator.js +17 -11
  37. package/dist/resources/extensions/gsd/pre-execution-checks.js +12 -5
  38. package/dist/resources/extensions/gsd/preferences-types.js +3 -0
  39. package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
  40. package/dist/resources/extensions/gsd/preferences.js +9 -2
  41. package/dist/resources/extensions/gsd/preparation.js +1092 -0
  42. package/dist/resources/extensions/gsd/prompt-validation.js +67 -0
  43. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
  44. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
  46. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  47. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
  48. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
  49. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
  50. package/dist/resources/extensions/gsd/prompts/queue.md +2 -0
  51. package/dist/resources/extensions/gsd/prompts/rethink.md +2 -1
  52. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
  53. package/dist/resources/extensions/gsd/quick.js +19 -15
  54. package/dist/resources/extensions/gsd/reactive-graph.js +12 -0
  55. package/dist/resources/extensions/gsd/roadmap-slices.js +24 -5
  56. package/dist/resources/extensions/gsd/safety/content-validator.js +3 -3
  57. package/dist/resources/extensions/gsd/session-lock.js +23 -1
  58. package/dist/resources/extensions/gsd/state.js +115 -28
  59. package/dist/resources/extensions/gsd/templates/context-enhanced.md +138 -0
  60. package/dist/resources/extensions/gsd/tools/complete-milestone.js +15 -3
  61. package/dist/resources/extensions/gsd/tools/complete-slice.js +27 -6
  62. package/dist/resources/extensions/gsd/tools/complete-task.js +31 -7
  63. package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -5
  64. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +5 -2
  65. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +119 -0
  66. package/dist/resources/extensions/gsd/tools/reopen-slice.js +30 -0
  67. package/dist/resources/extensions/gsd/tools/reopen-task.js +18 -0
  68. package/dist/resources/extensions/gsd/triage-resolution.js +33 -16
  69. package/dist/resources/extensions/gsd/undo.js +3 -2
  70. package/dist/resources/extensions/gsd/workflow-events.js +1 -0
  71. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  72. package/dist/resources/extensions/gsd/workflow-projections.js +7 -9
  73. package/dist/resources/extensions/gsd/workflow-reconcile.js +100 -9
  74. package/dist/resources/extensions/gsd/workflow-templates.js +11 -2
  75. package/dist/resources/extensions/gsd/worktree-manager.js +5 -2
  76. package/dist/resources/extensions/gsd/worktree.js +9 -0
  77. package/dist/resources/extensions/shared/interview-ui.js +1 -1
  78. package/dist/web/standalone/.next/BUILD_ID +1 -1
  79. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  80. package/dist/web/standalone/.next/build-manifest.json +3 -3
  81. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  82. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  84. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/index.html +1 -1
  100. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  107. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  110. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  111. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  112. package/dist/web/standalone/.next/static/chunks/6502.8874bcae249c02e1.js +9 -0
  113. package/dist/web/standalone/.next/static/chunks/{webpack-a1c1e452c6b32d04.js → webpack-9fed74684e1c5bb1.js} +1 -1
  114. package/package.json +1 -1
  115. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/retry-handler.js +30 -19
  117. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +51 -0
  119. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/sdk.js +9 -9
  121. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +10 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +20 -5
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +15 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +18 -0
  133. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -0
  136. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  137. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +80 -0
  138. package/packages/pi-coding-agent/src/core/retry-handler.ts +37 -25
  139. package/packages/pi-coding-agent/src/core/sdk.ts +9 -9
  140. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +10 -0
  141. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +20 -4
  142. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +27 -0
  143. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +16 -1
  144. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
  145. package/packages/pi-tui/dist/components/image.d.ts +2 -0
  146. package/packages/pi-tui/dist/components/image.d.ts.map +1 -1
  147. package/packages/pi-tui/dist/components/image.js +4 -0
  148. package/packages/pi-tui/dist/components/image.js.map +1 -1
  149. package/packages/pi-tui/dist/components/image.test.d.ts +6 -0
  150. package/packages/pi-tui/dist/components/image.test.d.ts.map +1 -0
  151. package/packages/pi-tui/dist/components/image.test.js +32 -0
  152. package/packages/pi-tui/dist/components/image.test.js.map +1 -0
  153. package/packages/pi-tui/src/components/image.test.ts +36 -0
  154. package/packages/pi-tui/src/components/image.ts +5 -0
  155. package/src/resources/extensions/browser-tools/capture.ts +19 -1
  156. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
  157. package/src/resources/extensions/gsd/auto/run-unit.ts +12 -2
  158. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  159. package/src/resources/extensions/gsd/auto-dispatch.ts +110 -9
  160. package/src/resources/extensions/gsd/auto-model-selection.ts +7 -5
  161. package/src/resources/extensions/gsd/auto-post-unit.ts +16 -6
  162. package/src/resources/extensions/gsd/auto-prompts.ts +31 -0
  163. package/src/resources/extensions/gsd/auto-recovery.ts +29 -23
  164. package/src/resources/extensions/gsd/auto-start.ts +45 -10
  165. package/src/resources/extensions/gsd/auto-tool-tracking.ts +10 -0
  166. package/src/resources/extensions/gsd/auto-worktree.ts +28 -7
  167. package/src/resources/extensions/gsd/auto.ts +19 -8
  168. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +16 -4
  169. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +10 -0
  170. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +5 -4
  171. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -1
  172. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -3
  173. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +36 -1
  174. package/src/resources/extensions/gsd/commands/context.ts +7 -1
  175. package/src/resources/extensions/gsd/commands/handlers/core.ts +23 -0
  176. package/src/resources/extensions/gsd/commands-extensions.ts +1 -1
  177. package/src/resources/extensions/gsd/config-overlay.ts +331 -0
  178. package/src/resources/extensions/gsd/db-writer.ts +11 -3
  179. package/src/resources/extensions/gsd/detection.ts +1 -1
  180. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
  181. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  182. package/src/resources/extensions/gsd/doctor.ts +2 -1
  183. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  184. package/src/resources/extensions/gsd/gsd-db.ts +46 -4
  185. package/src/resources/extensions/gsd/guided-flow.ts +254 -30
  186. package/src/resources/extensions/gsd/index.ts +1 -0
  187. package/src/resources/extensions/gsd/json-persistence.ts +6 -3
  188. package/src/resources/extensions/gsd/md-importer.ts +13 -6
  189. package/src/resources/extensions/gsd/parallel-orchestrator.ts +19 -11
  190. package/src/resources/extensions/gsd/pre-execution-checks.ts +15 -7
  191. package/src/resources/extensions/gsd/preferences-types.ts +25 -0
  192. package/src/resources/extensions/gsd/preferences-validation.ts +45 -1
  193. package/src/resources/extensions/gsd/preferences.ts +9 -2
  194. package/src/resources/extensions/gsd/preparation.ts +1419 -0
  195. package/src/resources/extensions/gsd/prompt-validation.ts +88 -0
  196. package/src/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
  197. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  198. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
  199. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  200. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
  201. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
  202. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
  203. package/src/resources/extensions/gsd/prompts/queue.md +2 -0
  204. package/src/resources/extensions/gsd/prompts/rethink.md +2 -1
  205. package/src/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
  206. package/src/resources/extensions/gsd/quick.ts +20 -15
  207. package/src/resources/extensions/gsd/reactive-graph.ts +18 -0
  208. package/src/resources/extensions/gsd/roadmap-slices.ts +21 -5
  209. package/src/resources/extensions/gsd/safety/content-validator.ts +3 -3
  210. package/src/resources/extensions/gsd/session-lock.ts +17 -1
  211. package/src/resources/extensions/gsd/state.ts +115 -26
  212. package/src/resources/extensions/gsd/templates/context-enhanced.md +138 -0
  213. package/src/resources/extensions/gsd/tests/adversarial-review-fixes.test.ts +223 -0
  214. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +33 -2
  215. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +56 -0
  216. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +41 -0
  217. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +72 -0
  218. package/src/resources/extensions/gsd/tests/complete-task-normalize-lists.test.ts +54 -0
  219. package/src/resources/extensions/gsd/tests/defer-milestone-stamp.test.ts +30 -0
  220. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -3
  221. package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +36 -0
  222. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +46 -0
  223. package/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts +33 -0
  224. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +37 -0
  225. package/src/resources/extensions/gsd/tests/error-success-mask.test.ts +37 -0
  226. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +48 -0
  227. package/src/resources/extensions/gsd/tests/frontmatter-parse-noise.test.ts +42 -0
  228. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +38 -0
  229. package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +103 -0
  230. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +42 -0
  231. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +11 -9
  232. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  233. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +28 -30
  234. package/src/resources/extensions/gsd/tests/integration/test-isolation.ts +53 -0
  235. package/src/resources/extensions/gsd/tests/integration-prepared-discussion.test.ts +525 -0
  236. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +62 -0
  237. package/src/resources/extensions/gsd/tests/needs-remediation-revalidation.test.ts +48 -0
  238. package/src/resources/extensions/gsd/tests/note-captures-executed.test.ts +46 -0
  239. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +77 -0
  240. package/src/resources/extensions/gsd/tests/phantom-ghost-detection.test.ts +55 -0
  241. package/src/resources/extensions/gsd/tests/phantom-milestone-default-queued.test.ts +39 -0
  242. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +68 -0
  243. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +218 -20
  244. package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +2 -2
  245. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +2 -2
  246. package/src/resources/extensions/gsd/tests/preparation.test.ts +1211 -0
  247. package/src/resources/extensions/gsd/tests/project-root-cwd-crash.test.ts +53 -0
  248. package/src/resources/extensions/gsd/tests/projection-no-plan-overwrite.test.ts +83 -0
  249. package/src/resources/extensions/gsd/tests/prompt-builder.test.ts +669 -0
  250. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +7 -4
  251. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +85 -0
  252. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -1
  253. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +47 -0
  254. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +107 -0
  255. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +45 -0
  256. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +63 -0
  257. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +4 -5
  258. package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +51 -0
  259. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +56 -0
  260. package/src/resources/extensions/gsd/tests/skip-slice-state-rebuild.test.ts +31 -0
  261. package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +39 -0
  262. package/src/resources/extensions/gsd/tests/slice-sequence-insert.test.ts +51 -0
  263. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +1 -1
  264. package/src/resources/extensions/gsd/tests/stale-lockfile-recovery.test.ts +36 -0
  265. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +147 -0
  266. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +13 -0
  267. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +21 -0
  268. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +21 -0
  269. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +6 -7
  270. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +47 -0
  271. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +1 -0
  272. package/src/resources/extensions/gsd/tests/symlink-extension-discovery.test.ts +125 -0
  273. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +65 -0
  274. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +29 -1
  275. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +2 -1
  276. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +3 -4
  277. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +15 -0
  278. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +89 -0
  279. package/src/resources/extensions/gsd/tests/wave1-critical-regressions.test.ts +49 -0
  280. package/src/resources/extensions/gsd/tests/wave2-events-regressions.test.ts +48 -0
  281. package/src/resources/extensions/gsd/tests/wave3-session-regressions.test.ts +47 -0
  282. package/src/resources/extensions/gsd/tests/wave4-write-safety-regressions.test.ts +70 -0
  283. package/src/resources/extensions/gsd/tests/wave5-consistency-regressions.test.ts +165 -0
  284. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +48 -0
  285. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +6 -3
  286. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +38 -0
  287. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +16 -0
  288. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +20 -0
  289. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +16 -17
  290. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +13 -9
  291. package/src/resources/extensions/gsd/tests/worktree.test.ts +26 -9
  292. package/src/resources/extensions/gsd/tests/write-gate.test.ts +127 -2
  293. package/src/resources/extensions/gsd/tests/zero-slice-roadmap-guided.test.ts +19 -0
  294. package/src/resources/extensions/gsd/tools/complete-milestone.ts +13 -3
  295. package/src/resources/extensions/gsd/tools/complete-slice.ts +26 -6
  296. package/src/resources/extensions/gsd/tools/complete-task.ts +29 -7
  297. package/src/resources/extensions/gsd/tools/plan-milestone.ts +11 -9
  298. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +5 -2
  299. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +152 -0
  300. package/src/resources/extensions/gsd/tools/reopen-slice.ts +27 -0
  301. package/src/resources/extensions/gsd/tools/reopen-task.ts +17 -0
  302. package/src/resources/extensions/gsd/triage-resolution.ts +37 -17
  303. package/src/resources/extensions/gsd/types.ts +4 -0
  304. package/src/resources/extensions/gsd/undo.ts +3 -2
  305. package/src/resources/extensions/gsd/workflow-events.ts +5 -3
  306. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  307. package/src/resources/extensions/gsd/workflow-projections.ts +7 -8
  308. package/src/resources/extensions/gsd/workflow-reconcile.ts +109 -8
  309. package/src/resources/extensions/gsd/workflow-templates.ts +11 -2
  310. package/src/resources/extensions/gsd/worktree-manager.ts +4 -2
  311. package/src/resources/extensions/gsd/worktree.ts +10 -0
  312. package/src/resources/extensions/shared/interview-ui.ts +1 -1
  313. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +8 -10
  314. package/dist/web/standalone/.next/static/chunks/6502.7593d7797a4b3999.js +0 -9
  315. /package/dist/web/standalone/.next/static/{qq3YfHPfyqvh3DIMVmsRH → JwdBI3y1H8vtBKiYvWfEK}/_buildManifest.js +0 -0
  316. /package/dist/web/standalone/.next/static/{qq3YfHPfyqvh3DIMVmsRH → JwdBI3y1H8vtBKiYvWfEK}/_ssgManifest.js +0 -0
@@ -0,0 +1,152 @@
1
+ // GSD — reopen-milestone tool handler
2
+
3
+ /**
4
+ * reopen-milestone handler — the core operation behind gsd_milestone_reopen.
5
+ *
6
+ * Resets a closed milestone back to "active", all of its slices to
7
+ * "in_progress", and all tasks to "pending". Cleans up stale filesystem
8
+ * artifacts so the DB-filesystem reconciler does not auto-correct
9
+ * entities back to "complete".
10
+ */
11
+
12
+ import {
13
+ getMilestone,
14
+ getMilestoneSlices,
15
+ getSliceTasks,
16
+ updateMilestoneStatus,
17
+ updateSliceStatus,
18
+ updateTaskStatus,
19
+ transaction,
20
+ } from "../gsd-db.js";
21
+ import { invalidateStateCache } from "../state.js";
22
+ import { isClosedStatus } from "../status-guards.js";
23
+ import { renderAllProjections } from "../workflow-projections.js";
24
+ import { writeManifest } from "../workflow-manifest.js";
25
+ import { appendEvent } from "../workflow-events.js";
26
+ import { logWarning } from "../workflow-logger.js";
27
+ import { debugLog } from "../debug-logger.js";
28
+ import { existsSync, unlinkSync } from "node:fs";
29
+ import { join } from "node:path";
30
+ import { resolveMilestonePath, resolveSlicePath, resolveTasksDir, clearPathCache } from "../paths.js";
31
+
32
+ export interface ReopenMilestoneParams {
33
+ milestoneId: string;
34
+ reason?: string;
35
+ /** Optional caller-provided identity for audit trail */
36
+ actorName?: string;
37
+ /** Optional caller-provided reason this action was triggered */
38
+ triggerReason?: string;
39
+ }
40
+
41
+ export interface ReopenMilestoneResult {
42
+ milestoneId: string;
43
+ slicesReset: number;
44
+ tasksReset: number;
45
+ }
46
+
47
+ export async function handleReopenMilestone(
48
+ params: ReopenMilestoneParams,
49
+ basePath: string,
50
+ ): Promise<ReopenMilestoneResult | { error: string }> {
51
+ // ── Validate required fields ────────────────────────────────────────────
52
+ if (!params.milestoneId || typeof params.milestoneId !== "string" || params.milestoneId.trim() === "") {
53
+ return { error: "milestoneId is required and must be a non-empty string" };
54
+ }
55
+
56
+ // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
57
+ let guardError: string | null = null;
58
+ let slicesResetCount = 0;
59
+ let tasksResetCount = 0;
60
+
61
+ transaction(() => {
62
+ const milestone = getMilestone(params.milestoneId);
63
+ if (!milestone) {
64
+ guardError = `milestone not found: ${params.milestoneId}`;
65
+ return;
66
+ }
67
+ if (!isClosedStatus(milestone.status)) {
68
+ guardError = `milestone ${params.milestoneId} is not closed (status: ${milestone.status}) — nothing to reopen`;
69
+ return;
70
+ }
71
+
72
+ updateMilestoneStatus(params.milestoneId, "active", null);
73
+
74
+ const slices = getMilestoneSlices(params.milestoneId);
75
+ slicesResetCount = slices.length;
76
+
77
+ for (const slice of slices) {
78
+ updateSliceStatus(params.milestoneId, slice.id, "in_progress");
79
+ const tasks = getSliceTasks(params.milestoneId, slice.id);
80
+ tasksResetCount += tasks.length;
81
+ for (const task of tasks) {
82
+ updateTaskStatus(params.milestoneId, slice.id, task.id, "pending");
83
+ }
84
+ }
85
+ });
86
+
87
+ if (guardError) {
88
+ return { error: guardError };
89
+ }
90
+
91
+ // ── Invalidate caches ────────────────────────────────────────────────────
92
+ invalidateStateCache();
93
+
94
+ // ── Clean up stale filesystem artifacts (M12 fix) ────────────────────────
95
+ // Without this, the DB-filesystem reconciler sees SUMMARY.md files and
96
+ // auto-corrects entities back to "complete", making reopen a no-op (#3161).
97
+ try {
98
+ const milestoneDir = resolveMilestonePath(basePath, params.milestoneId);
99
+ if (milestoneDir) {
100
+ const milestoneSummary = join(milestoneDir, `${params.milestoneId}-SUMMARY.md`);
101
+ if (existsSync(milestoneSummary)) unlinkSync(milestoneSummary);
102
+ }
103
+
104
+ const slices = getMilestoneSlices(params.milestoneId);
105
+ for (const slice of slices) {
106
+ const sliceDir = resolveSlicePath(basePath, params.milestoneId, slice.id);
107
+ if (sliceDir) {
108
+ const sliceSummary = join(sliceDir, `${slice.id}-SUMMARY.md`);
109
+ if (existsSync(sliceSummary)) unlinkSync(sliceSummary);
110
+ const sliceUat = join(sliceDir, `${slice.id}-UAT.md`);
111
+ if (existsSync(sliceUat)) unlinkSync(sliceUat);
112
+ }
113
+
114
+ const tasksDir = resolveTasksDir(basePath, params.milestoneId, slice.id);
115
+ if (tasksDir) {
116
+ const tasks = getSliceTasks(params.milestoneId, slice.id);
117
+ for (const task of tasks) {
118
+ const taskSummary = join(tasksDir, `${task.id}-SUMMARY.md`);
119
+ if (existsSync(taskSummary)) unlinkSync(taskSummary);
120
+ }
121
+ }
122
+ }
123
+ } catch (err) { debugLog("reopen-milestone-cleanup-failed", { milestoneId: params.milestoneId, error: String(err) }); }
124
+ clearPathCache();
125
+
126
+ // ── Post-mutation hook ───────────────────────────────────────────────────
127
+ try {
128
+ await renderAllProjections(basePath, params.milestoneId);
129
+ writeManifest(basePath);
130
+ appendEvent(basePath, {
131
+ cmd: "reopen-milestone",
132
+ params: {
133
+ milestoneId: params.milestoneId,
134
+ reason: params.reason ?? null,
135
+ slicesReset: slicesResetCount,
136
+ tasksReset: tasksResetCount,
137
+ },
138
+ ts: new Date().toISOString(),
139
+ actor: "agent",
140
+ actor_name: params.actorName,
141
+ trigger_reason: params.triggerReason,
142
+ });
143
+ } catch (hookErr) {
144
+ logWarning("tool", `reopen-milestone post-mutation hook warning: ${(hookErr as Error).message}`);
145
+ }
146
+
147
+ return {
148
+ milestoneId: params.milestoneId,
149
+ slicesReset: slicesResetCount,
150
+ tasksReset: tasksResetCount,
151
+ };
152
+ }
@@ -25,6 +25,9 @@ import { renderAllProjections } from "../workflow-projections.js";
25
25
  import { writeManifest } from "../workflow-manifest.js";
26
26
  import { appendEvent } from "../workflow-events.js";
27
27
  import { logWarning } from "../workflow-logger.js";
28
+ import { existsSync, unlinkSync } from "node:fs";
29
+ import { join } from "node:path";
30
+ import { resolveTasksDir, resolveSlicePath, clearPathCache } from "../paths.js";
28
31
 
29
32
  export interface ReopenSliceParams {
30
33
  milestoneId: string;
@@ -96,6 +99,30 @@ export async function handleReopenSlice(
96
99
  // ── Invalidate caches ────────────────────────────────────────────────────
97
100
  invalidateStateCache();
98
101
 
102
+ // ── Clean up stale filesystem artifacts (M12 fix) ────────────────────────
103
+ // Without this, the DB-filesystem reconciler sees SUMMARY.md files and
104
+ // auto-corrects tasks back to "complete", making reopen a no-op (#3161).
105
+ try {
106
+ const tasksDir = resolveTasksDir(basePath, params.milestoneId, params.sliceId);
107
+ if (tasksDir) {
108
+ const tasks = getSliceTasks(params.milestoneId, params.sliceId);
109
+ for (const task of tasks) {
110
+ const summaryPath = join(tasksDir, `${task.id}-SUMMARY.md`);
111
+ if (existsSync(summaryPath)) unlinkSync(summaryPath);
112
+ }
113
+ }
114
+ const sliceDir = resolveSlicePath(basePath, params.milestoneId, params.sliceId);
115
+ if (sliceDir) {
116
+ const sliceSummary = join(sliceDir, `${params.sliceId}-SUMMARY.md`);
117
+ if (existsSync(sliceSummary)) unlinkSync(sliceSummary);
118
+ const sliceUat = join(sliceDir, `${params.sliceId}-UAT.md`);
119
+ if (existsSync(sliceUat)) unlinkSync(sliceUat);
120
+ }
121
+ } catch (cleanupErr) {
122
+ logWarning("tool", `reopen-slice artifact cleanup warning: ${(cleanupErr as Error).message}`);
123
+ }
124
+ clearPathCache();
125
+
99
126
  // ── Post-mutation hook ───────────────────────────────────────────────────
100
127
  try {
101
128
  await renderAllProjections(basePath, params.milestoneId);
@@ -23,6 +23,9 @@ import { renderAllProjections } from "../workflow-projections.js";
23
23
  import { writeManifest } from "../workflow-manifest.js";
24
24
  import { appendEvent } from "../workflow-events.js";
25
25
  import { logWarning } from "../workflow-logger.js";
26
+ import { existsSync, unlinkSync } from "node:fs";
27
+ import { join } from "node:path";
28
+ import { resolveTasksDir, clearPathCache } from "../paths.js";
26
29
 
27
30
  export interface ReopenTaskParams {
28
31
  milestoneId: string;
@@ -100,6 +103,20 @@ export async function handleReopenTask(
100
103
  // ── Invalidate caches ────────────────────────────────────────────────────
101
104
  invalidateStateCache();
102
105
 
106
+ // ── Clean up stale filesystem artifacts (M12 fix) ────────────────────────
107
+ // Without this, the DB-filesystem reconciler sees the SUMMARY.md and
108
+ // auto-corrects the task back to "complete", making reopen a no-op (#3161).
109
+ try {
110
+ const tasksDir = resolveTasksDir(basePath, params.milestoneId, params.sliceId);
111
+ if (tasksDir) {
112
+ const summaryPath = join(tasksDir, `${params.taskId}-SUMMARY.md`);
113
+ if (existsSync(summaryPath)) unlinkSync(summaryPath);
114
+ }
115
+ } catch (cleanupErr) {
116
+ logWarning("tool", `reopen-task artifact cleanup warning: ${(cleanupErr as Error).message}`);
117
+ }
118
+ clearPathCache();
119
+
103
120
  // ── Post-mutation hook ───────────────────────────────────────────────────
104
121
  try {
105
122
  await renderAllProjections(basePath, params.milestoneId);
@@ -10,7 +10,8 @@
10
10
  * Also provides detectFileOverlap() for surfacing downstream impact on quick tasks.
11
11
  */
12
12
 
13
- import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
13
+ import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
14
+ import { atomicWriteSync } from "./atomic-write.js";
14
15
  import { join } from "node:path";
15
16
  import { createRequire } from "node:module";
16
17
  import { gsdRoot, milestonesDir } from "./paths.js";
@@ -65,10 +66,10 @@ export function executeInject(
65
66
  const filesSection = content.indexOf("## Files Likely Touched");
66
67
  if (filesSection !== -1) {
67
68
  const updated = content.slice(0, filesSection) + newTask + "\n\n" + content.slice(filesSection);
68
- writeFileSync(planPath, updated, "utf-8");
69
+ atomicWriteSync(planPath, updated, "utf-8");
69
70
  } else {
70
71
  // No Files section — append at end
71
- writeFileSync(planPath, content.trimEnd() + "\n\n" + newTask + "\n", "utf-8");
72
+ atomicWriteSync(planPath, content.trimEnd() + "\n\n" + newTask + "\n", "utf-8");
72
73
  }
73
74
 
74
75
  return newId;
@@ -105,7 +106,7 @@ export function executeReplan(
105
106
  `will detect it and enter the replanning-slice phase.`,
106
107
  ].join("\n");
107
108
 
108
- writeFileSync(triggerPath, content, "utf-8");
109
+ atomicWriteSync(triggerPath, content, "utf-8");
109
110
 
110
111
  // Also write replan_triggered_at column for DB-backed detection
111
112
  try {
@@ -183,7 +184,7 @@ export function executeBacktrack(
183
184
  `3. Resume auto-mode — the state machine will re-enter discussion for the target`,
184
185
  ].join("\n");
185
186
 
186
- writeFileSync(triggerPath, content, "utf-8");
187
+ atomicWriteSync(triggerPath, content, "utf-8");
187
188
 
188
189
  // If we have a valid target, also reset that milestone's completion status
189
190
  // so deriveState() will re-enter it as the active milestone.
@@ -194,7 +195,7 @@ export function executeBacktrack(
194
195
  // Write a regression marker so the state machine knows this milestone
195
196
  // needs re-discussion, not just re-execution
196
197
  const regressionPath = join(targetDir, `${targetMilestoneId}-REGRESSION.md`);
197
- writeFileSync(regressionPath, [
198
+ atomicWriteSync(regressionPath, [
198
199
  `# Milestone Regression`,
199
200
  ``,
200
201
  `**From:** ${currentMilestoneId}`,
@@ -361,7 +362,7 @@ export function ensureDeferMilestoneDir(
361
362
  ``,
362
363
  ].join("\n");
363
364
 
364
- writeFileSync(
365
+ atomicWriteSync(
365
366
  join(msDir, `${targetMilestone}-CONTEXT-DRAFT.md`),
366
367
  draftContent,
367
368
  "utf-8",
@@ -479,15 +480,18 @@ export function executeTriageResolutions(
479
480
  }
480
481
  }
481
482
 
482
- // Also process deferred captures that target milestone IDs — create
483
- // milestone directories so deriveState() discovers them.
484
- const deferred = loadAllCaptures(basePath).filter(
485
- c => c.status === "resolved" && !c.executed && c.classification === "defer",
483
+ // Also process deferred and milestone-class captures (#3542).
484
+ // A defer/milestone capture's "action" is the triage decision itself —
485
+ // once classified and resolved, the capture is done. The target milestone
486
+ // picks up the work naturally from its planning context.
487
+ const deferrable = loadAllCaptures(basePath).filter(
488
+ c => c.status === "resolved" && !c.executed &&
489
+ (c.classification === "defer" || (c.classification as string) === "milestone"),
486
490
  );
487
- if (deferred.length > 0) {
488
- // Group deferred captures by target milestone
491
+ if (deferrable.length > 0) {
492
+ // Group captures that reference a specific milestone — create dirs as needed.
489
493
  const byMilestone = new Map<string, CaptureEntry[]>();
490
- for (const cap of deferred) {
494
+ for (const cap of deferrable) {
491
495
  const target = cap.resolution?.match(/\b(M\d{3}(?:-[a-z0-9]{6})?)\b/)?.[1];
492
496
  if (target) {
493
497
  const list = byMilestone.get(target) ?? [];
@@ -502,12 +506,28 @@ export function executeTriageResolutions(
502
506
  if (created) {
503
507
  result.deferredMilestones++;
504
508
  result.actions.push(`Created milestone ${milestoneId} for ${captures.length} deferred capture(s)`);
505
- for (const cap of captures) {
506
- markCaptureExecuted(basePath, cap.id);
507
- }
508
509
  }
509
510
  }
510
511
  }
512
+ // Stamp ALL defer/milestone captures as executed (#3542 gaps 1-3).
513
+ // Previously only captures that triggered dir creation were stamped.
514
+ // Captures without a milestone ID in resolution text, or targeting an
515
+ // existing directory, were silently dropped — never stamped.
516
+ for (const cap of deferrable) {
517
+ if (!cap.executed) {
518
+ markCaptureExecuted(basePath, cap.id);
519
+ }
520
+ }
521
+ }
522
+
523
+ // Mark note captures as executed — they're informational only, no action
524
+ // needed. Without this they stay in "resolved but not executed" limbo (#3578).
525
+ const notes = loadAllCaptures(basePath).filter(
526
+ c => c.status === "resolved" && !c.executed && c.classification === "note",
527
+ );
528
+ for (const cap of notes) {
529
+ markCaptureExecuted(basePath, cap.id);
530
+ result.actions.push(`Note acknowledged: ${cap.id} — "${cap.text}"`);
511
531
  }
512
532
 
513
533
  if (actionable.length === 0) return result;
@@ -453,6 +453,8 @@ export interface ParallelConfig {
453
453
  budget_ceiling?: number;
454
454
  merge_strategy: MergeStrategy;
455
455
  auto_merge: AutoMergeMode;
456
+ /** Optional model override for parallel milestone workers (e.g. "claude-haiku-4-5"). */
457
+ worker_model?: string;
456
458
  }
457
459
 
458
460
  // ─── Reactive Task Execution Types ───────────────────────────────────────
@@ -479,6 +481,8 @@ export interface ReactiveExecutionConfig {
479
481
  max_parallel: number;
480
482
  /** Isolation mode for parallel tasks within a slice. Currently only "same-tree" is supported. */
481
483
  isolation_mode: "same-tree";
484
+ /** Optional model override for subagents spawned during parallel execution. */
485
+ subagent_model?: string;
482
486
  }
483
487
 
484
488
  /** Per-slice reactive execution runtime state, persisted to disk. */
@@ -4,9 +4,10 @@
4
4
  // handleResetSlice: Reset a slice and all its tasks, re-rendering plan + roadmap.
5
5
 
6
6
  import type { ExtensionCommandContext, ExtensionAPI } from "@gsd/pi-coding-agent";
7
- import { existsSync, readFileSync, writeFileSync, unlinkSync, readdirSync } from "node:fs";
7
+ import { existsSync, readFileSync, unlinkSync, readdirSync } from "node:fs";
8
8
  import { join, basename } from "node:path";
9
9
  import { nativeRevertCommit, nativeRevertAbort } from "./native-git-bridge.js";
10
+ import { atomicWriteSync } from "./atomic-write.js";
10
11
  import { parseUnitId } from "./unit-id.js";
11
12
  import { deriveState } from "./state.js";
12
13
  import { invalidateAllCaches } from "./cache.js";
@@ -393,7 +394,7 @@ export function uncheckTaskInPlan(basePath: string, mid: string, sid: string, ti
393
394
  const regex = new RegExp(`^(\\s*-\\s*)\\[x\\](\\s*\\**${tid}\\**[:\\s])`, "mi");
394
395
  if (regex.test(content)) {
395
396
  content = content.replace(regex, "$1[ ]$2");
396
- writeFileSync(planFile, content, "utf-8");
397
+ atomicWriteSync(planFile, content);
397
398
  return true;
398
399
  }
399
400
  return false;
@@ -19,10 +19,11 @@ export function getSessionId(): string {
19
19
  // ─── Event Types ─────────────────────────────────────────────────────────
20
20
 
21
21
  export interface WorkflowEvent {
22
- cmd: string; // e.g. "complete_task"
22
+ v?: number; // schema version — omitted in v1 (legacy), 2 for current format
23
+ cmd: string; // e.g. "complete-task" (canonical: hyphens; legacy: underscores — both accepted by replay)
23
24
  params: Record<string, unknown>;
24
- ts: string; // ISO 8601
25
- hash: string; // content hash (hex, 16 chars)
25
+ ts: string; // ISO 8601
26
+ hash: string; // content hash (hex, 16 chars)
26
27
  actor: "agent" | "system";
27
28
  actor_name?: string; // e.g. "executor-agent-01" — caller-provided identity
28
29
  trigger_reason?: string; // e.g. "plan-phase complete" — caller-provided causation
@@ -46,6 +47,7 @@ export function appendEvent(
46
47
  .slice(0, 16);
47
48
 
48
49
  const fullEvent: WorkflowEvent = {
50
+ v: 2,
49
51
  ...event,
50
52
  hash,
51
53
  session_id: ENGINE_SESSION_ID,
@@ -295,7 +295,7 @@ function _sanitizeForAudit(entry: LogEntry): LogEntry {
295
295
  };
296
296
  if (entry.context) {
297
297
  // Allowlist: only persist known-safe structured keys
298
- const SAFE_KEYS = new Set(["fn", "tool", "mid", "sid", "tid", "worktree"]);
298
+ const SAFE_KEYS = new Set(["fn", "tool", "mid", "sid", "tid", "worktree", "id", "error", "count"]);
299
299
  const filtered: Record<string, string> = {};
300
300
  for (const [k, v] of Object.entries(entry.context)) {
301
301
  if (SAFE_KEYS.has(k)) {
@@ -16,6 +16,7 @@ import { atomicWriteSync } from "./atomic-write.js";
16
16
  import { join } from "node:path";
17
17
  import { mkdirSync, existsSync } from "node:fs";
18
18
  import { logWarning } from "./workflow-logger.js";
19
+ import { isClosedStatus } from "./status-guards.js";
19
20
  import { deriveState } from "./state.js";
20
21
  import type { GSDState } from "./types.js";
21
22
 
@@ -55,7 +56,7 @@ export function renderPlanContent(sliceRow: SliceRow, taskRows: TaskRow[]): stri
55
56
  lines.push("## Tasks");
56
57
 
57
58
  for (const task of taskRows) {
58
- const checkbox = task.status === "done" || task.status === "complete" ? "[x]" : "[ ]";
59
+ const checkbox = isClosedStatus(task.status) ? "[x]" : "[ ]";
59
60
  lines.push(`- ${checkbox} **${task.id}: ${task.title}** \u2014 ${task.description}`);
60
61
 
61
62
  // Estimate subline (always present if non-empty)
@@ -125,7 +126,7 @@ export function renderRoadmapContent(milestoneRow: MilestoneRow, sliceRows: Slic
125
126
  lines.push("|----|-------|------|---------|------|------------|");
126
127
 
127
128
  for (const slice of sliceRows) {
128
- const done = slice.status === "done" || slice.status === "complete" ? "\u2705" : "\u2B1C";
129
+ const done = isClosedStatus(slice.status) ? "\u2705" : "\u2B1C";
129
130
 
130
131
  // depends is already parsed to string[] by rowToSlice
131
132
  let depends = "\u2014";
@@ -370,12 +371,10 @@ export async function renderAllProjections(basePath: string, milestoneId: string
370
371
  const sliceRows = getMilestoneSlices(milestoneId);
371
372
 
372
373
  for (const slice of sliceRows) {
373
- // Render PLAN.md for each slice
374
- try {
375
- renderPlanProjection(basePath, milestoneId, slice.id);
376
- } catch (err) {
377
- logWarning("projection", `renderPlanProjection failed for ${milestoneId}/${slice.id}: ${(err as Error).message}`);
378
- }
374
+ // PLAN.md is rendered by the authoritative markdown-renderer.js in
375
+ // plan-slice/replan-slice tools. Do NOT overwrite it here — the simplified
376
+ // projection is missing key sections (Must-Haves, Verification, Files
377
+ // Likely Touched) and corrupts multi-line task descriptions (#3651).
379
378
 
380
379
  // Render SUMMARY.md for each completed task
381
380
  const taskRows = getSliceTasks(milestoneId, slice.id);
@@ -7,12 +7,20 @@ import {
7
7
  transaction,
8
8
  updateTaskStatus,
9
9
  updateSliceStatus,
10
+ updateMilestoneStatus,
10
11
  getSliceTasks,
12
+ insertMilestone,
13
+ _getAdapter,
14
+ getMilestoneSlices,
11
15
  insertVerificationEvidence,
12
16
  upsertDecision,
13
17
  openDatabase,
18
+ setTaskBlockerDiscovered,
14
19
  } from "./gsd-db.js";
15
20
  import { isClosedStatus } from "./status-guards.js";
21
+ import { invalidateStateCache } from "./state.js";
22
+ import { clearPathCache } from "./paths.js";
23
+ import { clearParseCache } from "./files.js";
16
24
  import { writeManifest } from "./workflow-manifest.js";
17
25
  import { atomicWriteSync } from "./atomic-write.js";
18
26
  import { acquireSyncLock, releaseSyncLock } from "./sync-lock.js";
@@ -73,7 +81,15 @@ function replayEvents(events: WorkflowEvent[]): void {
73
81
  transaction(() => {
74
82
  for (const event of events) {
75
83
  const p = event.params;
76
- switch (event.cmd) {
84
+ // Normalize cmd format: completion tools write hyphens ("complete-task"),
85
+ // legacy logs use underscores ("complete_task"). Accept both formats.
86
+ // Type guard: malformed event lines with non-string cmd are skipped.
87
+ if (typeof event.cmd !== "string") {
88
+ logWarning("reconcile", `Event with non-string cmd skipped: ${JSON.stringify(event.cmd)}`);
89
+ continue;
90
+ }
91
+ const cmd = event.cmd.replace(/-/g, "_");
92
+ switch (cmd) {
77
93
  case "complete_task": {
78
94
  const milestoneId = p["milestoneId"] as string;
79
95
  const sliceId = p["sliceId"] as string;
@@ -89,13 +105,11 @@ function replayEvents(events: WorkflowEvent[]): void {
89
105
  break;
90
106
  }
91
107
  case "report_blocker": {
92
- // report_blocker marks the task with blocker_discovered = 1
93
- // The DB helper updateTaskStatus doesn't handle blockers,
94
- // so we just update status to "blocked" as a best-effort replay.
95
108
  const milestoneId = p["milestoneId"] as string;
96
109
  const sliceId = p["sliceId"] as string;
97
110
  const taskId = p["taskId"] as string;
98
111
  updateTaskStatus(milestoneId, sliceId, taskId, "blocked");
112
+ setTaskBlockerDiscovered(milestoneId, sliceId, taskId, true);
99
113
  break;
100
114
  }
101
115
  case "record_verification": {
@@ -120,9 +134,66 @@ function replayEvents(events: WorkflowEvent[]): void {
120
134
  replaySliceComplete(milestoneId, sliceId, event.ts);
121
135
  break;
122
136
  }
137
+ case "complete_milestone": {
138
+ const milestoneId = p["milestoneId"] as string;
139
+ if (!milestoneId) break;
140
+ // Invariant check: only mark complete if all slices are closed.
141
+ // Without this guard, a reordered/partial event stream could close
142
+ // a milestone while work is still incomplete.
143
+ const mSlices = getMilestoneSlices(milestoneId);
144
+ const allClosed = mSlices.length === 0 || mSlices.every(s => isClosedStatus(s.status));
145
+ if (allClosed) {
146
+ updateMilestoneStatus(milestoneId, "complete", event.ts);
147
+ } else {
148
+ logWarning("reconcile", `Skipping complete_milestone replay for ${milestoneId}: not all slices are closed`);
149
+ }
150
+ break;
151
+ }
152
+ case "plan_milestone": {
153
+ // Replay milestone creation — uses INSERT OR IGNORE (gsd-db's insertMilestone is safe)
154
+ const mId = p["milestoneId"] as string;
155
+ if (mId) {
156
+ insertMilestone({ id: mId, title: (p["title"] as string) ?? mId });
157
+ }
158
+ break;
159
+ }
123
160
  case "plan_slice": {
124
- // plan_slice events are informational slice should already exist.
125
- // No DB mutation needed during replay (the slice was inserted at plan time).
161
+ // Replay slice creationstrict INSERT OR IGNORE to avoid overwriting
162
+ // progressed status. insertSlice() uses ON CONFLICT DO UPDATE which
163
+ // could downgrade a completed slice back to pending.
164
+ const milestoneId = p["milestoneId"] as string;
165
+ const sliceId = p["sliceId"] as string;
166
+ if (milestoneId && sliceId) {
167
+ const adapter = _getAdapter();
168
+ if (adapter) {
169
+ adapter.prepare(
170
+ `INSERT OR IGNORE INTO slices (milestone_id, id, title, status, created_at)
171
+ VALUES (:mid, :sid, :title, 'pending', :ts)`,
172
+ ).run({ ":mid": milestoneId, ":sid": sliceId, ":title": (p["title"] as string) ?? sliceId, ":ts": event.ts });
173
+ }
174
+ }
175
+ break;
176
+ }
177
+ case "plan_task": {
178
+ // Replay task creation — strict INSERT OR IGNORE to avoid overwriting
179
+ // progressed status. insertTask() uses ON CONFLICT DO UPDATE which
180
+ // could downgrade a done/in-progress task back to pending.
181
+ const milestoneId = p["milestoneId"] as string;
182
+ const sliceId = p["sliceId"] as string;
183
+ const taskId = p["taskId"] as string;
184
+ if (milestoneId && sliceId && taskId) {
185
+ const adapter = _getAdapter();
186
+ if (adapter) {
187
+ adapter.prepare(
188
+ `INSERT OR IGNORE INTO tasks (milestone_id, slice_id, id, title, status, created_at)
189
+ VALUES (:mid, :sid, :tid, :title, 'pending', :ts)`,
190
+ ).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId, ":title": (p["title"] as string) ?? taskId, ":ts": event.ts });
191
+ }
192
+ }
193
+ break;
194
+ }
195
+ case "replan_slice": {
196
+ // Informational — replan events don't mutate DB during replay
126
197
  break;
127
198
  }
128
199
  case "save_decision": {
@@ -140,7 +211,7 @@ function replayEvents(events: WorkflowEvent[]): void {
140
211
  break;
141
212
  }
142
213
  default:
143
- // Unknown commands are silently skipped during replay
214
+ logWarning("reconcile", `Unknown event cmd during replay: "${event.cmd}" — skipped`);
144
215
  break;
145
216
  }
146
217
  }
@@ -158,26 +229,42 @@ export function extractEntityKey(
158
229
  event: WorkflowEvent,
159
230
  ): { type: string; id: string } | null {
160
231
  const p = event.params;
232
+ // Normalize cmd format: accept both hyphens and underscores
233
+ if (typeof event.cmd !== "string") return null;
234
+ const cmd = event.cmd.replace(/-/g, "_");
161
235
 
162
- switch (event.cmd) {
236
+ switch (cmd) {
163
237
  case "complete_task":
164
238
  case "start_task":
165
239
  case "report_blocker":
166
240
  case "record_verification":
241
+ case "plan_task":
167
242
  return typeof p["taskId"] === "string"
168
243
  ? { type: "task", id: p["taskId"] }
169
244
  : null;
170
245
 
171
246
  case "complete_slice":
247
+ case "replan_slice":
172
248
  return typeof p["sliceId"] === "string"
173
249
  ? { type: "slice", id: p["sliceId"] }
174
250
  : null;
175
251
 
252
+ case "complete_milestone":
253
+ return typeof p["milestoneId"] === "string"
254
+ ? { type: "milestone", id: p["milestoneId"] }
255
+ : null;
256
+
176
257
  case "plan_slice":
177
258
  return typeof p["sliceId"] === "string"
178
259
  ? { type: "slice_plan", id: p["sliceId"] }
179
260
  : null;
180
261
 
262
+ case "complete_milestone":
263
+ case "plan_milestone":
264
+ return typeof p["milestoneId"] === "string"
265
+ ? { type: "milestone", id: p["milestoneId"] }
266
+ : null;
267
+
181
268
  case "save_decision":
182
269
  if (typeof p["scope"] === "string" && typeof p["decision"] === "string") {
183
270
  return { type: "decision", id: `${p["scope"]}:${p["decision"]}` };
@@ -360,6 +447,14 @@ function _reconcileWorktreeLogsInner(
360
447
  const merged = indexed.map(({ e }) => e);
361
448
 
362
449
  // Step 7: Write merged event log FIRST (so crash recovery can re-derive DB state)
450
+ // Guard: detect concurrent appendEvent calls between our read (step 1) and
451
+ // this rewrite. If the log grew, re-read and retry to avoid dropping events.
452
+ const preWriteEvents = readEvents(mainLogPath);
453
+ if (preWriteEvents.length > mainEvents.length) {
454
+ logWarning("reconcile", `Event log grew during reconcile (${mainEvents.length} → ${preWriteEvents.length}), retrying with fresh read`);
455
+ return _reconcileWorktreeLogsInner(mainBasePath, worktreeBasePath);
456
+ }
457
+
363
458
  const baseEvents = mainEvents.slice(0, forkPoint + 1);
364
459
  const mergedLog = baseEvents.concat(merged);
365
460
  const logContent = mergedLog.map((e) => JSON.stringify(e)).join("\n") + (mergedLog.length > 0 ? "\n" : "");
@@ -377,6 +472,12 @@ function _reconcileWorktreeLogsInner(
377
472
  logWarning("reconcile", "manifest write failed (non-fatal)", { error: (err as Error).message });
378
473
  }
379
474
 
475
+ // Step 10: Invalidate caches so deriveState() sees post-reconcile DB state.
476
+ // Use targeted invalidation (not invalidateAllCaches) to avoid wiping artifacts table.
477
+ invalidateStateCache();
478
+ clearPathCache();
479
+ clearParseCache();
480
+
380
481
  return { autoMerged: merged.length, conflicts: [] };
381
482
  }
382
483