gsd-pi 2.71.0-dev.06b86c6 → 2.71.0-dev.4c35d99

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 (276) hide show
  1. package/README.md +34 -1
  2. package/dist/cli.js +17 -0
  3. package/dist/headless-events.d.ts +2 -0
  4. package/dist/headless-events.js +7 -0
  5. package/dist/headless.js +16 -3
  6. package/dist/mcp-server.js +37 -14
  7. package/dist/resource-loader.js +6 -3
  8. package/dist/resources/agents/debugger.md +58 -0
  9. package/dist/resources/agents/doc-writer.md +43 -0
  10. package/dist/resources/agents/git-ops.md +56 -0
  11. package/dist/resources/agents/javascript-pro.md +46 -271
  12. package/dist/resources/agents/planner.md +55 -0
  13. package/dist/resources/agents/refactorer.md +47 -0
  14. package/dist/resources/agents/reviewer.md +48 -0
  15. package/dist/resources/agents/security.md +59 -0
  16. package/dist/resources/agents/tester.md +50 -0
  17. package/dist/resources/agents/typescript-pro.md +41 -235
  18. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +113 -10
  19. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  20. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  21. package/dist/resources/extensions/gsd/auto/phases.js +5 -1
  22. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  23. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  24. package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
  25. package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
  26. package/dist/resources/extensions/gsd/auto-start.js +33 -6
  27. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  28. package/dist/resources/extensions/gsd/auto.js +56 -0
  29. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  30. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +63 -51
  31. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  32. package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
  33. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  34. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  35. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  36. package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
  37. package/dist/resources/extensions/gsd/forensics.js +19 -6
  38. package/dist/resources/extensions/gsd/gate-registry.js +208 -0
  39. package/dist/resources/extensions/gsd/gsd-db.js +41 -0
  40. package/dist/resources/extensions/gsd/guided-flow.js +5 -10
  41. package/dist/resources/extensions/gsd/metrics.js +1 -0
  42. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  43. package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
  44. package/dist/resources/extensions/gsd/notification-overlay.js +42 -13
  45. package/dist/resources/extensions/gsd/notification-store.js +35 -4
  46. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  47. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  48. package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
  49. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  50. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
  51. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  52. package/dist/resources/extensions/gsd/session-model-override.js +25 -0
  53. package/dist/resources/extensions/gsd/shortcut-defs.js +40 -0
  54. package/dist/resources/extensions/gsd/state.js +9 -2
  55. package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
  56. package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
  57. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
  58. package/dist/resources/extensions/ollama/index.js +13 -5
  59. package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
  60. package/dist/resources/extensions/subagent/agents.js +8 -0
  61. package/dist/resources/extensions/subagent/index.js +17 -0
  62. package/dist/startup-model-validation.d.ts +0 -1
  63. package/dist/startup-model-validation.js +6 -2
  64. package/dist/web/standalone/.next/BUILD_ID +1 -1
  65. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  66. package/dist/web/standalone/.next/build-manifest.json +2 -2
  67. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.html +1 -1
  85. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  92. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  94. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  95. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  96. package/package.json +1 -1
  97. package/packages/mcp-server/dist/server.d.ts +12 -1
  98. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  99. package/packages/mcp-server/dist/server.js +90 -42
  100. package/packages/mcp-server/dist/server.js.map +1 -1
  101. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  102. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  103. package/packages/mcp-server/src/server.ts +110 -38
  104. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  105. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  106. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  107. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  108. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  109. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
  110. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  111. package/packages/pi-ai/dist/providers/anthropic.js +7 -4
  112. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  113. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  114. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  115. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  116. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  117. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  118. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  119. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  121. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  123. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  125. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  127. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
  129. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
  130. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
  131. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
  132. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
  134. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  136. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  137. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  138. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  139. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  140. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  142. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  144. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  145. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  146. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  147. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  148. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/index.js +1 -1
  150. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  157. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  161. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  163. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
  165. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
  168. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  169. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  170. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  171. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  172. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
  174. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  176. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  177. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  178. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  179. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  180. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  181. package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
  182. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  183. package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
  184. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  185. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  186. package/packages/pi-coding-agent/src/index.ts +1 -0
  187. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
  188. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
  189. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  190. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
  191. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
  192. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  193. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
  194. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  195. package/src/resources/agents/debugger.md +58 -0
  196. package/src/resources/agents/doc-writer.md +43 -0
  197. package/src/resources/agents/git-ops.md +56 -0
  198. package/src/resources/agents/javascript-pro.md +46 -271
  199. package/src/resources/agents/planner.md +55 -0
  200. package/src/resources/agents/refactorer.md +47 -0
  201. package/src/resources/agents/reviewer.md +48 -0
  202. package/src/resources/agents/security.md +59 -0
  203. package/src/resources/agents/tester.md +50 -0
  204. package/src/resources/agents/typescript-pro.md +41 -235
  205. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +122 -8
  206. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +189 -6
  207. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  208. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  209. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  210. package/src/resources/extensions/gsd/auto/phases.ts +6 -0
  211. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  212. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  213. package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
  214. package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
  215. package/src/resources/extensions/gsd/auto-start.ts +40 -6
  216. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  217. package/src/resources/extensions/gsd/auto.ts +72 -0
  218. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  219. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +79 -60
  220. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  221. package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
  222. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  223. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  224. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  225. package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
  226. package/src/resources/extensions/gsd/forensics.ts +23 -7
  227. package/src/resources/extensions/gsd/gate-registry.ts +251 -0
  228. package/src/resources/extensions/gsd/gsd-db.ts +51 -0
  229. package/src/resources/extensions/gsd/guided-flow.ts +5 -10
  230. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  231. package/src/resources/extensions/gsd/metrics.ts +12 -1
  232. package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
  233. package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
  234. package/src/resources/extensions/gsd/notification-overlay.ts +47 -14
  235. package/src/resources/extensions/gsd/notification-store.ts +35 -4
  236. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  237. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  238. package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
  239. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  240. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
  241. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  242. package/src/resources/extensions/gsd/session-model-override.ts +36 -0
  243. package/src/resources/extensions/gsd/shortcut-defs.ts +56 -0
  244. package/src/resources/extensions/gsd/state.ts +13 -2
  245. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +25 -9
  246. package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
  247. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
  248. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  249. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +31 -0
  250. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
  251. package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
  252. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  253. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
  254. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
  255. package/src/resources/extensions/gsd/tests/notification-store.test.ts +18 -0
  256. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +3 -2
  257. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  258. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  259. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  260. package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
  261. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +63 -5
  262. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
  263. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  264. package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
  265. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
  266. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
  267. package/src/resources/extensions/gsd/types.ts +26 -0
  268. package/src/resources/extensions/ollama/index.ts +13 -3
  269. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  270. package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
  271. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
  272. package/src/resources/extensions/subagent/agents.ts +10 -0
  273. package/src/resources/extensions/subagent/index.ts +18 -0
  274. package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
  275. /package/dist/web/standalone/.next/static/{dYVdRaunb2ZSEA8fjkT-V → OI4n_CKC-lM8IQbvGJ_tK}/_buildManifest.js +0 -0
  276. /package/dist/web/standalone/.next/static/{dYVdRaunb2ZSEA8fjkT-V → OI4n_CKC-lM8IQbvGJ_tK}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { mkdirSync, rmSync } from "node:fs";
3
+ import { mkdirSync, realpathSync, rmSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
@@ -37,10 +37,10 @@ test("dashboard shortcut resolves the project root instead of the current worktr
37
37
  });
38
38
 
39
39
  let capturedHandler: ((ctx: any) => Promise<void>) | null = null;
40
- const shortcuts: Array<{ description: string; handler: (ctx: any) => Promise<void> }> = [];
40
+ const shortcuts: Array<{ key: string; description: string; handler: (ctx: any) => Promise<void> }> = [];
41
41
  const pi = {
42
- registerShortcut: (_key: unknown, shortcut: { description: string; handler: (ctx: any) => Promise<void> }) => {
43
- shortcuts.push(shortcut);
42
+ registerShortcut: (key: unknown, shortcut: { description: string; handler: (ctx: any) => Promise<void> }) => {
43
+ shortcuts.push({ key: String(key), ...shortcut });
44
44
  if (!capturedHandler) {
45
45
  capturedHandler = shortcut.handler;
46
46
  }
@@ -69,5 +69,63 @@ test("dashboard shortcut resolves the project root instead of the current worktr
69
69
 
70
70
  assert.ok(customCalls > 0, "shortcut opens the dashboard overlay when project root is resolved");
71
71
  assert.equal(notices.length, 0, "shortcut does not fall back to the missing-.gsd warning");
72
- assert.equal(shortcuts.length, 3, "all GSD shortcuts are still registered");
72
+ assert.equal(shortcuts.length, 5, "all GSD shortcuts are still registered");
73
+ const keys = shortcuts.map((shortcut) => shortcut.key);
74
+ assert.ok(keys.includes("ctrl+alt+g"), "primary dashboard shortcut is registered");
75
+ assert.ok(keys.includes("ctrl+shift+g"), "fallback dashboard shortcut is registered");
76
+ assert.ok(keys.includes("ctrl+alt+n"), "primary notifications shortcut is registered");
77
+ assert.ok(keys.includes("ctrl+shift+n"), "fallback notifications shortcut is registered");
78
+ assert.ok(keys.includes("ctrl+alt+p"), "primary parallel shortcut is registered");
79
+ // No Ctrl+Shift+P fallback — conflicts with cycleModelBackward (shift+ctrl+p)
80
+ assert.ok(!keys.includes("ctrl+shift+p"), "parallel fallback must not be registered (conflicts with cycleModelBackward)");
81
+ });
82
+
83
+ test("parallel shortcut passes resolved project root into overlay", async (t) => {
84
+ const base = makeTempDir("parallel-root");
85
+ const worktreeRoot = join(base, ".gsd", "worktrees", "M001");
86
+ mkdirSync(join(base, ".gsd", "parallel"), { recursive: true });
87
+ mkdirSync(worktreeRoot, { recursive: true });
88
+
89
+ const originalCwd = process.cwd();
90
+ process.chdir(worktreeRoot);
91
+ t.after(() => {
92
+ process.chdir(originalCwd);
93
+ cleanup(base);
94
+ });
95
+
96
+ const shortcuts: Array<{ key: string; description: string; handler: (ctx: any) => Promise<void> }> = [];
97
+ registerShortcuts({
98
+ registerShortcut: (key: unknown, shortcut: { description: string; handler: (ctx: any) => Promise<void> }) => {
99
+ shortcuts.push({ key: String(key), ...shortcut });
100
+ },
101
+ } as any);
102
+
103
+ const parallelShortcut = shortcuts.find((shortcut) => shortcut.key === "ctrl+alt+p");
104
+ assert.ok(parallelShortcut, "parallel shortcut is registered");
105
+
106
+ let capturedBasePath: string | undefined;
107
+ await parallelShortcut!.handler({
108
+ hasUI: true,
109
+ ui: {
110
+ custom: async (factory: any) => {
111
+ const overlay = factory(
112
+ { requestRender() {} },
113
+ { fg: (_color: string, text: string) => text, bold: (text: string) => text },
114
+ null,
115
+ () => {},
116
+ );
117
+ capturedBasePath = (overlay as any).basePath;
118
+ overlay.dispose?.();
119
+ return true;
120
+ },
121
+ notify: () => {},
122
+ },
123
+ });
124
+
125
+ assert.ok(capturedBasePath, "parallel shortcut should construct overlay with a basePath");
126
+ assert.equal(
127
+ realpathSync(capturedBasePath),
128
+ realpathSync(base),
129
+ "parallel overlay should use the resolved project root, not the worktree cwd",
130
+ );
73
131
  });
@@ -0,0 +1,35 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+
6
+ import {
7
+ clearSessionModelOverride,
8
+ getSessionModelOverride,
9
+ setSessionModelOverride,
10
+ } from "../session-model-override.js";
11
+
12
+ const phasesSource = readFileSync(join(import.meta.dirname, "..", "auto", "phases.ts"), "utf-8");
13
+
14
+ test("setSessionModelOverride stores provider/model for the session", () => {
15
+ const sessionId = `session-override-${Date.now()}`;
16
+ setSessionModelOverride(sessionId, { provider: "openai-codex", id: "gpt-5.4" });
17
+
18
+ const override = getSessionModelOverride(sessionId);
19
+ assert.equal(override?.provider, "openai-codex");
20
+ assert.equal(override?.id, "gpt-5.4");
21
+ });
22
+
23
+ test("clearSessionModelOverride removes the session override", () => {
24
+ const sessionId = `session-clear-${Date.now()}`;
25
+ setSessionModelOverride(sessionId, { provider: "anthropic", id: "claude-sonnet-4-6" });
26
+ clearSessionModelOverride(sessionId);
27
+ assert.equal(getSessionModelOverride(sessionId), undefined);
28
+ });
29
+
30
+ test("auto dispatch threads manual session model override into selectAndApplyModel", () => {
31
+ assert.ok(
32
+ phasesSource.includes("s.manualSessionModelOverride"),
33
+ "auto/phases.ts should pass s.manualSessionModelOverride into selectAndApplyModel",
34
+ );
35
+ });
@@ -0,0 +1,90 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+
6
+ const gsdDir = resolve(import.meta.dirname, "..");
7
+
8
+ function readGsdFile(relativePath: string): string {
9
+ return readFileSync(resolve(gsdDir, relativePath), "utf-8");
10
+ }
11
+
12
+ test("command entrypoints use startAutoDetached instead of awaiting startAuto (#3733)", () => {
13
+ const autoHandlerSrc = readGsdFile("commands/handlers/auto.ts");
14
+ const workflowHandlerSrc = readGsdFile("commands/handlers/workflow.ts");
15
+ const guidedFlowSrc = readGsdFile("guided-flow.ts");
16
+
17
+ assert.ok(
18
+ !autoHandlerSrc.includes("await startAuto("),
19
+ "auto command handler should not await startAuto from the active agent turn",
20
+ );
21
+ assert.ok(
22
+ !workflowHandlerSrc.includes("await startAuto("),
23
+ "workflow command handler should not await startAuto from the active agent turn",
24
+ );
25
+ assert.ok(
26
+ !guidedFlowSrc.includes("await startAuto("),
27
+ "guided flow should not await startAuto from the active agent turn",
28
+ );
29
+
30
+ assert.ok(
31
+ autoHandlerSrc.includes("startAutoDetached("),
32
+ "auto command handler should launch auto-mode through startAutoDetached",
33
+ );
34
+ assert.ok(
35
+ workflowHandlerSrc.includes("startAutoDetached("),
36
+ "workflow handler should launch auto-mode through startAutoDetached",
37
+ );
38
+ assert.ok(
39
+ guidedFlowSrc.includes("startAutoDetached("),
40
+ "guided flow should launch auto-mode through startAutoDetached",
41
+ );
42
+ });
43
+
44
+ test("startAutoDetached reports failures asynchronously (#3733)", () => {
45
+ const autoSrc = readGsdFile("auto.ts");
46
+
47
+ assert.ok(
48
+ autoSrc.includes("export function startAutoDetached"),
49
+ "auto.ts should export startAutoDetached",
50
+ );
51
+ assert.ok(
52
+ autoSrc.includes("void startAuto(ctx, pi, base, verboseMode, options).catch"),
53
+ "startAutoDetached should launch startAuto without awaiting it",
54
+ );
55
+ assert.ok(
56
+ autoSrc.includes("ctx.ui.notify(`Auto-start failed: ${message}`, \"error\")"),
57
+ "startAutoDetached should surface async startup failures to the user",
58
+ );
59
+ });
60
+
61
+ test("detached auto-start preserves milestone lock across pause/stop cleanup (#3733)", () => {
62
+ const autoSrc = readGsdFile("auto.ts");
63
+ const sessionSrc = readGsdFile("auto/session.ts");
64
+
65
+ assert.ok(
66
+ autoSrc.includes("milestoneLock?: string | null"),
67
+ "startAuto/startAutoDetached options should carry an explicit milestone lock",
68
+ );
69
+ assert.ok(
70
+ autoSrc.includes("s.sessionMilestoneLock = options.milestoneLock ?? null;"),
71
+ "startAuto should capture the requested milestone lock before async work begins",
72
+ );
73
+ assert.ok(
74
+ autoSrc.includes("milestoneLock: s.sessionMilestoneLock ?? undefined"),
75
+ "pause metadata should persist the detached milestone lock for resume",
76
+ );
77
+ assert.ok(
78
+ autoSrc.includes("s.sessionMilestoneLock = meta.milestoneLock ?? null;"),
79
+ "resume should restore the persisted milestone lock",
80
+ );
81
+ assert.ok(
82
+ autoSrc.includes("restoreMilestoneLockEnv();"),
83
+ "auto cleanup should restore the previous process milestone-lock env",
84
+ );
85
+
86
+ assert.ok(
87
+ sessionSrc.includes("sessionMilestoneLock: string | null = null;"),
88
+ "AutoSession should track the detached milestone lock explicitly",
89
+ );
90
+ });
@@ -21,7 +21,10 @@ import {
21
21
  getMilestone,
22
22
  updateSliceStatus,
23
23
  setSliceSummaryMd,
24
+ saveGateResult,
25
+ getPendingGatesForTurn,
24
26
  } from "../gsd-db.js";
27
+ import { getGatesForTurn } from "../gate-registry.js";
25
28
  import { resolveSliceFile, resolveSlicePath, clearPathCache } from "../paths.js";
26
29
  import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
27
30
  import { saveFile, clearParseCache } from "../files.js";
@@ -39,6 +42,23 @@ export interface CompleteSliceResult {
39
42
  uatPath: string;
40
43
  }
41
44
 
45
+ /**
46
+ * Map a complete-slice-owned gate id to the CompleteSliceParams field
47
+ * whose presence drives `pass` vs. `omitted`. Keep this in lockstep with
48
+ * the gates declared in gate-registry.ts under ownerTurn "complete-slice".
49
+ */
50
+ function sliceGateFieldForId(
51
+ id: string,
52
+ params: CompleteSliceParams,
53
+ ): string | undefined {
54
+ switch (id) {
55
+ case "Q8":
56
+ return params.operationalReadiness;
57
+ default:
58
+ return undefined;
59
+ }
60
+ }
61
+
42
62
  /**
43
63
  * Render slice summary markdown matching the template format.
44
64
  * YAML frontmatter uses snake_case keys for parseSummary() compatibility.
@@ -169,6 +189,10 @@ ${reqSurfaced}
169
189
 
170
190
  ${reqInvalidated}
171
191
 
192
+ ## Operational Readiness
193
+
194
+ ${params.operationalReadiness?.trim() || "None."}
195
+
172
196
  ## Deviations
173
197
 
174
198
  ${params.deviations || "None."}
@@ -330,6 +354,45 @@ export async function handleCompleteSlice(
330
354
  // Store rendered markdown in DB for D004 recovery
331
355
  setSliceSummaryMd(params.milestoneId, params.sliceId, summaryMd, uatMd);
332
356
 
357
+ // ── Close gates owned by complete-slice (Q8) ───────────────────────────
358
+ // Each owned gate maps to a specific summary section via the registry.
359
+ // If the caller populated the corresponding field, record `pass`; if the
360
+ // field is empty, record `omitted`. Without this loop, Q8 would stay
361
+ // pending forever and block future state derivation (see gate-registry).
362
+ try {
363
+ const pendingGates = getPendingGatesForTurn(
364
+ params.milestoneId,
365
+ params.sliceId,
366
+ "complete-slice",
367
+ );
368
+ if (pendingGates.length > 0) {
369
+ const ownedDefs = new Map(getGatesForTurn("complete-slice").map((g) => [g.id, g] as const));
370
+ for (const row of pendingGates) {
371
+ const def = ownedDefs.get(row.gate_id);
372
+ if (!def) continue;
373
+ // Map gate id → param field it maps to. Keep the map local so
374
+ // adding a new complete-slice gate is a single place change.
375
+ const field = sliceGateFieldForId(def.id, params);
376
+ const hasContent = typeof field === "string" && field.trim().length > 0;
377
+ saveGateResult({
378
+ milestoneId: params.milestoneId,
379
+ sliceId: params.sliceId,
380
+ gateId: def.id,
381
+ verdict: hasContent ? "pass" : "omitted",
382
+ rationale: hasContent
383
+ ? `${def.promptSection} section populated in slice summary`
384
+ : `${def.promptSection} section left empty — recorded as omitted`,
385
+ findings: hasContent ? (field as string).trim() : "",
386
+ });
387
+ }
388
+ }
389
+ } catch (gateErr) {
390
+ logWarning(
391
+ "tool",
392
+ `complete-slice gate close warning for ${params.milestoneId}/${params.sliceId}: ${(gateErr as Error).message}`,
393
+ );
394
+ }
395
+
333
396
  // Invalidate all caches
334
397
  invalidateStateCache();
335
398
  clearPathCache();
@@ -24,7 +24,10 @@ import {
24
24
  updateTaskStatus,
25
25
  setTaskSummaryMd,
26
26
  deleteVerificationEvidence,
27
+ saveGateResult,
28
+ getPendingGatesForTurn,
27
29
  } from "../gsd-db.js";
30
+ import { getGatesForTurn } from "../gate-registry.js";
28
31
  import { resolveSliceFile, resolveTasksDir, clearPathCache } from "../paths.js";
29
32
  import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
30
33
  import { saveFile, clearParseCache } from "../files.js";
@@ -44,6 +47,27 @@ export interface CompleteTaskResult {
44
47
 
45
48
  import type { TaskRow } from "../gsd-db.js";
46
49
 
50
+ /**
51
+ * Map an execute-task-owned gate id to the CompleteTaskParams field whose
52
+ * presence drives `pass` vs. `omitted`. Keep in lockstep with the gates
53
+ * declared in gate-registry.ts under ownerTurn "execute-task".
54
+ */
55
+ function taskGateFieldForId(
56
+ id: string,
57
+ params: CompleteTaskParams,
58
+ ): string | undefined {
59
+ switch (id) {
60
+ case "Q5":
61
+ return params.failureModes;
62
+ case "Q6":
63
+ return params.loadProfile;
64
+ case "Q7":
65
+ return params.negativeTests;
66
+ default:
67
+ return undefined;
68
+ }
69
+ }
70
+
47
71
  /**
48
72
  * Normalize a list parameter that may arrive as a string (newline-delimited
49
73
  * bullet list from the LLM) into a string array (#3361).
@@ -236,6 +260,45 @@ export async function handleCompleteTask(
236
260
  // Store rendered markdown in DB for D004 recovery
237
261
  setTaskSummaryMd(params.milestoneId, params.sliceId, params.taskId, summaryMd);
238
262
 
263
+ // ── Close gates owned by execute-task (Q5/Q6/Q7) for this task ────────
264
+ // Each gate id maps to a specific params field via taskGateFieldForId.
265
+ // When the model populates the field, record `pass`; when it's empty,
266
+ // record `omitted`. Task-scoped rows are filtered by taskId so a single
267
+ // task's completion doesn't touch sibling tasks' gate rows.
268
+ try {
269
+ const pendingGates = getPendingGatesForTurn(
270
+ params.milestoneId,
271
+ params.sliceId,
272
+ "execute-task",
273
+ params.taskId,
274
+ );
275
+ if (pendingGates.length > 0) {
276
+ const ownedDefs = new Map(getGatesForTurn("execute-task").map((g) => [g.id, g] as const));
277
+ for (const row of pendingGates) {
278
+ const def = ownedDefs.get(row.gate_id);
279
+ if (!def) continue;
280
+ const field = taskGateFieldForId(def.id, params);
281
+ const hasContent = typeof field === "string" && field.trim().length > 0;
282
+ saveGateResult({
283
+ milestoneId: params.milestoneId,
284
+ sliceId: params.sliceId,
285
+ taskId: params.taskId,
286
+ gateId: def.id,
287
+ verdict: hasContent ? "pass" : "omitted",
288
+ rationale: hasContent
289
+ ? `${def.promptSection} section populated in task summary`
290
+ : `${def.promptSection} section left empty — recorded as omitted`,
291
+ findings: hasContent ? (field as string).trim() : "",
292
+ });
293
+ }
294
+ }
295
+ } catch (gateErr) {
296
+ logWarning(
297
+ "tool",
298
+ `complete-task gate close warning for ${params.milestoneId}/${params.sliceId}/${params.taskId}: ${(gateErr as Error).message}`,
299
+ );
300
+ }
301
+
239
302
  // Invalidate all caches
240
303
  invalidateStateCache();
241
304
  clearPathCache();
@@ -8,6 +8,7 @@ import {
8
8
  _getAdapter,
9
9
  saveGateResult,
10
10
  } from "../gsd-db.js";
11
+ import { GATE_REGISTRY } from "../gate-registry.js";
11
12
  import { saveArtifactToDb } from "../db-writer.js";
12
13
  import type { CompleteMilestoneParams } from "./complete-milestone.js";
13
14
  import { handleCompleteMilestone } from "./complete-milestone.js";
@@ -427,7 +428,9 @@ export async function executeSaveGateResult(
427
428
  };
428
429
  }
429
430
 
430
- const validGates = ["Q3", "Q4", "Q5", "Q6", "Q7", "Q8"];
431
+ // Source of truth: gate-registry.ts. Every declared GateId is accepted,
432
+ // so adding a new gate in one place automatically flows through here.
433
+ const validGates = Object.keys(GATE_REGISTRY);
431
434
  if (!validGates.includes(params.gateId)) {
432
435
  return {
433
436
  content: [{ type: "text", text: `Error: Invalid gateId "${params.gateId}". Must be one of: ${validGates.join(", ")}` }],
@@ -536,6 +536,24 @@ export interface CompleteTaskParams {
536
536
  verdict: string;
537
537
  durationMs: number;
538
538
  }>;
539
+ /**
540
+ * Q5 failure-modes section content (what breaks when dependencies fail).
541
+ * Populated → `pass`; omitted/empty → `omitted`.
542
+ * @optional
543
+ */
544
+ failureModes?: string;
545
+ /**
546
+ * Q6 load-profile section content (10x breakpoint + protection).
547
+ * Populated → `pass`; omitted/empty → `omitted`.
548
+ * @optional
549
+ */
550
+ loadProfile?: string;
551
+ /**
552
+ * Q7 negative-tests section content (malformed inputs, error paths,
553
+ * boundaries). Populated → `pass`; omitted/empty → `omitted`.
554
+ * @optional
555
+ */
556
+ negativeTests?: string;
539
557
  /** Optional caller-provided identity for audit trail */
540
558
  actorName?: string;
541
559
  /** Optional caller-provided reason this action was triggered */
@@ -584,6 +602,14 @@ export interface CompleteSliceParams {
584
602
  affects?: string[];
585
603
  /** @optional — defaults to [] when omitted */
586
604
  drillDownPaths?: string[];
605
+ /**
606
+ * Q8 operational readiness section content (health signal, failure signal,
607
+ * recovery, monitoring gaps). When populated, the complete-slice handler
608
+ * records Q8 as `pass`; when omitted or empty, Q8 is recorded as `omitted`.
609
+ * See gate-registry.ts.
610
+ * @optional
611
+ */
612
+ operationalReadiness?: string;
587
613
  /** Optional caller-provided identity for audit trail */
588
614
  actorName?: string;
589
615
  /** Optional caller-provided reason this action was triggered */
@@ -57,7 +57,15 @@ async function probeAndRegister(pi: ExtensionAPI): Promise<boolean> {
57
57
  }
58
58
 
59
59
  const models = await discoverModels();
60
- if (models.length === 0) return true; // Running but no models pulled
60
+ if (models.length === 0) {
61
+ // No local models means there's nothing usable to register in GSD.
62
+ // Keep the footer/status clean instead of advertising Ollama availability.
63
+ if (providerRegistered) {
64
+ pi.unregisterProvider("ollama");
65
+ providerRegistered = false;
66
+ }
67
+ return false;
68
+ }
61
69
 
62
70
  const baseUrl = client.getOllamaHost();
63
71
 
@@ -115,9 +123,11 @@ export default function ollama(pi: ExtensionAPI) {
115
123
  } else {
116
124
  probeAndRegister(pi)
117
125
  .then((found) => {
118
- if (found) ctx.ui.setStatus("ollama", "Ollama");
126
+ ctx.ui.setStatus("ollama", found ? "Ollama" : undefined);
119
127
  })
120
- .catch(() => {});
128
+ .catch(() => {
129
+ ctx.ui.setStatus("ollama", undefined);
130
+ });
121
131
  }
122
132
  });
123
133
 
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Regression test: don't show an Ollama footer status unless Ollama is
3
+ * actually usable (running with at least one discovered model).
4
+ */
5
+ import { test } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { readFileSync } from "node:fs";
8
+ import { join, dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const src = readFileSync(join(__dirname, "index.ts"), "utf-8");
13
+
14
+ test("probeAndRegister returns false when no Ollama models are discovered", () => {
15
+ assert.match(
16
+ src,
17
+ /if \(models\.length === 0\)[\s\S]*return false;/,
18
+ "running-without-models should not be treated as available",
19
+ );
20
+ });
21
+
22
+ test("interactive session clears ollama footer status when unavailable", () => {
23
+ assert.match(
24
+ src,
25
+ /ctx\.ui\.setStatus\("ollama", found \? "Ollama" : undefined\)/,
26
+ "status should be cleared when probeAndRegister reports unavailable",
27
+ );
28
+ });
@@ -0,0 +1,42 @@
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
+
10
+ let _active = false;
11
+ let _currentPhase: string | null = null;
12
+
13
+ /** Mark GSD auto-mode as active. */
14
+ export function activateGSD(): void {
15
+ _active = true;
16
+ }
17
+
18
+ /** Mark GSD auto-mode as inactive and clear the current phase. */
19
+ export function deactivateGSD(): void {
20
+ _active = false;
21
+ _currentPhase = null;
22
+ }
23
+
24
+ /** Set the currently dispatched GSD phase (e.g. "plan-milestone"). */
25
+ export function setCurrentPhase(phase: string): void {
26
+ _currentPhase = phase;
27
+ }
28
+
29
+ /** Clear the current phase (unit completed or aborted). */
30
+ export function clearCurrentPhase(): void {
31
+ _currentPhase = null;
32
+ }
33
+
34
+ /** Returns true if GSD auto-mode is currently active. */
35
+ export function isGSDActive(): boolean {
36
+ return _active;
37
+ }
38
+
39
+ /** Returns the current GSD phase, or null if none is active. */
40
+ export function getCurrentPhase(): string | null {
41
+ return _active ? _currentPhase : null;
42
+ }
@@ -0,0 +1,48 @@
1
+ import { describe, it, beforeEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ activateGSD,
5
+ deactivateGSD,
6
+ setCurrentPhase,
7
+ clearCurrentPhase,
8
+ isGSDActive,
9
+ getCurrentPhase,
10
+ } from "../gsd-phase-state.js";
11
+
12
+ describe("gsd-phase-state", () => {
13
+ beforeEach(() => {
14
+ deactivateGSD();
15
+ });
16
+
17
+ it("tracks active/inactive state", () => {
18
+ assert.equal(isGSDActive(), false);
19
+ activateGSD();
20
+ assert.equal(isGSDActive(), true);
21
+ deactivateGSD();
22
+ assert.equal(isGSDActive(), false);
23
+ });
24
+
25
+ it("tracks the current phase when active", () => {
26
+ activateGSD();
27
+ assert.equal(getCurrentPhase(), null);
28
+ setCurrentPhase("plan-milestone");
29
+ assert.equal(getCurrentPhase(), "plan-milestone");
30
+ clearCurrentPhase();
31
+ assert.equal(getCurrentPhase(), null);
32
+ });
33
+
34
+ it("returns null phase when inactive even if phase was set", () => {
35
+ activateGSD();
36
+ setCurrentPhase("plan-milestone");
37
+ deactivateGSD();
38
+ assert.equal(getCurrentPhase(), null);
39
+ });
40
+
41
+ it("deactivation clears the current phase", () => {
42
+ activateGSD();
43
+ setCurrentPhase("execute-task");
44
+ deactivateGSD();
45
+ activateGSD();
46
+ assert.equal(getCurrentPhase(), null);
47
+ });
48
+ });
@@ -15,6 +15,7 @@ export interface AgentConfig {
15
15
  description: string;
16
16
  tools?: string[];
17
17
  model?: string;
18
+ conflictsWith?: string[];
18
19
  systemPrompt: string;
19
20
  source: "user" | "project";
20
21
  filePath: string;
@@ -30,6 +31,13 @@ interface AgentFrontmatter extends Record<string, unknown> {
30
31
  description?: string;
31
32
  tools?: string | string[];
32
33
  model?: string;
34
+ conflicts_with?: string;
35
+ }
36
+
37
+ export function parseConflictsWith(value: string | undefined): string[] | undefined {
38
+ if (typeof value !== "string") return undefined;
39
+ const conflicts = value.split(",").map((s) => s.trim()).filter(Boolean);
40
+ return conflicts.length > 0 ? conflicts : undefined;
33
41
  }
34
42
 
35
43
  function parseAgentTools(value: string | string[] | undefined): string[] | undefined {
@@ -85,12 +93,14 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
85
93
  }
86
94
 
87
95
  const tools = parseAgentTools(frontmatter.tools);
96
+ const conflictsWith = parseConflictsWith(frontmatter.conflicts_with);
88
97
 
89
98
  agents.push({
90
99
  name: frontmatter.name,
91
100
  description: frontmatter.description,
92
101
  tools: tools && tools.length > 0 ? tools : undefined,
93
102
  model: frontmatter.model,
103
+ conflictsWith,
94
104
  systemPrompt: body,
95
105
  source,
96
106
  filePath,
@@ -24,6 +24,7 @@ import { type ExtensionAPI, getMarkdownTheme } from "@gsd/pi-coding-agent";
24
24
  import { Container, Markdown, Spacer, Text } from "@gsd/pi-tui";
25
25
  import { Type } from "@sinclair/typebox";
26
26
  import { formatTokenCount } from "../shared/mod.js";
27
+ import { getCurrentPhase } from "../shared/gsd-phase-state.js";
27
28
  import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js";
28
29
  import {
29
30
  type IsolationEnvironment,
@@ -352,6 +353,23 @@ async function runSingleAgent(
352
353
  };
353
354
  }
354
355
 
356
+ // GSD phase guard: block agents that conflict with the active GSD phase
357
+ if (agent.conflictsWith && agent.conflictsWith.length > 0) {
358
+ const activePhase = getCurrentPhase();
359
+ if (activePhase && agent.conflictsWith.includes(activePhase)) {
360
+ return {
361
+ agent: agentName,
362
+ agentSource: agent.source,
363
+ task,
364
+ exitCode: 1,
365
+ messages: [],
366
+ stderr: `Agent "${agentName}" is blocked: it conflicts with the active GSD phase "${activePhase}". Use the built-in GSD workflow instead.`,
367
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
368
+ step,
369
+ };
370
+ }
371
+ }
372
+
355
373
  let tmpPromptDir: string | null = null;
356
374
  let tmpPromptPath: string | null = null;
357
375