gsd-pi 2.71.0-dev.e17e0ce → 2.72.0-dev.3118184

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 (352) hide show
  1. package/README.md +46 -3
  2. package/dist/cli.js +76 -3
  3. package/dist/mcp-server.js +37 -14
  4. package/dist/onboarding.js +10 -0
  5. package/dist/resources/agents/debugger.md +58 -0
  6. package/dist/resources/agents/doc-writer.md +43 -0
  7. package/dist/resources/agents/git-ops.md +56 -0
  8. package/dist/resources/agents/javascript-pro.md +46 -271
  9. package/dist/resources/agents/planner.md +55 -0
  10. package/dist/resources/agents/refactorer.md +47 -0
  11. package/dist/resources/agents/reviewer.md +48 -0
  12. package/dist/resources/agents/security.md +59 -0
  13. package/dist/resources/agents/tester.md +50 -0
  14. package/dist/resources/agents/typescript-pro.md +41 -235
  15. package/dist/resources/extensions/async-jobs/await-tool.js +7 -4
  16. package/dist/resources/extensions/async-jobs/job-manager.js +28 -3
  17. package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
  18. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +132 -10
  19. package/dist/resources/extensions/gsd/auto/loop.js +84 -1
  20. package/dist/resources/extensions/gsd/auto/phases.js +4 -0
  21. package/dist/resources/extensions/gsd/auto-post-unit.js +6 -0
  22. package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
  23. package/dist/resources/extensions/gsd/auto-recovery.js +11 -0
  24. package/dist/resources/extensions/gsd/auto-start.js +24 -4
  25. package/dist/resources/extensions/gsd/auto.js +29 -19
  26. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  27. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -11
  28. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +2 -5
  29. package/dist/resources/extensions/gsd/commands-handlers.js +4 -1
  30. package/dist/resources/extensions/gsd/context-injector.js +1 -1
  31. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -7
  32. package/dist/resources/extensions/gsd/definition-io.js +15 -0
  33. package/dist/resources/extensions/gsd/dispatch-guard.js +4 -0
  34. package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
  35. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +6 -3
  36. package/dist/resources/extensions/gsd/error-classifier.js +4 -1
  37. package/dist/resources/extensions/gsd/gate-registry.js +208 -0
  38. package/dist/resources/extensions/gsd/git-service.js +11 -8
  39. package/dist/resources/extensions/gsd/gitignore.js +12 -6
  40. package/dist/resources/extensions/gsd/gsd-db.js +90 -6
  41. package/dist/resources/extensions/gsd/key-manager.js +2 -0
  42. package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
  43. package/dist/resources/extensions/gsd/notification-overlay.js +26 -12
  44. package/dist/resources/extensions/gsd/notification-store.js +5 -4
  45. package/dist/resources/extensions/gsd/preferences-skills.js +2 -34
  46. package/dist/resources/extensions/gsd/preferences-types.js +15 -0
  47. package/dist/resources/extensions/gsd/preferences.js +16 -3
  48. package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
  49. package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
  50. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  51. package/dist/resources/extensions/gsd/prompts/discuss.md +122 -13
  52. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
  53. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  55. package/dist/resources/extensions/gsd/shortcut-defs.js +7 -1
  56. package/dist/resources/extensions/gsd/state.js +29 -2
  57. package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
  58. package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
  59. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
  60. package/dist/resources/extensions/gsd/workflow-projections.js +7 -0
  61. package/dist/resources/extensions/gsd/worktree-manager.js +30 -3
  62. package/dist/resources/extensions/gsd/write-intercept.js +10 -1
  63. package/dist/resources/extensions/ollama/index.js +17 -10
  64. package/dist/resources/extensions/ollama/ollama-client.js +35 -6
  65. package/dist/resources/extensions/ollama/ollama-discovery.js +32 -6
  66. package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
  67. package/dist/resources/extensions/subagent/agents.js +8 -0
  68. package/dist/resources/extensions/subagent/index.js +17 -0
  69. package/dist/startup-model-validation.d.ts +0 -1
  70. package/dist/startup-model-validation.js +6 -2
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  73. package/dist/web/standalone/.next/build-manifest.json +2 -2
  74. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  75. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  76. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  86. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  99. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  105. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  115. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  120. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  123. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  130. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  131. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  132. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +3 -3
  133. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/index.html +1 -1
  138. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/page.js +2 -2
  145. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  147. package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
  148. package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
  149. package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
  150. package/dist/web/standalone/.next/server/chunks/63.js +8 -8
  151. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  152. package/dist/web/standalone/.next/server/edge-runtime-webpack.js +2 -0
  153. package/dist/web/standalone/.next/server/functions-config-manifest.json +0 -9
  154. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/middleware-manifest.json +29 -2
  156. package/dist/web/standalone/.next/server/middleware.js +4 -12
  157. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  158. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  159. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  160. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  161. package/package.json +1 -1
  162. package/packages/mcp-server/dist/server.d.ts +12 -1
  163. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  164. package/packages/mcp-server/dist/server.js +90 -42
  165. package/packages/mcp-server/dist/server.js.map +1 -1
  166. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  167. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  168. package/packages/mcp-server/src/server.ts +110 -38
  169. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  170. package/packages/pi-ai/dist/env-api-keys.js +1 -0
  171. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  172. package/packages/pi-ai/dist/models.custom.d.ts +105 -0
  173. package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
  174. package/packages/pi-ai/dist/models.custom.js +97 -0
  175. package/packages/pi-ai/dist/models.custom.js.map +1 -1
  176. package/packages/pi-ai/dist/models.generated.d.ts +648 -140
  177. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  178. package/packages/pi-ai/dist/models.generated.js +867 -370
  179. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  180. package/packages/pi-ai/dist/models.generated.test.d.ts +2 -0
  181. package/packages/pi-ai/dist/models.generated.test.d.ts.map +1 -0
  182. package/packages/pi-ai/dist/models.generated.test.js +334 -0
  183. package/packages/pi-ai/dist/models.generated.test.js.map +1 -0
  184. package/packages/pi-ai/dist/models.test.js +105 -0
  185. package/packages/pi-ai/dist/models.test.js.map +1 -1
  186. package/packages/pi-ai/dist/types.d.ts +1 -1
  187. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  188. package/packages/pi-ai/dist/types.js.map +1 -1
  189. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  190. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
  191. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  192. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts +2 -0
  193. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts.map +1 -0
  194. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +57 -0
  195. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -0
  196. package/packages/pi-ai/src/env-api-keys.ts +1 -0
  197. package/packages/pi-ai/src/models.custom.ts +98 -0
  198. package/packages/pi-ai/src/models.generated.test.ts +373 -0
  199. package/packages/pi-ai/src/models.generated.ts +867 -370
  200. package/packages/pi-ai/src/models.test.ts +135 -0
  201. package/packages/pi-ai/src/types.ts +1 -0
  202. package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +71 -0
  203. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +4 -1
  204. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  206. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  208. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  209. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  210. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  211. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +5 -0
  212. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/core/retry-handler.js +55 -1
  214. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +57 -0
  216. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  217. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  218. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  219. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
  221. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  222. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  224. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  225. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
  227. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  228. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  229. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  230. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  231. package/packages/pi-coding-agent/package.json +1 -1
  232. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  233. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  234. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +83 -0
  235. package/packages/pi-coding-agent/src/core/retry-handler.ts +60 -1
  236. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  237. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
  238. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  239. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
  240. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  241. package/packages/pi-tui/dist/components/__tests__/editor.test.js +12 -0
  242. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -1
  243. package/packages/pi-tui/dist/components/__tests__/input.test.js +12 -0
  244. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  245. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  246. package/packages/pi-tui/dist/keys.js +27 -0
  247. package/packages/pi-tui/dist/keys.js.map +1 -1
  248. package/packages/pi-tui/src/components/__tests__/editor.test.ts +18 -0
  249. package/packages/pi-tui/src/components/__tests__/input.test.ts +18 -0
  250. package/packages/pi-tui/src/keys.ts +32 -0
  251. package/pkg/package.json +1 -1
  252. package/src/resources/agents/debugger.md +58 -0
  253. package/src/resources/agents/doc-writer.md +43 -0
  254. package/src/resources/agents/git-ops.md +56 -0
  255. package/src/resources/agents/javascript-pro.md +46 -271
  256. package/src/resources/agents/planner.md +55 -0
  257. package/src/resources/agents/refactorer.md +47 -0
  258. package/src/resources/agents/reviewer.md +48 -0
  259. package/src/resources/agents/security.md +59 -0
  260. package/src/resources/agents/tester.md +50 -0
  261. package/src/resources/agents/typescript-pro.md +41 -235
  262. package/src/resources/extensions/async-jobs/await-tool.test.ts +40 -7
  263. package/src/resources/extensions/async-jobs/await-tool.ts +7 -4
  264. package/src/resources/extensions/async-jobs/job-manager.ts +33 -3
  265. package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
  266. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +139 -8
  267. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
  268. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +245 -2
  269. package/src/resources/extensions/gsd/auto/loop.ts +89 -1
  270. package/src/resources/extensions/gsd/auto/phases.ts +4 -0
  271. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -0
  272. package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
  273. package/src/resources/extensions/gsd/auto-recovery.ts +10 -0
  274. package/src/resources/extensions/gsd/auto-start.ts +31 -4
  275. package/src/resources/extensions/gsd/auto.ts +29 -20
  276. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  277. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -10
  278. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +2 -5
  279. package/src/resources/extensions/gsd/commands-handlers.ts +5 -1
  280. package/src/resources/extensions/gsd/context-injector.ts +1 -1
  281. package/src/resources/extensions/gsd/custom-workflow-engine.ts +4 -8
  282. package/src/resources/extensions/gsd/definition-io.ts +18 -0
  283. package/src/resources/extensions/gsd/dispatch-guard.ts +5 -0
  284. package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
  285. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +6 -3
  286. package/src/resources/extensions/gsd/error-classifier.ts +4 -1
  287. package/src/resources/extensions/gsd/gate-registry.ts +251 -0
  288. package/src/resources/extensions/gsd/git-service.ts +11 -8
  289. package/src/resources/extensions/gsd/gitignore.ts +12 -6
  290. package/src/resources/extensions/gsd/gsd-db.ts +105 -6
  291. package/src/resources/extensions/gsd/key-manager.ts +2 -0
  292. package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
  293. package/src/resources/extensions/gsd/notification-overlay.ts +27 -11
  294. package/src/resources/extensions/gsd/notification-store.ts +5 -4
  295. package/src/resources/extensions/gsd/preferences-skills.ts +2 -36
  296. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  297. package/src/resources/extensions/gsd/preferences.ts +19 -6
  298. package/src/resources/extensions/gsd/prompt-loader.ts +6 -1
  299. package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
  300. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  301. package/src/resources/extensions/gsd/prompts/discuss.md +122 -13
  302. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
  303. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  304. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  305. package/src/resources/extensions/gsd/shortcut-defs.ts +8 -1
  306. package/src/resources/extensions/gsd/state.ts +33 -2
  307. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +27 -0
  308. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +14 -0
  309. package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +63 -0
  310. package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
  311. package/src/resources/extensions/gsd/tests/definition-io.test.ts +57 -0
  312. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +26 -0
  313. package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +14 -0
  314. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
  315. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +104 -0
  316. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +16 -0
  317. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
  318. package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
  319. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +107 -5
  320. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +8 -6
  321. package/src/resources/extensions/gsd/tests/key-manager.test.ts +63 -0
  322. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +54 -0
  323. package/src/resources/extensions/gsd/tests/plan-milestone-artifact-verification.test.ts +62 -0
  324. package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +34 -0
  325. package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +87 -0
  326. package/src/resources/extensions/gsd/tests/preferences.test.ts +53 -0
  327. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +96 -1
  328. package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +19 -0
  329. package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
  330. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +9 -0
  331. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +97 -0
  332. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +3 -2
  333. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +41 -0
  334. package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
  335. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
  336. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
  337. package/src/resources/extensions/gsd/types.ts +26 -0
  338. package/src/resources/extensions/gsd/workflow-projections.ts +8 -0
  339. package/src/resources/extensions/gsd/worktree-manager.ts +29 -3
  340. package/src/resources/extensions/gsd/write-intercept.ts +10 -1
  341. package/src/resources/extensions/ollama/index.ts +17 -8
  342. package/src/resources/extensions/ollama/ollama-client.ts +35 -6
  343. package/src/resources/extensions/ollama/ollama-discovery.ts +37 -6
  344. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  345. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +54 -0
  346. package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
  347. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
  348. package/src/resources/extensions/subagent/agents.ts +10 -0
  349. package/src/resources/extensions/subagent/index.ts +18 -0
  350. package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
  351. /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → NzO79SOz9jHX-VY5-0t2O}/_buildManifest.js +0 -0
  352. /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → NzO79SOz9jHX-VY5-0t2O}/_ssgManifest.js +0 -0
@@ -29,6 +29,69 @@ import {
29
29
  import { debugLog } from "../debug-logger.js";
30
30
  import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
31
31
  import { resolveEngine } from "../engine-resolver.js";
32
+ import { logWarning } from "../workflow-logger.js";
33
+ import { gsdRoot } from "../paths.js";
34
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
35
+ import { join } from "node:path";
36
+
37
+ // ── Stuck detection persistence (#3704) ──────────────────────────────────
38
+ // Persist stuck detection state to disk so it survives session restarts.
39
+ // Without this, restarting auto-mode resets all counters, allowing the
40
+ // same blocked unit to burn a full retry budget each session.
41
+ function stuckStatePath(basePath: string): string {
42
+ return join(gsdRoot(basePath), "runtime", "stuck-state.json");
43
+ }
44
+
45
+ function loadStuckState(basePath: string): { recentUnits: Array<{ key: string }>; stuckRecoveryAttempts: number } {
46
+ try {
47
+ const data = JSON.parse(readFileSync(stuckStatePath(basePath), "utf-8"));
48
+ return {
49
+ recentUnits: Array.isArray(data.recentUnits) ? data.recentUnits : [],
50
+ stuckRecoveryAttempts: typeof data.stuckRecoveryAttempts === "number" ? data.stuckRecoveryAttempts : 0,
51
+ };
52
+ } catch (err) {
53
+ debugLog("autoLoop", { phase: "load-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
54
+ return { recentUnits: [], stuckRecoveryAttempts: 0 };
55
+ }
56
+ }
57
+
58
+ function saveStuckState(basePath: string, state: LoopState): void {
59
+ try {
60
+ const filePath = stuckStatePath(basePath);
61
+ mkdirSync(join(gsdRoot(basePath), "runtime"), { recursive: true });
62
+ writeFileSync(filePath, JSON.stringify({
63
+ recentUnits: state.recentUnits.slice(-20), // keep last 20 entries
64
+ stuckRecoveryAttempts: state.stuckRecoveryAttempts,
65
+ updatedAt: new Date().toISOString(),
66
+ }) + "\n");
67
+ } catch (err) {
68
+ debugLog("autoLoop", { phase: "save-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
69
+ }
70
+ }
71
+
72
+ // ── Memory pressure monitoring (#3331) ──────────────────────────────────
73
+ // Check heap usage every N iterations and trigger graceful shutdown before
74
+ // the OS OOM killer sends SIGKILL. The threshold is 90% of the V8 heap
75
+ // limit (--max-old-space-size or default ~1.5-4GB depending on platform).
76
+ const MEMORY_CHECK_INTERVAL = 5; // check every 5 iterations
77
+ const MEMORY_PRESSURE_THRESHOLD = 0.85; // 85% of heap limit
78
+
79
+ function checkMemoryPressure(): { pressured: boolean; heapMB: number; limitMB: number; pct: number } {
80
+ const mem = process.memoryUsage();
81
+ // v8.getHeapStatistics() gives heap_size_limit but requires import
82
+ // Use a conservative estimate: RSS > 3GB is danger zone on most systems
83
+ const heapMB = Math.round(mem.heapUsed / 1024 / 1024);
84
+ const rssMB = Math.round(mem.rss / 1024 / 1024);
85
+ // Try to get the actual V8 heap limit
86
+ let limitMB = 4096; // conservative default
87
+ try {
88
+ const v8 = require("node:v8");
89
+ const stats = v8.getHeapStatistics();
90
+ limitMB = Math.round(stats.heap_size_limit / 1024 / 1024);
91
+ } catch { limitMB = 4096; /* v8 stats unavailable — use conservative default */ }
92
+ const pct = heapMB / limitMB;
93
+ return { pressured: pct > MEMORY_PRESSURE_THRESHOLD, heapMB, limitMB, pct };
94
+ }
32
95
 
33
96
  /**
34
97
  * Main auto-mode execution loop. Iterates: derive → dispatch → guards →
@@ -46,7 +109,13 @@ export async function autoLoop(
46
109
  ): Promise<void> {
47
110
  debugLog("autoLoop", { phase: "enter" });
48
111
  let iteration = 0;
49
- const loopState: LoopState = { recentUnits: [], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 };
112
+ // Load persisted stuck state so counters survive session restarts (#3704)
113
+ const persisted = loadStuckState(s.basePath);
114
+ const loopState: LoopState = {
115
+ recentUnits: persisted.recentUnits,
116
+ stuckRecoveryAttempts: persisted.stuckRecoveryAttempts,
117
+ consecutiveFinalizeTimeouts: 0,
118
+ };
50
119
  let consecutiveErrors = 0;
51
120
  let consecutiveCooldowns = 0;
52
121
  const recentErrorMessages: string[] = [];
@@ -74,6 +143,24 @@ export async function autoLoop(
74
143
  break;
75
144
  }
76
145
 
146
+ // ── Memory pressure check (#3331) ──
147
+ // Graceful shutdown before OOM killer sends SIGKILL.
148
+ if (iteration % MEMORY_CHECK_INTERVAL === 0) {
149
+ const mem = checkMemoryPressure();
150
+ debugLog("autoLoop", { phase: "memory-check", ...mem });
151
+ if (mem.pressured) {
152
+ logWarning("dispatch", `Memory pressure: ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%) — stopping auto-mode to prevent OOM kill`);
153
+ await deps.stopAuto(
154
+ ctx,
155
+ pi,
156
+ `Memory pressure: heap at ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%). ` +
157
+ `Stopping gracefully to prevent OOM kill after ${iteration} iterations. ` +
158
+ `Resume with /gsd auto to continue from where you left off.`,
159
+ );
160
+ break;
161
+ }
162
+ }
163
+
77
164
  if (!s.cmdCtx) {
78
165
  debugLog("autoLoop", { phase: "exit", reason: "no-cmdCtx" });
79
166
  break;
@@ -207,6 +294,7 @@ export async function autoLoop(
207
294
  consecutiveCooldowns = 0;
208
295
  recentErrorMessages.length = 0;
209
296
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
297
+ saveStuckState(s.basePath, loopState); // persist across session restarts (#3704)
210
298
  debugLog("autoLoop", { phase: "iteration-complete", iteration });
211
299
 
212
300
  if (reconcileResult.outcome === "milestone-complete") {
@@ -27,6 +27,7 @@ import { runUnit } from "./run-unit.js";
27
27
  import { debugLog } from "../debug-logger.js";
28
28
  import { PROJECT_FILES } from "../detection.js";
29
29
  import { MergeConflictError } from "../git-service.js";
30
+ import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
30
31
  import { join, basename, dirname, parse as parsePath } from "node:path";
31
32
  import { existsSync, cpSync, readdirSync } from "node:fs";
32
33
  import { logWarning, logError } from "../workflow-logger.js";
@@ -1068,6 +1069,7 @@ export async function runUnitPhase(
1068
1069
  const previousTier = s.currentUnitRouting?.tier;
1069
1070
 
1070
1071
  s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
1072
+ setCurrentPhase(unitType);
1071
1073
  s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
1072
1074
  const unitStartSeq = ic.nextSeq();
1073
1075
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
@@ -1529,6 +1531,7 @@ export async function runFinalize(
1529
1531
  // Detach session from the timed-out unit so late async completions
1530
1532
  // cannot mutate state for the next unit (#3757).
1531
1533
  s.currentUnit = null;
1534
+ clearCurrentPhase();
1532
1535
  loopState.consecutiveFinalizeTimeouts++;
1533
1536
  debugLog("autoLoop", {
1534
1537
  phase: "pre-verification-timeout",
@@ -1626,6 +1629,7 @@ export async function runFinalize(
1626
1629
  // Detach session from the timed-out unit so late async completions
1627
1630
  // cannot mutate state for the next unit (#3757).
1628
1631
  s.currentUnit = null;
1632
+ clearCurrentPhase();
1629
1633
  loopState.consecutiveFinalizeTimeouts++;
1630
1634
  debugLog("autoLoop", {
1631
1635
  phase: "post-verification-timeout",
@@ -25,6 +25,7 @@ import {
25
25
  buildTaskFileName,
26
26
  } from "./paths.js";
27
27
  import { invalidateAllCaches } from "./cache.js";
28
+ import { rebuildState } from "./doctor.js";
28
29
  import { parseUnitId } from "./unit-id.js";
29
30
  import { closeoutUnit, type CloseoutOptions } from "./auto-unit-closeout.js";
30
31
  import {
@@ -367,6 +368,12 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
367
368
  }
368
369
  });
369
370
 
371
+ // Keep the on-disk STATE.md aligned with the live derived state after
372
+ // ordinary unit completion, before any worktree state is synced back.
373
+ await runSafely("postUnit", "state-rebuild", async () => {
374
+ await rebuildState(s.basePath);
375
+ });
376
+
370
377
  // Sync worktree state back to project root (skipped for lightweight sidecars)
371
378
  if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
372
379
  await runSafely("postUnit", "worktree-sync", () => {
@@ -24,7 +24,13 @@ import { getLoadedSkills, type Skill } from "@gsd/pi-coding-agent";
24
24
  import { join, basename } from "node:path";
25
25
  import { existsSync } from "node:fs";
26
26
  import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
27
- import { getPendingGates } from "./gsd-db.js";
27
+ import { getPendingGates, getPendingGatesForTurn } from "./gsd-db.js";
28
+ import {
29
+ GATE_REGISTRY,
30
+ assertGateCoverage,
31
+ getGatesForTurn,
32
+ type GateDefinition,
33
+ } from "./gate-registry.js";
28
34
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
29
35
  import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
30
36
  import { logWarning } from "./workflow-logger.js";
@@ -1395,6 +1401,17 @@ export async function buildExecuteTaskPrompt(
1395
1401
 
1396
1402
  const phaseAnchorSection = planAnchor ? formatAnchorForPrompt(planAnchor) : "";
1397
1403
 
1404
+ // Task-scoped gates owned by execute-task (Q5/Q6/Q7). Pull only the
1405
+ // gates that plan-slice actually seeded for this task — tasks with no
1406
+ // external dependencies legitimately skip Q5, tasks with no runtime
1407
+ // load dimension skip Q6, etc.
1408
+ const etPending = getPendingGatesForTurn(mid, sid, "execute-task", tid);
1409
+ assertGateCoverage(etPending, "execute-task", { requireAll: false });
1410
+ const gatesToClose = renderGatesToCloseBlock(
1411
+ getGatesForTurn("execute-task"),
1412
+ { pending: new Set(etPending.map((g) => g.gate_id)), allowOmit: true },
1413
+ );
1414
+
1398
1415
  return loadPrompt("execute-task", {
1399
1416
  overridesSection,
1400
1417
  runtimeContext,
@@ -1412,6 +1429,7 @@ export async function buildExecuteTaskPrompt(
1412
1429
  taskSummaryPath,
1413
1430
  inlinedTemplates,
1414
1431
  verificationBudget,
1432
+ gatesToClose,
1415
1433
  skillActivation: buildSkillActivationBlock({
1416
1434
  base,
1417
1435
  milestoneId: mid,
@@ -1477,6 +1495,19 @@ export async function buildCompleteSlicePrompt(
1477
1495
  const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
1478
1496
  const sliceUatPath = join(base, `${sliceRel}/${sid}-UAT.md`);
1479
1497
 
1498
+ // Gates owned by complete-slice (e.g. Q8). Pull from the DB so the
1499
+ // prompt only prompts for gates the plan actually seeded. The tool
1500
+ // handler closes each gate based on the SUMMARY.md section content
1501
+ // after the assistant calls gsd_complete_slice.
1502
+ const csPending = getPendingGatesForTurn(mid, sid, "complete-slice");
1503
+ // coverage check: every pending row must be owned by complete-slice.
1504
+ // requireAll:false because a slice may have already closed some gates.
1505
+ assertGateCoverage(csPending, "complete-slice", { requireAll: false });
1506
+ const gatesToClose = renderGatesToCloseBlock(
1507
+ getGatesForTurn("complete-slice"),
1508
+ { pending: new Set(csPending.map((g) => g.gate_id)), allowOmit: true },
1509
+ );
1510
+
1480
1511
  return loadPrompt("complete-slice", {
1481
1512
  workingDirectory: base,
1482
1513
  milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
@@ -1485,6 +1516,7 @@ export async function buildCompleteSlicePrompt(
1485
1516
  inlinedContext,
1486
1517
  sliceSummaryPath,
1487
1518
  sliceUatPath,
1519
+ gatesToClose,
1488
1520
  });
1489
1521
  }
1490
1522
 
@@ -1675,6 +1707,16 @@ export async function buildValidateMilestonePrompt(
1675
1707
  const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
1676
1708
  const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
1677
1709
 
1710
+ // Every milestone validation turn owns MV01–MV04 unconditionally: the
1711
+ // registry is the source of truth for which gates the validator must
1712
+ // address, and the block below is what the template renders so the
1713
+ // assistant can never accidentally skip one.
1714
+ const mvGates = getGatesForTurn("validate-milestone");
1715
+ const gatesToEvaluate = renderGatesToCloseBlock(mvGates, {
1716
+ pending: new Set(mvGates.map((g) => g.id)),
1717
+ allowOmit: false,
1718
+ });
1719
+
1678
1720
  return loadPrompt("validate-milestone", {
1679
1721
  workingDirectory: base,
1680
1722
  milestoneId: mid,
@@ -1683,6 +1725,7 @@ export async function buildValidateMilestonePrompt(
1683
1725
  inlinedContext,
1684
1726
  validationPath: validationOutputPath,
1685
1727
  remediationRound: String(remediationRound),
1728
+ gatesToEvaluate,
1686
1729
  skillActivation: buildSkillActivationBlock({
1687
1730
  base,
1688
1731
  milestoneId: mid,
@@ -1955,27 +1998,51 @@ export async function buildReactiveExecutePrompt(
1955
1998
  }
1956
1999
 
1957
2000
  // ─── Gate Evaluation ──────────────────────────────────────────────────────
2001
+ //
2002
+ // Gate definitions (question, guidance, owner turn) now live in
2003
+ // gate-registry.ts so that prompt builders, dispatch rules, state
2004
+ // derivation, and tool handlers all consult the same source of truth.
2005
+ // See gate-registry.ts for the full ownership map.
1958
2006
 
1959
- const GATE_QUESTIONS: Record<string, { question: string; guidance: string }> = {
1960
- Q3: {
1961
- question: "How can this be exploited?",
1962
- guidance: [
1963
- "Identify abuse scenarios: parameter tampering, replay attacks, privilege escalation.",
1964
- "Map data exposure risks: PII, tokens, secrets accessible through this slice.",
1965
- "Define input trust boundaries: untrusted user input reaching DB, API, or filesystem.",
1966
- "If none apply, return verdict 'omitted' with rationale explaining why.",
1967
- ].join("\n"),
1968
- },
1969
- Q4: {
1970
- question: "What existing promises does this break?",
1971
- guidance: [
1972
- "List which existing requirements (R001, R003, etc.) are touched by this slice.",
1973
- "Identify what must be re-tested after shipping.",
1974
- "Flag decisions that should be revisited given the new scope.",
1975
- "If no existing requirements are affected, return verdict 'omitted'.",
1976
- ].join("\n"),
1977
- },
1978
- };
2007
+ /**
2008
+ * Render a "Gates to Close" block for turns like `complete-slice` and
2009
+ * `validate-milestone` that own gates which are closed as a side-effect
2010
+ * of writing artifact sections (not via a dedicated gate-evaluate
2011
+ * subagent loop).
2012
+ *
2013
+ * Returns a plain-text block or an empty string if there are no gates to
2014
+ * close, so callers can drop it straight into a template variable.
2015
+ */
2016
+ function renderGatesToCloseBlock(
2017
+ gates: ReadonlyArray<GateDefinition>,
2018
+ opts: { pending: ReadonlySet<string>; allowOmit: boolean },
2019
+ ): string {
2020
+ const applicable = gates.filter((g) => opts.pending.has(g.id));
2021
+ if (applicable.length === 0) return "";
2022
+
2023
+ const lines: string[] = [];
2024
+ lines.push("## Gates to Close");
2025
+ lines.push("");
2026
+ lines.push(
2027
+ "These quality gates are still pending for this unit. You MUST address every one before calling the closing tool — the handler closes the DB row based on whether the corresponding artifact section is present.",
2028
+ );
2029
+ lines.push("");
2030
+ for (const def of applicable) {
2031
+ lines.push(`### ${def.id} — ${def.promptSection}`);
2032
+ lines.push("");
2033
+ lines.push(`**Question:** ${def.question}`);
2034
+ lines.push("");
2035
+ lines.push(def.guidance);
2036
+ if (opts.allowOmit) {
2037
+ lines.push("");
2038
+ lines.push(
2039
+ `If this gate genuinely does not apply to this unit, leave the **${def.promptSection}** section empty and the handler will record it as \`omitted\`. Otherwise, fill the section with concrete evidence.`,
2040
+ );
2041
+ }
2042
+ lines.push("");
2043
+ }
2044
+ return lines.join("\n").trimEnd();
2045
+ }
1979
2046
 
1980
2047
  export async function buildParallelResearchSlicesPrompt(
1981
2048
  mid: string,
@@ -2011,28 +2078,39 @@ export async function buildGateEvaluatePrompt(
2011
2078
  mid: string, midTitle: string, sid: string, sTitle: string,
2012
2079
  base: string,
2013
2080
  ): Promise<string> {
2014
- const pending = getPendingGates(mid, sid, "slice");
2081
+ // Pull only the gates this turn actually owns (Q3/Q4). Filter via the
2082
+ // registry so that scope:"slice" gates owned by other turns (Q8) can't
2083
+ // leak into this prompt and can't block dispatch via silent skip.
2084
+ const pending = getPendingGatesForTurn(mid, sid, "gate-evaluate");
2085
+
2086
+ // Fails loudly if the pending list contains a gate id the registry
2087
+ // doesn't own for this turn. Missing owned gates is allowed here —
2088
+ // `gate-evaluate` is dispatched whenever *any* of its owned gates are
2089
+ // pending, not only when all of them are.
2090
+ assertGateCoverage(pending, "gate-evaluate", { requireAll: false });
2015
2091
 
2016
2092
  // Load the slice plan for context
2017
2093
  const planFile = resolveSliceFile(base, mid, sid, "PLAN");
2018
2094
  const planContent = planFile ? (await loadFile(planFile)) ?? "(plan file empty)" : "(plan file not found)";
2019
2095
 
2020
- // Build per-gate subagent prompts
2096
+ // Build per-gate subagent prompts from the pending rows. Because the
2097
+ // registry has already validated every row, `getGateDefinition` cannot
2098
+ // return undefined here.
2099
+ const pendingIds = new Set(pending.map((g) => g.gate_id));
2100
+ const gateDefs = getGatesForTurn("gate-evaluate").filter((def) => pendingIds.has(def.id));
2101
+
2021
2102
  const subagentSections: string[] = [];
2022
2103
  const gateListLines: string[] = [];
2023
2104
 
2024
- for (const gate of pending) {
2025
- const meta = GATE_QUESTIONS[gate.gate_id];
2026
- if (!meta) continue;
2027
-
2028
- gateListLines.push(`- **${gate.gate_id}**: ${meta.question}`);
2105
+ for (const def of gateDefs) {
2106
+ gateListLines.push(`- **${def.id}**: ${def.question}`);
2029
2107
 
2030
2108
  const subPrompt = [
2031
- `You are evaluating quality gate **${gate.gate_id}** for slice ${sid} (${sTitle}).`,
2109
+ `You are evaluating quality gate **${def.id}** for slice ${sid} (${sTitle}).`,
2032
2110
  "",
2033
- `## Question: ${meta.question}`,
2111
+ `## Question: ${def.question}`,
2034
2112
  "",
2035
- meta.guidance,
2113
+ def.guidance,
2036
2114
  "",
2037
2115
  "## Slice Plan",
2038
2116
  "",
@@ -2044,14 +2122,14 @@ export async function buildGateEvaluatePrompt(
2044
2122
  `Call the \`gsd_save_gate_result\` tool with:`,
2045
2123
  `- \`milestoneId\`: "${mid}"`,
2046
2124
  `- \`sliceId\`: "${sid}"`,
2047
- `- \`gateId\`: "${gate.gate_id}"`,
2125
+ `- \`gateId\`: "${def.id}"`,
2048
2126
  "- `verdict`: \"pass\" (no concerns), \"flag\" (concerns found), or \"omitted\" (not applicable)",
2049
2127
  "- `rationale`: one-sentence justification",
2050
2128
  "- `findings`: detailed markdown findings (or empty if omitted)",
2051
2129
  ].join("\n");
2052
2130
 
2053
2131
  subagentSections.push([
2054
- `### ${gate.gate_id}: ${meta.question}`,
2132
+ `### ${def.id}: ${def.question}`,
2055
2133
  "",
2056
2134
  "Use this as the prompt for a `subagent` call:",
2057
2135
  "",
@@ -272,6 +272,16 @@ export function verifyExpectedArtifact(
272
272
  if (!isValidationTerminal(validationContent)) return false;
273
273
  }
274
274
 
275
+ if (unitType === "plan-milestone") {
276
+ try {
277
+ const roadmap = parseLegacyRoadmap(readFileSync(absPath, "utf-8"));
278
+ if (roadmap.slices.length === 0) return false;
279
+ } catch (err) {
280
+ logWarning("recovery", `plan-milestone roadmap verification failed: ${err instanceof Error ? err.message : String(err)}`);
281
+ return false;
282
+ }
283
+ }
284
+
275
285
  // plan-slice must produce a plan with actual task entries, not just a scaffold.
276
286
  // The plan file may exist from a prior discussion/context step with only headings
277
287
  // but no tasks. Without this check the artifact is considered "complete" and the
@@ -269,16 +269,40 @@ export async function bootstrapAutoSession(
269
269
  //
270
270
  // Precedence:
271
271
  // 1) Explicit session override via /gsd model (this session)
272
- // 2) GSD model preferences from PREFERENCES.md
273
- // 3) Current session model from settings/session restore
272
+ // 2) GSD model preferences from PREFERENCES.md (validated against live auth)
273
+ // 3) Current session model from settings/session restore (if provider ready)
274
274
  //
275
275
  // This preserves #3517 defaults while honoring explicit runtime model
276
276
  // selection for subsequent /gsd runs in the same session.
277
277
  const manualSessionOverride = getSessionModelOverride(ctx.sessionManager.getSessionId());
278
278
  const preferredModel = resolveDefaultSessionModel(ctx.model?.provider);
279
+ // Validate the preferred model against the live registry + provider auth so
280
+ // an unconfigured PREFERENCES.md entry (no API key / OAuth) can't become the
281
+ // start-model snapshot. Without this, every subsequent unit would try to
282
+ // fall back to an unusable model.
283
+ let validatedPreferredModel: { provider: string; id: string } | undefined;
284
+ if (preferredModel) {
285
+ const { resolveModelId } = await import("./auto-model-selection.js");
286
+ const available = ctx.modelRegistry.getAvailable();
287
+ const match = resolveModelId(
288
+ `${preferredModel.provider}/${preferredModel.id}`,
289
+ available,
290
+ ctx.model?.provider,
291
+ );
292
+ if (match) {
293
+ validatedPreferredModel = { provider: match.provider, id: match.id };
294
+ } else {
295
+ ctx.ui.notify(
296
+ `Preferred model ${preferredModel.provider}/${preferredModel.id} from PREFERENCES.md is not configured; falling back to session default.`,
297
+ "warning",
298
+ );
299
+ }
300
+ }
301
+ const sessionModelReady =
302
+ ctx.model && ctx.modelRegistry.isProviderRequestReady(ctx.model.provider);
279
303
  const startModelSnapshot = manualSessionOverride
280
- ?? preferredModel
281
- ?? (ctx.model
304
+ ?? validatedPreferredModel
305
+ ?? (sessionModelReady && ctx.model
282
306
  ? { provider: ctx.model.provider, id: ctx.model.id }
283
307
  : null);
284
308
 
@@ -600,6 +624,9 @@ export async function bootstrapAutoSession(
600
624
  s.consecutiveCompleteBootstraps = 0;
601
625
 
602
626
  // ── Initialize session state ──
627
+ // Notify shared phase state so subagent conflict checks can fire
628
+ const { activateGSD: activateGSDPhaseState } = await import("../shared/gsd-phase-state.js");
629
+ activateGSDPhaseState();
603
630
  s.active = true;
604
631
  s.stepMode = requestedStepMode;
605
632
  s.verbose = verboseMode;
@@ -115,6 +115,7 @@ import {
115
115
  resetSkillTelemetry,
116
116
  } from "./skill-telemetry.js";
117
117
  import { getRtkSessionSavings } from "../shared/rtk-session-stats.js";
118
+ import { deactivateGSD } from "../shared/gsd-phase-state.js";
118
119
  import {
119
120
  initMetrics,
120
121
  resetMetrics,
@@ -622,6 +623,7 @@ function handleLostSessionLock(
622
623
  });
623
624
  s.active = false;
624
625
  s.paused = false;
626
+ deactivateGSD();
625
627
  clearUnitTimeout();
626
628
  restoreProjectRootEnv();
627
629
  restoreMilestoneLockEnv();
@@ -659,6 +661,7 @@ function handleLostSessionLock(
659
661
  function cleanupAfterLoopExit(ctx: ExtensionContext): void {
660
662
  s.currentUnit = null;
661
663
  s.active = false;
664
+ deactivateGSD();
662
665
  clearUnitTimeout();
663
666
  restoreProjectRootEnv();
664
667
  restoreMilestoneLockEnv();
@@ -674,9 +677,13 @@ function cleanupAfterLoopExit(ctx: ExtensionContext): void {
674
677
  logWarning("session", `lock cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
675
678
  }
676
679
 
677
- ctx.ui.setStatus("gsd-auto", undefined);
678
- ctx.ui.setWidget("gsd-progress", undefined);
679
- ctx.ui.setFooter(undefined);
680
+ // A transient provider-error pause intentionally leaves the paused badge
681
+ // visible so the user still has a resumable auto-mode signal on screen.
682
+ if (!s.paused) {
683
+ ctx.ui.setStatus("gsd-auto", undefined);
684
+ ctx.ui.setWidget("gsd-progress", undefined);
685
+ ctx.ui.setFooter(undefined);
686
+ }
680
687
 
681
688
  // Restore CWD out of worktree back to original project root
682
689
  if (s.originalBasePath) {
@@ -788,7 +795,22 @@ export async function stopAuto(
788
795
  debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
789
796
  }
790
797
 
791
- // ── Step 5: DB cleanup ──
798
+ // ── Step 5: Rebuild state while DB is still open (#3599) ──
799
+ // rebuildState() calls deriveState() which needs the DB for authoritative
800
+ // state. Previously this ran after closeDatabase(), forcing a filesystem
801
+ // fallback that could disagree with the DB-backed dispatch decisions —
802
+ // a split-brain where dispatch says "blocked" but STATE.md shows work.
803
+ if (s.basePath) {
804
+ try {
805
+ await rebuildState(s.basePath);
806
+ } catch (e) {
807
+ debugLog("stop-rebuild-state-failed", {
808
+ error: e instanceof Error ? e.message : String(e),
809
+ });
810
+ }
811
+ }
812
+
813
+ // ── Step 6: DB cleanup ──
792
814
  if (isDbAvailable()) {
793
815
  try {
794
816
  const { closeDatabase } = await import("./gsd-db.js");
@@ -800,7 +822,7 @@ export async function stopAuto(
800
822
  }
801
823
  }
802
824
 
803
- // ── Step 6: Restore basePath and chdir ──
825
+ // ── Step 7: Restore basePath and chdir ──
804
826
  try {
805
827
  if (s.originalBasePath) {
806
828
  s.basePath = s.originalBasePath;
@@ -815,7 +837,7 @@ export async function stopAuto(
815
837
  debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
816
838
  }
817
839
 
818
- // ── Step 7: Ledger notification ──
840
+ // ── Step 8: Ledger notification ──
819
841
  try {
820
842
  const ledger = getLedger();
821
843
  if (ledger && ledger.units.length > 0) {
@@ -831,17 +853,6 @@ export async function stopAuto(
831
853
  debugLog("stop-cleanup-ledger", { error: e instanceof Error ? e.message : String(e) });
832
854
  }
833
855
 
834
- // ── Step 8: Rebuild state ──
835
- if (s.basePath) {
836
- try {
837
- await rebuildState(s.basePath);
838
- } catch (e) {
839
- debugLog("stop-rebuild-state-failed", {
840
- error: e instanceof Error ? e.message : String(e),
841
- });
842
- }
843
- }
844
-
845
856
  // ── Step 9: Cmux sidebar / event log ──
846
857
  try {
847
858
  clearCmuxSidebar(loadedPreferences);
@@ -1024,6 +1035,7 @@ export async function pauseAuto(
1024
1035
 
1025
1036
  s.active = false;
1026
1037
  s.paused = true;
1038
+ deactivateGSD();
1027
1039
  restoreProjectRootEnv();
1028
1040
  restoreMilestoneLockEnv();
1029
1041
  s.pendingVerificationRetry = null;
@@ -1709,9 +1721,6 @@ export async function dispatchHookUnit(
1709
1721
  return true;
1710
1722
  }
1711
1723
 
1712
- // Direct phase dispatch → auto-direct-dispatch.ts
1713
- export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
1714
-
1715
1724
  // Re-export recovery functions for external consumers
1716
1725
  export {
1717
1726
  buildLoopRemediationSteps,
@@ -1026,12 +1026,12 @@ export function registerDbTools(pi: ExtensionAPI): void {
1026
1026
  name: "gsd_save_gate_result",
1027
1027
  label: "Save Gate Result",
1028
1028
  description:
1029
- "Save the result of a quality gate evaluation (Q3-Q8) to the GSD database. " +
1029
+ "Save the result of a quality gate evaluation (Q3-Q8 or MV01-MV04) to the GSD database. " +
1030
1030
  "Called by gate evaluation sub-agents after analyzing a specific quality question.",
1031
1031
  promptSnippet: "Save quality gate evaluation result (verdict, rationale, findings)",
1032
1032
  promptGuidelines: [
1033
1033
  "Use gsd_save_gate_result after evaluating a quality gate question.",
1034
- "gateId must be one of: Q3, Q4, Q5, Q6, Q7, Q8.",
1034
+ "gateId must be one of: Q3, Q4, Q5, Q6, Q7, Q8, MV01, MV02, MV03, MV04.",
1035
1035
  "verdict must be: pass (no concerns), flag (concerns found), or omitted (not applicable).",
1036
1036
  "rationale should be a one-sentence justification for the verdict.",
1037
1037
  "findings should contain detailed markdown analysis (or empty string if omitted).",
@@ -1039,7 +1039,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
1039
1039
  parameters: Type.Object({
1040
1040
  milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
1041
1041
  sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
1042
- gateId: Type.String({ description: "Gate ID: Q3, Q4, Q5, Q6, Q7, or Q8" }),
1042
+ gateId: Type.String({ description: "Gate ID: Q3, Q4, Q5, Q6, Q7, Q8, MV01, MV02, MV03, or MV04" }),
1043
1043
  taskId: Type.Optional(Type.String({ description: "Task ID for task-scoped gates (Q5/Q6/Q7)" })),
1044
1044
  verdict: Type.String({ description: "pass, flag, or omitted" }),
1045
1045
  rationale: Type.String({ description: "One-sentence justification" }),
@@ -181,14 +181,10 @@ export function registerHooks(pi: ExtensionAPI): void {
181
181
  // Only gate-shaped ask_user_questions calls should block execution.
182
182
  // The gate stays pending until the user selects the approval option.
183
183
  if (event.toolName === "ask_user_questions") {
184
- const milestoneId = getDiscussionMilestoneId(discussionBasePath);
185
- const inDiscussion = milestoneId !== null || isQueuePhaseActive();
186
- if (inDiscussion) {
187
- const questions: any[] = (event.input as any)?.questions ?? [];
188
- const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
189
- if (typeof questionId === "string") {
190
- setPendingGate(questionId);
191
- }
184
+ const questions: any[] = (event.input as any)?.questions ?? [];
185
+ const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
186
+ if (typeof questionId === "string") {
187
+ setPendingGate(questionId);
192
188
  }
193
189
  }
194
190
 
@@ -286,7 +282,6 @@ export function registerHooks(pi: ExtensionAPI): void {
286
282
  if (event.toolName !== "ask_user_questions") return;
287
283
  const milestoneId = getDiscussionMilestoneId(process.cwd());
288
284
  const queueActive = isQueuePhaseActive();
289
- if (!milestoneId && !queueActive) return;
290
285
 
291
286
  const details = event.details as any;
292
287
 
@@ -319,13 +314,16 @@ export function registerHooks(pi: ExtensionAPI): void {
319
314
  // Only unlock the gate if the user selected the first option (confirmation).
320
315
  // Cross-references against the question's defined options to reject free-form "Other" text.
321
316
  const answer = details.response?.answers?.[question.id];
317
+ const inferredMilestoneId = extractDepthVerificationMilestoneId(question.id) ?? milestoneId;
322
318
  if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
323
- markDepthVerified(extractDepthVerificationMilestoneId(question.id) ?? milestoneId);
319
+ markDepthVerified(inferredMilestoneId);
320
+ clearPendingGate();
324
321
  }
325
322
  break;
326
323
  }
327
324
  }
328
325
 
326
+ if (!milestoneId && !queueActive) return;
329
327
  if (!milestoneId) return;
330
328
 
331
329
  const basePath = process.cwd();