@opengsd/gsd-pi 1.1.1-dev.9bb7453 → 1.1.1-dev.9f86580

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 (219) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
  3. package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
  4. package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
  5. package/dist/resources/extensions/browser-tools/index.js +29 -2
  6. package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
  7. package/dist/resources/extensions/gsd/auto/phases.js +45 -3
  8. package/dist/resources/extensions/gsd/auto/session.js +2 -0
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +21 -2
  10. package/dist/resources/extensions/gsd/auto-model-selection.js +26 -0
  11. package/dist/resources/extensions/gsd/auto-prompts.js +4 -0
  12. package/dist/resources/extensions/gsd/auto-recovery.js +3 -4
  13. package/dist/resources/extensions/gsd/auto-timers.js +24 -10
  14. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +18 -66
  15. package/dist/resources/extensions/gsd/auto-worktree.js +18 -5
  16. package/dist/resources/extensions/gsd/auto.js +26 -4
  17. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +16 -10
  18. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +48 -29
  19. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  20. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +18 -29
  21. package/dist/resources/extensions/gsd/closeout-consistency-gate.js +61 -0
  22. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
  23. package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
  24. package/dist/resources/extensions/gsd/config-overlay.js +1 -0
  25. package/dist/resources/extensions/gsd/context-masker.js +129 -5
  26. package/dist/resources/extensions/gsd/guided-flow.js +93 -108
  27. package/dist/resources/extensions/gsd/milestone-closeout.js +3 -1
  28. package/dist/resources/extensions/gsd/pending-auto-start.js +0 -1
  29. package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
  30. package/dist/resources/extensions/gsd/preferences-models.js +1 -0
  31. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/run-uat.md +5 -19
  33. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  34. package/dist/resources/extensions/gsd/recovery-classification.js +20 -0
  35. package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
  36. package/dist/resources/extensions/gsd/tool-contract.js +6 -1
  37. package/dist/resources/extensions/gsd/tool-presentation-plan.js +47 -7
  38. package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -1
  39. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +113 -8
  40. package/dist/resources/extensions/gsd/unit-tool-contracts.js +193 -0
  41. package/dist/resources/extensions/gsd/workflow-mcp.js +5 -78
  42. package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
  43. package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
  44. package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
  45. package/dist/web/standalone/.next/BUILD_ID +1 -1
  46. package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
  47. package/dist/web/standalone/.next/build-manifest.json +2 -2
  48. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  49. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/index.html +1 -1
  66. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
  73. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  74. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  76. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  77. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  78. package/package.json +1 -1
  79. package/packages/cloud-mcp-gateway/package.json +2 -2
  80. package/packages/contracts/package.json +1 -1
  81. package/packages/daemon/package.json +4 -4
  82. package/packages/gsd-agent-core/package.json +5 -5
  83. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  84. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
  85. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  86. package/packages/gsd-agent-modes/package.json +7 -7
  87. package/packages/mcp-server/package.json +3 -3
  88. package/packages/native/package.json +1 -1
  89. package/packages/pi-agent-core/dist/agent-loop.js +4 -3
  90. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  91. package/packages/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  92. package/packages/pi-agent-core/dist/harness/agent-harness.js +3 -1
  93. package/packages/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  94. package/packages/pi-agent-core/dist/harness/types.d.ts +1 -0
  95. package/packages/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  96. package/packages/pi-agent-core/dist/harness/types.js.map +1 -1
  97. package/packages/pi-agent-core/dist/types.d.ts +3 -1
  98. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  99. package/packages/pi-agent-core/dist/types.js.map +1 -1
  100. package/packages/pi-agent-core/package.json +1 -1
  101. package/packages/pi-ai/dist/models.generated.d.ts +157 -18
  102. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  103. package/packages/pi-ai/dist/models.generated.js +159 -36
  104. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  105. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  106. package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
  107. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  108. package/packages/pi-ai/package.json +1 -1
  109. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +3 -0
  110. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/tools/bash.js +2 -2
  113. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  115. package/packages/pi-coding-agent/dist/core/tools/edit.js +3 -2
  116. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts +1 -0
  118. package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/tools/render-utils.js +6 -0
  120. package/packages/pi-coding-agent/dist/core/tools/render-utils.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/tools/write.js +3 -2
  123. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  124. package/packages/pi-coding-agent/package.json +7 -7
  125. package/packages/pi-tui/package.json +1 -1
  126. package/packages/rpc-client/package.json +2 -2
  127. package/pkg/package.json +1 -1
  128. package/scripts/install/handoff.js +16 -3
  129. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
  130. package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
  131. package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
  132. package/src/resources/extensions/browser-tools/index.ts +36 -5
  133. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
  134. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
  135. package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
  136. package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
  137. package/src/resources/extensions/gsd/auto/phases.ts +48 -6
  138. package/src/resources/extensions/gsd/auto/session.ts +2 -0
  139. package/src/resources/extensions/gsd/auto-dispatch.ts +48 -2
  140. package/src/resources/extensions/gsd/auto-model-selection.ts +26 -0
  141. package/src/resources/extensions/gsd/auto-prompts.ts +4 -0
  142. package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
  143. package/src/resources/extensions/gsd/auto-timers.ts +25 -9
  144. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -74
  145. package/src/resources/extensions/gsd/auto-worktree.ts +23 -5
  146. package/src/resources/extensions/gsd/auto.ts +28 -4
  147. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +16 -10
  148. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +63 -29
  149. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  150. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +50 -54
  151. package/src/resources/extensions/gsd/closeout-consistency-gate.ts +137 -0
  152. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
  153. package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
  154. package/src/resources/extensions/gsd/config-overlay.ts +1 -0
  155. package/src/resources/extensions/gsd/context-masker.ts +152 -5
  156. package/src/resources/extensions/gsd/guided-flow.ts +128 -135
  157. package/src/resources/extensions/gsd/milestone-closeout.ts +3 -1
  158. package/src/resources/extensions/gsd/pending-auto-start.ts +0 -2
  159. package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
  160. package/src/resources/extensions/gsd/preferences-models.ts +1 -0
  161. package/src/resources/extensions/gsd/preferences-types.ts +8 -0
  162. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  163. package/src/resources/extensions/gsd/prompts/run-uat.md +5 -19
  164. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  165. package/src/resources/extensions/gsd/recovery-classification.ts +20 -0
  166. package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
  167. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +99 -0
  168. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
  169. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +10 -2
  170. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +4 -1
  171. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
  172. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +12 -2
  173. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
  174. package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +9 -15
  175. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +26 -16
  176. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +21 -0
  177. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
  178. package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
  179. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +1 -0
  180. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +40 -1
  181. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
  182. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +31 -79
  183. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +5 -3
  184. package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +40 -4
  185. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +8 -0
  186. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +16 -0
  187. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +7 -1
  188. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
  189. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -0
  190. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
  191. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
  192. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +63 -0
  193. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +10 -1
  194. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +9 -1
  195. package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
  196. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +147 -5
  197. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
  198. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +44 -0
  199. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +4 -0
  200. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +56 -0
  201. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
  202. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -4
  203. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
  204. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +409 -0
  205. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
  206. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
  207. package/src/resources/extensions/gsd/tool-contract.ts +7 -1
  208. package/src/resources/extensions/gsd/tool-presentation-plan.ts +82 -7
  209. package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -1
  210. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +146 -9
  211. package/src/resources/extensions/gsd/unit-tool-contracts.ts +210 -0
  212. package/src/resources/extensions/gsd/workflow-mcp.ts +5 -78
  213. package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
  214. package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
  215. package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
  216. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +0 -246
  217. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +0 -218
  218. /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_buildManifest.js +0 -0
  219. /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → zzYMrKpPGfRQRxSFO32Jr}/_ssgManifest.js +0 -0
@@ -101,7 +101,7 @@ test("buildMinimalAutoGsdToolSet keeps unit-specific completion tools without al
101
101
  assert.ok(!result.includes("gsd_complete_slice"));
102
102
  });
103
103
 
104
- test("buildMinimalAutoGsdToolSet scopes run-uat to UAT-specific tools", () => {
104
+ test("buildMinimalAutoGsdToolSet scopes run-uat to UAT-specific and read-only tools", () => {
105
105
  const active = ["ask_user_questions", "bash", "read", "edit", "write", "gsd_summary_save"];
106
106
  const registered = [
107
107
  ...active,
@@ -123,11 +123,11 @@ test("buildMinimalAutoGsdToolSet scopes run-uat to UAT-specific tools", () => {
123
123
  assert.ok(result.includes("gsd_resume"));
124
124
  assert.ok(result.includes("gsd_milestone_status"));
125
125
  assert.ok(result.includes("gsd_journal_query"));
126
+ assert.ok(result.includes("read"));
126
127
  assert.ok(result.includes("browser_navigate"), "run-uat needs browser_navigate");
127
128
  assert.ok(result.includes("browser_click"), "run-uat needs browser_click");
128
129
  assert.ok(!result.includes("ToolSearch"));
129
130
  assert.ok(!result.includes("bash"));
130
- assert.ok(!result.includes("read"));
131
131
  assert.ok(!result.includes("edit"));
132
132
  assert.ok(!result.includes("write"));
133
133
  assert.ok(!result.includes("gsd_exec"));
@@ -230,9 +230,9 @@ test("buildMinimalAutoGsdToolSet preserves compatible browser add-ons for run-ua
230
230
  assert.ok(result.includes("gsd_uat_exec"));
231
231
  assert.ok(result.includes("gsd_uat_result_save"));
232
232
  assert.ok(result.includes("subagent"));
233
+ assert.ok(result.includes("read"));
233
234
  assert.ok(!result.includes("ToolSearch"));
234
235
  assert.ok(!result.includes("bash"));
235
- assert.ok(!result.includes("read"));
236
236
  assert.ok(!result.includes("edit"));
237
237
  assert.ok(!result.includes("write"));
238
238
  assert.ok(!result.includes("gsd_exec"));
@@ -281,12 +281,12 @@ test("buildMinimalAutoGsdToolSet honors provider-compatible registered tools for
281
281
 
282
282
  assert.ok(result.includes("gsd_uat_exec"));
283
283
  assert.ok(result.includes("gsd_uat_result_save"));
284
+ assert.ok(result.includes("read"));
284
285
  assert.ok(result.includes("browser_navigate"));
285
286
  assert.ok(result.includes("browser_click"));
286
287
  assert.ok(!result.includes("browser_screenshot"), "provider-filtered screenshot tool must stay filtered");
287
288
  assert.ok(!result.includes("ToolSearch"));
288
289
  assert.ok(!result.includes("bash"));
289
- assert.ok(!result.includes("read"));
290
290
  assert.ok(!result.includes("gsd_exec"));
291
291
  assert.ok(!result.includes("gsd_summary_save"));
292
292
  assert.ok(!result.includes("gsd_save_gate_result"));
@@ -48,12 +48,52 @@ test("auto execute-task requires canonical task completion tool", () => {
48
48
  assert.deepEqual(getRequiredWorkflowToolsForAutoUnit("execute-task"), ["gsd_task_complete"]);
49
49
  });
50
50
 
51
+ test("plan-slice requires planning and roadmap reassessment tools", () => {
52
+ const expected = ["gsd_plan_slice", "gsd_reassess_roadmap"];
53
+ assert.deepEqual(getRequiredWorkflowToolsForGuidedUnit("plan-slice"), expected);
54
+ assert.deepEqual(getRequiredWorkflowToolsForAutoUnit("plan-slice"), expected);
55
+ });
56
+
57
+ test("plan-milestone requires status, roadmap, and single-slice planning tools", () => {
58
+ const expected = ["gsd_milestone_status", "gsd_plan_milestone", "gsd_plan_slice"];
59
+ assert.deepEqual(getRequiredWorkflowToolsForGuidedUnit("plan-milestone"), expected);
60
+ assert.deepEqual(getRequiredWorkflowToolsForAutoUnit("plan-milestone"), expected);
61
+ });
62
+
63
+ test("refine-slice requires canonical slice planning tool", () => {
64
+ assert.deepEqual(getRequiredWorkflowToolsForGuidedUnit("refine-slice"), ["gsd_plan_slice"]);
65
+ assert.deepEqual(getRequiredWorkflowToolsForAutoUnit("refine-slice"), ["gsd_plan_slice"]);
66
+ });
67
+
51
68
  test("complete-slice requires closeout and execution handoff tools", () => {
52
- const expected = ["gsd_slice_complete", "gsd_task_reopen", "gsd_replan_slice"];
69
+ const expected = [
70
+ "gsd_slice_complete",
71
+ "gsd_task_reopen",
72
+ "gsd_replan_slice",
73
+ "gsd_requirement_update",
74
+ "gsd_summary_save",
75
+ ];
53
76
  assert.deepEqual(getRequiredWorkflowToolsForGuidedUnit("complete-slice"), expected);
54
77
  assert.deepEqual(getRequiredWorkflowToolsForAutoUnit("complete-slice"), expected);
55
78
  });
56
79
 
80
+ test("complete-milestone requires status, requirement, project refresh, and closeout tools", () => {
81
+ const expected = [
82
+ "gsd_milestone_status",
83
+ "gsd_requirement_update",
84
+ "gsd_summary_save",
85
+ "gsd_complete_milestone",
86
+ ];
87
+ assert.deepEqual(getRequiredWorkflowToolsForGuidedUnit("complete-milestone"), expected);
88
+ assert.deepEqual(getRequiredWorkflowToolsForAutoUnit("complete-milestone"), expected);
89
+ });
90
+
91
+ test("reactive-execute requires task completion and failed-task summary tools", () => {
92
+ const expected = ["gsd_task_complete", "gsd_summary_save"];
93
+ assert.deepEqual(getRequiredWorkflowToolsForGuidedUnit("reactive-execute"), expected);
94
+ assert.deepEqual(getRequiredWorkflowToolsForAutoUnit("reactive-execute"), expected);
95
+ });
96
+
57
97
  test("workflow MCP capability surface includes native legacy gsd aliases", () => {
58
98
  const err = getWorkflowTransportSupportError(
59
99
  "claude-code",
@@ -679,7 +719,7 @@ test("transport compatibility ignores API-backed providers", () => {
679
719
  test("transport compatibility now allows plan-slice over workflow MCP surface", () => {
680
720
  const error = getWorkflowTransportSupportError(
681
721
  "claude-code",
682
- ["gsd_plan_slice"],
722
+ getRequiredWorkflowToolsForAutoUnit("plan-slice"),
683
723
  {
684
724
  projectRoot: "/tmp/project",
685
725
  env: { GSD_WORKFLOW_MCP_COMMAND: "node" },
@@ -696,7 +736,7 @@ test("transport compatibility now allows plan-slice over workflow MCP surface",
696
736
  test("transport compatibility now allows complete-slice over workflow MCP surface", () => {
697
737
  const error = getWorkflowTransportSupportError(
698
738
  "claude-code",
699
- ["gsd_complete_slice"],
739
+ getRequiredWorkflowToolsForAutoUnit("complete-slice"),
700
740
  {
701
741
  projectRoot: "/tmp/project",
702
742
  env: { GSD_WORKFLOW_MCP_COMMAND: "node" },
@@ -747,7 +787,7 @@ test("transport compatibility now allows gate-evaluate over workflow MCP surface
747
787
  test("transport compatibility now allows validate-milestone over workflow MCP surface", () => {
748
788
  const error = getWorkflowTransportSupportError(
749
789
  "claude-code",
750
- ["gsd_milestone_status", "gsd_validate_milestone"],
790
+ getRequiredWorkflowToolsForAutoUnit("validate-milestone"),
751
791
  {
752
792
  projectRoot: "/tmp/project",
753
793
  env: { GSD_WORKFLOW_MCP_COMMAND: "node" },
@@ -764,7 +804,7 @@ test("transport compatibility now allows validate-milestone over workflow MCP su
764
804
  test("transport compatibility now allows complete-milestone over workflow MCP surface", () => {
765
805
  const error = getWorkflowTransportSupportError(
766
806
  "claude-code",
767
- ["gsd_milestone_status", "gsd_complete_milestone"],
807
+ getRequiredWorkflowToolsForAutoUnit("complete-milestone"),
768
808
  {
769
809
  projectRoot: "/tmp/project",
770
810
  env: { GSD_WORKFLOW_MCP_COMMAND: "node" },
@@ -795,7 +835,7 @@ test("transport compatibility now allows replan-slice over workflow MCP surface"
795
835
  assert.equal(error, null);
796
836
  });
797
837
 
798
- test("transport compatibility accepts workflow MCP tools absent from parent active tool surface", () => {
838
+ test("transport compatibility rejects MCP tools not connected in active tool surface", () => {
799
839
  const error = getWorkflowTransportSupportError(
800
840
  "claude-code",
801
841
  ["gsd_summary_save"],
@@ -810,10 +850,10 @@ test("transport compatibility accepts workflow MCP tools absent from parent acti
810
850
  },
811
851
  );
812
852
 
813
- assert.equal(error, null);
853
+ assert.match(error ?? "", /requires gsd_summary_save/);
814
854
  });
815
855
 
816
- test("transport compatibility still checks non-MCP tools against parent active tool surface", () => {
856
+ test("transport compatibility checks all required tools against active tool surface", () => {
817
857
  const error = getWorkflowTransportSupportError(
818
858
  "claude-code",
819
859
  ["gsd_summary_save", "secure_env_collect"],
@@ -828,8 +868,9 @@ test("transport compatibility still checks non-MCP tools against parent active t
828
868
  },
829
869
  );
830
870
 
831
- assert.match(error ?? "", /requires secure_env_collect/);
832
- assert.doesNotMatch(error ?? "", /gsd_summary_save/);
871
+ assert.match(error ?? "", /requires.*(?:gsd_summary_save|secure_env_collect)/);
872
+ assert.match(error ?? "", /gsd_summary_save/);
873
+ assert.match(error ?? "", /secure_env_collect/);
833
874
  });
834
875
 
835
876
  test("transport compatibility still blocks units whose MCP tools are not exposed", () => {
@@ -850,6 +891,32 @@ test("transport compatibility still blocks units whose MCP tools are not exposed
850
891
  assert.match(error ?? "", /currently exposes only/);
851
892
  });
852
893
 
894
+ test("discuss-milestone guided flow does not abort when all required tools are on MCP surface (regression #469)", () => {
895
+ // Guided flow starts the workflow MCP server as part of dispatch, so the
896
+ // parent session active-tool list is not authoritative for MCP tools.
897
+ const discussMilestoneTools = [
898
+ "gsd_summary_save",
899
+ "gsd_requirement_save",
900
+ "gsd_requirement_update",
901
+ "gsd_plan_milestone",
902
+ "gsd_milestone_generate_id",
903
+ ];
904
+ const error = getWorkflowTransportSupportError(
905
+ "claude-code",
906
+ discussMilestoneTools,
907
+ {
908
+ projectRoot: "/tmp/project",
909
+ env: { GSD_WORKFLOW_MCP_COMMAND: "node" },
910
+ surface: "guided flow",
911
+ unitType: "discuss-milestone",
912
+ authMode: "externalCli",
913
+ baseUrl: "local://claude-code",
914
+ },
915
+ );
916
+
917
+ assert.equal(error, null);
918
+ });
919
+
853
920
  test("transport compatibility accepts MCP-namespaced runtime tools", () => {
854
921
  const error = getWorkflowTransportSupportError(
855
922
  "claude-code",
@@ -75,6 +75,11 @@ test("closeout executors reject phase escalation from the wrong active auto unit
75
75
  const milestone = await executeCompleteMilestone({} as Parameters<typeof executeCompleteMilestone>[0], "/tmp/project");
76
76
  assert.equal(milestone.isError, true);
77
77
  assert.match(String(milestone.details.error), /complete_milestone may only run from complete-milestone/);
78
+
79
+ const uat = await executeUatResultSave({} as Parameters<typeof executeUatResultSave>[0], "/tmp/project");
80
+ assert.equal(uat.isError, true);
81
+ assert.match(String(uat.details.error), /save_uat_result may only run from run-uat/);
82
+ assert.match(String(uat.details.error), /Tool Contract failure/);
78
83
  } finally {
79
84
  autoSession.reset();
80
85
  }
@@ -643,6 +648,398 @@ test("executeUatResultSave accepts gsd_uat_exec evidence written in a milestone
643
648
  }
644
649
  });
645
650
 
651
+ test("executeUatResultSave supplies canonical presentation and normalizes verdict casing", async () => {
652
+ const base = makeTmpBase();
653
+ const worktree = join(base, ".gsd", "worktrees", "M001");
654
+ const worktreeExecDir = join(worktree, ".gsd", "exec");
655
+ const evidenceId = "uat-lowercase-verdict";
656
+ try {
657
+ openTestDb(base);
658
+ seedMilestone("M001", "Milestone One");
659
+ seedSlice("M001", "S03", "complete");
660
+ mkdirSync(worktreeExecDir, { recursive: true });
661
+ writeFileSync(
662
+ join(worktreeExecDir, `${evidenceId}.meta.json`),
663
+ JSON.stringify({
664
+ id: evidenceId,
665
+ metadata: {
666
+ kind: "uat_exec",
667
+ milestoneId: "M001",
668
+ sliceId: "S03",
669
+ checkId: "UAT-01",
670
+ intent: "uat-artifact-check",
671
+ },
672
+ }),
673
+ "utf-8",
674
+ );
675
+
676
+ const result = await inProjectDir(worktree, () => executeUatResultSave({
677
+ milestoneId: "M001",
678
+ sliceId: "S03",
679
+ uatType: "artifact-driven",
680
+ verdict: "pass",
681
+ checks: [{
682
+ id: "UAT-01",
683
+ description: "Static artifact contract passes",
684
+ mode: "artifact",
685
+ result: "PASS",
686
+ evidence: [{ kind: "gsd_uat_exec", ref: evidenceId }],
687
+ notes: "Artifact check passed.",
688
+ }],
689
+ notes: "UAT passed with canonical presentation supplied by the executor.",
690
+ } as unknown as Parameters<typeof executeUatResultSave>[0], worktree));
691
+
692
+ assert.equal(result.isError, undefined);
693
+ assert.equal(result.details.verdict, "PASS");
694
+
695
+ const attempt = JSON.parse(readFileSync(
696
+ join(base, ".gsd", "uat", "M001", "S03", "attempt-1.json"),
697
+ "utf-8",
698
+ )) as { presentation?: { toolPresentationPlanId?: string; presentedTools?: string[] } };
699
+ assert.equal(attempt.presentation?.toolPresentationPlanId, "run-uat/default-v1");
700
+ assert.ok(attempt.presentation?.presentedTools?.includes("gsd_uat_result_save"));
701
+ assert.ok(attempt.presentation?.presentedTools?.includes("read"));
702
+ } finally {
703
+ closeDatabase();
704
+ cleanup(base);
705
+ }
706
+ });
707
+
708
+ test("executeUatResultSave supplies direct browser tools for browser-executable UAT", async () => {
709
+ const base = makeTmpBase();
710
+ const worktree = join(base, ".gsd", "worktrees", "M001");
711
+ const worktreeExecDir = join(worktree, ".gsd", "exec");
712
+ const evidenceId = "uat-direct-browser-evidence";
713
+ try {
714
+ openTestDb(base);
715
+ seedMilestone("M001", "Milestone One");
716
+ seedSlice("M001", "S06", "complete");
717
+ mkdirSync(worktreeExecDir, { recursive: true });
718
+ writeFileSync(
719
+ join(worktreeExecDir, `${evidenceId}.meta.json`),
720
+ JSON.stringify({
721
+ id: evidenceId,
722
+ metadata: {
723
+ kind: "uat_exec",
724
+ milestoneId: "M001",
725
+ sliceId: "S06",
726
+ checkId: "UAT-01",
727
+ intent: "uat-browser-check",
728
+ },
729
+ }),
730
+ "utf-8",
731
+ );
732
+
733
+ const result = await inProjectDir(worktree, () => executeUatResultSave({
734
+ milestoneId: "M001",
735
+ sliceId: "S06",
736
+ uatType: "browser-executable",
737
+ verdict: "PASS",
738
+ checks: [{
739
+ id: "UAT-01",
740
+ description: "Browser flow used browser tools",
741
+ mode: "browser",
742
+ result: "PASS",
743
+ evidence: [{ kind: "gsd_uat_exec", ref: evidenceId }],
744
+ notes: "Browser check passed.",
745
+ }],
746
+ notes: "UAT passed with browser evidence.",
747
+ } as unknown as Parameters<typeof executeUatResultSave>[0], worktree));
748
+
749
+ assert.equal(result.isError, undefined);
750
+ const attempt = JSON.parse(readFileSync(
751
+ join(base, ".gsd", "uat", "M001", "S06", "attempt-1.json"),
752
+ "utf-8",
753
+ )) as { presentation?: { presentedTools?: string[] } };
754
+
755
+ assert.ok(attempt.presentation?.presentedTools?.includes("browser_navigate"));
756
+ assert.ok(attempt.presentation?.presentedTools?.includes("browser_assert"));
757
+ assert.equal(
758
+ attempt.presentation?.presentedTools?.some((toolName) => toolName.startsWith("mcp__gsd-browser__")),
759
+ false,
760
+ );
761
+ } finally {
762
+ closeDatabase();
763
+ cleanup(base);
764
+ }
765
+ });
766
+
767
+ test("executeUatResultSave merges canonical plan ID and read-only tools when presentation lacks plan ID", async () => {
768
+ const base = makeTmpBase();
769
+ const worktree = join(base, ".gsd", "worktrees", "M001");
770
+ const worktreeExecDir = join(worktree, ".gsd", "exec");
771
+ const evidenceId = "uat-no-plan-id-evidence";
772
+ try {
773
+ openTestDb(base);
774
+ seedMilestone("M001", "Milestone One");
775
+ seedSlice("M001", "S05", "complete");
776
+ mkdirSync(worktreeExecDir, { recursive: true });
777
+ writeFileSync(
778
+ join(worktreeExecDir, `${evidenceId}.meta.json`),
779
+ JSON.stringify({
780
+ id: evidenceId,
781
+ metadata: {
782
+ kind: "uat_exec",
783
+ milestoneId: "M001",
784
+ sliceId: "S05",
785
+ checkId: "UAT-01",
786
+ intent: "uat-artifact-check",
787
+ },
788
+ }),
789
+ "utf-8",
790
+ );
791
+
792
+ const result = await inProjectDir(worktree, () => executeUatResultSave({
793
+ milestoneId: "M001",
794
+ sliceId: "S05",
795
+ uatType: "artifact-driven",
796
+ verdict: "PASS",
797
+ checks: [{
798
+ id: "UAT-01",
799
+ description: "Presentation plan ID absent from provider call",
800
+ mode: "artifact",
801
+ result: "PASS",
802
+ evidence: [{ kind: "gsd_uat_exec", ref: evidenceId }],
803
+ notes: "Canonical merge should apply even when toolPresentationPlanId is absent.",
804
+ }],
805
+ presentation: {
806
+ surface: "mcp",
807
+ presentedTools: [
808
+ "gsd_uat_exec",
809
+ "gsd_uat_result_save",
810
+ "gsd_resume",
811
+ "gsd_milestone_status",
812
+ "gsd_journal_query",
813
+ ],
814
+ blockedTools: [
815
+ { name: "gsd_exec", reason: "forbidden during run-uat" },
816
+ { name: "gsd_summary_save", reason: "forbidden during run-uat" },
817
+ { name: "gsd_save_gate_result", reason: "forbidden during run-uat" },
818
+ ],
819
+ },
820
+ notes: "Provider omitted toolPresentationPlanId; executor must canonicalize.",
821
+ } as unknown as Parameters<typeof executeUatResultSave>[0], worktree));
822
+
823
+ assert.equal(result.isError, undefined);
824
+ assert.equal(result.details.verdict, "PASS");
825
+
826
+ const attempt = JSON.parse(readFileSync(
827
+ join(base, ".gsd", "uat", "M001", "S05", "attempt-1.json"),
828
+ "utf-8",
829
+ )) as { presentation?: { toolPresentationPlanId?: string; presentedTools?: string[] } };
830
+ assert.equal(attempt.presentation?.toolPresentationPlanId, "run-uat/default-v1");
831
+ assert.ok(attempt.presentation?.presentedTools?.includes("read"), "read-only tool must be merged in");
832
+ assert.ok(attempt.presentation?.presentedTools?.includes("gsd_uat_result_save"));
833
+ } finally {
834
+ closeDatabase();
835
+ cleanup(base);
836
+ }
837
+ });
838
+
839
+ test("executeUatResultSave surfaces the worktree validation path for NEEDS-HUMAN checks", async () => {
840
+ const base = makeTmpBase();
841
+ const worktree = join(base, ".gsd", "worktrees", "M001");
842
+ const worktreeExecDir = join(worktree, ".gsd", "exec");
843
+ const evidenceId = "uat-human-validation-evidence";
844
+ try {
845
+ openTestDb(base);
846
+ seedMilestone("M001", "Milestone One");
847
+ seedSlice("M001", "S07", "complete");
848
+ mkdirSync(worktreeExecDir, { recursive: true });
849
+ writeFileSync(
850
+ join(worktreeExecDir, `${evidenceId}.meta.json`),
851
+ JSON.stringify({
852
+ id: evidenceId,
853
+ metadata: {
854
+ kind: "uat_exec",
855
+ milestoneId: "M001",
856
+ sliceId: "S07",
857
+ checkId: "UAT-01",
858
+ intent: "uat-runtime-check",
859
+ },
860
+ }),
861
+ "utf-8",
862
+ );
863
+
864
+ const result = await inProjectDir(worktree, () => executeUatResultSave({
865
+ milestoneId: "M001",
866
+ sliceId: "S07",
867
+ uatType: "human-experience",
868
+ verdict: "PASS",
869
+ checks: [
870
+ {
871
+ id: "UAT-01",
872
+ description: "Service boots and renders the dashboard",
873
+ mode: "runtime",
874
+ result: "PASS",
875
+ evidence: [{ kind: "gsd_uat_exec", ref: evidenceId }],
876
+ notes: "Boot check passed.",
877
+ },
878
+ {
879
+ id: "UAT-02",
880
+ description: "Dashboard layout feels balanced",
881
+ mode: "human-follow-up",
882
+ result: "NEEDS-HUMAN",
883
+ nonAutomatable: true,
884
+ notes: "Open the app and eyeball the spacing.",
885
+ },
886
+ ],
887
+ notes: "Automatable checks passed; layout taste needs a human.",
888
+ } as unknown as Parameters<typeof executeUatResultSave>[0], worktree));
889
+
890
+ assert.equal(result.isError, undefined);
891
+ assert.equal(result.details.verdict, "PASS");
892
+ // The reviewer needs the buried worktree checkout path, not just the file.
893
+ assert.equal(result.details.manualValidationPath, worktree);
894
+ const returnedText = (result.content[0] as { text: string }).text;
895
+ assert.match(returnedText, /Manual validation needed/);
896
+ assert.ok(returnedText.includes(worktree), "tool return should include the worktree path");
897
+
898
+ const assessment = readFileSync(
899
+ join(base, ".gsd", "milestones", "M001", "slices", "S07", "S07-ASSESSMENT.md"),
900
+ "utf-8",
901
+ );
902
+ assert.match(assessment, /## Manual Validation/);
903
+ assert.ok(assessment.includes(worktree), "assessment should include the worktree checkout path");
904
+ assert.match(assessment, /git worktree/);
905
+ } finally {
906
+ closeDatabase();
907
+ cleanup(base);
908
+ }
909
+ });
910
+
911
+ test("executeUatResultSave omits manual-validation guidance when no human checks remain", async () => {
912
+ const base = makeTmpBase();
913
+ const worktree = join(base, ".gsd", "worktrees", "M001");
914
+ const worktreeExecDir = join(worktree, ".gsd", "exec");
915
+ const evidenceId = "uat-no-human-evidence";
916
+ try {
917
+ openTestDb(base);
918
+ seedMilestone("M001", "Milestone One");
919
+ seedSlice("M001", "S08", "complete");
920
+ mkdirSync(worktreeExecDir, { recursive: true });
921
+ writeFileSync(
922
+ join(worktreeExecDir, `${evidenceId}.meta.json`),
923
+ JSON.stringify({
924
+ id: evidenceId,
925
+ metadata: {
926
+ kind: "uat_exec",
927
+ milestoneId: "M001",
928
+ sliceId: "S08",
929
+ checkId: "UAT-01",
930
+ intent: "uat-artifact-check",
931
+ },
932
+ }),
933
+ "utf-8",
934
+ );
935
+
936
+ const result = await inProjectDir(worktree, () => executeUatResultSave({
937
+ milestoneId: "M001",
938
+ sliceId: "S08",
939
+ uatType: "artifact-driven",
940
+ verdict: "PASS",
941
+ checks: [{
942
+ id: "UAT-01",
943
+ description: "Config file exists",
944
+ mode: "artifact",
945
+ result: "PASS",
946
+ evidence: [{ kind: "gsd_uat_exec", ref: evidenceId }],
947
+ notes: "Artifact present.",
948
+ }],
949
+ notes: "Fully automated pass.",
950
+ } as unknown as Parameters<typeof executeUatResultSave>[0], worktree));
951
+
952
+ assert.equal(result.isError, undefined);
953
+ assert.equal(result.details.manualValidationPath, undefined);
954
+ const returnedText = (result.content[0] as { text: string }).text;
955
+ assert.equal(returnedText.includes("Manual validation needed"), false);
956
+
957
+ const assessment = readFileSync(
958
+ join(base, ".gsd", "milestones", "M001", "slices", "S08", "S08-ASSESSMENT.md"),
959
+ "utf-8",
960
+ );
961
+ assert.equal(assessment.includes("## Manual Validation"), false);
962
+ } finally {
963
+ closeDatabase();
964
+ cleanup(base);
965
+ }
966
+ });
967
+
968
+ test("executeUatResultSave rejects saved UAT without fresh UAT-owned evidence", async () => {
969
+ const base = makeTmpBase();
970
+ const worktree = join(base, ".gsd", "worktrees", "M001");
971
+ const worktreeExecDir = join(worktree, ".gsd", "exec");
972
+ const evidenceId = "generic-exec-evidence";
973
+ try {
974
+ openTestDb(base);
975
+ seedMilestone("M001", "Milestone One");
976
+ seedSlice("M001", "S04", "complete");
977
+ mkdirSync(worktreeExecDir, { recursive: true });
978
+ writeFileSync(
979
+ join(worktreeExecDir, `${evidenceId}.meta.json`),
980
+ JSON.stringify({
981
+ id: evidenceId,
982
+ metadata: { kind: "exec" },
983
+ }),
984
+ "utf-8",
985
+ );
986
+
987
+ const result = await inProjectDir(worktree, () => executeUatResultSave({
988
+ milestoneId: "M001",
989
+ sliceId: "S04",
990
+ uatType: "artifact-driven",
991
+ verdict: "PASS",
992
+ checks: [{
993
+ id: "UAT-01",
994
+ description: "Static artifact contract passes",
995
+ mode: "artifact",
996
+ result: "PASS",
997
+ evidence: [{ kind: "gsd_exec", ref: evidenceId }],
998
+ notes: "Generic evidence should not satisfy fresh UAT evidence.",
999
+ }],
1000
+ notes: "UAT should not pass without fresh UAT-owned evidence.",
1001
+ } as unknown as Parameters<typeof executeUatResultSave>[0], worktree));
1002
+
1003
+ assert.equal(result.isError, true);
1004
+ assert.match(String(result.content[0]?.text), /fresh gsd_uat_exec evidence/);
1005
+ } finally {
1006
+ closeDatabase();
1007
+ cleanup(base);
1008
+ }
1009
+ });
1010
+
1011
+ test("executeUatResultSave rejects an unrecognized uatType", async () => {
1012
+ const base = makeTmpBase();
1013
+ const worktree = join(base, ".gsd", "worktrees", "M001");
1014
+ try {
1015
+ openTestDb(base);
1016
+ mkdirSync(worktree, { recursive: true });
1017
+ seedMilestone("M001", "Milestone One");
1018
+ seedSlice("M001", "S06", "complete");
1019
+
1020
+ const result = await inProjectDir(worktree, () => executeUatResultSave({
1021
+ milestoneId: "M001",
1022
+ sliceId: "S06",
1023
+ uatType: "hallucinated-mode",
1024
+ verdict: "PASS",
1025
+ checks: [{
1026
+ id: "UAT-01",
1027
+ description: "Static artifact contract passes",
1028
+ mode: "artifact",
1029
+ result: "PASS",
1030
+ evidence: [{ kind: "gsd_uat_exec", ref: "some-ref" }],
1031
+ }],
1032
+ notes: "Should fail before evidence validation.",
1033
+ } as unknown as Parameters<typeof executeUatResultSave>[0], worktree));
1034
+
1035
+ assert.equal(result.isError, true);
1036
+ assert.match(String(result.content[0]?.text), /uatType must be one of/);
1037
+ } finally {
1038
+ closeDatabase();
1039
+ cleanup(base);
1040
+ }
1041
+ });
1042
+
646
1043
  test("executeUatResultSave rejects artifact-driven PASS with human follow-up checks", async () => {
647
1044
  const base = makeTmpBase();
648
1045
  const worktree = join(base, ".gsd", "worktrees", "M001");
@@ -1409,6 +1806,10 @@ test("executeSummarySave blocks final root artifacts while approval gate is pend
1409
1806
 
1410
1807
  assert.equal(result.isError, true);
1411
1808
  assert.equal(result.details.error, "root_artifact_write_blocked");
1809
+ assert.equal(
1810
+ result.details.displayReason,
1811
+ "Approval confirmation required before saving final project setup artifacts.",
1812
+ );
1412
1813
  assert.match(result.content[0].text, /has not been confirmed/);
1413
1814
  assert.equal(existsSync(join(base, ".gsd", "REQUIREMENTS.md")), false);
1414
1815
 
@@ -1451,6 +1852,10 @@ test("executeSummarySave requires verified root approval in deep mode", async ()
1451
1852
 
1452
1853
  assert.equal(blocked.isError, true);
1453
1854
  assert.equal(blocked.details.error, "root_artifact_write_blocked");
1855
+ assert.equal(
1856
+ blocked.details.displayReason,
1857
+ "Approval confirmation required before saving final project setup artifacts.",
1858
+ );
1454
1859
  assert.match(blocked.content[0].text, /fail-closed/);
1455
1860
  assert.equal(existsSync(join(base, ".gsd", "PROJECT.md")), false);
1456
1861
 
@@ -1667,6 +2072,10 @@ test("executeSummarySave CONTEXT HARD BLOCK clears after write-gate state file i
1667
2072
  content: "# Context\n\ncontent",
1668
2073
  }, base));
1669
2074
  assert.equal(blocked.isError, true, "should be blocked without depth verification");
2075
+ assert.equal(
2076
+ blocked.details.displayReason,
2077
+ "Depth check required before writing milestone context.",
2078
+ );
1670
2079
  assert.match(
1671
2080
  blocked.content[0].text,
1672
2081
  /HARD BLOCK/,