gsd-pi 2.63.0 → 2.64.0

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 (353) hide show
  1. package/README.md +46 -134
  2. package/dist/cli.js +48 -6
  3. package/dist/headless-query.js +11 -1
  4. package/dist/help-text.js +4 -1
  5. package/dist/onboarding.js +15 -8
  6. package/dist/resource-loader.js +18 -3
  7. package/dist/resources/extensions/cmux/index.js +21 -12
  8. package/dist/resources/extensions/gsd/auto/detect-stuck.js +27 -0
  9. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
  10. package/dist/resources/extensions/gsd/auto/loop.js +4 -0
  11. package/dist/resources/extensions/gsd/auto/phases.js +157 -22
  12. package/dist/resources/extensions/gsd/auto/session.js +12 -0
  13. package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
  14. package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +124 -10
  16. package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
  17. package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
  18. package/dist/resources/extensions/gsd/auto-start.js +10 -21
  19. package/dist/resources/extensions/gsd/auto-timers.js +2 -1
  20. package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
  21. package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
  22. package/dist/resources/extensions/gsd/auto.js +19 -2
  23. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +147 -75
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
  25. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
  26. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
  27. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  28. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
  29. package/dist/resources/extensions/gsd/bootstrap/system-context.js +30 -2
  30. package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
  31. package/dist/resources/extensions/gsd/constants.js +42 -0
  32. package/dist/resources/extensions/gsd/db-writer.js +72 -4
  33. package/dist/resources/extensions/gsd/forensics.js +20 -4
  34. package/dist/resources/extensions/gsd/gsd-db.js +64 -17
  35. package/dist/resources/extensions/gsd/guided-flow.js +19 -0
  36. package/dist/resources/extensions/gsd/metrics.js +27 -1
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
  38. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  39. package/dist/resources/extensions/gsd/preferences.js +7 -2
  40. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  41. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  42. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  43. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  44. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
  45. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/system.md +4 -7
  47. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  48. package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
  49. package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
  50. package/dist/resources/extensions/gsd/safety/content-validator.js +73 -0
  51. package/dist/resources/extensions/gsd/safety/destructive-guard.js +34 -0
  52. package/dist/resources/extensions/gsd/safety/evidence-collector.js +109 -0
  53. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +83 -0
  54. package/dist/resources/extensions/gsd/safety/file-change-validator.js +71 -0
  55. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +91 -0
  56. package/dist/resources/extensions/gsd/safety/safety-harness.js +64 -0
  57. package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
  58. package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
  59. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
  60. package/dist/resources/extensions/gsd/state.js +74 -14
  61. package/dist/resources/extensions/gsd/status-guards.js +11 -0
  62. package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
  63. package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
  64. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
  65. package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
  66. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
  67. package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
  68. package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
  69. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
  70. package/dist/resources/extensions/mcp-client/auth.js +101 -0
  71. package/dist/resources/extensions/mcp-client/index.js +10 -1
  72. package/dist/resources/extensions/ollama/index.js +28 -22
  73. package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
  74. package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
  75. package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
  76. package/dist/resources/extensions/ollama/ollama-client.js +23 -32
  77. package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
  78. package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
  79. package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
  80. package/dist/update-cmd.js +4 -2
  81. package/dist/web/standalone/.next/BUILD_ID +1 -1
  82. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  83. package/dist/web/standalone/.next/build-manifest.json +2 -2
  84. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  85. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  86. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  108. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +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/cleanup/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  115. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  117. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  119. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  121. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  144. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  158. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/index.html +1 -1
  164. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  165. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  166. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  167. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  168. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  169. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  170. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  171. package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
  172. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  173. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  174. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  175. package/dist/welcome-screen.js +1 -1
  176. package/package.json +1 -1
  177. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  178. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  179. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  180. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  181. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  182. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  183. package/packages/pi-ai/dist/types.d.ts +16 -1
  184. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  185. package/packages/pi-ai/dist/types.js.map +1 -1
  186. package/packages/pi-ai/src/types.ts +18 -1
  187. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  188. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  190. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  192. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  193. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  194. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  195. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  196. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  198. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  199. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
  200. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
  201. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
  202. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
  203. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  204. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  206. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  207. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  208. package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
  209. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  211. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  212. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  213. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  214. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  215. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  216. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  217. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  218. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  219. package/packages/pi-coding-agent/package.json +1 -1
  220. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  221. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  222. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  223. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  224. package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
  225. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  226. package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
  227. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  228. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  229. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  230. package/pkg/package.json +1 -1
  231. package/src/resources/extensions/cmux/index.ts +18 -12
  232. package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
  233. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  234. package/src/resources/extensions/gsd/auto/loop.ts +5 -0
  235. package/src/resources/extensions/gsd/auto/phases.ts +194 -33
  236. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  237. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  238. package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
  239. package/src/resources/extensions/gsd/auto-post-unit.ts +141 -12
  240. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  241. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  242. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  243. package/src/resources/extensions/gsd/auto-timers.ts +2 -1
  244. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  245. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  246. package/src/resources/extensions/gsd/auto.ts +22 -1
  247. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
  248. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  249. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  250. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  251. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
  252. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  253. package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
  254. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  255. package/src/resources/extensions/gsd/constants.ts +44 -0
  256. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  257. package/src/resources/extensions/gsd/forensics.ts +21 -5
  258. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  259. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  260. package/src/resources/extensions/gsd/metrics.ts +28 -1
  261. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  262. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  263. package/src/resources/extensions/gsd/preferences.ts +9 -2
  264. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  265. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  266. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  267. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  268. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  269. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  270. package/src/resources/extensions/gsd/prompts/system.md +4 -7
  271. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  272. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  273. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  274. package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
  275. package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
  276. package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
  277. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
  278. package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
  279. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
  280. package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
  281. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  282. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  283. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  284. package/src/resources/extensions/gsd/state.ts +67 -12
  285. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  286. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  287. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  288. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  289. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  290. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  291. package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
  292. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  293. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  294. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  295. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  296. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  297. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  298. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  299. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  300. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  301. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
  302. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  303. package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
  304. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  305. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  306. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  307. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  308. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  309. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  310. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  311. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  312. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  313. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  314. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  315. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  316. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  317. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  318. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  319. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
  320. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  321. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  322. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  323. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  324. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  325. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  326. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  327. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  328. package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
  329. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  330. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  331. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  332. package/src/resources/extensions/gsd/types.ts +44 -22
  333. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  334. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  335. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  336. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  337. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  338. package/src/resources/extensions/mcp-client/index.ts +16 -1
  339. package/src/resources/extensions/ollama/index.ts +26 -25
  340. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  341. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  342. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
  343. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  344. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  345. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  346. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  347. package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
  348. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  349. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  350. package/src/resources/extensions/ollama/types.ts +23 -0
  351. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  352. /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → eebXKteM9EaWyseHKTjqp}/_buildManifest.js +0 -0
  353. /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → eebXKteM9EaWyseHKTjqp}/_ssgManifest.js +0 -0
@@ -6,9 +6,10 @@ import type { ExtensionContext } from "@gsd/pi-coding-agent";
6
6
 
7
7
  import { logWarning } from "../workflow-logger.js";
8
8
  import { debugTime } from "../debug-logger.js";
9
- import { loadPrompt } from "../prompt-loader.js";
9
+ import { loadPrompt, getTemplatesDir } from "../prompt-loader.js";
10
10
  import { readForensicsMarker } from "../forensics.js";
11
11
  import { resolveAllSkillReferences, renderPreferencesForSystemPrompt, loadEffectiveGSDPreferences } from "../preferences.js";
12
+ import { resolveSkillReference } from "../preferences-skills.js";
12
13
  import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
13
14
  import { hasSkillSnapshot, detectNewSkills, formatSkillsXml } from "../skill-discovery.js";
14
15
  import { getActiveAutoWorktreeContext } from "../auto-worktree.js";
@@ -20,6 +21,31 @@ import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../../cmux/index.
20
21
 
21
22
  const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
22
23
 
24
+ /**
25
+ * Bundled skill triggers — resolved dynamically at runtime instead of
26
+ * hardcoding absolute paths in the system prompt template. Only skills
27
+ * that actually exist on disk are included in the table. (#3575)
28
+ */
29
+ const BUNDLED_SKILL_TRIGGERS: Array<{ trigger: string; skill: string }> = [
30
+ { trigger: "Frontend UI - web components, pages, landing pages, dashboards, React/HTML/CSS, styling", skill: "frontend-design" },
31
+ { trigger: "macOS or iOS apps - SwiftUI, Xcode, App Store", skill: "swiftui" },
32
+ { trigger: "Debugging - complex bugs, failing tests, root-cause investigation after standard approaches fail", skill: "debug-like-expert" },
33
+ ];
34
+
35
+ function buildBundledSkillsTable(): string {
36
+ const cwd = process.cwd();
37
+ const rows: string[] = [];
38
+ for (const { trigger, skill } of BUNDLED_SKILL_TRIGGERS) {
39
+ const resolution = resolveSkillReference(skill, cwd);
40
+ if (resolution.method === "unresolved") continue; // skill not installed — omit from prompt
41
+ rows.push(`| ${trigger} | \`${resolution.resolvedPath}\` |`);
42
+ }
43
+ if (rows.length === 0) {
44
+ return "*No bundled skills found. Install skills to `~/.agents/skills/` or `~/.claude/skills/`.*";
45
+ }
46
+ return `| Trigger | Skill to load |\n|---|---|\n${rows.join("\n")}`;
47
+ }
48
+
23
49
  function warnDeprecatedAgentInstructions(): void {
24
50
  const paths = [
25
51
  join(gsdHome, "agent-instructions.md"),
@@ -43,7 +69,10 @@ export async function buildBeforeAgentStartResult(
43
69
  if (!existsSync(join(process.cwd(), ".gsd"))) return undefined;
44
70
 
45
71
  const stopContextTimer = debugTime("context-inject");
46
- const systemContent = loadPrompt("system");
72
+ const systemContent = loadPrompt("system", {
73
+ bundledSkillsTable: buildBundledSkillsTable(),
74
+ templatesDir: getTemplatesDir(),
75
+ });
47
76
  const loadedPreferences = loadEffectiveGSDPreferences();
48
77
  if (shouldPromptToEnableCmux(loadedPreferences?.preferences)) {
49
78
  markCmuxPromptShown();
@@ -43,21 +43,27 @@ export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined,
43
43
  );
44
44
  }
45
45
 
46
- export async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
46
+ /** Parse doctor command args into structured flags and positionals (pure, no I/O). */
47
+ export function parseDoctorArgs(args: string) {
47
48
  const trimmed = args.trim();
48
- // Extract flags before positional parsing
49
49
  const jsonMode = trimmed.includes("--json");
50
50
  const dryRun = trimmed.includes("--dry-run");
51
+ const fixFlag = trimmed.includes("--fix");
51
52
  const includeBuild = trimmed.includes("--build");
52
53
  const includeTests = trimmed.includes("--test");
53
- const stripped = trimmed.replace(/--json|--dry-run|--build|--test/g, "").trim();
54
+ const stripped = trimmed.replace(/--json|--dry-run|--build|--test|--fix/g, "").trim();
54
55
  const parts = stripped ? stripped.split(/\s+/) : [];
55
56
  const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
56
57
  const requestedScope = mode === "doctor" ? parts[0] : parts[1];
58
+ return { jsonMode, dryRun, fixFlag, includeBuild, includeTests, mode, requestedScope };
59
+ }
60
+
61
+ export async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
62
+ const { jsonMode, dryRun, fixFlag, includeBuild, includeTests, mode, requestedScope } = parseDoctorArgs(args);
57
63
  const scope = await selectDoctorScope(projectRoot(), requestedScope);
58
64
  const effectiveScope = mode === "audit" ? requestedScope : scope;
59
65
  const report = await runGSDDoctor(projectRoot(), {
60
- fix: mode === "fix" || mode === "heal" || dryRun,
66
+ fix: mode === "fix" || mode === "heal" || dryRun || fixFlag,
61
67
  dryRun,
62
68
  scope: effectiveScope,
63
69
  includeBuild,
@@ -19,3 +19,47 @@ export const DIR_CACHE_MAX = 200;
19
19
 
20
20
  /** Max parse-cache entries before eviction. */
21
21
  export const CACHE_MAX = 50;
22
+
23
+ // ─── Tool Scoping ─────────────────────────────────────────────────────────────
24
+
25
+ /**
26
+ * GSD tools allowed during discuss flows (#2949).
27
+ *
28
+ * xAI/Grok (and potentially other providers with grammar-based constrained
29
+ * decoding) return "Grammar is too complex" (HTTP 400) when the combined
30
+ * tool schemas exceed their internal grammar limit. The full GSD tool set
31
+ * registers ~33 tools with deeply nested schemas; discuss flows only need
32
+ * a small subset.
33
+ *
34
+ * By scoping tools to this allowlist during discuss dispatches, the grammar
35
+ * sent to the provider stays well under provider limits.
36
+ *
37
+ * Included tools and why:
38
+ * - gsd_summary_save: writes CONTEXT.md artifacts (all discuss prompts)
39
+ * - gsd_save_summary: alias for above
40
+ * - gsd_decision_save: records decisions (discuss.md output phase)
41
+ * - gsd_save_decision: alias for above
42
+ * - gsd_plan_milestone: writes roadmap (discuss.md single/multi milestone)
43
+ * - gsd_milestone_plan: alias for above
44
+ * - gsd_milestone_generate_id: generates milestone IDs (discuss.md multi-milestone)
45
+ * - gsd_generate_milestone_id: alias for above
46
+ * - gsd_requirement_update: updates requirements during discuss
47
+ * - gsd_update_requirement: alias for above
48
+ */
49
+ export const DISCUSS_TOOLS_ALLOWLIST: readonly string[] = [
50
+ // Context / summary writing
51
+ "gsd_summary_save",
52
+ "gsd_save_summary",
53
+ // Decision recording
54
+ "gsd_decision_save",
55
+ "gsd_save_decision",
56
+ // Milestone planning (needed for discuss.md output phase)
57
+ "gsd_plan_milestone",
58
+ "gsd_milestone_plan",
59
+ // Milestone ID generation (multi-milestone flow)
60
+ "gsd_milestone_generate_id",
61
+ "gsd_generate_milestone_id",
62
+ // Requirement updates
63
+ "gsd_requirement_update",
64
+ "gsd_update_requirement",
65
+ ];
@@ -469,6 +469,23 @@ export async function saveDecisionToDb(
469
469
  adapter?.prepare('DELETE FROM decisions WHERE id = :id').run({ ':id': id });
470
470
  throw diskErr;
471
471
  }
472
+ // #2661: When a decision defers a slice, update the slice status in the DB
473
+ // so the dispatcher skips it. Without this, STATE.md and DECISIONS.md are
474
+ // in split-brain: the decision says "deferred" but the state still says
475
+ // "active", causing auto-mode to keep dispatching the deferred work.
476
+ try {
477
+ const sliceRef = extractDeferredSliceRef(fields);
478
+ if (sliceRef) {
479
+ db.updateSliceStatus(sliceRef.milestoneId, sliceRef.sliceId, 'deferred');
480
+ }
481
+ } catch (deferErr) {
482
+ // Non-fatal — log but don't fail the decision save
483
+ logError('manifest', 'failed to update deferred slice status', {
484
+ fn: 'saveDecisionToDb',
485
+ error: String((deferErr as Error).message),
486
+ });
487
+ }
488
+
472
489
  // Invalidate file-read caches so deriveState() sees the updated markdown.
473
490
  // Do NOT clear the artifacts table — we just wrote to it intentionally.
474
491
  invalidateStateCache();
@@ -482,6 +499,39 @@ export async function saveDecisionToDb(
482
499
  }
483
500
  }
484
501
 
502
+ /**
503
+ * Extract a milestone/slice reference from a deferral decision.
504
+ *
505
+ * Detects deferrals by checking:
506
+ * - scope contains "defer" (e.g., "deferral", "defer")
507
+ * - choice or decision contains "defer" + an M###/S## pattern
508
+ *
509
+ * Returns { milestoneId, sliceId } if found, null otherwise.
510
+ */
511
+ export function extractDeferredSliceRef(
512
+ fields: Pick<SaveDecisionFields, 'scope' | 'decision' | 'choice'>,
513
+ ): { milestoneId: string; sliceId: string } | null {
514
+ const isDeferral =
515
+ /\bdefer(?:ral|red|ring|s)?\b/i.test(fields.scope) ||
516
+ /\bdefer(?:ral|red|ring|s)?\b/i.test(fields.choice) ||
517
+ /\bdefer(?:ral|red|ring|s)?\b/i.test(fields.decision);
518
+
519
+ if (!isDeferral) return null;
520
+
521
+ // Look for M###/S## pattern in choice first, then decision
522
+ const slicePattern = /\b(M\d{3,4})\/(S\d{2,3})\b/;
523
+ const choiceMatch = fields.choice.match(slicePattern);
524
+ if (choiceMatch) {
525
+ return { milestoneId: choiceMatch[1], sliceId: choiceMatch[2] };
526
+ }
527
+ const decisionMatch = fields.decision.match(slicePattern);
528
+ if (decisionMatch) {
529
+ return { milestoneId: decisionMatch[1], sliceId: decisionMatch[2] };
530
+ }
531
+
532
+ return null;
533
+ }
534
+
485
535
  // ─── Update Requirement in DB + Regenerate Markdown ───────────────────────
486
536
 
487
537
  /**
@@ -496,11 +546,35 @@ export async function updateRequirementInDb(
496
546
  try {
497
547
  const db = await import('./gsd-db.js');
498
548
 
499
- const existing = db.getRequirementById(id);
549
+ let existing = db.getRequirementById(id);
550
+
551
+ // If requirement doesn't exist in DB, seed the entire requirements table
552
+ // from REQUIREMENTS.md first (#3346). This handles the standard workflow
553
+ // where requirements are authored in markdown during discussion but never
554
+ // imported into the database — making gsd_requirement_update always fail
555
+ // with "not_found" at milestone completion.
556
+ if (!existing) {
557
+ const reqFilePath = resolveGsdRootFile(basePath, 'REQUIREMENTS');
558
+ try {
559
+ const content = readFileSync(reqFilePath, 'utf-8');
560
+ const { parseRequirementsSections } = await import('./md-importer.js');
561
+ const parsed = parseRequirementsSections(content);
562
+ if (parsed.length > 0) {
563
+ logWarning('manifest', `Seeding ${parsed.length} requirements from REQUIREMENTS.md into DB (first update triggers import)`, { fn: 'updateRequirementInDb' });
564
+ for (const req of parsed) {
565
+ // Only seed if not already in DB (avoid overwriting concurrent inserts)
566
+ if (!db.getRequirementById(req.id)) {
567
+ db.upsertRequirement(req);
568
+ }
569
+ }
570
+ // Re-check after seeding
571
+ existing = db.getRequirementById(id);
572
+ }
573
+ } catch {
574
+ // REQUIREMENTS.md missing or unparseable — fall through to skeleton
575
+ }
576
+ }
500
577
 
501
- // If requirement doesn't exist in DB, create a skeleton and merge updates.
502
- // This handles the case where requirements were written to REQUIREMENTS.md
503
- // but never imported into the database (see #2919).
504
578
  const base: Requirement = existing ?? {
505
579
  id,
506
580
  class: '',
@@ -38,7 +38,7 @@ import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./comm
38
38
 
39
39
  // ─── Types ────────────────────────────────────────────────────────────────────
40
40
 
41
- interface ForensicAnomaly {
41
+ export interface ForensicAnomaly {
42
42
  type: "stuck-loop" | "cost-spike" | "timeout" | "missing-artifact" | "crash" | "doctor-issue" | "error-trace" | "journal-stuck" | "journal-guard-block" | "journal-rapid-iterations" | "journal-worktree-failure";
43
43
  severity: "info" | "warning" | "error";
44
44
  unitType?: string;
@@ -640,13 +640,29 @@ function getDbCompletionCounts(): DbCompletionCounts | null {
640
640
 
641
641
  // ─── Anomaly Detectors ───────────────────────────────────────────────────────
642
642
 
643
- function detectStuckLoops(units: UnitMetrics[], anomalies: ForensicAnomaly[]): void {
644
- const counts = new Map<string, number>();
643
+ /**
644
+ * Detect units that were dispatched multiple times (stuck in a loop).
645
+ *
646
+ * Counts distinct dispatches by grouping on (type, id, startedAt) first to
647
+ * collapse idle-watchdog duplicate snapshots (#1943), then counts unique
648
+ * startedAt values per type/id to determine actual dispatch count.
649
+ *
650
+ * Exported for testability.
651
+ */
652
+ export function detectStuckLoops(units: UnitMetrics[], anomalies: ForensicAnomaly[]): void {
653
+ // First, collect unique startedAt values per type/id key
654
+ const dispatchMap = new Map<string, Set<number>>();
645
655
  for (const u of units) {
646
656
  const key = `${u.type}/${u.id}`;
647
- counts.set(key, (counts.get(key) ?? 0) + 1);
657
+ let starts = dispatchMap.get(key);
658
+ if (!starts) {
659
+ starts = new Set();
660
+ dispatchMap.set(key, starts);
661
+ }
662
+ starts.add(u.startedAt);
648
663
  }
649
- for (const [key, count] of counts) {
664
+ for (const [key, starts] of dispatchMap) {
665
+ const count = starts.size;
650
666
  if (count > 1) {
651
667
  const [unitType, ...idParts] = key.split("/");
652
668
  anomalies.push({
@@ -1136,11 +1136,12 @@ export function insertMilestone(m: {
1136
1136
  });
1137
1137
  }
1138
1138
 
1139
- export function upsertMilestonePlanning(milestoneId: string, planning: Partial<MilestonePlanningRecord>, title?: string): void {
1139
+ export function upsertMilestonePlanning(milestoneId: string, planning: Partial<MilestonePlanningRecord> & { title?: string; status?: string }): void {
1140
1140
  if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1141
1141
  currentDb.prepare(
1142
1142
  `UPDATE milestones SET
1143
- title = COALESCE(:title, title),
1143
+ title = COALESCE(NULLIF(:title, ''), title),
1144
+ status = COALESCE(NULLIF(:status, ''), status),
1144
1145
  vision = COALESCE(:vision, vision),
1145
1146
  success_criteria = COALESCE(:success_criteria, success_criteria),
1146
1147
  key_risks = COALESCE(:key_risks, key_risks),
@@ -1155,7 +1156,8 @@ export function upsertMilestonePlanning(milestoneId: string, planning: Partial<M
1155
1156
  WHERE id = :id`,
1156
1157
  ).run({
1157
1158
  ":id": milestoneId,
1158
- ":title": title ?? null,
1159
+ ":title": planning.title ?? "",
1160
+ ":status": planning.status ?? "",
1159
1161
  ":vision": planning.vision ?? null,
1160
1162
  ":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
1161
1163
  ":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
@@ -1183,13 +1185,25 @@ export function insertSlice(s: {
1183
1185
  }): void {
1184
1186
  if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1185
1187
  currentDb.prepare(
1186
- `INSERT OR IGNORE INTO slices (
1188
+ `INSERT INTO slices (
1187
1189
  milestone_id, id, title, status, risk, depends, demo, created_at,
1188
1190
  goal, success_criteria, proof_level, integration_closure, observability_impact, sequence
1189
1191
  ) VALUES (
1190
1192
  :milestone_id, :id, :title, :status, :risk, :depends, :demo, :created_at,
1191
1193
  :goal, :success_criteria, :proof_level, :integration_closure, :observability_impact, :sequence
1192
- )`,
1194
+ )
1195
+ ON CONFLICT (milestone_id, id) DO UPDATE SET
1196
+ title = CASE WHEN :raw_title IS NOT NULL THEN excluded.title ELSE slices.title END,
1197
+ status = CASE WHEN slices.status IN ('complete', 'done') THEN slices.status ELSE excluded.status END,
1198
+ risk = CASE WHEN :raw_risk IS NOT NULL THEN excluded.risk ELSE slices.risk END,
1199
+ depends = excluded.depends,
1200
+ demo = CASE WHEN :raw_demo IS NOT NULL THEN excluded.demo ELSE slices.demo END,
1201
+ goal = CASE WHEN :raw_goal IS NOT NULL THEN excluded.goal ELSE slices.goal END,
1202
+ success_criteria = CASE WHEN :raw_success_criteria IS NOT NULL THEN excluded.success_criteria ELSE slices.success_criteria END,
1203
+ proof_level = CASE WHEN :raw_proof_level IS NOT NULL THEN excluded.proof_level ELSE slices.proof_level END,
1204
+ integration_closure = CASE WHEN :raw_integration_closure IS NOT NULL THEN excluded.integration_closure ELSE slices.integration_closure END,
1205
+ observability_impact = CASE WHEN :raw_observability_impact IS NOT NULL THEN excluded.observability_impact ELSE slices.observability_impact END,
1206
+ sequence = CASE WHEN :raw_sequence IS NOT NULL THEN excluded.sequence ELSE slices.sequence END`,
1193
1207
  ).run({
1194
1208
  ":milestone_id": s.milestoneId,
1195
1209
  ":id": s.id,
@@ -1205,6 +1219,16 @@ export function insertSlice(s: {
1205
1219
  ":integration_closure": s.planning?.integrationClosure ?? "",
1206
1220
  ":observability_impact": s.planning?.observabilityImpact ?? "",
1207
1221
  ":sequence": s.sequence ?? 0,
1222
+ // Raw sentinel params: NULL when caller omitted the field, used in ON CONFLICT guards
1223
+ ":raw_title": s.title ?? null,
1224
+ ":raw_risk": s.risk ?? null,
1225
+ ":raw_demo": s.demo ?? null,
1226
+ ":raw_goal": s.planning?.goal ?? null,
1227
+ ":raw_success_criteria": s.planning?.successCriteria ?? null,
1228
+ ":raw_proof_level": s.planning?.proofLevel ?? null,
1229
+ ":raw_integration_closure": s.planning?.integrationClosure ?? null,
1230
+ ":raw_observability_impact": s.planning?.observabilityImpact ?? null,
1231
+ ":raw_sequence": s.sequence ?? null,
1208
1232
  });
1209
1233
  }
1210
1234
 
@@ -1884,20 +1908,32 @@ export function reconcileWorktreeDb(
1884
1908
  FROM wt.milestones
1885
1909
  `).run());
1886
1910
 
1887
- // Merge slices — preserve worktree progress (status, summaries, planning)
1911
+ // Merge slices — preserve worktree progress but never downgrade completed status (#2558).
1912
+ // Uses INSERT OR REPLACE with a subquery that picks the best status — if the main DB
1913
+ // already has a completed slice, keep that status even if the worktree copy is stale.
1888
1914
  merged.slices = countChanges(adapter.prepare(`
1889
1915
  INSERT OR REPLACE INTO slices (
1890
1916
  milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
1891
1917
  full_summary_md, full_uat_md, goal, success_criteria, proof_level,
1892
1918
  integration_closure, observability_impact, sequence, replan_triggered_at
1893
1919
  )
1894
- SELECT milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
1895
- full_summary_md, full_uat_md, goal, success_criteria, proof_level,
1896
- integration_closure, observability_impact, sequence, replan_triggered_at
1897
- FROM wt.slices
1920
+ SELECT w.milestone_id, w.id, w.title,
1921
+ CASE
1922
+ WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
1923
+ THEN m.status ELSE w.status
1924
+ END,
1925
+ w.risk, w.depends, w.demo, w.created_at,
1926
+ CASE
1927
+ WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
1928
+ THEN m.completed_at ELSE w.completed_at
1929
+ END,
1930
+ w.full_summary_md, w.full_uat_md, w.goal, w.success_criteria, w.proof_level,
1931
+ w.integration_closure, w.observability_impact, w.sequence, w.replan_triggered_at
1932
+ FROM wt.slices w
1933
+ LEFT JOIN slices m ON m.milestone_id = w.milestone_id AND m.id = w.id
1898
1934
  `).run());
1899
1935
 
1900
- // Merge tasks — preserve execution results, status, summaries
1936
+ // Merge tasks — preserve execution results, never downgrade completed status (#2558)
1901
1937
  merged.tasks = countChanges(adapter.prepare(`
1902
1938
  INSERT OR REPLACE INTO tasks (
1903
1939
  milestone_id, slice_id, id, title, status, one_liner, narrative,
@@ -1906,12 +1942,23 @@ export function reconcileWorktreeDb(
1906
1942
  description, estimate, files, verify, inputs, expected_output,
1907
1943
  observability_impact, full_plan_md, sequence
1908
1944
  )
1909
- SELECT milestone_id, slice_id, id, title, status, one_liner, narrative,
1910
- verification_result, duration, completed_at, blocker_discovered,
1911
- deviations, known_issues, key_files, key_decisions, full_summary_md,
1912
- description, estimate, files, verify, inputs, expected_output,
1913
- observability_impact, full_plan_md, sequence
1914
- FROM wt.tasks
1945
+ SELECT w.milestone_id, w.slice_id, w.id, w.title,
1946
+ CASE
1947
+ WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
1948
+ THEN m.status ELSE w.status
1949
+ END,
1950
+ w.one_liner, w.narrative,
1951
+ w.verification_result, w.duration,
1952
+ CASE
1953
+ WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
1954
+ THEN m.completed_at ELSE w.completed_at
1955
+ END,
1956
+ w.blocker_discovered,
1957
+ w.deviations, w.known_issues, w.key_files, w.key_decisions, w.full_summary_md,
1958
+ w.description, w.estimate, w.files, w.verify, w.inputs, w.expected_output,
1959
+ w.observability_impact, w.full_plan_md, w.sequence
1960
+ FROM wt.tasks w
1961
+ LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
1915
1962
  `).run());
1916
1963
 
1917
1964
  // Merge memories — keep worktree-learned insights
@@ -39,6 +39,7 @@ import { debugLog } from "./debug-logger.js";
39
39
  import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMilestoneIds, clearReservedMilestoneIds } from "./milestone-ids.js";
40
40
  import { parkMilestone, discardMilestone } from "./milestone-actions.js";
41
41
  import { selectAndApplyModel } from "./auto-model-selection.js";
42
+ import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
42
43
 
43
44
  // ─── Re-exports (preserve public API for existing importers) ────────────────
44
45
  export {
@@ -289,6 +290,27 @@ async function dispatchWorkflow(
289
290
  }
290
291
  }
291
292
 
293
+ // Scope tools for discuss flows (#2949).
294
+ // Providers with grammar-based constrained decoding (xAI/Grok) return
295
+ // "Grammar is too complex" when the combined tool schema is too large.
296
+ // Discuss flows only need a small subset of GSD tools — strip the heavy
297
+ // planning/execution/completion tools to keep the grammar within limits.
298
+ if (unitType?.startsWith("discuss-")) {
299
+ const currentTools = pi.getActiveTools();
300
+ // Keep all non-GSD tools (builtins, other extensions) and only the
301
+ // GSD tools on the discuss allowlist.
302
+ const scopedTools = currentTools.filter(
303
+ (t) => !t.startsWith("gsd_") || DISCUSS_TOOLS_ALLOWLIST.includes(t),
304
+ );
305
+ pi.setActiveTools(scopedTools);
306
+ debugLog("discuss-tool-scoping", {
307
+ unitType,
308
+ before: currentTools.length,
309
+ after: scopedTools.length,
310
+ removed: currentTools.length - scopedTools.length,
311
+ });
312
+ }
313
+
292
314
  const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
293
315
  const workflow = readFileSync(workflowPath, "utf-8");
294
316
 
@@ -567,7 +567,34 @@ export function loadLedgerFromDisk(base: string): MetricsLedger | null {
567
567
  }
568
568
 
569
569
  function loadLedger(base: string): MetricsLedger {
570
- return loadJsonFile(metricsPath(base), isMetricsLedger, defaultLedger);
570
+ const raw = loadJsonFile(metricsPath(base), isMetricsLedger, defaultLedger);
571
+ const before = raw.units.length;
572
+ raw.units = deduplicateUnits(raw.units);
573
+ if (raw.units.length < before) {
574
+ // Persist the cleaned ledger so duplicates don't re-accumulate
575
+ saveLedger(base, raw);
576
+ }
577
+ return raw;
578
+ }
579
+
580
+ /**
581
+ * Collapse duplicate entries with the same (type, id, startedAt) triple.
582
+ * Keeps the entry with the highest finishedAt (the most complete snapshot).
583
+ *
584
+ * This is a defensive measure against idle-watchdog race conditions that can
585
+ * produce duplicate entries on disk despite the in-memory idempotency guard
586
+ * in snapshotUnitMetrics(). See #1943.
587
+ */
588
+ function deduplicateUnits(units: UnitMetrics[]): UnitMetrics[] {
589
+ const map = new Map<string, UnitMetrics>();
590
+ for (const u of units) {
591
+ const key = `${u.type}\0${u.id}\0${u.startedAt}`;
592
+ const existing = map.get(key);
593
+ if (!existing || u.finishedAt > existing.finishedAt) {
594
+ map.set(key, u);
595
+ }
596
+ }
597
+ return Array.from(map.values());
571
598
  }
572
599
 
573
600
  function saveLedger(base: string, data: MetricsLedger): void {
@@ -724,10 +724,12 @@ export function nativeAddAllWithExclusions(basePath: string, exclusions: readonl
724
724
  return;
725
725
  }
726
726
  // When .gsd is a symlink, git rejects `:!.gsd/...` pathspecs with
727
- // "beyond a symbolic link". Fall back to plain `git add -A` which
728
- // respects .gitignore (where .gsd/ is listed by default).
727
+ // "beyond a symbolic link". Fall back to `git add -u` which only
728
+ // stages changes to already-tracked files O(tracked) not O(filesystem).
729
+ // Using `git add -A` here would traverse the entire working tree,
730
+ // hanging indefinitely on repos with large untracked data dirs. (#1977)
729
731
  if (stderr.includes("beyond a symbolic link")) {
730
- nativeAddAll(basePath);
732
+ gitFileExec(basePath, ["add", "-u"]);
731
733
  return;
732
734
  }
733
735
  throw new GSDError(GSD_GIT_ERROR, `git add -A with exclusions failed in ${basePath}: ${getErrorMessage(err)}`);
@@ -104,6 +104,8 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
104
104
  "context_management",
105
105
  "experimental",
106
106
  "codebase",
107
+ "slice_parallel",
108
+ "safety_harness",
107
109
  ]);
108
110
 
109
111
  /** Canonical list of all dispatch unit types. */
@@ -288,6 +290,20 @@ export interface GSDPreferences {
288
290
  experimental?: ExperimentalPreferences;
289
291
  /** Configuration for the codebase map generator (/gsd codebase). */
290
292
  codebase?: CodebaseMapPreferences;
293
+ /** Slice-level parallelism within a milestone. Disabled by default. */
294
+ slice_parallel?: { enabled?: boolean; max_workers?: number };
295
+ /** LLM safety harness configuration. Monitors, validates, and constrains LLM behavior during auto-mode. Enabled by default with warn-and-continue policy. */
296
+ safety_harness?: {
297
+ enabled?: boolean;
298
+ evidence_collection?: boolean;
299
+ file_change_validation?: boolean;
300
+ evidence_cross_reference?: boolean;
301
+ destructive_command_warnings?: boolean;
302
+ content_validation?: boolean;
303
+ checkpoints?: boolean;
304
+ auto_rollback?: boolean;
305
+ timeout_scale_cap?: number;
306
+ };
291
307
  }
292
308
 
293
309
  export interface LoadedGSDPreferences {
@@ -224,9 +224,13 @@ export function parsePreferencesMarkdown(content: string): GSDPreferences | null
224
224
  return parseHeadingListFormat(content);
225
225
  }
226
226
 
227
- if (!_warnedUnrecognizedFormat) {
227
+ // Warn when a non-empty file exists but lacks frontmatter delimiters (#2036).
228
+ if (content.trim().length > 0 && !_warnedUnrecognizedFormat) {
228
229
  _warnedUnrecognizedFormat = true;
229
- console.warn("[parsePreferencesMarkdown] preferences.md exists but uses an unrecognized format — skipping.");
230
+ console.warn(
231
+ "[GSD] Warning: preferences file has unrecognized format — content does not use YAML frontmatter delimiters (---). " +
232
+ "Wrap your preferences in --- fences. See https://github.com/gsd-build/gsd-2/issues/2036",
233
+ );
230
234
  }
231
235
  return null;
232
236
  }
@@ -384,6 +388,9 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
384
388
  ].filter(Boolean),
385
389
  }
386
390
  : undefined,
391
+ slice_parallel: (base.slice_parallel || override.slice_parallel)
392
+ ? { ...(base.slice_parallel ?? {}), ...(override.slice_parallel ?? {}) }
393
+ : undefined,
387
394
  };
388
395
  }
389
396
 
@@ -51,6 +51,14 @@ const __extensionDir = resolveExtensionDir();
51
51
  const promptsDir = join(__extensionDir, "prompts");
52
52
  const templatesDir = join(__extensionDir, "templates");
53
53
 
54
+ /**
55
+ * Return the resolved templates directory path for use in prompts.
56
+ * Avoids hardcoding `~/.gsd/agent/extensions/gsd/templates/` in templates. (#3575)
57
+ */
58
+ export function getTemplatesDir(): string {
59
+ return templatesDir;
60
+ }
61
+
54
62
  // Cache all templates eagerly at module load — a running session uses the
55
63
  // template versions that were on disk at startup, immune to later overwrites.
56
64
  const templateCache = new Map<string, string>();
@@ -24,6 +24,8 @@ Then:
24
24
  7. Fill the **Decision Re-evaluation** table in the milestone summary. For each key decision from `.gsd/DECISIONS.md` made during this milestone, evaluate whether it is still valid given what was actually built. Flag decisions that should be revisited next milestone.
25
25
  8. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
26
26
 
27
+ **DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')` — the engine owns the WAL connection. Use `gsd_milestone_status` to read milestone and slice state. All data you need is already inlined in the context above or accessible via the `gsd_*` tools — never via direct SQL.
28
+
27
29
  ### Verification Gate — STOP if verification failed
28
30
 
29
31
  **If ANY verification failure was recorded in steps 3, 4, or 5, you MUST follow the failure path below. Do NOT proceed to step 9.**
@@ -35,6 +35,8 @@ Then:
35
35
 
36
36
  **Autonomous execution:** Do not call `ask_user_questions` or `secure_env_collect`. You are running in auto-mode — there is no human available to answer questions. Make reasonable assumptions and document them in the slice summary. If a decision genuinely requires human input, note it in the summary and proceed with the best available option.
37
37
 
38
+ **File system safety:** Task summaries are preloaded in the inlined context above. If you need to re-read any of them, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` to list file paths first — never pass `{{slicePath}}` or any other directory path directly to the `read` tool. The `read` tool only accepts file paths, not directories.
39
+
38
40
  **You MUST call `gsd_complete_slice` with the slice summary and UAT content before finishing. The tool persists to both DB and disk and renders `{{sliceSummaryPath}}` and `{{sliceUatPath}}` automatically.**
39
41
 
40
42
  When done, say: "Slice {{sliceId}} complete."
@@ -9,6 +9,7 @@ Rules:
9
9
  4. For missing summaries or UAT files, generate the real artifact from existing slice/task context when possible — do not leave placeholders if you can reconstruct the real content.
10
10
  5. After each repair cluster, verify the relevant invariant directly from disk.
11
11
  6. When done, rerun `/gsd doctor {{doctorCommandSuffix}}` mentally by ensuring the remaining issue set for this scope is reduced or cleared.
12
+ 7. Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')` — use `gsd_milestone_status` to inspect DB state. Direct access bypasses the WAL connection owned by the engine and can corrupt in-flight writes.
12
13
 
13
14
  ## Doctor Summary
14
15
 
@@ -116,6 +116,8 @@ A unit dispatched more than once (`type/id` appears multiple times) indicates a
116
116
 
117
117
  5. **Read the actual GSD source code** at `{{gsdSourceDir}}` to confirm or deny each hypothesis. Do not guess what code does — read it.
118
118
 
119
+ **DB inspection:** If you need to check DB state as part of investigation, use `gsd_milestone_status` — never run `sqlite3 .gsd/gsd.db` or `node -e require('better-sqlite3')` directly. The engine holds a WAL write lock; direct access will either fail or return stale data.
120
+
119
121
  6. **Trace the code path** from the entry point (usually `auto-loop.ts` dispatch or `auto-dispatch.ts`) through to the failure point. Follow function calls across files.
120
122
 
121
123
  7. **Identify the specific file and line** where the bug lives. Determine what kind of defect it is:
@@ -63,4 +63,6 @@ If `.gsd/REQUIREMENTS.md` exists and requirement ownership or status changed, up
63
63
 
64
64
  {{commitInstruction}}
65
65
 
66
+ **DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')`. Use `gsd_milestone_status` to read current milestone and slice state. All roadmap mutations go through `gsd_reassess_roadmap` — the tool writes to the DB and re-renders ROADMAP.md atomically.
67
+
66
68
  When done, say: "Roadmap reassessed."