@opengsd/gsd-pi 1.2.0-dev.9ad8ae33 → 1.2.0-dev.a6376d75

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 (264) hide show
  1. package/dist/cli-model-override.d.ts +15 -0
  2. package/dist/cli-model-override.js +21 -0
  3. package/dist/cli.js +1 -18
  4. package/dist/loader.js +6 -4
  5. package/dist/register-agent-bundles.d.ts +11 -2
  6. package/dist/register-agent-bundles.js +18 -4
  7. package/dist/resources/.managed-resources-content-hash +1 -1
  8. package/dist/resources/extensions/ask-user-questions.js +3 -2
  9. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +447 -215
  10. package/dist/resources/extensions/claude-code-cli/turn-assembler.js +33 -1
  11. package/dist/resources/extensions/gsd/auto/closeout.js +215 -0
  12. package/dist/resources/extensions/gsd/auto/dispatch-history.js +21 -6
  13. package/dist/resources/extensions/gsd/auto/dispatch.js +365 -0
  14. package/dist/resources/extensions/gsd/auto/finalize.js +347 -0
  15. package/dist/resources/extensions/gsd/auto/loop.js +4 -1
  16. package/dist/resources/extensions/gsd/auto/milestone-lease-reclaim.js +56 -0
  17. package/dist/resources/extensions/gsd/auto/orchestrator.js +85 -15
  18. package/dist/resources/extensions/gsd/auto/phase-helpers.js +146 -0
  19. package/dist/resources/extensions/gsd/auto/phases.js +17 -2372
  20. package/dist/resources/extensions/gsd/auto/pre-dispatch.js +534 -0
  21. package/dist/resources/extensions/gsd/auto/unit-phase.js +694 -0
  22. package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +1 -1
  23. package/dist/resources/extensions/gsd/auto/worktree-safety-phase.js +125 -0
  24. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  25. package/dist/resources/extensions/gsd/auto.js +15 -1
  26. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +37 -7
  27. package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -2
  28. package/dist/resources/extensions/gsd/commands-workflow-templates.js +9 -2
  29. package/dist/resources/extensions/gsd/db/queries.js +30 -0
  30. package/dist/resources/extensions/gsd/doctor-environment.js +256 -125
  31. package/dist/resources/extensions/gsd/guided-flow.js +88 -2
  32. package/dist/resources/extensions/gsd/health-widget.js +87 -28
  33. package/dist/resources/extensions/gsd/mcp-bridge.js +10 -0
  34. package/dist/resources/extensions/gsd/milestone-settlement.js +2 -2
  35. package/dist/resources/extensions/gsd/notifications.js +12 -7
  36. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -1
  38. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -0
  39. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -1
  40. package/dist/resources/extensions/gsd/skill-activation.js +3 -6
  41. package/dist/resources/extensions/gsd/state.js +6 -2
  42. package/dist/resources/extensions/gsd/tool-surface-readiness.js +83 -31
  43. package/dist/resources/extensions/gsd/tools/complete-task.js +62 -0
  44. package/dist/resources/extensions/gsd/unit-context-composer.js +1 -1
  45. package/dist/resources/extensions/gsd/unit-registry.js +34 -4
  46. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +2 -0
  47. package/dist/resources/extensions/gsd/workflow-mcp-readiness-cache.js +105 -0
  48. package/dist/resources/extensions/gsd/worktree-safety.js +28 -26
  49. package/dist/resources/extensions/mcp-client/manager.js +6 -1
  50. package/dist/runtime-checks.d.ts +10 -0
  51. package/dist/runtime-checks.js +27 -0
  52. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  53. package/dist/web/standalone/.next/BUILD_ID +1 -1
  54. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  55. package/dist/web/standalone/.next/build-manifest.json +2 -2
  56. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  57. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.html +1 -1
  74. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  81. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  83. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  84. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  85. package/package.json +2 -2
  86. package/packages/cloud-mcp-gateway/package.json +2 -2
  87. package/packages/contracts/package.json +1 -1
  88. package/packages/daemon/package.json +4 -4
  89. package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
  90. package/packages/gsd-agent-core/dist/sdk.js +6 -4
  91. package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
  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/components/tool-execution.d.ts +8 -0
  98. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  99. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +50 -6
  100. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  101. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +2 -0
  102. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  103. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +34 -5
  104. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  105. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +1 -0
  106. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +12 -0
  108. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
  110. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +4 -0
  111. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
  112. package/packages/gsd-agent-modes/package.json +7 -7
  113. package/packages/mcp-server/README.md +12 -3
  114. package/packages/mcp-server/dist/cli-runner.d.ts +40 -0
  115. package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -0
  116. package/packages/mcp-server/dist/cli-runner.js +137 -0
  117. package/packages/mcp-server/dist/cli-runner.js.map +1 -0
  118. package/packages/mcp-server/dist/cli.js +2 -58
  119. package/packages/mcp-server/dist/cli.js.map +1 -1
  120. package/packages/mcp-server/dist/pid-registry.d.ts +46 -0
  121. package/packages/mcp-server/dist/pid-registry.d.ts.map +1 -0
  122. package/packages/mcp-server/dist/pid-registry.js +452 -0
  123. package/packages/mcp-server/dist/pid-registry.js.map +1 -0
  124. package/packages/mcp-server/dist/probe-mode.d.ts +4 -0
  125. package/packages/mcp-server/dist/probe-mode.d.ts.map +1 -0
  126. package/packages/mcp-server/dist/probe-mode.js +10 -0
  127. package/packages/mcp-server/dist/probe-mode.js.map +1 -0
  128. package/packages/mcp-server/dist/stdio-watchdog.d.ts +8 -0
  129. package/packages/mcp-server/dist/stdio-watchdog.d.ts.map +1 -0
  130. package/packages/mcp-server/dist/stdio-watchdog.js +40 -0
  131. package/packages/mcp-server/dist/stdio-watchdog.js.map +1 -0
  132. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  133. package/packages/mcp-server/dist/workflow-tools.js +62 -43
  134. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  135. package/packages/mcp-server/package.json +5 -5
  136. package/packages/native/package.json +1 -1
  137. package/packages/pi-agent-core/dist/agent-loop.js +43 -2
  138. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  139. package/packages/pi-agent-core/package.json +1 -1
  140. package/packages/pi-ai/package.json +1 -1
  141. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  142. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  144. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  145. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/theme/theme.js +45 -17
  147. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  148. package/packages/pi-coding-agent/package.json +7 -7
  149. package/packages/pi-tui/dist/index.d.ts +1 -1
  150. package/packages/pi-tui/dist/index.d.ts.map +1 -1
  151. package/packages/pi-tui/dist/index.js +1 -1
  152. package/packages/pi-tui/dist/index.js.map +1 -1
  153. package/packages/pi-tui/dist/terminal-image.d.ts +33 -0
  154. package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
  155. package/packages/pi-tui/dist/terminal-image.js +54 -2
  156. package/packages/pi-tui/dist/terminal-image.js.map +1 -1
  157. package/packages/pi-tui/dist/tui.d.ts +8 -0
  158. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  159. package/packages/pi-tui/dist/tui.js +63 -18
  160. package/packages/pi-tui/dist/tui.js.map +1 -1
  161. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  162. package/packages/pi-tui/dist/utils.js +110 -36
  163. package/packages/pi-tui/dist/utils.js.map +1 -1
  164. package/packages/pi-tui/package.json +2 -2
  165. package/packages/rpc-client/package.json +2 -2
  166. package/pkg/dist/theme/theme.d.ts.map +1 -1
  167. package/pkg/dist/theme/theme.js +45 -17
  168. package/pkg/dist/theme/theme.js.map +1 -1
  169. package/pkg/package.json +1 -1
  170. package/src/resources/extensions/ask-user-questions.ts +7 -2
  171. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +531 -226
  172. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +672 -7
  173. package/src/resources/extensions/claude-code-cli/turn-assembler.ts +38 -1
  174. package/src/resources/extensions/gsd/auto/closeout.ts +309 -0
  175. package/src/resources/extensions/gsd/auto/dispatch-history.ts +22 -6
  176. package/src/resources/extensions/gsd/auto/dispatch.ts +449 -0
  177. package/src/resources/extensions/gsd/auto/finalize.ts +445 -0
  178. package/src/resources/extensions/gsd/auto/loop.ts +4 -1
  179. package/src/resources/extensions/gsd/auto/milestone-lease-reclaim.ts +74 -0
  180. package/src/resources/extensions/gsd/auto/orchestrator.ts +95 -15
  181. package/src/resources/extensions/gsd/auto/phase-helpers.ts +199 -0
  182. package/src/resources/extensions/gsd/auto/phases.ts +58 -3061
  183. package/src/resources/extensions/gsd/auto/pre-dispatch.ts +704 -0
  184. package/src/resources/extensions/gsd/auto/unit-phase.ts +910 -0
  185. package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +1 -1
  186. package/src/resources/extensions/gsd/auto/worktree-safety-phase.ts +149 -0
  187. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  188. package/src/resources/extensions/gsd/auto.ts +20 -1
  189. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +56 -6
  190. package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -2
  191. package/src/resources/extensions/gsd/commands-workflow-templates.ts +11 -4
  192. package/src/resources/extensions/gsd/db/queries.ts +29 -0
  193. package/src/resources/extensions/gsd/doctor-environment.ts +267 -142
  194. package/src/resources/extensions/gsd/guided-flow.ts +128 -2
  195. package/src/resources/extensions/gsd/health-widget.ts +91 -27
  196. package/src/resources/extensions/gsd/mcp-bridge.ts +39 -0
  197. package/src/resources/extensions/gsd/milestone-settlement.ts +2 -2
  198. package/src/resources/extensions/gsd/notifications.ts +13 -6
  199. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  200. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -1
  201. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -0
  202. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -1
  203. package/src/resources/extensions/gsd/skill-activation.ts +3 -6
  204. package/src/resources/extensions/gsd/state.ts +7 -1
  205. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +1 -1
  206. package/src/resources/extensions/gsd/tests/auto-blocked-remediation-message.test.ts +1 -1
  207. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +206 -22
  208. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +76 -12
  209. package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +1 -1
  210. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +77 -1
  211. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +2 -1
  212. package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +169 -1
  213. package/src/resources/extensions/gsd/tests/complete-task.test.ts +141 -5
  214. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +2 -1
  215. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +36 -0
  216. package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +55 -0
  217. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +8 -0
  218. package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +117 -91
  219. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +113 -0
  220. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +16 -0
  221. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +15 -0
  222. package/src/resources/extensions/gsd/tests/integration/doctor-environment-async.test.ts +104 -0
  223. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +18 -0
  224. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +47 -16
  225. package/src/resources/extensions/gsd/tests/mcp-readiness-preflight.test.ts +205 -0
  226. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +6 -5
  227. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +1 -1
  228. package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +1 -1
  229. package/src/resources/extensions/gsd/tests/milestone-settlement.test.ts +92 -0
  230. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +1 -1
  231. package/src/resources/extensions/gsd/tests/notifications.test.ts +64 -9
  232. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +2 -2
  233. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +5 -0
  234. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +1 -1
  235. package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +1 -1
  236. package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +3 -3
  237. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -2
  238. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -4
  239. package/src/resources/extensions/gsd/tests/remote-notification-from-desktop.test.ts +31 -81
  240. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -1
  241. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -17
  242. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +7 -3
  243. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +1 -1
  244. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -2
  245. package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +184 -10
  246. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  247. package/src/resources/extensions/gsd/tests/workflow-mcp-readiness-cache.test.ts +119 -0
  248. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +65 -2
  249. package/src/resources/extensions/gsd/tests/workflow-phase-contract-matrix.test.ts +332 -0
  250. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +92 -0
  251. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +1 -1
  252. package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +1 -1
  253. package/src/resources/extensions/gsd/tests/worktree-safety-phase.test.ts +100 -0
  254. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +72 -0
  255. package/src/resources/extensions/gsd/tool-surface-readiness.ts +126 -19
  256. package/src/resources/extensions/gsd/tools/complete-task.ts +87 -0
  257. package/src/resources/extensions/gsd/unit-context-composer.ts +1 -1
  258. package/src/resources/extensions/gsd/unit-registry.ts +34 -4
  259. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -0
  260. package/src/resources/extensions/gsd/workflow-mcp-readiness-cache.ts +150 -0
  261. package/src/resources/extensions/gsd/worktree-safety.ts +41 -39
  262. package/src/resources/extensions/mcp-client/manager.ts +7 -1
  263. /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_buildManifest.js +0 -0
  264. /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_ssgManifest.js +0 -0
@@ -1,10 +1,14 @@
1
1
  // gsd-pi - Claude Code stream adapter regression tests
2
2
  import { describe, test } from "node:test";
3
+ import { clearGuidedUnitContext, setGuidedUnitContext } from "../../gsd/guided-unit-context.ts";
3
4
  import assert from "node:assert/strict";
4
5
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
6
+ import { createRequire } from "node:module";
5
7
  import { join, resolve } from "node:path";
6
8
  import { tmpdir } from "node:os";
9
+ import { pathToFileURL } from "node:url";
7
10
  import {
11
+ streamViaClaudeCode,
8
12
  makeStreamExhaustedErrorMessage,
9
13
  isClaudeCodeAbortErrorMessage,
10
14
  resolveClaudeCodeAbortedMessageText,
@@ -35,12 +39,20 @@ import {
35
39
  roundResultToElicitationContent,
36
40
  autoInitClaudeCodeWorkflowMcp,
37
41
  inferGsdPhaseFromContext,
42
+ resolveGsdPhaseForSdk,
43
+ resolveClaudeCodeToolSurfaceReadinessError,
44
+ resolveClaudeCodeToolSurfaceReadinessRetryDelayMs,
45
+ shouldRetryClaudeCodeToolSurfaceReadiness,
46
+ buildWorkflowMcpReadinessProgressMessage,
47
+ pushWorkflowMcpReadinessProgressEvent,
38
48
  } from "../stream-adapter.ts";
39
49
  import { CLAUDE_CODE_MODELS } from "../models.ts";
40
50
  import type { AssistantMessage, Context, Message } from "@gsd/pi-ai";
41
51
  import type { SDKUserMessage } from "../sdk-types.ts";
42
52
  import { _setAutoActiveForTest } from "../../gsd/auto.ts";
43
53
  import { getInFlightToolCount, hasInteractiveToolInFlight, clearInFlightTools, isInteractiveElicitationInFlight } from "../../gsd/auto-tool-tracking.ts";
54
+ import { clearMcpConfigCache } from "../../mcp-client/manager.ts";
55
+ import { UNIT_TOOL_CONTRACTS } from "../../gsd/unit-tool-contracts.ts";
44
56
 
45
57
  // ---------------------------------------------------------------------------
46
58
  // Env helpers — `GSD_WORKFLOW_MCP_*` save/restore
@@ -356,6 +368,114 @@ describe("stream-adapter — image prompt forwarding (#4183)", () => {
356
368
  parent_tool_use_id: null,
357
369
  });
358
370
  });
371
+
372
+ test("buildSdkQueryPrompt image iterable can be consumed for each SDK retry", async () => {
373
+ const context: Context = {
374
+ messages: [
375
+ {
376
+ role: "user",
377
+ content: [
378
+ { type: "image", data: "ZmFrZQ==", mimeType: "image/jpeg" },
379
+ { type: "text", text: "Retry with this image." },
380
+ ],
381
+ } as Message,
382
+ ],
383
+ };
384
+ const textPrompt = buildPromptFromContext(context);
385
+ const prompt = buildSdkQueryPrompt(context, textPrompt);
386
+
387
+ const firstAttempt = [];
388
+ for await (const item of prompt as AsyncIterable<any>) {
389
+ firstAttempt.push(item);
390
+ }
391
+
392
+ const retryAttempt = [];
393
+ for await (const item of prompt as AsyncIterable<any>) {
394
+ retryAttempt.push(item);
395
+ }
396
+
397
+ assert.equal(firstAttempt.length, 1);
398
+ assert.deepEqual(retryAttempt, firstAttempt);
399
+ });
400
+
401
+ test("SDK readiness retries do not leak partial content into the next attempt", async () => {
402
+ let queryCalls = 0;
403
+ const cwd = mkdtempSync(join(tmpdir(), "claude-sdk-retry-state-"));
404
+ const context: Context = {
405
+ systemPrompt: "UNIT: Run UAT",
406
+ messages: [{ role: "user", content: "Run UAT." } as Message],
407
+ };
408
+ try {
409
+ const stream = streamViaClaudeCode(
410
+ { id: "claude-sonnet-4-6" } as any,
411
+ context,
412
+ {
413
+ cwd,
414
+ _skipWorkflowMcpPreflightForTest: true,
415
+ async *_sdkQueryForTest() {
416
+ queryCalls += 1;
417
+ if (queryCalls === 1) {
418
+ yield {
419
+ type: "stream_event",
420
+ event: { type: "message_start", message: { model: "claude-sonnet-4-6" } },
421
+ parent_tool_use_id: null,
422
+ uuid: "partial-1",
423
+ session_id: "session-1",
424
+ };
425
+ yield {
426
+ type: "stream_event",
427
+ event: { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } },
428
+ parent_tool_use_id: null,
429
+ uuid: "partial-1",
430
+ session_id: "session-1",
431
+ };
432
+ yield {
433
+ type: "stream_event",
434
+ event: { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "stale retry text" } },
435
+ parent_tool_use_id: null,
436
+ uuid: "partial-1",
437
+ session_id: "session-1",
438
+ };
439
+ yield {
440
+ type: "system",
441
+ subtype: "init",
442
+ tools: ["Read"],
443
+ mcp_servers: [{ name: "gsd-workflow", status: "connected" }],
444
+ };
445
+ return;
446
+ }
447
+
448
+ yield {
449
+ type: "result",
450
+ subtype: "success",
451
+ uuid: "result-2",
452
+ session_id: "session-2",
453
+ duration_ms: 1,
454
+ duration_api_ms: 1,
455
+ is_error: false,
456
+ num_turns: 1,
457
+ result: "fresh retry result",
458
+ stop_reason: "end_turn",
459
+ total_cost_usd: 0,
460
+ usage: {
461
+ input_tokens: 0,
462
+ output_tokens: 0,
463
+ cache_read_input_tokens: 0,
464
+ cache_creation_input_tokens: 0,
465
+ },
466
+ };
467
+ },
468
+ } as any,
469
+ );
470
+
471
+ const message = await stream.result();
472
+
473
+ assert.equal(queryCalls, 2);
474
+ assert.deepEqual(message.content, [{ type: "text", text: "fresh retry result" }]);
475
+ } finally {
476
+ rmSync(cwd, { recursive: true, force: true });
477
+ }
478
+ });
359
479
  });
360
480
 
361
481
  // ---------------------------------------------------------------------------
@@ -461,6 +581,33 @@ describe("stream-adapter — no transcript fabrication (#4102)", () => {
461
581
  assert.ok(!prompt.includes("mcp__gsd-workflow__<tool_name>"));
462
582
  });
463
583
 
584
+ test("buildPromptFromContext remaps structured user input to the workflow MCP question tool", () => {
585
+ const context: Context = {
586
+ systemPrompt: "Use ask_user_questions for structured user input.",
587
+ messages: [{ role: "user", content: "Ask the user what comes next" } as Message],
588
+ };
589
+
590
+ const prompt = buildPromptFromContext(context, { workflowMcpServerName: "gsd-workflow" });
591
+
592
+ assert.ok(prompt.includes("mcp__gsd-workflow__ask_user_questions"));
593
+ assert.ok(prompt.includes("Do not call bare ask_user_questions"));
594
+ assert.ok(prompt.includes("Do not call native AskUserQuestion"));
595
+ });
596
+
597
+ test("buildPromptFromContext allows ToolSearch only for deferred workflow MCP hydration", () => {
598
+ const context: Context = {
599
+ messages: [{ role: "user", content: "Plan the slice" } as Message],
600
+ };
601
+
602
+ const prompt = buildPromptFromContext(context, { workflowMcpServerName: "gsd-workflow" });
603
+
604
+ assert.ok(prompt.includes("ToolSearch is available only for Claude Code deferred workflow MCP hydration"));
605
+ assert.ok(prompt.includes("use ToolSearch with select:mcp__gsd-workflow__<tool_name> or the base tool name"));
606
+ assert.ok(prompt.includes("then call the returned MCP tool directly"));
607
+ assert.ok(prompt.includes("Do not use ToolSearch for browser_* tools or general discovery"));
608
+ assert.ok(!prompt.includes("ToolSearch is NOT available"));
609
+ });
610
+
464
611
  test("buildPromptFromContext does not advertise workflow MCP tools when unavailable", () => {
465
612
  const context: Context = {
466
613
  messages: [{ role: "user", content: "Check status" } as Message],
@@ -760,6 +907,91 @@ describe("stream-adapter — Claude Code external tool results", () => {
760
907
  assert.deepEqual(finalContent[1], { type: "text", text: "All done." });
761
908
  });
762
909
 
910
+ test("buildFinalAssistantContent suppresses duplicate empty MCP tool-unavailable failures after same-turn success", () => {
911
+ const finalContent = buildFinalAssistantContent({
912
+ intermediateToolBlocks: [
913
+ {
914
+ type: "toolCall",
915
+ id: "tool-empty-uat",
916
+ name: "gsd_uat_exec",
917
+ arguments: {},
918
+ mcpServer: "gsd-workflow",
919
+ } as any,
920
+ {
921
+ type: "toolCall",
922
+ id: "tool-real-uat",
923
+ name: "gsd_uat_exec",
924
+ arguments: {
925
+ milestoneId: "M004",
926
+ sliceId: "S01",
927
+ checkId: "S01-UAT-01-smoke",
928
+ intent: "uat-runtime-check",
929
+ script: "npx playwright test e2e/priority.spec.js --reporter=line",
930
+ },
931
+ mcpServer: "gsd-workflow",
932
+ } as any,
933
+ ],
934
+ pendingContent: [{ type: "text", text: "UAT S01 complete." }],
935
+ toolResultsById: new Map([
936
+ [
937
+ "tool-empty-uat",
938
+ {
939
+ content: [{
940
+ type: "text",
941
+ text: "<tool_use_error>Error: No such tool available: mcp__gsd-workflow__gsd_uat_exec</tool_use_error>",
942
+ }],
943
+ isError: true,
944
+ },
945
+ ],
946
+ [
947
+ "tool-real-uat",
948
+ {
949
+ content: [{ type: "text", text: "{\"operation\":\"gsd_uat_exec\",\"exit_code\":0}" }],
950
+ isError: false,
951
+ },
952
+ ],
953
+ ]),
954
+ });
955
+
956
+ assert.equal(finalContent.length, 2);
957
+ assert.equal((finalContent[0] as any).id, "tool-real-uat");
958
+ assert.deepEqual((finalContent[0] as any).externalResult, {
959
+ content: [{ type: "text", text: "{\"operation\":\"gsd_uat_exec\",\"exit_code\":0}" }],
960
+ isError: false,
961
+ });
962
+ assert.deepEqual(finalContent[1], { type: "text", text: "UAT S01 complete." });
963
+ });
964
+
965
+ test("buildFinalAssistantContent keeps lone MCP tool-unavailable failures", () => {
966
+ const finalContent = buildFinalAssistantContent({
967
+ intermediateToolBlocks: [
968
+ {
969
+ type: "toolCall",
970
+ id: "tool-empty-uat",
971
+ name: "gsd_uat_exec",
972
+ arguments: {},
973
+ mcpServer: "gsd-workflow",
974
+ } as any,
975
+ ],
976
+ toolResultsById: new Map([
977
+ [
978
+ "tool-empty-uat",
979
+ {
980
+ content: [{
981
+ type: "text",
982
+ text: "<tool_use_error>Error: No such tool available: mcp__gsd-workflow__gsd_uat_exec</tool_use_error>",
983
+ }],
984
+ isError: true,
985
+ },
986
+ ],
987
+ ]),
988
+ });
989
+
990
+ assert.equal(finalContent.length, 1);
991
+ assert.equal((finalContent[0] as any).id, "tool-empty-uat");
992
+ assert.equal((finalContent[0] as any).externalResult.isError, true);
993
+ });
994
+
763
995
  test("buildFinalAssistantContent keeps final-turn tool calls when result arrives without a synthetic user boundary", () => {
764
996
  const finalContent = buildFinalAssistantContent({
765
997
  intermediateToolBlocks: [],
@@ -1053,7 +1285,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
1053
1285
  assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
1054
1286
  assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
1055
1287
  assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
1056
- assert.deepEqual(options.disallowedTools, ["ToolSearch", "AskUserQuestion"]);
1288
+ assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
1057
1289
  assert.deepEqual(options.allowedTools, [
1058
1290
  "Read",
1059
1291
  "Write",
@@ -1173,8 +1405,8 @@ describe("stream-adapter — session persistence (#2859)", () => {
1173
1405
  "plan-milestone must expose exact milestone status helper before ToolSearch is needed",
1174
1406
  );
1175
1407
  assert.ok(
1176
- allowedTools.includes("mcp__gsd-workflow__*"),
1177
- "non-UAT workflow phases keep the wildcard for existing broad workflow behavior",
1408
+ !allowedTools.includes("mcp__gsd-workflow__*"),
1409
+ "strict GSD phases must not rely on a workflow wildcard that can mask missing exact tools",
1178
1410
  );
1179
1411
  assert.ok((options.disallowedTools as string[]).includes("AskUserQuestion"));
1180
1412
  assert.equal(options.strictMcpConfig, true);
@@ -1186,6 +1418,121 @@ describe("stream-adapter — session persistence (#2859)", () => {
1186
1418
  }
1187
1419
  });
1188
1420
 
1421
+ test("buildSdkOptions leaves ToolSearch available for complete-milestone workflow MCP hydration", () => {
1422
+ const restore = setWorkflowMcpEnv({
1423
+ GSD_WORKFLOW_MCP_COMMAND: "node",
1424
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
1425
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
1426
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
1427
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1428
+ });
1429
+ const originalCwd = process.cwd();
1430
+ const emptyDir = mkdtempSync(join(tmpdir(), "claude-mcp-complete-milestone-"));
1431
+ try {
1432
+ process.chdir(emptyDir);
1433
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { gsdPhase: "complete-milestone" });
1434
+ const disallowedTools = options.disallowedTools as string[];
1435
+
1436
+ assert.ok(!disallowedTools.includes("ToolSearch"));
1437
+ assert.ok(disallowedTools.includes("Skill"));
1438
+ assert.ok(disallowedTools.includes("AskUserQuestion"));
1439
+ assert.equal(options.strictMcpConfig, true);
1440
+ } finally {
1441
+ process.chdir(originalCwd);
1442
+ rmSync(emptyDir, { recursive: true, force: true });
1443
+ restore();
1444
+ }
1445
+ });
1446
+
1447
+ test("buildSdkOptions scopes complete-slice away from native write and shell tools", () => {
1448
+ const restore = setWorkflowMcpEnv({
1449
+ GSD_WORKFLOW_MCP_COMMAND: "node",
1450
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
1451
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
1452
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
1453
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1454
+ });
1455
+ const originalCwd = process.cwd();
1456
+ const emptyDir = mkdtempSync(join(tmpdir(), "claude-mcp-complete-slice-"));
1457
+ try {
1458
+ process.chdir(emptyDir);
1459
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { gsdPhase: "complete-slice" });
1460
+ const allowedTools = options.allowedTools as string[];
1461
+
1462
+ assert.ok(allowedTools.includes("Read"));
1463
+ assert.ok(allowedTools.includes("Glob"));
1464
+ assert.ok(allowedTools.includes("Grep"));
1465
+ assert.ok(allowedTools.includes("mcp__gsd-workflow__gsd_exec"));
1466
+ assert.ok(allowedTools.includes("mcp__gsd-workflow__gsd_slice_complete"));
1467
+ assert.ok(!allowedTools.includes("Bash"));
1468
+ assert.ok(!allowedTools.includes("Write"));
1469
+ assert.ok(!allowedTools.includes("Edit"));
1470
+ assert.ok(!allowedTools.includes("mcp__gsd-workflow__*"));
1471
+ } finally {
1472
+ process.chdir(originalCwd);
1473
+ rmSync(emptyDir, { recursive: true, force: true });
1474
+ restore();
1475
+ }
1476
+ });
1477
+
1478
+ test("buildSdkOptions blocks native Skill tool during GSD phases", () => {
1479
+ const restore = setWorkflowMcpEnv({
1480
+ GSD_WORKFLOW_MCP_COMMAND: "node",
1481
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
1482
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
1483
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
1484
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1485
+ });
1486
+ const originalCwd = process.cwd();
1487
+ const emptyDir = mkdtempSync(join(tmpdir(), "claude-mcp-no-native-skill-"));
1488
+ try {
1489
+ process.chdir(emptyDir);
1490
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { gsdPhase: "complete-slice" });
1491
+ const allowedTools = options.allowedTools as string[];
1492
+ const disallowedTools = options.disallowedTools as string[];
1493
+
1494
+ assert.ok(!allowedTools.includes("Skill"));
1495
+ assert.ok(disallowedTools.includes("Skill"));
1496
+ } finally {
1497
+ process.chdir(originalCwd);
1498
+ rmSync(emptyDir, { recursive: true, force: true });
1499
+ restore();
1500
+ }
1501
+ });
1502
+
1503
+ test("buildSdkOptions presents every unit's required workflow MCP tools", () => {
1504
+ const restore = setWorkflowMcpEnv({
1505
+ GSD_WORKFLOW_MCP_COMMAND: "node",
1506
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
1507
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
1508
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
1509
+ });
1510
+ const originalCwd = process.cwd();
1511
+ const emptyDir = mkdtempSync(join(tmpdir(), "claude-mcp-all-units-"));
1512
+ try {
1513
+ process.chdir(emptyDir);
1514
+ for (const [unitType, contract] of Object.entries(UNIT_TOOL_CONTRACTS)) {
1515
+ const requiredTools = contract.requiredWorkflowTools.filter(
1516
+ (tool) => tool.startsWith("gsd_") || tool === "ask_user_questions",
1517
+ );
1518
+ if (requiredTools.length === 0) continue;
1519
+
1520
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { gsdPhase: unitType });
1521
+ const allowedTools = options.allowedTools as string[];
1522
+ for (const toolName of requiredTools) {
1523
+ assert.ok(
1524
+ allowedTools.includes(`mcp__gsd-workflow__${toolName}`) || allowedTools.includes("mcp__gsd-workflow__*"),
1525
+ `${unitType} must allow ${toolName}; allowed=${JSON.stringify(allowedTools)}`,
1526
+ );
1527
+ }
1528
+ }
1529
+ } finally {
1530
+ process.chdir(originalCwd);
1531
+ rmSync(emptyDir, { recursive: true, force: true });
1532
+ restore();
1533
+ }
1534
+ });
1535
+
1189
1536
  test("inferGsdPhaseFromContext recognizes non-UAT unit prompts", () => {
1190
1537
  const context = {
1191
1538
  messages: [
@@ -1196,6 +1543,53 @@ describe("stream-adapter — session persistence (#2859)", () => {
1196
1543
  assert.equal(inferGsdPhaseFromContext(context), "plan-milestone");
1197
1544
  });
1198
1545
 
1546
+ test("inferGsdPhaseFromContext recognizes discuss-slice and refine-slice prompts", () => {
1547
+ const discussSlice = {
1548
+ messages: [{ role: "user", content: "Discuss slice S001 in milestone M001." }],
1549
+ } as Context;
1550
+ const refineSlice = {
1551
+ messages: [{ role: "user", content: "## UNIT: Refine Slice S001 (\"Auth\") - Milestone M001" }],
1552
+ } as Context;
1553
+
1554
+ assert.equal(inferGsdPhaseFromContext(discussSlice), "discuss-slice");
1555
+ assert.equal(inferGsdPhaseFromContext(refineSlice), "refine-slice");
1556
+ });
1557
+
1558
+ test("resolveGsdPhaseForSdk prefers guided unit context over prompt inference", () => {
1559
+ const projectRoot = "/tmp/gsd-guided-phase-project";
1560
+ clearGuidedUnitContext();
1561
+ setGuidedUnitContext(projectRoot, "discuss-slice");
1562
+ try {
1563
+ const context = {
1564
+ messages: [{ role: "user", content: "Generic workflow task with no phase slug." }],
1565
+ } as Context;
1566
+ assert.equal(resolveGsdPhaseForSdk(context, projectRoot), "discuss-slice");
1567
+ } finally {
1568
+ clearGuidedUnitContext(projectRoot);
1569
+ }
1570
+ });
1571
+
1572
+ test("resolveGsdPhaseForSdk matches guided context across milestone worktrees", () => {
1573
+ const projectRoot = "/tmp/gsd-guided-phase-root";
1574
+ const worktreeRoot = `${projectRoot}/.gsd/worktrees/m001-wt`;
1575
+ clearGuidedUnitContext();
1576
+ setGuidedUnitContext(worktreeRoot, "refine-slice");
1577
+ try {
1578
+ const context = { messages: [{ role: "user", content: "No UNIT header here." }] } as Context;
1579
+ assert.equal(resolveGsdPhaseForSdk(context, projectRoot), "refine-slice");
1580
+ } finally {
1581
+ clearGuidedUnitContext(worktreeRoot);
1582
+ }
1583
+ });
1584
+
1585
+ test("resolveGsdPhaseForSdk falls back to prompt inference when guided context is absent", () => {
1586
+ clearGuidedUnitContext();
1587
+ const context = {
1588
+ messages: [{ role: "user", content: "## UNIT: Run UAT — M001/S001" }],
1589
+ } as Context;
1590
+ assert.equal(resolveGsdPhaseForSdk(context, "/tmp/unrelated-project"), "run-uat");
1591
+ });
1592
+
1199
1593
  test("buildSdkOptions presents ask_user_questions for discuss phases", () => {
1200
1594
  const restore = setWorkflowMcpEnv({
1201
1595
  GSD_WORKFLOW_MCP_COMMAND: "node",
@@ -1245,7 +1639,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
1245
1639
  const mcpServers = options.mcpServers as Record<string, any>;
1246
1640
  assert.ok(mcpServers?.["custom-workflow"], "expected custom workflow server config");
1247
1641
  assert.ok(mcpServers?.["gsd-browser"], "expected gsd-browser server config");
1248
- assert.deepEqual(options.disallowedTools, ["ToolSearch", "AskUserQuestion"]);
1642
+ assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
1249
1643
  assert.deepEqual(options.allowedTools, [
1250
1644
  "Read",
1251
1645
  "Write",
@@ -1291,7 +1685,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
1291
1685
  if (mcpServers) {
1292
1686
  assert.ok(mcpServers["gsd-workflow"], "if present, must include gsd-workflow");
1293
1687
  assert.ok(mcpServers["gsd-browser"], "if present, must include gsd-browser");
1294
- assert.deepEqual((options as any).disallowedTools, ["ToolSearch", "AskUserQuestion"]);
1688
+ assert.deepEqual((options as any).disallowedTools, ["AskUserQuestion"]);
1295
1689
  } else {
1296
1690
  assert.deepEqual((options as any).disallowedTools, ["ToolSearch"]);
1297
1691
  }
@@ -1333,7 +1727,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
1333
1727
  assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
1334
1728
  assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
1335
1729
  assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, resolvedRepoDir);
1336
- assert.deepEqual(options.disallowedTools, ["ToolSearch", "AskUserQuestion"]);
1730
+ assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
1337
1731
  } finally {
1338
1732
  process.chdir(originalCwd);
1339
1733
  rmSync(repoDir, { recursive: true, force: true });
@@ -1403,7 +1797,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
1403
1797
  const allowedTools = options.allowedTools as string[];
1404
1798
  assert.ok(allowedTools.includes("mcp__gsd-workflow__gsd_plan_milestone"));
1405
1799
  assert.ok(allowedTools.includes("mcp__gsd-workflow__gsd_milestone_status"));
1406
- assert.ok(allowedTools.includes("mcp__gsd-workflow__*"));
1800
+ assert.ok(!allowedTools.includes("mcp__gsd-workflow__*"));
1407
1801
  } finally {
1408
1802
  process.chdir(originalCwd);
1409
1803
  rmSync(projectDir, { recursive: true, force: true });
@@ -1518,6 +1912,277 @@ describe("stream-adapter — session persistence (#2859)", () => {
1518
1912
  });
1519
1913
  });
1520
1914
 
1915
+ describe("stream-adapter — workflow MCP readiness", () => {
1916
+ test("emits visible progress text before workflow MCP readiness waits", () => {
1917
+ const partial: AssistantMessage = {
1918
+ role: "assistant",
1919
+ content: [],
1920
+ api: "anthropic-messages",
1921
+ provider: "claude-code",
1922
+ model: "claude-sonnet-4-6",
1923
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
1924
+ stopReason: "stop",
1925
+ timestamp: Date.now(),
1926
+ };
1927
+ const events: any[] = [];
1928
+ const state = {};
1929
+ const message = buildWorkflowMcpReadinessProgressMessage({
1930
+ unitType: "complete-milestone",
1931
+ workflowServerName: "gsd-workflow",
1932
+ stage: "preflight",
1933
+ });
1934
+
1935
+ pushWorkflowMcpReadinessProgressEvent({
1936
+ stream: { push: (event: any) => events.push(event) } as any,
1937
+ partial,
1938
+ state,
1939
+ message,
1940
+ });
1941
+ const retryMessage = buildWorkflowMcpReadinessProgressMessage({
1942
+ unitType: "complete-milestone",
1943
+ workflowServerName: "gsd-workflow",
1944
+ stage: "retry",
1945
+ attempt: 1,
1946
+ delayMs: 1_000,
1947
+ });
1948
+ pushWorkflowMcpReadinessProgressEvent({
1949
+ stream: { push: (event: any) => events.push(event) } as any,
1950
+ partial,
1951
+ state,
1952
+ message: retryMessage,
1953
+ });
1954
+
1955
+ assert.deepEqual(events.map((event) => event.type), ["text_start", "text_delta", "text_delta"]);
1956
+ assert.match(events[1].delta, /Starting gsd-workflow MCP/);
1957
+ assert.match(events[1].delta, /complete-milestone/);
1958
+ assert.match(events[2].delta, /Still waiting for gsd-workflow MCP tools/);
1959
+ assert.match(events[2].delta, /Retrying in 1s/);
1960
+ assert.deepEqual(partial.content, [{ type: "text", text: `${message}\n${retryMessage}` }]);
1961
+ });
1962
+
1963
+ test("execute-task requires gsd_exec before the model follows verification guidance", async () => {
1964
+ const error = await resolveClaudeCodeToolSurfaceReadinessError({
1965
+ unitType: "execute-task",
1966
+ workflowServerName: "gsd-workflow",
1967
+ observation: {
1968
+ tools: [
1969
+ "Read",
1970
+ "Bash",
1971
+ "mcp__gsd-workflow__gsd_task_complete",
1972
+ "mcp__gsd-workflow__gsd_exec_search",
1973
+ "mcp__gsd-workflow__gsd_resume",
1974
+ "mcp__gsd-workflow__gsd_capture_thought",
1975
+ ],
1976
+ mcpServers: [{ name: "gsd-workflow", status: "connected" }],
1977
+ },
1978
+ });
1979
+
1980
+ assert.match(error ?? "", /gsd_exec/);
1981
+ });
1982
+
1983
+ test("complete-slice requires gsd_exec before the model follows closeout verification guidance", async () => {
1984
+ const error = await resolveClaudeCodeToolSurfaceReadinessError({
1985
+ unitType: "complete-slice",
1986
+ workflowServerName: "gsd-workflow",
1987
+ observation: {
1988
+ tools: [
1989
+ "Read",
1990
+ "mcp__gsd-workflow__gsd_slice_complete",
1991
+ "mcp__gsd-workflow__gsd_task_reopen",
1992
+ "mcp__gsd-workflow__gsd_replan_slice",
1993
+ "mcp__gsd-workflow__gsd_requirement_update",
1994
+ "mcp__gsd-workflow__gsd_summary_save",
1995
+ "mcp__gsd-workflow__gsd_capture_thought",
1996
+ "mcp__gsd-workflow__gsd_exec_search",
1997
+ ],
1998
+ mcpServers: [{ name: "gsd-workflow", status: "connected" }],
1999
+ },
2000
+ });
2001
+
2002
+ assert.match(error ?? "", /gsd_exec/);
2003
+ });
2004
+
2005
+ test("complete-slice requires workflow MCP memory capture before closeout guidance can use it", async () => {
2006
+ const error = await resolveClaudeCodeToolSurfaceReadinessError({
2007
+ unitType: "complete-slice",
2008
+ workflowServerName: "gsd-workflow",
2009
+ observation: {
2010
+ tools: [
2011
+ "Read",
2012
+ "capture_thought",
2013
+ "mcp__gsd-workflow__gsd_exec",
2014
+ "mcp__gsd-workflow__gsd_slice_complete",
2015
+ "mcp__gsd-workflow__gsd_task_reopen",
2016
+ "mcp__gsd-workflow__gsd_replan_slice",
2017
+ "mcp__gsd-workflow__gsd_requirement_update",
2018
+ "mcp__gsd-workflow__gsd_summary_save",
2019
+ ],
2020
+ mcpServers: [{ name: "gsd-workflow", status: "connected" }],
2021
+ },
2022
+ });
2023
+
2024
+ assert.match(error ?? "", /gsd_capture_thought/);
2025
+ });
2026
+
2027
+ test("terminal init surface remains not ready even when configured workflow MCP probes available", async () => {
2028
+ const previousGsdHome = process.env.GSD_HOME;
2029
+ const projectRoot = realpathSync(mkdtempSync(join(tmpdir(), "claude-sdk-mcp-pending-ready-")));
2030
+ const gsdHomeDir = realpathSync(mkdtempSync(join(tmpdir(), "claude-sdk-mcp-pending-home-")));
2031
+ try {
2032
+ process.env.GSD_HOME = gsdHomeDir;
2033
+
2034
+ const require = createRequire(import.meta.url);
2035
+ const mcpModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/mcp.js")).href;
2036
+ const stdioModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/stdio.js")).href;
2037
+ const serverPath = join(projectRoot, "fake-workflow-mcp-server.mjs");
2038
+ writeFileSync(
2039
+ serverPath,
2040
+ [
2041
+ `const { McpServer } = await import(${JSON.stringify(mcpModuleUrl)});`,
2042
+ `const { StdioServerTransport } = await import(${JSON.stringify(stdioModuleUrl)});`,
2043
+ 'const server = new McpServer({ name: "fake", version: "1.0.0" }, { capabilities: { tools: {} } });',
2044
+ 'server.tool("gsd_plan_slice", "Plan slice", {}, async () => ({ content: [{ type: "text", text: "ok" }] }));',
2045
+ 'server.tool("gsd_reassess_roadmap", "Reassess roadmap", {}, async () => ({ content: [{ type: "text", text: "ok" }] }));',
2046
+ 'await server.connect(new StdioServerTransport());',
2047
+ ].join("\n"),
2048
+ "utf-8",
2049
+ );
2050
+ writeFileSync(
2051
+ join(projectRoot, ".mcp.json"),
2052
+ JSON.stringify({ mcpServers: { "gsd-workflow": { command: process.execPath, args: [serverPath] } } }),
2053
+ "utf-8",
2054
+ );
2055
+
2056
+ const error = await resolveClaudeCodeToolSurfaceReadinessError({
2057
+ unitType: "plan-slice",
2058
+ workflowServerName: "gsd-workflow",
2059
+ observation: {
2060
+ tools: ["Read", "Bash"],
2061
+ mcpServers: [{ name: "gsd-workflow", status: "failed" }],
2062
+ },
2063
+ });
2064
+
2065
+ assert.match(error ?? "", /status is "failed"/);
2066
+ assert.match(error ?? "", /gsd_plan_slice/);
2067
+ } finally {
2068
+ if (previousGsdHome === undefined) {
2069
+ delete process.env.GSD_HOME;
2070
+ } else {
2071
+ process.env.GSD_HOME = previousGsdHome;
2072
+ }
2073
+ rmSync(projectRoot, { recursive: true, force: true });
2074
+ rmSync(gsdHomeDir, { recursive: true, force: true });
2075
+ clearMcpConfigCache();
2076
+ }
2077
+ });
2078
+
2079
+ test("pending init surface is not accepted until the live Claude session exposes plan-slice tools", async () => {
2080
+ const projectRoot = realpathSync(mkdtempSync(join(tmpdir(), "claude-sdk-mcp-plan-pending-")));
2081
+ try {
2082
+ const require = createRequire(import.meta.url);
2083
+ const mcpModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/mcp.js")).href;
2084
+ const stdioModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/stdio.js")).href;
2085
+ const serverPath = join(projectRoot, "fake-workflow-mcp-server.mjs");
2086
+ writeFileSync(
2087
+ serverPath,
2088
+ [
2089
+ `const { McpServer } = await import(${JSON.stringify(mcpModuleUrl)});`,
2090
+ `const { StdioServerTransport } = await import(${JSON.stringify(stdioModuleUrl)});`,
2091
+ 'const server = new McpServer({ name: "fake", version: "1.0.0" }, { capabilities: { tools: {} } });',
2092
+ 'server.tool("gsd_plan_slice", "Plan slice", {}, async () => ({ content: [{ type: "text", text: "ok" }] }));',
2093
+ 'server.tool("gsd_reassess_roadmap", "Reassess roadmap", {}, async () => ({ content: [{ type: "text", text: "ok" }] }));',
2094
+ 'await server.connect(new StdioServerTransport());',
2095
+ ].join("\n"),
2096
+ "utf-8",
2097
+ );
2098
+ writeFileSync(
2099
+ join(projectRoot, ".mcp.json"),
2100
+ JSON.stringify({ mcpServers: { "gsd-workflow": { command: process.execPath, args: [serverPath] } } }),
2101
+ "utf-8",
2102
+ );
2103
+
2104
+ const error = await resolveClaudeCodeToolSurfaceReadinessError({
2105
+ unitType: "plan-slice",
2106
+ workflowServerName: "gsd-workflow",
2107
+ projectRoot,
2108
+ observation: {
2109
+ tools: ["Read", "Bash"],
2110
+ mcpServers: [{ name: "gsd-workflow", status: "pending" }],
2111
+ },
2112
+ });
2113
+
2114
+ assert.match(error ?? "", /status is "pending"/);
2115
+ assert.match(error ?? "", /gsd_plan_slice/);
2116
+ } finally {
2117
+ rmSync(projectRoot, { recursive: true, force: true });
2118
+ clearMcpConfigCache();
2119
+ }
2120
+ });
2121
+
2122
+ test("pending init surface can be deferred to Claude Code ToolSearch hydration", async () => {
2123
+ const error = await resolveClaudeCodeToolSurfaceReadinessError({
2124
+ unitType: "plan-slice",
2125
+ workflowServerName: "gsd-workflow",
2126
+ allowPendingToolSearchHydration: true,
2127
+ observation: {
2128
+ tools: ["Read", "Bash"],
2129
+ mcpServers: [{ name: "gsd-workflow", status: "pending" }],
2130
+ },
2131
+ });
2132
+
2133
+ assert.equal(error, null);
2134
+ });
2135
+
2136
+ test("complete-milestone pending init surface defers to Claude Code ToolSearch hydration", async () => {
2137
+ const error = await resolveClaudeCodeToolSurfaceReadinessError({
2138
+ unitType: "complete-milestone",
2139
+ workflowServerName: "gsd-workflow",
2140
+ allowPendingToolSearchHydration: true,
2141
+ observation: {
2142
+ tools: ["Read", "Bash"],
2143
+ mcpServers: [{ name: "gsd-workflow", status: "pending" }],
2144
+ },
2145
+ });
2146
+
2147
+ assert.equal(error, null);
2148
+ });
2149
+
2150
+ test("retries transient readiness errors internally but not terminal MCP failures", () => {
2151
+ const pendingError =
2152
+ 'workflow tool surface not ready for run-uat: MCP server "gsd-workflow" status is "pending" (not yet connected): gsd_uat_exec';
2153
+ const partialError =
2154
+ 'workflow tool surface not ready for run-uat: MCP server "gsd-workflow" is connected but has not registered: gsd_uat_exec';
2155
+ const terminalError =
2156
+ 'workflow tool surface not ready for run-uat: MCP server "gsd-workflow" status is "failed" (terminal) — cannot register: gsd_uat_exec';
2157
+
2158
+ assert.equal(shouldRetryClaudeCodeToolSurfaceReadiness(pendingError), true);
2159
+ assert.equal(shouldRetryClaudeCodeToolSurfaceReadiness(partialError), true);
2160
+ assert.equal(shouldRetryClaudeCodeToolSurfaceReadiness(terminalError), false);
2161
+ assert.equal(
2162
+ resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 0),
2163
+ 500,
2164
+ );
2165
+ assert.equal(
2166
+ resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 1),
2167
+ 1_000,
2168
+ );
2169
+ assert.equal(
2170
+ resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(partialError, 2),
2171
+ 2_000,
2172
+ );
2173
+ assert.equal(resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 3), 4_000);
2174
+ assert.equal(resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 4), 8_000);
2175
+ assert.equal(resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 5), 15_000);
2176
+ assert.equal(resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 6), 15_000);
2177
+ assert.equal(resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 7), 15_000);
2178
+ assert.equal(resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 8), null);
2179
+ assert.equal(resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 0, true), 1_000);
2180
+ assert.equal(resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 9, true), 15_000);
2181
+ assert.equal(resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(pendingError, 10, true), null);
2182
+ assert.equal(resolveClaudeCodeToolSurfaceReadinessRetryDelayMs(terminalError, 0), null);
2183
+ });
2184
+ });
2185
+
1521
2186
  describe("stream-adapter — MCP elicitation bridge", () => {
1522
2187
  const askUserQuestionsRequest = {
1523
2188
  serverName: "gsd-workflow",