gsd-pi 2.65.0 → 2.66.0-dev.1b4e601

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 (463) 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/finalize-timeout.js +2 -0
  5. package/dist/resources/extensions/gsd/auto/loop.js +2 -2
  6. package/dist/resources/extensions/gsd/auto/phases.js +48 -5
  7. package/dist/resources/extensions/gsd/auto/run-unit.js +13 -2
  8. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  9. package/dist/resources/extensions/gsd/auto/types.js +2 -0
  10. package/dist/resources/extensions/gsd/auto-dashboard.js +2 -1
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +99 -9
  12. package/dist/resources/extensions/gsd/auto-model-selection.js +7 -5
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -6
  14. package/dist/resources/extensions/gsd/auto-prompts.js +24 -0
  15. package/dist/resources/extensions/gsd/auto-recovery.js +40 -22
  16. package/dist/resources/extensions/gsd/auto-start.js +175 -12
  17. package/dist/resources/extensions/gsd/auto-tool-tracking.js +10 -0
  18. package/dist/resources/extensions/gsd/auto-worktree.js +29 -7
  19. package/dist/resources/extensions/gsd/auto.js +21 -15
  20. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -4
  21. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
  22. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +6 -4
  23. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -1
  24. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -3
  25. package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -1
  26. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +31 -1
  27. package/dist/resources/extensions/gsd/commands/context.js +8 -1
  28. package/dist/resources/extensions/gsd/commands/handlers/core.js +23 -2
  29. package/dist/resources/extensions/gsd/commands-extensions.js +1 -1
  30. package/dist/resources/extensions/gsd/config-overlay.js +312 -0
  31. package/dist/resources/extensions/gsd/db-writer.js +13 -3
  32. package/dist/resources/extensions/gsd/detection.js +1 -1
  33. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -1
  34. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  35. package/dist/resources/extensions/gsd/doctor.js +2 -1
  36. package/dist/resources/extensions/gsd/files.js +17 -0
  37. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  38. package/dist/resources/extensions/gsd/gsd-db.js +47 -4
  39. package/dist/resources/extensions/gsd/guided-flow.js +220 -29
  40. package/dist/resources/extensions/gsd/index.js +1 -1
  41. package/dist/resources/extensions/gsd/json-persistence.js +5 -2
  42. package/dist/resources/extensions/gsd/md-importer.js +14 -7
  43. package/dist/resources/extensions/gsd/notification-overlay.js +1 -1
  44. package/dist/resources/extensions/gsd/notification-widget.js +2 -1
  45. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +1 -1
  46. package/dist/resources/extensions/gsd/parallel-orchestrator.js +17 -11
  47. package/dist/resources/extensions/gsd/pre-execution-checks.js +26 -5
  48. package/dist/resources/extensions/gsd/preferences-types.js +3 -0
  49. package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
  50. package/dist/resources/extensions/gsd/preferences.js +9 -2
  51. package/dist/resources/extensions/gsd/preparation.js +1092 -0
  52. package/dist/resources/extensions/gsd/prompt-validation.js +67 -0
  53. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
  54. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  55. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
  56. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  57. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
  58. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
  59. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
  60. package/dist/resources/extensions/gsd/prompts/queue.md +2 -0
  61. package/dist/resources/extensions/gsd/prompts/rethink.md +2 -1
  62. package/dist/resources/extensions/gsd/prompts/system.md +2 -2
  63. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
  64. package/dist/resources/extensions/gsd/quick.js +19 -15
  65. package/dist/resources/extensions/gsd/reactive-graph.js +12 -0
  66. package/dist/resources/extensions/gsd/roadmap-slices.js +24 -5
  67. package/dist/resources/extensions/gsd/safety/content-validator.js +3 -3
  68. package/dist/resources/extensions/gsd/session-lock.js +23 -1
  69. package/dist/resources/extensions/gsd/state.js +115 -28
  70. package/dist/resources/extensions/gsd/templates/context-enhanced.md +138 -0
  71. package/dist/resources/extensions/gsd/tools/complete-milestone.js +15 -3
  72. package/dist/resources/extensions/gsd/tools/complete-slice.js +27 -6
  73. package/dist/resources/extensions/gsd/tools/complete-task.js +31 -7
  74. package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -5
  75. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +5 -2
  76. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +119 -0
  77. package/dist/resources/extensions/gsd/tools/reopen-slice.js +30 -0
  78. package/dist/resources/extensions/gsd/tools/reopen-task.js +18 -0
  79. package/dist/resources/extensions/gsd/triage-resolution.js +33 -16
  80. package/dist/resources/extensions/gsd/undo.js +3 -2
  81. package/dist/resources/extensions/gsd/workflow-events.js +1 -0
  82. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  83. package/dist/resources/extensions/gsd/workflow-projections.js +7 -9
  84. package/dist/resources/extensions/gsd/workflow-reconcile.js +100 -9
  85. package/dist/resources/extensions/gsd/workflow-templates.js +11 -2
  86. package/dist/resources/extensions/gsd/worktree-manager.js +5 -2
  87. package/dist/resources/extensions/gsd/worktree.js +9 -0
  88. package/dist/resources/extensions/shared/interview-ui.js +1 -1
  89. package/dist/resources/extensions/subagent/agents.js +19 -5
  90. package/dist/web/standalone/.next/BUILD_ID +1 -1
  91. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
  92. package/dist/web/standalone/.next/build-manifest.json +4 -4
  93. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  94. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  95. package/dist/web/standalone/.next/required-server-files.json +3 -3
  96. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  97. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  99. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  107. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  109. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  110. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  111. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  113. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  123. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  135. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  155. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  165. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  171. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  185. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  187. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  189. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  191. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  200. package/dist/web/standalone/.next/server/app/index.html +1 -1
  201. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  202. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  203. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  204. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  205. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  206. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  207. package/dist/web/standalone/.next/server/app/page.js +2 -2
  208. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  209. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
  210. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  211. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  212. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  213. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  214. package/dist/web/standalone/.next/server/middleware.js +2 -2
  215. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  216. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  217. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  218. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  219. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  220. package/dist/web/standalone/.next/static/chunks/6502.8874bcae249c02e1.js +9 -0
  221. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  222. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  223. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  224. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  225. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  226. package/dist/web/standalone/.next/static/chunks/{webpack-a1c1e452c6b32d04.js → webpack-9fed74684e1c5bb1.js} +1 -1
  227. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  228. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  229. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  230. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  231. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  232. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  233. package/dist/web/standalone/server.js +1 -1
  234. package/package.json +1 -1
  235. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  236. package/packages/pi-coding-agent/dist/core/retry-handler.js +30 -19
  237. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  238. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +51 -0
  239. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  240. package/packages/pi-coding-agent/dist/core/sdk.js +9 -9
  241. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  242. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -1
  243. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  244. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +10 -1
  245. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  246. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  247. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  248. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +20 -5
  249. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  250. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +15 -1
  251. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  252. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +18 -0
  253. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  254. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  255. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -0
  256. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  257. package/packages/pi-coding-agent/package.json +1 -1
  258. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +80 -0
  259. package/packages/pi-coding-agent/src/core/retry-handler.ts +37 -25
  260. package/packages/pi-coding-agent/src/core/sdk.ts +9 -9
  261. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +10 -0
  262. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +20 -4
  263. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +27 -0
  264. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +16 -1
  265. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
  266. package/packages/pi-tui/dist/components/image.d.ts +2 -0
  267. package/packages/pi-tui/dist/components/image.d.ts.map +1 -1
  268. package/packages/pi-tui/dist/components/image.js +4 -0
  269. package/packages/pi-tui/dist/components/image.js.map +1 -1
  270. package/packages/pi-tui/dist/components/image.test.d.ts +6 -0
  271. package/packages/pi-tui/dist/components/image.test.d.ts.map +1 -0
  272. package/packages/pi-tui/dist/components/image.test.js +32 -0
  273. package/packages/pi-tui/dist/components/image.test.js.map +1 -0
  274. package/packages/pi-tui/dist/tui.d.ts +1 -0
  275. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  276. package/packages/pi-tui/dist/tui.js +8 -2
  277. package/packages/pi-tui/dist/tui.js.map +1 -1
  278. package/packages/pi-tui/src/components/image.test.ts +36 -0
  279. package/packages/pi-tui/src/components/image.ts +5 -0
  280. package/packages/pi-tui/src/tui.ts +8 -2
  281. package/pkg/package.json +1 -1
  282. package/src/resources/extensions/browser-tools/capture.ts +19 -1
  283. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
  284. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +3 -0
  285. package/src/resources/extensions/gsd/auto/loop.ts +2 -2
  286. package/src/resources/extensions/gsd/auto/phases.ts +68 -3
  287. package/src/resources/extensions/gsd/auto/run-unit.ts +12 -2
  288. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  289. package/src/resources/extensions/gsd/auto/types.ts +5 -0
  290. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -1
  291. package/src/resources/extensions/gsd/auto-dispatch.ts +110 -9
  292. package/src/resources/extensions/gsd/auto-model-selection.ts +7 -5
  293. package/src/resources/extensions/gsd/auto-post-unit.ts +16 -6
  294. package/src/resources/extensions/gsd/auto-prompts.ts +31 -0
  295. package/src/resources/extensions/gsd/auto-recovery.ts +29 -23
  296. package/src/resources/extensions/gsd/auto-start.ts +188 -10
  297. package/src/resources/extensions/gsd/auto-tool-tracking.ts +10 -0
  298. package/src/resources/extensions/gsd/auto-worktree.ts +28 -7
  299. package/src/resources/extensions/gsd/auto.ts +19 -8
  300. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +16 -4
  301. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +10 -0
  302. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +5 -4
  303. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -1
  304. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -3
  305. package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -1
  306. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +36 -1
  307. package/src/resources/extensions/gsd/commands/context.ts +7 -1
  308. package/src/resources/extensions/gsd/commands/handlers/core.ts +26 -2
  309. package/src/resources/extensions/gsd/commands-extensions.ts +1 -1
  310. package/src/resources/extensions/gsd/config-overlay.ts +331 -0
  311. package/src/resources/extensions/gsd/db-writer.ts +11 -3
  312. package/src/resources/extensions/gsd/detection.ts +1 -1
  313. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
  314. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  315. package/src/resources/extensions/gsd/doctor.ts +2 -1
  316. package/src/resources/extensions/gsd/files.ts +19 -0
  317. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  318. package/src/resources/extensions/gsd/gsd-db.ts +46 -4
  319. package/src/resources/extensions/gsd/guided-flow.ts +254 -30
  320. package/src/resources/extensions/gsd/index.ts +1 -0
  321. package/src/resources/extensions/gsd/json-persistence.ts +6 -3
  322. package/src/resources/extensions/gsd/md-importer.ts +13 -6
  323. package/src/resources/extensions/gsd/notification-overlay.ts +1 -1
  324. package/src/resources/extensions/gsd/notification-widget.ts +2 -1
  325. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +1 -1
  326. package/src/resources/extensions/gsd/parallel-orchestrator.ts +19 -11
  327. package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -7
  328. package/src/resources/extensions/gsd/preferences-types.ts +25 -0
  329. package/src/resources/extensions/gsd/preferences-validation.ts +45 -1
  330. package/src/resources/extensions/gsd/preferences.ts +9 -2
  331. package/src/resources/extensions/gsd/preparation.ts +1419 -0
  332. package/src/resources/extensions/gsd/prompt-validation.ts +88 -0
  333. package/src/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
  334. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  335. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
  336. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  337. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
  338. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
  339. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
  340. package/src/resources/extensions/gsd/prompts/queue.md +2 -0
  341. package/src/resources/extensions/gsd/prompts/rethink.md +2 -1
  342. package/src/resources/extensions/gsd/prompts/system.md +2 -2
  343. package/src/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
  344. package/src/resources/extensions/gsd/quick.ts +20 -15
  345. package/src/resources/extensions/gsd/reactive-graph.ts +18 -0
  346. package/src/resources/extensions/gsd/roadmap-slices.ts +21 -5
  347. package/src/resources/extensions/gsd/safety/content-validator.ts +3 -3
  348. package/src/resources/extensions/gsd/session-lock.ts +17 -1
  349. package/src/resources/extensions/gsd/state.ts +115 -26
  350. package/src/resources/extensions/gsd/templates/context-enhanced.md +138 -0
  351. package/src/resources/extensions/gsd/tests/adversarial-review-fixes.test.ts +223 -0
  352. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +33 -2
  353. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +56 -0
  354. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +41 -0
  355. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +72 -0
  356. package/src/resources/extensions/gsd/tests/complete-task-normalize-lists.test.ts +54 -0
  357. package/src/resources/extensions/gsd/tests/defer-milestone-stamp.test.ts +30 -0
  358. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -3
  359. package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +36 -0
  360. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +46 -0
  361. package/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts +33 -0
  362. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +37 -0
  363. package/src/resources/extensions/gsd/tests/error-success-mask.test.ts +37 -0
  364. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +125 -0
  365. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +48 -0
  366. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +69 -0
  367. package/src/resources/extensions/gsd/tests/frontmatter-parse-noise.test.ts +42 -0
  368. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +38 -0
  369. package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +103 -0
  370. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +42 -0
  371. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +11 -9
  372. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  373. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +28 -30
  374. package/src/resources/extensions/gsd/tests/integration/test-isolation.ts +53 -0
  375. package/src/resources/extensions/gsd/tests/integration-prepared-discussion.test.ts +525 -0
  376. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +62 -0
  377. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +11 -10
  378. package/src/resources/extensions/gsd/tests/needs-remediation-revalidation.test.ts +48 -0
  379. package/src/resources/extensions/gsd/tests/note-captures-executed.test.ts +46 -0
  380. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +189 -0
  381. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +77 -0
  382. package/src/resources/extensions/gsd/tests/phantom-ghost-detection.test.ts +55 -0
  383. package/src/resources/extensions/gsd/tests/phantom-milestone-default-queued.test.ts +39 -0
  384. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +68 -0
  385. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +284 -20
  386. package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +2 -2
  387. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +2 -2
  388. package/src/resources/extensions/gsd/tests/preparation.test.ts +1211 -0
  389. package/src/resources/extensions/gsd/tests/project-root-cwd-crash.test.ts +53 -0
  390. package/src/resources/extensions/gsd/tests/projection-no-plan-overwrite.test.ts +83 -0
  391. package/src/resources/extensions/gsd/tests/prompt-builder.test.ts +669 -0
  392. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +7 -4
  393. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +85 -0
  394. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -1
  395. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +47 -0
  396. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +107 -0
  397. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +45 -0
  398. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +63 -0
  399. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +4 -5
  400. package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +51 -0
  401. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +56 -0
  402. package/src/resources/extensions/gsd/tests/skip-slice-state-rebuild.test.ts +31 -0
  403. package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +39 -0
  404. package/src/resources/extensions/gsd/tests/slice-sequence-insert.test.ts +51 -0
  405. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +1 -1
  406. package/src/resources/extensions/gsd/tests/stale-lockfile-recovery.test.ts +36 -0
  407. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +147 -0
  408. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +13 -0
  409. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +21 -0
  410. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +21 -0
  411. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +6 -7
  412. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +47 -0
  413. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +1 -0
  414. package/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts +47 -0
  415. package/src/resources/extensions/gsd/tests/symlink-extension-discovery.test.ts +125 -0
  416. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +65 -0
  417. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +29 -1
  418. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +2 -1
  419. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +3 -4
  420. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +15 -0
  421. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +89 -0
  422. package/src/resources/extensions/gsd/tests/wave1-critical-regressions.test.ts +49 -0
  423. package/src/resources/extensions/gsd/tests/wave2-events-regressions.test.ts +48 -0
  424. package/src/resources/extensions/gsd/tests/wave3-session-regressions.test.ts +47 -0
  425. package/src/resources/extensions/gsd/tests/wave4-write-safety-regressions.test.ts +70 -0
  426. package/src/resources/extensions/gsd/tests/wave5-consistency-regressions.test.ts +165 -0
  427. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +48 -0
  428. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +6 -3
  429. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +38 -0
  430. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +16 -0
  431. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +20 -0
  432. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +16 -17
  433. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +13 -9
  434. package/src/resources/extensions/gsd/tests/worktree.test.ts +26 -9
  435. package/src/resources/extensions/gsd/tests/write-gate.test.ts +127 -2
  436. package/src/resources/extensions/gsd/tests/zero-slice-roadmap-guided.test.ts +19 -0
  437. package/src/resources/extensions/gsd/tools/complete-milestone.ts +13 -3
  438. package/src/resources/extensions/gsd/tools/complete-slice.ts +26 -6
  439. package/src/resources/extensions/gsd/tools/complete-task.ts +29 -7
  440. package/src/resources/extensions/gsd/tools/plan-milestone.ts +11 -9
  441. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +5 -2
  442. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +152 -0
  443. package/src/resources/extensions/gsd/tools/reopen-slice.ts +27 -0
  444. package/src/resources/extensions/gsd/tools/reopen-task.ts +17 -0
  445. package/src/resources/extensions/gsd/triage-resolution.ts +37 -17
  446. package/src/resources/extensions/gsd/types.ts +4 -0
  447. package/src/resources/extensions/gsd/undo.ts +3 -2
  448. package/src/resources/extensions/gsd/workflow-events.ts +5 -3
  449. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  450. package/src/resources/extensions/gsd/workflow-projections.ts +7 -8
  451. package/src/resources/extensions/gsd/workflow-reconcile.ts +109 -8
  452. package/src/resources/extensions/gsd/workflow-templates.ts +11 -2
  453. package/src/resources/extensions/gsd/worktree-manager.ts +4 -2
  454. package/src/resources/extensions/gsd/worktree.ts +10 -0
  455. package/src/resources/extensions/shared/interview-ui.ts +1 -1
  456. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +8 -10
  457. package/src/resources/extensions/subagent/agents.ts +30 -6
  458. package/dist/web/standalone/.next/static/chunks/6502.7593d7797a4b3999.js +0 -9
  459. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  460. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  461. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  462. /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → fcV2z87tmOazTEreFWNdG}/_buildManifest.js +0 -0
  463. /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → fcV2z87tmOazTEreFWNdG}/_ssgManifest.js +0 -0
@@ -15,6 +15,7 @@ import type { PostUnitContext, PreVerificationOpts } from "../auto-post-unit.js"
15
15
  import {
16
16
  MAX_RECOVERY_CHARS,
17
17
  BUDGET_THRESHOLDS,
18
+ MAX_FINALIZE_TIMEOUTS,
18
19
  type PhaseResult,
19
20
  type IterationContext,
20
21
  type LoopState,
@@ -33,7 +34,7 @@ import { gsdRoot } from "../paths.js";
33
34
  import { atomicWriteSync } from "../atomic-write.js";
34
35
  import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
35
36
  import { writeUnitRuntimeRecord } from "../unit-runtime.js";
36
- import { withTimeout, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
37
+ import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
37
38
  import { getEligibleSlices } from "../slice-parallel-eligibility.js";
38
39
  import { startSliceParallel } from "../slice-parallel-orchestrator.js";
39
40
  import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
@@ -1427,6 +1428,7 @@ export async function runUnitPhase(
1427
1428
  export async function runFinalize(
1428
1429
  ic: IterationContext,
1429
1430
  iterData: IterationData,
1431
+ loopState: LoopState,
1430
1432
  sidecarItem?: SidecarItem,
1431
1433
  ): Promise<PhaseResult> {
1432
1434
  const { ctx, pi, s, deps } = ic;
@@ -1450,13 +1452,58 @@ export async function runFinalize(
1450
1452
  };
1451
1453
 
1452
1454
  // Pre-verification processing (commit, doctor, state rebuild, etc.)
1455
+ // Timeout guard: if postUnitPreVerification hangs (e.g., safety harness
1456
+ // deadlock, browser teardown hang, worktree sync stall), force-continue
1457
+ // after timeout so the auto-loop is not permanently frozen (#3757).
1458
+ //
1459
+ // On timeout, null out s.currentUnit so the timed-out task's late async
1460
+ // mutations are harmless — postUnitPreVerification guards all side effects
1461
+ // behind `if (s.currentUnit)`. The next iteration sets a fresh currentUnit.
1453
1462
  // Sidecar items use lightweight pre-verification opts
1454
1463
  const preVerificationOpts: PreVerificationOpts | undefined = sidecarItem
1455
1464
  ? sidecarItem.kind === "hook"
1456
1465
  ? { skipSettleDelay: true, skipWorktreeSync: true }
1457
1466
  : { skipSettleDelay: true }
1458
1467
  : undefined;
1459
- const preResult = await deps.postUnitPreVerification(postUnitCtx, preVerificationOpts);
1468
+ const preUnitSnapshot = s.currentUnit
1469
+ ? { type: s.currentUnit.type, id: s.currentUnit.id, startedAt: s.currentUnit.startedAt }
1470
+ : null;
1471
+ const preResultGuard = await withTimeout(
1472
+ deps.postUnitPreVerification(postUnitCtx, preVerificationOpts),
1473
+ FINALIZE_PRE_TIMEOUT_MS,
1474
+ "postUnitPreVerification",
1475
+ );
1476
+
1477
+ if (preResultGuard.timedOut) {
1478
+ // Detach session from the timed-out unit so late async completions
1479
+ // cannot mutate state for the next unit (#3757).
1480
+ s.currentUnit = null;
1481
+ loopState.consecutiveFinalizeTimeouts++;
1482
+ debugLog("autoLoop", {
1483
+ phase: "pre-verification-timeout",
1484
+ iteration: ic.iteration,
1485
+ unitType: iterData.unitType,
1486
+ unitId: iterData.unitId,
1487
+ consecutiveTimeouts: loopState.consecutiveFinalizeTimeouts,
1488
+ });
1489
+
1490
+ if (loopState.consecutiveFinalizeTimeouts >= MAX_FINALIZE_TIMEOUTS) {
1491
+ ctx.ui.notify(
1492
+ `postUnitPreVerification timed out ${loopState.consecutiveFinalizeTimeouts} consecutive times — stopping auto-mode to prevent budget waste`,
1493
+ "error",
1494
+ );
1495
+ await deps.stopAuto(ctx, pi, `${loopState.consecutiveFinalizeTimeouts} consecutive finalize timeouts`);
1496
+ return { action: "break", reason: "finalize-timeout-escalation" };
1497
+ }
1498
+
1499
+ ctx.ui.notify(
1500
+ `postUnitPreVerification timed out after ${FINALIZE_PRE_TIMEOUT_MS / 1000}s for ${iterData.unitType} ${iterData.unitId} (${loopState.consecutiveFinalizeTimeouts}/${MAX_FINALIZE_TIMEOUTS}) — continuing to next iteration`,
1501
+ "warning",
1502
+ );
1503
+ return { action: "next", data: undefined as void };
1504
+ }
1505
+
1506
+ const preResult = preResultGuard.value;
1460
1507
  if (preResult === "dispatched") {
1461
1508
  debugLog("autoLoop", {
1462
1509
  phase: "exit",
@@ -1525,14 +1572,29 @@ export async function runFinalize(
1525
1572
  );
1526
1573
 
1527
1574
  if (postResultGuard.timedOut) {
1575
+ // Detach session from the timed-out unit so late async completions
1576
+ // cannot mutate state for the next unit (#3757).
1577
+ s.currentUnit = null;
1578
+ loopState.consecutiveFinalizeTimeouts++;
1528
1579
  debugLog("autoLoop", {
1529
1580
  phase: "post-verification-timeout",
1530
1581
  iteration: ic.iteration,
1531
1582
  unitType: iterData.unitType,
1532
1583
  unitId: iterData.unitId,
1584
+ consecutiveTimeouts: loopState.consecutiveFinalizeTimeouts,
1533
1585
  });
1586
+
1587
+ if (loopState.consecutiveFinalizeTimeouts >= MAX_FINALIZE_TIMEOUTS) {
1588
+ ctx.ui.notify(
1589
+ `postUnitPostVerification timed out ${loopState.consecutiveFinalizeTimeouts} consecutive times — stopping auto-mode to prevent budget waste`,
1590
+ "error",
1591
+ );
1592
+ await deps.stopAuto(ctx, pi, `${loopState.consecutiveFinalizeTimeouts} consecutive finalize timeouts`);
1593
+ return { action: "break", reason: "finalize-timeout-escalation" };
1594
+ }
1595
+
1534
1596
  ctx.ui.notify(
1535
- `postUnitPostVerification timed out after ${FINALIZE_POST_TIMEOUT_MS / 1000}s for ${iterData.unitType} ${iterData.unitId} — continuing to next iteration`,
1597
+ `postUnitPostVerification timed out after ${FINALIZE_POST_TIMEOUT_MS / 1000}s for ${iterData.unitType} ${iterData.unitId} (${loopState.consecutiveFinalizeTimeouts}/${MAX_FINALIZE_TIMEOUTS}) — continuing to next iteration`,
1536
1598
  "warning",
1537
1599
  );
1538
1600
  return { action: "next", data: undefined as void };
@@ -1554,6 +1616,9 @@ export async function runFinalize(
1554
1616
  return { action: "break", reason: "step-wizard" };
1555
1617
  }
1556
1618
 
1619
+ // Both pre and post verification completed without timeout — reset counter
1620
+ loopState.consecutiveFinalizeTimeouts = 0;
1621
+
1557
1622
  return { action: "next", data: undefined as void };
1558
1623
  }
1559
1624
 
@@ -108,9 +108,19 @@ export async function runUnit(
108
108
  { triggerTurn: true },
109
109
  );
110
110
 
111
- // ── Await agent_end ──
111
+ // ── Await agent_end with absolute timeout (H4 fix) ──
112
+ // If supervision fails to resolve unitPromise within 30s, treat as cancelled.
113
+ // Without this, a crashed agent that never emits agent_end hangs the loop (#3161).
112
114
  debugLog("runUnit", { phase: "awaiting-agent-end", unitType, unitId });
113
- const result = await unitPromise;
115
+ const UNIT_HARD_TIMEOUT_MS = 30_000;
116
+ let unitTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
117
+ const timeoutResult = new Promise<UnitResult>((resolve) => {
118
+ unitTimeoutHandle = setTimeout(() => {
119
+ resolve({ status: "cancelled", errorContext: { message: "Unit hard timeout — supervision may have failed", category: "timeout", isTransient: true } });
120
+ }, UNIT_HARD_TIMEOUT_MS);
121
+ });
122
+ const result = await Promise.race([unitPromise, timeoutResult]);
123
+ if (unitTimeoutHandle) clearTimeout(unitTimeoutHandle);
114
124
  debugLog("runUnit", {
115
125
  phase: "agent-end-received",
116
126
  unitType,
@@ -138,6 +138,9 @@ export class AutoSession {
138
138
 
139
139
  // ── Dispatch circuit breakers ──────────────────────────────────────
140
140
  rewriteAttemptCount = 0;
141
+ /** Tracks consecutive bootstrap attempts that found phase === "complete".
142
+ * Moved from module-level to per-session so s.reset() clears it (#1348). */
143
+ consecutiveCompleteBootstraps = 0;
141
144
 
142
145
  // ── Metrics ──────────────────────────────────────────────────────────────
143
146
  autoStartTime = 0;
@@ -224,6 +227,7 @@ export class AutoSession {
224
227
  this.pendingQuickTasks = [];
225
228
  this.sidecarQueue = [];
226
229
  this.rewriteAttemptCount = 0;
230
+ this.consecutiveCompleteBootstraps = 0;
227
231
  this.lastToolInvocationError = null;
228
232
  this.isolationDegraded = false;
229
233
  this.milestoneMergedInPhases = false;
@@ -91,8 +91,13 @@ export interface IterationContext {
91
91
  export interface LoopState {
92
92
  recentUnits: Array<{ key: string; error?: string }>;
93
93
  stuckRecoveryAttempts: number;
94
+ /** Consecutive finalize timeout count — stops auto-mode after threshold. */
95
+ consecutiveFinalizeTimeouts: number;
94
96
  }
95
97
 
98
+ /** Max consecutive finalize timeouts before hard-stopping auto-mode. */
99
+ export const MAX_FINALIZE_TIMEOUTS = 3;
100
+
96
101
  export interface PreDispatchData {
97
102
  state: GSDState;
98
103
  mid: string;
@@ -16,6 +16,7 @@ import {
16
16
  resolveSliceFile,
17
17
  } from "./paths.js";
18
18
  import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
19
+ import { formatShortcut } from "./files.js";
19
20
  import { readFileSync, writeFileSync, existsSync } from "node:fs";
20
21
  import { execFileSync } from "node:child_process";
21
22
  import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
@@ -855,7 +856,7 @@ export function updateProgressWidget(
855
856
  // Hints line
856
857
  const hintParts: string[] = [];
857
858
  hintParts.push("esc pause");
858
- hintParts.push(process.platform === "darwin" ? "⌃⌥G dashboard" : "Ctrl+Alt+G dashboard");
859
+ hintParts.push(`${formatShortcut("Ctrl+Alt+G")} dashboard`);
859
860
  const hintStr = theme.fg("dim", hintParts.join(" | "));
860
861
  const commitStr = lastCommit
861
862
  ? theme.fg("dim", `${lastCommit.timeAgo} ago: ${commitMsg}`)
@@ -27,6 +27,7 @@ import {
27
27
  buildMilestoneFileName,
28
28
  buildSliceFileName,
29
29
  } from "./paths.js";
30
+ import { parseRoadmap } from "./parsers-legacy.js";
30
31
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
31
32
  import { logWarning, logError } from "./workflow-logger.js";
32
33
  import { join } from "node:path";
@@ -47,6 +48,7 @@ import {
47
48
  buildRewriteDocsPrompt,
48
49
  buildReactiveExecutePrompt,
49
50
  buildGateEvaluatePrompt,
51
+ buildParallelResearchSlicesPrompt,
50
52
  checkNeedsReassessment,
51
53
  checkNeedsRunUat,
52
54
  } from "./auto-prompts.js";
@@ -93,14 +95,22 @@ function missingSliceStop(mid: string, phase: string): DispatchAction {
93
95
  /**
94
96
  * Check for milestone slices missing SUMMARY files.
95
97
  * Returns array of missing slice IDs, or empty array if all present or DB unavailable.
98
+ *
99
+ * Excludes skipped slices (intentionally summary-less) and legacy-complete
100
+ * slices whose DB status is authoritative even without on-disk SUMMARY (#3620).
96
101
  */
97
102
  function findMissingSummaries(basePath: string, mid: string): string[] {
98
103
  if (!isDbAvailable()) return [];
99
- const sliceIds = getMilestoneSlices(mid).map(s => s.id);
100
- return sliceIds.filter(sid => {
101
- const summaryPath = resolveSliceFile(basePath, mid, sid, "SUMMARY");
102
- return !summaryPath || !existsSync(summaryPath);
103
- });
104
+ const slices = getMilestoneSlices(mid);
105
+ // Skipped slices never produce SUMMARYs; legacy-complete slices may lack them
106
+ const CLOSED_STATUSES = new Set(["skipped", "complete", "done"]);
107
+ return slices
108
+ .filter(s => !CLOSED_STATUSES.has(s.status))
109
+ .filter(s => {
110
+ const summaryPath = resolveSliceFile(basePath, mid, s.id, "SUMMARY");
111
+ return !summaryPath || !existsSync(summaryPath);
112
+ })
113
+ .map(s => s.id);
104
114
  }
105
115
 
106
116
  // ─── Rewrite Circuit Breaker ──────────────────────────────────────────────
@@ -130,6 +140,32 @@ export function setRewriteCount(basePath: string, count: number): void {
130
140
  writeFileSync(filePath, JSON.stringify({ count, updatedAt: new Date().toISOString() }) + "\n");
131
141
  }
132
142
 
143
+ // ─── Run-UAT dispatch counter (per-slice) ────────────────────────────────
144
+ // Caps run-uat dispatches to prevent infinite replay when verification
145
+ // commands fail before writing a verdict (#3624).
146
+ const MAX_UAT_ATTEMPTS = 3;
147
+
148
+ function uatCountPath(basePath: string, mid: string, sid: string): string {
149
+ return join(gsdRoot(basePath), "runtime", `uat-count-${mid}-${sid}.json`);
150
+ }
151
+
152
+ export function getUatCount(basePath: string, mid: string, sid: string): number {
153
+ try {
154
+ const data = JSON.parse(readFileSync(uatCountPath(basePath, mid, sid), "utf-8"));
155
+ return typeof data.count === "number" ? data.count : 0;
156
+ } catch {
157
+ return 0;
158
+ }
159
+ }
160
+
161
+ export function incrementUatCount(basePath: string, mid: string, sid: string): number {
162
+ const count = getUatCount(basePath, mid, sid) + 1;
163
+ const filePath = uatCountPath(basePath, mid, sid);
164
+ mkdirSync(join(gsdRoot(basePath), "runtime"), { recursive: true });
165
+ writeFileSync(filePath, JSON.stringify({ count, updatedAt: new Date().toISOString() }) + "\n");
166
+ return count;
167
+ }
168
+
133
169
  // ─── Helpers ─────────────────────────────────────────────────────────────
134
170
 
135
171
  /**
@@ -140,9 +176,9 @@ export function setRewriteCount(basePath: string, count: number): void {
140
176
  * @see https://github.com/gsd-build/gsd-2/issues/2931
141
177
  */
142
178
  export function isVerificationNotApplicable(value: string): boolean {
143
- const v = (value ?? "").toLowerCase().trim();
179
+ const v = (value ?? "").toLowerCase().trim().replace(/[.\s]+$/, "");
144
180
  if (!v || v === "none") return true;
145
- return /^(?:none[\s._-]*(?:required|needed|planned)?|n\/?a|not[\s._-]+(?:applicable|required|needed)|no[\s._-]+operational[\s\S]*)$/i.test(v);
181
+ return /^(?:none[\s._-]*(?:required|needed|planned)?|n\/?a|not[\s._-]+(?:applicable|required|needed|provided)|no[\s._-]+operational[\s\S]*)$/i.test(v);
146
182
  }
147
183
 
148
184
  // ─── Rules ────────────────────────────────────────────────────────────────
@@ -203,6 +239,16 @@ export const DISPATCH_RULES: DispatchRule[] = [
203
239
  const needsRunUat = await checkNeedsRunUat(basePath, mid, state, prefs);
204
240
  if (!needsRunUat) return null;
205
241
  const { sliceId, uatType } = needsRunUat;
242
+
243
+ // Cap run-uat dispatch attempts to prevent infinite replay (#3624)
244
+ const attempts = incrementUatCount(basePath, mid, sliceId);
245
+ if (attempts > MAX_UAT_ATTEMPTS) {
246
+ return {
247
+ action: "stop" as const,
248
+ reason: `run-uat for ${mid}/${sliceId} has been dispatched ${attempts - 1} times without producing a verdict. Verification commands may be broken — fix the UAT spec or manually write an ASSESSMENT verdict.`,
249
+ level: "warning" as const,
250
+ };
251
+ }
206
252
  const uatFile = resolveSliceFile(basePath, mid, sliceId, "UAT")!;
207
253
  const uatContent = await loadFile(uatFile);
208
254
  return {
@@ -366,6 +412,53 @@ export const DISPATCH_RULES: DispatchRule[] = [
366
412
  };
367
413
  },
368
414
  },
415
+ {
416
+ name: "planning (multiple slices need research) → parallel-research-slices",
417
+ match: async ({ state, mid, midTitle, basePath, prefs }) => {
418
+ if (state.phase !== "planning") return null;
419
+ if (prefs?.phases?.skip_research || prefs?.phases?.skip_slice_research) return null;
420
+
421
+ // Load roadmap to find all slices
422
+ const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
423
+ const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null;
424
+ if (!roadmapContent) return null;
425
+ const roadmap = parseRoadmap(roadmapContent);
426
+
427
+ // Find slices that need research (no RESEARCH file, dependencies done)
428
+ const milestoneResearchFile = resolveMilestoneFile(basePath, mid, "RESEARCH");
429
+ const researchReadySlices: Array<{ id: string; title: string }> = [];
430
+
431
+ for (const slice of roadmap.slices) {
432
+ if (slice.done) continue;
433
+ // Skip S01 when milestone research exists
434
+ if (milestoneResearchFile && slice.id === "S01") continue;
435
+ // Skip if already has research
436
+ if (resolveSliceFile(basePath, mid, slice.id, "RESEARCH")) continue;
437
+ // Skip if dependencies aren't done (check for SUMMARY files)
438
+ const depsComplete = (slice.depends ?? []).every((depId) =>
439
+ !!resolveSliceFile(basePath, mid, depId, "SUMMARY"),
440
+ );
441
+ if (!depsComplete) continue;
442
+
443
+ researchReadySlices.push({ id: slice.id, title: slice.title });
444
+ }
445
+
446
+ // Only dispatch parallel if 2+ slices are ready
447
+ if (researchReadySlices.length < 2) return null;
448
+
449
+ return {
450
+ action: "dispatch",
451
+ unitType: "research-slice",
452
+ unitId: `${mid}/parallel-research`,
453
+ prompt: await buildParallelResearchSlicesPrompt(
454
+ mid,
455
+ midTitle,
456
+ researchReadySlices,
457
+ basePath,
458
+ ),
459
+ };
460
+ },
461
+ },
369
462
  {
370
463
  name: "planning → plan-slice",
371
464
  match: async ({ state, mid, midTitle, basePath }) => {
@@ -674,13 +767,17 @@ export const DISPATCH_RULES: DispatchRule[] = [
674
767
  // Safety guard (#1703): verify the milestone produced implementation
675
768
  // artifacts (non-.gsd/ files). A milestone with only plan files and
676
769
  // zero implementation code should not be marked complete.
677
- if (!hasImplementationArtifacts(basePath)) {
770
+ const artifactCheck = hasImplementationArtifacts(basePath);
771
+ if (artifactCheck === "absent") {
678
772
  return {
679
773
  action: "stop",
680
774
  reason: `Cannot complete milestone ${mid}: no implementation files found outside .gsd/. The milestone has only plan files — actual code changes are required.`,
681
775
  level: "error",
682
776
  };
683
777
  }
778
+ if (artifactCheck === "unknown") {
779
+ logWarning("dispatch", `Implementation artifact check inconclusive for ${mid} — proceeding (git context unavailable)`);
780
+ }
684
781
 
685
782
  // Verification class compliance: if operational verification was planned,
686
783
  // ensure the validation output documents it before allowing completion.
@@ -693,6 +790,10 @@ export const DISPATCH_RULES: DispatchRule[] = [
693
790
  if (validationPath) {
694
791
  const validationContent = await loadFile(validationPath);
695
792
  if (validationContent) {
793
+ // Allow completion when validation was intentionally skipped by
794
+ // preference/budget profile (#3399, #3344).
795
+ const skippedByPreference = /skip(?:ped)?[\s\-]+(?:by|per|due to)\s+(?:preference|budget|profile)/i.test(validationContent);
796
+
696
797
  // Accept either the structured template format (table with MET/N/A/SATISFIED)
697
798
  // or prose evidence patterns the validation agent may emit.
698
799
  const structuredMatch =
@@ -700,7 +801,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
700
801
  (validationContent.includes("MET") || validationContent.includes("N/A") || validationContent.includes("SATISFIED"));
701
802
  const proseMatch =
702
803
  /[Oo]perational[\s\S]{0,500}?(?:✅|pass|verified|confirmed|met|complete|true|yes|addressed|covered|satisfied|partially|n\/a|not[\s-]+applicable)/i.test(validationContent);
703
- const hasOperationalCheck = structuredMatch || proseMatch;
804
+ const hasOperationalCheck = skippedByPreference || structuredMatch || proseMatch;
704
805
  if (!hasOperationalCheck) {
705
806
  return {
706
807
  action: "stop" as const,
@@ -246,11 +246,13 @@ export async function selectAndApplyModel(
246
246
  const ok = await pi.setModel(model, { persist: false });
247
247
  if (ok) {
248
248
  appliedModel = model;
249
- const fallbackNote = modelId === effectiveModelConfig.primary
250
- ? ""
251
- : ` (fallback from ${effectiveModelConfig.primary})`;
252
- const phase = unitPhaseLabel(unitType);
253
- ctx.ui.notify(`Model [${phase}]${routingTierLabel}: ${model.provider}/${model.id}${fallbackNote}`, "info");
249
+ if (verbose) {
250
+ const fallbackNote = modelId === effectiveModelConfig.primary
251
+ ? ""
252
+ : ` (fallback from ${effectiveModelConfig.primary})`;
253
+ const phase = unitPhaseLabel(unitType);
254
+ ctx.ui.notify(`Model [${phase}]${routingTierLabel}: ${model.provider}/${model.id}${fallbackNote}`, "info");
255
+ }
254
256
  break;
255
257
  } else {
256
258
  const nextModel = modelsToTry[modelsToTry.indexOf(modelId) + 1];
@@ -39,7 +39,7 @@ import {
39
39
  } from "./auto-recovery.js";
40
40
  import { regenerateIfMissing } from "./workflow-projections.js";
41
41
  import { syncStateToProjectRoot } from "./auto-worktree.js";
42
- import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
42
+ import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, updateSliceStatus, _getAdapter } from "./gsd-db.js";
43
43
  import { renderPlanCheckboxes } from "./markdown-renderer.js";
44
44
  import { consumeSignal } from "./session-status-io.js";
45
45
  import {
@@ -161,7 +161,14 @@ export function detectRogueFileWrites(
161
161
 
162
162
  const dbRow = getSlice(mid, sid);
163
163
  if (!dbRow || dbRow.status !== "complete") {
164
- rogues.push({ path: summaryPath, unitType, unitId });
164
+ // Auto-remediate: SUMMARY exists on disk but DB is stale — sync DB to
165
+ // match filesystem instead of reporting as rogue (#3633).
166
+ try {
167
+ updateSliceStatus(mid, sid, "complete", new Date().toISOString());
168
+ } catch {
169
+ // If DB update fails, fall back to rogue detection so the issue is visible
170
+ rogues.push({ path: summaryPath, unitType, unitId });
171
+ }
165
172
  }
166
173
  } else if (unitType === "plan-milestone") {
167
174
  if (!mid) return [];
@@ -582,11 +589,14 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
582
589
  "error",
583
590
  );
584
591
  } else if (!triggerArtifactVerified) {
585
- // #2883: If the artifact is missing because the tool invocation itself
586
- // failed (malformed/truncated JSON arguments), retrying will produce the
587
- // same failure. Pause auto-mode instead of entering a stuck retry loop.
592
+ // #2883/#3595: If the artifact is missing because the tool invocation
593
+ // failed (malformed JSON) or was skipped (queued user message), retrying
594
+ // will produce the same failure. Pause auto-mode instead of looping.
588
595
  if (s.lastToolInvocationError) {
589
- const errMsg = `Tool invocation failed for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Structured argument generation failed — pausing auto-mode.`;
596
+ const isUserSkip = /queued user message/i.test(s.lastToolInvocationError);
597
+ const errMsg = isUserSkip
598
+ ? `Tool skipped for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Queued user message interrupted the turn — pausing auto-mode.`
599
+ : `Tool invocation failed for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Structured argument generation failed — pausing auto-mode.`;
590
600
  debugLog("postUnit", { phase: "tool-invocation-error-pause", unitType: s.currentUnit.type, unitId: s.currentUnit.id, error: s.lastToolInvocationError });
591
601
  ctx.ui.notify(errMsg, "error");
592
602
  s.lastToolInvocationError = null;
@@ -858,6 +858,7 @@ export async function buildDiscussMilestonePrompt(mid: string, midTitle: string,
858
858
  inlinedTemplates: discussTemplates,
859
859
  structuredQuestionsAvailable: "true",
860
860
  commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
861
+ fastPathInstruction: "",
861
862
  });
862
863
 
863
864
  // If a CONTEXT-DRAFT.md exists, append it as seed material
@@ -1801,6 +1802,36 @@ const GATE_QUESTIONS: Record<string, { question: string; guidance: string }> = {
1801
1802
  },
1802
1803
  };
1803
1804
 
1805
+ export async function buildParallelResearchSlicesPrompt(
1806
+ mid: string,
1807
+ midTitle: string,
1808
+ slices: Array<{ id: string; title: string }>,
1809
+ basePath: string,
1810
+ ): Promise<string> {
1811
+ // Build individual research-slice prompts for each slice
1812
+ const subagentSections: string[] = [];
1813
+ for (const slice of slices) {
1814
+ const slicePrompt = await buildResearchSlicePrompt(mid, midTitle, slice.id, slice.title, basePath);
1815
+ subagentSections.push([
1816
+ `### ${slice.id}: ${slice.title}`,
1817
+ "",
1818
+ "Use this as the prompt for a `subagent` call (agent: `gsd-executor` or the default agent):",
1819
+ "",
1820
+ "```",
1821
+ slicePrompt,
1822
+ "```",
1823
+ ].join("\n"));
1824
+ }
1825
+
1826
+ return loadPrompt("parallel-research-slices", {
1827
+ mid,
1828
+ midTitle,
1829
+ sliceCount: String(slices.length),
1830
+ sliceList: slices.map((s) => `- **${s.id}**: ${s.title}`).join("\n"),
1831
+ subagentPrompts: subagentSections.join("\n\n---\n\n"),
1832
+ });
1833
+ }
1834
+
1804
1835
  export async function buildGateEvaluatePrompt(
1805
1836
  mid: string, midTitle: string, sid: string, sTitle: string,
1806
1837
  base: string,
@@ -9,6 +9,7 @@
9
9
 
10
10
  import type { ExtensionContext } from "@gsd/pi-coding-agent";
11
11
  import { parseUnitId } from "./unit-id.js";
12
+ import { appendEvent } from "./workflow-events.js";
12
13
  import { atomicWriteSync } from "./atomic-write.js";
13
14
  import { clearParseCache } from "./files.js";
14
15
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
@@ -60,13 +61,12 @@ export { resolveExpectedArtifactPath, diagnoseExpectedArtifact };
60
61
  * in the git history. Uses `git log --name-only` to inspect all commits on the
61
62
  * current branch that touch files outside `.gsd/`.
62
63
  *
63
- * Returns true if at least one non-`.gsd/` file was committed, false otherwise.
64
- * Non-fatal: returns true on git errors to avoid blocking the pipeline when
65
- * running outside a git repo (e.g., tests).
64
+ * Returns "present" if implementation files found, "absent" if only .gsd/ files,
65
+ * "unknown" if git is unavailable or check failed (callers decide how to handle).
66
66
  */
67
- export function hasImplementationArtifacts(basePath: string): boolean {
67
+ export function hasImplementationArtifacts(basePath: string): "present" | "absent" | "unknown" {
68
68
  try {
69
- // Verify we're in a git repo — fail open if not
69
+ // Verify we're in a git repo
70
70
  try {
71
71
  execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
72
72
  cwd: basePath,
@@ -75,7 +75,7 @@ export function hasImplementationArtifacts(basePath: string): boolean {
75
75
  });
76
76
  } catch (e) {
77
77
  logWarning("recovery", `git rev-parse check failed: ${(e as Error).message}`);
78
- return true;
78
+ return "unknown";
79
79
  }
80
80
 
81
81
  // Strategy: check `git diff --name-only` against the merge-base with the
@@ -85,19 +85,19 @@ export function hasImplementationArtifacts(basePath: string): boolean {
85
85
  const mainBranch = detectMainBranch(basePath);
86
86
  const changedFiles = getChangedFilesSinceBranch(basePath, mainBranch);
87
87
 
88
- // No files changed at all — fail open (could be detached HEAD, single-
88
+ // No files changed at all — unknown (could be detached HEAD, single-
89
89
  // commit repo, or other edge case where git diff returns nothing).
90
- if (changedFiles.length === 0) return true;
90
+ if (changedFiles.length === 0) return "unknown";
91
91
 
92
92
  // Filter out .gsd/ files — only implementation files count.
93
93
  // If every changed file is under .gsd/, the milestone produced no
94
94
  // implementation code (#1703).
95
95
  const implFiles = changedFiles.filter(f => !f.startsWith(".gsd/") && !f.startsWith(".gsd\\"));
96
- return implFiles.length > 0;
96
+ return implFiles.length > 0 ? "present" : "absent";
97
97
  } catch (e) {
98
- // Non-fatal — if git operations fail, don't block the pipeline
98
+ // Non-fatal — if git operations fail, return unknown so callers can decide
99
99
  logWarning("recovery", `implementation artifact check failed: ${(e as Error).message}`);
100
- return true;
100
+ return "unknown";
101
101
  }
102
102
  }
103
103
 
@@ -286,7 +286,7 @@ export function verifyExpectedArtifact(
286
286
  if (!hasCheckboxTask && !hasHeadingTask) return false;
287
287
  }
288
288
 
289
- // execute-task: DB status is authoritative. Fall back to heading-style plan
289
+ // execute-task: DB status is authoritative. Fall back to checked-checkbox
290
290
  // detection when the DB is unavailable (unmigrated projects).
291
291
  if (unitType === "execute-task") {
292
292
  const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
@@ -297,20 +297,22 @@ export function verifyExpectedArtifact(
297
297
  if (dbTask.status !== "complete" && dbTask.status !== "done") return false;
298
298
  } else if (!isDbAvailable()) {
299
299
  // LEGACY: Pre-migration fallback for projects without DB.
300
- // Fall back to plan heading check (format detection, not reconciliation).
301
- // Heading-style entries (### T01 --) count as verified because the
302
- // summary file existence (checked above) is the real signal.
300
+ // Require a CHECKED checkbox a bare heading or unchecked checkbox
301
+ // does not prove gsd_complete_task ran. Summary file on disk alone
302
+ // is not sufficient evidence (could be a rogue write) (#3607).
303
303
  const planAbs = resolveSliceFile(base, mid, sid, "PLAN");
304
304
  if (planAbs && existsSync(planAbs)) {
305
305
  const planContent = readFileSync(planAbs, "utf-8");
306
306
  const escapedTid = tid.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
307
- const hdRe = new RegExp(`^#{2,4}\\s+${escapedTid}\\s*(?:--|—|:)`, "m");
308
307
  const cbRe = new RegExp(`^- \\[[xX]\\] \\*\\*${escapedTid}:`, "m");
309
- if (!hdRe.test(planContent) && !cbRe.test(planContent)) return false;
308
+ if (!cbRe.test(planContent)) return false;
309
+ } else {
310
+ return false; // no plan file → cannot verify
310
311
  }
312
+ } else {
313
+ // DB available but task row not found — completion tool never ran (#3607)
314
+ return false;
311
315
  }
312
- // else: DB available but task not found — summary file exists (checked above),
313
- // so treat as verified (task may not be imported yet)
314
316
  }
315
317
  }
316
318
 
@@ -392,7 +394,7 @@ export function verifyExpectedArtifact(
392
394
  // A milestone with only .gsd/ plan files and zero implementation code is
393
395
  // not genuinely complete — the LLM wrote plan files but skipped actual work.
394
396
  if (unitType === "complete-milestone") {
395
- if (!hasImplementationArtifacts(base)) return false;
397
+ if (hasImplementationArtifacts(base) === "absent") return false;
396
398
  }
397
399
 
398
400
  return true;
@@ -429,11 +431,15 @@ export function writeBlockerPlaceholder(
429
431
  // re-derives the same unit indefinitely (#2531, #2653).
430
432
  if (isDbAvailable()) {
431
433
  const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
434
+ const ts = new Date().toISOString();
432
435
  if (unitType === "execute-task" && mid && sid && tid) {
433
- try { updateTaskStatus(mid, sid, tid, "complete", new Date().toISOString()); } catch (e) { logWarning("recovery", `updateTaskStatus failed during context exhaustion: ${e instanceof Error ? e.message : String(e)}`); }
436
+ try { updateTaskStatus(mid, sid, tid, "complete", ts); } catch (e) { logWarning("recovery", `updateTaskStatus failed during context exhaustion: ${e instanceof Error ? e.message : String(e)}`); }
437
+ // Append event so worktree reconciliation can replay this recovery completion
438
+ try { appendEvent(base, { cmd: "complete-task", params: { milestoneId: mid, sliceId: sid, taskId: tid }, ts, actor: "system", trigger_reason: "blocker-placeholder-recovery" }); } catch (e) { logWarning("recovery", `appendEvent failed for task recovery: ${e instanceof Error ? e.message : String(e)}`); }
434
439
  }
435
440
  if (unitType === "complete-slice" && mid && sid) {
436
- try { updateSliceStatus(mid, sid, "complete", new Date().toISOString()); } catch (e) { logWarning("recovery", `updateSliceStatus failed during context exhaustion: ${e instanceof Error ? e.message : String(e)}`); }
441
+ try { updateSliceStatus(mid, sid, "complete", ts); } catch (e) { logWarning("recovery", `updateSliceStatus failed during context exhaustion: ${e instanceof Error ? e.message : String(e)}`); }
442
+ try { appendEvent(base, { cmd: "complete-slice", params: { milestoneId: mid, sliceId: sid }, ts, actor: "system", trigger_reason: "blocker-placeholder-recovery" }); } catch (e) { logWarning("recovery", `appendEvent failed for slice recovery: ${e instanceof Error ? e.message : String(e)}`); }
437
443
  }
438
444
  }
439
445
 
@@ -495,7 +501,7 @@ export function reconcileMergeState(
495
501
  if (conflictedFiles.length === 0) {
496
502
  // All conflicts resolved — finalize the merge/squash commit
497
503
  try {
498
- const commitSha = nativeCommit(basePath, ""); // --no-edit equivalent: use empty message placeholder
504
+ const commitSha = nativeCommit(basePath, "chore(gsd): reconcile merge state");
499
505
  if (commitSha) {
500
506
  const mode = hasMergeHead ? "merge" : "squash commit";
501
507
  ctx.ui.notify(`Finalized leftover ${mode} from prior session.`, "info");