gsd-pi 2.62.1 → 2.63.0-dev.351157b

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 (399) hide show
  1. package/README.md +46 -134
  2. package/dist/cli.js +44 -6
  3. package/dist/help-text.js +4 -1
  4. package/dist/onboarding.js +15 -8
  5. package/dist/resource-loader.js +18 -3
  6. package/dist/resources/extensions/cmux/index.js +21 -12
  7. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
  8. package/dist/resources/extensions/gsd/auto/loop.js +12 -1
  9. package/dist/resources/extensions/gsd/auto/phases.js +133 -25
  10. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  11. package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
  12. package/dist/resources/extensions/gsd/auto-post-unit.js +47 -10
  13. package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
  14. package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
  15. package/dist/resources/extensions/gsd/auto-start.js +10 -21
  16. package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
  17. package/dist/resources/extensions/gsd/auto-verification.js +14 -3
  18. package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
  19. package/dist/resources/extensions/gsd/auto.js +19 -2
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +73 -60
  21. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
  22. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
  23. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
  24. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -1
  25. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
  26. package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
  27. package/dist/resources/extensions/gsd/constants.js +42 -0
  28. package/dist/resources/extensions/gsd/db-writer.js +72 -4
  29. package/dist/resources/extensions/gsd/forensics.js +20 -4
  30. package/dist/resources/extensions/gsd/gsd-db.js +64 -17
  31. package/dist/resources/extensions/gsd/guided-flow.js +19 -0
  32. package/dist/resources/extensions/gsd/metrics.js +27 -1
  33. package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
  34. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  35. package/dist/resources/extensions/gsd/preferences.js +7 -2
  36. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  38. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  39. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
  40. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  41. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  42. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  43. package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
  44. package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
  45. package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
  46. package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
  47. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
  48. package/dist/resources/extensions/gsd/state.js +75 -14
  49. package/dist/resources/extensions/gsd/status-guards.js +11 -0
  50. package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
  51. package/dist/resources/extensions/gsd/tools/complete-slice.js +43 -29
  52. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
  53. package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
  54. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
  55. package/dist/resources/extensions/gsd/workflow-logger.js +13 -8
  56. package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
  57. package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -1
  58. package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
  59. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
  60. package/dist/resources/extensions/mcp-client/auth.js +101 -0
  61. package/dist/resources/extensions/mcp-client/index.js +10 -1
  62. package/dist/resources/extensions/ollama/index.js +6 -12
  63. package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
  64. package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
  65. package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
  66. package/dist/resources/extensions/ollama/ollama-client.js +23 -32
  67. package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
  68. package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
  69. package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
  70. package/dist/web/standalone/.next/BUILD_ID +1 -1
  71. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  72. package/dist/web/standalone/.next/build-manifest.json +3 -3
  73. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  74. package/dist/web/standalone/.next/required-server-files.json +4 -4
  75. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  76. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  78. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  86. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  105. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  106. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  114. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  119. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  121. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  124. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  130. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  136. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  142. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  156. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  161. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  164. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  169. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  170. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +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.js.nft.json +1 -1
  176. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  181. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  184. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  187. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  189. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  191. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  193. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  194. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  196. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  197. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  200. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  201. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  202. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  203. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  204. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  205. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  206. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  207. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/app/index.html +1 -1
  209. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  210. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  211. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  212. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  213. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  214. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  215. package/dist/web/standalone/.next/server/app/page.js +2 -2
  216. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  217. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  218. package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
  219. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  220. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  221. package/dist/web/standalone/.next/server/middleware.js +2 -2
  222. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  223. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  224. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  225. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  226. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  227. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  228. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  229. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  230. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  231. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  232. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  233. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  234. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  235. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  236. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  237. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  238. package/dist/web/standalone/server.js +1 -1
  239. package/package.json +1 -1
  240. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  241. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  242. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  243. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  244. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  245. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  246. package/packages/pi-ai/dist/types.d.ts +16 -1
  247. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  248. package/packages/pi-ai/dist/types.js.map +1 -1
  249. package/packages/pi-ai/src/types.ts +18 -1
  250. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  251. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  252. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  253. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  254. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  255. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  256. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  257. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  258. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  259. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  260. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  261. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  262. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  263. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  264. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  265. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  266. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  267. package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
  268. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  269. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  270. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  271. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  272. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  273. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  274. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  275. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  276. package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
  277. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  278. package/packages/pi-coding-agent/package.json +1 -1
  279. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  280. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  281. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  282. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  283. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  284. package/packages/pi-coding-agent/src/core/model-registry.ts +2 -0
  285. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  286. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  287. package/packages/pi-coding-agent/src/core/sdk.ts +11 -0
  288. package/pkg/package.json +1 -1
  289. package/src/resources/extensions/cmux/index.ts +18 -12
  290. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  291. package/src/resources/extensions/gsd/auto/loop.ts +13 -1
  292. package/src/resources/extensions/gsd/auto/phases.ts +164 -40
  293. package/src/resources/extensions/gsd/auto/session.ts +9 -0
  294. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  295. package/src/resources/extensions/gsd/auto-post-unit.ts +56 -12
  296. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  297. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  298. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  299. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  300. package/src/resources/extensions/gsd/auto-verification.ts +14 -3
  301. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  302. package/src/resources/extensions/gsd/auto.ts +22 -1
  303. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +74 -60
  304. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  305. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  306. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  307. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -1
  308. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  309. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  310. package/src/resources/extensions/gsd/constants.ts +44 -0
  311. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  312. package/src/resources/extensions/gsd/forensics.ts +21 -5
  313. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  314. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  315. package/src/resources/extensions/gsd/metrics.ts +28 -1
  316. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  317. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  318. package/src/resources/extensions/gsd/preferences.ts +9 -2
  319. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  320. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  321. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  322. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  323. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  324. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  325. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  326. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  327. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  328. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  329. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  330. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  331. package/src/resources/extensions/gsd/state.ts +68 -12
  332. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  333. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  334. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  335. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  336. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  337. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  338. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  339. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  340. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  341. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  342. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  343. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  344. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  345. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  346. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  347. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  348. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  349. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  350. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  351. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  352. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  353. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  354. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  355. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  356. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  357. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  358. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  359. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  360. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  361. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  362. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  363. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  364. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  365. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +17 -41
  366. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  367. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  368. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  369. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  370. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  371. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  372. package/src/resources/extensions/gsd/tools/complete-slice.ts +44 -31
  373. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  374. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  375. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  376. package/src/resources/extensions/gsd/types.ts +44 -22
  377. package/src/resources/extensions/gsd/workflow-logger.ts +13 -8
  378. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  379. package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -1
  380. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  381. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  382. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  383. package/src/resources/extensions/mcp-client/index.ts +16 -1
  384. package/src/resources/extensions/ollama/index.ts +6 -14
  385. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  386. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  387. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  388. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  389. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  390. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  391. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  392. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  393. package/src/resources/extensions/ollama/types.ts +23 -0
  394. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  395. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  396. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  397. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  398. /package/dist/web/standalone/.next/static/{86gWhNPP3233lZ7KPwda7 → QmuF-eAbuU_2MQ03t38qr}/_buildManifest.js +0 -0
  399. /package/dist/web/standalone/.next/static/{86gWhNPP3233lZ7KPwda7 → QmuF-eAbuU_2MQ03t38qr}/_ssgManifest.js +0 -0
@@ -13,13 +13,17 @@ import { runUnit } from "./run-unit.js";
13
13
  import { debugLog } from "../debug-logger.js";
14
14
  import { PROJECT_FILES } from "../detection.js";
15
15
  import { MergeConflictError } from "../git-service.js";
16
- import { join, basename } from "node:path";
17
- import { existsSync, cpSync } from "node:fs";
16
+ import { join, basename, dirname, parse as parsePath } from "node:path";
17
+ import { existsSync, cpSync, readdirSync } from "node:fs";
18
18
  import { logWarning, logError } from "../workflow-logger.js";
19
19
  import { gsdRoot } from "../paths.js";
20
20
  import { atomicWriteSync } from "../atomic-write.js";
21
- import { verifyExpectedArtifact } from "../auto-recovery.js";
21
+ import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
22
22
  import { writeUnitRuntimeRecord } from "../unit-runtime.js";
23
+ import { withTimeout, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
24
+ import { getEligibleSlices } from "../slice-parallel-eligibility.js";
25
+ import { startSliceParallel } from "../slice-parallel-orchestrator.js";
26
+ import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
23
27
  // ─── generateMilestoneReport ──────────────────────────────────────────────────
24
28
  /**
25
29
  * Resolve the base path for milestone reports.
@@ -116,7 +120,7 @@ export async function runPreDispatch(ic, loopState) {
116
120
  ctx.ui.notify(`Pre-dispatch: ${healthGate.fixesApplied.join(", ")}`, "info");
117
121
  }
118
122
  if (!healthGate.proceed) {
119
- ctx.ui.notify(healthGate.reason ?? "Pre-dispatch health check failed.", "error");
123
+ ctx.ui.notify(healthGate.reason || "Pre-dispatch health check failed — run /gsd doctor for details.", "error");
120
124
  await deps.pauseAuto(ctx, pi);
121
125
  debugLog("autoLoop", { phase: "exit", reason: "health-gate-failed" });
122
126
  return { action: "break", reason: "health-gate-failed" };
@@ -142,6 +146,50 @@ export async function runPreDispatch(ic, loopState) {
142
146
  mid,
143
147
  statePhase: state.phase,
144
148
  });
149
+ // ── Slice-level parallelism gate (#2340) ─────────────────────────────
150
+ // When slice_parallel is enabled, check if multiple slices are eligible
151
+ // for parallel execution. If so, dispatch them in parallel and stop the
152
+ // sequential loop. Workers are spawned via slice-parallel-orchestrator.ts.
153
+ if (prefs?.slice_parallel?.enabled &&
154
+ mid &&
155
+ !process.env.GSD_PARALLEL_WORKER &&
156
+ isDbAvailable()) {
157
+ try {
158
+ const dbSlices = getMilestoneSlices(mid);
159
+ if (dbSlices.length > 0) {
160
+ const doneIds = new Set(dbSlices.filter(sl => sl.status === "complete" || sl.status === "done").map(sl => sl.id));
161
+ const sliceInputs = dbSlices.map(sl => ({
162
+ id: sl.id,
163
+ done: doneIds.has(sl.id),
164
+ depends: sl.depends ?? [],
165
+ }));
166
+ const eligible = getEligibleSlices(sliceInputs, doneIds);
167
+ if (eligible.length > 1) {
168
+ debugLog("autoLoop", {
169
+ phase: "slice-parallel-dispatch",
170
+ iteration: ic.iteration,
171
+ mid,
172
+ eligibleSlices: eligible.map(e => e.id),
173
+ });
174
+ ctx.ui.notify(`Slice-parallel: dispatching ${eligible.length} eligible slices for ${mid}.`, "info");
175
+ const result = await startSliceParallel(s.basePath, mid, eligible, { maxWorkers: prefs.slice_parallel.max_workers ?? 2 });
176
+ if (result.started.length > 0) {
177
+ ctx.ui.notify(`Slice-parallel: started ${result.started.length} worker(s): ${result.started.join(", ")}.`, "info");
178
+ await deps.stopAuto(ctx, pi, `Slice-parallel dispatched for ${mid}`);
179
+ return { action: "break", reason: "slice-parallel-dispatched" };
180
+ }
181
+ // Fall through to sequential if no workers started
182
+ }
183
+ }
184
+ }
185
+ catch (err) {
186
+ debugLog("autoLoop", {
187
+ phase: "slice-parallel-check-error",
188
+ error: err instanceof Error ? err.message : String(err),
189
+ });
190
+ // Non-fatal — fall through to sequential dispatch
191
+ }
192
+ }
145
193
  // ── Milestone transition ────────────────────────────────────────────
146
194
  if (mid && s.currentMilestoneId && mid !== s.currentMilestoneId) {
147
195
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "milestone-transition", data: { from: s.currentMilestoneId, to: mid } });
@@ -431,8 +479,15 @@ export async function runDispatch(ic, preData, loopState) {
431
479
  unitId,
432
480
  reason: stuckSignal.reason,
433
481
  });
482
+ const stuckDiag = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
483
+ const stuckRemediation = buildLoopRemediationSteps(unitType, unitId, s.basePath);
484
+ const stuckParts = [`Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}.`];
485
+ if (stuckDiag)
486
+ stuckParts.push(`Expected: ${stuckDiag}`);
487
+ if (stuckRemediation)
488
+ stuckParts.push(`To recover:\n${stuckRemediation}`);
489
+ ctx.ui.notify(stuckParts.join(" "), "error");
434
490
  await deps.stopAuto(ctx, pi, `Stuck: ${stuckSignal.reason}`);
435
- ctx.ui.notify(`Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}. The expected artifact was not written.`, "error");
436
491
  return { action: "break", reason: "stuck-detected" };
437
492
  }
438
493
  }
@@ -662,11 +717,40 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
662
717
  }
663
718
  const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
664
719
  const hasSrcDir = deps.existsSync(join(s.basePath, "src"));
665
- if (!hasProjectFile && !hasSrcDir) {
720
+ // Xcode bundles have project-specific names (*.xcodeproj, *.xcworkspace)
721
+ // that cannot be matched by exact filename — scan the directory by suffix.
722
+ let hasXcodeBundle = false;
723
+ try {
724
+ const entries = deps.existsSync(s.basePath) ? readdirSync(s.basePath) : [];
725
+ hasXcodeBundle = entries.some((e) => e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace"));
726
+ }
727
+ catch (err) {
728
+ debugLog("runUnitPhase", { phase: "xcode-bundle-scan-failed", basePath: s.basePath, error: String(err) });
729
+ }
730
+ // Monorepo support (#2347): if no project files in the worktree directory,
731
+ // walk parent directories up to the filesystem root. In monorepos,
732
+ // package.json / Cargo.toml etc. live in a parent directory.
733
+ let hasProjectFileInParent = false;
734
+ if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle) {
735
+ let checkDir = dirname(s.basePath);
736
+ const { root } = parsePath(checkDir);
737
+ while (checkDir !== root) {
738
+ // Stop at git repository boundary — ancestors above the repo root
739
+ // (e.g. ~ or /usr/local) may contain unrelated project files.
740
+ if (deps.existsSync(join(checkDir, ".git")))
741
+ break;
742
+ if (PROJECT_FILES.some((f) => deps.existsSync(join(checkDir, f)))) {
743
+ hasProjectFileInParent = true;
744
+ break;
745
+ }
746
+ checkDir = dirname(checkDir);
747
+ }
748
+ }
749
+ if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle && !hasProjectFileInParent) {
666
750
  // Greenfield projects won't have project files yet — the first task creates them.
667
751
  // Log a warning but allow execution to proceed. The .git check above is sufficient
668
752
  // to ensure we're in a valid working directory.
669
- debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir });
753
+ debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir, hasXcodeBundle });
670
754
  ctx.ui.notify(`Warning: ${s.basePath} has no recognized project files — proceeding as greenfield project`, "warning");
671
755
  }
672
756
  }
@@ -676,6 +760,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
676
760
  s.currentUnit.id === unitId);
677
761
  const previousTier = s.currentUnitRouting?.tier;
678
762
  s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
763
+ s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
679
764
  const unitStartSeq = ic.nextSeq();
680
765
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
681
766
  deps.captureAvailableSkills();
@@ -688,18 +773,10 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
688
773
  lastProgressKind: "dispatch",
689
774
  recoveryAttempts: 0, // Reset so re-dispatched units get full recovery budget (#2322)
690
775
  });
691
- // Select and apply model (with tier escalation on retrynormal units only)
692
- const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier });
693
- s.currentUnitRouting =
694
- modelResult.routing;
695
- s.currentUnitModel =
696
- modelResult.appliedModel;
697
- // Status bar + progress widget
776
+ // Status bar (widget + preconditions deferred until after model selection see #2899)
698
777
  ctx.ui.setStatus("gsd-auto", "auto");
699
778
  if (mid)
700
779
  deps.updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
701
- deps.updateProgressWidget(ctx, unitType, unitId, state);
702
- deps.ensurePreconditions(unitType, unitId, s.basePath, state);
703
780
  // Prompt injection
704
781
  let finalPrompt = prompt;
705
782
  if (s.pendingVerificationRetry) {
@@ -757,6 +834,12 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
757
834
  const msg = reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
758
835
  logWarning("engine", "Prompt reorder failed", { error: msg });
759
836
  }
837
+ // Select and apply model (with tier escalation on retry — normal units only)
838
+ const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier });
839
+ s.currentUnitRouting =
840
+ modelResult.routing;
841
+ s.currentUnitModel =
842
+ modelResult.appliedModel;
760
843
  // Apply sidecar/pre-dispatch hook model override (takes priority over standard model selection)
761
844
  const hookModelOverride = sidecarItem?.model ?? iterData.hookModelOverride;
762
845
  if (hookModelOverride) {
@@ -777,6 +860,15 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
777
860
  `Ensure the model is defined in models.json and has auth configured.`, "warning");
778
861
  }
779
862
  }
863
+ // Store the final dispatched model ID so the dashboard can read it (#2899).
864
+ // This accounts for hook model overrides applied after selectAndApplyModel.
865
+ s.currentDispatchedModelId = s.currentUnitModel
866
+ ? `${s.currentUnitModel.provider ?? ""}/${s.currentUnitModel.id ?? ""}`
867
+ : null;
868
+ // Progress widget + preconditions — deferred to after model selection so the
869
+ // widget's first render tick shows the correct model (#2899).
870
+ deps.updateProgressWidget(ctx, unitType, unitId, state);
871
+ deps.ensurePreconditions(unitType, unitId, s.basePath, state);
780
872
  // Start unit supervision
781
873
  deps.clearUnitTimeout();
782
874
  deps.startUnitSupervision({
@@ -853,11 +945,13 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
853
945
  if (s.currentUnit) {
854
946
  await deps.closeoutUnit(ctx, s.basePath, unitType, unitId, s.currentUnit.startedAt, deps.buildSnapshotOpts(unitType, unitId));
855
947
  }
856
- // ── Zero tool-call guard (#1833) ──────────────────────────────────
857
- // An execute-task agent that completes with 0 tool calls made no
858
- // real changes its summary is hallucinated. Treat as failed so
859
- // the task is retried instead of silently marked complete.
860
- if (unitType === "execute-task") {
948
+ // ── Zero tool-call guard (#1833, #2653) ──────────────────────────
949
+ // Any unit that completes with 0 tool calls made no real progress —
950
+ // likely context exhaustion where all tool calls errored out. Treat
951
+ // as failed so the unit is retried in a fresh context instead of
952
+ // silently passing through to artifact verification (which loops
953
+ // forever when the unit never produced its artifact).
954
+ {
861
955
  const currentLedger = deps.getLedger();
862
956
  if (currentLedger?.units) {
863
957
  const lastUnit = [...currentLedger.units].reverse().find((u) => u.type === unitType && u.id === unitId && u.startedAt === s.currentUnit?.startedAt);
@@ -866,11 +960,11 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
866
960
  phase: "zero-tool-calls",
867
961
  unitType,
868
962
  unitId,
869
- warning: "Task completed with 0 tool calls — likely hallucinated, marking as failed",
963
+ warning: "Unit completed with 0 tool calls — likely context exhaustion, marking as failed",
870
964
  });
871
- ctx.ui.notify(`${unitType} ${unitId} completed with 0 tool calls — hallucinated summary, will retry`, "warning");
965
+ ctx.ui.notify(`${unitType} ${unitId} completed with 0 tool calls — context exhaustion, will retry`, "warning");
872
966
  // Fall through to next iteration where dispatch will re-derive
873
- // and re-dispatch this task.
967
+ // and re-dispatch this unit.
874
968
  return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt } };
875
969
  }
876
970
  }
@@ -986,7 +1080,21 @@ export async function runFinalize(ic, iterData, sidecarItem) {
986
1080
  }
987
1081
  }
988
1082
  // Post-verification processing (DB dual-write, hooks, triage, quick-tasks)
989
- const postResult = await deps.postUnitPostVerification(postUnitCtx);
1083
+ // Timeout guard: if postUnitPostVerification hangs (e.g., module import
1084
+ // deadlock, SQLite transaction hang), force-continue after timeout so the
1085
+ // auto-loop is not permanently frozen (#2344).
1086
+ const postResultGuard = await withTimeout(deps.postUnitPostVerification(postUnitCtx), FINALIZE_POST_TIMEOUT_MS, "postUnitPostVerification");
1087
+ if (postResultGuard.timedOut) {
1088
+ debugLog("autoLoop", {
1089
+ phase: "post-verification-timeout",
1090
+ iteration: ic.iteration,
1091
+ unitType: iterData.unitType,
1092
+ unitId: iterData.unitId,
1093
+ });
1094
+ ctx.ui.notify(`postUnitPostVerification timed out after ${FINALIZE_POST_TIMEOUT_MS / 1000}s for ${iterData.unitType} ${iterData.unitId} — continuing to next iteration`, "warning");
1095
+ return { action: "next", data: undefined };
1096
+ }
1097
+ const postResult = postResultGuard.value;
990
1098
  if (postResult === "stopped") {
991
1099
  debugLog("autoLoop", {
992
1100
  phase: "exit",
@@ -50,6 +50,8 @@ export class AutoSession {
50
50
  // ── Model state ──────────────────────────────────────────────────────────
51
51
  autoModeStartModel = null;
52
52
  currentUnitModel = null;
53
+ /** Fully-qualified model ID (provider/id) set after selectAndApplyModel + hook overrides (#2899). */
54
+ currentDispatchedModelId = null;
53
55
  originalModelId = null;
54
56
  originalModelProvider = null;
55
57
  lastBudgetAlertLevel = 0;
@@ -62,6 +64,10 @@ export class AutoSession {
62
64
  lastStateRebuildAt = 0;
63
65
  // ── Sidecar queue ─────────────────────────────────────────────────────
64
66
  sidecarQueue = [];
67
+ // ── Tool invocation errors (#2883) ──────────────────────────────────
68
+ /** Set when a GSD tool execution ends with isError due to malformed/truncated
69
+ * JSON arguments. Checked by postUnitPreVerification to break retry loops. */
70
+ lastToolInvocationError = null;
65
71
  // ── Isolation degradation ────────────────────────────────────────────
66
72
  /** Set to true when worktree creation fails; prevents merge of nonexistent branch. */
67
73
  isolationDegraded = false;
@@ -132,6 +138,7 @@ export class AutoSession {
132
138
  // Model
133
139
  this.autoModeStartModel = null;
134
140
  this.currentUnitModel = null;
141
+ this.currentDispatchedModelId = null;
135
142
  this.originalModelId = null;
136
143
  this.originalModelProvider = null;
137
144
  this.lastBudgetAlertLevel = 0;
@@ -149,6 +156,7 @@ export class AutoSession {
149
156
  this.pendingQuickTasks = [];
150
157
  this.sidecarQueue = [];
151
158
  this.rewriteAttemptCount = 0;
159
+ this.lastToolInvocationError = null;
152
160
  this.isolationDegraded = false;
153
161
  this.milestoneMergedInPhases = false;
154
162
  // Signal handler
@@ -516,9 +516,15 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
516
516
  const cxWindow = cxUsage?.contextWindow ?? cmdCtx?.model?.contextWindow ?? 0;
517
517
  const cxPctVal = cxUsage?.percent ?? 0;
518
518
  const cxPct = cxUsage?.percent !== null ? cxPctVal.toFixed(1) : "?";
519
- // Model display — shown in context section, not stats
520
- const modelId = cmdCtx?.model?.id ?? "";
521
- const modelProvider = cmdCtx?.model?.provider ?? "";
519
+ // Model display — prefer dispatched model ID (set after selectAndApplyModel
520
+ // + hook overrides) over cmdCtx?.model which can be stale (#2899).
521
+ const dispatchedModelId = accessors.getCurrentDispatchedModelId();
522
+ const modelId = dispatchedModelId
523
+ ? dispatchedModelId.split("/").slice(1).join("/") || dispatchedModelId
524
+ : (cmdCtx?.model?.id ?? "");
525
+ const modelProvider = dispatchedModelId
526
+ ? dispatchedModelId.split("/")[0] || ""
527
+ : (cmdCtx?.model?.provider ?? "");
522
528
  const tierIcon = resolveServiceTierIcon(effectiveServiceTier, modelId);
523
529
  const modelDisplay = (modelProvider && modelId
524
530
  ? `${modelProvider}/${modelId}`
@@ -19,7 +19,7 @@ import { invalidateAllCaches } from "./cache.js";
19
19
  import { parseUnitId } from "./unit-id.js";
20
20
  import { closeoutUnit } from "./auto-unit-closeout.js";
21
21
  import { autoCommitCurrentBranch, } from "./worktree.js";
22
- import { verifyExpectedArtifact, resolveExpectedArtifactPath, } from "./auto-recovery.js";
22
+ import { verifyExpectedArtifact, resolveExpectedArtifactPath, writeBlockerPlaceholder, diagnoseExpectedArtifact, } from "./auto-recovery.js";
23
23
  import { regenerateIfMissing } from "./workflow-projections.js";
24
24
  import { syncStateToProjectRoot } from "./auto-worktree.js";
25
25
  import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
@@ -29,6 +29,8 @@ import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookSta
29
29
  import { hasPendingCaptures, loadPendingCaptures, revertExecutorResolvedCaptures } from "./captures.js";
30
30
  import { debugLog } from "./debug-logger.js";
31
31
  import { runSafely } from "./auto-utils.js";
32
+ /** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */
33
+ const MAX_VERIFICATION_RETRIES = 3;
32
34
  /** Enqueue a sidecar item (hook, triage, or quick-task) for the main loop to
33
35
  * drain via runUnit. Logs the enqueue event and notifies the UI. */
34
36
  function enqueueSidecar(s, ctx, entry, debugExtra, notification) {
@@ -374,6 +376,8 @@ export async function postUnitPreVerification(pctx, opts) {
374
376
  // When artifact verification fails for a unit type that has a known expected
375
377
  // artifact, return "retry" so the caller re-dispatches with failure context
376
378
  // instead of blindly re-dispatching the same unit (#1571).
379
+ // After MAX_VERIFICATION_RETRIES, escalate to writeBlockerPlaceholder so the
380
+ // pipeline can advance instead of looping forever (#2653).
377
381
  //
378
382
  // HOWEVER, if the DB is unavailable (db_unavailable), the artifact was never
379
383
  // written because the completion tool failed at the infra level. Retrying
@@ -383,22 +387,55 @@ export async function postUnitPreVerification(pctx, opts) {
383
387
  // db_unavailable so the artifact was never written. Retrying would
384
388
  // produce an infinite re-dispatch loop (#2517).
385
389
  debugLog("postUnit", { phase: "artifact-verify-skip-db-unavailable", unitType: s.currentUnit.type, unitId: s.currentUnit.id });
386
- ctx.ui.notify(`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} but DB is unavailable — skipping retry to avoid loop (#2517)`, "error");
390
+ const dbSkipDiag = diagnoseExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
391
+ ctx.ui.notify(`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — DB unavailable, skipping retry.${dbSkipDiag ? ` Expected: ${dbSkipDiag}` : ""}`, "error");
387
392
  }
388
393
  else if (!triggerArtifactVerified) {
394
+ // #2883: If the artifact is missing because the tool invocation itself
395
+ // failed (malformed/truncated JSON arguments), retrying will produce the
396
+ // same failure. Pause auto-mode instead of entering a stuck retry loop.
397
+ if (s.lastToolInvocationError) {
398
+ const errMsg = `Tool invocation failed for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Structured argument generation failed — pausing auto-mode.`;
399
+ debugLog("postUnit", { phase: "tool-invocation-error-pause", unitType: s.currentUnit.type, unitId: s.currentUnit.id, error: s.lastToolInvocationError });
400
+ ctx.ui.notify(errMsg, "error");
401
+ s.lastToolInvocationError = null;
402
+ await pauseAuto(ctx, pi);
403
+ return "dispatched";
404
+ }
389
405
  const hasExpectedArtifact = resolveExpectedArtifactPath(s.currentUnit.type, s.currentUnit.id, s.basePath) !== null;
390
406
  if (hasExpectedArtifact) {
391
407
  const retryKey = `${s.currentUnit.type}:${s.currentUnit.id}`;
392
408
  const attempt = (s.verificationRetryCount.get(retryKey) ?? 0) + 1;
393
409
  s.verificationRetryCount.set(retryKey, attempt);
394
- s.pendingVerificationRetry = {
395
- unitId: s.currentUnit.id,
396
- failureContext: `Artifact verification failed: expected artifact for ${s.currentUnit.type} "${s.currentUnit.id}" was not found on disk after unit execution (attempt ${attempt}).`,
397
- attempt,
398
- };
399
- debugLog("postUnit", { phase: "artifact-verify-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, attempt });
400
- ctx.ui.notify(`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — retrying (attempt ${attempt})`, "warning");
401
- return "retry";
410
+ if (attempt > MAX_VERIFICATION_RETRIES) {
411
+ // Retries exhausted — write a blocker placeholder so the pipeline
412
+ // can advance past this stuck unit (#2653).
413
+ debugLog("postUnit", {
414
+ phase: "artifact-verify-escalate",
415
+ unitType: s.currentUnit.type,
416
+ unitId: s.currentUnit.id,
417
+ attempt,
418
+ maxRetries: MAX_VERIFICATION_RETRIES,
419
+ });
420
+ const reason = `Artifact verification failed after ${MAX_VERIFICATION_RETRIES} retries for ${s.currentUnit.type} "${s.currentUnit.id}".`;
421
+ writeBlockerPlaceholder(s.currentUnit.type, s.currentUnit.id, s.basePath, reason);
422
+ ctx.ui.notify(`${s.currentUnit.type} ${s.currentUnit.id} — verification retries exhausted (${MAX_VERIFICATION_RETRIES}), wrote blocker placeholder to advance pipeline`, "warning");
423
+ // Reset retry count and fall through to "continue" so the loop
424
+ // re-derives state with the placeholder in place.
425
+ s.verificationRetryCount.delete(retryKey);
426
+ s.pendingVerificationRetry = null;
427
+ // Do NOT return "retry" — fall through to "continue" below.
428
+ }
429
+ else {
430
+ s.pendingVerificationRetry = {
431
+ unitId: s.currentUnit.id,
432
+ failureContext: `Artifact verification failed: expected artifact for ${s.currentUnit.type} "${s.currentUnit.id}" was not found on disk after unit execution (attempt ${attempt}).`,
433
+ attempt,
434
+ };
435
+ debugLog("postUnit", { phase: "artifact-verify-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, attempt });
436
+ ctx.ui.notify(`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — retrying (attempt ${attempt})`, "warning");
437
+ return "retry";
438
+ }
402
439
  }
403
440
  }
404
441
  }
@@ -880,11 +880,16 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
880
880
  const contextRel = relMilestoneFile(base, mid, "CONTEXT");
881
881
  const milestoneResearchPath = resolveMilestoneFile(base, mid, "RESEARCH");
882
882
  const milestoneResearchRel = relMilestoneFile(base, mid, "RESEARCH");
883
+ const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
884
+ const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
883
885
  const inlined = [];
884
886
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
885
887
  const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
886
888
  if (contextInline)
887
889
  inlined.push(contextInline);
890
+ const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
891
+ if (sliceCtxInline)
892
+ inlined.push(sliceCtxInline);
888
893
  const researchInline = await inlineFileOptional(milestoneResearchPath, milestoneResearchRel, "Milestone Research");
889
894
  if (researchInline)
890
895
  inlined.push(researchInline);
@@ -931,12 +936,17 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
931
936
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
932
937
  const researchPath = resolveSliceFile(base, mid, sid, "RESEARCH");
933
938
  const researchRel = relSliceFile(base, mid, sid, "RESEARCH");
939
+ const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
940
+ const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
934
941
  const inlined = [];
935
942
  // Inject phase handoff anchor from research phase (if available)
936
943
  const researchSliceAnchor = readPhaseAnchor(base, mid, "research-slice");
937
944
  if (researchSliceAnchor)
938
945
  inlined.push(formatAnchorForPrompt(researchSliceAnchor));
939
946
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
947
+ const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
948
+ if (sliceCtxInline)
949
+ inlined.push(sliceCtxInline);
940
950
  const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research");
941
951
  if (researchInline)
942
952
  inlined.push(researchInline);
@@ -1097,8 +1107,13 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
1097
1107
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
1098
1108
  const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN");
1099
1109
  const slicePlanRel = relSliceFile(base, mid, sid, "PLAN");
1110
+ const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
1111
+ const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
1100
1112
  const inlined = [];
1101
1113
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
1114
+ const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
1115
+ if (sliceCtxInline)
1116
+ inlined.push(sliceCtxInline);
1102
1117
  inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Slice Plan"));
1103
1118
  if (inlineLevel !== "minimal") {
1104
1119
  const requirementsInline = await inlineRequirementsFromDb(base, sid, inlineLevel);
@@ -1351,8 +1366,13 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
1351
1366
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
1352
1367
  const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN");
1353
1368
  const slicePlanRel = relSliceFile(base, mid, sid, "PLAN");
1369
+ const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
1370
+ const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
1354
1371
  const inlined = [];
1355
1372
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
1373
+ const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
1374
+ if (sliceCtxInline)
1375
+ inlined.push(sliceCtxInline);
1356
1376
  inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Current Slice Plan"));
1357
1377
  // Find the blocker task summary — the completed task with blocker_discovered: true
1358
1378
  let blockerTaskId = "";
@@ -1454,8 +1474,13 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
1454
1474
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
1455
1475
  const summaryPath = resolveSliceFile(base, mid, completedSliceId, "SUMMARY");
1456
1476
  const summaryRel = relSliceFile(base, mid, completedSliceId, "SUMMARY");
1477
+ const sliceContextPath = resolveSliceFile(base, mid, completedSliceId, "CONTEXT");
1478
+ const sliceContextRel = relSliceFile(base, mid, completedSliceId, "CONTEXT");
1457
1479
  const inlined = [];
1458
1480
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Current Roadmap"));
1481
+ const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
1482
+ if (sliceCtxInline)
1483
+ inlined.push(sliceCtxInline);
1459
1484
  inlined.push(await inlineFile(summaryPath, summaryRel, `${completedSliceId} Summary`));
1460
1485
  if (inlineLevel !== "minimal") {
1461
1486
  const projectInline = await inlineProjectFromDb(base);
@@ -9,7 +9,7 @@
9
9
  import { parseUnitId } from "./unit-id.js";
10
10
  import { clearParseCache } from "./files.js";
11
11
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
12
- import { isDbAvailable, getTask, getSlice, getSliceTasks, updateTaskStatus } from "./gsd-db.js";
12
+ import { isDbAvailable, getTask, getSlice, getSliceTasks, updateTaskStatus, updateSliceStatus } from "./gsd-db.js";
13
13
  import { isValidationTerminal } from "./state.js";
14
14
  import { getErrorMessage } from "./error-utils.js";
15
15
  import { logWarning, logError } from "./workflow-logger.js";
@@ -377,17 +377,25 @@ export function writeBlockerPlaceholder(unitType, unitId, base, reason) {
377
377
  `Review and replace this file before relying on downstream artifacts.`,
378
378
  ].join("\n");
379
379
  writeFileSync(absPath, content, "utf-8");
380
- // Mark the task as complete in the DB so verifyExpectedArtifact passes.
380
+ // Mark the task/slice as complete in the DB so verifyExpectedArtifact passes.
381
381
  // Without this, the DB status stays "pending" and the dispatch loop
382
- // re-derives the same task indefinitely (#2531).
383
- if (unitType === "execute-task" && isDbAvailable()) {
382
+ // re-derives the same unit indefinitely (#2531, #2653).
383
+ if (isDbAvailable()) {
384
384
  const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
385
- if (mid && sid && tid) {
385
+ if (unitType === "execute-task" && mid && sid && tid) {
386
386
  try {
387
387
  updateTaskStatus(mid, sid, tid, "complete", new Date().toISOString());
388
388
  }
389
- catch (err) { /* non-fatal */
390
- logError("recovery", `DB status update failed: ${err instanceof Error ? err.message : String(err)}`);
389
+ catch (e) {
390
+ logWarning("recovery", `updateTaskStatus failed during context exhaustion: ${e instanceof Error ? e.message : String(e)}`);
391
+ }
392
+ }
393
+ if (unitType === "complete-slice" && mid && sid) {
394
+ try {
395
+ updateSliceStatus(mid, sid, "complete", new Date().toISOString());
396
+ }
397
+ catch (e) {
398
+ logWarning("recovery", `updateSliceStatus failed during context exhaustion: ${e instanceof Error ? e.message : String(e)}`);
391
399
  }
392
400
  }
393
401
  }
@@ -30,7 +30,7 @@ import { initRoutingHistory } from "./routing-history.js";
30
30
  import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
31
31
  import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
32
32
  import { snapshotSkills } from "./skill-discovery.js";
33
- import { isDbAvailable, getMilestone } from "./gsd-db.js";
33
+ import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
34
34
  import { hideFooter } from "./auto-dashboard.js";
35
35
  import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
36
36
  import { logWarning, logError } from "./workflow-logger.js";
@@ -48,34 +48,23 @@ import { resolveDefaultSessionModel } from "./preferences-models.js";
48
48
  * Returns false if the bootstrap aborted (e.g., guided flow returned,
49
49
  * concurrent session detected). Returns true when ready to dispatch.
50
50
  */
51
- /**
52
- * Open the project-root DB before the first deriveState call (#2841).
53
- * When auto-mode starts cold (no prior DB handle), state derivation that
54
- * touches DB-backed helpers (queue-order, task status) silently falls back
55
- * to markdown-only data, producing stale or incomplete state. Opening the
56
- * DB first ensures deriveState sees the full picture on its very first run.
57
- */
58
- async function openProjectDbIfPresent(basePath) {
51
+ /** Guard: tracks consecutive bootstrap attempts that found phase === "complete".
52
+ * Prevents the recursive dialog loop described in #1348 where
53
+ * bootstrapAutoSession showSmartEntry checkAutoStartAfterDiscuss startAuto
54
+ * cycles indefinitely when the discuss workflow doesn't produce a milestone. */
55
+ let _consecutiveCompleteBootstraps = 0;
56
+ const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
57
+ export async function openProjectDbIfPresent(basePath) {
59
58
  const gsdDbPath = resolveProjectRootDbPath(basePath);
60
- if (!existsSync(gsdDbPath))
61
- return;
62
- if (isDbAvailable())
59
+ if (!existsSync(gsdDbPath) || isDbAvailable())
63
60
  return;
64
61
  try {
65
- const { openDatabase } = await import("./gsd-db.js");
66
62
  openDatabase(gsdDbPath);
67
63
  }
68
64
  catch (err) {
69
- /* non-fatal DB lifecycle block below will retry */
70
- logWarning("engine", `DB open failed: ${err instanceof Error ? err.message : String(err)}`);
65
+ logWarning("engine", `gsd-db: failed to open existing database: ${err instanceof Error ? err.message : String(err)}`);
71
66
  }
72
67
  }
73
- /** Guard: tracks consecutive bootstrap attempts that found phase === "complete".
74
- * Prevents the recursive dialog loop described in #1348 where
75
- * bootstrapAutoSession → showSmartEntry → checkAutoStartAfterDiscuss → startAuto
76
- * cycles indefinitely when the discuss workflow doesn't produce a milestone. */
77
- let _consecutiveCompleteBootstraps = 0;
78
- const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
79
68
  export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, deps) {
80
69
  const { shouldUseWorktreeIsolation, registerSigtermHandler, lockBase, buildResolver, } = deps;
81
70
  const lockResult = acquireSessionLock(base);
@@ -75,3 +75,20 @@ export function hasInteractiveToolInFlight() {
75
75
  export function clearInFlightTools() {
76
76
  inFlightTools.clear();
77
77
  }
78
+ // ─── Tool invocation error classification (#2883) ────────────────────────
79
+ /**
80
+ * Patterns that indicate a tool invocation failed due to malformed or truncated
81
+ * JSON arguments — as opposed to a normal business-logic error from the tool
82
+ * handler. When these errors occur, retrying the same unit will produce the same
83
+ * failure, so the retry loop must be broken.
84
+ */
85
+ const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}' in JSON|Unexpected end of JSON|Unexpected token.*in JSON/i;
86
+ /**
87
+ * Returns true if the error message indicates a tool invocation failure due to
88
+ * malformed/truncated arguments (as opposed to a normal tool execution error).
89
+ */
90
+ export function isToolInvocationError(errorMsg) {
91
+ if (!errorMsg)
92
+ return false;
93
+ return TOOL_INVOCATION_ERROR_RE.test(errorMsg);
94
+ }
@@ -142,16 +142,27 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
142
142
  failureContext: formatFailureContext(result),
143
143
  attempt: nextAttempt,
144
144
  };
145
- ctx.ui.notify(`Verification failed auto-fix attempt ${nextAttempt}/${maxRetries}`, "warning");
145
+ const failedCmds = result.checks
146
+ .filter((c) => c.exitCode !== 0)
147
+ .map((c) => c.command);
148
+ const cmdSummary = failedCmds.length <= 3
149
+ ? failedCmds.join(", ")
150
+ : `${failedCmds.slice(0, 3).join(", ")}... and ${failedCmds.length - 3} more`;
151
+ ctx.ui.notify(`Verification failed (${cmdSummary}) — auto-fix attempt ${nextAttempt}/${maxRetries}`, "warning");
146
152
  // Return "retry" — the autoLoop while loop will re-iterate with the retry context
147
153
  return "retry";
148
154
  }
149
155
  else {
150
156
  // Gate failed, retries exhausted
151
- const exhaustedAttempt = attempt + 1;
152
157
  s.verificationRetryCount.delete(s.currentUnit.id);
153
158
  s.pendingVerificationRetry = null;
154
- ctx.ui.notify(`Verification gate FAILED after ${exhaustedAttempt > maxRetries ? exhaustedAttempt - 1 : exhaustedAttempt} retries — pausing for human review`, "error");
159
+ const exhaustedFails = result.checks
160
+ .filter((c) => c.exitCode !== 0)
161
+ .map((c) => c.command);
162
+ const exhaustedSummary = exhaustedFails.length <= 3
163
+ ? exhaustedFails.join(", ")
164
+ : `${exhaustedFails.slice(0, 3).join(", ")}... and ${exhaustedFails.length - 3} more`;
165
+ ctx.ui.notify(`Verification gate FAILED after ${attempt} ${attempt === 1 ? "retry" : "retries"} (${exhaustedSummary}) — pausing for human review`, "error");
155
166
  await pauseAuto(ctx, pi);
156
167
  return "pause";
157
168
  }