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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +167 -16
  3. package/dist/resources/extensions/gsd/auto/phases.js +4 -3
  4. package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +39 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +113 -7
  7. package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
  8. package/dist/resources/extensions/gsd/auto-recovery.js +4 -4
  9. package/dist/resources/extensions/gsd/auto-start.js +94 -15
  10. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
  11. package/dist/resources/extensions/gsd/auto.js +22 -4
  12. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
  13. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
  16. package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
  17. package/dist/resources/extensions/gsd/commands/handlers/core.js +6 -2
  18. package/dist/resources/extensions/gsd/commands/handlers/ops.js +7 -3
  19. package/dist/resources/extensions/gsd/commands-maintenance.js +172 -2
  20. package/dist/resources/extensions/gsd/commands-mcp-status.js +107 -59
  21. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
  22. package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
  23. package/dist/resources/extensions/gsd/config-overlay.js +2 -1
  24. package/dist/resources/extensions/gsd/error-classifier.js +2 -1
  25. package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
  26. package/dist/resources/extensions/gsd/gsd-db.js +37 -4
  27. package/dist/resources/extensions/gsd/guided-flow.js +1 -1
  28. package/dist/resources/extensions/gsd/mcp-filter.js +3 -0
  29. package/dist/resources/extensions/gsd/mcp-project-config.js +67 -8
  30. package/dist/resources/extensions/gsd/migration-auto-check.js +2 -2
  31. package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
  32. package/dist/resources/extensions/gsd/prompts/system.md +3 -1
  33. package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
  34. package/dist/resources/extensions/gsd/skill-activation.js +20 -3
  35. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +4 -2
  36. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +1 -1
  37. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
  38. package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
  39. package/dist/resources/extensions/gsd/state.js +15 -12
  40. package/dist/resources/extensions/gsd/tool-presentation-plan.js +120 -0
  41. package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
  42. package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -9
  43. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
  44. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
  45. package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
  46. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
  47. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -1
  48. package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
  49. package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -0
  50. package/dist/resources/extensions/mcp-client/manager.js +31 -1
  51. package/dist/web/standalone/.next/BUILD_ID +1 -1
  52. package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
  53. package/dist/web/standalone/.next/build-manifest.json +2 -2
  54. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  55. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.html +1 -1
  72. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app-paths-manifest.json +4 -4
  79. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  80. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  82. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  83. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  84. package/package.json +2 -2
  85. package/packages/cloud-mcp-gateway/package.json +2 -2
  86. package/packages/contracts/dist/workflow.d.ts +14 -0
  87. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  88. package/packages/contracts/dist/workflow.js +16 -0
  89. package/packages/contracts/dist/workflow.js.map +1 -1
  90. package/packages/contracts/package.json +1 -1
  91. package/packages/daemon/package.json +4 -4
  92. package/packages/gsd-agent-core/package.json +5 -5
  93. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  94. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  95. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
  96. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  97. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
  98. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  99. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +72 -31
  100. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  101. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.d.ts.map +1 -1
  102. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js +2 -0
  103. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
  104. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
  105. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
  106. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
  108. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
  110. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  111. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
  112. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
  113. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
  114. package/packages/gsd-agent-modes/package.json +7 -7
  115. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  116. package/packages/mcp-server/dist/workflow-tools.js +82 -0
  117. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  118. package/packages/mcp-server/package.json +3 -3
  119. package/packages/native/package.json +1 -1
  120. package/packages/pi-agent-core/package.json +1 -1
  121. package/packages/pi-ai/dist/image-models.generated.d.ts +15 -0
  122. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  123. package/packages/pi-ai/dist/image-models.generated.js +15 -0
  124. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  125. package/packages/pi-ai/dist/models.generated.d.ts +338 -17
  126. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  127. package/packages/pi-ai/dist/models.generated.js +412 -112
  128. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  129. package/packages/pi-ai/package.json +1 -1
  130. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  131. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  133. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  134. package/packages/pi-coding-agent/package.json +7 -7
  135. package/packages/pi-tui/dist/terminal.d.ts +1 -0
  136. package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
  137. package/packages/pi-tui/dist/terminal.js +8 -4
  138. package/packages/pi-tui/dist/terminal.js.map +1 -1
  139. package/packages/pi-tui/package.json +1 -1
  140. package/packages/rpc-client/package.json +2 -2
  141. package/pkg/package.json +1 -1
  142. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +196 -16
  143. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +239 -63
  144. package/src/resources/extensions/gsd/auto/phases.ts +5 -3
  145. package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
  146. package/src/resources/extensions/gsd/auto-dispatch.ts +48 -0
  147. package/src/resources/extensions/gsd/auto-post-unit.ts +138 -7
  148. package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
  149. package/src/resources/extensions/gsd/auto-recovery.ts +4 -4
  150. package/src/resources/extensions/gsd/auto-start.ts +112 -17
  151. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
  152. package/src/resources/extensions/gsd/auto.ts +35 -3
  153. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
  154. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
  155. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
  156. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
  157. package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
  158. package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -2
  159. package/src/resources/extensions/gsd/commands/handlers/ops.ts +7 -3
  160. package/src/resources/extensions/gsd/commands-maintenance.ts +197 -2
  161. package/src/resources/extensions/gsd/commands-mcp-status.ts +134 -57
  162. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
  163. package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
  164. package/src/resources/extensions/gsd/config-overlay.ts +3 -1
  165. package/src/resources/extensions/gsd/error-classifier.ts +2 -1
  166. package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
  167. package/src/resources/extensions/gsd/gsd-db.ts +41 -6
  168. package/src/resources/extensions/gsd/guided-flow.ts +1 -1
  169. package/src/resources/extensions/gsd/mcp-filter.ts +3 -0
  170. package/src/resources/extensions/gsd/mcp-project-config.ts +92 -10
  171. package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
  172. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  173. package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
  174. package/src/resources/extensions/gsd/prompts/system.md +3 -1
  175. package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
  176. package/src/resources/extensions/gsd/skill-activation.ts +20 -2
  177. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +4 -2
  178. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +1 -1
  179. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
  180. package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
  181. package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
  182. package/src/resources/extensions/gsd/state.ts +16 -12
  183. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
  184. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +86 -0
  185. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +143 -2
  186. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +24 -2
  187. package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
  188. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
  189. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
  190. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +50 -13
  191. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +60 -0
  192. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
  193. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
  194. package/src/resources/extensions/gsd/tests/gsd-rebuild.test.ts +199 -0
  195. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +75 -0
  196. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +13 -6
  197. package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +15 -0
  198. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +68 -0
  199. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +177 -0
  200. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +3 -3
  201. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
  202. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +39 -1
  203. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
  204. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
  205. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
  206. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
  207. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
  208. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
  209. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +6 -2
  210. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
  211. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
  212. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
  213. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
  214. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
  215. package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
  216. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +17 -2
  217. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
  218. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
  219. package/src/resources/extensions/gsd/tool-presentation-plan.ts +167 -0
  220. package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
  221. package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -9
  222. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
  223. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
  224. package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
  225. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
  226. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -1
  227. package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
  228. package/src/resources/extensions/gsd/worktree-lifecycle.ts +26 -0
  229. package/src/resources/extensions/mcp-client/manager.ts +33 -1
  230. package/src/resources/extensions/mcp-client/tests/manager.test.ts +35 -0
  231. /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
  232. /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
@@ -118,6 +118,37 @@ function makeRepoWithMultipleStrandedMilestones(): string {
118
118
  return base;
119
119
  }
120
120
 
121
+ function makeRepoWithActiveMismatchAndStrandedTarget(): string {
122
+ const base = mkdtempSync(join(tmpdir(), "gsd-targeted-stranded-bootstrap-"));
123
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
124
+ mkdirSync(join(base, ".gsd", "milestones", "M002"), { recursive: true });
125
+ writeFileSync(
126
+ join(base, ".gsd", "PREFERENCES.md"),
127
+ "---\ngit:\n isolation: \"worktree\"\n---\n",
128
+ );
129
+ runGit(base, ["init"]);
130
+ runGit(base, ["config", "user.email", "test@test.com"]);
131
+ runGit(base, ["config", "user.name", "Test"]);
132
+ writeFileSync(join(base, "README.md"), "# test\n");
133
+ runGit(base, ["add", "-A"]);
134
+ runGit(base, ["commit", "-m", "init"]);
135
+ runGit(base, ["branch", "-M", "main"]);
136
+
137
+ runGit(base, ["checkout", "-b", "milestone/M002"]);
138
+ writeFileSync(join(base, "m002.txt"), "target stranded work\n");
139
+ runGit(base, ["add", "-A"]);
140
+ runGit(base, ["commit", "-m", "feat: M002 in progress"]);
141
+ runGit(base, ["checkout", "main"]);
142
+ runGit(base, ["worktree", "add", ".gsd/worktrees/M002", "milestone/M002"]);
143
+
144
+ openDatabase(join(base, ".gsd", "gsd.db"));
145
+ insertMilestone({ id: "M001", title: "Incorrect active milestone", status: "active" });
146
+ insertMilestone({ id: "M002", title: "Target stranded milestone", status: "active" });
147
+ closeDatabase();
148
+
149
+ return base;
150
+ }
151
+
121
152
  function makeRepoWithRecoveredCleanupAndStrandedMismatch(): string {
122
153
  const base = mkdtempSync(join(tmpdir(), "gsd-headless-stranded-bootstrap-"));
123
154
  mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
@@ -321,6 +352,9 @@ test("headless bootstrap checks stranded work before recovered-complete shortcut
321
352
  const messages = notifications.map((entry) => entry.message).join("\n");
322
353
  assert.equal(ready, false);
323
354
  assert.match(messages, /Stranded work for M002 blocks auto-mode/);
355
+ assert.match(messages, /\/gsd auto M002/);
356
+ assert.match(messages, /\/gsd park M002 "reason"/);
357
+ assert.match(messages, /\/gsd rethink/);
324
358
  assert.doesNotMatch(messages, /all milestones complete/);
325
359
  } finally {
326
360
  if (previousHeadless === undefined) {
@@ -409,6 +443,9 @@ test("bootstrap blocks active stranded recovery when another open milestone also
409
443
  assert.equal(ready, false);
410
444
  assert.deepEqual(adoptCalls, []);
411
445
  assert.match(messages, /Stranded work for M002 blocks auto-mode before M001/);
446
+ assert.match(messages, /\/gsd auto M002/);
447
+ assert.match(messages, /\/gsd park M002 "reason"/);
448
+ assert.match(messages, /explicitly discard M002/);
412
449
  } finally {
413
450
  try {
414
451
  closeDatabase();
@@ -418,6 +455,102 @@ test("bootstrap blocks active stranded recovery when another open milestone also
418
455
  }
419
456
  });
420
457
 
458
+ test("bootstrap honors explicit solo milestone lock when recovering stranded target worktree", async () => {
459
+ const base = makeRepoWithActiveMismatchAndStrandedTarget();
460
+ const previousCwd = process.cwd();
461
+ const previousLock = process.env.GSD_MILESTONE_LOCK;
462
+ const previousWorker = process.env.GSD_PARALLEL_WORKER;
463
+ const s = new AutoSession();
464
+ const adoptCalls: Array<{ milestoneId: string; mode: string }> = [];
465
+ const notifications: Array<{ message: string; level?: string }> = [];
466
+
467
+ try {
468
+ delete process.env.GSD_PARALLEL_WORKER;
469
+ process.env.GSD_MILESTONE_LOCK = "M002";
470
+
471
+ const ready = await bootstrapAutoSession(
472
+ s,
473
+ makeCtx(notifications) as any,
474
+ {
475
+ getThinkingLevel: () => "medium",
476
+ getActiveTools: () => [],
477
+ events: { emit: () => {} },
478
+ } as any,
479
+ base,
480
+ false,
481
+ false,
482
+ {
483
+ shouldUseWorktreeIsolation: () => false,
484
+ registerSigtermHandler: () => {},
485
+ registerAutoWorkerForSession: () => {},
486
+ lockBase: () => base,
487
+ buildLifecycle: () => ({
488
+ adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
489
+ s.basePath = sessionBase;
490
+ if (originalBase !== undefined) {
491
+ s.originalBasePath = originalBase;
492
+ } else if (!s.originalBasePath) {
493
+ s.originalBasePath = sessionBase;
494
+ }
495
+ },
496
+ enterMilestone: () => ({ ok: true, mode: "worktree", path: base }),
497
+ adoptStrandedMilestone: (
498
+ milestoneId: string,
499
+ sessionBase: string,
500
+ _ctx: unknown,
501
+ opts: { mode: "worktree" | "branch" },
502
+ ) => {
503
+ adoptCalls.push({ milestoneId, mode: opts.mode });
504
+ s.basePath = sessionBase;
505
+ s.originalBasePath = sessionBase;
506
+ s.strandedRecoveryIsolationMode = opts.mode;
507
+ return { ok: true, mode: opts.mode, path: sessionBase };
508
+ },
509
+ adoptOrphanWorktree: <T extends { merged: boolean }>(
510
+ _mid: string,
511
+ _base: string,
512
+ run: () => T,
513
+ ): T => run(),
514
+ }) as any,
515
+ },
516
+ {
517
+ classification: "none",
518
+ lock: null,
519
+ pausedSession: null,
520
+ state: null,
521
+ recovery: null,
522
+ recoveryPrompt: null,
523
+ recoveryToolCallCount: 0,
524
+ artifactSatisfied: false,
525
+ hasResumableDiskState: false,
526
+ isBootstrapCrash: false,
527
+ },
528
+ );
529
+
530
+ const messages = notifications.map((entry) => entry.message).join("\n");
531
+ assert.equal(ready, true);
532
+ assert.deepEqual(adoptCalls, [{ milestoneId: "M002", mode: "worktree" }]);
533
+ assert.equal(s.currentMilestoneId, "M002");
534
+ assert.match(messages, /Resuming saved milestone work for M002/);
535
+ assert.doesNotMatch(messages, /blocks auto-mode before M001/);
536
+ assert.doesNotMatch(messages, /Stranded work for in-progress milestone M002/);
537
+ assert.ok(
538
+ notifications.some((entry) => entry.level === "info" && entry.message.includes("Resuming saved milestone work for M002")),
539
+ "active recovery should be presented as an info-level resume",
540
+ );
541
+ } finally {
542
+ if (previousLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
543
+ else process.env.GSD_MILESTONE_LOCK = previousLock;
544
+ if (previousWorker === undefined) delete process.env.GSD_PARALLEL_WORKER;
545
+ else process.env.GSD_PARALLEL_WORKER = previousWorker;
546
+ try {
547
+ closeDatabase();
548
+ } catch {}
549
+ process.chdir(previousCwd);
550
+ rmSync(base, { recursive: true, force: true });
551
+ }
552
+ });
553
+
421
554
  test("bootstrap adopts stranded active branch even when isolation is none", async () => {
422
555
  const base = makeRepoWithStrandedActiveMilestone();
423
556
  const previousCwd = process.cwd();
@@ -496,7 +629,11 @@ test("bootstrap adopts stranded active branch even when isolation is none", asyn
496
629
  assert.equal(s.strandedRecoveryIsolationMode, "branch");
497
630
  assert.match(
498
631
  notifications.map((entry) => entry.message).join("\n"),
499
- /Recovering stranded work for M001/,
632
+ /Resuming saved milestone work for M001/,
633
+ );
634
+ assert.ok(
635
+ notifications.every((entry) => entry.level !== "warning" || !entry.message.includes("Stranded work for in-progress milestone M001")),
636
+ "adopting the active milestone should not emit a scary stranded-work warning",
500
637
  );
501
638
  } finally {
502
639
  try {
@@ -585,7 +722,11 @@ test("bootstrap adopts stranded active branch before deep project setup", async
585
722
  assert.equal(s.strandedRecoveryIsolationMode, "branch");
586
723
  assert.match(
587
724
  notifications.map((entry) => entry.message).join("\n"),
588
- /Recovering stranded work for M001/,
725
+ /Resuming saved milestone work for M001/,
726
+ );
727
+ assert.ok(
728
+ notifications.every((entry) => entry.level !== "warning" || !entry.message.includes("Stranded work for in-progress milestone M001")),
729
+ "adopting the active milestone should not emit a scary stranded-work warning",
589
730
  );
590
731
  } finally {
591
732
  try {
@@ -4,13 +4,35 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
7
- import { closeDatabase, getAllMilestones, insertMilestone, isDbAvailable, openDatabase } from "../gsd-db.ts";
8
- import { reconcileProjectMilestonesFromDisk } from "../auto-start.ts";
7
+ import { closeDatabase, getAllMilestones, getMilestone, insertMilestone, isDbAvailable, openDatabase } from "../gsd-db.ts";
8
+ import { reconcileMergedMilestonesFromJournal, reconcileProjectMilestonesFromDisk } from "../auto-start.ts";
9
+ import { emitWorktreeMerged } from "../worktree-telemetry.ts";
9
10
 
10
11
  test.afterEach(() => {
11
12
  if (isDbAvailable()) closeDatabase();
12
13
  });
13
14
 
15
+ test("bootstrap reconciliation treats a successful worktree merge as milestone closed", () => {
16
+ const base = mkdtempSync(join(tmpdir(), "gsd-merged-reconcile-"));
17
+ try {
18
+ mkdirSync(join(base, ".gsd"), { recursive: true });
19
+ openDatabase(join(base, ".gsd", "gsd.db"));
20
+ insertMilestone({ id: "M001", title: "Merged Milestone", status: "active" });
21
+
22
+ emitWorktreeMerged(base, "M001", { reason: "milestone-complete", conflict: false });
23
+
24
+ const closed = reconcileMergedMilestonesFromJournal(base);
25
+ const row = getMilestone("M001");
26
+
27
+ assert.equal(closed, 1);
28
+ assert.equal(row?.status, "complete");
29
+ assert.ok(row?.completed_at);
30
+ } finally {
31
+ if (isDbAvailable()) closeDatabase();
32
+ rmSync(base, { recursive: true, force: true });
33
+ }
34
+ });
35
+
14
36
  test("#5389: bootstrap reconciles PROJECT.md milestones that are missing from DB", () => {
15
37
  const base = mkdtempSync(join(tmpdir(), "gsd-project-reconcile-"));
16
38
  try {
@@ -40,10 +40,12 @@ function makeMockCtx(base: string): {
40
40
  calls: NotifyCall[];
41
41
  widgets: Array<[string, unknown]>;
42
42
  statuses: Array<[string, string | undefined]>;
43
+ newSessions: Array<{ workspaceRoot?: string }>;
43
44
  } {
44
45
  const calls: NotifyCall[] = [];
45
46
  const widgets: Array<[string, unknown]> = [];
46
47
  const statuses: Array<[string, string | undefined]> = [];
48
+ const newSessions: Array<{ workspaceRoot?: string }> = [];
47
49
  return {
48
50
  ctx: {
49
51
  cwd: base,
@@ -58,10 +60,15 @@ function makeMockCtx(base: string): {
58
60
  statuses.push([key, value]);
59
61
  },
60
62
  },
63
+ newSession: async (options?: { workspaceRoot?: string }) => {
64
+ newSessions.push(options ?? {});
65
+ return { cancelled: false };
66
+ },
61
67
  },
62
68
  calls,
63
69
  widgets,
64
70
  statuses,
71
+ newSessions,
65
72
  };
66
73
  }
67
74
 
@@ -77,7 +84,10 @@ function makeMockPi(): { pi: any; messages: SentMessage[] } {
77
84
  };
78
85
  }
79
86
 
80
- function seedValidationBlockedMilestone(base: string): void {
87
+ function seedValidationBlockedMilestone(
88
+ base: string,
89
+ status: "needs-attention" | "needs-remediation" = "needs-attention",
90
+ ): void {
81
91
  openDatabase(join(base, ".gsd", "gsd.db"));
82
92
  insertMilestone({ id: "M006", title: "Mark All Complete", status: "active" });
83
93
  insertSlice({
@@ -91,9 +101,9 @@ function seedValidationBlockedMilestone(base: string): void {
91
101
  insertAssessment({
92
102
  path: "milestones/M006/M006-VALIDATION.md",
93
103
  milestoneId: "M006",
94
- status: "needs-attention",
104
+ status,
95
105
  scope: "milestone-validation",
96
- fullContent: "verdict: needs-attention",
106
+ fullContent: `verdict: ${status}`,
97
107
  });
98
108
  invalidateStateCache();
99
109
  }
@@ -155,6 +165,31 @@ test("dispatcher blocks workflow-advancing aliases while validation is blocked",
155
165
  }
156
166
  });
157
167
 
168
+ test("dispatcher allows reassess dispatch while validation needs remediation", async () => {
169
+ const base = makeBase();
170
+ try {
171
+ seedValidationBlockedMilestone(base, "needs-remediation");
172
+ const { ctx, calls, newSessions } = makeMockCtx(base);
173
+ const { pi, messages } = makeMockPi();
174
+
175
+ await handleGSDCommand("dispatch reassess", ctx, pi);
176
+
177
+ assert.equal(messages.length, 1);
178
+ assert.equal(messages[0].customType, "gsd-dispatch");
179
+ assert.equal(messages[0].display, false);
180
+ assert.match(messages[0].content, /UNIT: Reassess Roadmap/);
181
+ assert.ok(
182
+ calls.some((call) => call.kind === "info" && /Dispatching reassess-roadmap for M006\/S01/.test(call.message)),
183
+ `expected reassess dispatch notification, got: ${JSON.stringify(calls)}`,
184
+ );
185
+ assert.deepEqual(newSessions, [{ workspaceRoot: base }]);
186
+ } finally {
187
+ closeDatabase();
188
+ invalidateStateCache();
189
+ cleanup(base);
190
+ }
191
+ });
192
+
158
193
  test("dispatcher still allows recovery commands while validation is blocked", async () => {
159
194
  const base = makeBase();
160
195
  try {
@@ -399,8 +399,12 @@ test("handleVerdict needs-remediation override with --rationale rewrites verdict
399
399
  assert.match(rewritten, /found missing slice/);
400
400
 
401
401
  assert.ok(
402
- calls.some((c) => /gsd_reassess_roadmap/.test(c.message)),
403
- "needs-remediation override should suggest gsd_reassess_roadmap follow-up",
402
+ calls.some((c) => /\/gsd dispatch reassess/.test(c.message)),
403
+ "needs-remediation override should suggest the reassess dispatch follow-up",
404
+ );
405
+ assert.ok(
406
+ calls.every((c) => !/gsd_reassess_roadmap/.test(c.message)),
407
+ "needs-remediation override should not expose the internal tool name",
404
408
  );
405
409
  } finally {
406
410
  closeDatabase();
@@ -868,6 +868,14 @@ describe('derive-state-db', async () => {
868
868
  dbState.blockers.some(b => b.includes('needs-remediation') && b.includes('M001')),
869
869
  'remediation-stuck-db: blocker message mentions milestone and verdict',
870
870
  );
871
+ assert.ok(
872
+ dbState.blockers.some(b => b.includes('/gsd dispatch reassess')),
873
+ 'remediation-stuck-db: blocker message points users to the reassess command',
874
+ );
875
+ assert.ok(
876
+ dbState.blockers.every(b => !b.includes('gsd_reassess_roadmap')),
877
+ 'remediation-stuck-db: blocker message does not expose the internal tool name',
878
+ );
871
879
 
872
880
  closeDatabase();
873
881
  } finally {
@@ -473,13 +473,13 @@ describe('derive-state-helpers', () => {
473
473
  }
474
474
  });
475
475
 
476
- test('getActiveMilestoneId: DB lock path ignores PARKED flag projection', async () => {
477
- const base = createFixtureBase();
478
- const previousLock = process.env.GSD_MILESTONE_LOCK;
479
- const previousWorker = process.env.GSD_PARALLEL_WORKER;
480
- try {
481
- process.env.GSD_MILESTONE_LOCK = 'M001';
482
- process.env.GSD_PARALLEL_WORKER = '1';
476
+ test('getActiveMilestoneId: DB lock path ignores PARKED flag projection', async () => {
477
+ const base = createFixtureBase();
478
+ const previousLock = process.env.GSD_MILESTONE_LOCK;
479
+ const previousWorker = process.env.GSD_PARALLEL_WORKER;
480
+ try {
481
+ process.env.GSD_MILESTONE_LOCK = 'M001';
482
+ process.env.GSD_PARALLEL_WORKER = '1';
483
483
  writeFile(base, 'milestones/M001/M001-PARKED.md', '# Parked on disk');
484
484
 
485
485
  openDatabase(':memory:');
@@ -487,12 +487,41 @@ describe('derive-state-helpers', () => {
487
487
 
488
488
  const id = await getActiveMilestoneId(base);
489
489
  assert.equal(id, 'M001', 'DB status remains authoritative despite PARKED projection');
490
- } finally {
491
- if (previousLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
492
- else process.env.GSD_MILESTONE_LOCK = previousLock;
493
- if (previousWorker === undefined) delete process.env.GSD_PARALLEL_WORKER;
494
- else process.env.GSD_PARALLEL_WORKER = previousWorker;
495
- closeDatabase();
490
+ } finally {
491
+ if (previousLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
492
+ else process.env.GSD_MILESTONE_LOCK = previousLock;
493
+ if (previousWorker === undefined) delete process.env.GSD_PARALLEL_WORKER;
494
+ else process.env.GSD_PARALLEL_WORKER = previousWorker;
495
+ closeDatabase();
496
+ cleanup(base);
497
+ }
498
+ });
499
+
500
+ test('deriveStateFromDb: solo milestone lock scopes state to the requested milestone', async () => {
501
+ const base = createFixtureBase();
502
+ const previousLock = process.env.GSD_MILESTONE_LOCK;
503
+ const previousWorker = process.env.GSD_PARALLEL_WORKER;
504
+ try {
505
+ delete process.env.GSD_PARALLEL_WORKER;
506
+ process.env.GSD_MILESTONE_LOCK = 'M002';
507
+
508
+ openDatabase(':memory:');
509
+ insertMilestone({ id: 'M001', title: 'Earlier', status: 'active' });
510
+ insertMilestone({ id: 'M002', title: 'Requested', status: 'active' });
511
+
512
+ invalidateStateCache();
513
+ const state = await deriveStateFromDb(base);
514
+ const id = await getActiveMilestoneId(base);
515
+
516
+ assert.equal(state.activeMilestone?.id, 'M002', 'explicit lock chooses requested milestone');
517
+ assert.deepEqual(state.registry.map((entry) => entry.id), ['M002'], 'registry is scoped to requested milestone');
518
+ assert.equal(id, 'M002', 'active milestone helper honors solo lock');
519
+ } finally {
520
+ if (previousLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
521
+ else process.env.GSD_MILESTONE_LOCK = previousLock;
522
+ if (previousWorker === undefined) delete process.env.GSD_PARALLEL_WORKER;
523
+ else process.env.GSD_PARALLEL_WORKER = previousWorker;
524
+ closeDatabase();
496
525
  cleanup(base);
497
526
  }
498
527
  });
@@ -526,6 +555,14 @@ describe('derive-state-helpers', () => {
526
555
  state.blockers.some(b => b.includes('needs-remediation') && b.includes('M001')),
527
556
  'remediation-stuck: blocker message mentions milestone and verdict',
528
557
  );
558
+ assert.ok(
559
+ state.blockers.some(b => b.includes('/gsd dispatch reassess')),
560
+ 'remediation-stuck: blocker message points users to the reassess command',
561
+ );
562
+ assert.ok(
563
+ state.blockers.every(b => !b.includes('gsd_reassess_roadmap')),
564
+ 'remediation-stuck: blocker message does not expose the internal tool name',
565
+ );
529
566
  } finally {
530
567
  closeDatabase();
531
568
  cleanup(base);
@@ -19,6 +19,7 @@ import type { DispatchContext } from "../auto-dispatch.ts";
19
19
  import type { AutoSession } from "../auto/session.ts";
20
20
  import type { GSDState } from "../types.ts";
21
21
  import { enableDebug, disableDebug, getDebugLogPath } from "../debug-logger.ts";
22
+ import { closeDatabase, insertMilestone, isDbAvailable, openDatabase } from "../gsd-db.ts";
22
23
 
23
24
  function makeState(overrides: Partial<GSDState> = {}): GSDState {
24
25
  return {
@@ -123,6 +124,29 @@ test("dispatch: missing task plan triggers plan-slice (not stop) — issue #909"
123
124
  `unitId should be M002/S03, got: ${result.action === "dispatch" ? result.unitId : "(stop)"}`);
124
125
  });
125
126
 
127
+ test("dispatch: closed milestone is not implicitly recovered or reopened", async (t) => {
128
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-closed-dispatch-"));
129
+ t.after(() => {
130
+ if (isDbAvailable()) closeDatabase();
131
+ rmSync(tmp, { recursive: true, force: true });
132
+ });
133
+
134
+ if (isDbAvailable()) closeDatabase();
135
+ mkdirSync(join(tmp, ".gsd"), { recursive: true });
136
+ openDatabase(join(tmp, ".gsd", "gsd.db"));
137
+ insertMilestone({ id: "M002", title: "Closed Milestone", status: "complete" });
138
+ scaffoldMilestoneContext(tmp, "M002");
139
+ scaffoldSlicePlan(tmp, "M002", "S03");
140
+
141
+ const result = await resolveDispatch(makeContext(tmp));
142
+
143
+ assert.equal(result.action, "stop");
144
+ assert.ok(result.action === "stop");
145
+ assert.equal(result.level, "warning");
146
+ assert.match(result.reason, /Milestone M002 is closed/);
147
+ assert.match(result.reason, /will not reopen or recover it implicitly/);
148
+ });
149
+
126
150
  test("dispatch: present task plan proceeds to execute-task normally", async (t) => {
127
151
  const tmp = mkdtempSync(join(tmpdir(), "gsd-909-ok-"));
128
152
  t.after(() => rmSync(tmp, { recursive: true, force: true }));
@@ -141,6 +165,42 @@ test("dispatch: present task plan proceeds to execute-task normally", async (t)
141
165
  `unitId should be M002/S03/T01, got: ${result.action === "dispatch" ? result.unitId : "(stop)"}`);
142
166
  });
143
167
 
168
+ test("dispatch: session milestone mismatch stops before missing-task-plan recovery", async (t) => {
169
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-session-milestone-mismatch-"));
170
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
171
+
172
+ const worktreeRoot = join(tmp, ".gsd", "worktrees", "M002");
173
+ mkdirSync(worktreeRoot, { recursive: true });
174
+
175
+ const ctx = makeContextFor(tmp, "M001", "S01", "T01", {
176
+ basePath: worktreeRoot,
177
+ originalBasePath: tmp,
178
+ currentMilestoneId: "M002",
179
+ });
180
+ const result = await resolveDispatch(ctx);
181
+
182
+ assert.equal(result.action, "stop");
183
+ assert.ok(result.action === "stop");
184
+ assert.equal(result.level, "warning");
185
+ assert.match(result.reason, /context mid "M001" does not match session\.currentMilestoneId "M002"/);
186
+ });
187
+
188
+ test("dispatch: worktree path mismatch stops before planning a different milestone", async (t) => {
189
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-worktree-path-milestone-mismatch-"));
190
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
191
+
192
+ const worktreeRoot = join(tmp, ".gsd", "worktrees", "M002");
193
+ mkdirSync(worktreeRoot, { recursive: true });
194
+
195
+ const ctx = makeContextFor(worktreeRoot, "M001", "S01", "T01");
196
+ const result = await resolveDispatch(ctx);
197
+
198
+ assert.equal(result.action, "stop");
199
+ assert.ok(result.action === "stop");
200
+ assert.equal(result.level, "warning");
201
+ assert.match(result.reason, /context mid "M001" does not match basePath worktree "M002"/);
202
+ });
203
+
144
204
  test("dispatch: executing recovery checks active milestone worktree task plans before re-dispatching plan-slice", async (t) => {
145
205
  const tmp = mkdtempSync(join(tmpdir(), "gsd-6192-"));
146
206
  t.after(() => rmSync(tmp, { recursive: true, force: true }));
@@ -53,6 +53,24 @@ test('runExecSandbox: captures stdout, persists artifacts, returns digest', asyn
53
53
  }
54
54
  });
55
55
 
56
+ test('runExecSandbox: persists optional request metadata', async () => {
57
+ const base = freshBase();
58
+ try {
59
+ const result = await runExecSandbox(
60
+ {
61
+ runtime: 'bash',
62
+ script: 'echo metadata-ok',
63
+ metadata: { kind: 'uat_exec', intent: 'uat-artifact-check' },
64
+ },
65
+ baseOpts(base),
66
+ );
67
+ const meta = JSON.parse(readFileSync(result.meta_path, 'utf-8')) as Record<string, unknown>;
68
+ assert.deepEqual(meta.metadata, { kind: 'uat_exec', intent: 'uat-artifact-check' });
69
+ } finally {
70
+ cleanup(base);
71
+ }
72
+ });
73
+
56
74
  test('runExecSandbox: enforces stdout cap and marks truncation', async () => {
57
75
  const base = freshBase();
58
76
  try {
@@ -0,0 +1,69 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { registerExecTools } from "../bootstrap/exec-tools.ts";
5
+ import { executeUatExec } from "../tools/exec-tool.ts";
6
+ import type { ExecSandboxRequest, ExecSandboxResult } from "../exec-sandbox.ts";
7
+
8
+ function makeExecResult(request: ExecSandboxRequest): ExecSandboxResult {
9
+ return {
10
+ id: "exec-1",
11
+ runtime: request.runtime,
12
+ exit_code: 0,
13
+ signal: null,
14
+ timed_out: false,
15
+ duration_ms: 1,
16
+ stdout_bytes: 12,
17
+ stderr_bytes: 0,
18
+ stdout_truncated: false,
19
+ stderr_truncated: false,
20
+ stdout_path: ".gsd/exec/exec-1.stdout",
21
+ stderr_path: ".gsd/exec/exec-1.stderr",
22
+ meta_path: ".gsd/exec/exec-1.meta.json",
23
+ digest: "check passed",
24
+ };
25
+ }
26
+
27
+ test("executeUatExec accepts evidence-mode aliases for intent", async () => {
28
+ const requests: ExecSandboxRequest[] = [];
29
+ const result = await executeUatExec(
30
+ {
31
+ milestoneId: "M001",
32
+ sliceId: "S01",
33
+ checkId: "UAT-PRE",
34
+ intent: "artifact",
35
+ runtime: "bash",
36
+ script: "printf ok",
37
+ },
38
+ {
39
+ baseDir: "/tmp/gsd-uat-exec-test",
40
+ preferences: null,
41
+ run: async (request) => {
42
+ requests.push(request);
43
+ return makeExecResult(request);
44
+ },
45
+ },
46
+ );
47
+
48
+ assert.equal(result.isError, false);
49
+ assert.equal(result.details?.operation, "gsd_uat_exec");
50
+ assert.equal(result.details?.intent, "uat-artifact-check");
51
+ assert.equal(requests[0]?.metadata?.intent, "uat-artifact-check");
52
+ });
53
+
54
+ test("registerExecTools exposes gsd_uat_exec intent as recoverable string schema", () => {
55
+ const tools: Array<{ name: string; parameters: any }> = [];
56
+ registerExecTools({
57
+ registerTool: (tool: { name: string; parameters: any }) => {
58
+ tools.push(tool);
59
+ },
60
+ } as any);
61
+
62
+ const tool = tools.find((registeredTool) => registeredTool.name === "gsd_uat_exec");
63
+ assert.ok(tool, "gsd_uat_exec should be registered");
64
+ const intentSchema = tool.parameters.properties.intent;
65
+ assert.equal(intentSchema.type, "string");
66
+ assert.equal("anyOf" in intentSchema, false);
67
+ assert.match(intentSchema.description, /uat-artifact-check/);
68
+ assert.match(intentSchema.description, /artifact/);
69
+ });