@opengsd/gsd-pi 1.1.1-dev.616a1a1 → 1.1.1-dev.74e8dd1

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 (232) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +167 -16
  3. package/dist/resources/extensions/gsd/auto/phases.js +4 -3
  4. package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +39 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +113 -7
  7. package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
  8. package/dist/resources/extensions/gsd/auto-recovery.js +4 -4
  9. package/dist/resources/extensions/gsd/auto-start.js +94 -15
  10. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
  11. package/dist/resources/extensions/gsd/auto.js +22 -4
  12. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
  13. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
  16. package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
  17. package/dist/resources/extensions/gsd/commands/handlers/core.js +6 -2
  18. package/dist/resources/extensions/gsd/commands/handlers/ops.js +7 -3
  19. package/dist/resources/extensions/gsd/commands-maintenance.js +172 -2
  20. package/dist/resources/extensions/gsd/commands-mcp-status.js +107 -59
  21. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
  22. package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
  23. package/dist/resources/extensions/gsd/config-overlay.js +2 -1
  24. package/dist/resources/extensions/gsd/error-classifier.js +2 -1
  25. package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
  26. package/dist/resources/extensions/gsd/gsd-db.js +37 -4
  27. package/dist/resources/extensions/gsd/guided-flow.js +1 -1
  28. package/dist/resources/extensions/gsd/mcp-filter.js +3 -0
  29. package/dist/resources/extensions/gsd/mcp-project-config.js +67 -8
  30. package/dist/resources/extensions/gsd/migration-auto-check.js +2 -2
  31. package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
  32. package/dist/resources/extensions/gsd/prompts/system.md +3 -1
  33. package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
  34. package/dist/resources/extensions/gsd/skill-activation.js +20 -3
  35. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +4 -2
  36. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +1 -1
  37. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
  38. package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
  39. package/dist/resources/extensions/gsd/state.js +15 -12
  40. package/dist/resources/extensions/gsd/tool-presentation-plan.js +120 -0
  41. package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
  42. package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -9
  43. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
  44. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
  45. package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
  46. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
  47. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -1
  48. package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
  49. package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -0
  50. package/dist/resources/extensions/mcp-client/manager.js +31 -1
  51. package/dist/web/standalone/.next/BUILD_ID +1 -1
  52. package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
  53. package/dist/web/standalone/.next/build-manifest.json +2 -2
  54. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  55. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.html +1 -1
  72. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app-paths-manifest.json +4 -4
  79. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  80. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  82. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  83. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  84. package/package.json +2 -2
  85. package/packages/cloud-mcp-gateway/package.json +2 -2
  86. package/packages/contracts/dist/workflow.d.ts +14 -0
  87. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  88. package/packages/contracts/dist/workflow.js +16 -0
  89. package/packages/contracts/dist/workflow.js.map +1 -1
  90. package/packages/contracts/package.json +1 -1
  91. package/packages/daemon/package.json +4 -4
  92. package/packages/gsd-agent-core/package.json +5 -5
  93. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  94. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  95. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
  96. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  97. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
  98. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  99. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +72 -31
  100. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  101. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.d.ts.map +1 -1
  102. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js +2 -0
  103. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
  104. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
  105. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
  106. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
  108. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
  110. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  111. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
  112. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
  113. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
  114. package/packages/gsd-agent-modes/package.json +7 -7
  115. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  116. package/packages/mcp-server/dist/workflow-tools.js +82 -0
  117. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  118. package/packages/mcp-server/package.json +3 -3
  119. package/packages/native/package.json +1 -1
  120. package/packages/pi-agent-core/package.json +1 -1
  121. package/packages/pi-ai/dist/image-models.generated.d.ts +15 -0
  122. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  123. package/packages/pi-ai/dist/image-models.generated.js +15 -0
  124. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  125. package/packages/pi-ai/dist/models.generated.d.ts +338 -17
  126. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  127. package/packages/pi-ai/dist/models.generated.js +412 -112
  128. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  129. package/packages/pi-ai/package.json +1 -1
  130. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  131. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  133. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  134. package/packages/pi-coding-agent/package.json +7 -7
  135. package/packages/pi-tui/dist/terminal.d.ts +1 -0
  136. package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
  137. package/packages/pi-tui/dist/terminal.js +8 -4
  138. package/packages/pi-tui/dist/terminal.js.map +1 -1
  139. package/packages/pi-tui/package.json +1 -1
  140. package/packages/rpc-client/package.json +2 -2
  141. package/pkg/package.json +1 -1
  142. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +196 -16
  143. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +239 -63
  144. package/src/resources/extensions/gsd/auto/phases.ts +5 -3
  145. package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
  146. package/src/resources/extensions/gsd/auto-dispatch.ts +48 -0
  147. package/src/resources/extensions/gsd/auto-post-unit.ts +138 -7
  148. package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
  149. package/src/resources/extensions/gsd/auto-recovery.ts +4 -4
  150. package/src/resources/extensions/gsd/auto-start.ts +112 -17
  151. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
  152. package/src/resources/extensions/gsd/auto.ts +35 -3
  153. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
  154. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
  155. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
  156. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
  157. package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
  158. package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -2
  159. package/src/resources/extensions/gsd/commands/handlers/ops.ts +7 -3
  160. package/src/resources/extensions/gsd/commands-maintenance.ts +197 -2
  161. package/src/resources/extensions/gsd/commands-mcp-status.ts +134 -57
  162. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
  163. package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
  164. package/src/resources/extensions/gsd/config-overlay.ts +3 -1
  165. package/src/resources/extensions/gsd/error-classifier.ts +2 -1
  166. package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
  167. package/src/resources/extensions/gsd/gsd-db.ts +41 -6
  168. package/src/resources/extensions/gsd/guided-flow.ts +1 -1
  169. package/src/resources/extensions/gsd/mcp-filter.ts +3 -0
  170. package/src/resources/extensions/gsd/mcp-project-config.ts +92 -10
  171. package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
  172. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  173. package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
  174. package/src/resources/extensions/gsd/prompts/system.md +3 -1
  175. package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
  176. package/src/resources/extensions/gsd/skill-activation.ts +20 -2
  177. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +4 -2
  178. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +1 -1
  179. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
  180. package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
  181. package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
  182. package/src/resources/extensions/gsd/state.ts +16 -12
  183. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
  184. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +86 -0
  185. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +143 -2
  186. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +24 -2
  187. package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
  188. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
  189. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
  190. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +50 -13
  191. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +60 -0
  192. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
  193. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
  194. package/src/resources/extensions/gsd/tests/gsd-rebuild.test.ts +199 -0
  195. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +75 -0
  196. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +13 -6
  197. package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +15 -0
  198. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +68 -0
  199. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +177 -0
  200. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +3 -3
  201. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
  202. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +39 -1
  203. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
  204. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
  205. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
  206. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
  207. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
  208. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
  209. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +6 -2
  210. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
  211. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
  212. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
  213. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
  214. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
  215. package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
  216. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +17 -2
  217. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
  218. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
  219. package/src/resources/extensions/gsd/tool-presentation-plan.ts +167 -0
  220. package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
  221. package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -9
  222. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
  223. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
  224. package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
  225. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
  226. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -1
  227. package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
  228. package/src/resources/extensions/gsd/worktree-lifecycle.ts +26 -0
  229. package/src/resources/extensions/mcp-client/manager.ts +33 -1
  230. package/src/resources/extensions/mcp-client/tests/manager.test.ts +35 -0
  231. /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
  232. /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
@@ -1,7 +1,7 @@
1
1
  // gsd-pi - Claude Code stream adapter regression tests
2
2
  import { describe, test } from "node:test";
3
3
  import assert from "node:assert/strict";
4
- import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
4
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
5
5
  import { join, resolve } from "node:path";
6
6
  import { tmpdir } from "node:os";
7
7
  import {
@@ -33,6 +33,8 @@ import {
33
33
  resolveBundledClaudeCliPath,
34
34
  normalizeClaudePathForSdk,
35
35
  roundResultToElicitationContent,
36
+ autoInitClaudeCodeWorkflowMcp,
37
+ inferGsdPhaseFromContext,
36
38
  } from "../stream-adapter.ts";
37
39
  import type { AssistantMessage, Context, Message } from "@gsd/pi-ai";
38
40
  import type { SDKUserMessage } from "../sdk-types.ts";
@@ -794,6 +796,11 @@ describe("stream-adapter — session persistence (#2859)", () => {
794
796
  assert.equal(options.persistSession, true, "persistSession must default to true");
795
797
  });
796
798
 
799
+ test("buildSdkOptions loads project and local settings so approved .mcp.json servers are active", () => {
800
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test prompt");
801
+ assert.deepEqual(options.settingSources, ["project", "local"]);
802
+ });
803
+
797
804
  test("buildSdkOptions sets model and prompt correctly", () => {
798
805
  const options = buildSdkOptions("claude-sonnet-4-20250514", "hello world");
799
806
  assert.equal(options.model, "claude-sonnet-4-20250514");
@@ -829,6 +836,29 @@ describe("stream-adapter — session persistence (#2859)", () => {
829
836
  }
830
837
  });
831
838
 
839
+ test("autoInitClaudeCodeWorkflowMcp writes and approves project GSD MCP config", () => {
840
+ const projectRoot = realpathSync(mkdtempSync(join(tmpdir(), "claude-sdk-auto-init-")));
841
+ const restore = setWorkflowMcpEnv({
842
+ GSD_WORKFLOW_MCP_COMMAND: "node",
843
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
844
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["server.js"]),
845
+ GSD_WORKFLOW_MCP_CWD: projectRoot,
846
+ });
847
+
848
+ try {
849
+ autoInitClaudeCodeWorkflowMcp(projectRoot);
850
+ assert.equal(existsSync(join(projectRoot, ".mcp.json")), true);
851
+
852
+ const settings = JSON.parse(readFileSync(join(projectRoot, ".claude", "settings.local.json"), "utf-8")) as {
853
+ enabledMcpjsonServers?: string[];
854
+ };
855
+ assert.deepEqual(settings.enabledMcpjsonServers, ["gsd-workflow", "gsd-browser"]);
856
+ } finally {
857
+ restore();
858
+ rmSync(projectRoot, { recursive: true, force: true });
859
+ }
860
+ });
861
+
832
862
  test("resolveClaudeCodeCwd falls back to process cwd when no stream cwd is provided", () => {
833
863
  assert.equal(resolveClaudeCodeCwd(), process.cwd());
834
864
  assert.equal(resolveClaudeCodeCwd({ cwd: " " }), process.cwd());
@@ -1013,21 +1043,118 @@ describe("stream-adapter — session persistence (#2859)", () => {
1013
1043
  }
1014
1044
  });
1015
1045
 
1016
- test("buildSdkOptions prefers custom workflow MCP question tools over native AskUserQuestion", () => {
1017
- const restore = setWorkflowMcpEnv({
1018
- GSD_WORKFLOW_MCP_COMMAND: "node",
1046
+ test("buildSdkOptions scopes run-uat to exact workflow MCP tools", () => {
1047
+ const restore = setWorkflowMcpEnv({
1048
+ GSD_WORKFLOW_MCP_COMMAND: "node",
1049
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
1050
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
1051
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
1052
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1053
+ });
1054
+ const originalCwd = process.cwd();
1055
+ const emptyDir = mkdtempSync(join(tmpdir(), "claude-mcp-uat-"));
1056
+ try {
1057
+ process.chdir(emptyDir);
1058
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { gsdPhase: "run-uat" });
1059
+ const allowedTools = options.allowedTools as string[];
1060
+ const disallowedTools = options.disallowedTools as string[];
1061
+
1062
+ assert.deepEqual(allowedTools, [
1063
+ "Read",
1064
+ "Glob",
1065
+ "Grep",
1066
+ "mcp__gsd-workflow__gsd_uat_exec",
1067
+ "mcp__gsd-workflow__gsd_uat_result_save",
1068
+ "mcp__gsd-workflow__gsd_resume",
1069
+ "mcp__gsd-workflow__gsd_milestone_status",
1070
+ "mcp__gsd-workflow__gsd_journal_query",
1071
+ "mcp__gsd-browser__*",
1072
+ ]);
1073
+ assert.ok(!allowedTools.includes("Bash"));
1074
+ assert.ok(!allowedTools.includes("Write"));
1075
+ assert.ok(!allowedTools.includes("Edit"));
1076
+ assert.ok(!allowedTools.includes("WebSearch"));
1077
+ assert.ok(!allowedTools.includes("mcp__gsd-workflow__*"));
1078
+ assert.ok(disallowedTools.includes("Bash"));
1079
+ assert.ok(disallowedTools.includes("Write"));
1080
+ assert.ok(disallowedTools.includes("Edit"));
1081
+ assert.ok(disallowedTools.includes("WebSearch"));
1082
+ assert.ok(disallowedTools.includes("mcp__gsd-workflow__gsd_exec"));
1083
+ assert.ok(disallowedTools.includes("mcp__gsd-workflow__gsd_summary_save"));
1084
+ assert.ok(disallowedTools.includes("mcp__gsd-workflow__gsd_save_gate_result"));
1085
+ assert.equal(options.strictMcpConfig, true);
1086
+ assert.deepEqual(options.settingSources, []);
1087
+ assert.ok((options.mcpServers as Record<string, unknown>)?.["gsd-workflow"]);
1088
+ assert.ok((options.mcpServers as Record<string, unknown>)?.["gsd-browser"]);
1089
+ } finally {
1090
+ process.chdir(originalCwd);
1091
+ rmSync(emptyDir, { recursive: true, force: true });
1092
+ restore();
1093
+ }
1094
+ });
1095
+
1096
+ test("buildSdkOptions presents exact required workflow MCP tools for non-UAT GSD phases", () => {
1097
+ const restore = setWorkflowMcpEnv({
1098
+ GSD_WORKFLOW_MCP_COMMAND: "node",
1099
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
1100
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
1101
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
1102
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1103
+ });
1104
+ const originalCwd = process.cwd();
1105
+ const emptyDir = mkdtempSync(join(tmpdir(), "claude-mcp-plan-"));
1106
+ try {
1107
+ process.chdir(emptyDir);
1108
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { gsdPhase: "plan-milestone" });
1109
+ const allowedTools = options.allowedTools as string[];
1110
+
1111
+ assert.ok(
1112
+ allowedTools.includes("mcp__gsd-workflow__gsd_plan_milestone"),
1113
+ "plan-milestone must expose exact planning tool",
1114
+ );
1115
+ assert.ok(
1116
+ allowedTools.includes("mcp__gsd-workflow__gsd_milestone_status"),
1117
+ "plan-milestone must expose exact milestone status helper before ToolSearch is needed",
1118
+ );
1119
+ assert.ok(
1120
+ allowedTools.includes("mcp__gsd-workflow__*"),
1121
+ "non-UAT workflow phases keep the wildcard for existing broad workflow behavior",
1122
+ );
1123
+ assert.ok((options.disallowedTools as string[]).includes("AskUserQuestion"));
1124
+ assert.equal(options.strictMcpConfig, true);
1125
+ assert.ok((options.mcpServers as Record<string, unknown>)?.["gsd-workflow"]);
1126
+ } finally {
1127
+ process.chdir(originalCwd);
1128
+ rmSync(emptyDir, { recursive: true, force: true });
1129
+ restore();
1130
+ }
1131
+ });
1132
+
1133
+ test("inferGsdPhaseFromContext recognizes non-UAT unit prompts", () => {
1134
+ const context = {
1135
+ messages: [
1136
+ { role: "user", content: "## UNIT: Plan Milestone M002 (\"Plan by Priority\")" },
1137
+ ],
1138
+ } as Context;
1139
+
1140
+ assert.equal(inferGsdPhaseFromContext(context), "plan-milestone");
1141
+ });
1142
+
1143
+ test("buildSdkOptions prefers custom workflow MCP question tools over native AskUserQuestion", () => {
1144
+ const restore = setWorkflowMcpEnv({
1145
+ GSD_WORKFLOW_MCP_COMMAND: "node",
1019
1146
  GSD_WORKFLOW_MCP_NAME: "custom-workflow",
1020
1147
  GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
1021
- GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
1022
- GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1023
- });
1024
- const originalCwd = process.cwd();
1025
- const emptyDir = mkdtempSync(join(tmpdir(), "claude-mcp-custom-inject-"));
1026
- try {
1027
- process.chdir(emptyDir);
1148
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
1149
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1150
+ });
1151
+ const originalCwd = process.cwd();
1152
+ const emptyDir = mkdtempSync(join(tmpdir(), "claude-mcp-custom-inject-"));
1153
+ try {
1154
+ process.chdir(emptyDir);
1028
1155
 
1029
- const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
1030
- const mcpServers = options.mcpServers as Record<string, any>;
1156
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
1157
+ const mcpServers = options.mcpServers as Record<string, any>;
1031
1158
  assert.ok(mcpServers?.["custom-workflow"], "expected custom workflow server config");
1032
1159
  assert.ok(mcpServers?.["gsd-browser"], "expected gsd-browser server config");
1033
1160
  assert.deepEqual(options.disallowedTools, ["ToolSearch", "AskUserQuestion"]);
@@ -1041,15 +1168,15 @@ describe("stream-adapter — session persistence (#2859)", () => {
1041
1168
  "Agent",
1042
1169
  "WebFetch",
1043
1170
  "WebSearch",
1044
- "mcp__custom-workflow__*",
1045
- "mcp__gsd-browser__*",
1046
- ]);
1047
- } finally {
1048
- process.chdir(originalCwd);
1049
- rmSync(emptyDir, { recursive: true, force: true });
1050
- restore();
1051
- }
1052
- });
1171
+ "mcp__custom-workflow__*",
1172
+ "mcp__gsd-browser__*",
1173
+ ]);
1174
+ } finally {
1175
+ process.chdir(originalCwd);
1176
+ rmSync(emptyDir, { recursive: true, force: true });
1177
+ restore();
1178
+ }
1179
+ });
1053
1180
 
1054
1181
  test("buildSdkOptions auto-discovers bundled MCP server even without env hints", () => {
1055
1182
  // Use setWorkflowMcpEnv with no values to save current state;
@@ -1162,7 +1289,41 @@ describe("stream-adapter — session persistence (#2859)", () => {
1162
1289
  }
1163
1290
  });
1164
1291
 
1165
- test("buildSdkOptions does not inject workflow MCP when already declared in project .mcp.json (avoids duplicate registration)", () => {
1292
+ test("buildSdkOptions force-inlines workflow MCP for GSD phases when project .mcp.json already declares it", () => {
1293
+ const restore = setWorkflowMcpEnv({
1294
+ GSD_WORKFLOW_MCP_COMMAND: "node",
1295
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
1296
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
1297
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
1298
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1299
+ });
1300
+ const originalCwd = process.cwd();
1301
+ const projectDir = mkdtempSync(join(tmpdir(), "claude-mcp-inline-project-"));
1302
+ try {
1303
+ writeFileSync(
1304
+ join(projectDir, ".mcp.json"),
1305
+ JSON.stringify({ mcpServers: { "gsd-workflow": { command: "node", args: ["old-cli.js"] }, "other-mcp": { command: "npx", args: ["other"] } } }),
1306
+ );
1307
+ process.chdir(projectDir);
1308
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { gsdPhase: "plan-milestone" });
1309
+
1310
+ assert.equal(options.strictMcpConfig, true);
1311
+ assert.deepEqual(options.settingSources, []);
1312
+ assert.deepEqual(options.mcpServers, {
1313
+ "gsd-workflow": { command: "node", args: ["old-cli.js"] },
1314
+ });
1315
+ const allowedTools = options.allowedTools as string[];
1316
+ assert.ok(allowedTools.includes("mcp__gsd-workflow__gsd_plan_milestone"));
1317
+ assert.ok(allowedTools.includes("mcp__gsd-workflow__gsd_milestone_status"));
1318
+ assert.ok(allowedTools.includes("mcp__gsd-workflow__*"));
1319
+ } finally {
1320
+ process.chdir(originalCwd);
1321
+ rmSync(projectDir, { recursive: true, force: true });
1322
+ restore();
1323
+ }
1324
+ });
1325
+
1326
+ test("buildSdkOptions does not inject workflow MCP when already declared in project .mcp.json outside GSD phases", () => {
1166
1327
  const restore = setWorkflowMcpEnv({
1167
1328
  GSD_WORKFLOW_MCP_COMMAND: "node",
1168
1329
  GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
@@ -1194,50 +1355,65 @@ describe("stream-adapter — session persistence (#2859)", () => {
1194
1355
  process.chdir(originalCwd);
1195
1356
  rmSync(projectDir, { recursive: true, force: true });
1196
1357
  restore();
1197
- }
1358
+ }
1359
+ });
1360
+
1361
+ test("buildSdkOptions uses project-declared custom workflow MCP namespace", () => {
1362
+ const restore = setWorkflowMcpEnv({
1363
+ GSD_WORKFLOW_MCP_COMMAND: "node",
1364
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
1365
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
1366
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
1367
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1198
1368
  });
1369
+ const originalCwd = process.cwd();
1370
+ const projectDir = mkdtempSync(join(tmpdir(), "claude-mcp-custom-project-"));
1371
+ try {
1372
+ writeFileSync(
1373
+ join(projectDir, ".mcp.json"),
1374
+ JSON.stringify({
1375
+ mcpServers: {
1376
+ "custom-workflow": {
1377
+ command: "node",
1378
+ args: ["custom-cli.js"],
1379
+ env: { GSD_WORKFLOW_PROJECT_ROOT: projectDir },
1380
+ },
1381
+ },
1382
+ }),
1383
+ );
1384
+ process.chdir(projectDir);
1385
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
1386
+ const mcpServers = options.mcpServers as Record<string, any>;
1387
+ assert.deepEqual(Object.keys(mcpServers), ["gsd-browser"], "should inject only browser when project declares a workflow server");
1388
+ const allowedTools = options.allowedTools as string[];
1389
+ assert.ok(allowedTools.includes("mcp__custom-workflow__*"), "allowedTools must use the project workflow namespace");
1390
+ assert.ok(allowedTools.includes("mcp__gsd-browser__*"), "allowedTools must include default browser namespace");
1391
+ assert.ok(!allowedTools.includes("mcp__gsd-workflow__*"), "allowedTools must not advertise the absent default namespace");
1392
+ const disallowedTools = options.disallowedTools as string[];
1393
+ assert.ok(disallowedTools.includes("AskUserQuestion"), "AskUserQuestion should be suppressed when workflow is available");
1199
1394
 
1200
- test("buildSdkOptions uses project-declared custom workflow MCP namespace", () => {
1201
- const restore = setWorkflowMcpEnv({
1202
- GSD_WORKFLOW_MCP_COMMAND: "node",
1203
- GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
1204
- GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
1205
- GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
1206
- GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1395
+ const phaseOptions = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { gsdPhase: "plan-milestone" });
1396
+ assert.equal(phaseOptions.strictMcpConfig, true);
1397
+ assert.deepEqual(phaseOptions.settingSources, []);
1398
+ assert.deepEqual(phaseOptions.mcpServers, {
1399
+ "custom-workflow": {
1400
+ command: "node",
1401
+ args: ["custom-cli.js"],
1402
+ env: { GSD_WORKFLOW_PROJECT_ROOT: projectDir },
1403
+ },
1207
1404
  });
1208
- const originalCwd = process.cwd();
1209
- const projectDir = mkdtempSync(join(tmpdir(), "claude-mcp-custom-project-"));
1210
- try {
1211
- writeFileSync(
1212
- join(projectDir, ".mcp.json"),
1213
- JSON.stringify({
1214
- mcpServers: {
1215
- "custom-workflow": {
1216
- command: "node",
1217
- args: ["custom-cli.js"],
1218
- env: { GSD_WORKFLOW_PROJECT_ROOT: projectDir },
1219
- },
1220
- },
1221
- }),
1222
- );
1223
- process.chdir(projectDir);
1224
- const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
1225
- const mcpServers = options.mcpServers as Record<string, any>;
1226
- assert.deepEqual(Object.keys(mcpServers), ["gsd-browser"], "should inject only browser when project declares a workflow server");
1227
- const allowedTools = options.allowedTools as string[];
1228
- assert.ok(allowedTools.includes("mcp__custom-workflow__*"), "allowedTools must use the project workflow namespace");
1229
- assert.ok(allowedTools.includes("mcp__gsd-browser__*"), "allowedTools must include default browser namespace");
1230
- assert.ok(!allowedTools.includes("mcp__gsd-workflow__*"), "allowedTools must not advertise the absent default namespace");
1231
- const disallowedTools = options.disallowedTools as string[];
1232
- assert.ok(disallowedTools.includes("AskUserQuestion"), "AskUserQuestion should be suppressed when workflow is available");
1233
- } finally {
1234
- process.chdir(originalCwd);
1235
- rmSync(projectDir, { recursive: true, force: true });
1236
- restore();
1237
- }
1238
- });
1405
+ const phaseAllowedTools = phaseOptions.allowedTools as string[];
1406
+ assert.ok(phaseAllowedTools.includes("mcp__custom-workflow__gsd_plan_milestone"));
1407
+ assert.ok(phaseAllowedTools.includes("mcp__custom-workflow__gsd_milestone_status"));
1408
+ assert.ok(!phaseAllowedTools.includes("mcp__gsd-workflow__*"));
1409
+ } finally {
1410
+ process.chdir(originalCwd);
1411
+ rmSync(projectDir, { recursive: true, force: true });
1412
+ restore();
1413
+ }
1414
+ });
1239
1415
 
1240
- test("buildSdkOptions preserves runtime callbacks such as onElicitation", () => {
1416
+ test("buildSdkOptions preserves runtime callbacks such as onElicitation", () => {
1241
1417
  const restore = setWorkflowMcpEnv({});
1242
1418
  const onElicitation = async () => ({ action: "decline" as const });
1243
1419
  try {
@@ -88,7 +88,7 @@ import { resolveManifest } from "../unit-context-manifest.js";
88
88
  import { createWorktreeSafetyModule, type WorktreeSafetyResult } from "../worktree-safety.js";
89
89
  import { isSuspiciousGhostCompletion } from "../auto-unit-closeout.js";
90
90
  import { decideVerificationRetry, verificationRetryKey } from "./verification-retry-policy.js";
91
- import { buildPhaseHandoffOutcome, setAutoOutcomeWidget } from "../auto-dashboard.js";
91
+ import { buildPhaseHandoffOutcome, setAutoActiveStatus, setAutoOutcomeWidget } from "../auto-dashboard.js";
92
92
  import { getConsecutiveDispatchBlocker } from "../dispatch-guard.js";
93
93
  import {
94
94
  captureRootDirtySnapshot,
@@ -100,7 +100,7 @@ import { classifyError, isTransient } from "../error-classifier.js";
100
100
  export const STUCK_WINDOW_SIZE = 6;
101
101
  const STUCK_RECOVERY_ATTEMPTS_KEY = "stuck_recovery_attempts";
102
102
  const ZERO_TOOL_PROVIDER_ERROR_PREFIX_RE =
103
- /^(?:api error(?::|$|\s*\()|provider error(?::|$|\s*\()|request failed\b|(?:http\s*)?(?:429|500|502|503)\b|\b(?:econnreset|etimedout|econnrefused|epipe)\b|socket hang up\b|fetch failed\b|(?:network|connection|server) error(?::|$)|connection (?:reset|refused)(?::|$|\s+by\b)|dns\b.*(?:fail|error|timeout)|unexpected eof\b|stream idle timeout\b|partial response received\b|stream_exhausted\b|terminated(?::|$)|(?:connection|stream|request)\b.{0,40}\bterminated\b|other side closed\b|rate.?limit(?:ed| exceeded| reached| error)|too many requests\b|you(?:'ve| have) hit your limit\b|usage limit\b|out of extra usage\b|service.?unavailable\b|internal(?: server)? error(?::|$)|internal(?:[_-]server)?[_-]error\b|server[_-]error\b|(?:provider|server|api|model|codex|claude|openai|anthropic|gemini)\b.{0,80}\boverloaded\b|overloaded\b.{0,80}\b(?:provider|server|api|model)\b|context (?:window|length) exceed|context window exceed)/i;
103
+ /^(?:api error(?::|$|\s*\()|provider error(?::|$|\s*\()|request failed\b|(?:http\s*)?(?:429|500|502|503)\b|\b(?:econnreset|etimedout|econnrefused|epipe)\b|socket hang up\b|fetch failed\b|(?:network|connection|server) error(?::|$)|connection (?:reset|refused)(?::|$|\s+by\b)|dns\b.*(?:fail|error|timeout)|unexpected eof\b|stream idle timeout\b|partial response received\b|stream_exhausted\b|terminated(?::|$)|(?:connection|stream|request)\b.{0,40}\bterminated\b|other side closed\b|rate.?limit(?:ed| exceeded| reached| error)|too many requests\b|you(?:'ve| have) (?:hit|reached) your (?:\w+ )?limit\b|.*\b(?:usage|session|weekly|daily|monthly|quota) limit\b|limit\b.{0,40}\bresets?\b|out of extra usage\b|service.?unavailable\b|internal(?: server)? error(?::|$)|internal(?:[_-]server)?[_-]error\b|server[_-]error\b|(?:provider|server|api|model|codex|claude|openai|anthropic|gemini)\b.{0,80}\boverloaded\b|overloaded\b.{0,80}\b(?:provider|server|api|model)\b|context (?:window|length) exceed|context window exceed)/i;
104
104
  const ZERO_TOOL_PROVIDER_ERROR_SIGNAL_RE =
105
105
  /(?:\b(?:http|status(?: code)?|code|error:)\s*(?:429|500|502|503)\b|\b(?:api|provider) error\s*[:(]?\s*(?:429|500|502|503)\b|\b(?:typeerror|error):\s*(?:fetch failed\b|socket hang up\b|terminated(?::|$)|connection (?:reset|refused)(?::|$|\s+by\b)|(?:network|connection|server) error(?::|$)|stream idle timeout\b|partial response received\b|unexpected eof\b)|\b(?:server_error|api_error|stream_exhausted(?:_without_result)?)\b|\b(?:econnreset|etimedout|econnrefused|epipe)\b|context (?:window|length) exceed|context window exceed)/i;
106
106
 
@@ -114,6 +114,8 @@ function classifyZeroToolProviderMessage(message: string): ReturnType<typeof cla
114
114
  return classifyError(firstLine);
115
115
  }
116
116
 
117
+ export const _classifyZeroToolProviderMessageForTest = classifyZeroToolProviderMessage;
118
+
117
119
  export function resolveDispatchRecoveryAttempts(
118
120
  unitRecoveryCount: Map<string, number>,
119
121
  unitType: string,
@@ -2092,7 +2094,7 @@ export async function runUnitPhase(
2092
2094
  const nextDispatchCount = (s.unitDispatchCount.get(dispatchKey) ?? 0) + 1;
2093
2095
 
2094
2096
  // Status bar (widget + preconditions deferred until after model selection — see #2899)
2095
- ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
2097
+ setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
2096
2098
  if (mid)
2097
2099
  deps.updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
2098
2100
 
@@ -516,8 +516,9 @@ export const hideFooter = (_tui: unknown, theme: Theme, footerData: ReadonlyFoot
516
516
 
517
517
  /** Widget display modes: full → small → min → off → full */
518
518
  export type WidgetMode = "full" | "small" | "min" | "off";
519
+ export const DEFAULT_WIDGET_MODE: WidgetMode = "small";
519
520
  const WIDGET_MODES: WidgetMode[] = ["full", "small", "min", "off"];
520
- let widgetMode: WidgetMode = "full";
521
+ let widgetMode: WidgetMode = DEFAULT_WIDGET_MODE;
521
522
  let widgetModeInitialized = false;
522
523
  let widgetModePreferencePath: string | null = null;
523
524
 
@@ -628,7 +629,7 @@ export function getWidgetMode(projectPath?: string, globalPath?: string): Widget
628
629
 
629
630
  /** Test-only reset for widget mode caching. */
630
631
  export function _resetWidgetModeForTests(): void {
631
- widgetMode = "full";
632
+ widgetMode = DEFAULT_WIDGET_MODE;
632
633
  widgetModeInitialized = false;
633
634
  widgetModePreferencePath = null;
634
635
  }
@@ -648,6 +649,16 @@ export interface WidgetStateAccessors {
648
649
  getCurrentDispatchedModelId(): string | null;
649
650
  }
650
651
 
652
+ function clearAutoOutcomeWidget(ctx: ExtensionContext): void {
653
+ if (!ctx.hasUI) return;
654
+ ctx.ui.setWidget("gsd-outcome", undefined);
655
+ }
656
+
657
+ export function setAutoActiveStatus(ctx: ExtensionContext, status: "auto" | "next"): void {
658
+ ctx.ui.setStatus("gsd-auto", status);
659
+ clearAutoOutcomeWidget(ctx);
660
+ }
661
+
651
662
  export function updateProgressWidget(
652
663
  ctx: ExtensionContext,
653
664
  unitType: string,
@@ -673,7 +684,7 @@ export function updateProgressWidget(
673
684
  ctx.ui.setStatus("gsd-step", undefined);
674
685
  }
675
686
  if (!accessors.isSessionSwitching()) {
676
- ctx.ui.setWidget("gsd-outcome", undefined);
687
+ clearAutoOutcomeWidget(ctx);
677
688
  }
678
689
 
679
690
  const verb = unitVerb(unitType);
@@ -732,6 +743,7 @@ export function updateProgressWidget(
732
743
  logWarning("dashboard", `DB status update failed: ${err instanceof Error ? err.message : String(err)}`);
733
744
  }
734
745
  }, 15_000);
746
+ progressRefreshTimer.unref?.();
735
747
 
736
748
  return {
737
749
  render(width: number): string[] {
@@ -1003,7 +1015,7 @@ export function setCompletionProgressWidget(
1003
1015
  ): void {
1004
1016
  if (!ctx.hasUI) return;
1005
1017
  const widgetKey = "gsd-progress";
1006
- ctx.ui.setWidget("gsd-outcome", undefined);
1018
+ clearAutoOutcomeWidget(ctx);
1007
1019
 
1008
1020
  if (typeof ctx.ui?.setHeader === "function") {
1009
1021
  ctx.ui.setHeader(() => ({
@@ -91,6 +91,7 @@ import { nativeHasChanges, nativeIsRepo, _resetHasChangesCache } from "./native-
91
91
  import { debugLog, isDebugEnabled } from "./debug-logger.js";
92
92
  import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
93
93
  import { resolveWorktreeProjectRoot } from "./worktree-root.js";
94
+ import { detectWorktreeName } from "./worktree.js";
94
95
  import { probeGitConflictState } from "./git-conflict-state.js";
95
96
  import { runTurnGitAction } from "./git-service.js";
96
97
  import { parseUnitId } from "./unit-id.js";
@@ -340,6 +341,29 @@ function isRegistryMilestoneComplete(state: GSDState, mid: string): boolean {
340
341
  );
341
342
  }
342
343
 
344
+ function normalizeMilestoneScope(value: string | null | undefined): string | null {
345
+ const trimmed = value?.trim();
346
+ if (!trimmed || !MILESTONE_ID_RE.test(trimmed)) return null;
347
+ return trimmed;
348
+ }
349
+
350
+ function resolveDispatchMilestoneScope(
351
+ ctx: DispatchContext,
352
+ ): { id: string; source: string } | null {
353
+ const sessionMilestone = normalizeMilestoneScope(ctx.session?.currentMilestoneId);
354
+ if (sessionMilestone) return { id: sessionMilestone, source: "session.currentMilestoneId" };
355
+
356
+ const sessionWorktree = normalizeMilestoneScope(
357
+ ctx.session?.basePath ? detectWorktreeName(ctx.session.basePath) : null,
358
+ );
359
+ if (sessionWorktree) return { id: sessionWorktree, source: "session.basePath worktree" };
360
+
361
+ const baseWorktree = normalizeMilestoneScope(detectWorktreeName(ctx.basePath));
362
+ if (baseWorktree) return { id: baseWorktree, source: "basePath worktree" };
363
+
364
+ return null;
365
+ }
366
+
343
367
  function hasMilestonePassedDiscuss(basePath: string, mid: string): boolean {
344
368
  if (isDbAvailable()) {
345
369
  try {
@@ -1631,6 +1655,19 @@ import { getRegistry } from "./rule-registry.js";
1631
1655
  export async function resolveDispatch(
1632
1656
  ctx: DispatchContext,
1633
1657
  ): Promise<DispatchAction> {
1658
+ if (ctx.mid && isDbAvailable()) {
1659
+ const milestone = getMilestone(ctx.mid);
1660
+ if (milestone && isClosedStatus(milestone.status)) {
1661
+ return {
1662
+ action: "stop",
1663
+ reason:
1664
+ `Milestone ${ctx.mid} is closed (status: ${milestone.status}); auto-mode will not reopen or recover it implicitly. ` +
1665
+ "Use an explicit reopen command before planning or executing more work for this milestone.",
1666
+ level: "warning",
1667
+ };
1668
+ }
1669
+ }
1670
+
1634
1671
  const activeMid = ctx.state.activeMilestone?.id;
1635
1672
  if (activeMid && ctx.mid !== activeMid) {
1636
1673
  return {
@@ -1642,6 +1679,17 @@ export async function resolveDispatch(
1642
1679
  };
1643
1680
  }
1644
1681
 
1682
+ const scopedMilestone = resolveDispatchMilestoneScope(ctx);
1683
+ if (scopedMilestone && ctx.mid !== scopedMilestone.id) {
1684
+ return {
1685
+ action: "stop",
1686
+ reason:
1687
+ `Dispatch milestone mismatch: context mid "${ctx.mid}" does not match ${scopedMilestone.source} "${scopedMilestone.id}". ` +
1688
+ "The active worktree/session and derived project state disagree; recover, park, or discard the stranded milestone before continuing.",
1689
+ level: "warning",
1690
+ };
1691
+ }
1692
+
1645
1693
  // Delegate to registry when available
1646
1694
  try {
1647
1695
  const registry = getRegistry();