gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.786f0ff

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 (350) 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 +20 -20
  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 +2 -2
  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 +20 -20
  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/package.json +1 -1
  176. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  177. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  178. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  179. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  180. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  181. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  182. package/packages/pi-ai/dist/types.d.ts +16 -1
  183. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  184. package/packages/pi-ai/dist/types.js.map +1 -1
  185. package/packages/pi-ai/src/types.ts +18 -1
  186. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  187. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  189. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  191. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  193. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  195. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  197. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
  199. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
  201. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
  202. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  203. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  204. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  206. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
  208. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  209. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  210. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  211. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  212. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  214. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  216. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  217. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  218. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  219. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  220. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  221. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  222. package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
  223. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  224. package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
  225. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  226. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  227. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  228. package/src/resources/extensions/cmux/index.ts +18 -12
  229. package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
  230. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  231. package/src/resources/extensions/gsd/auto/loop.ts +5 -0
  232. package/src/resources/extensions/gsd/auto/phases.ts +194 -33
  233. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  234. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  235. package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
  236. package/src/resources/extensions/gsd/auto-post-unit.ts +141 -12
  237. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  238. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  239. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  240. package/src/resources/extensions/gsd/auto-timers.ts +2 -1
  241. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  242. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  243. package/src/resources/extensions/gsd/auto.ts +22 -1
  244. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
  245. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  246. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  247. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  248. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
  249. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  250. package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
  251. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  252. package/src/resources/extensions/gsd/constants.ts +44 -0
  253. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  254. package/src/resources/extensions/gsd/forensics.ts +21 -5
  255. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  256. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  257. package/src/resources/extensions/gsd/metrics.ts +28 -1
  258. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  259. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  260. package/src/resources/extensions/gsd/preferences.ts +9 -2
  261. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  262. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  263. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  264. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  265. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  266. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  267. package/src/resources/extensions/gsd/prompts/system.md +4 -7
  268. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  269. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  270. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  271. package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
  272. package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
  273. package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
  274. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
  275. package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
  276. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
  277. package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
  278. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  279. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  280. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  281. package/src/resources/extensions/gsd/state.ts +67 -12
  282. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  283. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  284. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  285. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  286. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  287. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  288. package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
  289. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  290. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  291. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  292. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  293. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  294. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  295. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  296. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  297. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  298. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
  299. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  300. package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
  301. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  302. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  303. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  304. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  305. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  306. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  307. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  308. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  309. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  310. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  311. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  312. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  313. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  314. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  315. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  316. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
  317. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  318. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  319. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  320. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  321. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  322. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  323. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  324. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  325. package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
  326. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  327. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  328. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  329. package/src/resources/extensions/gsd/types.ts +44 -22
  330. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  331. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  332. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  333. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  334. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  335. package/src/resources/extensions/mcp-client/index.ts +16 -1
  336. package/src/resources/extensions/ollama/index.ts +26 -25
  337. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  338. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  339. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
  340. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  341. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  342. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  343. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  344. package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
  345. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  346. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  347. package/src/resources/extensions/ollama/types.ts +23 -0
  348. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  349. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_buildManifest.js +0 -0
  350. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_ssgManifest.js +0 -0
@@ -6,12 +6,13 @@ import { parseSummary, loadFile, parseRequirementCounts, parseContextDependsOn,
6
6
  import { resolveMilestoneFile, resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTasksDir, resolveGsdRootFile, gsdRoot, } from './paths.js';
7
7
  import { findMilestoneIds } from './milestone-ids.js';
8
8
  import { loadQueueOrder, sortByQueueOrder } from './queue-order.js';
9
+ import { isDeferredStatus } from './status-guards.js';
9
10
  import { nativeBatchParseGsdFiles } from './native-parser-bridge.js';
10
11
  import { join, resolve } from 'path';
11
12
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
12
13
  import { debugCount, debugTime } from './debug-logger.js';
13
- import { extractVerdict } from './verdict-parser.js';
14
14
  import { logWarning, logError } from './workflow-logger.js';
15
+ import { extractVerdict } from './verdict-parser.js';
15
16
  import { isDbAvailable, getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getReplanHistory, getSlice, insertMilestone, insertSlice, updateTaskStatus, getPendingSliceGateCount, } from './gsd-db.js';
16
17
  /**
17
18
  * A "ghost" milestone directory contains only META.json (and no substantive
@@ -568,13 +569,43 @@ export async function deriveStateFromDb(basePath) {
568
569
  const doneSliceIds = new Set(activeMilestoneSlices.filter(s => isStatusDone(s.status)).map(s => s.id));
569
570
  let activeSlice = null;
570
571
  let activeSliceRow = null;
571
- for (const s of activeMilestoneSlices) {
572
- if (isStatusDone(s.status))
573
- continue;
574
- if (s.depends.every(dep => doneSliceIds.has(dep))) {
575
- activeSlice = { id: s.id, title: s.title };
576
- activeSliceRow = s;
577
- break;
572
+ // ── Slice-level parallel worker isolation ─────────────────────────────
573
+ // When GSD_SLICE_LOCK is set, this process is a parallel worker scoped
574
+ // to a single slice. Override activeSlice to only the locked slice ID.
575
+ const sliceLock = process.env.GSD_SLICE_LOCK;
576
+ if (sliceLock) {
577
+ const lockedSlice = activeMilestoneSlices.find(s => s.id === sliceLock);
578
+ if (lockedSlice) {
579
+ activeSlice = { id: lockedSlice.id, title: lockedSlice.title };
580
+ activeSliceRow = lockedSlice;
581
+ }
582
+ else {
583
+ logWarning("state", `GSD_SLICE_LOCK=${sliceLock} not found in active slices — worker has no assigned work`);
584
+ // Don't silently continue — this is a dispatch error
585
+ return {
586
+ activeMilestone, activeSlice: null, activeTask: null,
587
+ phase: 'blocked',
588
+ recentDecisions: [], blockers: [`GSD_SLICE_LOCK=${sliceLock} not found in active milestone slices`],
589
+ nextAction: 'Slice lock references a non-existent slice — check orchestrator dispatch.',
590
+ registry, requirements,
591
+ progress: { milestones: milestoneProgress, slices: sliceProgress },
592
+ };
593
+ }
594
+ }
595
+ else {
596
+ for (const s of activeMilestoneSlices) {
597
+ if (isStatusDone(s.status))
598
+ continue;
599
+ // #2661: Skip deferred slices — a decision explicitly deferred this work.
600
+ // Without this guard the dispatcher would keep dispatching deferred slices
601
+ // because DECISIONS.md is only contextual, not authoritative for dispatch.
602
+ if (isDeferredStatus(s.status))
603
+ continue;
604
+ if (s.depends.every(dep => doneSliceIds.has(dep))) {
605
+ activeSlice = { id: s.id, title: s.title };
606
+ activeSliceRow = s;
607
+ break;
608
+ }
578
609
  }
579
610
  }
580
611
  if (!activeSlice) {
@@ -1188,12 +1219,41 @@ export async function _deriveStateImpl(basePath) {
1188
1219
  // Find the active slice (first incomplete with deps satisfied)
1189
1220
  const doneSliceIds = new Set(activeRoadmap.slices.filter(s => s.done).map(s => s.id));
1190
1221
  let activeSlice = null;
1191
- for (const s of activeRoadmap.slices) {
1192
- if (s.done)
1193
- continue;
1194
- if (s.depends.every(dep => doneSliceIds.has(dep))) {
1195
- activeSlice = { id: s.id, title: s.title };
1196
- break;
1222
+ // ── Slice-level parallel worker isolation ─────────────────────────────
1223
+ // When GSD_SLICE_LOCK is set, override activeSlice to only the locked slice.
1224
+ const sliceLockLegacy = process.env.GSD_SLICE_LOCK;
1225
+ if (sliceLockLegacy) {
1226
+ const lockedSlice = activeRoadmap.slices.find(s => s.id === sliceLockLegacy);
1227
+ if (lockedSlice) {
1228
+ activeSlice = { id: lockedSlice.id, title: lockedSlice.title };
1229
+ }
1230
+ else {
1231
+ logWarning("state", `GSD_SLICE_LOCK=${sliceLockLegacy} not found in active slices — worker has no assigned work`);
1232
+ return {
1233
+ activeMilestone,
1234
+ activeSlice: null,
1235
+ activeTask: null,
1236
+ phase: 'blocked',
1237
+ recentDecisions: [],
1238
+ blockers: [`GSD_SLICE_LOCK=${sliceLockLegacy} not found in active milestone slices`],
1239
+ nextAction: 'Slice lock references a non-existent slice — check orchestrator dispatch.',
1240
+ registry,
1241
+ requirements,
1242
+ progress: {
1243
+ milestones: milestoneProgress,
1244
+ slices: sliceProgress,
1245
+ },
1246
+ };
1247
+ }
1248
+ }
1249
+ else {
1250
+ for (const s of activeRoadmap.slices) {
1251
+ if (s.done)
1252
+ continue;
1253
+ if (s.depends.every(dep => doneSliceIds.has(dep))) {
1254
+ activeSlice = { id: s.id, title: s.title };
1255
+ break;
1256
+ }
1197
1257
  }
1198
1258
  }
1199
1259
  if (!activeSlice) {
@@ -11,3 +11,14 @@
11
11
  export function isClosedStatus(status) {
12
12
  return status === "complete" || status === "done" || status === "skipped";
13
13
  }
14
+ /** Returns true when a slice status indicates it was deferred by a decision. */
15
+ export function isDeferredStatus(status) {
16
+ return status === "deferred";
17
+ }
18
+ /**
19
+ * Returns true when a slice should be skipped during active-slice selection.
20
+ * This includes both closed (complete/done) and deferred slices.
21
+ */
22
+ export function isInactiveStatus(status) {
23
+ return isClosedStatus(status) || isDeferredStatus(status);
24
+ }
@@ -12,24 +12,29 @@ import { resolveMilestonePath, clearPathCache } from "../paths.js";
12
12
  import { isClosedStatus } from "../status-guards.js";
13
13
  import { saveFile, clearParseCache } from "../files.js";
14
14
  import { invalidateStateCache } from "../state.js";
15
- import { renderAllProjections } from "../workflow-projections.js";
15
+ import { renderAllProjections, stripIdPrefix } from "../workflow-projections.js";
16
16
  import { writeManifest } from "../workflow-manifest.js";
17
17
  import { appendEvent } from "../workflow-events.js";
18
18
  import { logWarning } from "../workflow-logger.js";
19
19
  function renderMilestoneSummaryMarkdown(params) {
20
20
  const now = new Date().toISOString();
21
- const keyDecisionsYaml = params.keyDecisions.length > 0
22
- ? params.keyDecisions.map(d => ` - ${d}`).join("\n")
21
+ const displayTitle = stripIdPrefix(params.title, params.milestoneId);
22
+ // Apply defaults for optional enrichment fields (#2771)
23
+ const keyDecisions = params.keyDecisions ?? [];
24
+ const keyFiles = params.keyFiles ?? [];
25
+ const lessonsLearned = params.lessonsLearned ?? [];
26
+ const keyDecisionsYaml = keyDecisions.length > 0
27
+ ? keyDecisions.map(d => ` - ${d}`).join("\n")
23
28
  : " - (none)";
24
- const keyFilesYaml = params.keyFiles.length > 0
25
- ? params.keyFiles.map(f => ` - ${f}`).join("\n")
29
+ const keyFilesYaml = keyFiles.length > 0
30
+ ? keyFiles.map(f => ` - ${f}`).join("\n")
26
31
  : " - (none)";
27
- const lessonsYaml = params.lessonsLearned.length > 0
28
- ? params.lessonsLearned.map(l => ` - ${l}`).join("\n")
32
+ const lessonsYaml = lessonsLearned.length > 0
33
+ ? lessonsLearned.map(l => ` - ${l}`).join("\n")
29
34
  : " - (none)";
30
35
  return `---
31
36
  id: ${params.milestoneId}
32
- title: "${params.title}"
37
+ title: "${displayTitle}"
33
38
  status: complete
34
39
  completed_at: ${now}
35
40
  key_decisions:
@@ -40,7 +45,7 @@ lessons_learned:
40
45
  ${lessonsYaml}
41
46
  ---
42
47
 
43
- # ${params.milestoneId}: ${params.title}
48
+ # ${params.milestoneId}: ${displayTitle}
44
49
 
45
50
  **${params.oneLiner}**
46
51
 
@@ -50,15 +55,15 @@ ${params.narrative}
50
55
 
51
56
  ## Success Criteria Results
52
57
 
53
- ${params.successCriteriaResults}
58
+ ${params.successCriteriaResults ?? "Not provided."}
54
59
 
55
60
  ## Definition of Done Results
56
61
 
57
- ${params.definitionOfDoneResults}
62
+ ${params.definitionOfDoneResults ?? "Not provided."}
58
63
 
59
64
  ## Requirement Outcomes
60
65
 
61
- ${params.requirementOutcomes}
66
+ ${params.requirementOutcomes ?? "Not provided."}
62
67
 
63
68
  ## Deviations
64
69
 
@@ -25,46 +25,60 @@ import { logWarning } from "../workflow-logger.js";
25
25
  */
26
26
  function renderSliceSummaryMarkdown(params) {
27
27
  const now = new Date().toISOString();
28
- const providesYaml = params.provides.length > 0
29
- ? params.provides.map(p => ` - ${p}`).join("\n")
28
+ // Apply defaults for optional enrichment arrays (#2771)
29
+ const provides = params.provides ?? [];
30
+ const requires = params.requires ?? [];
31
+ const affects = params.affects ?? [];
32
+ const keyFiles = params.keyFiles ?? [];
33
+ const keyDecisions = params.keyDecisions ?? [];
34
+ const patternsEstablished = params.patternsEstablished ?? [];
35
+ const observabilitySurfaces = params.observabilitySurfaces ?? [];
36
+ const drillDownPaths = params.drillDownPaths ?? [];
37
+ const requirementsAdvanced = params.requirementsAdvanced ?? [];
38
+ const requirementsValidated = params.requirementsValidated ?? [];
39
+ const requirementsSurfaced = params.requirementsSurfaced ?? [];
40
+ const requirementsInvalidated = params.requirementsInvalidated ?? [];
41
+ const filesModified = params.filesModified ?? [];
42
+ const providesYaml = provides.length > 0
43
+ ? provides.map(p => ` - ${p}`).join("\n")
30
44
  : " - (none)";
31
- const requiresYaml = params.requires.length > 0
32
- ? params.requires.map(r => ` - slice: ${r.slice}\n provides: ${r.provides}`).join("\n")
45
+ const requiresYaml = requires.length > 0
46
+ ? requires.map(r => ` - slice: ${r.slice}\n provides: ${r.provides}`).join("\n")
33
47
  : " []";
34
- const affectsYaml = params.affects.length > 0
35
- ? params.affects.map(a => ` - ${a}`).join("\n")
48
+ const affectsYaml = affects.length > 0
49
+ ? affects.map(a => ` - ${a}`).join("\n")
36
50
  : " []";
37
- const keyFilesYaml = params.keyFiles.length > 0
38
- ? params.keyFiles.map(f => ` - ${f}`).join("\n")
51
+ const keyFilesYaml = keyFiles.length > 0
52
+ ? keyFiles.map(f => ` - ${f}`).join("\n")
39
53
  : " - (none)";
40
- const keyDecisionsYaml = params.keyDecisions.length > 0
41
- ? params.keyDecisions.map(d => ` - ${d}`).join("\n")
54
+ const keyDecisionsYaml = keyDecisions.length > 0
55
+ ? keyDecisions.map(d => ` - ${d}`).join("\n")
42
56
  : " - (none)";
43
- const patternsYaml = params.patternsEstablished.length > 0
44
- ? params.patternsEstablished.map(p => ` - ${p}`).join("\n")
57
+ const patternsYaml = patternsEstablished.length > 0
58
+ ? patternsEstablished.map(p => ` - ${p}`).join("\n")
45
59
  : " - (none)";
46
- const observabilityYaml = params.observabilitySurfaces.length > 0
47
- ? params.observabilitySurfaces.map(o => ` - ${o}`).join("\n")
60
+ const observabilityYaml = observabilitySurfaces.length > 0
61
+ ? observabilitySurfaces.map(o => ` - ${o}`).join("\n")
48
62
  : " - none";
49
- const drillDownYaml = params.drillDownPaths.length > 0
50
- ? params.drillDownPaths.map(d => ` - ${d}`).join("\n")
63
+ const drillDownYaml = drillDownPaths.length > 0
64
+ ? drillDownPaths.map(d => ` - ${d}`).join("\n")
51
65
  : " []";
52
66
  // Requirements sections
53
- const reqAdvanced = params.requirementsAdvanced.length > 0
54
- ? params.requirementsAdvanced.map(r => `- ${r.id} — ${r.how}`).join("\n")
67
+ const reqAdvanced = requirementsAdvanced.length > 0
68
+ ? requirementsAdvanced.map(r => `- ${r.id} — ${r.how}`).join("\n")
55
69
  : "None.";
56
- const reqValidated = params.requirementsValidated.length > 0
57
- ? params.requirementsValidated.map(r => `- ${r.id} — ${r.proof}`).join("\n")
70
+ const reqValidated = requirementsValidated.length > 0
71
+ ? requirementsValidated.map(r => `- ${r.id} — ${r.proof}`).join("\n")
58
72
  : "None.";
59
- const reqSurfaced = params.requirementsSurfaced.length > 0
60
- ? params.requirementsSurfaced.map(r => `- ${r}`).join("\n")
73
+ const reqSurfaced = requirementsSurfaced.length > 0
74
+ ? requirementsSurfaced.map(r => `- ${r}`).join("\n")
61
75
  : "None.";
62
- const reqInvalidated = params.requirementsInvalidated.length > 0
63
- ? params.requirementsInvalidated.map(r => `- ${r.id} — ${r.what}`).join("\n")
76
+ const reqInvalidated = requirementsInvalidated.length > 0
77
+ ? requirementsInvalidated.map(r => `- ${r.id} — ${r.what}`).join("\n")
64
78
  : "None.";
65
79
  // Files modified
66
- const filesMod = params.filesModified.length > 0
67
- ? params.filesModified.map(f => `- \`${f.path}\` — ${f.description}`).join("\n")
80
+ const filesMod = filesModified.length > 0
81
+ ? filesModified.map(f => `- \`${f.path}\` — ${f.description}`).join("\n")
68
82
  : "None.";
69
83
  return `---
70
84
  id: ${params.sliceId}
@@ -35,11 +35,11 @@ function paramsToTaskRow(params, completedAt) {
35
35
  verification_result: params.verification,
36
36
  duration: "",
37
37
  completed_at: completedAt,
38
- blocker_discovered: params.blockerDiscovered,
39
- deviations: params.deviations,
40
- known_issues: params.knownIssues,
41
- key_files: params.keyFiles,
42
- key_decisions: params.keyDecisions,
38
+ blocker_discovered: params.blockerDiscovered ?? false,
39
+ deviations: params.deviations ?? "",
40
+ known_issues: params.knownIssues ?? "",
41
+ key_files: params.keyFiles ?? [],
42
+ key_decisions: params.keyDecisions ?? [],
43
43
  full_summary_md: "",
44
44
  description: "",
45
45
  estimate: "",
@@ -113,13 +113,13 @@ export async function handleCompleteTask(params, basePath) {
113
113
  narrative: params.narrative,
114
114
  verificationResult: params.verification,
115
115
  duration: "",
116
- blockerDiscovered: params.blockerDiscovered,
117
- deviations: params.deviations,
118
- knownIssues: params.knownIssues,
119
- keyFiles: params.keyFiles,
120
- keyDecisions: params.keyDecisions,
116
+ blockerDiscovered: params.blockerDiscovered ?? false,
117
+ deviations: params.deviations ?? "None.",
118
+ knownIssues: params.knownIssues ?? "None.",
119
+ keyFiles: params.keyFiles ?? [],
120
+ keyDecisions: params.keyDecisions ?? [],
121
121
  });
122
- for (const evidence of params.verificationEvidence) {
122
+ for (const evidence of (params.verificationEvidence ?? [])) {
123
123
  insertVerificationEvidence({
124
124
  taskId: params.taskId,
125
125
  sliceId: params.sliceId,
@@ -139,7 +139,7 @@ export async function handleCompleteTask(params, basePath) {
139
139
  // verifyExpectedArtifact() stay consistent (both say "not done").
140
140
  // Render summary markdown via the single source of truth (#2720)
141
141
  const taskRow = paramsToTaskRow(params, completedAt);
142
- const summaryMd = renderSummaryContent(taskRow, params.sliceId, params.milestoneId, params.verificationEvidence);
142
+ const summaryMd = renderSummaryContent(taskRow, params.sliceId, params.milestoneId, params.verificationEvidence ?? []);
143
143
  // Resolve and write summary to disk
144
144
  let summaryPath;
145
145
  const tasksDir = resolveTasksDir(basePath, params.milestoneId, params.sliceId);
@@ -1,7 +1,7 @@
1
1
  import { clearParseCache } from "../files.js";
2
2
  import { isClosedStatus } from "../status-guards.js";
3
3
  import { isNonEmptyString, validateStringArray } from "../validation.js";
4
- import { transaction, getMilestone, getMilestoneSlices, insertMilestone, insertSlice, upsertMilestonePlanning, upsertSlicePlanning, } from "../gsd-db.js";
4
+ import { transaction, getMilestone, getMilestoneSlices, getSlice, insertMilestone, insertSlice, upsertMilestonePlanning, upsertSlicePlanning, } from "../gsd-db.js";
5
5
  import { invalidateStateCache } from "../state.js";
6
6
  import { renderRoadmapFromDb } from "../markdown-renderer.js";
7
7
  import { renderAllProjections } from "../workflow-projections.js";
@@ -106,25 +106,20 @@ function validateParams(params) {
106
106
  throw new Error("title is required");
107
107
  if (!isNonEmptyString(params?.vision))
108
108
  throw new Error("vision is required");
109
- if (!isNonEmptyString(params?.verificationContract))
110
- throw new Error("verificationContract is required");
111
- if (!isNonEmptyString(params?.verificationIntegration))
112
- throw new Error("verificationIntegration is required");
113
- if (!isNonEmptyString(params?.verificationOperational))
114
- throw new Error("verificationOperational is required");
115
- if (!isNonEmptyString(params?.verificationUat))
116
- throw new Error("verificationUat is required");
117
- if (!isNonEmptyString(params?.requirementCoverage))
118
- throw new Error("requirementCoverage is required");
119
- if (!isNonEmptyString(params?.boundaryMapMarkdown))
120
- throw new Error("boundaryMapMarkdown is required");
121
109
  return {
122
110
  ...params,
123
111
  dependsOn: params.dependsOn ? validateStringArray(params.dependsOn, "dependsOn") : [],
124
- successCriteria: validateStringArray(params.successCriteria, "successCriteria"),
125
- keyRisks: validateRiskEntries(params.keyRisks),
126
- proofStrategy: validateProofStrategy(params.proofStrategy),
127
- definitionOfDone: validateStringArray(params.definitionOfDone, "definitionOfDone"),
112
+ // Apply defaults for optional enrichment fields (#2771)
113
+ successCriteria: params.successCriteria ? validateStringArray(params.successCriteria, "successCriteria") : [],
114
+ keyRisks: params.keyRisks ? validateRiskEntries(params.keyRisks) : [],
115
+ proofStrategy: params.proofStrategy ? validateProofStrategy(params.proofStrategy) : [],
116
+ verificationContract: params.verificationContract ?? "Not provided.",
117
+ verificationIntegration: params.verificationIntegration ?? "Not provided.",
118
+ verificationOperational: params.verificationOperational ?? "Not provided.",
119
+ verificationUat: params.verificationUat ?? "Not provided.",
120
+ definitionOfDone: params.definitionOfDone ? validateStringArray(params.definitionOfDone, "definitionOfDone") : [],
121
+ requirementCoverage: params.requirementCoverage ?? "Not provided.",
122
+ boundaryMapMarkdown: params.boundaryMapMarkdown ?? "Not provided.",
128
123
  slices: validateSlices(params.slices),
129
124
  };
130
125
  }
@@ -147,15 +142,19 @@ export async function handlePlanMilestone(rawParams, basePath) {
147
142
  guardError = `cannot re-plan milestone ${params.milestoneId}: it is already complete`;
148
143
  return;
149
144
  }
150
- // Guard: refuse to re-plan a milestone that has completed slices (#2960).
151
- // INSERT OR IGNORE on slices won't overwrite existing rows, but a full
152
- // re-plan after worktree recreation or DB resync can create new slice rows
153
- // that shadow completed work. Block early when any slice is already done.
145
+ // Guard: refuse to re-plan a milestone that would drop completed slices (#2960).
146
+ // Allow re-planning when all completed slices are still present in the
147
+ // incoming plan their status is preserved below (#2558). Block only when
148
+ // the new plan omits a completed slice, which could shadow completed work.
154
149
  const existingSlices = getMilestoneSlices(params.milestoneId);
155
150
  const completedSlices = existingSlices.filter(s => isClosedStatus(s.status));
156
151
  if (completedSlices.length > 0) {
157
- guardError = `cannot re-plan milestone ${params.milestoneId}: ${completedSlices.length} slice(s) already completed (${completedSlices.map(s => s.id).join(", ")}). Use gsd_reassess_roadmap to modify the roadmap.`;
158
- return;
152
+ const incomingSliceIds = new Set(params.slices.map(s => s.sliceId));
153
+ const droppedCompleted = completedSlices.filter(s => !incomingSliceIds.has(s.id));
154
+ if (droppedCompleted.length > 0) {
155
+ guardError = `cannot re-plan milestone ${params.milestoneId}: ${droppedCompleted.length} completed slice(s) would be dropped (${droppedCompleted.map(s => s.id).join(", ")}). Use gsd_reassess_roadmap to modify the roadmap.`;
156
+ return;
157
+ }
159
158
  }
160
159
  // Validate depends_on: all dependencies must exist and be complete
161
160
  if (params.dependsOn && params.dependsOn.length > 0) {
@@ -178,6 +177,8 @@ export async function handlePlanMilestone(rawParams, basePath) {
178
177
  depends_on: params.dependsOn ?? [],
179
178
  });
180
179
  upsertMilestonePlanning(params.milestoneId, {
180
+ title: params.title,
181
+ status: params.status ?? "active",
181
182
  vision: params.vision,
182
183
  successCriteria: params.successCriteria,
183
184
  keyRisks: params.keyRisks,
@@ -189,13 +190,20 @@ export async function handlePlanMilestone(rawParams, basePath) {
189
190
  definitionOfDone: params.definitionOfDone,
190
191
  requirementCoverage: params.requirementCoverage,
191
192
  boundaryMapMarkdown: params.boundaryMapMarkdown,
192
- }, params.title);
193
+ });
193
194
  for (const slice of params.slices) {
195
+ // Preserve completed/done status on re-plan (#2558).
196
+ // Without this, a re-plan after milestone transition would reset
197
+ // already-completed slices back to "pending".
198
+ const existing = getSlice(params.milestoneId, slice.sliceId);
199
+ const status = existing && (existing.status === "complete" || existing.status === "done")
200
+ ? existing.status
201
+ : "pending";
194
202
  insertSlice({
195
203
  id: slice.sliceId,
196
204
  milestoneId: params.milestoneId,
197
205
  title: slice.title,
198
- status: "pending",
206
+ status,
199
207
  risk: slice.risk,
200
208
  depends: slice.depends,
201
209
  demo: slice.demo,
@@ -72,16 +72,13 @@ function validateParams(params) {
72
72
  throw new Error("sliceId is required");
73
73
  if (!isNonEmptyString(params?.goal))
74
74
  throw new Error("goal is required");
75
- if (!isNonEmptyString(params?.successCriteria))
76
- throw new Error("successCriteria is required");
77
- if (!isNonEmptyString(params?.proofLevel))
78
- throw new Error("proofLevel is required");
79
- if (!isNonEmptyString(params?.integrationClosure))
80
- throw new Error("integrationClosure is required");
81
- if (!isNonEmptyString(params?.observabilityImpact))
82
- throw new Error("observabilityImpact is required");
83
75
  return {
84
76
  ...params,
77
+ // Apply defaults for optional enrichment fields (#2771)
78
+ successCriteria: params.successCriteria ?? "Not provided.",
79
+ proofLevel: params.proofLevel ?? "Not provided.",
80
+ integrationClosure: params.integrationClosure ?? "Not provided.",
81
+ observabilityImpact: params.observabilityImpact ?? "Not provided.",
85
82
  tasks: validateTasks(params.tasks),
86
83
  };
87
84
  }
@@ -7,6 +7,20 @@ import { join } from "node:path";
7
7
  import { mkdirSync, existsSync } from "node:fs";
8
8
  import { logWarning } from "./workflow-logger.js";
9
9
  import { deriveState } from "./state.js";
10
+ // ─── Helpers ─────────────────────────────────────────────────────────────
11
+ /**
12
+ * Strip a leading ID prefix (e.g. "M001: " or "S04: ") from a title
13
+ * to prevent double-prefixing when the renderer adds its own prefix.
14
+ * Handles repeated prefixes (e.g. "M001: M001: M001: Title" → "Title").
15
+ */
16
+ export function stripIdPrefix(title, id) {
17
+ const prefix = `${id}: `;
18
+ let result = title;
19
+ while (result.startsWith(prefix)) {
20
+ result = result.slice(prefix.length);
21
+ }
22
+ return result.trim() || title;
23
+ }
10
24
  // ─── PLAN.md Projection ──────────────────────────────────────────────────
11
25
  /**
12
26
  * Render PLAN.md content from a slice row and its task rows.
@@ -14,7 +28,8 @@ import { deriveState } from "./state.js";
14
28
  */
15
29
  export function renderPlanContent(sliceRow, taskRows) {
16
30
  const lines = [];
17
- lines.push(`# ${sliceRow.id}: ${sliceRow.title}`);
31
+ const displayTitle = stripIdPrefix(sliceRow.title, sliceRow.id);
32
+ lines.push(`# ${sliceRow.id}: ${displayTitle}`);
18
33
  lines.push("");
19
34
  // #2945: never use full_summary_md/full_uat_md as display fallbacks —
20
35
  // they contain multi-line rendered markdown that corrupts single-line fields.
@@ -71,7 +86,8 @@ export function renderPlanProjection(basePath, milestoneId, sliceId) {
71
86
  */
72
87
  export function renderRoadmapContent(milestoneRow, sliceRows) {
73
88
  const lines = [];
74
- lines.push(`# ${milestoneRow.id}: ${milestoneRow.title}`);
89
+ const displayTitle = stripIdPrefix(milestoneRow.title, milestoneRow.id);
90
+ lines.push(`# ${milestoneRow.id}: ${displayTitle}`);
75
91
  lines.push("");
76
92
  lines.push("## Vision");
77
93
  lines.push(milestoneRow.vision || milestoneRow.title || "TBD");
@@ -218,14 +234,14 @@ export function renderStateContent(state) {
218
234
  const lines = [];
219
235
  lines.push("# GSD State", "");
220
236
  const activeSlice = state.activeSlice
221
- ? `${state.activeSlice.id}: ${state.activeSlice.title}`
237
+ ? `${state.activeSlice.id}: ${stripIdPrefix(state.activeSlice.title, state.activeSlice.id)}`
222
238
  : "None";
223
239
  if (state.phase === 'complete' && state.lastCompletedMilestone) {
224
240
  lines.push(`**Last Completed Milestone:** ${state.lastCompletedMilestone.id}: ${state.lastCompletedMilestone.title}`);
225
241
  }
226
242
  else {
227
243
  const activeMilestone = state.activeMilestone
228
- ? `${state.activeMilestone.id}: ${state.activeMilestone.title}`
244
+ ? `${state.activeMilestone.id}: ${stripIdPrefix(state.activeMilestone.title, state.activeMilestone.id)}`
229
245
  : "None";
230
246
  lines.push(`**Active Milestone:** ${activeMilestone}`);
231
247
  }
@@ -238,7 +254,7 @@ export function renderStateContent(state) {
238
254
  lines.push("## Milestone Registry");
239
255
  for (const entry of state.registry) {
240
256
  const glyph = entry.status === "complete" ? "\u2705" : entry.status === "active" ? "\uD83D\uDD04" : entry.status === "parked" ? "\u23F8\uFE0F" : "\u2B1C";
241
- lines.push(`- ${glyph} **${entry.id}:** ${entry.title}`);
257
+ lines.push(`- ${glyph} **${entry.id}:** ${stripIdPrefix(entry.title, entry.id)}`);
242
258
  }
243
259
  lines.push("");
244
260
  lines.push("## Recent Decisions");