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
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glittercowboy/gsd",
3
- "version": "2.62.1",
3
+ "version": "2.63.0",
4
4
  "piConfig": {
5
5
  "name": "gsd",
6
6
  "configDir": ".gsd"
@@ -1,12 +1,9 @@
1
- import { execFile, execFileSync } from "node:child_process";
1
+ import { execFileSync, spawn } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { promisify } from "node:util";
6
5
  import type { GSDPreferences } from "../gsd/preferences.js";
7
6
  import type { GSDState, Phase } from "../gsd/types.js";
8
-
9
- const execFileAsync = promisify(execFile);
10
7
  const DEFAULT_SOCKET_PATH = "/tmp/cmux.sock";
11
8
  const STATUS_KEY = "gsd";
12
9
  const lastSidebarSnapshots = new Map<string, string>();
@@ -200,6 +197,7 @@ export class CmuxClient {
200
197
  return execFileSync("cmux", args, {
201
198
  encoding: "utf-8",
202
199
  timeout: 3000,
200
+ stdio: ["ignore", "pipe", "pipe"],
203
201
  env: process.env,
204
202
  });
205
203
  } catch {
@@ -209,16 +207,24 @@ export class CmuxClient {
209
207
 
210
208
  private async runAsync(args: string[]): Promise<string | null> {
211
209
  if (!this.canRun()) return null;
212
- try {
213
- const result = await execFileAsync("cmux", args, {
214
- encoding: "utf-8",
215
- timeout: 5000,
210
+ return new Promise<string | null>((resolve) => {
211
+ const child = spawn("cmux", args, {
212
+ stdio: ["ignore", "pipe", "pipe"],
216
213
  env: process.env,
217
214
  });
218
- return result.stdout;
219
- } catch {
220
- return null;
221
- }
215
+ const chunks: Buffer[] = [];
216
+ let settled = false;
217
+ const done = (result: string | null) => {
218
+ if (!settled) { settled = true; resolve(result); }
219
+ };
220
+ const timer = setTimeout(() => { child.kill(); done(null); }, 5000);
221
+ child.stdout!.on("data", (chunk: Buffer) => chunks.push(chunk));
222
+ child.on("close", (code) => {
223
+ clearTimeout(timer);
224
+ done(code === 0 ? Buffer.concat(chunks).toString("utf-8") : null);
225
+ });
226
+ child.on("error", () => { clearTimeout(timer); done(null); });
227
+ });
222
228
  }
223
229
 
224
230
  getCapabilities(): unknown | null {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * auto/finalize-timeout.ts — Timeout guard for post-unit finalization.
3
+ *
4
+ * Prevents the auto-loop from hanging indefinitely when
5
+ * postUnitPostVerification() never resolves (#2344).
6
+ *
7
+ * Leaf module — no imports from auto/ to avoid circular dependencies.
8
+ */
9
+
10
+ /** Timeout for postUnitPostVerification in runFinalize (ms). */
11
+ export const FINALIZE_POST_TIMEOUT_MS = 60_000;
12
+
13
+ /**
14
+ * Race a promise against a timeout. Returns an object indicating whether
15
+ * the timeout fired and the resolved value (if any).
16
+ *
17
+ * Unlike Promise.race with a rejection, this returns a discriminated
18
+ * result so callers can handle timeouts as a recoverable condition
19
+ * rather than an exception.
20
+ *
21
+ * The timeout timer is always cleaned up, whether the promise resolves
22
+ * or the timeout fires.
23
+ */
24
+ export async function withTimeout<T>(
25
+ promise: Promise<T>,
26
+ timeoutMs: number,
27
+ label: string,
28
+ ): Promise<{ value: T; timedOut: false } | { value: undefined; timedOut: true }> {
29
+ let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
30
+
31
+ const timeoutPromise = new Promise<{ value: undefined; timedOut: true }>((resolve) => {
32
+ timeoutHandle = setTimeout(() => {
33
+ resolve({ value: undefined, timedOut: true });
34
+ }, timeoutMs);
35
+ });
36
+
37
+ try {
38
+ const result = await Promise.race([
39
+ promise.then((value) => ({ value, timedOut: false as const })),
40
+ timeoutPromise,
41
+ ]);
42
+ return result;
43
+ } finally {
44
+ if (timeoutHandle) clearTimeout(timeoutHandle);
45
+ }
46
+ }
@@ -48,6 +48,7 @@ export async function autoLoop(
48
48
  let iteration = 0;
49
49
  const loopState: LoopState = { recentUnits: [], stuckRecoveryAttempts: 0 };
50
50
  let consecutiveErrors = 0;
51
+ const recentErrorMessages: string[] = [];
51
52
 
52
53
  while (s.active) {
53
54
  iteration++;
@@ -202,6 +203,7 @@ export async function autoLoop(
202
203
 
203
204
  deps.clearUnitTimeout();
204
205
  consecutiveErrors = 0;
206
+ recentErrorMessages.length = 0;
205
207
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
206
208
  debugLog("autoLoop", { phase: "iteration-complete", iteration });
207
209
  continue;
@@ -250,12 +252,18 @@ export async function autoLoop(
250
252
  if (finalizeResult.action === "continue") continue;
251
253
 
252
254
  consecutiveErrors = 0; // Iteration completed successfully
255
+ recentErrorMessages.length = 0;
253
256
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
254
257
  debugLog("autoLoop", { phase: "iteration-complete", iteration });
255
258
  } catch (loopErr) {
256
259
  // ── Blanket catch: absorb unexpected exceptions, apply graduated recovery ──
257
260
  const msg = loopErr instanceof Error ? loopErr.message : String(loopErr);
258
261
 
262
+ // Always emit iteration-end on error so the journal records iteration
263
+ // completion even on failure (#2344). Without this, errors in
264
+ // runFinalize leave the journal incomplete, making diagnosis harder.
265
+ deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration, error: msg } });
266
+
259
267
  // ── Infrastructure errors: immediate stop, no retry ──
260
268
  // These are unrecoverable (disk full, OOM, etc.). Retrying just burns
261
269
  // LLM budget on guaranteed failures.
@@ -280,6 +288,7 @@ export async function autoLoop(
280
288
  }
281
289
 
282
290
  consecutiveErrors++;
291
+ recentErrorMessages.push(msg.length > 120 ? msg.slice(0, 120) + "..." : msg);
283
292
  debugLog("autoLoop", {
284
293
  phase: "iteration-error",
285
294
  iteration,
@@ -289,8 +298,11 @@ export async function autoLoop(
289
298
 
290
299
  if (consecutiveErrors >= 3) {
291
300
  // 3+ consecutive: hard stop — something is fundamentally broken
301
+ const errorHistory = recentErrorMessages
302
+ .map((m, i) => ` ${i + 1}. ${m}`)
303
+ .join("\n");
292
304
  ctx.ui.notify(
293
- `Auto-mode stopped: ${consecutiveErrors} consecutive iteration failures. Last: ${msg}`,
305
+ `Auto-mode stopped: ${consecutiveErrors} consecutive iteration failures:\n${errorHistory}`,
294
306
  "error",
295
307
  );
296
308
  await deps.stopAuto(
@@ -26,13 +26,17 @@ import { runUnit } from "./run-unit.js";
26
26
  import { debugLog } from "../debug-logger.js";
27
27
  import { PROJECT_FILES } from "../detection.js";
28
28
  import { MergeConflictError } from "../git-service.js";
29
- import { join, basename } from "node:path";
30
- import { existsSync, cpSync } from "node:fs";
29
+ import { join, basename, dirname, parse as parsePath } from "node:path";
30
+ import { existsSync, cpSync, readdirSync } from "node:fs";
31
31
  import { logWarning, logError } from "../workflow-logger.js";
32
32
  import { gsdRoot } from "../paths.js";
33
33
  import { atomicWriteSync } from "../atomic-write.js";
34
- import { verifyExpectedArtifact } from "../auto-recovery.js";
34
+ import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
35
35
  import { writeUnitRuntimeRecord } from "../unit-runtime.js";
36
+ import { withTimeout, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
37
+ import { getEligibleSlices } from "../slice-parallel-eligibility.js";
38
+ import { startSliceParallel } from "../slice-parallel-orchestrator.js";
39
+ import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
36
40
 
37
41
  // ─── generateMilestoneReport ──────────────────────────────────────────────────
38
42
 
@@ -182,7 +186,7 @@ export async function runPreDispatch(
182
186
  }
183
187
  if (!healthGate.proceed) {
184
188
  ctx.ui.notify(
185
- healthGate.reason ?? "Pre-dispatch health check failed.",
189
+ healthGate.reason || "Pre-dispatch health check failed — run /gsd doctor for details.",
186
190
  "error",
187
191
  );
188
192
  await deps.pauseAuto(ctx, pi);
@@ -218,6 +222,63 @@ export async function runPreDispatch(
218
222
  statePhase: state.phase,
219
223
  });
220
224
 
225
+ // ── Slice-level parallelism gate (#2340) ─────────────────────────────
226
+ // When slice_parallel is enabled, check if multiple slices are eligible
227
+ // for parallel execution. If so, dispatch them in parallel and stop the
228
+ // sequential loop. Workers are spawned via slice-parallel-orchestrator.ts.
229
+ if (
230
+ prefs?.slice_parallel?.enabled &&
231
+ mid &&
232
+ !process.env.GSD_PARALLEL_WORKER &&
233
+ isDbAvailable()
234
+ ) {
235
+ try {
236
+ const dbSlices = getMilestoneSlices(mid);
237
+ if (dbSlices.length > 0) {
238
+ const doneIds = new Set(dbSlices.filter(sl => sl.status === "complete" || sl.status === "done").map(sl => sl.id));
239
+ const sliceInputs = dbSlices.map(sl => ({
240
+ id: sl.id,
241
+ done: doneIds.has(sl.id),
242
+ depends: sl.depends ?? [],
243
+ }));
244
+ const eligible = getEligibleSlices(sliceInputs, doneIds);
245
+ if (eligible.length > 1) {
246
+ debugLog("autoLoop", {
247
+ phase: "slice-parallel-dispatch",
248
+ iteration: ic.iteration,
249
+ mid,
250
+ eligibleSlices: eligible.map(e => e.id),
251
+ });
252
+ ctx.ui.notify(
253
+ `Slice-parallel: dispatching ${eligible.length} eligible slices for ${mid}.`,
254
+ "info",
255
+ );
256
+ const result = await startSliceParallel(
257
+ s.basePath,
258
+ mid,
259
+ eligible,
260
+ { maxWorkers: prefs.slice_parallel.max_workers ?? 2 },
261
+ );
262
+ if (result.started.length > 0) {
263
+ ctx.ui.notify(
264
+ `Slice-parallel: started ${result.started.length} worker(s): ${result.started.join(", ")}.`,
265
+ "info",
266
+ );
267
+ await deps.stopAuto(ctx, pi, `Slice-parallel dispatched for ${mid}`);
268
+ return { action: "break", reason: "slice-parallel-dispatched" };
269
+ }
270
+ // Fall through to sequential if no workers started
271
+ }
272
+ }
273
+ } catch (err) {
274
+ debugLog("autoLoop", {
275
+ phase: "slice-parallel-check-error",
276
+ error: err instanceof Error ? err.message : String(err),
277
+ });
278
+ // Non-fatal — fall through to sequential dispatch
279
+ }
280
+ }
281
+
221
282
  // ── Milestone transition ────────────────────────────────────────────
222
283
  if (mid && s.currentMilestoneId && mid !== s.currentMilestoneId) {
223
284
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "milestone-transition", data: { from: s.currentMilestoneId, to: mid } });
@@ -628,15 +689,17 @@ export async function runDispatch(
628
689
  unitId,
629
690
  reason: stuckSignal.reason,
630
691
  });
692
+ const stuckDiag = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
693
+ const stuckRemediation = buildLoopRemediationSteps(unitType, unitId, s.basePath);
694
+ const stuckParts = [`Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}.`];
695
+ if (stuckDiag) stuckParts.push(`Expected: ${stuckDiag}`);
696
+ if (stuckRemediation) stuckParts.push(`To recover:\n${stuckRemediation}`);
697
+ ctx.ui.notify(stuckParts.join(" "), "error");
631
698
  await deps.stopAuto(
632
699
  ctx,
633
700
  pi,
634
701
  `Stuck: ${stuckSignal.reason}`,
635
702
  );
636
- ctx.ui.notify(
637
- `Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}. The expected artifact was not written.`,
638
- "error",
639
- );
640
703
  return { action: "break", reason: "stuck-detected" };
641
704
  }
642
705
  } else {
@@ -946,11 +1009,38 @@ export async function runUnitPhase(
946
1009
  }
947
1010
  const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
948
1011
  const hasSrcDir = deps.existsSync(join(s.basePath, "src"));
949
- if (!hasProjectFile && !hasSrcDir) {
1012
+ // Xcode bundles have project-specific names (*.xcodeproj, *.xcworkspace)
1013
+ // that cannot be matched by exact filename — scan the directory by suffix.
1014
+ let hasXcodeBundle = false;
1015
+ try {
1016
+ const entries = deps.existsSync(s.basePath) ? readdirSync(s.basePath) : [];
1017
+ hasXcodeBundle = entries.some((e: string) => e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace"));
1018
+ } catch (err) {
1019
+ debugLog("runUnitPhase", { phase: "xcode-bundle-scan-failed", basePath: s.basePath, error: String(err) });
1020
+ }
1021
+ // Monorepo support (#2347): if no project files in the worktree directory,
1022
+ // walk parent directories up to the filesystem root. In monorepos,
1023
+ // package.json / Cargo.toml etc. live in a parent directory.
1024
+ let hasProjectFileInParent = false;
1025
+ if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle) {
1026
+ let checkDir = dirname(s.basePath);
1027
+ const { root } = parsePath(checkDir);
1028
+ while (checkDir !== root) {
1029
+ // Stop at git repository boundary — ancestors above the repo root
1030
+ // (e.g. ~ or /usr/local) may contain unrelated project files.
1031
+ if (deps.existsSync(join(checkDir, ".git"))) break;
1032
+ if (PROJECT_FILES.some((f) => deps.existsSync(join(checkDir, f)))) {
1033
+ hasProjectFileInParent = true;
1034
+ break;
1035
+ }
1036
+ checkDir = dirname(checkDir);
1037
+ }
1038
+ }
1039
+ if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle && !hasProjectFileInParent) {
950
1040
  // Greenfield projects won't have project files yet — the first task creates them.
951
1041
  // Log a warning but allow execution to proceed. The .git check above is sufficient
952
1042
  // to ensure we're in a valid working directory.
953
- debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir });
1043
+ debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir, hasXcodeBundle });
954
1044
  ctx.ui.notify(`Warning: ${s.basePath} has no recognized project files — proceeding as greenfield project`, "warning");
955
1045
  }
956
1046
  }
@@ -964,6 +1054,7 @@ export async function runUnitPhase(
964
1054
  const previousTier = s.currentUnitRouting?.tier;
965
1055
 
966
1056
  s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
1057
+ s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
967
1058
  const unitStartSeq = ic.nextSeq();
968
1059
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
969
1060
  deps.captureAvailableSkills();
@@ -983,30 +1074,10 @@ export async function runUnitPhase(
983
1074
  },
984
1075
  );
985
1076
 
986
- // Select and apply model (with tier escalation on retrynormal units only)
987
- const modelResult = await deps.selectAndApplyModel(
988
- ctx,
989
- pi,
990
- unitType,
991
- unitId,
992
- s.basePath,
993
- prefs,
994
- s.verbose,
995
- s.autoModeStartModel,
996
- sidecarItem ? undefined : { isRetry, previousTier },
997
- );
998
- s.currentUnitRouting =
999
- modelResult.routing as AutoSession["currentUnitRouting"];
1000
- s.currentUnitModel =
1001
- modelResult.appliedModel as AutoSession["currentUnitModel"];
1002
-
1003
- // Status bar + progress widget
1077
+ // Status bar (widget + preconditions deferred until after model selection see #2899)
1004
1078
  ctx.ui.setStatus("gsd-auto", "auto");
1005
1079
  if (mid)
1006
1080
  deps.updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
1007
- deps.updateProgressWidget(ctx, unitType, unitId, state);
1008
-
1009
- deps.ensurePreconditions(unitType, unitId, s.basePath, state);
1010
1081
 
1011
1082
  // Prompt injection
1012
1083
  let finalPrompt = prompt;
@@ -1072,6 +1143,23 @@ export async function runUnitPhase(
1072
1143
  logWarning("engine", "Prompt reorder failed", { error: msg });
1073
1144
  }
1074
1145
 
1146
+ // Select and apply model (with tier escalation on retry — normal units only)
1147
+ const modelResult = await deps.selectAndApplyModel(
1148
+ ctx,
1149
+ pi,
1150
+ unitType,
1151
+ unitId,
1152
+ s.basePath,
1153
+ prefs,
1154
+ s.verbose,
1155
+ s.autoModeStartModel,
1156
+ sidecarItem ? undefined : { isRetry, previousTier },
1157
+ );
1158
+ s.currentUnitRouting =
1159
+ modelResult.routing as AutoSession["currentUnitRouting"];
1160
+ s.currentUnitModel =
1161
+ modelResult.appliedModel as AutoSession["currentUnitModel"];
1162
+
1075
1163
  // Apply sidecar/pre-dispatch hook model override (takes priority over standard model selection)
1076
1164
  const hookModelOverride = sidecarItem?.model ?? iterData.hookModelOverride;
1077
1165
  if (hookModelOverride) {
@@ -1097,6 +1185,17 @@ export async function runUnitPhase(
1097
1185
  }
1098
1186
  }
1099
1187
 
1188
+ // Store the final dispatched model ID so the dashboard can read it (#2899).
1189
+ // This accounts for hook model overrides applied after selectAndApplyModel.
1190
+ s.currentDispatchedModelId = s.currentUnitModel
1191
+ ? `${(s.currentUnitModel as any).provider ?? ""}/${(s.currentUnitModel as any).id ?? ""}`
1192
+ : null;
1193
+
1194
+ // Progress widget + preconditions — deferred to after model selection so the
1195
+ // widget's first render tick shows the correct model (#2899).
1196
+ deps.updateProgressWidget(ctx, unitType, unitId, state);
1197
+ deps.ensurePreconditions(unitType, unitId, s.basePath, state);
1198
+
1100
1199
  // Start unit supervision
1101
1200
  deps.clearUnitTimeout();
1102
1201
  deps.startUnitSupervision({
@@ -1209,11 +1308,13 @@ export async function runUnitPhase(
1209
1308
  );
1210
1309
  }
1211
1310
 
1212
- // ── Zero tool-call guard (#1833) ──────────────────────────────────
1213
- // An execute-task agent that completes with 0 tool calls made no
1214
- // real changes its summary is hallucinated. Treat as failed so
1215
- // the task is retried instead of silently marked complete.
1216
- if (unitType === "execute-task") {
1311
+ // ── Zero tool-call guard (#1833, #2653) ──────────────────────────
1312
+ // Any unit that completes with 0 tool calls made no real progress —
1313
+ // likely context exhaustion where all tool calls errored out. Treat
1314
+ // as failed so the unit is retried in a fresh context instead of
1315
+ // silently passing through to artifact verification (which loops
1316
+ // forever when the unit never produced its artifact).
1317
+ {
1217
1318
  const currentLedger = deps.getLedger() as { units: Array<{ type: string; id: string; startedAt: number; toolCalls: number }> } | null;
1218
1319
  if (currentLedger?.units) {
1219
1320
  const lastUnit = [...currentLedger.units].reverse().find(
@@ -1224,14 +1325,14 @@ export async function runUnitPhase(
1224
1325
  phase: "zero-tool-calls",
1225
1326
  unitType,
1226
1327
  unitId,
1227
- warning: "Task completed with 0 tool calls — likely hallucinated, marking as failed",
1328
+ warning: "Unit completed with 0 tool calls — likely context exhaustion, marking as failed",
1228
1329
  });
1229
1330
  ctx.ui.notify(
1230
- `${unitType} ${unitId} completed with 0 tool calls — hallucinated summary, will retry`,
1331
+ `${unitType} ${unitId} completed with 0 tool calls — context exhaustion, will retry`,
1231
1332
  "warning",
1232
1333
  );
1233
1334
  // Fall through to next iteration where dispatch will re-derive
1234
- // and re-dispatch this task.
1335
+ // and re-dispatch this unit.
1235
1336
  return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt } };
1236
1337
  }
1237
1338
  }
@@ -1375,7 +1476,30 @@ export async function runFinalize(
1375
1476
  }
1376
1477
 
1377
1478
  // Post-verification processing (DB dual-write, hooks, triage, quick-tasks)
1378
- const postResult = await deps.postUnitPostVerification(postUnitCtx);
1479
+ // Timeout guard: if postUnitPostVerification hangs (e.g., module import
1480
+ // deadlock, SQLite transaction hang), force-continue after timeout so the
1481
+ // auto-loop is not permanently frozen (#2344).
1482
+ const postResultGuard = await withTimeout(
1483
+ deps.postUnitPostVerification(postUnitCtx),
1484
+ FINALIZE_POST_TIMEOUT_MS,
1485
+ "postUnitPostVerification",
1486
+ );
1487
+
1488
+ if (postResultGuard.timedOut) {
1489
+ debugLog("autoLoop", {
1490
+ phase: "post-verification-timeout",
1491
+ iteration: ic.iteration,
1492
+ unitType: iterData.unitType,
1493
+ unitId: iterData.unitId,
1494
+ });
1495
+ ctx.ui.notify(
1496
+ `postUnitPostVerification timed out after ${FINALIZE_POST_TIMEOUT_MS / 1000}s for ${iterData.unitType} ${iterData.unitId} — continuing to next iteration`,
1497
+ "warning",
1498
+ );
1499
+ return { action: "next", data: undefined as void };
1500
+ }
1501
+
1502
+ const postResult = postResultGuard.value;
1379
1503
 
1380
1504
  if (postResult === "stopped") {
1381
1505
  debugLog("autoLoop", {
@@ -105,6 +105,8 @@ export class AutoSession {
105
105
  // ── Model state ──────────────────────────────────────────────────────────
106
106
  autoModeStartModel: StartModel | null = null;
107
107
  currentUnitModel: Model<Api> | null = null;
108
+ /** Fully-qualified model ID (provider/id) set after selectAndApplyModel + hook overrides (#2899). */
109
+ currentDispatchedModelId: string | null = null;
108
110
  originalModelId: string | null = null;
109
111
  originalModelProvider: string | null = null;
110
112
  lastBudgetAlertLevel: BudgetAlertLevel = 0;
@@ -120,6 +122,11 @@ export class AutoSession {
120
122
  // ── Sidecar queue ─────────────────────────────────────────────────────
121
123
  sidecarQueue: SidecarItem[] = [];
122
124
 
125
+ // ── Tool invocation errors (#2883) ──────────────────────────────────
126
+ /** Set when a GSD tool execution ends with isError due to malformed/truncated
127
+ * JSON arguments. Checked by postUnitPreVerification to break retry loops. */
128
+ lastToolInvocationError: string | null = null;
129
+
123
130
  // ── Isolation degradation ────────────────────────────────────────────
124
131
  /** Set to true when worktree creation fails; prevents merge of nonexistent branch. */
125
132
  isolationDegraded = false;
@@ -193,6 +200,7 @@ export class AutoSession {
193
200
  // Model
194
201
  this.autoModeStartModel = null;
195
202
  this.currentUnitModel = null;
203
+ this.currentDispatchedModelId = null;
196
204
  this.originalModelId = null;
197
205
  this.originalModelProvider = null;
198
206
  this.lastBudgetAlertLevel = 0;
@@ -212,6 +220,7 @@ export class AutoSession {
212
220
  this.pendingQuickTasks = [];
213
221
  this.sidecarQueue = [];
214
222
  this.rewriteAttemptCount = 0;
223
+ this.lastToolInvocationError = null;
215
224
  this.isolationDegraded = false;
216
225
  this.milestoneMergedInPhases = false;
217
226
 
@@ -438,6 +438,8 @@ export interface WidgetStateAccessors {
438
438
  isVerbose(): boolean;
439
439
  /** True while newSession() is in-flight — render must not access session state. */
440
440
  isSessionSwitching(): boolean;
441
+ /** Fully-qualified dispatched model ID (provider/id) set after model selection + hook overrides (#2899). */
442
+ getCurrentDispatchedModelId(): string | null;
441
443
  }
442
444
 
443
445
  export function updateProgressWidget(
@@ -629,9 +631,15 @@ export function updateProgressWidget(
629
631
  const cxPctVal = cxUsage?.percent ?? 0;
630
632
  const cxPct = cxUsage?.percent !== null ? cxPctVal.toFixed(1) : "?";
631
633
 
632
- // Model display — shown in context section, not stats
633
- const modelId = cmdCtx?.model?.id ?? "";
634
- const modelProvider = cmdCtx?.model?.provider ?? "";
634
+ // Model display — prefer dispatched model ID (set after selectAndApplyModel
635
+ // + hook overrides) over cmdCtx?.model which can be stale (#2899).
636
+ const dispatchedModelId = accessors.getCurrentDispatchedModelId();
637
+ const modelId = dispatchedModelId
638
+ ? dispatchedModelId.split("/").slice(1).join("/") || dispatchedModelId
639
+ : (cmdCtx?.model?.id ?? "");
640
+ const modelProvider = dispatchedModelId
641
+ ? dispatchedModelId.split("/")[0] || ""
642
+ : (cmdCtx?.model?.provider ?? "");
635
643
  const tierIcon = resolveServiceTierIcon(effectiveServiceTier, modelId);
636
644
  const modelDisplay = (modelProvider && modelId
637
645
  ? `${modelProvider}/${modelId}`
@@ -33,6 +33,8 @@ import {
33
33
  import {
34
34
  verifyExpectedArtifact,
35
35
  resolveExpectedArtifactPath,
36
+ writeBlockerPlaceholder,
37
+ diagnoseExpectedArtifact,
36
38
  } from "./auto-recovery.js";
37
39
  import { regenerateIfMissing } from "./workflow-projections.js";
38
40
  import { syncStateToProjectRoot } from "./auto-worktree.js";
@@ -51,6 +53,9 @@ import { debugLog } from "./debug-logger.js";
51
53
  import { runSafely } from "./auto-utils.js";
52
54
  import type { AutoSession, SidecarItem } from "./auto/session.js";
53
55
 
56
+ /** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */
57
+ const MAX_VERIFICATION_RETRIES = 3;
58
+
54
59
 
55
60
  /** Enqueue a sidecar item (hook, triage, or quick-task) for the main loop to
56
61
  * drain via runUnit. Logs the enqueue event and notifies the UI. */
@@ -467,6 +472,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
467
472
  // When artifact verification fails for a unit type that has a known expected
468
473
  // artifact, return "retry" so the caller re-dispatches with failure context
469
474
  // instead of blindly re-dispatching the same unit (#1571).
475
+ // After MAX_VERIFICATION_RETRIES, escalate to writeBlockerPlaceholder so the
476
+ // pipeline can advance instead of looping forever (#2653).
470
477
  //
471
478
  // HOWEVER, if the DB is unavailable (db_unavailable), the artifact was never
472
479
  // written because the completion tool failed at the infra level. Retrying
@@ -476,27 +483,64 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
476
483
  // db_unavailable so the artifact was never written. Retrying would
477
484
  // produce an infinite re-dispatch loop (#2517).
478
485
  debugLog("postUnit", { phase: "artifact-verify-skip-db-unavailable", unitType: s.currentUnit.type, unitId: s.currentUnit.id });
486
+ const dbSkipDiag = diagnoseExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
479
487
  ctx.ui.notify(
480
- `Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} but DB is unavailable skipping retry to avoid loop (#2517)`,
488
+ `Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} DB unavailable, skipping retry.${dbSkipDiag ? ` Expected: ${dbSkipDiag}` : ""}`,
481
489
  "error",
482
490
  );
483
491
  } else if (!triggerArtifactVerified) {
492
+ // #2883: If the artifact is missing because the tool invocation itself
493
+ // failed (malformed/truncated JSON arguments), retrying will produce the
494
+ // same failure. Pause auto-mode instead of entering a stuck retry loop.
495
+ if (s.lastToolInvocationError) {
496
+ const errMsg = `Tool invocation failed for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Structured argument generation failed — pausing auto-mode.`;
497
+ debugLog("postUnit", { phase: "tool-invocation-error-pause", unitType: s.currentUnit.type, unitId: s.currentUnit.id, error: s.lastToolInvocationError });
498
+ ctx.ui.notify(errMsg, "error");
499
+ s.lastToolInvocationError = null;
500
+ await pauseAuto(ctx, pi);
501
+ return "dispatched";
502
+ }
503
+
484
504
  const hasExpectedArtifact = resolveExpectedArtifactPath(s.currentUnit.type, s.currentUnit.id, s.basePath) !== null;
485
505
  if (hasExpectedArtifact) {
486
506
  const retryKey = `${s.currentUnit.type}:${s.currentUnit.id}`;
487
507
  const attempt = (s.verificationRetryCount.get(retryKey) ?? 0) + 1;
488
508
  s.verificationRetryCount.set(retryKey, attempt);
489
- s.pendingVerificationRetry = {
490
- unitId: s.currentUnit.id,
491
- failureContext: `Artifact verification failed: expected artifact for ${s.currentUnit.type} "${s.currentUnit.id}" was not found on disk after unit execution (attempt ${attempt}).`,
492
- attempt,
493
- };
494
- debugLog("postUnit", { phase: "artifact-verify-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, attempt });
495
- ctx.ui.notify(
496
- `Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — retrying (attempt ${attempt})`,
497
- "warning",
498
- );
499
- return "retry";
509
+
510
+ if (attempt > MAX_VERIFICATION_RETRIES) {
511
+ // Retries exhausted write a blocker placeholder so the pipeline
512
+ // can advance past this stuck unit (#2653).
513
+ debugLog("postUnit", {
514
+ phase: "artifact-verify-escalate",
515
+ unitType: s.currentUnit.type,
516
+ unitId: s.currentUnit.id,
517
+ attempt,
518
+ maxRetries: MAX_VERIFICATION_RETRIES,
519
+ });
520
+ const reason = `Artifact verification failed after ${MAX_VERIFICATION_RETRIES} retries for ${s.currentUnit.type} "${s.currentUnit.id}".`;
521
+ writeBlockerPlaceholder(s.currentUnit.type, s.currentUnit.id, s.basePath, reason);
522
+ ctx.ui.notify(
523
+ `${s.currentUnit.type} ${s.currentUnit.id} — verification retries exhausted (${MAX_VERIFICATION_RETRIES}), wrote blocker placeholder to advance pipeline`,
524
+ "warning",
525
+ );
526
+ // Reset retry count and fall through to "continue" so the loop
527
+ // re-derives state with the placeholder in place.
528
+ s.verificationRetryCount.delete(retryKey);
529
+ s.pendingVerificationRetry = null;
530
+ // Do NOT return "retry" — fall through to "continue" below.
531
+ } else {
532
+ s.pendingVerificationRetry = {
533
+ unitId: s.currentUnit.id,
534
+ failureContext: `Artifact verification failed: expected artifact for ${s.currentUnit.type} "${s.currentUnit.id}" was not found on disk after unit execution (attempt ${attempt}).`,
535
+ attempt,
536
+ };
537
+ debugLog("postUnit", { phase: "artifact-verify-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, attempt });
538
+ ctx.ui.notify(
539
+ `Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — retrying (attempt ${attempt})`,
540
+ "warning",
541
+ );
542
+ return "retry";
543
+ }
500
544
  }
501
545
  }
502
546
  } else {