@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
@@ -15,12 +15,27 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
15
15
  import { tmpdir } from "node:os";
16
16
  import { join } from "node:path";
17
17
 
18
+ /** Create a temp project root with a git repo for phase-function tests. */
19
+ function makeTestBase(prefix: string): string {
20
+ const base = mkdtempSync(join(tmpdir(), prefix));
21
+ execFileSync("git", ["init", "--initial-branch=main"], { cwd: base, stdio: "ignore" });
22
+ execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
23
+ execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
24
+ writeFileSync(join(base, "README.md"), "# test\n");
25
+ execFileSync("git", ["add", "README.md"], { cwd: base, stdio: "ignore" });
26
+ execFileSync("git", ["commit", "-m", "chore: seed"], { cwd: base, stdio: "ignore" });
27
+ return base;
28
+ }
29
+
18
30
  import type { JournalEntry } from "../journal.js";
19
31
  import type { LoopDeps } from "../auto/loop-deps.js";
20
32
  import { WorktreeStateProjection } from "../worktree-state-projection.js";
21
33
  import type { IterationContext, LoopState, PreDispatchData, IterationData } from "../auto/types.js";
22
34
  import type { SessionLockStatus } from "../session-lock.js";
23
- import { runDispatch, runUnitPhase, runPreDispatch, runFinalize } from "../auto/phases.js";
35
+ import { runDispatch } from "../auto/dispatch.js";
36
+ import { runUnitPhase } from "../auto/unit-phase.js";
37
+ import { runPreDispatch } from "../auto/pre-dispatch.js";
38
+ import { runFinalize } from "../auto/finalize.js";
24
39
  import { readUnitRuntimeRecord } from "../unit-runtime.js";
25
40
  import { ModelPolicyDispatchBlockedError } from "../auto-model-selection.js";
26
41
  import {
@@ -182,7 +197,7 @@ function makeSession() {
182
197
  verbose: false,
183
198
  stepMode: false,
184
199
  paused: false,
185
- basePath: "/tmp/project",
200
+ basePath: makeTestBase("gsd-journal-session-"),
186
201
  originalBasePath: "",
187
202
  currentMilestoneId: "M001",
188
203
  currentUnit: null,
@@ -296,10 +311,18 @@ test("runDispatch emits dispatch-stop when dispatch returns stop action", async
296
311
  assert.equal(stopEvents[0].flowId, ic.flowId);
297
312
  });
298
313
 
299
- test("runDispatch checks prior-slice completion against the project root in worktree mode", async () => {
314
+ test("runDispatch checks prior-slice completion against the project root in worktree mode", async (t) => {
300
315
  const capture = createEventCapture();
301
316
  const guardCalls: Array<{ fn: string; args: unknown[] }> = [];
317
+ const projectRoot = makeTestBase("gsd-wt-prior-slice-");
318
+ const milestoneId = "M029-xoklo9";
319
+ const worktreeRoot = join(projectRoot, ".gsd", "worktrees", milestoneId);
320
+ execFileSync("git", ["worktree", "add", "-b", `auto/${milestoneId}`, worktreeRoot], { cwd: projectRoot, stdio: "ignore" });
321
+ t.after(() => rmSync(projectRoot, { recursive: true, force: true }));
322
+
302
323
  const deps = makeMockDeps(capture, {
324
+ getIsolationMode: () => "worktree",
325
+ autoWorktreeBranch: (mid: string) => `auto/${mid}`,
303
326
  getMainBranch: (basePath: string) => {
304
327
  guardCalls.push({ fn: "getMainBranch", args: [basePath] });
305
328
  return "main";
@@ -320,19 +343,21 @@ test("runDispatch checks prior-slice completion against the project root in work
320
343
  const ic = makeIC(deps, {
321
344
  s: {
322
345
  ...makeSession(),
323
- basePath: "/tmp/project/.gsd/worktrees/M029-xoklo9",
324
- originalBasePath: "/tmp/project",
346
+ basePath: worktreeRoot,
347
+ originalBasePath: projectRoot,
348
+ canonicalProjectRoot: projectRoot,
349
+ currentMilestoneId: milestoneId,
325
350
  } as any,
326
351
  });
327
352
  const preData: PreDispatchData = {
328
353
  state: {
329
354
  phase: "executing",
330
- activeMilestone: { id: "M029-xoklo9", title: "Test", status: "active" },
355
+ activeMilestone: { id: milestoneId, title: "Test", status: "active" },
331
356
  activeSlice: { id: "S01", title: "Slice 1" },
332
- registry: [{ id: "M029-xoklo9", status: "active" }],
357
+ registry: [{ id: milestoneId, status: "active" }],
333
358
  blockers: [],
334
359
  } as any,
335
- mid: "M029-xoklo9",
360
+ mid: milestoneId,
336
361
  midTitle: "Test Milestone",
337
362
  };
338
363
 
@@ -342,12 +367,12 @@ test("runDispatch checks prior-slice completion against the project root in work
342
367
  consecutiveFinalizeTimeouts: 0,
343
368
  });
344
369
 
345
- assert.equal(result.action, "next");
370
+ assert.equal(result.action, "next", "dispatch must proceed under worktree isolation");
346
371
  assert.deepEqual(guardCalls, [
347
- { fn: "getMainBranch", args: ["/tmp/project"] },
372
+ { fn: "getMainBranch", args: [projectRoot] },
348
373
  {
349
374
  fn: "getPriorSliceCompletionBlocker",
350
- args: ["/tmp/project", "main", "execute-task", "M001/S01/T01"],
375
+ args: [projectRoot, "main", "execute-task", "M001/S01/T01"],
351
376
  },
352
377
  ]);
353
378
  });
@@ -434,6 +459,9 @@ test("runDispatch pauses when execute-task artifacts exist but DB status is stil
434
459
  const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
435
460
  const tasksDir = join(sliceDir, "tasks");
436
461
  mkdirSync(tasksDir, { recursive: true });
462
+ execFileSync("git", ["init", "--initial-branch=main"], { cwd: base, stdio: "ignore" });
463
+ execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
464
+ execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
437
465
  openDatabase(join(base, ".gsd", "gsd.db"));
438
466
  insertMilestone({ id: "M001", title: "Test", status: "active" });
439
467
  insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "in_progress" });
@@ -517,6 +545,9 @@ test("runDispatch pauses at Level 2 when execute-task artifacts exist but DB sta
517
545
  const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
518
546
  const tasksDir = join(sliceDir, "tasks");
519
547
  mkdirSync(tasksDir, { recursive: true });
548
+ execFileSync("git", ["init", "--initial-branch=main"], { cwd: base, stdio: "ignore" });
549
+ execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
550
+ execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
520
551
  openDatabase(join(base, ".gsd", "gsd.db"));
521
552
  insertMilestone({ id: "M001", title: "Test", status: "active" });
522
553
  insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "in_progress" });
@@ -888,7 +919,7 @@ test("runUnitPhase increments unitDispatchCount for repeated artifact-missing re
888
919
 
889
920
  test("runUnitPhase pre-dispatch model validation failures do not emit unit-start or dispatch runtime state", async (t) => {
890
921
  const capture = createEventCapture();
891
- const base = mkdtempSync(join(tmpdir(), `gsd-pre-dispatch-block-${randomUUID()}`));
922
+ const base = makeTestBase(`gsd-pre-dispatch-block-${randomUUID()}`);
892
923
  t.after(() => rmSync(base, { recursive: true, force: true }));
893
924
 
894
925
  const deps = makeMockDeps(capture, {
@@ -1084,7 +1115,7 @@ test("terminal event is emitted on blocked state", async () => {
1084
1115
  });
1085
1116
 
1086
1117
  test("#4671: plan-v2 missing CONTEXT.md reaches dispatch recovery instead of pausing", async () => {
1087
- const basePath = mkdtempSync(join(tmpdir(), "gsd-4671-predispatch-"));
1118
+ const basePath = makeTestBase("gsd-4671-predispatch-");
1088
1119
  mkdirSync(join(basePath, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
1089
1120
  openDatabase(join(basePath, ".gsd", "gsd.db"));
1090
1121
  try {
@@ -1141,7 +1172,7 @@ test("#4671: plan-v2 missing CONTEXT.md reaches dispatch recovery instead of pau
1141
1172
  });
1142
1173
 
1143
1174
  test("plan-v2 empty graph rederives state before pausing", async () => {
1144
- const basePath = mkdtempSync(join(tmpdir(), "gsd-plan-v2-empty-graph-"));
1175
+ const basePath = makeTestBase("gsd-plan-v2-empty-graph-");
1145
1176
  mkdirSync(join(basePath, ".gsd", "milestones", "M001"), { recursive: true });
1146
1177
  writeFileSync(
1147
1178
  join(basePath, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
@@ -1204,7 +1235,7 @@ test("plan-v2 empty graph rederives state before pausing", async () => {
1204
1235
  });
1205
1236
 
1206
1237
  test("plan-v2 empty graph pauses after one failed rederive", async () => {
1207
- const basePath = mkdtempSync(join(tmpdir(), "gsd-plan-v2-empty-graph-pause-"));
1238
+ const basePath = makeTestBase("gsd-plan-v2-empty-graph-pause-");
1208
1239
  mkdirSync(join(basePath, ".gsd", "milestones", "M001"), { recursive: true });
1209
1240
  writeFileSync(
1210
1241
  join(basePath, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
@@ -1394,7 +1425,7 @@ test("session-failed cancellations close out and emit unit-end before hard stop"
1394
1425
  test("runFinalize pauses and emits unit-end when pre-verification times out", async () => {
1395
1426
  const capture = createEventCapture();
1396
1427
  let pauseCalls = 0;
1397
- const basePath = mkdtempSync(join(tmpdir(), "gsd-finalize-timeout-"));
1428
+ const basePath = makeTestBase("gsd-finalize-timeout-");
1398
1429
 
1399
1430
  const deps = makeMockDeps(capture, {
1400
1431
  pauseAuto: async () => { pauseCalls++; },
@@ -0,0 +1,205 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Tests for the MCP readiness preflight gate in dispatchWorkflow.
3
+
4
+ import { describe, test, mock } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
7
+ import { createRequire } from "node:module";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { pathToFileURL } from "node:url";
11
+
12
+ import { _awaitWorkflowMcpReadinessForTest as awaitWorkflowMcpReadiness } from "../guided-flow.ts";
13
+ import { clearMcpConfigCache } from "../../mcp-client/manager.ts";
14
+ import { clearWorkflowMcpProbeCache } from "../workflow-mcp-readiness-cache.ts";
15
+
16
+ function makeCtx(opts: { authMode?: string; baseUrl?: string; systemPrompt?: string } = {}) {
17
+ const { authMode = "externalCli", baseUrl = "local://pipe", systemPrompt = "" } = opts;
18
+ return {
19
+ model: { provider: "anthropic", baseUrl },
20
+ modelRegistry: {
21
+ getProviderAuthMode: mock.fn(() => authMode),
22
+ },
23
+ getSystemPrompt: mock.fn(() => systemPrompt),
24
+ ui: {
25
+ setStatus: mock.fn(),
26
+ notify: mock.fn(),
27
+ },
28
+ } as any;
29
+ }
30
+
31
+ function makePi(tools: string[] | (() => string[])) {
32
+ const getTools = typeof tools === "function" ? tools : () => tools;
33
+ return {
34
+ getActiveTools: getTools,
35
+ getAllTools: () => getTools().map((name: string) => ({ name })),
36
+ } as any;
37
+ }
38
+
39
+ describe("awaitWorkflowMcpReadiness", () => {
40
+ test("returns null (ready) when not using MCP transport", async () => {
41
+ clearWorkflowMcpProbeCache();
42
+ const ctx = makeCtx({ authMode: "native", baseUrl: "https://api.anthropic.com" });
43
+ const pi = makePi([]);
44
+ assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path"), null);
45
+ });
46
+
47
+ test("returns null (ready) when provider has no auth mode", async () => {
48
+ const ctx = {
49
+ model: undefined,
50
+ modelRegistry: { getProviderAuthMode: mock.fn(() => undefined) },
51
+ ui: { setStatus: mock.fn(), notify: mock.fn() },
52
+ } as any;
53
+ const pi = makePi([]);
54
+ assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path"), null);
55
+ });
56
+
57
+ test("returns null (ready) when MCP tools are already registered", async () => {
58
+ const ctx = makeCtx();
59
+ const pi = makePi(["read", "bash", "mcp__gsd-workflow__ask_user_questions", "mcp__gsd-workflow__gsd_plan_slice"]);
60
+ assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path"), null);
61
+ });
62
+
63
+ test("returns null (ready) when host system prompt advertises workflow MCP tools", async () => {
64
+ const originalCommand = process.env.GSD_WORKFLOW_MCP_COMMAND;
65
+ const ctx = makeCtx({
66
+ systemPrompt: "Available tools: read, bash, mcp__gsd-workflow__ask_user_questions",
67
+ });
68
+ const pi = makePi(["read", "bash"]);
69
+ try {
70
+ process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
71
+ assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path"), null);
72
+ } finally {
73
+ if (originalCommand === undefined) {
74
+ delete process.env.GSD_WORKFLOW_MCP_COMMAND;
75
+ } else {
76
+ process.env.GSD_WORKFLOW_MCP_COMMAND = originalCommand;
77
+ }
78
+ }
79
+ });
80
+
81
+ test("treats a successful direct probe as readiness when session tools are absent", async () => {
82
+ const previousGsdHome = process.env.GSD_HOME;
83
+ const projectDir = mkdtempSync(join(tmpdir(), "gsd-guided-mcp-probe-project-"));
84
+ const gsdHomeDir = mkdtempSync(join(tmpdir(), "gsd-guided-mcp-probe-home-"));
85
+ try {
86
+ process.env.GSD_HOME = gsdHomeDir;
87
+ mkdirSync(join(projectDir, ".gsd"), { recursive: true });
88
+
89
+ const require = createRequire(import.meta.url);
90
+ const mcpModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/mcp.js")).href;
91
+ const stdioModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/stdio.js")).href;
92
+ const serverPath = join(projectDir, "fake-workflow-mcp-server.mjs");
93
+ writeFileSync(
94
+ serverPath,
95
+ [
96
+ `const { McpServer } = await import(${JSON.stringify(mcpModuleUrl)});`,
97
+ `const { StdioServerTransport } = await import(${JSON.stringify(stdioModuleUrl)});`,
98
+ 'const server = new McpServer({ name: "fake", version: "1.0.0" }, { capabilities: { tools: {} } });',
99
+ 'server.tool("gsd_status", "Probe-visible workflow tool", {}, async () => ({ content: [{ type: "text", text: "ok" }] }));',
100
+ 'await server.connect(new StdioServerTransport());',
101
+ ].join("\n"),
102
+ "utf-8",
103
+ );
104
+ writeFileSync(
105
+ join(projectDir, ".mcp.json"),
106
+ JSON.stringify({ mcpServers: { "gsd-workflow": { command: process.execPath, args: [serverPath] } } }),
107
+ "utf-8",
108
+ );
109
+
110
+ const ctx = makeCtx();
111
+ const pi = makePi(["read", "bash"]);
112
+
113
+ assert.equal(
114
+ await awaitWorkflowMcpReadiness(pi, ctx, projectDir, { timeoutMs: 15_000, pollMs: 100 }),
115
+ null,
116
+ );
117
+ } finally {
118
+ if (previousGsdHome === undefined) {
119
+ delete process.env.GSD_HOME;
120
+ } else {
121
+ process.env.GSD_HOME = previousGsdHome;
122
+ }
123
+ rmSync(projectDir, { recursive: true, force: true });
124
+ rmSync(gsdHomeDir, { recursive: true, force: true });
125
+ clearMcpConfigCache();
126
+ }
127
+ });
128
+
129
+ test("returns server and probe error when workflow MCP never becomes ready", async () => {
130
+ const originalCommand = process.env.GSD_WORKFLOW_MCP_COMMAND;
131
+ const ctx = makeCtx();
132
+ const pi = makePi(["read", "bash"]);
133
+ try {
134
+ process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
135
+ const failure = await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path", {
136
+ timeoutMs: 5,
137
+ pollMs: 1,
138
+ probe: async () => ({
139
+ ok: false,
140
+ tools: [],
141
+ serverName: "gsd-workflow",
142
+ error: "connection refused",
143
+ }),
144
+ });
145
+ assert.deepEqual(failure, { server: "gsd-workflow", error: "connection refused" });
146
+ assert.ok(ctx.ui.setStatus.mock.calls.length >= 2);
147
+ } finally {
148
+ if (originalCommand === undefined) {
149
+ delete process.env.GSD_WORKFLOW_MCP_COMMAND;
150
+ } else {
151
+ process.env.GSD_WORKFLOW_MCP_COMMAND = originalCommand;
152
+ }
153
+ }
154
+ });
155
+
156
+ test("returns null (ready) when MCP tools appear during polling", async () => {
157
+ const ctx = makeCtx();
158
+ let callCount = 0;
159
+ const pi = makePi(() => {
160
+ callCount++;
161
+ if (callCount >= 3) return ["mcp__gsd-workflow__gsd_plan_slice"];
162
+ return ["read", "bash"];
163
+ });
164
+ assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path"), null);
165
+ assert.ok(callCount >= 3, `expected at least 3 tool-check calls, got ${callCount}`);
166
+ assert.ok(ctx.ui.setStatus.mock.calls.length >= 2);
167
+ });
168
+
169
+ test("requires the active unit's workflow tools, not just any MCP workflow tool", async () => {
170
+ const originalCommand = process.env.GSD_WORKFLOW_MCP_COMMAND;
171
+ const ctx = makeCtx();
172
+ const pi = makePi(["read", "bash", "mcp__gsd-workflow__gsd_slice_complete"]);
173
+ try {
174
+ process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
175
+ const failure = await awaitWorkflowMcpReadiness(pi, ctx, "/fake/path", {
176
+ unitType: "complete-slice",
177
+ timeoutMs: 5,
178
+ pollMs: 1,
179
+ probe: async () => ({
180
+ ok: true,
181
+ tools: ["gsd_slice_complete"],
182
+ serverName: "gsd-workflow",
183
+ }),
184
+ });
185
+
186
+ assert.deepEqual(failure, { server: "gsd-workflow" });
187
+ } finally {
188
+ if (originalCommand === undefined) {
189
+ delete process.env.GSD_WORKFLOW_MCP_COMMAND;
190
+ } else {
191
+ process.env.GSD_WORKFLOW_MCP_COMMAND = originalCommand;
192
+ }
193
+ }
194
+ });
195
+
196
+ test("returns null when no launch config is discoverable", async () => {
197
+ const ctx = makeCtx();
198
+ const pi = makePi(["read", "bash"]);
199
+ const launch = (await import("../workflow-mcp.js")).detectWorkflowMcpLaunchConfig(process.cwd());
200
+ if (!launch) {
201
+ assert.equal(await awaitWorkflowMcpReadiness(pi, ctx, process.cwd()), null);
202
+ }
203
+ // When launch config IS found, the function would timeout (15s) — skip that path in tests.
204
+ });
205
+ });
@@ -58,13 +58,14 @@ describe("formatMcpStatusReport", () => {
58
58
  assert.match(result, /disabled/i);
59
59
  });
60
60
 
61
- test("shows available state for servers that pass a status probe", () => {
61
+ test("labels probe-only availability separately from live connections", () => {
62
62
  const servers: McpServerStatus[] = [
63
63
  { name: "gsd-workflow", transport: "stdio", connected: false, available: true, toolCount: 62, error: undefined },
64
64
  ];
65
65
  const result = formatMcpStatusReport(servers);
66
66
  assert.match(result, /gsd-workflow/);
67
- assert.match(result, /available — 62 tools/);
67
+ assert.match(result, /probe available — 62 tools/);
68
+ assert.doesNotMatch(result, /connected — 62 tools/);
68
69
  assert.doesNotMatch(result, /disconnected/);
69
70
  });
70
71
 
@@ -132,7 +133,7 @@ describe("formatMcpServerDetail", () => {
132
133
  assert.match(result, /disconnected/i);
133
134
  });
134
135
 
135
- test("shows available status with tool names", () => {
136
+ test("shows probe-available status with tool names", () => {
136
137
  const result = formatMcpServerDetail({
137
138
  name: "gsd-workflow",
138
139
  transport: "stdio",
@@ -142,7 +143,7 @@ describe("formatMcpServerDetail", () => {
142
143
  tools: ["gsd_milestone_status"],
143
144
  error: undefined,
144
145
  });
145
- assert.match(result, /available/i);
146
+ assert.match(result, /probe available/i);
146
147
  assert.match(result, /gsd_milestone_status/);
147
148
  });
148
149
 
@@ -206,7 +207,7 @@ describe("handleMcpStatus", () => {
206
207
  await handleMcpStatus("status", ctx as unknown as ExtensionCommandContext);
207
208
 
208
209
  assert.match(message, /gsd-workflow/);
209
- assert.match(message, /available — 1 tools/);
210
+ assert.match(message, /probe available — 1 tools/);
210
211
  assert.doesNotMatch(message, /disconnected/);
211
212
  } finally {
212
213
  process.chdir(originalCwd);
@@ -4,7 +4,7 @@
4
4
  import test from "node:test";
5
5
  import assert from "node:assert/strict";
6
6
 
7
- import { _runMilestoneMergeWithStashRestore } from "../auto/phases.js";
7
+ import { _runMilestoneMergeWithStashRestore } from "../auto/closeout.js";
8
8
  import type { IterationContext } from "../auto/types.js";
9
9
  import { MergeConflictError } from "../git-service.js";
10
10
  import type {
@@ -10,7 +10,7 @@
10
10
  import { describe, test } from "node:test";
11
11
  import assert from "node:assert/strict";
12
12
 
13
- import { _resolveDispatchGuardBasePath, _resolveReportBasePath } from "../auto/phases.ts";
13
+ import { _resolveDispatchGuardBasePath, _resolveReportBasePath } from "../auto/phase-helpers.ts";
14
14
 
15
15
  describe("_resolveReportBasePath", () => {
16
16
  test("uses originalBasePath when set (worktree scenario)", () => {
@@ -0,0 +1,92 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Regression tests for milestone closeout settlement guidance.
3
+
4
+ import test, { afterEach } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { execFileSync } from "node:child_process";
7
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { dirname, join } from "node:path";
10
+
11
+ import { evaluateAllCompleteSettlement } from "../milestone-settlement.ts";
12
+ import { resolveExpectedArtifactPath } from "../auto-artifact-paths.ts";
13
+ import {
14
+ closeDatabase,
15
+ insertAssessment,
16
+ insertMilestone,
17
+ insertSlice,
18
+ insertTask,
19
+ openDatabase,
20
+ } from "../gsd-db.ts";
21
+
22
+ let base = "";
23
+
24
+ function runGit(cwd: string, args: string[]): void {
25
+ execFileSync("git", args, { cwd, stdio: "ignore" });
26
+ }
27
+
28
+ function initRepo(root: string): void {
29
+ runGit(root, ["init", "-b", "main"]);
30
+ runGit(root, ["config", "user.email", "test@example.com"]);
31
+ runGit(root, ["config", "user.name", "Test User"]);
32
+ writeFileSync(join(root, "README.md"), "# fixture\n");
33
+ runGit(root, ["add", "."]);
34
+ runGit(root, ["commit", "-m", "chore: init"]);
35
+ }
36
+
37
+ function seedClosedMilestone(root: string, worktree: string): void {
38
+ mkdirSync(join(root, ".gsd"), { recursive: true });
39
+ openDatabase(join(root, ".gsd", "gsd.db"));
40
+ insertMilestone({ id: "M001", title: "Milestone One", status: "complete" });
41
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice One", status: "complete" });
42
+ insertTask({
43
+ id: "T01",
44
+ sliceId: "S01",
45
+ milestoneId: "M001",
46
+ title: "Task One",
47
+ status: "complete",
48
+ verificationResult: "passed",
49
+ });
50
+ insertAssessment({
51
+ path: ".gsd/milestones/M001/M001-VALIDATION.md",
52
+ milestoneId: "M001",
53
+ status: "pass",
54
+ scope: "milestone-validation",
55
+ fullContent: "verdict: pass\n",
56
+ });
57
+
58
+ mkdirSync(join(worktree, ".gsd", "milestones", "M001"), { recursive: true });
59
+ const summaryPath = resolveExpectedArtifactPath("complete-milestone", "M001", worktree);
60
+ assert.ok(summaryPath, "complete-milestone summary path should resolve");
61
+ mkdirSync(dirname(summaryPath), { recursive: true });
62
+ writeFileSync(summaryPath, "# Milestone One\n\nComplete.\n");
63
+ }
64
+
65
+ afterEach(() => {
66
+ try { closeDatabase(); } catch { /* ignore */ }
67
+ if (base) rmSync(base, { recursive: true, force: true });
68
+ base = "";
69
+ });
70
+
71
+ test("merge-pending settlement routes back to complete-milestone dispatch without manual merge guidance", () => {
72
+ base = mkdtempSync(join(tmpdir(), "gsd-milestone-settlement-"));
73
+ initRepo(base);
74
+ const worktree = join(base, ".gsd", "worktrees", "M001");
75
+ mkdirSync(dirname(worktree), { recursive: true });
76
+ runGit(base, ["worktree", "add", "-b", "milestone/M001", worktree, "HEAD"]);
77
+ seedClosedMilestone(base, worktree);
78
+
79
+ const result = evaluateAllCompleteSettlement({
80
+ milestoneId: "M001",
81
+ statePhase: "complete",
82
+ basePath: worktree,
83
+ originalBasePath: base,
84
+ milestoneMerged: false,
85
+ });
86
+
87
+ assert.equal(result.ok, false);
88
+ assert.equal(result.reason, "merge-pending");
89
+ assert.equal(result.nextAction, "Retry `/gsd dispatch complete-milestone M001`.");
90
+ assert.doesNotMatch(result.message, /merge manually/i);
91
+ assert.doesNotMatch(result.nextAction, /merge manually/i);
92
+ });
@@ -14,7 +14,7 @@ import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync
14
14
  import { join } from "node:path";
15
15
  import { tmpdir } from "node:os";
16
16
  import { AutoSession } from "../auto/session.ts";
17
- import { runPreDispatch } from "../auto/phases.ts";
17
+ import { runPreDispatch } from "../auto/pre-dispatch.ts";
18
18
 
19
19
  test("milestone transition archives completed units and rebuilds state", async () => {
20
20
  const tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-cu-reset-")));
@@ -1,7 +1,12 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { readFileSync } from "node:fs";
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
4
6
 
7
+ import { playQuestionBell } from "../../ask-user-questions.js";
8
+ import { stopAuto } from "../auto.js";
9
+ import { autoSession } from "../auto-runtime-state.js";
5
10
  import {
6
11
  buildDesktopNotificationCommand,
7
12
  shouldSendDesktopNotification,
@@ -58,18 +63,68 @@ test("playNotificationBell is silent when disabled", () => {
58
63
  assert.equal(output, "");
59
64
  });
60
65
 
61
- test("ask_user_questions plays local bell before waiting for an answer", () => {
62
- const source = readFileSync(new URL("../../ask-user-questions.ts", import.meta.url), "utf-8");
66
+ test("playQuestionBell writes a terminal bell when local bell is enabled", async () => {
67
+ let output = "";
68
+ const stream = { write: (chunk: string) => { output += chunk; } };
63
69
 
64
- assert.match(source, /playNotificationBell\("question"\)/);
65
- assert.match(source, /await playQuestionBell\(\)/);
70
+ await playQuestionBell({ enabled: true, local_bell: true }, stream);
71
+
72
+ assert.equal(output, "\u0007");
66
73
  });
67
74
 
68
- test("stopAuto plays local bell for auto-mode stop notifications", () => {
69
- const source = readFileSync(new URL("../auto.ts", import.meta.url), "utf-8");
75
+ test("playQuestionBell is silent when local bell is disabled", async () => {
76
+ let output = "";
77
+ const stream = { write: (chunk: string) => { output += chunk; } };
70
78
 
71
- assert.match(source, /import \{[^}]*playNotificationBell[^}]*\} from "\.\/notifications\.js"/);
72
- assert.match(source, /playNotificationBell\("stop", loadedPreferences\?\.notifications\)/);
79
+ await playQuestionBell({ enabled: true, local_bell: false }, stream);
80
+
81
+ assert.equal(output, "");
82
+ });
83
+
84
+ test("stopAuto plays local bell for auto-mode stop notifications", async () => {
85
+ const base = mkdtempSync(join(tmpdir(), "gsd-stop-bell-"));
86
+ const previousCwd = process.cwd();
87
+ const previousStderrWrite = process.stderr.write;
88
+ const previousStderrIsTTY = process.stderr.isTTY;
89
+ let bellOutput = "";
90
+
91
+ autoSession.reset();
92
+ autoSession.active = true;
93
+ autoSession.basePath = base;
94
+
95
+ mkdirSync(join(base, ".gsd"), { recursive: true });
96
+ writeFileSync(
97
+ join(base, ".gsd", "PREFERENCES.md"),
98
+ "---\nnotifications:\n enabled: true\n local_bell: true\n---\n",
99
+ "utf-8",
100
+ );
101
+
102
+ process.stderr.isTTY = true;
103
+ process.stderr.write = ((chunk: string | Uint8Array) => {
104
+ if (typeof chunk === "string") {
105
+ bellOutput += chunk;
106
+ }
107
+ return true;
108
+ }) as typeof process.stderr.write;
109
+
110
+ try {
111
+ await stopAuto(
112
+ { hasUI: false, ui: { notify: () => {}, setStatus: () => {}, setWidget: () => {}, setHeader: () => {} } } as any,
113
+ undefined,
114
+ "test stop",
115
+ );
116
+
117
+ assert.ok(
118
+ bellOutput.includes("\u0007"),
119
+ "stopAuto must write a terminal bell to stderr when the local bell preference is enabled",
120
+ );
121
+ } finally {
122
+ process.stderr.write = previousStderrWrite;
123
+ process.stderr.isTTY = previousStderrIsTTY;
124
+ autoSession.reset();
125
+ process.chdir(previousCwd);
126
+ rmSync(base, { recursive: true, force: true });
127
+ }
73
128
  });
74
129
 
75
130
  test("buildDesktopNotificationCommand falls back to osascript on macOS when terminal-notifier is absent", () => {
@@ -29,8 +29,8 @@ import {
29
29
 
30
30
  const SKILL_NAME = "testskill";
31
31
  const COMPLETE_SLICE_SKILL_NAME = "complete-slice-policies";
32
- const SKILL_ACTIVATION_SUBSTRING = `Call Skill({ skill: '${SKILL_NAME}' })`;
33
- const COMPLETE_SLICE_SKILL_ACTIVATION_SUBSTRING = `Call Skill({ skill: '${COMPLETE_SLICE_SKILL_NAME}' })`;
32
+ const SKILL_ACTIVATION_SUBSTRING = `Read the installed '${SKILL_NAME}' skill file from <available_skills>`;
33
+ const COMPLETE_SLICE_SKILL_ACTIVATION_SUBSTRING = `Read the installed '${COMPLETE_SLICE_SKILL_NAME}' skill file from <available_skills>`;
34
34
 
35
35
  const tmpDirs: string[] = [];
36
36
  let savedCwd: string | undefined;
@@ -28,6 +28,11 @@ const BANNED_DECISION_PATHS = new Set([
28
28
  "gsd/auto-post-unit.ts",
29
29
  "gsd/milestone-closeout.ts",
30
30
  "gsd/auto/phases.ts",
31
+ "gsd/auto/pre-dispatch.ts",
32
+ "gsd/auto/dispatch.ts",
33
+ "gsd/auto/unit-phase.ts",
34
+ "gsd/auto/finalize.ts",
35
+ "gsd/auto/closeout.ts",
31
36
  "gsd/auto/orchestrator.ts",
32
37
  "gsd/auto/loop.ts",
33
38
  "gsd/tools/complete-slice.ts",
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { createTestContext } from "./test-helpers.ts";
13
- import { runPreDispatch } from "../auto/phases.ts";
13
+ import { runPreDispatch } from "../auto/pre-dispatch.ts";
14
14
 
15
15
  const { assertTrue, report } = createTestContext();
16
16