@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 +1 @@
1
- 1fcc10ec4a2a1496
1
+ 5f6f7839b5420a75
@@ -15,10 +15,11 @@ import { homedir } from "node:os";
15
15
  import { createRequire } from "node:module";
16
16
  import { dirname, join } from "node:path";
17
17
  import { PartialMessageBuilder, ZERO_USAGE, mapUsage } from "./partial-builder.js";
18
- import { buildWorkflowMcpServers, resolveWorkflowMcpProjectRoot } from "../gsd/workflow-mcp.js";
19
- import { buildProjectGsdMcpServers } from "../gsd/mcp-project-config.js";
18
+ import { buildWorkflowMcpServers, getRequiredWorkflowToolsForAutoUnit, resolveWorkflowMcpProjectRoot, } from "../gsd/workflow-mcp.js";
19
+ import { buildProjectGsdMcpServers, ensureProjectWorkflowMcpConfig } from "../gsd/mcp-project-config.js";
20
20
  import { loadProjectGSDPreferences } from "../gsd/preferences.js";
21
- import { discoverBrowserMcpServerName, discoverMcpServerNames, discoverWorkflowMcpServerName, computeMcpDisallowedTools } from "../gsd/mcp-filter.js";
21
+ import { discoverBrowserMcpServerName, discoverMcpServers, discoverMcpServerNames, discoverWorkflowMcpServerName, computeMcpDisallowedTools, } from "../gsd/mcp-filter.js";
22
+ import { RUN_UAT_CLAUDE_NATIVE_TOOL_NAMES, RUN_UAT_FORBIDDEN_TOOL_NAMES, RUN_UAT_WORKFLOW_TOOL_NAMES, resolveToolPresentationPlan } from "../gsd/tool-presentation-plan.js";
22
23
  import { showInterviewRound } from "../shared/tui.js";
23
24
  export function serverToolUseToToolCallLike(block) {
24
25
  const argumentsValue = block.input && typeof block.input === "object" && !Array.isArray(block.input)
@@ -164,6 +165,32 @@ function extractMessageText(msg) {
164
165
  }
165
166
  return "";
166
167
  }
168
+ const GSD_PHASE_PATTERNS = [
169
+ ["run-uat", /\b(?:UNIT:\s*Run UAT|run-uat)\b/i],
170
+ ["complete-milestone", /\b(?:UNIT:\s*Complete Milestone|complete-milestone)\b/i],
171
+ ["validate-milestone", /\b(?:UNIT:\s*Validate Milestone|validate-milestone)\b/i],
172
+ ["reassess-roadmap", /\b(?:UNIT:\s*Reassess Roadmap|reassess-roadmap)\b/i],
173
+ ["complete-slice", /\b(?:UNIT:\s*Complete Slice|complete-slice)\b/i],
174
+ ["replan-slice", /\b(?:UNIT:\s*Replan Slice|replan-slice)\b/i],
175
+ ["plan-slice", /\b(?:UNIT:\s*Plan Slice|plan-slice|gsd_plan_slice)\b/i],
176
+ ["plan-milestone", /\b(?:UNIT:\s*Plan Milestone|plan-milestone|gsd_plan_milestone)\b/i],
177
+ ["execute-task", /\b(?:UNIT:\s*Execute Task|execute-task|execute-task-simple|reactive-execute)\b/i],
178
+ ["gate-evaluate", /\b(?:UNIT:\s*Gate Evaluate|gate-evaluate|gsd_save_gate_result)\b/i],
179
+ ["research-milestone", /\b(?:UNIT:\s*Research Milestone|research-milestone)\b/i],
180
+ ["research-slice", /\b(?:UNIT:\s*Research Slice|research-slice)\b/i],
181
+ ["discuss-milestone", /\b(?:Discuss milestone|discuss-milestone)\b/i],
182
+ ];
183
+ export function inferGsdPhaseFromContext(context) {
184
+ const text = [
185
+ context.systemPrompt ?? "",
186
+ ...context.messages.map((message) => extractMessageText(message)),
187
+ ].join("\n");
188
+ for (const [phase, pattern] of GSD_PHASE_PATTERNS) {
189
+ if (pattern.test(text))
190
+ return phase;
191
+ }
192
+ return undefined;
193
+ }
167
194
  /**
168
195
  * Build a full conversational prompt from GSD's context messages.
169
196
  *
@@ -1175,22 +1202,106 @@ function mapThinkingLevelToAnthropicEffort(level, modelId) {
1175
1202
  return "high";
1176
1203
  }
1177
1204
  }
1178
- function workflowMcpServerNameFromAllowedTools(allowedTools) {
1205
+ function parseAllowedMcpToolName(toolName) {
1206
+ const match = /^mcp__(.+)__(\*|[^*]+)$/.exec(toolName);
1207
+ return match?.[1] && match[2] ? { server: match[1], tool: match[2] } : undefined;
1208
+ }
1209
+ function browserMcpServerNameFromAllowedTools(allowedTools) {
1179
1210
  if (!Array.isArray(allowedTools))
1180
1211
  return undefined;
1181
1212
  for (const toolName of allowedTools) {
1182
1213
  if (typeof toolName !== "string")
1183
1214
  continue;
1184
- const match = /^mcp__(.+)__\*$/.exec(toolName);
1185
- if (match?.[1] && match[1] !== "gsd-browser")
1186
- return match[1];
1215
+ const parsed = parseAllowedMcpToolName(toolName);
1216
+ if (!parsed)
1217
+ continue;
1218
+ if (parsed.server === "gsd-browser" || parsed.tool.startsWith("browser_")) {
1219
+ return parsed.server;
1220
+ }
1187
1221
  }
1188
1222
  return undefined;
1189
1223
  }
1190
- function browserMcpServerNameFromAllowedTools(allowedTools) {
1224
+ function workflowMcpServerNameFromAllowedTools(allowedTools) {
1191
1225
  if (!Array.isArray(allowedTools))
1192
1226
  return undefined;
1193
- return allowedTools.includes("mcp__gsd-browser__*") ? "gsd-browser" : undefined;
1227
+ const browserServerName = browserMcpServerNameFromAllowedTools(allowedTools);
1228
+ for (const toolName of allowedTools) {
1229
+ if (typeof toolName !== "string")
1230
+ continue;
1231
+ const parsed = parseAllowedMcpToolName(toolName);
1232
+ if (!parsed || parsed.server === browserServerName || parsed.tool.startsWith("browser_"))
1233
+ continue;
1234
+ return parsed.server;
1235
+ }
1236
+ return undefined;
1237
+ }
1238
+ function isRecord(value) {
1239
+ return !!value && typeof value === "object" && !Array.isArray(value);
1240
+ }
1241
+ function isStringRecord(value) {
1242
+ return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
1243
+ }
1244
+ function cloneSdkMcpServerConfig(config) {
1245
+ if (!isRecord(config))
1246
+ return undefined;
1247
+ const cloned = {};
1248
+ for (const key of ["type", "command", "cwd", "url"]) {
1249
+ if (typeof config[key] === "string")
1250
+ cloned[key] = config[key];
1251
+ }
1252
+ if (Array.isArray(config.args)) {
1253
+ cloned.args = config.args.filter((arg) => typeof arg === "string");
1254
+ }
1255
+ if (isStringRecord(config.env))
1256
+ cloned.env = { ...config.env };
1257
+ if (isStringRecord(config.headers))
1258
+ cloned.headers = { ...config.headers };
1259
+ if (isRecord(config.oauth))
1260
+ cloned.oauth = { ...config.oauth };
1261
+ return Object.keys(cloned).length > 0 ? cloned : undefined;
1262
+ }
1263
+ function resolveProjectMcpServerConfig(projectRoot, serverName, fallbackServers) {
1264
+ if (!serverName)
1265
+ return undefined;
1266
+ const projectServer = discoverMcpServers(projectRoot).find((server) => server.name === serverName);
1267
+ return cloneSdkMcpServerConfig(projectServer?.config) ?? cloneSdkMcpServerConfig(fallbackServers?.[serverName]);
1268
+ }
1269
+ function resolveProjectMcpServerConfigs(projectRoot, serverNames, fallbackServers) {
1270
+ const resolved = {};
1271
+ for (const serverName of serverNames) {
1272
+ const serverConfig = resolveProjectMcpServerConfig(projectRoot, serverName, fallbackServers);
1273
+ if (serverName && serverConfig)
1274
+ resolved[serverName] = serverConfig;
1275
+ }
1276
+ return Object.keys(resolved).length > 0 ? resolved : undefined;
1277
+ }
1278
+ function resolveExactWorkflowMcpToolsForPhase(gsdPhase, workflowServerName, workflowExplicitlyBlocked) {
1279
+ if (!gsdPhase || !workflowServerName || workflowExplicitlyBlocked)
1280
+ return [];
1281
+ const requiredTools = gsdPhase === "run-uat"
1282
+ ? [...RUN_UAT_WORKFLOW_TOOL_NAMES]
1283
+ : getRequiredWorkflowToolsForAutoUnit(gsdPhase);
1284
+ const supportTools = gsdPhase === "run-uat" ? [] : ["gsd_milestone_status"];
1285
+ const requestedToolNames = [...new Set([...requiredTools, ...supportTools])];
1286
+ if (requestedToolNames.length === 0)
1287
+ return [];
1288
+ return resolveToolPresentationPlan({
1289
+ phase: gsdPhase,
1290
+ surface: "claude-code-sdk",
1291
+ workflowMcpServerName: workflowServerName,
1292
+ requestedToolNames,
1293
+ }).presentedToolNames;
1294
+ }
1295
+ export function autoInitClaudeCodeWorkflowMcp(cwd) {
1296
+ const projectRoot = resolveWorkflowMcpProjectRoot(cwd);
1297
+ try {
1298
+ ensureProjectWorkflowMcpConfig(projectRoot);
1299
+ }
1300
+ catch (err) {
1301
+ if (process.env.GSD_DEBUG === "1") {
1302
+ console.warn(`[claude-code] workflow MCP auto-init failed: ${err instanceof Error ? err.message : String(err)}`);
1303
+ }
1304
+ }
1194
1305
  }
1195
1306
  /**
1196
1307
  * Build the options object passed to the Claude Agent SDK's `query()` call.
@@ -1204,7 +1315,7 @@ function browserMcpServerNameFromAllowedTools(allowedTools) {
1204
1315
  * behaviour pass `permissionMode: "bypassPermissions"` explicitly.
1205
1316
  */
1206
1317
  export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
1207
- const { reasoning, cwd, ...sdkExtraOptions } = extraOptions;
1318
+ const { reasoning, cwd, gsdPhase, ...sdkExtraOptions } = extraOptions;
1208
1319
  const sdkCwd = typeof cwd === "string" && cwd.trim().length > 0 ? cwd : process.cwd();
1209
1320
  // Claude Code runs in the milestone worktree for file/shell work, but workflow MCP
1210
1321
  // config (.mcp.json) and server discovery live at the project root.
@@ -1283,13 +1394,38 @@ export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
1283
1394
  const browserMcpTools = !browserExplicitlyBlocked && browserServerName
1284
1395
  ? [`mcp__${browserServerName}__*`]
1285
1396
  : [];
1286
- const gsdMcpTools = [...workflowMcpTools, ...browserMcpTools];
1397
+ const phaseUsesBrowserMcp = !gsdPhase || gsdPhase === "run-uat";
1398
+ const allowedBrowserMcpTools = phaseUsesBrowserMcp ? browserMcpTools : [];
1399
+ const inlinePhaseMcpServers = gsdPhase
1400
+ ? resolveProjectMcpServerConfigs(projectRoot, [
1401
+ workflowExplicitlyBlocked ? undefined : workflowServerName,
1402
+ phaseUsesBrowserMcp && !browserExplicitlyBlocked ? browserServerName : undefined,
1403
+ ], defaultMcpServers.servers)
1404
+ : undefined;
1405
+ const sdkMcpServers = inlinePhaseMcpServers ?? filteredMcpServers;
1406
+ const strictMcpConfig = !!inlinePhaseMcpServers;
1407
+ // Strict phase configs inline the exact MCP servers GSD needs. Loading
1408
+ // project/local settings at the same time can duplicate those servers and
1409
+ // leave allowed mcp__... tools with no registered backing tool.
1410
+ const settingSources = strictMcpConfig ? [] : ["project", "local"];
1411
+ const exactWorkflowMcpTools = resolveExactWorkflowMcpToolsForPhase(gsdPhase, workflowServerName, workflowExplicitlyBlocked);
1412
+ const runUatDisallowedTools = gsdPhase === "run-uat" && workflowServerName
1413
+ ? [
1414
+ ...RUN_UAT_FORBIDDEN_TOOL_NAMES.filter((toolName) => !toolName.startsWith("mcp__")),
1415
+ "WebFetch",
1416
+ "Agent",
1417
+ `mcp__${workflowServerName}__gsd_exec`,
1418
+ `mcp__${workflowServerName}__gsd_summary_save`,
1419
+ `mcp__${workflowServerName}__gsd_save_gate_result`,
1420
+ ]
1421
+ : [];
1287
1422
  const disallowedTools = [...new Set([
1288
1423
  "ToolSearch",
1289
- ...(workflowMcpTools.length > 0 ? ["AskUserQuestion"] : []),
1424
+ ...(workflowMcpTools.length > 0 || exactWorkflowMcpTools.length > 0 ? ["AskUserQuestion"] : []),
1425
+ ...runUatDisallowedTools,
1290
1426
  ...extraDisallowedTools,
1291
1427
  ])];
1292
- const allowedTools = [
1428
+ const standardClaudeTools = [
1293
1429
  "Read",
1294
1430
  "Write",
1295
1431
  "Edit",
@@ -1299,8 +1435,19 @@ export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
1299
1435
  "Agent",
1300
1436
  "WebFetch",
1301
1437
  "WebSearch",
1302
- ...(workflowMcpTools.length > 0 ? gsdMcpTools : ["AskUserQuestion", ...browserMcpTools]),
1303
1438
  ];
1439
+ const allowedTools = gsdPhase === "run-uat"
1440
+ ? [
1441
+ ...RUN_UAT_CLAUDE_NATIVE_TOOL_NAMES,
1442
+ ...(exactWorkflowMcpTools.length > 0 ? exactWorkflowMcpTools : []),
1443
+ ...allowedBrowserMcpTools,
1444
+ ]
1445
+ : [
1446
+ ...standardClaudeTools,
1447
+ ...exactWorkflowMcpTools,
1448
+ ...(workflowMcpTools.length > 0 ? workflowMcpTools : ["AskUserQuestion"]),
1449
+ ...allowedBrowserMcpTools,
1450
+ ];
1304
1451
  const supportsAdaptive = modelSupportsAdaptiveThinking(modelId);
1305
1452
  const effort = reasoning && supportsAdaptive
1306
1453
  ? mapThinkingLevelToAnthropicEffort(reasoning, modelId)
@@ -1321,11 +1468,12 @@ export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
1321
1468
  cwd: sdkCwd,
1322
1469
  permissionMode,
1323
1470
  allowDangerouslySkipPermissions: permissionMode === "bypassPermissions",
1324
- settingSources: ["project"],
1471
+ settingSources,
1325
1472
  systemPrompt: { type: "preset", preset: "claude_code" },
1326
1473
  disallowedTools,
1327
1474
  ...(allowedTools.length > 0 ? { allowedTools } : {}),
1328
- ...(filteredMcpServers ? { mcpServers: filteredMcpServers } : {}),
1475
+ ...(sdkMcpServers ? { mcpServers: sdkMcpServers } : {}),
1476
+ ...(strictMcpConfig ? { strictMcpConfig: true } : {}),
1329
1477
  betas: (modelId.includes("sonnet")
1330
1478
  || modelId.includes("opus-4-7")
1331
1479
  || modelId.includes("opus-4.7")
@@ -1599,6 +1747,8 @@ async function pumpSdkMessages(model, context, options, stream) {
1599
1747
  const onExternalToolCall = options?.onExternalToolCall;
1600
1748
  const onExternalToolResult = options?.onExternalToolResult;
1601
1749
  const cwd = resolveClaudeCodeCwd(options);
1750
+ autoInitClaudeCodeWorkflowMcp(cwd);
1751
+ const gsdPhase = inferGsdPhaseFromContext(context);
1602
1752
  const canUseToolHandler = createClaudeCodeCanUseToolHandler(uiContext);
1603
1753
  // When no UI is available (headless / auto-mode), auto-approve all
1604
1754
  // tool requests. This replaces the old bypassPermissions workaround.
@@ -1606,6 +1756,7 @@ async function pumpSdkMessages(model, context, options, stream) {
1606
1756
  ?? (async (_toolName, _input, opts) => ({ behavior: "allow", toolUseID: opts.toolUseID }));
1607
1757
  const sdkOpts = buildSdkOptions(modelId, "", { permissionMode }, {
1608
1758
  cwd,
1759
+ gsdPhase,
1609
1760
  reasoning: options?.reasoning,
1610
1761
  canUseTool: canUseToolFallback,
1611
1762
  ...(uiContext
@@ -51,13 +51,13 @@ import { resolveManifest } from "../unit-context-manifest.js";
51
51
  import { createWorktreeSafetyModule } from "../worktree-safety.js";
52
52
  import { isSuspiciousGhostCompletion } from "../auto-unit-closeout.js";
53
53
  import { decideVerificationRetry, verificationRetryKey } from "./verification-retry-policy.js";
54
- import { buildPhaseHandoffOutcome, setAutoOutcomeWidget } from "../auto-dashboard.js";
54
+ import { buildPhaseHandoffOutcome, setAutoActiveStatus, setAutoOutcomeWidget } from "../auto-dashboard.js";
55
55
  import { getConsecutiveDispatchBlocker } from "../dispatch-guard.js";
56
56
  import { captureRootDirtySnapshot, detectRootWriteLeak, formatRootWriteLeakMessage, } from "../root-write-leak-guard.js";
57
57
  import { classifyError, isTransient } from "../error-classifier.js";
58
58
  export const STUCK_WINDOW_SIZE = 6;
59
59
  const STUCK_RECOVERY_ATTEMPTS_KEY = "stuck_recovery_attempts";
60
- const ZERO_TOOL_PROVIDER_ERROR_PREFIX_RE = /^(?: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;
60
+ const ZERO_TOOL_PROVIDER_ERROR_PREFIX_RE = /^(?: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;
61
61
  const ZERO_TOOL_PROVIDER_ERROR_SIGNAL_RE = /(?:\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;
62
62
  function classifyZeroToolProviderMessage(message) {
63
63
  const firstLine = message.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
@@ -67,6 +67,7 @@ function classifyZeroToolProviderMessage(message) {
67
67
  return null;
68
68
  return classifyError(firstLine);
69
69
  }
70
+ export const _classifyZeroToolProviderMessageForTest = classifyZeroToolProviderMessage;
70
71
  export function resolveDispatchRecoveryAttempts(unitRecoveryCount, unitType, unitId) {
71
72
  return (unitRecoveryCount.get(`${unitType}/${unitId}`) ?? 0) > 0
72
73
  ? 0
@@ -1582,7 +1583,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1582
1583
  const dispatchKey = `${unitType}/${unitId}`;
1583
1584
  const nextDispatchCount = (s.unitDispatchCount.get(dispatchKey) ?? 0) + 1;
1584
1585
  // Status bar (widget + preconditions deferred until after model selection — see #2899)
1585
- ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
1586
+ setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
1586
1587
  if (mid)
1587
1588
  deps.updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
1588
1589
  // ── Safety harness: reset evidence + create checkpoint ──
@@ -369,8 +369,9 @@ export const hideFooter = (_tui, theme, footerData) => ({
369
369
  invalidate() { },
370
370
  dispose() { },
371
371
  });
372
+ export const DEFAULT_WIDGET_MODE = "small";
372
373
  const WIDGET_MODES = ["full", "small", "min", "off"];
373
- let widgetMode = "full";
374
+ let widgetMode = DEFAULT_WIDGET_MODE;
374
375
  let widgetModeInitialized = false;
375
376
  let widgetModePreferencePath = null;
376
377
  function safeReadTextFile(path) {
@@ -473,10 +474,19 @@ export function getWidgetMode(projectPath, globalPath) {
473
474
  }
474
475
  /** Test-only reset for widget mode caching. */
475
476
  export function _resetWidgetModeForTests() {
476
- widgetMode = "full";
477
+ widgetMode = DEFAULT_WIDGET_MODE;
477
478
  widgetModeInitialized = false;
478
479
  widgetModePreferencePath = null;
479
480
  }
481
+ function clearAutoOutcomeWidget(ctx) {
482
+ if (!ctx.hasUI)
483
+ return;
484
+ ctx.ui.setWidget("gsd-outcome", undefined);
485
+ }
486
+ export function setAutoActiveStatus(ctx, status) {
487
+ ctx.ui.setStatus("gsd-auto", status);
488
+ clearAutoOutcomeWidget(ctx);
489
+ }
480
490
  export function updateProgressWidget(ctx, unitType, unitId, state, accessors, tierBadge) {
481
491
  if (!ctx.hasUI)
482
492
  return;
@@ -495,7 +505,7 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
495
505
  ctx.ui.setStatus("gsd-step", undefined);
496
506
  }
497
507
  if (!accessors.isSessionSwitching()) {
498
- ctx.ui.setWidget("gsd-outcome", undefined);
508
+ clearAutoOutcomeWidget(ctx);
499
509
  }
500
510
  const verb = unitVerb(unitType);
501
511
  const phaseLabel = unitPhaseLabel(unitType);
@@ -548,6 +558,7 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
548
558
  logWarning("dashboard", `DB status update failed: ${err instanceof Error ? err.message : String(err)}`);
549
559
  }
550
560
  }, 15_000);
561
+ progressRefreshTimer.unref?.();
551
562
  return {
552
563
  render(width) {
553
564
  if (cachedLines && cachedWidth === width)
@@ -789,7 +800,7 @@ export function setCompletionProgressWidget(ctx, snapshot) {
789
800
  if (!ctx.hasUI)
790
801
  return;
791
802
  const widgetKey = "gsd-progress";
792
- ctx.ui.setWidget("gsd-outcome", undefined);
803
+ clearAutoOutcomeWidget(ctx);
793
804
  if (typeof ctx.ui?.setHeader === "function") {
794
805
  ctx.ui.setHeader(() => ({
795
806
  render() { return []; },
@@ -30,6 +30,7 @@ import { nativeHasChanges, nativeIsRepo, _resetHasChangesCache } from "./native-
30
30
  import { debugLog, isDebugEnabled } from "./debug-logger.js";
31
31
  import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
32
32
  import { resolveWorktreeProjectRoot } from "./worktree-root.js";
33
+ import { detectWorktreeName } from "./worktree.js";
33
34
  import { probeGitConflictState } from "./git-conflict-state.js";
34
35
  import { runTurnGitAction } from "./git-service.js";
35
36
  import { parseUnitId } from "./unit-id.js";
@@ -178,6 +179,24 @@ function missingSliceStop(mid, phase) {
178
179
  function isRegistryMilestoneComplete(state, mid) {
179
180
  return state.registry.some((milestone) => milestone.id === mid && milestone.status === "complete");
180
181
  }
182
+ function normalizeMilestoneScope(value) {
183
+ const trimmed = value?.trim();
184
+ if (!trimmed || !MILESTONE_ID_RE.test(trimmed))
185
+ return null;
186
+ return trimmed;
187
+ }
188
+ function resolveDispatchMilestoneScope(ctx) {
189
+ const sessionMilestone = normalizeMilestoneScope(ctx.session?.currentMilestoneId);
190
+ if (sessionMilestone)
191
+ return { id: sessionMilestone, source: "session.currentMilestoneId" };
192
+ const sessionWorktree = normalizeMilestoneScope(ctx.session?.basePath ? detectWorktreeName(ctx.session.basePath) : null);
193
+ if (sessionWorktree)
194
+ return { id: sessionWorktree, source: "session.basePath worktree" };
195
+ const baseWorktree = normalizeMilestoneScope(detectWorktreeName(ctx.basePath));
196
+ if (baseWorktree)
197
+ return { id: baseWorktree, source: "basePath worktree" };
198
+ return null;
199
+ }
181
200
  function hasMilestonePassedDiscuss(basePath, mid) {
182
201
  if (isDbAvailable()) {
183
202
  try {
@@ -1351,6 +1370,17 @@ import { getRegistry } from "./rule-registry.js";
1351
1370
  * resolveDispatch directly without registry initialization).
1352
1371
  */
1353
1372
  export async function resolveDispatch(ctx) {
1373
+ if (ctx.mid && isDbAvailable()) {
1374
+ const milestone = getMilestone(ctx.mid);
1375
+ if (milestone && isClosedStatus(milestone.status)) {
1376
+ return {
1377
+ action: "stop",
1378
+ reason: `Milestone ${ctx.mid} is closed (status: ${milestone.status}); auto-mode will not reopen or recover it implicitly. ` +
1379
+ "Use an explicit reopen command before planning or executing more work for this milestone.",
1380
+ level: "warning",
1381
+ };
1382
+ }
1383
+ }
1354
1384
  const activeMid = ctx.state.activeMilestone?.id;
1355
1385
  if (activeMid && ctx.mid !== activeMid) {
1356
1386
  return {
@@ -1360,6 +1390,15 @@ export async function resolveDispatch(ctx) {
1360
1390
  level: "warning",
1361
1391
  };
1362
1392
  }
1393
+ const scopedMilestone = resolveDispatchMilestoneScope(ctx);
1394
+ if (scopedMilestone && ctx.mid !== scopedMilestone.id) {
1395
+ return {
1396
+ action: "stop",
1397
+ reason: `Dispatch milestone mismatch: context mid "${ctx.mid}" does not match ${scopedMilestone.source} "${scopedMilestone.id}". ` +
1398
+ "The active worktree/session and derived project state disagree; recover, park, or discard the stranded milestone before continuing.",
1399
+ level: "warning",
1400
+ };
1401
+ }
1363
1402
  // Delegate to registry when available
1364
1403
  try {
1365
1404
  const registry = getRegistry();
@@ -323,6 +323,41 @@ function stripKnownIdPrefix(value, id) {
323
323
  return raw.slice(id.length + 1).trim() || undefined;
324
324
  return raw;
325
325
  }
326
+ function parseReactiveBatchTaskIds(unitId) {
327
+ const { task: batchPart } = parseUnitId(unitId);
328
+ if (!batchPart?.startsWith("reactive+"))
329
+ return [];
330
+ const rawIds = batchPart
331
+ .slice("reactive+".length)
332
+ .split(",")
333
+ .map((taskId) => taskId.trim().toUpperCase())
334
+ .filter(Boolean);
335
+ const unique = new Set();
336
+ for (const taskId of rawIds) {
337
+ unique.add(taskId);
338
+ }
339
+ return [...unique];
340
+ }
341
+ function dedupePaths(values) {
342
+ const seen = new Set();
343
+ const result = [];
344
+ for (const value of values) {
345
+ if (!seen.has(value)) {
346
+ seen.add(value);
347
+ result.push(value);
348
+ }
349
+ }
350
+ return result;
351
+ }
352
+ function getPlannedKeyFiles(tasks) {
353
+ return dedupePaths(tasks.flatMap((taskRow) => [
354
+ ...(taskRow.expected_output ?? []),
355
+ ...(taskRow.files ?? []),
356
+ ...(taskRow.key_files ?? []),
357
+ ]));
358
+ }
359
+ export const _parseReactiveBatchTaskIdsForTest = parseReactiveBatchTaskIds;
360
+ export const _getPlannedKeyFilesForTest = getPlannedKeyFiles;
326
361
  function resolveVerificationFailureMarkerPath(unitType, unitId, basePath) {
327
362
  const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
328
363
  switch (unitType) {
@@ -402,6 +437,34 @@ async function buildTaskCommitContextForUnit(basePath, unitId) {
402
437
  issueNumber: ghIssueNumber,
403
438
  };
404
439
  }
440
+ async function buildReactiveTaskCommitContext(_basePath, unitId) {
441
+ const { milestone: mid, slice: sid } = parseUnitId(unitId);
442
+ if (!mid || !sid || !isDbAvailable())
443
+ return undefined;
444
+ const batchTaskIds = parseReactiveBatchTaskIds(unitId);
445
+ if (batchTaskIds.length === 0)
446
+ return undefined;
447
+ const milestone = getMilestone(mid);
448
+ const slice = getSlice(mid, sid);
449
+ const taskRows = batchTaskIds
450
+ .map((tid) => getTask(mid, sid, tid))
451
+ .filter((taskRow) => taskRow !== null);
452
+ const keyFiles = getPlannedKeyFiles(taskRows);
453
+ if (taskRows.length === 0 || keyFiles.length === 0)
454
+ return undefined;
455
+ const taskLabel = taskRows.map((row) => row.id).join(",");
456
+ return {
457
+ taskId: `${sid}/${taskLabel}`,
458
+ taskDisplayId: "reactive-batch",
459
+ taskTitle: `Reactive batch: ${taskLabel}`,
460
+ milestoneId: mid,
461
+ milestoneTitle: stripKnownIdPrefix(milestone?.title, mid),
462
+ sliceId: sid,
463
+ sliceTitle: stripKnownIdPrefix(slice?.title, sid),
464
+ oneLiner: `Reactive execute for ${taskLabel}`,
465
+ keyFiles,
466
+ };
467
+ }
405
468
  async function runPostUnitGitHubSyncIfNeeded(basePath, unit) {
406
469
  if (unit.type === "complete-milestone")
407
470
  return;
@@ -761,6 +824,9 @@ export async function autoCommitUnit(basePath, unitType, unitId, ctx) {
761
824
  if (unitType === "execute-task") {
762
825
  taskContext = await buildTaskCommitContextForUnit(basePath, unitId);
763
826
  }
827
+ else if (unitType === "reactive-execute") {
828
+ taskContext = await buildReactiveTaskCommitContext(basePath, unitId);
829
+ }
764
830
  _resetHasChangesCache();
765
831
  if (LIFECYCLE_ONLY_UNITS.has(unitType)) {
766
832
  return null;
@@ -812,6 +878,22 @@ async function runCloseoutGitAction(pctx, unit, opts) {
812
878
  targetRepositories = getTask(mid, sid, tid)?.target_repositories;
813
879
  }
814
880
  }
881
+ else if (turnAction === "commit" && unit.type === "reactive-execute") {
882
+ taskContext = await buildReactiveTaskCommitContext(s.basePath, unit.id);
883
+ const { milestone: mid, slice: sid } = parseUnitId(unit.id);
884
+ if (mid && sid && isDbAvailable()) {
885
+ const repositories = new Set();
886
+ for (const tid of parseReactiveBatchTaskIds(unit.id)) {
887
+ const taskRow = getTask(mid, sid, tid);
888
+ for (const repoId of taskRow?.target_repositories ?? []) {
889
+ repositories.add(repoId);
890
+ }
891
+ }
892
+ if (repositories.size > 0) {
893
+ targetRepositories = [...repositories];
894
+ }
895
+ }
896
+ }
815
897
  // Invalidate the nativeHasChanges cache before auto-commit (#1853).
816
898
  // The cache has a 10-second TTL and is keyed by basePath. A stale
817
899
  // `false` result causes autoCommit to skip staging entirely.
@@ -1206,12 +1288,19 @@ export async function postUnitPreVerification(pctx, opts) {
1206
1288
  if (safetyConfig.enabled) {
1207
1289
  const { milestone: sMid, slice: sSid, task: sTid } = parseUnitId(s.currentUnit.id);
1208
1290
  // File change validation (execute-task only, after unit execution)
1209
- if (safetyConfig.file_change_validation && s.currentUnit.type === "execute-task" && sMid && sSid && sTid && isDbAvailable()) {
1291
+ if (safetyConfig.file_change_validation && s.currentUnit.type === "execute-task" && sMid && sSid && sTid) {
1210
1292
  try {
1211
- const taskRow = getTask(sMid, sSid, sTid);
1212
- if (taskRow) {
1213
- const expectedOutput = taskRow.expected_output ?? [];
1214
- const plannedFiles = taskRow.files ?? [];
1293
+ const sliceTaskRows = isDbAvailable()
1294
+ ? getSliceTasks(sMid, sSid).filter((t) => isClosedStatus(t.status) || t.id === sTid)
1295
+ : [];
1296
+ if (sliceTaskRows.length > 0) {
1297
+ const expectedOutput = getPlannedKeyFiles(sliceTaskRows.map((taskRow) => ({
1298
+ expected_output: taskRow.expected_output,
1299
+ files: taskRow.files,
1300
+ })));
1301
+ const plannedFiles = getPlannedKeyFiles(sliceTaskRows.map((taskRow) => ({
1302
+ files: taskRow.files,
1303
+ })));
1215
1304
  const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles, safetyConfig.file_change_allowlist);
1216
1305
  if (audit && audit.violations.length > 0) {
1217
1306
  const warnings = audit.violations.filter(v => v.severity === "warning");
@@ -1223,6 +1312,23 @@ export async function postUnitPreVerification(pctx, opts) {
1223
1312
  }
1224
1313
  }
1225
1314
  }
1315
+ else {
1316
+ const taskRow = getTask(sMid, sSid, sTid);
1317
+ if (taskRow) {
1318
+ const expectedOutput = taskRow.expected_output ?? [];
1319
+ const plannedFiles = taskRow.files ?? [];
1320
+ const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles, safetyConfig.file_change_allowlist);
1321
+ if (audit && audit.violations.length > 0) {
1322
+ const warnings = audit.violations.filter(v => v.severity === "warning");
1323
+ for (const v of warnings) {
1324
+ logWarning("safety", `file-change: ${v.file} — ${v.reason}`);
1325
+ }
1326
+ if (warnings.length > 0) {
1327
+ ctx.ui.notify(`Safety: ${warnings.length} unexpected file change(s) outside task plan`, "warning");
1328
+ }
1329
+ }
1330
+ }
1331
+ }
1226
1332
  }
1227
1333
  catch (e) {
1228
1334
  debugLog("postUnit", { phase: "safety-file-change", error: String(e) });
@@ -1781,8 +1887,8 @@ export async function postUnitPostVerification(pctx) {
1781
1887
  }
1782
1888
  catch (dbErr) {
1783
1889
  // DB unavailable — fail explicitly rather than silently reverting to markdown mutation.
1784
- // Use 'gsd recover' to rebuild DB state from disk if needed.
1785
- logError("engine", `retry state-reset failed (DB unavailable): ${dbErr.message}. Run 'gsd recover' to reconcile.`);
1890
+ // Use 'gsd recover --confirm' to import markdown into the DB if needed.
1891
+ logError("engine", `retry state-reset failed (DB unavailable): ${dbErr.message}. Run 'gsd recover --confirm' to import markdown into the DB.`);
1786
1892
  }
1787
1893
  }
1788
1894
  // 2. Delete SUMMARY.md for the task
@@ -2351,6 +2351,15 @@ export async function buildCompleteSlicePrompt(mid, midTitle, sid, sTitle, base,
2351
2351
  sliceSummaryPath,
2352
2352
  sliceUatPath,
2353
2353
  gatesToClose,
2354
+ skillActivation: buildSkillActivationBlock({
2355
+ base,
2356
+ milestoneId: mid,
2357
+ milestoneTitle: midTitle,
2358
+ sliceId: sid,
2359
+ sliceTitle: sTitle,
2360
+ extraContext: [inlinedContext],
2361
+ unitType: "complete-slice",
2362
+ }),
2354
2363
  });
2355
2364
  }
2356
2365
  export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
@@ -802,7 +802,7 @@ export function buildLoopRemediationSteps(unitType, unitId, base) {
802
802
  return [
803
803
  ` 1. Run \`gsd undo-task ${mid}/${sid}/${tid}\` to reset the task state`,
804
804
  ` 2. Resume auto-mode — it will re-execute the task`,
805
- ` 3. If the task keeps failing, run \`gsd recover\` to rebuild DB state from disk`,
805
+ ` 3. If the task keeps failing and markdown should repopulate the DB, run \`gsd recover --confirm\``,
806
806
  ].join("\n");
807
807
  }
808
808
  case "plan-slice":
@@ -814,7 +814,7 @@ export function buildLoopRemediationSteps(unitType, unitId, base) {
814
814
  : relSliceFile(base, mid, sid, "RESEARCH");
815
815
  return [
816
816
  ` 1. Write ${artifactRel} manually (or with the LLM in interactive mode)`,
817
- ` 2. Run \`gsd recover\` to rebuild DB state from disk`,
817
+ ` 2. Run \`gsd recover --confirm\` to import the markdown into the DB`,
818
818
  ` 3. Resume auto-mode`,
819
819
  ].join("\n");
820
820
  }
@@ -824,7 +824,7 @@ export function buildLoopRemediationSteps(unitType, unitId, base) {
824
824
  return [
825
825
  ` 1. Run \`gsd reset-slice ${mid}/${sid}\` to reset the slice and all its tasks`,
826
826
  ` 2. Resume auto-mode — it will re-execute incomplete tasks and re-complete the slice`,
827
- ` 3. If the slice keeps failing, run \`gsd recover\` to rebuild DB state from disk`,
827
+ ` 3. If the slice keeps failing and markdown should repopulate the DB, run \`gsd recover --confirm\``,
828
828
  ].join("\n");
829
829
  }
830
830
  case "validate-milestone": {
@@ -833,7 +833,7 @@ export function buildLoopRemediationSteps(unitType, unitId, base) {
833
833
  const artifactRel = relMilestoneFile(base, mid, "VALIDATION");
834
834
  return [
835
835
  ` 1. Write ${artifactRel} with verdict: pass`,
836
- ` 2. Run \`gsd recover\` to rebuild DB state from disk`,
836
+ ` 2. Run \`gsd recover --confirm\` to import the markdown into the DB`,
837
837
  ` 3. Resume auto-mode`,
838
838
  ].join("\n");
839
839
  }