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
@@ -13,6 +13,69 @@ import { runPreDispatch, runDispatch, runGuards, runUnitPhase, runFinalize, } fr
13
13
  import { debugLog } from "../debug-logger.js";
14
14
  import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
15
15
  import { resolveEngine } from "../engine-resolver.js";
16
+ import { logWarning } from "../workflow-logger.js";
17
+ import { gsdRoot } from "../paths.js";
18
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ // ── Stuck detection persistence (#3704) ──────────────────────────────────
21
+ // Persist stuck detection state to disk so it survives session restarts.
22
+ // Without this, restarting auto-mode resets all counters, allowing the
23
+ // same blocked unit to burn a full retry budget each session.
24
+ function stuckStatePath(basePath) {
25
+ return join(gsdRoot(basePath), "runtime", "stuck-state.json");
26
+ }
27
+ function loadStuckState(basePath) {
28
+ try {
29
+ const data = JSON.parse(readFileSync(stuckStatePath(basePath), "utf-8"));
30
+ return {
31
+ recentUnits: Array.isArray(data.recentUnits) ? data.recentUnits : [],
32
+ stuckRecoveryAttempts: typeof data.stuckRecoveryAttempts === "number" ? data.stuckRecoveryAttempts : 0,
33
+ };
34
+ }
35
+ catch (err) {
36
+ debugLog("autoLoop", { phase: "load-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
37
+ return { recentUnits: [], stuckRecoveryAttempts: 0 };
38
+ }
39
+ }
40
+ function saveStuckState(basePath, state) {
41
+ try {
42
+ const filePath = stuckStatePath(basePath);
43
+ mkdirSync(join(gsdRoot(basePath), "runtime"), { recursive: true });
44
+ writeFileSync(filePath, JSON.stringify({
45
+ recentUnits: state.recentUnits.slice(-20), // keep last 20 entries
46
+ stuckRecoveryAttempts: state.stuckRecoveryAttempts,
47
+ updatedAt: new Date().toISOString(),
48
+ }) + "\n");
49
+ }
50
+ catch (err) {
51
+ debugLog("autoLoop", { phase: "save-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
52
+ }
53
+ }
54
+ // ── Memory pressure monitoring (#3331) ──────────────────────────────────
55
+ // Check heap usage every N iterations and trigger graceful shutdown before
56
+ // the OS OOM killer sends SIGKILL. The threshold is 90% of the V8 heap
57
+ // limit (--max-old-space-size or default ~1.5-4GB depending on platform).
58
+ const MEMORY_CHECK_INTERVAL = 5; // check every 5 iterations
59
+ const MEMORY_PRESSURE_THRESHOLD = 0.85; // 85% of heap limit
60
+ function checkMemoryPressure() {
61
+ const mem = process.memoryUsage();
62
+ // v8.getHeapStatistics() gives heap_size_limit but requires import
63
+ // Use a conservative estimate: RSS > 3GB is danger zone on most systems
64
+ const heapMB = Math.round(mem.heapUsed / 1024 / 1024);
65
+ const rssMB = Math.round(mem.rss / 1024 / 1024);
66
+ // Try to get the actual V8 heap limit
67
+ let limitMB = 4096; // conservative default
68
+ try {
69
+ const v8 = require("node:v8");
70
+ const stats = v8.getHeapStatistics();
71
+ limitMB = Math.round(stats.heap_size_limit / 1024 / 1024);
72
+ }
73
+ catch {
74
+ limitMB = 4096; /* v8 stats unavailable — use conservative default */
75
+ }
76
+ const pct = heapMB / limitMB;
77
+ return { pressured: pct > MEMORY_PRESSURE_THRESHOLD, heapMB, limitMB, pct };
78
+ }
16
79
  /**
17
80
  * Main auto-mode execution loop. Iterates: derive → dispatch → guards →
18
81
  * runUnit → finalize → repeat. Exits when s.active becomes false or a
@@ -24,7 +87,13 @@ import { resolveEngine } from "../engine-resolver.js";
24
87
  export async function autoLoop(ctx, pi, s, deps) {
25
88
  debugLog("autoLoop", { phase: "enter" });
26
89
  let iteration = 0;
27
- const loopState = { recentUnits: [], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 };
90
+ // Load persisted stuck state so counters survive session restarts (#3704)
91
+ const persisted = loadStuckState(s.basePath);
92
+ const loopState = {
93
+ recentUnits: persisted.recentUnits,
94
+ stuckRecoveryAttempts: persisted.stuckRecoveryAttempts,
95
+ consecutiveFinalizeTimeouts: 0,
96
+ };
28
97
  let consecutiveErrors = 0;
29
98
  let consecutiveCooldowns = 0;
30
99
  const recentErrorMessages = [];
@@ -44,6 +113,19 @@ export async function autoLoop(ctx, pi, s, deps) {
44
113
  await deps.stopAuto(ctx, pi, `Safety: loop exceeded ${MAX_LOOP_ITERATIONS} iterations — possible runaway`);
45
114
  break;
46
115
  }
116
+ // ── Memory pressure check (#3331) ──
117
+ // Graceful shutdown before OOM killer sends SIGKILL.
118
+ if (iteration % MEMORY_CHECK_INTERVAL === 0) {
119
+ const mem = checkMemoryPressure();
120
+ debugLog("autoLoop", { phase: "memory-check", ...mem });
121
+ if (mem.pressured) {
122
+ logWarning("dispatch", `Memory pressure: ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%) — stopping auto-mode to prevent OOM kill`);
123
+ await deps.stopAuto(ctx, pi, `Memory pressure: heap at ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%). ` +
124
+ `Stopping gracefully to prevent OOM kill after ${iteration} iterations. ` +
125
+ `Resume with /gsd auto to continue from where you left off.`);
126
+ break;
127
+ }
128
+ }
47
129
  if (!s.cmdCtx) {
48
130
  debugLog("autoLoop", { phase: "exit", reason: "no-cmdCtx" });
49
131
  break;
@@ -162,6 +244,7 @@ export async function autoLoop(ctx, pi, s, deps) {
162
244
  consecutiveCooldowns = 0;
163
245
  recentErrorMessages.length = 0;
164
246
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
247
+ saveStuckState(s.basePath, loopState); // persist across session restarts (#3704)
165
248
  debugLog("autoLoop", { phase: "iteration-complete", iteration });
166
249
  if (reconcileResult.outcome === "milestone-complete") {
167
250
  await deps.stopAuto(ctx, pi, "Workflow complete");
@@ -13,6 +13,7 @@ import { runUnit } from "./run-unit.js";
13
13
  import { debugLog } from "../debug-logger.js";
14
14
  import { PROJECT_FILES } from "../detection.js";
15
15
  import { MergeConflictError } from "../git-service.js";
16
+ import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
16
17
  import { join, basename, dirname, parse as parsePath } from "node:path";
17
18
  import { existsSync, cpSync, readdirSync } from "node:fs";
18
19
  import { logWarning, logError } from "../workflow-logger.js";
@@ -770,6 +771,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
770
771
  s.currentUnit.id === unitId);
771
772
  const previousTier = s.currentUnitRouting?.tier;
772
773
  s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
774
+ setCurrentPhase(unitType);
773
775
  s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
774
776
  const unitStartSeq = ic.nextSeq();
775
777
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
@@ -1115,6 +1117,7 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
1115
1117
  // Detach session from the timed-out unit so late async completions
1116
1118
  // cannot mutate state for the next unit (#3757).
1117
1119
  s.currentUnit = null;
1120
+ clearCurrentPhase();
1118
1121
  loopState.consecutiveFinalizeTimeouts++;
1119
1122
  debugLog("autoLoop", {
1120
1123
  phase: "pre-verification-timeout",
@@ -1189,6 +1192,7 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
1189
1192
  // Detach session from the timed-out unit so late async completions
1190
1193
  // cannot mutate state for the next unit (#3757).
1191
1194
  s.currentUnit = null;
1195
+ clearCurrentPhase();
1192
1196
  loopState.consecutiveFinalizeTimeouts++;
1193
1197
  debugLog("autoLoop", {
1194
1198
  phase: "post-verification-timeout",
@@ -16,6 +16,7 @@ import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
16
16
  import { loadPrompt } from "./prompt-loader.js";
17
17
  import { resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName, } from "./paths.js";
18
18
  import { invalidateAllCaches } from "./cache.js";
19
+ import { rebuildState } from "./doctor.js";
19
20
  import { parseUnitId } from "./unit-id.js";
20
21
  import { closeoutUnit } from "./auto-unit-closeout.js";
21
22
  import { autoCommitCurrentBranch, } from "./worktree.js";
@@ -288,6 +289,11 @@ export async function postUnitPreVerification(pctx, opts) {
288
289
  debugLog("postUnit", { phase: "browser-teardown", status: "closed" });
289
290
  }
290
291
  });
292
+ // Keep the on-disk STATE.md aligned with the live derived state after
293
+ // ordinary unit completion, before any worktree state is synced back.
294
+ await runSafely("postUnit", "state-rebuild", async () => {
295
+ await rebuildState(s.basePath);
296
+ });
291
297
  // Sync worktree state back to project root (skipped for lightweight sidecars)
292
298
  if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
293
299
  await runSafely("postUnit", "worktree-sync", () => {
@@ -15,7 +15,8 @@ import { getLoadedSkills } from "@gsd/pi-coding-agent";
15
15
  import { join, basename } from "node:path";
16
16
  import { existsSync } from "node:fs";
17
17
  import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
18
- import { getPendingGates } from "./gsd-db.js";
18
+ import { getPendingGatesForTurn } from "./gsd-db.js";
19
+ import { assertGateCoverage, getGatesForTurn, } from "./gate-registry.js";
19
20
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
20
21
  import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
21
22
  import { logWarning } from "./workflow-logger.js";
@@ -1221,6 +1222,13 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
1221
1222
  ? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
1222
1223
  : "";
1223
1224
  const phaseAnchorSection = planAnchor ? formatAnchorForPrompt(planAnchor) : "";
1225
+ // Task-scoped gates owned by execute-task (Q5/Q6/Q7). Pull only the
1226
+ // gates that plan-slice actually seeded for this task — tasks with no
1227
+ // external dependencies legitimately skip Q5, tasks with no runtime
1228
+ // load dimension skip Q6, etc.
1229
+ const etPending = getPendingGatesForTurn(mid, sid, "execute-task", tid);
1230
+ assertGateCoverage(etPending, "execute-task", { requireAll: false });
1231
+ const gatesToClose = renderGatesToCloseBlock(getGatesForTurn("execute-task"), { pending: new Set(etPending.map((g) => g.gate_id)), allowOmit: true });
1224
1232
  return loadPrompt("execute-task", {
1225
1233
  overridesSection,
1226
1234
  runtimeContext,
@@ -1238,6 +1246,7 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
1238
1246
  taskSummaryPath,
1239
1247
  inlinedTemplates,
1240
1248
  verificationBudget,
1249
+ gatesToClose,
1241
1250
  skillActivation: buildSkillActivationBlock({
1242
1251
  base,
1243
1252
  milestoneId: mid,
@@ -1298,6 +1307,15 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
1298
1307
  const sliceRel = relSlicePath(base, mid, sid);
1299
1308
  const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
1300
1309
  const sliceUatPath = join(base, `${sliceRel}/${sid}-UAT.md`);
1310
+ // Gates owned by complete-slice (e.g. Q8). Pull from the DB so the
1311
+ // prompt only prompts for gates the plan actually seeded. The tool
1312
+ // handler closes each gate based on the SUMMARY.md section content
1313
+ // after the assistant calls gsd_complete_slice.
1314
+ const csPending = getPendingGatesForTurn(mid, sid, "complete-slice");
1315
+ // coverage check: every pending row must be owned by complete-slice.
1316
+ // requireAll:false because a slice may have already closed some gates.
1317
+ assertGateCoverage(csPending, "complete-slice", { requireAll: false });
1318
+ const gatesToClose = renderGatesToCloseBlock(getGatesForTurn("complete-slice"), { pending: new Set(csPending.map((g) => g.gate_id)), allowOmit: true });
1301
1319
  return loadPrompt("complete-slice", {
1302
1320
  workingDirectory: base,
1303
1321
  milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
@@ -1306,6 +1324,7 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
1306
1324
  inlinedContext,
1307
1325
  sliceSummaryPath,
1308
1326
  sliceUatPath,
1327
+ gatesToClose,
1309
1328
  });
1310
1329
  }
1311
1330
  export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
@@ -1498,6 +1517,15 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
1498
1517
  const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1499
1518
  const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
1500
1519
  const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
1520
+ // Every milestone validation turn owns MV01–MV04 unconditionally: the
1521
+ // registry is the source of truth for which gates the validator must
1522
+ // address, and the block below is what the template renders so the
1523
+ // assistant can never accidentally skip one.
1524
+ const mvGates = getGatesForTurn("validate-milestone");
1525
+ const gatesToEvaluate = renderGatesToCloseBlock(mvGates, {
1526
+ pending: new Set(mvGates.map((g) => g.id)),
1527
+ allowOmit: false,
1528
+ });
1501
1529
  return loadPrompt("validate-milestone", {
1502
1530
  workingDirectory: base,
1503
1531
  milestoneId: mid,
@@ -1506,6 +1534,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
1506
1534
  inlinedContext,
1507
1535
  validationPath: validationOutputPath,
1508
1536
  remediationRound: String(remediationRound),
1537
+ gatesToEvaluate,
1509
1538
  skillActivation: buildSkillActivationBlock({
1510
1539
  base,
1511
1540
  milestoneId: mid,
@@ -1740,26 +1769,43 @@ export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, rea
1740
1769
  });
1741
1770
  }
1742
1771
  // ─── Gate Evaluation ──────────────────────────────────────────────────────
1743
- const GATE_QUESTIONS = {
1744
- Q3: {
1745
- question: "How can this be exploited?",
1746
- guidance: [
1747
- "Identify abuse scenarios: parameter tampering, replay attacks, privilege escalation.",
1748
- "Map data exposure risks: PII, tokens, secrets accessible through this slice.",
1749
- "Define input trust boundaries: untrusted user input reaching DB, API, or filesystem.",
1750
- "If none apply, return verdict 'omitted' with rationale explaining why.",
1751
- ].join("\n"),
1752
- },
1753
- Q4: {
1754
- question: "What existing promises does this break?",
1755
- guidance: [
1756
- "List which existing requirements (R001, R003, etc.) are touched by this slice.",
1757
- "Identify what must be re-tested after shipping.",
1758
- "Flag decisions that should be revisited given the new scope.",
1759
- "If no existing requirements are affected, return verdict 'omitted'.",
1760
- ].join("\n"),
1761
- },
1762
- };
1772
+ //
1773
+ // Gate definitions (question, guidance, owner turn) now live in
1774
+ // gate-registry.ts so that prompt builders, dispatch rules, state
1775
+ // derivation, and tool handlers all consult the same source of truth.
1776
+ // See gate-registry.ts for the full ownership map.
1777
+ /**
1778
+ * Render a "Gates to Close" block for turns like `complete-slice` and
1779
+ * `validate-milestone` that own gates which are closed as a side-effect
1780
+ * of writing artifact sections (not via a dedicated gate-evaluate
1781
+ * subagent loop).
1782
+ *
1783
+ * Returns a plain-text block or an empty string if there are no gates to
1784
+ * close, so callers can drop it straight into a template variable.
1785
+ */
1786
+ function renderGatesToCloseBlock(gates, opts) {
1787
+ const applicable = gates.filter((g) => opts.pending.has(g.id));
1788
+ if (applicable.length === 0)
1789
+ return "";
1790
+ const lines = [];
1791
+ lines.push("## Gates to Close");
1792
+ lines.push("");
1793
+ lines.push("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.");
1794
+ lines.push("");
1795
+ for (const def of applicable) {
1796
+ lines.push(`### ${def.id} — ${def.promptSection}`);
1797
+ lines.push("");
1798
+ lines.push(`**Question:** ${def.question}`);
1799
+ lines.push("");
1800
+ lines.push(def.guidance);
1801
+ if (opts.allowOmit) {
1802
+ lines.push("");
1803
+ lines.push(`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.`);
1804
+ }
1805
+ lines.push("");
1806
+ }
1807
+ return lines.join("\n").trimEnd();
1808
+ }
1763
1809
  export async function buildParallelResearchSlicesPrompt(mid, midTitle, slices, basePath) {
1764
1810
  // Build individual research-slice prompts for each slice
1765
1811
  const subagentSections = [];
@@ -1784,24 +1830,33 @@ export async function buildParallelResearchSlicesPrompt(mid, midTitle, slices, b
1784
1830
  });
1785
1831
  }
1786
1832
  export async function buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, base) {
1787
- const pending = getPendingGates(mid, sid, "slice");
1833
+ // Pull only the gates this turn actually owns (Q3/Q4). Filter via the
1834
+ // registry so that scope:"slice" gates owned by other turns (Q8) can't
1835
+ // leak into this prompt and can't block dispatch via silent skip.
1836
+ const pending = getPendingGatesForTurn(mid, sid, "gate-evaluate");
1837
+ // Fails loudly if the pending list contains a gate id the registry
1838
+ // doesn't own for this turn. Missing owned gates is allowed here —
1839
+ // `gate-evaluate` is dispatched whenever *any* of its owned gates are
1840
+ // pending, not only when all of them are.
1841
+ assertGateCoverage(pending, "gate-evaluate", { requireAll: false });
1788
1842
  // Load the slice plan for context
1789
1843
  const planFile = resolveSliceFile(base, mid, sid, "PLAN");
1790
1844
  const planContent = planFile ? (await loadFile(planFile)) ?? "(plan file empty)" : "(plan file not found)";
1791
- // Build per-gate subagent prompts
1845
+ // Build per-gate subagent prompts from the pending rows. Because the
1846
+ // registry has already validated every row, `getGateDefinition` cannot
1847
+ // return undefined here.
1848
+ const pendingIds = new Set(pending.map((g) => g.gate_id));
1849
+ const gateDefs = getGatesForTurn("gate-evaluate").filter((def) => pendingIds.has(def.id));
1792
1850
  const subagentSections = [];
1793
1851
  const gateListLines = [];
1794
- for (const gate of pending) {
1795
- const meta = GATE_QUESTIONS[gate.gate_id];
1796
- if (!meta)
1797
- continue;
1798
- gateListLines.push(`- **${gate.gate_id}**: ${meta.question}`);
1852
+ for (const def of gateDefs) {
1853
+ gateListLines.push(`- **${def.id}**: ${def.question}`);
1799
1854
  const subPrompt = [
1800
- `You are evaluating quality gate **${gate.gate_id}** for slice ${sid} (${sTitle}).`,
1855
+ `You are evaluating quality gate **${def.id}** for slice ${sid} (${sTitle}).`,
1801
1856
  "",
1802
- `## Question: ${meta.question}`,
1857
+ `## Question: ${def.question}`,
1803
1858
  "",
1804
- meta.guidance,
1859
+ def.guidance,
1805
1860
  "",
1806
1861
  "## Slice Plan",
1807
1862
  "",
@@ -1813,13 +1868,13 @@ export async function buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, base)
1813
1868
  `Call the \`gsd_save_gate_result\` tool with:`,
1814
1869
  `- \`milestoneId\`: "${mid}"`,
1815
1870
  `- \`sliceId\`: "${sid}"`,
1816
- `- \`gateId\`: "${gate.gate_id}"`,
1871
+ `- \`gateId\`: "${def.id}"`,
1817
1872
  "- `verdict`: \"pass\" (no concerns), \"flag\" (concerns found), or \"omitted\" (not applicable)",
1818
1873
  "- `rationale`: one-sentence justification",
1819
1874
  "- `findings`: detailed markdown findings (or empty if omitted)",
1820
1875
  ].join("\n");
1821
1876
  subagentSections.push([
1822
- `### ${gate.gate_id}: ${meta.question}`,
1877
+ `### ${def.id}: ${def.question}`,
1823
1878
  "",
1824
1879
  "Use this as the prompt for a `subagent` call:",
1825
1880
  "",
@@ -224,6 +224,17 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
224
224
  if (!isValidationTerminal(validationContent))
225
225
  return false;
226
226
  }
227
+ if (unitType === "plan-milestone") {
228
+ try {
229
+ const roadmap = parseLegacyRoadmap(readFileSync(absPath, "utf-8"));
230
+ if (roadmap.slices.length === 0)
231
+ return false;
232
+ }
233
+ catch (err) {
234
+ logWarning("recovery", `plan-milestone roadmap verification failed: ${err instanceof Error ? err.message : String(err)}`);
235
+ return false;
236
+ }
237
+ }
227
238
  // plan-slice must produce a plan with actual task entries, not just a scaffold.
228
239
  // The plan file may exist from a prior discussion/context step with only headings
229
240
  // but no tasks. Without this check the artifact is considered "complete" and the
@@ -190,16 +190,33 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
190
190
  //
191
191
  // Precedence:
192
192
  // 1) Explicit session override via /gsd model (this session)
193
- // 2) GSD model preferences from PREFERENCES.md
194
- // 3) Current session model from settings/session restore
193
+ // 2) GSD model preferences from PREFERENCES.md (validated against live auth)
194
+ // 3) Current session model from settings/session restore (if provider ready)
195
195
  //
196
196
  // This preserves #3517 defaults while honoring explicit runtime model
197
197
  // selection for subsequent /gsd runs in the same session.
198
198
  const manualSessionOverride = getSessionModelOverride(ctx.sessionManager.getSessionId());
199
199
  const preferredModel = resolveDefaultSessionModel(ctx.model?.provider);
200
+ // Validate the preferred model against the live registry + provider auth so
201
+ // an unconfigured PREFERENCES.md entry (no API key / OAuth) can't become the
202
+ // start-model snapshot. Without this, every subsequent unit would try to
203
+ // fall back to an unusable model.
204
+ let validatedPreferredModel;
205
+ if (preferredModel) {
206
+ const { resolveModelId } = await import("./auto-model-selection.js");
207
+ const available = ctx.modelRegistry.getAvailable();
208
+ const match = resolveModelId(`${preferredModel.provider}/${preferredModel.id}`, available, ctx.model?.provider);
209
+ if (match) {
210
+ validatedPreferredModel = { provider: match.provider, id: match.id };
211
+ }
212
+ else {
213
+ ctx.ui.notify(`Preferred model ${preferredModel.provider}/${preferredModel.id} from PREFERENCES.md is not configured; falling back to session default.`, "warning");
214
+ }
215
+ }
216
+ const sessionModelReady = ctx.model && ctx.modelRegistry.isProviderRequestReady(ctx.model.provider);
200
217
  const startModelSnapshot = manualSessionOverride
201
- ?? preferredModel
202
- ?? (ctx.model
218
+ ?? validatedPreferredModel
219
+ ?? (sessionModelReady && ctx.model
203
220
  ? { provider: ctx.model.provider, id: ctx.model.id }
204
221
  : null);
205
222
  try {
@@ -453,6 +470,9 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
453
470
  // Successfully resolved an active milestone — reset the re-entry guard
454
471
  s.consecutiveCompleteBootstraps = 0;
455
472
  // ── Initialize session state ──
473
+ // Notify shared phase state so subagent conflict checks can fire
474
+ const { activateGSD: activateGSDPhaseState } = await import("../shared/gsd-phase-state.js");
475
+ activateGSDPhaseState();
456
476
  s.active = true;
457
477
  s.stepMode = requestedStepMode;
458
478
  s.verbose = verboseMode;
@@ -34,6 +34,7 @@ import { preDispatchHealthGate, resetProactiveHealing, setLevelChangeCallback, }
34
34
  import { clearSkillSnapshot } from "./skill-discovery.js";
35
35
  import { captureAvailableSkills, resetSkillTelemetry, } from "./skill-telemetry.js";
36
36
  import { getRtkSessionSavings } from "../shared/rtk-session-stats.js";
37
+ import { deactivateGSD } from "../shared/gsd-phase-state.js";
37
38
  import { initMetrics, resetMetrics, getLedger, getProjectTotals, formatCost, formatTokenCount, } from "./metrics.js";
38
39
  import { logWarning } from "./workflow-logger.js";
39
40
  import { homedir } from "node:os";
@@ -374,6 +375,7 @@ function handleLostSessionLock(ctx, lockStatus) {
374
375
  });
375
376
  s.active = false;
376
377
  s.paused = false;
378
+ deactivateGSD();
377
379
  clearUnitTimeout();
378
380
  restoreProjectRootEnv();
379
381
  restoreMilestoneLockEnv();
@@ -406,6 +408,7 @@ function handleLostSessionLock(ctx, lockStatus) {
406
408
  function cleanupAfterLoopExit(ctx) {
407
409
  s.currentUnit = null;
408
410
  s.active = false;
411
+ deactivateGSD();
409
412
  clearUnitTimeout();
410
413
  restoreProjectRootEnv();
411
414
  restoreMilestoneLockEnv();
@@ -422,9 +425,13 @@ function cleanupAfterLoopExit(ctx) {
422
425
  /* best-effort — mirror stopAuto cleanup */
423
426
  logWarning("session", `lock cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
424
427
  }
425
- ctx.ui.setStatus("gsd-auto", undefined);
426
- ctx.ui.setWidget("gsd-progress", undefined);
427
- ctx.ui.setFooter(undefined);
428
+ // A transient provider-error pause intentionally leaves the paused badge
429
+ // visible so the user still has a resumable auto-mode signal on screen.
430
+ if (!s.paused) {
431
+ ctx.ui.setStatus("gsd-auto", undefined);
432
+ ctx.ui.setWidget("gsd-progress", undefined);
433
+ ctx.ui.setFooter(undefined);
434
+ }
428
435
  // Restore CWD out of worktree back to original project root
429
436
  if (s.originalBasePath) {
430
437
  s.basePath = s.originalBasePath;
@@ -526,7 +533,22 @@ export async function stopAuto(ctx, pi, reason) {
526
533
  catch (e) {
527
534
  debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
528
535
  }
529
- // ── Step 5: DB cleanup ──
536
+ // ── Step 5: Rebuild state while DB is still open (#3599) ──
537
+ // rebuildState() calls deriveState() which needs the DB for authoritative
538
+ // state. Previously this ran after closeDatabase(), forcing a filesystem
539
+ // fallback that could disagree with the DB-backed dispatch decisions —
540
+ // a split-brain where dispatch says "blocked" but STATE.md shows work.
541
+ if (s.basePath) {
542
+ try {
543
+ await rebuildState(s.basePath);
544
+ }
545
+ catch (e) {
546
+ debugLog("stop-rebuild-state-failed", {
547
+ error: e instanceof Error ? e.message : String(e),
548
+ });
549
+ }
550
+ }
551
+ // ── Step 6: DB cleanup ──
530
552
  if (isDbAvailable()) {
531
553
  try {
532
554
  const { closeDatabase } = await import("./gsd-db.js");
@@ -538,7 +560,7 @@ export async function stopAuto(ctx, pi, reason) {
538
560
  });
539
561
  }
540
562
  }
541
- // ── Step 6: Restore basePath and chdir ──
563
+ // ── Step 7: Restore basePath and chdir ──
542
564
  try {
543
565
  if (s.originalBasePath) {
544
566
  s.basePath = s.originalBasePath;
@@ -554,7 +576,7 @@ export async function stopAuto(ctx, pi, reason) {
554
576
  catch (e) {
555
577
  debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
556
578
  }
557
- // ── Step 7: Ledger notification ──
579
+ // ── Step 8: Ledger notification ──
558
580
  try {
559
581
  const ledger = getLedger();
560
582
  if (ledger && ledger.units.length > 0) {
@@ -568,17 +590,6 @@ export async function stopAuto(ctx, pi, reason) {
568
590
  catch (e) {
569
591
  debugLog("stop-cleanup-ledger", { error: e instanceof Error ? e.message : String(e) });
570
592
  }
571
- // ── Step 8: Rebuild state ──
572
- if (s.basePath) {
573
- try {
574
- await rebuildState(s.basePath);
575
- }
576
- catch (e) {
577
- debugLog("stop-rebuild-state-failed", {
578
- error: e instanceof Error ? e.message : String(e),
579
- });
580
- }
581
- }
582
593
  // ── Step 9: Cmux sidebar / event log ──
583
594
  try {
584
595
  clearCmuxSidebar(loadedPreferences);
@@ -743,6 +754,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
743
754
  _resetPendingResolve();
744
755
  s.active = false;
745
756
  s.paused = true;
757
+ deactivateGSD();
746
758
  restoreProjectRootEnv();
747
759
  restoreMilestoneLockEnv();
748
760
  s.pendingVerificationRetry = null;
@@ -1290,8 +1302,6 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
1290
1302
  pi.sendMessage({ customType: "gsd-auto", content: hookPrompt, display: true }, { triggerTurn: true });
1291
1303
  return true;
1292
1304
  }
1293
- // Direct phase dispatch → auto-direct-dispatch.ts
1294
- export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
1295
1305
  // Re-export recovery functions for external consumers
1296
1306
  export { buildLoopRemediationSteps, } from "./auto-recovery.js";
1297
1307
  export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
@@ -919,12 +919,12 @@ export function registerDbTools(pi) {
919
919
  const saveGateResultTool = {
920
920
  name: "gsd_save_gate_result",
921
921
  label: "Save Gate Result",
922
- description: "Save the result of a quality gate evaluation (Q3-Q8) to the GSD database. " +
922
+ description: "Save the result of a quality gate evaluation (Q3-Q8 or MV01-MV04) to the GSD database. " +
923
923
  "Called by gate evaluation sub-agents after analyzing a specific quality question.",
924
924
  promptSnippet: "Save quality gate evaluation result (verdict, rationale, findings)",
925
925
  promptGuidelines: [
926
926
  "Use gsd_save_gate_result after evaluating a quality gate question.",
927
- "gateId must be one of: Q3, Q4, Q5, Q6, Q7, Q8.",
927
+ "gateId must be one of: Q3, Q4, Q5, Q6, Q7, Q8, MV01, MV02, MV03, MV04.",
928
928
  "verdict must be: pass (no concerns), flag (concerns found), or omitted (not applicable).",
929
929
  "rationale should be a one-sentence justification for the verdict.",
930
930
  "findings should contain detailed markdown analysis (or empty string if omitted).",
@@ -932,7 +932,7 @@ export function registerDbTools(pi) {
932
932
  parameters: Type.Object({
933
933
  milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
934
934
  sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
935
- gateId: Type.String({ description: "Gate ID: Q3, Q4, Q5, Q6, Q7, or Q8" }),
935
+ gateId: Type.String({ description: "Gate ID: Q3, Q4, Q5, Q6, Q7, Q8, MV01, MV02, MV03, or MV04" }),
936
936
  taskId: Type.Optional(Type.String({ description: "Task ID for task-scoped gates (Q5/Q6/Q7)" })),
937
937
  verdict: Type.String({ description: "pass, flag, or omitted" }),
938
938
  rationale: Type.String({ description: "One-sentence justification" }),
@@ -172,14 +172,10 @@ export function registerHooks(pi) {
172
172
  // Only gate-shaped ask_user_questions calls should block execution.
173
173
  // The gate stays pending until the user selects the approval option.
174
174
  if (event.toolName === "ask_user_questions") {
175
- const milestoneId = getDiscussionMilestoneId(discussionBasePath);
176
- const inDiscussion = milestoneId !== null || isQueuePhaseActive();
177
- if (inDiscussion) {
178
- const questions = event.input?.questions ?? [];
179
- const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
180
- if (typeof questionId === "string") {
181
- setPendingGate(questionId);
182
- }
175
+ const questions = event.input?.questions ?? [];
176
+ const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
177
+ if (typeof questionId === "string") {
178
+ setPendingGate(questionId);
183
179
  }
184
180
  }
185
181
  // ── Discussion gate enforcement: block tool calls while gate is pending ──
@@ -261,8 +257,6 @@ export function registerHooks(pi) {
261
257
  return;
262
258
  const milestoneId = getDiscussionMilestoneId(process.cwd());
263
259
  const queueActive = isQueuePhaseActive();
264
- if (!milestoneId && !queueActive)
265
- return;
266
260
  const details = event.details;
267
261
  // ── Discussion gate enforcement: handle gate question responses ──
268
262
  // If the result is cancelled or has no response, the pending gate stays active
@@ -293,12 +287,16 @@ export function registerHooks(pi) {
293
287
  // Only unlock the gate if the user selected the first option (confirmation).
294
288
  // Cross-references against the question's defined options to reject free-form "Other" text.
295
289
  const answer = details.response?.answers?.[question.id];
290
+ const inferredMilestoneId = extractDepthVerificationMilestoneId(question.id) ?? milestoneId;
296
291
  if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
297
- markDepthVerified(extractDepthVerificationMilestoneId(question.id) ?? milestoneId);
292
+ markDepthVerified(inferredMilestoneId);
293
+ clearPendingGate();
298
294
  }
299
295
  break;
300
296
  }
301
297
  }
298
+ if (!milestoneId && !queueActive)
299
+ return;
302
300
  if (!milestoneId)
303
301
  return;
304
302
  const basePath = process.cwd();
@@ -71,9 +71,6 @@ export function registerShortcuts(pi) {
71
71
  description: shortcutDesc(GSD_SHORTCUTS.parallel.action, GSD_SHORTCUTS.parallel.command),
72
72
  handler: openParallelOverlay,
73
73
  });
74
- // Fallback for terminals where Ctrl+Alt letter chords are not forwarded reliably.
75
- pi.registerShortcut(Key.ctrlShift(GSD_SHORTCUTS.parallel.key), {
76
- description: shortcutDesc(`${GSD_SHORTCUTS.parallel.action} (fallback)`, GSD_SHORTCUTS.parallel.command),
77
- handler: openParallelOverlay,
78
- });
74
+ // No Ctrl+Shift+P fallback conflicts with cycleModelBackward (shift+ctrl+p).
75
+ // Use Ctrl+Alt+P or /gsd parallel watch instead.
79
76
  }