gsd-pi 2.63.0-dev.026d309 → 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 (312) 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 +4 -0
  9. package/dist/resources/extensions/gsd/auto/phases.js +123 -22
  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 +45 -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-worktree.js +13 -7
  18. package/dist/resources/extensions/gsd/auto.js +19 -2
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +73 -60
  20. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
  21. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
  23. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -1
  24. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
  25. package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
  26. package/dist/resources/extensions/gsd/constants.js +42 -0
  27. package/dist/resources/extensions/gsd/db-writer.js +72 -4
  28. package/dist/resources/extensions/gsd/forensics.js +20 -4
  29. package/dist/resources/extensions/gsd/gsd-db.js +64 -17
  30. package/dist/resources/extensions/gsd/guided-flow.js +19 -0
  31. package/dist/resources/extensions/gsd/metrics.js +27 -1
  32. package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
  33. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  34. package/dist/resources/extensions/gsd/preferences.js +7 -2
  35. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  36. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  37. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  38. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
  39. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  40. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  41. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  42. package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
  43. package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
  44. package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
  45. package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
  46. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
  47. package/dist/resources/extensions/gsd/state.js +74 -14
  48. package/dist/resources/extensions/gsd/status-guards.js +11 -0
  49. package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
  50. package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
  51. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
  52. package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
  53. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
  54. package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
  55. package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
  56. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
  57. package/dist/resources/extensions/mcp-client/auth.js +101 -0
  58. package/dist/resources/extensions/mcp-client/index.js +10 -1
  59. package/dist/resources/extensions/ollama/index.js +6 -12
  60. package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
  61. package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
  62. package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
  63. package/dist/resources/extensions/ollama/ollama-client.js +23 -32
  64. package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
  65. package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
  66. package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
  67. package/dist/web/standalone/.next/BUILD_ID +1 -1
  68. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  69. package/dist/web/standalone/.next/build-manifest.json +2 -2
  70. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  71. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  72. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  94. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  101. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  115. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  117. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  119. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  121. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  130. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  142. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  144. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/index.html +1 -1
  150. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  151. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  153. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  154. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  155. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  156. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  157. package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
  158. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  159. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  160. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  161. package/package.json +1 -1
  162. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  163. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  164. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  165. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  166. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  167. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  168. package/packages/pi-ai/dist/types.d.ts +16 -1
  169. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  170. package/packages/pi-ai/dist/types.js.map +1 -1
  171. package/packages/pi-ai/src/types.ts +18 -1
  172. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  173. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  175. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  177. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  178. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  179. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  180. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  181. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  183. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  185. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  188. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
  190. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  192. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  193. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  194. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  195. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  196. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
  199. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  200. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  201. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  202. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  203. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  204. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  205. package/packages/pi-coding-agent/src/core/model-registry.ts +2 -0
  206. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  207. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  208. package/packages/pi-coding-agent/src/core/sdk.ts +11 -0
  209. package/src/resources/extensions/cmux/index.ts +18 -12
  210. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  211. package/src/resources/extensions/gsd/auto/loop.ts +5 -0
  212. package/src/resources/extensions/gsd/auto/phases.ts +156 -34
  213. package/src/resources/extensions/gsd/auto/session.ts +9 -0
  214. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  215. package/src/resources/extensions/gsd/auto-post-unit.ts +53 -12
  216. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  217. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  218. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  219. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  220. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  221. package/src/resources/extensions/gsd/auto.ts +22 -1
  222. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +74 -60
  223. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  224. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  225. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  226. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -1
  227. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  228. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  229. package/src/resources/extensions/gsd/constants.ts +44 -0
  230. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  231. package/src/resources/extensions/gsd/forensics.ts +21 -5
  232. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  233. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  234. package/src/resources/extensions/gsd/metrics.ts +28 -1
  235. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  236. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  237. package/src/resources/extensions/gsd/preferences.ts +9 -2
  238. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  239. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  240. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  241. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  242. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  243. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  244. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  245. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  246. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  247. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  248. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  249. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  250. package/src/resources/extensions/gsd/state.ts +67 -12
  251. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  252. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  253. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  254. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  255. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  256. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  257. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  258. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  259. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  260. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  261. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  262. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  263. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  264. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  265. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  266. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  267. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  268. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  269. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  270. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  271. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  272. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  273. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  274. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  275. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  276. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  277. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  278. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  279. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  280. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  281. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  282. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  283. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  284. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  285. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  286. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  287. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  288. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  289. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  290. package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
  291. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  292. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  293. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  294. package/src/resources/extensions/gsd/types.ts +44 -22
  295. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  296. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  297. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  298. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  299. package/src/resources/extensions/mcp-client/index.ts +16 -1
  300. package/src/resources/extensions/ollama/index.ts +6 -14
  301. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  302. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  303. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  304. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  305. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  306. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  307. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  308. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  309. package/src/resources/extensions/ollama/types.ts +23 -0
  310. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  311. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → QmuF-eAbuU_2MQ03t38qr}/_buildManifest.js +0 -0
  312. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → QmuF-eAbuU_2MQ03t38qr}/_ssgManifest.js +0 -0
@@ -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
+ }
@@ -259,6 +259,11 @@ export async function autoLoop(
259
259
  // ── Blanket catch: absorb unexpected exceptions, apply graduated recovery ──
260
260
  const msg = loopErr instanceof Error ? loopErr.message : String(loopErr);
261
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
+
262
267
  // ── Infrastructure errors: immediate stop, no retry ──
263
268
  // These are unrecoverable (disk full, OOM, etc.). Retrying just burns
264
269
  // LLM budget on guaranteed failures.
@@ -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
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
 
@@ -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 } });
@@ -948,11 +1009,38 @@ export async function runUnitPhase(
948
1009
  }
949
1010
  const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
950
1011
  const hasSrcDir = deps.existsSync(join(s.basePath, "src"));
951
- 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) {
952
1040
  // Greenfield projects won't have project files yet — the first task creates them.
953
1041
  // Log a warning but allow execution to proceed. The .git check above is sufficient
954
1042
  // to ensure we're in a valid working directory.
955
- 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 });
956
1044
  ctx.ui.notify(`Warning: ${s.basePath} has no recognized project files — proceeding as greenfield project`, "warning");
957
1045
  }
958
1046
  }
@@ -966,6 +1054,7 @@ export async function runUnitPhase(
966
1054
  const previousTier = s.currentUnitRouting?.tier;
967
1055
 
968
1056
  s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
1057
+ s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
969
1058
  const unitStartSeq = ic.nextSeq();
970
1059
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
971
1060
  deps.captureAvailableSkills();
@@ -985,30 +1074,10 @@ export async function runUnitPhase(
985
1074
  },
986
1075
  );
987
1076
 
988
- // Select and apply model (with tier escalation on retrynormal units only)
989
- const modelResult = await deps.selectAndApplyModel(
990
- ctx,
991
- pi,
992
- unitType,
993
- unitId,
994
- s.basePath,
995
- prefs,
996
- s.verbose,
997
- s.autoModeStartModel,
998
- sidecarItem ? undefined : { isRetry, previousTier },
999
- );
1000
- s.currentUnitRouting =
1001
- modelResult.routing as AutoSession["currentUnitRouting"];
1002
- s.currentUnitModel =
1003
- modelResult.appliedModel as AutoSession["currentUnitModel"];
1004
-
1005
- // Status bar + progress widget
1077
+ // Status bar (widget + preconditions deferred until after model selection see #2899)
1006
1078
  ctx.ui.setStatus("gsd-auto", "auto");
1007
1079
  if (mid)
1008
1080
  deps.updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
1009
- deps.updateProgressWidget(ctx, unitType, unitId, state);
1010
-
1011
- deps.ensurePreconditions(unitType, unitId, s.basePath, state);
1012
1081
 
1013
1082
  // Prompt injection
1014
1083
  let finalPrompt = prompt;
@@ -1074,6 +1143,23 @@ export async function runUnitPhase(
1074
1143
  logWarning("engine", "Prompt reorder failed", { error: msg });
1075
1144
  }
1076
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
+
1077
1163
  // Apply sidecar/pre-dispatch hook model override (takes priority over standard model selection)
1078
1164
  const hookModelOverride = sidecarItem?.model ?? iterData.hookModelOverride;
1079
1165
  if (hookModelOverride) {
@@ -1099,6 +1185,17 @@ export async function runUnitPhase(
1099
1185
  }
1100
1186
  }
1101
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
+
1102
1199
  // Start unit supervision
1103
1200
  deps.clearUnitTimeout();
1104
1201
  deps.startUnitSupervision({
@@ -1211,11 +1308,13 @@ export async function runUnitPhase(
1211
1308
  );
1212
1309
  }
1213
1310
 
1214
- // ── Zero tool-call guard (#1833) ──────────────────────────────────
1215
- // An execute-task agent that completes with 0 tool calls made no
1216
- // real changes its summary is hallucinated. Treat as failed so
1217
- // the task is retried instead of silently marked complete.
1218
- 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
+ {
1219
1318
  const currentLedger = deps.getLedger() as { units: Array<{ type: string; id: string; startedAt: number; toolCalls: number }> } | null;
1220
1319
  if (currentLedger?.units) {
1221
1320
  const lastUnit = [...currentLedger.units].reverse().find(
@@ -1226,14 +1325,14 @@ export async function runUnitPhase(
1226
1325
  phase: "zero-tool-calls",
1227
1326
  unitType,
1228
1327
  unitId,
1229
- 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",
1230
1329
  });
1231
1330
  ctx.ui.notify(
1232
- `${unitType} ${unitId} completed with 0 tool calls — hallucinated summary, will retry`,
1331
+ `${unitType} ${unitId} completed with 0 tool calls — context exhaustion, will retry`,
1233
1332
  "warning",
1234
1333
  );
1235
1334
  // Fall through to next iteration where dispatch will re-derive
1236
- // and re-dispatch this task.
1335
+ // and re-dispatch this unit.
1237
1336
  return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt } };
1238
1337
  }
1239
1338
  }
@@ -1377,7 +1476,30 @@ export async function runFinalize(
1377
1476
  }
1378
1477
 
1379
1478
  // Post-verification processing (DB dual-write, hooks, triage, quick-tasks)
1380
- 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;
1381
1503
 
1382
1504
  if (postResult === "stopped") {
1383
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,7 @@ import {
33
33
  import {
34
34
  verifyExpectedArtifact,
35
35
  resolveExpectedArtifactPath,
36
+ writeBlockerPlaceholder,
36
37
  diagnoseExpectedArtifact,
37
38
  } from "./auto-recovery.js";
38
39
  import { regenerateIfMissing } from "./workflow-projections.js";
@@ -52,6 +53,9 @@ import { debugLog } from "./debug-logger.js";
52
53
  import { runSafely } from "./auto-utils.js";
53
54
  import type { AutoSession, SidecarItem } from "./auto/session.js";
54
55
 
56
+ /** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */
57
+ const MAX_VERIFICATION_RETRIES = 3;
58
+
55
59
 
56
60
  /** Enqueue a sidecar item (hook, triage, or quick-task) for the main loop to
57
61
  * drain via runUnit. Logs the enqueue event and notifies the UI. */
@@ -468,6 +472,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
468
472
  // When artifact verification fails for a unit type that has a known expected
469
473
  // artifact, return "retry" so the caller re-dispatches with failure context
470
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).
471
477
  //
472
478
  // HOWEVER, if the DB is unavailable (db_unavailable), the artifact was never
473
479
  // written because the completion tool failed at the infra level. Retrying
@@ -483,23 +489,58 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
483
489
  "error",
484
490
  );
485
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
+
486
504
  const hasExpectedArtifact = resolveExpectedArtifactPath(s.currentUnit.type, s.currentUnit.id, s.basePath) !== null;
487
505
  if (hasExpectedArtifact) {
488
506
  const retryKey = `${s.currentUnit.type}:${s.currentUnit.id}`;
489
507
  const attempt = (s.verificationRetryCount.get(retryKey) ?? 0) + 1;
490
508
  s.verificationRetryCount.set(retryKey, attempt);
491
- const retryDiag = diagnoseExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
492
- s.pendingVerificationRetry = {
493
- unitId: s.currentUnit.id,
494
- failureContext: `Artifact verification failed: expected artifact for ${s.currentUnit.type} "${s.currentUnit.id}" was not found on disk after unit execution (attempt ${attempt}).${retryDiag ? ` Expected: ${retryDiag}` : ""}`,
495
- attempt,
496
- };
497
- debugLog("postUnit", { phase: "artifact-verify-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, attempt });
498
- ctx.ui.notify(
499
- `Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — retrying (attempt ${attempt}).${retryDiag ? ` Expected: ${retryDiag}` : ""}`,
500
- "warning",
501
- );
502
- 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
+ }
503
544
  }
504
545
  }
505
546
  } else {
@@ -994,10 +994,15 @@ export async function buildResearchSlicePrompt(
994
994
  const milestoneResearchPath = resolveMilestoneFile(base, mid, "RESEARCH");
995
995
  const milestoneResearchRel = relMilestoneFile(base, mid, "RESEARCH");
996
996
 
997
+ const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
998
+ const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
999
+
997
1000
  const inlined: string[] = [];
998
1001
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
999
1002
  const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
1000
1003
  if (contextInline) inlined.push(contextInline);
1004
+ const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
1005
+ if (sliceCtxInline) inlined.push(sliceCtxInline);
1001
1006
  const researchInline = await inlineFileOptional(milestoneResearchPath, milestoneResearchRel, "Milestone Research");
1002
1007
  if (researchInline) inlined.push(researchInline);
1003
1008
  const decisionsInline = await inlineDecisionsFromDb(base, mid);
@@ -1045,6 +1050,8 @@ export async function buildPlanSlicePrompt(
1045
1050
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
1046
1051
  const researchPath = resolveSliceFile(base, mid, sid, "RESEARCH");
1047
1052
  const researchRel = relSliceFile(base, mid, sid, "RESEARCH");
1053
+ const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
1054
+ const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
1048
1055
 
1049
1056
  const inlined: string[] = [];
1050
1057
 
@@ -1053,6 +1060,8 @@ export async function buildPlanSlicePrompt(
1053
1060
  if (researchSliceAnchor) inlined.push(formatAnchorForPrompt(researchSliceAnchor));
1054
1061
 
1055
1062
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
1063
+ const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
1064
+ if (sliceCtxInline) inlined.push(sliceCtxInline);
1056
1065
  const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research");
1057
1066
  if (researchInline) inlined.push(researchInline);
1058
1067
  if (inlineLevel !== "minimal") {
@@ -1253,9 +1262,13 @@ export async function buildCompleteSlicePrompt(
1253
1262
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
1254
1263
  const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN");
1255
1264
  const slicePlanRel = relSliceFile(base, mid, sid, "PLAN");
1265
+ const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
1266
+ const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
1256
1267
 
1257
1268
  const inlined: string[] = [];
1258
1269
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
1270
+ const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
1271
+ if (sliceCtxInline) inlined.push(sliceCtxInline);
1259
1272
  inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Slice Plan"));
1260
1273
  if (inlineLevel !== "minimal") {
1261
1274
  const requirementsInline = await inlineRequirementsFromDb(base, sid, inlineLevel);
@@ -1510,9 +1523,13 @@ export async function buildReplanSlicePrompt(
1510
1523
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
1511
1524
  const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN");
1512
1525
  const slicePlanRel = relSliceFile(base, mid, sid, "PLAN");
1526
+ const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
1527
+ const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
1513
1528
 
1514
1529
  const inlined: string[] = [];
1515
1530
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
1531
+ const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
1532
+ if (sliceCtxInline) inlined.push(sliceCtxInline);
1516
1533
  inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Current Slice Plan"));
1517
1534
 
1518
1535
  // Find the blocker task summary — the completed task with blocker_discovered: true
@@ -1627,9 +1644,13 @@ export async function buildReassessRoadmapPrompt(
1627
1644
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
1628
1645
  const summaryPath = resolveSliceFile(base, mid, completedSliceId, "SUMMARY");
1629
1646
  const summaryRel = relSliceFile(base, mid, completedSliceId, "SUMMARY");
1647
+ const sliceContextPath = resolveSliceFile(base, mid, completedSliceId, "CONTEXT");
1648
+ const sliceContextRel = relSliceFile(base, mid, completedSliceId, "CONTEXT");
1630
1649
 
1631
1650
  const inlined: string[] = [];
1632
1651
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Current Roadmap"));
1652
+ const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
1653
+ if (sliceCtxInline) inlined.push(sliceCtxInline);
1633
1654
  inlined.push(await inlineFile(summaryPath, summaryRel, `${completedSliceId} Summary`));
1634
1655
  if (inlineLevel !== "minimal") {
1635
1656
  const projectInline = await inlineProjectFromDb(base);