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
@@ -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);
@@ -12,7 +12,7 @@ import { parseUnitId } from "./unit-id.js";
12
12
  import { atomicWriteSync } from "./atomic-write.js";
13
13
  import { clearParseCache } from "./files.js";
14
14
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
15
- import { isDbAvailable, getTask, getSlice, getSliceTasks, updateTaskStatus } from "./gsd-db.js";
15
+ import { isDbAvailable, getTask, getSlice, getSliceTasks, updateTaskStatus, updateSliceStatus } from "./gsd-db.js";
16
16
  import { isValidationTerminal } from "./state.js";
17
17
  import { getErrorMessage } from "./error-utils.js";
18
18
  import { logWarning, logError } from "./workflow-logger.js";
@@ -424,15 +424,16 @@ export function writeBlockerPlaceholder(
424
424
  ].join("\n");
425
425
  writeFileSync(absPath, content, "utf-8");
426
426
 
427
- // Mark the task as complete in the DB so verifyExpectedArtifact passes.
427
+ // Mark the task/slice as complete in the DB so verifyExpectedArtifact passes.
428
428
  // Without this, the DB status stays "pending" and the dispatch loop
429
- // re-derives the same task indefinitely (#2531).
430
- if (unitType === "execute-task" && isDbAvailable()) {
429
+ // re-derives the same unit indefinitely (#2531, #2653).
430
+ if (isDbAvailable()) {
431
431
  const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
432
- if (mid && sid && tid) {
433
- try { updateTaskStatus(mid, sid, tid, "complete", new Date().toISOString()); } catch (err) { /* non-fatal */
434
- logError("recovery", `DB status update failed: ${err instanceof Error ? err.message : String(err)}`);
435
- }
432
+ if (unitType === "execute-task" && mid && sid && tid) {
433
+ try { updateTaskStatus(mid, sid, tid, "complete", new Date().toISOString()); } catch (e) { logWarning("recovery", `updateTaskStatus failed during context exhaustion: ${e instanceof Error ? e.message : String(e)}`); }
434
+ }
435
+ if (unitType === "complete-slice" && mid && sid) {
436
+ try { updateSliceStatus(mid, sid, "complete", new Date().toISOString()); } catch (e) { logWarning("recovery", `updateSliceStatus failed during context exhaustion: ${e instanceof Error ? e.message : String(e)}`); }
436
437
  }
437
438
  }
438
439
 
@@ -58,7 +58,7 @@ import { initRoutingHistory } from "./routing-history.js";
58
58
  import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
59
59
  import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
60
60
  import { snapshotSkills } from "./skill-discovery.js";
61
- import { isDbAvailable, getMilestone } from "./gsd-db.js";
61
+ import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
62
62
  import { hideFooter } from "./auto-dashboard.js";
63
63
  import {
64
64
  debugLog,
@@ -99,33 +99,24 @@ export interface BootstrapDeps {
99
99
  * concurrent session detected). Returns true when ready to dispatch.
100
100
  */
101
101
 
102
- /**
103
- * Open the project-root DB before the first deriveState call (#2841).
104
- * When auto-mode starts cold (no prior DB handle), state derivation that
105
- * touches DB-backed helpers (queue-order, task status) silently falls back
106
- * to markdown-only data, producing stale or incomplete state. Opening the
107
- * DB first ensures deriveState sees the full picture on its very first run.
108
- */
109
- async function openProjectDbIfPresent(basePath: string): Promise<void> {
102
+ /** Guard: tracks consecutive bootstrap attempts that found phase === "complete".
103
+ * Prevents the recursive dialog loop described in #1348 where
104
+ * bootstrapAutoSession showSmartEntry checkAutoStartAfterDiscuss startAuto
105
+ * cycles indefinitely when the discuss workflow doesn't produce a milestone. */
106
+ let _consecutiveCompleteBootstraps = 0;
107
+ const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
108
+
109
+ export async function openProjectDbIfPresent(basePath: string): Promise<void> {
110
110
  const gsdDbPath = resolveProjectRootDbPath(basePath);
111
- if (!existsSync(gsdDbPath)) return;
112
- if (isDbAvailable()) return;
111
+ if (!existsSync(gsdDbPath) || isDbAvailable()) return;
113
112
 
114
113
  try {
115
- const { openDatabase } = await import("./gsd-db.js");
116
114
  openDatabase(gsdDbPath);
117
115
  } catch (err) {
118
- /* non-fatal DB lifecycle block below will retry */
119
- logWarning("engine", `DB open failed: ${err instanceof Error ? err.message : String(err)}`);
116
+ logWarning("engine", `gsd-db: failed to open existing database: ${err instanceof Error ? err.message : String(err)}`);
120
117
  }
121
118
  }
122
119
 
123
- /** Guard: tracks consecutive bootstrap attempts that found phase === "complete".
124
- * Prevents the recursive dialog loop described in #1348 where
125
- * bootstrapAutoSession → showSmartEntry → checkAutoStartAfterDiscuss → startAuto
126
- * cycles indefinitely when the discuss workflow doesn't produce a milestone. */
127
- let _consecutiveCompleteBootstraps = 0;
128
- const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
129
120
  export async function bootstrapAutoSession(
130
121
  s: AutoSession,
131
122
  ctx: ExtensionCommandContext,
@@ -83,3 +83,22 @@ export function hasInteractiveToolInFlight(): boolean {
83
83
  export function clearInFlightTools(): void {
84
84
  inFlightTools.clear();
85
85
  }
86
+
87
+ // ─── Tool invocation error classification (#2883) ────────────────────────
88
+
89
+ /**
90
+ * Patterns that indicate a tool invocation failed due to malformed or truncated
91
+ * JSON arguments — as opposed to a normal business-logic error from the tool
92
+ * handler. When these errors occur, retrying the same unit will produce the same
93
+ * failure, so the retry loop must be broken.
94
+ */
95
+ const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}' in JSON|Unexpected end of JSON|Unexpected token.*in JSON/i;
96
+
97
+ /**
98
+ * Returns true if the error message indicates a tool invocation failure due to
99
+ * malformed/truncated arguments (as opposed to a normal tool execution error).
100
+ */
101
+ export function isToolInvocationError(errorMsg: string): boolean {
102
+ if (!errorMsg) return false;
103
+ return TOOL_INVOCATION_ERROR_RE.test(errorMsg);
104
+ }
@@ -196,19 +196,30 @@ export async function runPostUnitVerification(
196
196
  failureContext: formatFailureContext(result),
197
197
  attempt: nextAttempt,
198
198
  };
199
+ const failedCmds = result.checks
200
+ .filter((c) => c.exitCode !== 0)
201
+ .map((c) => c.command);
202
+ const cmdSummary = failedCmds.length <= 3
203
+ ? failedCmds.join(", ")
204
+ : `${failedCmds.slice(0, 3).join(", ")}... and ${failedCmds.length - 3} more`;
199
205
  ctx.ui.notify(
200
- `Verification failed — auto-fix attempt ${nextAttempt}/${maxRetries}`,
206
+ `Verification failed (${cmdSummary}) — auto-fix attempt ${nextAttempt}/${maxRetries}`,
201
207
  "warning",
202
208
  );
203
209
  // Return "retry" — the autoLoop while loop will re-iterate with the retry context
204
210
  return "retry";
205
211
  } else {
206
212
  // Gate failed, retries exhausted
207
- const exhaustedAttempt = attempt + 1;
208
213
  s.verificationRetryCount.delete(s.currentUnit.id);
209
214
  s.pendingVerificationRetry = null;
215
+ const exhaustedFails = result.checks
216
+ .filter((c) => c.exitCode !== 0)
217
+ .map((c) => c.command);
218
+ const exhaustedSummary = exhaustedFails.length <= 3
219
+ ? exhaustedFails.join(", ")
220
+ : `${exhaustedFails.slice(0, 3).join(", ")}... and ${exhaustedFails.length - 3} more`;
210
221
  ctx.ui.notify(
211
- `Verification gate FAILED after ${exhaustedAttempt > maxRetries ? exhaustedAttempt - 1 : exhaustedAttempt} retries — pausing for human review`,
222
+ `Verification gate FAILED after ${attempt} ${attempt === 1 ? "retry" : "retries"} (${exhaustedSummary}) — pausing for human review`,
212
223
  "error",
213
224
  );
214
225
  await pauseAuto(ctx, pi);
@@ -36,6 +36,7 @@ import {
36
36
  removeWorktree,
37
37
  resolveGitDir,
38
38
  worktreePath,
39
+ isInsideWorktreesDir,
39
40
  } from "./worktree-manager.js";
40
41
  import {
41
42
  detectWorktreeName,
@@ -1204,12 +1205,19 @@ export function teardownAutoWorktree(
1204
1205
  `Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`,
1205
1206
  { worktree: milestoneId },
1206
1207
  );
1207
- // Attempt a direct filesystem removal as a fallback
1208
- try {
1209
- rmSync(wtDir, { recursive: true, force: true });
1210
- } catch (err) {
1211
- // Non-fatal the warning above tells the user how to clean up
1212
- logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
1208
+ // Attempt a direct filesystem removal as a fallback — but ONLY if the
1209
+ // path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
1210
+ if (isInsideWorktreesDir(originalBasePath, wtDir)) {
1211
+ try {
1212
+ rmSync(wtDir, { recursive: true, force: true });
1213
+ } catch (err) {
1214
+ // Non-fatal — the warning above tells the user how to clean up
1215
+ logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
1216
+ }
1217
+ } else {
1218
+ console.error(
1219
+ `[GSD] REFUSING fallback rmSync — path is outside .gsd/worktrees/: ${wtDir}`,
1220
+ );
1213
1221
  }
1214
1222
  }
1215
1223
  }
@@ -75,6 +75,7 @@ import {
75
75
  getOldestInFlightToolStart,
76
76
  hasInteractiveToolInFlight,
77
77
  clearInFlightTools,
78
+ isToolInvocationError,
78
79
  } from "./auto-tool-tracking.js";
79
80
  import { closeoutUnit } from "./auto-unit-closeout.js";
80
81
  import { recoverTimedOutUnit } from "./auto-timeout-recovery.js";
@@ -185,8 +186,11 @@ import {
185
186
  postUnitPreVerification,
186
187
  postUnitPostVerification,
187
188
  } from "./auto-post-unit.js";
188
- import { bootstrapAutoSession, type BootstrapDeps } from "./auto-start.js";
189
+ import { bootstrapAutoSession, openProjectDbIfPresent, type BootstrapDeps } from "./auto-start.js";
189
190
  import { autoLoop, resolveAgentEnd, resolveAgentEndCancelled, _resetPendingResolve, isSessionSwitchInFlight, type LoopDeps, type ErrorContext } from "./auto-loop.js";
191
+ // Slice-level parallelism (#2340)
192
+ import { getEligibleSlices } from "./slice-parallel-eligibility.js";
193
+ import { startSliceParallel } from "./slice-parallel-orchestrator.js";
190
194
  import {
191
195
  WorktreeResolver,
192
196
  type WorktreeResolverDeps,
@@ -385,6 +389,19 @@ export function markToolEnd(toolCallId: string): void {
385
389
  _markToolEnd(toolCallId);
386
390
  }
387
391
 
392
+ /**
393
+ * Record a tool invocation error on the current session (#2883).
394
+ * Called from tool_execution_end when a GSD tool fails with isError.
395
+ * Only stores the error if it matches the tool-invocation-error pattern
396
+ * (malformed/truncated JSON), not normal business-logic errors.
397
+ */
398
+ export function recordToolInvocationError(toolName: string, errorMsg: string): void {
399
+ if (!s.active) return;
400
+ if (isToolInvocationError(errorMsg)) {
401
+ s.lastToolInvocationError = `${toolName}: ${errorMsg}`;
402
+ }
403
+ }
404
+
388
405
  export function getOldestInFlightToolAgeMs(): number {
389
406
  return _getOldestInFlightToolAgeMs();
390
407
  }
@@ -1205,6 +1222,9 @@ export async function startAuto(
1205
1222
  "info",
1206
1223
  );
1207
1224
  restoreHookState(s.basePath);
1225
+ // Open the project DB before rebuild/derive so resume uses DB-backed
1226
+ // state instead of falling back to stale markdown parsing (#2940).
1227
+ await openProjectDbIfPresent(s.basePath);
1208
1228
  try {
1209
1229
  await rebuildState(s.basePath);
1210
1230
  syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
@@ -1351,6 +1371,7 @@ const widgetStateAccessors: WidgetStateAccessors = {
1351
1371
  getBasePath: () => s.basePath,
1352
1372
  isVerbose: () => s.verbose,
1353
1373
  isSessionSwitching: isSessionSwitchInFlight,
1374
+ getCurrentDispatchedModelId: () => s.currentDispatchedModelId,
1354
1375
  };
1355
1376
 
1356
1377
  // ─── Preconditions ────────────────────────────────────────────────────────────
@@ -501,28 +501,10 @@ export function registerDbTools(pi: ExtensionAPI): void {
501
501
  "Use the canonical name gsd_plan_milestone; gsd_milestone_plan is only an alias.",
502
502
  ],
503
503
  parameters: Type.Object({
504
+ // ── Core identification + content (required) ──────────────────────
504
505
  milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
505
506
  title: Type.String({ description: "Milestone title" }),
506
- status: Type.Optional(Type.String({ description: "Milestone status (defaults to active)" })),
507
- dependsOn: Type.Optional(Type.Array(Type.String(), { description: "Milestone dependencies" })),
508
507
  vision: Type.String({ description: "Milestone vision" }),
509
- successCriteria: Type.Array(Type.String(), { description: "Top-level success criteria bullets" }),
510
- keyRisks: Type.Array(Type.Object({
511
- risk: Type.String({ description: "Risk statement" }),
512
- whyItMatters: Type.String({ description: "Why the risk matters" }),
513
- }), { description: "Structured risk entries" }),
514
- proofStrategy: Type.Array(Type.Object({
515
- riskOrUnknown: Type.String({ description: "Risk or unknown to retire" }),
516
- retireIn: Type.String({ description: "Where it will be retired" }),
517
- whatWillBeProven: Type.String({ description: "What proof will be produced" }),
518
- }), { description: "Structured proof strategy entries" }),
519
- verificationContract: Type.String({ description: "Verification contract text" }),
520
- verificationIntegration: Type.String({ description: "Integration verification text" }),
521
- verificationOperational: Type.String({ description: "Operational verification text" }),
522
- verificationUat: Type.String({ description: "UAT verification text" }),
523
- definitionOfDone: Type.Array(Type.String(), { description: "Definition of done bullets" }),
524
- requirementCoverage: Type.String({ description: "Requirement coverage text" }),
525
- boundaryMapMarkdown: Type.String({ description: "Boundary map markdown block" }),
526
508
  slices: Type.Array(Type.Object({
527
509
  sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
528
510
  title: Type.String({ description: "Slice title" }),
@@ -535,6 +517,26 @@ export function registerDbTools(pi: ExtensionAPI): void {
535
517
  integrationClosure: Type.String({ description: "Slice integration closure" }),
536
518
  observabilityImpact: Type.String({ description: "Slice observability impact" }),
537
519
  }), { description: "Planned slices for the milestone" }),
520
+ // ── Enrichment metadata (optional — defaults to empty) ────────────
521
+ status: Type.Optional(Type.String({ description: "Milestone status (defaults to active)" })),
522
+ dependsOn: Type.Optional(Type.Array(Type.String(), { description: "Milestone dependencies" })),
523
+ successCriteria: Type.Optional(Type.Array(Type.String(), { description: "Top-level success criteria bullets" })),
524
+ keyRisks: Type.Optional(Type.Array(Type.Object({
525
+ risk: Type.String({ description: "Risk statement" }),
526
+ whyItMatters: Type.String({ description: "Why the risk matters" }),
527
+ }), { description: "Structured risk entries" })),
528
+ proofStrategy: Type.Optional(Type.Array(Type.Object({
529
+ riskOrUnknown: Type.String({ description: "Risk or unknown to retire" }),
530
+ retireIn: Type.String({ description: "Where it will be retired" }),
531
+ whatWillBeProven: Type.String({ description: "What proof will be produced" }),
532
+ }), { description: "Structured proof strategy entries" })),
533
+ verificationContract: Type.Optional(Type.String({ description: "Verification contract text" })),
534
+ verificationIntegration: Type.Optional(Type.String({ description: "Integration verification text" })),
535
+ verificationOperational: Type.Optional(Type.String({ description: "Operational verification text" })),
536
+ verificationUat: Type.Optional(Type.String({ description: "UAT verification text" })),
537
+ definitionOfDone: Type.Optional(Type.Array(Type.String(), { description: "Definition of done bullets" })),
538
+ requirementCoverage: Type.Optional(Type.String({ description: "Requirement coverage text" })),
539
+ boundaryMapMarkdown: Type.Optional(Type.String({ description: "Boundary map markdown block" })),
538
540
  }),
539
541
  execute: planMilestoneExecute,
540
542
  };
@@ -594,13 +596,10 @@ export function registerDbTools(pi: ExtensionAPI): void {
594
596
  "Use the canonical name gsd_plan_slice; gsd_slice_plan is only an alias.",
595
597
  ],
596
598
  parameters: Type.Object({
599
+ // ── Core identification + content (required) ──────────────────────
597
600
  milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
598
601
  sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
599
602
  goal: Type.String({ description: "Slice goal" }),
600
- successCriteria: Type.String({ description: "Slice success criteria block" }),
601
- proofLevel: Type.String({ description: "Slice proof level" }),
602
- integrationClosure: Type.String({ description: "Slice integration closure" }),
603
- observabilityImpact: Type.String({ description: "Slice observability impact" }),
604
603
  tasks: Type.Array(Type.Object({
605
604
  taskId: Type.String({ description: "Task ID (e.g. T01)" }),
606
605
  title: Type.String({ description: "Task title" }),
@@ -612,6 +611,11 @@ export function registerDbTools(pi: ExtensionAPI): void {
612
611
  expectedOutput: Type.Array(Type.String(), { description: "Expected output files or artifacts" }),
613
612
  observabilityImpact: Type.Optional(Type.String({ description: "Task observability impact" })),
614
613
  }), { description: "Planned tasks for the slice" }),
614
+ // ── Enrichment metadata (optional — defaults to empty) ────────────
615
+ successCriteria: Type.Optional(Type.String({ description: "Slice success criteria block" })),
616
+ proofLevel: Type.Optional(Type.String({ description: "Slice proof level" })),
617
+ integrationClosure: Type.Optional(Type.String({ description: "Slice integration closure" })),
618
+ observabilityImpact: Type.Optional(Type.String({ description: "Slice observability impact" })),
615
619
  }),
616
620
  execute: planSliceExecute,
617
621
  };
@@ -743,18 +747,20 @@ export function registerDbTools(pi: ExtensionAPI): void {
743
747
  "Idempotent — calling with the same params twice will upsert (INSERT OR REPLACE) without error.",
744
748
  ],
745
749
  parameters: Type.Object({
750
+ // ── Core identification + content (required) ──────────────────────
746
751
  taskId: Type.String({ description: "Task ID (e.g. T01)" }),
747
752
  sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
748
753
  milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
749
754
  oneLiner: Type.String({ description: "One-line summary of what was accomplished" }),
750
755
  narrative: Type.String({ description: "Detailed narrative of what happened during the task" }),
751
756
  verification: Type.String({ description: "What was verified and how — commands run, tests passed, behavior confirmed" }),
752
- deviations: Type.String({ description: "Deviations from the task plan, or 'None.'" }),
753
- knownIssues: Type.String({ description: "Known issues discovered but not fixed, or 'None.'" }),
754
- keyFiles: Type.Array(Type.String(), { description: "List of key files created or modified" }),
755
- keyDecisions: Type.Array(Type.String(), { description: "List of key decisions made during this task" }),
756
- blockerDiscovered: Type.Boolean({ description: "Whether a plan-invalidating blocker was discovered" }),
757
- verificationEvidence: Type.Array(
757
+ // ── Enrichment metadata (optional defaults to empty) ────────────
758
+ deviations: Type.Optional(Type.String({ description: "Deviations from the task plan, or 'None.'" })),
759
+ knownIssues: Type.Optional(Type.String({ description: "Known issues discovered but not fixed, or 'None.'" })),
760
+ keyFiles: Type.Optional(Type.Array(Type.String(), { description: "List of key files created or modified" })),
761
+ keyDecisions: Type.Optional(Type.Array(Type.String(), { description: "List of key decisions made during this task" })),
762
+ blockerDiscovered: Type.Optional(Type.Boolean({ description: "Whether a plan-invalidating blocker was discovered" })),
763
+ verificationEvidence: Type.Optional(Type.Array(
758
764
  Type.Object({
759
765
  command: Type.String({ description: "Verification command that was run" }),
760
766
  exitCode: Type.Number({ description: "Exit code of the command" }),
@@ -762,7 +768,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
762
768
  durationMs: Type.Number({ description: "Duration of the command in milliseconds" }),
763
769
  }),
764
770
  { description: "Array of verification evidence entries" },
765
- ),
771
+ )),
766
772
  }),
767
773
  execute: taskCompleteExecute,
768
774
  };
@@ -823,59 +829,61 @@ export function registerDbTools(pi: ExtensionAPI): void {
823
829
  "Idempotent — calling with the same params twice will not crash.",
824
830
  ],
825
831
  parameters: Type.Object({
832
+ // ── Core identification + content (required) ──────────────────────
826
833
  sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
827
834
  milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
828
835
  sliceTitle: Type.String({ description: "Title of the slice" }),
829
836
  oneLiner: Type.String({ description: "One-line summary of what the slice accomplished" }),
830
837
  narrative: Type.String({ description: "Detailed narrative of what happened across all tasks" }),
831
838
  verification: Type.String({ description: "What was verified across all tasks" }),
832
- deviations: Type.String({ description: "Deviations from the slice plan, or 'None.'" }),
833
- knownLimitations: Type.String({ description: "Known limitations or gaps, or 'None.'" }),
834
- followUps: Type.String({ description: "Follow-up work discovered during execution, or 'None.'" }),
835
- keyFiles: Type.Array(Type.String(), { description: "Key files created or modified" }),
836
- keyDecisions: Type.Array(Type.String(), { description: "Key decisions made during this slice" }),
837
- patternsEstablished: Type.Array(Type.String(), { description: "Patterns established by this slice" }),
838
- observabilitySurfaces: Type.Array(Type.String(), { description: "Observability surfaces added" }),
839
- provides: Type.Array(Type.String(), { description: "What this slice provides to downstream slices" }),
840
- requirementsSurfaced: Type.Array(Type.String(), { description: "New requirements surfaced" }),
841
- drillDownPaths: Type.Array(Type.String(), { description: "Paths to task summaries for drill-down" }),
842
- affects: Type.Array(Type.String(), { description: "Downstream slices affected" }),
843
- requirementsAdvanced: Type.Array(
839
+ uatContent: Type.String({ description: "UAT test content (markdown body)" }),
840
+ // ── Enrichment metadata (optional defaults to empty) ────────────
841
+ deviations: Type.Optional(Type.String({ description: "Deviations from the slice plan, or 'None.'" })),
842
+ knownLimitations: Type.Optional(Type.String({ description: "Known limitations or gaps, or 'None.'" })),
843
+ followUps: Type.Optional(Type.String({ description: "Follow-up work discovered during execution, or 'None.'" })),
844
+ keyFiles: Type.Optional(Type.Array(Type.String(), { description: "Key files created or modified" })),
845
+ keyDecisions: Type.Optional(Type.Array(Type.String(), { description: "Key decisions made during this slice" })),
846
+ patternsEstablished: Type.Optional(Type.Array(Type.String(), { description: "Patterns established by this slice" })),
847
+ observabilitySurfaces: Type.Optional(Type.Array(Type.String(), { description: "Observability surfaces added" })),
848
+ provides: Type.Optional(Type.Array(Type.String(), { description: "What this slice provides to downstream slices" })),
849
+ requirementsSurfaced: Type.Optional(Type.Array(Type.String(), { description: "New requirements surfaced" })),
850
+ drillDownPaths: Type.Optional(Type.Array(Type.String(), { description: "Paths to task summaries for drill-down" })),
851
+ affects: Type.Optional(Type.Array(Type.String(), { description: "Downstream slices affected" })),
852
+ requirementsAdvanced: Type.Optional(Type.Array(
844
853
  Type.Object({
845
854
  id: Type.String({ description: "Requirement ID" }),
846
855
  how: Type.String({ description: "How it was advanced" }),
847
856
  }),
848
857
  { description: "Requirements advanced by this slice" },
849
- ),
850
- requirementsValidated: Type.Array(
858
+ )),
859
+ requirementsValidated: Type.Optional(Type.Array(
851
860
  Type.Object({
852
861
  id: Type.String({ description: "Requirement ID" }),
853
862
  proof: Type.String({ description: "What proof validates it" }),
854
863
  }),
855
864
  { description: "Requirements validated by this slice" },
856
- ),
857
- requirementsInvalidated: Type.Array(
865
+ )),
866
+ requirementsInvalidated: Type.Optional(Type.Array(
858
867
  Type.Object({
859
868
  id: Type.String({ description: "Requirement ID" }),
860
869
  what: Type.String({ description: "What changed" }),
861
870
  }),
862
871
  { description: "Requirements invalidated or re-scoped" },
863
- ),
864
- filesModified: Type.Array(
872
+ )),
873
+ filesModified: Type.Optional(Type.Array(
865
874
  Type.Object({
866
875
  path: Type.String({ description: "File path" }),
867
876
  description: Type.String({ description: "What changed" }),
868
877
  }),
869
878
  { description: "Files modified with descriptions" },
870
- ),
871
- requires: Type.Array(
879
+ )),
880
+ requires: Type.Optional(Type.Array(
872
881
  Type.Object({
873
882
  slice: Type.String({ description: "Dependency slice ID" }),
874
883
  provides: Type.String({ description: "What was consumed from it" }),
875
884
  }),
876
885
  { description: "Upstream slice dependencies consumed" },
877
- ),
878
- uatContent: Type.String({ description: "UAT test content (markdown body)" }),
886
+ )),
879
887
  }),
880
888
  execute: sliceCompleteExecute,
881
889
  };
@@ -972,8 +980,12 @@ export function registerDbTools(pi: ExtensionAPI): void {
972
980
  };
973
981
  }
974
982
  try {
983
+ // ── Input sanitization: normalize markdown parameters (#3013) ──────
984
+ const { sanitizeCompleteMilestoneParams } = await import("./sanitize-complete-milestone.js");
985
+ const sanitized = sanitizeCompleteMilestoneParams(params);
986
+
975
987
  const { handleCompleteMilestone } = await import("../tools/complete-milestone.js");
976
- const result = await handleCompleteMilestone(params, process.cwd());
988
+ const result = await handleCompleteMilestone(sanitized, process.cwd());
977
989
  if ("error" in result) {
978
990
  return {
979
991
  content: [{ type: "text" as const, text: `Error completing milestone: ${result.error}` }],
@@ -1012,19 +1024,21 @@ export function registerDbTools(pi: ExtensionAPI): void {
1012
1024
  "On success, returns summaryPath where the MILESTONE-SUMMARY.md was written.",
1013
1025
  ],
1014
1026
  parameters: Type.Object({
1027
+ // ── Core identification + content (required) ──────────────────────
1015
1028
  milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
1016
1029
  title: Type.String({ description: "Milestone title" }),
1017
1030
  oneLiner: Type.String({ description: "One-sentence summary of what the milestone achieved" }),
1018
1031
  narrative: Type.String({ description: "Detailed narrative of what happened during the milestone" }),
1019
- successCriteriaResults: Type.String({ description: "Markdown detailing how each success criterion was met or not met" }),
1020
- definitionOfDoneResults: Type.String({ description: "Markdown detailing how each definition-of-done item was met" }),
1021
- requirementOutcomes: Type.String({ description: "Markdown detailing requirement status transitions with evidence" }),
1022
- keyDecisions: Type.Array(Type.String(), { description: "Key architectural/pattern decisions made during the milestone" }),
1023
- keyFiles: Type.Array(Type.String(), { description: "Key files created or modified during the milestone" }),
1024
- lessonsLearned: Type.Array(Type.String(), { description: "Lessons learned during the milestone" }),
1032
+ verificationPassed: Type.Boolean({ description: "Must be true confirms that code change verification, success criteria, and definition of done checks all passed before completion" }),
1033
+ // ── Enrichment metadata (optional defaults to empty) ────────────
1034
+ successCriteriaResults: Type.Optional(Type.String({ description: "Markdown detailing how each success criterion was met or not met" })),
1035
+ definitionOfDoneResults: Type.Optional(Type.String({ description: "Markdown detailing how each definition-of-done item was met" })),
1036
+ requirementOutcomes: Type.Optional(Type.String({ description: "Markdown detailing requirement status transitions with evidence" })),
1037
+ keyDecisions: Type.Optional(Type.Array(Type.String(), { description: "Key architectural/pattern decisions made during the milestone" })),
1038
+ keyFiles: Type.Optional(Type.Array(Type.String(), { description: "Key files created or modified during the milestone" })),
1039
+ lessonsLearned: Type.Optional(Type.Array(Type.String(), { description: "Lessons learned during the milestone" })),
1025
1040
  followUps: Type.Optional(Type.String({ description: "Follow-up items for future milestones" })),
1026
1041
  deviations: Type.Optional(Type.String({ description: "Deviations from the original plan" })),
1027
- verificationPassed: Type.Boolean({ description: "Must be true — confirms that code change verification, success criteria, and definition of done checks all passed before completion" }),
1028
1042
  }),
1029
1043
  execute: milestoneCompleteExecute,
1030
1044
  };
@@ -32,6 +32,20 @@ export function resolveProjectRootDbPath(basePath: string): string {
32
32
  return join(projectRoot, ".gsd", "gsd.db");
33
33
  }
34
34
 
35
+ // External-state layout: ~/.gsd/projects/<hash>/worktrees/<MID>/...
36
+ // Resolve to ~/.gsd/projects/<hash>/gsd.db (the canonical project DB) (#2952).
37
+ // Must be checked before the generic symlink-resolved handler: both match
38
+ // /.gsd/projects/<hash>/worktrees/ but require different resolution targets.
39
+ const extRe = /[/\\]\.gsd[/\\]projects[/\\][a-f0-9]+[/\\]worktrees(?:[/\\]|$)/;
40
+ const extMatch = extRe.exec(basePath);
41
+ if (extMatch) {
42
+ const matchStr = extMatch[0];
43
+ // Find the "/worktrees" portion within the match and slice up to it
44
+ const wtIdx = matchStr.search(/[/\\]worktrees(?:[/\\]|$)/);
45
+ const projectStateRoot = basePath.slice(0, extMatch.index + wtIdx);
46
+ return join(projectStateRoot, "gsd.db");
47
+ }
48
+
35
49
  // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/M001/...
36
50
  // The project root is everything before /.gsd/projects/ (#2517)
37
51
  const symlinkMarker = `${sep}.gsd${sep}projects${sep}`;
@@ -57,6 +71,7 @@ export function resolveProjectRootDbPath(basePath: string): string {
57
71
  }
58
72
  }
59
73
 
74
+
60
75
  return join(basePath, ".gsd", "gsd.db");
61
76
  }
62
77