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,7 +13,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
13
13
  import { debugCount, debugTime } from './debug-logger.js';
14
14
  import { logWarning, logError } from './workflow-logger.js';
15
15
  import { extractVerdict } from './verdict-parser.js';
16
- import { isDbAvailable, wasDbOpenAttempted, getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getReplanHistory, getSlice, insertMilestone, insertSlice, insertTask, updateTaskStatus, getPendingSliceGateCount, } from './gsd-db.js';
16
+ import { isDbAvailable, wasDbOpenAttempted, getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getReplanHistory, getSlice, insertMilestone, insertSlice, insertTask, updateSliceStatus, updateTaskStatus, getPendingGateCountForTurn, } from './gsd-db.js';
17
17
  /**
18
18
  * A "ghost" milestone directory contains only META.json (and no substantive
19
19
  * files like CONTEXT, CONTEXT-DRAFT, ROADMAP, or SUMMARY). These appear when
@@ -286,6 +286,26 @@ function reconcileDiskToDb(basePath) {
286
286
  depends: s.depends, demo: s.demo,
287
287
  });
288
288
  }
289
+ // Reconcile stale *existing* slice rows (#3599): a slice row may exist in
290
+ // the DB with status "pending" even though disk artifacts (SUMMARY) prove
291
+ // completion — the same class of desync that task-level reconciliation
292
+ // (further below) already handles. Without this, the dependency resolver
293
+ // builds doneSliceIds from stale DB rows and downstream slices stay blocked
294
+ // forever with "No slice eligible".
295
+ for (const dbSlice of dbSlices) {
296
+ if (isStatusDone(dbSlice.status))
297
+ continue;
298
+ const summaryPath = resolveSliceFile(basePath, mid, dbSlice.id, "SUMMARY");
299
+ if (summaryPath) {
300
+ try {
301
+ updateSliceStatus(mid, dbSlice.id, "complete");
302
+ logWarning("reconcile", `slice ${mid}/${dbSlice.id} status reconciled from "${dbSlice.status}" to "complete" (#3599)`, { mid, sid: dbSlice.id });
303
+ }
304
+ catch (e) {
305
+ logError("reconcile", `failed to update slice ${dbSlice.id}`, { sid: dbSlice.id, error: e.message });
306
+ }
307
+ }
308
+ }
289
309
  }
290
310
  return allMilestones;
291
311
  }
@@ -724,7 +744,14 @@ export async function deriveStateFromDb(basePath) {
724
744
  };
725
745
  }
726
746
  }
727
- const pendingGateCount = getPendingSliceGateCount(activeMilestone.id, activeSlice.id);
747
+ // ── Quality gate evaluation check ──────────────────────────────────
748
+ // Pause before execution only when gates owned by the `gate-evaluate`
749
+ // turn (Q3/Q4) are still pending. Q8 is also `scope:"slice"` but is
750
+ // owned by `complete-slice`, so it must NOT block the evaluating-gates
751
+ // phase — otherwise auto-loop stalls forever waiting for a gate that
752
+ // this turn never evaluates. See gate-registry.ts for the ownership map.
753
+ // Slices with zero gate rows (pre-feature or simple) skip straight through.
754
+ const pendingGateCount = getPendingGateCountForTurn(activeMilestone.id, activeSlice.id, "gate-evaluate");
728
755
  if (pendingGateCount > 0) {
729
756
  return {
730
757
  activeMilestone, activeSlice, activeTask: null,
@@ -9,7 +9,8 @@
9
9
  import { join } from "node:path";
10
10
  import { mkdirSync } from "node:fs";
11
11
  import { isClosedStatus } from "../status-guards.js";
12
- import { transaction, insertMilestone, insertSlice, getSlice, getSliceTasks, getMilestone, updateSliceStatus, setSliceSummaryMd, } from "../gsd-db.js";
12
+ import { transaction, insertMilestone, insertSlice, getSlice, getSliceTasks, getMilestone, updateSliceStatus, setSliceSummaryMd, saveGateResult, getPendingGatesForTurn, } from "../gsd-db.js";
13
+ import { getGatesForTurn } from "../gate-registry.js";
13
14
  import { resolveSlicePath, clearPathCache } from "../paths.js";
14
15
  import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
15
16
  import { saveFile, clearParseCache } from "../files.js";
@@ -19,6 +20,19 @@ import { renderAllProjections } from "../workflow-projections.js";
19
20
  import { writeManifest } from "../workflow-manifest.js";
20
21
  import { appendEvent } from "../workflow-events.js";
21
22
  import { logWarning, logError } from "../workflow-logger.js";
23
+ /**
24
+ * Map a complete-slice-owned gate id to the CompleteSliceParams field
25
+ * whose presence drives `pass` vs. `omitted`. Keep this in lockstep with
26
+ * the gates declared in gate-registry.ts under ownerTurn "complete-slice".
27
+ */
28
+ function sliceGateFieldForId(id, params) {
29
+ switch (id) {
30
+ case "Q8":
31
+ return params.operationalReadiness;
32
+ default:
33
+ return undefined;
34
+ }
35
+ }
22
36
  /**
23
37
  * Render slice summary markdown matching the template format.
24
38
  * YAML frontmatter uses snake_case keys for parseSummary() compatibility.
@@ -134,6 +148,10 @@ ${reqSurfaced}
134
148
 
135
149
  ${reqInvalidated}
136
150
 
151
+ ## Operational Readiness
152
+
153
+ ${params.operationalReadiness?.trim() || "None."}
154
+
137
155
  ## Deviations
138
156
 
139
157
  ${params.deviations || "None."}
@@ -271,6 +289,39 @@ export async function handleCompleteSlice(params, basePath) {
271
289
  }
272
290
  // Store rendered markdown in DB for D004 recovery
273
291
  setSliceSummaryMd(params.milestoneId, params.sliceId, summaryMd, uatMd);
292
+ // ── Close gates owned by complete-slice (Q8) ───────────────────────────
293
+ // Each owned gate maps to a specific summary section via the registry.
294
+ // If the caller populated the corresponding field, record `pass`; if the
295
+ // field is empty, record `omitted`. Without this loop, Q8 would stay
296
+ // pending forever and block future state derivation (see gate-registry).
297
+ try {
298
+ const pendingGates = getPendingGatesForTurn(params.milestoneId, params.sliceId, "complete-slice");
299
+ if (pendingGates.length > 0) {
300
+ const ownedDefs = new Map(getGatesForTurn("complete-slice").map((g) => [g.id, g]));
301
+ for (const row of pendingGates) {
302
+ const def = ownedDefs.get(row.gate_id);
303
+ if (!def)
304
+ continue;
305
+ // Map gate id → param field it maps to. Keep the map local so
306
+ // adding a new complete-slice gate is a single place change.
307
+ const field = sliceGateFieldForId(def.id, params);
308
+ const hasContent = typeof field === "string" && field.trim().length > 0;
309
+ saveGateResult({
310
+ milestoneId: params.milestoneId,
311
+ sliceId: params.sliceId,
312
+ gateId: def.id,
313
+ verdict: hasContent ? "pass" : "omitted",
314
+ rationale: hasContent
315
+ ? `${def.promptSection} section populated in slice summary`
316
+ : `${def.promptSection} section left empty — recorded as omitted`,
317
+ findings: hasContent ? field.trim() : "",
318
+ });
319
+ }
320
+ }
321
+ }
322
+ catch (gateErr) {
323
+ logWarning("tool", `complete-slice gate close warning for ${params.milestoneId}/${params.sliceId}: ${gateErr.message}`);
324
+ }
274
325
  // Invalidate all caches
275
326
  invalidateStateCache();
276
327
  clearPathCache();
@@ -9,7 +9,8 @@
9
9
  import { join } from "node:path";
10
10
  import { mkdirSync } from "node:fs";
11
11
  import { isClosedStatus } from "../status-guards.js";
12
- import { transaction, insertMilestone, insertSlice, insertTask, insertVerificationEvidence, getMilestone, getSlice, getTask, updateTaskStatus, setTaskSummaryMd, deleteVerificationEvidence, } from "../gsd-db.js";
12
+ import { transaction, insertMilestone, insertSlice, insertTask, insertVerificationEvidence, getMilestone, getSlice, getTask, updateTaskStatus, setTaskSummaryMd, deleteVerificationEvidence, saveGateResult, getPendingGatesForTurn, } from "../gsd-db.js";
13
+ import { getGatesForTurn } from "../gate-registry.js";
13
14
  import { resolveSliceFile, resolveTasksDir, clearPathCache } from "../paths.js";
14
15
  import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
15
16
  import { saveFile, clearParseCache } from "../files.js";
@@ -19,6 +20,23 @@ import { renderAllProjections, renderSummaryContent } from "../workflow-projecti
19
20
  import { writeManifest } from "../workflow-manifest.js";
20
21
  import { appendEvent } from "../workflow-events.js";
21
22
  import { logWarning, logError } from "../workflow-logger.js";
23
+ /**
24
+ * Map an execute-task-owned gate id to the CompleteTaskParams field whose
25
+ * presence drives `pass` vs. `omitted`. Keep in lockstep with the gates
26
+ * declared in gate-registry.ts under ownerTurn "execute-task".
27
+ */
28
+ function taskGateFieldForId(id, params) {
29
+ switch (id) {
30
+ case "Q5":
31
+ return params.failureModes;
32
+ case "Q6":
33
+ return params.loadProfile;
34
+ case "Q7":
35
+ return params.negativeTests;
36
+ default:
37
+ return undefined;
38
+ }
39
+ }
22
40
  /**
23
41
  * Normalize a list parameter that may arrive as a string (newline-delimited
24
42
  * bullet list from the LLM) into a string array (#3361).
@@ -189,6 +207,38 @@ export async function handleCompleteTask(params, basePath) {
189
207
  }
190
208
  // Store rendered markdown in DB for D004 recovery
191
209
  setTaskSummaryMd(params.milestoneId, params.sliceId, params.taskId, summaryMd);
210
+ // ── Close gates owned by execute-task (Q5/Q6/Q7) for this task ────────
211
+ // Each gate id maps to a specific params field via taskGateFieldForId.
212
+ // When the model populates the field, record `pass`; when it's empty,
213
+ // record `omitted`. Task-scoped rows are filtered by taskId so a single
214
+ // task's completion doesn't touch sibling tasks' gate rows.
215
+ try {
216
+ const pendingGates = getPendingGatesForTurn(params.milestoneId, params.sliceId, "execute-task", params.taskId);
217
+ if (pendingGates.length > 0) {
218
+ const ownedDefs = new Map(getGatesForTurn("execute-task").map((g) => [g.id, g]));
219
+ for (const row of pendingGates) {
220
+ const def = ownedDefs.get(row.gate_id);
221
+ if (!def)
222
+ continue;
223
+ const field = taskGateFieldForId(def.id, params);
224
+ const hasContent = typeof field === "string" && field.trim().length > 0;
225
+ saveGateResult({
226
+ milestoneId: params.milestoneId,
227
+ sliceId: params.sliceId,
228
+ taskId: params.taskId,
229
+ gateId: def.id,
230
+ verdict: hasContent ? "pass" : "omitted",
231
+ rationale: hasContent
232
+ ? `${def.promptSection} section populated in task summary`
233
+ : `${def.promptSection} section left empty — recorded as omitted`,
234
+ findings: hasContent ? field.trim() : "",
235
+ });
236
+ }
237
+ }
238
+ }
239
+ catch (gateErr) {
240
+ logWarning("tool", `complete-task gate close warning for ${params.milestoneId}/${params.sliceId}/${params.taskId}: ${gateErr.message}`);
241
+ }
192
242
  // Invalidate all caches
193
243
  invalidateStateCache();
194
244
  clearPathCache();
@@ -2,6 +2,7 @@ import { ensureDbOpen } from "../bootstrap/dynamic-tools.js";
2
2
  import { sanitizeCompleteMilestoneParams } from "../bootstrap/sanitize-complete-milestone.js";
3
3
  import { loadWriteGateSnapshot, shouldBlockContextArtifactSaveInSnapshot } from "../bootstrap/write-gate.js";
4
4
  import { getMilestone, getSliceStatusSummary, getSliceTaskCounts, _getAdapter, saveGateResult, } from "../gsd-db.js";
5
+ import { GATE_REGISTRY } from "../gate-registry.js";
5
6
  import { saveArtifactToDb } from "../db-writer.js";
6
7
  import { handleCompleteMilestone } from "./complete-milestone.js";
7
8
  import { handleCompleteTask } from "./complete-task.js";
@@ -323,7 +324,9 @@ export async function executeSaveGateResult(params, basePath = process.cwd()) {
323
324
  isError: true,
324
325
  };
325
326
  }
326
- const validGates = ["Q3", "Q4", "Q5", "Q6", "Q7", "Q8"];
327
+ // Source of truth: gate-registry.ts. Every declared GateId is accepted,
328
+ // so adding a new gate in one place automatically flows through here.
329
+ const validGates = Object.keys(GATE_REGISTRY);
327
330
  if (!validGates.includes(params.gateId)) {
328
331
  return {
329
332
  content: [{ type: "text", text: `Error: Invalid gateId "${params.gateId}". Must be one of: ${validGates.join(", ")}` }],
@@ -138,6 +138,13 @@ export function renderRoadmapProjection(basePath, milestoneId) {
138
138
  * regeneration, they are queried from the DB by renderSummaryProjection.
139
139
  */
140
140
  export function renderSummaryContent(taskRow, sliceId, milestoneId, evidence) {
141
+ // If the task already has a fully rendered summary (written by handleCompleteTask's
142
+ // renderSummaryMarkdown), use it as-is. That content already includes frontmatter,
143
+ // heading, and all sections. Re-wrapping it inside a second frontmatter/heading
144
+ // envelope produces double frontmatter and duplicate sections.
145
+ if (taskRow.full_summary_md && taskRow.full_summary_md.trimStart().startsWith("---")) {
146
+ return taskRow.full_summary_md;
147
+ }
141
148
  // ── Frontmatter (YAML list format, matches parseSummary() expectations) ──
142
149
  const keyFilesYaml = taskRow.key_files && taskRow.key_files.length > 0
143
150
  ? taskRow.key_files.map(f => ` - ${f}`).join("\n")
@@ -468,14 +468,41 @@ export function removeWorktree(basePath, name, opts = {}) {
468
468
  }
469
469
  }
470
470
  }
471
- /** Paths to skip in all worktree diffs (internal/runtime artifacts). */
472
- const SKIP_PATHS = [".gsd/worktrees/", ".gsd/runtime/", ".gsd/activity/"];
473
- const SKIP_EXACT = [".gsd/STATE.md", ".gsd/auto.lock", ".gsd/metrics.json"];
471
+ /**
472
+ * Paths to skip in all worktree diffs (internal/runtime artifacts).
473
+ *
474
+ * NOTE: These arrays must stay synchronized with GSD_RUNTIME_PATTERNS in gitignore.ts.
475
+ * That file is the canonical source of truth for runtime ignore patterns.
476
+ * This module uses a split representation (paths/exact/prefixes) for efficient matching.
477
+ */
478
+ const SKIP_PATHS = [
479
+ ".gsd/worktrees/",
480
+ ".gsd/runtime/",
481
+ ".gsd/activity/",
482
+ ".gsd/forensics/",
483
+ ".gsd/parallel/",
484
+ ".gsd/journal/",
485
+ ];
486
+ const SKIP_EXACT = [
487
+ ".gsd/STATE.md",
488
+ ".gsd/auto.lock",
489
+ ".gsd/metrics.json",
490
+ ".gsd/state-manifest.json",
491
+ ".gsd/doctor-history.jsonl",
492
+ ".gsd/event-log.jsonl",
493
+ ];
494
+ /** File prefixes to skip (for wildcard patterns like completed-units*.json, gsd.db*). */
495
+ const SKIP_PREFIXES = [
496
+ ".gsd/completed-units",
497
+ ".gsd/gsd.db",
498
+ ];
474
499
  function shouldSkipPath(filePath) {
475
500
  if (SKIP_PATHS.some(p => filePath.startsWith(p)))
476
501
  return true;
477
502
  if (SKIP_EXACT.includes(filePath))
478
503
  return true;
504
+ if (SKIP_PREFIXES.some(p => filePath.startsWith(p)))
505
+ return true;
479
506
  return false;
480
507
  }
481
508
  function parseDiffNameStatus(entries) {
@@ -22,6 +22,9 @@ const BLOCKED_PATTERNS = [
22
22
  /(^|[/\\])\.gsd[/\\]STATE\.md$/i,
23
23
  // Also match resolved symlink paths under ~/.gsd/projects/ (Pitfall #6)
24
24
  /(^|[/\\])\.gsd[/\\]projects[/\\][^/\\]+[/\\]STATE\.md$/i,
25
+ // gsd.db and WAL/SHM files — single-writer WAL connection managed by engine (#3625)
26
+ /(^|[/\\])\.gsd[/\\]gsd\.db(-wal|-shm)?$/i,
27
+ /(^|[/\\])\.gsd[/\\]projects[/\\][^/\\]+[/\\]gsd\.db(-wal|-shm)?$/i,
25
28
  ];
26
29
  /**
27
30
  * Bash command patterns that target STATE.md.
@@ -38,6 +41,12 @@ const BASH_STATE_PATTERNS = [
38
41
  /\bsed\b.*-i.*STATE\.md/i,
39
42
  // dd output to STATE.md
40
43
  /\bdd\b.*of=\S*STATE\.md/i,
44
+ // Direct DB access via sqlite3/sql.js/better-sqlite3 targeting gsd.db (#3625)
45
+ /\b(sqlite3|sql\.js|better-sqlite3|node:sqlite)\b.*gsd\.db/i,
46
+ /\bgsd\.db\b.*\b(sqlite3|sql\.js|better-sqlite3)\b/i,
47
+ // Shell writes targeting gsd.db files
48
+ /[>|]+\s*\S*gsd\.db/i,
49
+ /\b(cp|mv|dd)\b.*gsd\.db/i,
41
50
  ];
42
51
  /**
43
52
  * Tests whether the given file path matches a blocked authoritative .gsd/ state file.
@@ -75,7 +84,7 @@ function matchesBlockedPattern(path) {
75
84
  * Error message returned when an agent attempts to directly write an authoritative .gsd/ state file.
76
85
  * Directs the agent to use engine tool calls instead.
77
86
  */
78
- export const BLOCKED_WRITE_ERROR = `Direct writes to .gsd/STATE.md are blocked. Use engine tool calls instead:
87
+ export const BLOCKED_WRITE_ERROR = `Direct writes to .gsd/STATE.md and .gsd/gsd.db are blocked. Use engine tool calls instead:
79
88
  - To complete a task: call gsd_complete_task(milestone_id, slice_id, task_id, summary)
80
89
  - To complete a slice: call gsd_complete_slice(milestone_id, slice_id, summary, uat_result)
81
90
  - To save a decision: call gsd_save_decision(scope, decision, choice, rationale)
@@ -49,16 +49,22 @@ async function probeAndRegister(pi) {
49
49
  return false;
50
50
  }
51
51
  const models = await discoverModels();
52
- if (models.length === 0)
53
- return true; // Running but no models pulled
52
+ if (models.length === 0) {
53
+ // No local models means there's nothing usable to register in GSD.
54
+ // Keep the footer/status clean instead of advertising Ollama availability.
55
+ if (providerRegistered) {
56
+ pi.unregisterProvider("ollama");
57
+ providerRegistered = false;
58
+ }
59
+ return false;
60
+ }
54
61
  const baseUrl = client.getOllamaHost();
55
- // Use authMode "apiKey" with a dummy key (#3440).
56
- // authMode "none" requires a custom streamSimple handler, but Ollama uses
57
- // the standard OpenAI-compatible streaming endpoint. Ollama ignores the
58
- // Authorization header so the dummy key is harmless.
62
+ // Use authMode "apiKey" (#3440). Local Ollama ignores the Authorization header,
63
+ // so the "ollama" fallback is harmless. For cloud endpoints (OLLAMA_HOST pointing
64
+ // to ollama.com or a remote instance), OLLAMA_API_KEY is picked up here.
59
65
  pi.registerProvider("ollama", {
60
66
  authMode: "apiKey",
61
- apiKey: "ollama",
67
+ apiKey: process.env.OLLAMA_API_KEY ?? "ollama",
62
68
  baseUrl,
63
69
  api: "ollama-chat",
64
70
  streamSimple: streamOllamaChat,
@@ -102,10 +108,11 @@ export default function ollama(pi) {
102
108
  else {
103
109
  probeAndRegister(pi)
104
110
  .then((found) => {
105
- if (found)
106
- ctx.ui.setStatus("ollama", "Ollama");
111
+ ctx.ui.setStatus("ollama", found ? "Ollama" : undefined);
107
112
  })
108
- .catch(() => { });
113
+ .catch(() => {
114
+ ctx.ui.setStatus("ollama", undefined);
115
+ });
109
116
  }
110
117
  });
111
118
  pi.on("session_shutdown", async () => {
@@ -15,11 +15,34 @@ export function getOllamaHost() {
15
15
  return host;
16
16
  return `http://${host}`;
17
17
  }
18
+ /**
19
+ * Get auth headers for Ollama API requests.
20
+ * For cloud endpoints (OLLAMA_HOST pointing to ollama.com or remote instances),
21
+ * OLLAMA_API_KEY is used as a Bearer token. Local Ollama ignores the header.
22
+ */
23
+ function getAuthHeaders() {
24
+ const apiKey = process.env.OLLAMA_API_KEY;
25
+ if (!apiKey)
26
+ return {};
27
+ return { Authorization: `Bearer ${apiKey}` };
28
+ }
29
+ /**
30
+ * Merge auth headers into request options.
31
+ */
32
+ function withAuth(options = {}) {
33
+ const authHeaders = getAuthHeaders();
34
+ if (Object.keys(authHeaders).length === 0)
35
+ return options;
36
+ return {
37
+ ...options,
38
+ headers: { ...authHeaders, ...(options.headers || {}) },
39
+ };
40
+ }
18
41
  async function fetchWithTimeout(url, options = {}, timeoutMs = REQUEST_TIMEOUT_MS) {
19
42
  const controller = new AbortController();
20
43
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
21
44
  try {
22
- return await fetch(url, { ...options, signal: controller.signal });
45
+ return await fetch(url, withAuth({ ...options, signal: controller.signal }));
23
46
  }
24
47
  finally {
25
48
  clearTimeout(timeout);
@@ -27,10 +50,16 @@ async function fetchWithTimeout(url, options = {}, timeoutMs = REQUEST_TIMEOUT_M
27
50
  }
28
51
  /**
29
52
  * Check if Ollama is running and reachable.
53
+ * For cloud endpoints (OLLAMA_HOST pointing to ollama.com), uses /api/tags
54
+ * as the probe since the root endpoint may not be available.
30
55
  */
31
56
  export async function isRunning() {
32
57
  try {
33
- const response = await fetchWithTimeout(`${getOllamaHost()}/`, {}, PROBE_TIMEOUT_MS);
58
+ const host = getOllamaHost();
59
+ const isCloud = host.includes("ollama.com") || host.includes("cloud");
60
+ const probeUrl = isCloud ? `${host}/api/tags` : `${host}/`;
61
+ const timeout = isCloud ? REQUEST_TIMEOUT_MS : PROBE_TIMEOUT_MS;
62
+ const response = await fetchWithTimeout(probeUrl, isCloud ? { method: "GET" } : {}, timeout);
34
63
  return response.ok;
35
64
  }
36
65
  catch {
@@ -92,12 +121,12 @@ export async function getRunningModels() {
92
121
  * Returns when the pull is complete.
93
122
  */
94
123
  export async function pullModel(name, onProgress, signal) {
95
- const response = await fetch(`${getOllamaHost()}/api/pull`, {
124
+ const response = await fetch(`${getOllamaHost()}/api/pull`, withAuth({
96
125
  method: "POST",
97
126
  headers: { "Content-Type": "application/json" },
98
127
  body: JSON.stringify({ name, stream: true }),
99
128
  signal,
100
- });
129
+ }));
101
130
  if (!response.ok) {
102
131
  const text = await response.text();
103
132
  throw new Error(`Ollama /api/pull returned ${response.status}: ${text}`);
@@ -114,12 +143,12 @@ export async function pullModel(name, onProgress, signal) {
114
143
  * Returns an async generator yielding each NDJSON response chunk.
115
144
  */
116
145
  export async function* chat(request, signal) {
117
- const response = await fetch(`${getOllamaHost()}/api/chat`, {
146
+ const response = await fetch(`${getOllamaHost()}/api/chat`, withAuth({
118
147
  method: "POST",
119
148
  headers: { "Content-Type": "application/json" },
120
149
  body: JSON.stringify(request),
121
150
  signal,
122
- });
151
+ }));
123
152
  if (!response.ok) {
124
153
  const text = await response.text();
125
154
  throw new Error(`Ollama /api/chat returned ${response.status}: ${text}`);
@@ -6,14 +6,40 @@
6
6
  *
7
7
  * Returns models in the format expected by pi.registerProvider().
8
8
  */
9
- import { listModels } from "./ollama-client.js";
9
+ import { listModels, showModel } from "./ollama-client.js";
10
10
  import { estimateContextFromParams, formatModelSize, getModelCapabilities, humanizeModelName, } from "./model-capabilities.js";
11
+ /**
12
+ * Extract context window from /api/show model_info.
13
+ * Keys follow the pattern "{architecture}.context_length" (e.g. "llama.context_length").
14
+ */
15
+ function extractContextFromModelInfo(modelInfo) {
16
+ for (const [key, value] of Object.entries(modelInfo)) {
17
+ if (key.endsWith(".context_length") && typeof value === "number" && value > 0) {
18
+ return value;
19
+ }
20
+ }
21
+ return undefined;
22
+ }
11
23
  const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
12
- function enrichModel(info) {
24
+ async function enrichModel(info, deps) {
13
25
  const caps = getModelCapabilities(info.name);
14
26
  const parameterSize = info.details?.parameter_size ?? "";
15
- // Determine context window: known table > estimate from param size > default
27
+ // /api/tags doesn't include context length; /api/show does via "{arch}.context_length" in model_info.
28
+ let showContextWindow;
29
+ if (caps.contextWindow === undefined) {
30
+ try {
31
+ const showData = await deps.showModel(info.name);
32
+ showContextWindow = extractContextFromModelInfo(showData.model_info);
33
+ }
34
+ catch (err) {
35
+ // non-fatal: fall through to estimate
36
+ if (process.env.GSD_DEBUG)
37
+ console.warn(`[ollama] /api/show failed for ${info.name}:`, err instanceof Error ? err.message : String(err));
38
+ }
39
+ }
40
+ // Determine context window: known table > /api/show > estimate from param size > default
16
41
  const contextWindow = caps.contextWindow ??
42
+ showContextWindow ??
17
43
  (parameterSize ? estimateContextFromParams(parameterSize) : 8192);
18
44
  // Determine max tokens: known table > fraction of context > default
19
45
  const maxTokens = caps.maxTokens ?? Math.min(Math.floor(contextWindow / 4), 16384);
@@ -38,11 +64,11 @@ function enrichModel(info) {
38
64
  /**
39
65
  * Discover all locally available Ollama models with enriched capabilities.
40
66
  */
41
- export async function discoverModels() {
42
- const tags = await listModels();
67
+ export async function discoverModels(deps = { listModels, showModel }) {
68
+ const tags = await deps.listModels();
43
69
  if (!tags.models || tags.models.length === 0)
44
70
  return [];
45
- return tags.models.map(enrichModel);
71
+ return Promise.all(tags.models.map((m) => enrichModel(m, deps)));
46
72
  }
47
73
  /**
48
74
  * Format a discovered model for display in model list.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * GSD Phase State — cross-extension coordination
3
+ * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
4
+ *
5
+ * Lightweight module-level state that GSD auto-mode writes to and the
6
+ * subagent tool reads from. Both extensions run in the same process so
7
+ * a module variable is sufficient — no file I/O needed.
8
+ */
9
+ let _active = false;
10
+ let _currentPhase = null;
11
+ /** Mark GSD auto-mode as active. */
12
+ export function activateGSD() {
13
+ _active = true;
14
+ }
15
+ /** Mark GSD auto-mode as inactive and clear the current phase. */
16
+ export function deactivateGSD() {
17
+ _active = false;
18
+ _currentPhase = null;
19
+ }
20
+ /** Set the currently dispatched GSD phase (e.g. "plan-milestone"). */
21
+ export function setCurrentPhase(phase) {
22
+ _currentPhase = phase;
23
+ }
24
+ /** Clear the current phase (unit completed or aborted). */
25
+ export function clearCurrentPhase() {
26
+ _currentPhase = null;
27
+ }
28
+ /** Returns true if GSD auto-mode is currently active. */
29
+ export function isGSDActive() {
30
+ return _active;
31
+ }
32
+ /** Returns the current GSD phase, or null if none is active. */
33
+ export function getCurrentPhase() {
34
+ return _active ? _currentPhase : null;
35
+ }
@@ -5,6 +5,12 @@ import * as fs from "node:fs";
5
5
  import * as path from "node:path";
6
6
  import { getAgentDir, parseFrontmatter } from "@gsd/pi-coding-agent";
7
7
  const PROJECT_AGENT_DIR_CANDIDATES = [".gsd", ".pi"];
8
+ export function parseConflictsWith(value) {
9
+ if (typeof value !== "string")
10
+ return undefined;
11
+ const conflicts = value.split(",").map((s) => s.trim()).filter(Boolean);
12
+ return conflicts.length > 0 ? conflicts : undefined;
13
+ }
8
14
  function parseAgentTools(value) {
9
15
  if (typeof value === "string") {
10
16
  const tools = value
@@ -52,11 +58,13 @@ function loadAgentsFromDir(dir, source) {
52
58
  continue;
53
59
  }
54
60
  const tools = parseAgentTools(frontmatter.tools);
61
+ const conflictsWith = parseConflictsWith(frontmatter.conflicts_with);
55
62
  agents.push({
56
63
  name: frontmatter.name,
57
64
  description: frontmatter.description,
58
65
  tools: tools && tools.length > 0 ? tools : undefined,
59
66
  model: frontmatter.model,
67
+ conflictsWith,
60
68
  systemPrompt: body,
61
69
  source,
62
70
  filePath,
@@ -21,6 +21,7 @@ import { getMarkdownTheme } from "@gsd/pi-coding-agent";
21
21
  import { Container, Markdown, Spacer, Text } from "@gsd/pi-tui";
22
22
  import { Type } from "@sinclair/typebox";
23
23
  import { formatTokenCount } from "../shared/mod.js";
24
+ import { getCurrentPhase } from "../shared/gsd-phase-state.js";
24
25
  import { discoverAgents } from "./agents.js";
25
26
  import { createIsolation, mergeDeltaPatches, readIsolationMode, } from "./isolation.js";
26
27
  import { registerWorker, updateWorker } from "./worker-registry.js";
@@ -268,6 +269,22 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
268
269
  step,
269
270
  };
270
271
  }
272
+ // GSD phase guard: block agents that conflict with the active GSD phase
273
+ if (agent.conflictsWith && agent.conflictsWith.length > 0) {
274
+ const activePhase = getCurrentPhase();
275
+ if (activePhase && agent.conflictsWith.includes(activePhase)) {
276
+ return {
277
+ agent: agentName,
278
+ agentSource: agent.source,
279
+ task,
280
+ exitCode: 1,
281
+ messages: [],
282
+ stderr: `Agent "${agentName}" is blocked: it conflicts with the active GSD phase "${activePhase}". Use the built-in GSD workflow instead.`,
283
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
284
+ step,
285
+ };
286
+ }
287
+ }
271
288
  let tmpPromptDir = null;
272
289
  let tmpPromptPath = null;
273
290
  const currentResult = {
@@ -13,7 +13,6 @@ interface MinimalModel {
13
13
  id: string;
14
14
  }
15
15
  interface MinimalModelRegistry {
16
- getAll(): MinimalModel[];
17
16
  getAvailable(): MinimalModel[];
18
17
  }
19
18
  type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
@@ -23,10 +23,14 @@ import { getPiDefaultModelAndProvider } from './pi-migration.js';
23
23
  export function validateConfiguredModel(modelRegistry, settingsManager) {
24
24
  const configuredProvider = settingsManager.getDefaultProvider();
25
25
  const configuredModel = settingsManager.getDefaultModel();
26
- const allModels = modelRegistry.getAll();
27
26
  const availableModels = modelRegistry.getAvailable();
27
+ // Check against availableModels (configured + auth'd) rather than getAll()
28
+ // so a stale default pointing at an unconfigured provider triggers the
29
+ // fallback. Previously a model present in the registry but missing API
30
+ // key / OAuth would satisfy configuredExists and survive startup, ending
31
+ // up as ctx.model even though it couldn't actually be used.
28
32
  const configuredExists = configuredProvider && configuredModel &&
29
- allModels.some((m) => m.provider === configuredProvider && m.id === configuredModel);
33
+ availableModels.some((m) => m.provider === configuredProvider && m.id === configuredModel);
30
34
  if (!configuredModel || !configuredExists) {
31
35
  // Model not configured at all, or removed from registry — pick a fallback.
32
36
  // Only fires when the model is genuinely unknown (not just temporarily unavailable).
@@ -1 +1 @@
1
- cYPZv_bAhZk2ms-Pz6vsY
1
+ NzO79SOz9jHX-VY5-0t2O