@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
@@ -4,8 +4,11 @@
4
4
  import assert from "node:assert/strict";
5
5
  import test from "node:test";
6
6
 
7
+ import { registerToolCompatibility } from "@gsd/pi-coding-agent";
8
+
7
9
  import { DISCUSS_TOOLS_ALLOWLIST } from "../constants.ts";
8
10
  import { buildMinimalAutoGsdToolSet, buildMinimalGsdToolSet, buildMinimalGsdWorkflowToolSet, buildRequestScopedGsdToolSet, MINIMAL_AUTO_BASE_TOOL_NAMES, MINIMAL_GSD_TOOL_NAMES, requestHasGsdCustomType, restoreGsdWorkflowTools, scopeGsdWorkflowToolsForDispatch } from "../bootstrap/register-hooks.ts";
11
+ import { filterToolsForProvider } from "../model-router.ts";
9
12
  import { applyUnitSkillVisibility } from "../skill-scope.ts";
10
13
 
11
14
  test("buildMinimalGsdToolSet preserves non-GSD tools and replaces broad GSD surface", () => {
@@ -31,7 +34,7 @@ test("buildMinimalGsdToolSet preserves non-GSD tools and replaces broad GSD surf
31
34
  for (const toolName of MINIMAL_GSD_TOOL_NAMES) {
32
35
  assert.ok(result.includes(toolName), `expected ${toolName}`);
33
36
  }
34
- assert.ok(!result.includes("gsd_plan_milestone"));
37
+ assert.ok(result.includes("gsd_plan_milestone"));
35
38
  assert.ok(!result.includes("gsd_task_complete"));
36
39
  assert.ok(!result.includes("gsd_graph"));
37
40
  });
@@ -98,22 +101,39 @@ test("buildMinimalAutoGsdToolSet keeps unit-specific completion tools without al
98
101
  assert.ok(!result.includes("gsd_complete_slice"));
99
102
  });
100
103
 
101
- test("buildMinimalAutoGsdToolSet re-resolves run-uat browser tools from the registry when dropped from the active set", () => {
102
- // B2 drops the browser surface from the advertised interactive set, but the
103
- // tools stay registered. run-uat must still get them: resolution reads the
104
- // full registry, not just the (browser-stripped) active set.
105
- const active = ["ask_user_questions", "bash", "read", "gsd_summary_save"];
104
+ test("buildMinimalAutoGsdToolSet scopes run-uat to UAT-specific tools", () => {
105
+ const active = ["ask_user_questions", "bash", "read", "edit", "write", "gsd_summary_save"];
106
106
  const registered = [
107
107
  ...active,
108
+ "gsd_uat_exec",
109
+ "gsd_uat_result_save",
110
+ "gsd_resume",
111
+ "gsd_milestone_status",
112
+ "gsd_journal_query",
113
+ "gsd_exec",
114
+ "gsd_save_gate_result",
115
+ "search-the-web",
108
116
  "browser_navigate",
109
117
  "browser_click",
110
118
  "browser_snapshot_refs",
111
- "gsd_exec",
112
119
  ];
113
120
  const result = buildMinimalAutoGsdToolSet(active, "run-uat", registered);
121
+ assert.ok(result.includes("gsd_uat_exec"));
122
+ assert.ok(result.includes("gsd_uat_result_save"));
123
+ assert.ok(result.includes("gsd_resume"));
124
+ assert.ok(result.includes("gsd_milestone_status"));
125
+ assert.ok(result.includes("gsd_journal_query"));
114
126
  assert.ok(result.includes("browser_navigate"), "run-uat needs browser_navigate");
115
127
  assert.ok(result.includes("browser_click"), "run-uat needs browser_click");
116
- assert.ok(result.includes("gsd_summary_save"));
128
+ assert.ok(!result.includes("ToolSearch"));
129
+ assert.ok(!result.includes("bash"));
130
+ assert.ok(!result.includes("read"));
131
+ assert.ok(!result.includes("edit"));
132
+ assert.ok(!result.includes("write"));
133
+ assert.ok(!result.includes("gsd_exec"));
134
+ assert.ok(!result.includes("gsd_summary_save"));
135
+ assert.ok(!result.includes("gsd_save_gate_result"));
136
+ assert.ok(!result.includes("search-the-web"));
117
137
  });
118
138
 
119
139
  test("buildMinimalAutoGsdToolSet keeps only the auto base non-GSD tools", () => {
@@ -175,17 +195,27 @@ test("buildMinimalAutoGsdToolSet re-injects registered base tools filtered from
175
195
  assert.ok(result.includes("search-the-web"));
176
196
  });
177
197
 
178
- test("buildMinimalAutoGsdToolSet preserves browser tools for run-uat", () => {
198
+ test("buildMinimalAutoGsdToolSet preserves compatible browser add-ons for run-uat", () => {
179
199
  const result = buildMinimalAutoGsdToolSet([
180
200
  "bash",
181
201
  "read",
202
+ "edit",
203
+ "write",
182
204
  "browser_navigate",
183
205
  "browser_click",
184
206
  "browser_type",
185
207
  "browser_assert",
186
208
  "browser_screenshot",
187
209
  "browser_wait_for",
210
+ "gsd_uat_exec",
211
+ "gsd_uat_result_save",
212
+ "gsd_resume",
213
+ "gsd_milestone_status",
214
+ "gsd_journal_query",
215
+ "subagent",
188
216
  "gsd_summary_save",
217
+ "gsd_exec",
218
+ "gsd_save_gate_result",
189
219
  "gsd_task_complete",
190
220
  "memory_query",
191
221
  "capture_thought",
@@ -197,7 +227,17 @@ test("buildMinimalAutoGsdToolSet preserves browser tools for run-uat", () => {
197
227
  assert.ok(result.includes("browser_assert"));
198
228
  assert.ok(result.includes("browser_screenshot"));
199
229
  assert.ok(result.includes("browser_wait_for"));
200
- assert.ok(result.includes("gsd_summary_save"));
230
+ assert.ok(result.includes("gsd_uat_exec"));
231
+ assert.ok(result.includes("gsd_uat_result_save"));
232
+ assert.ok(result.includes("subagent"));
233
+ assert.ok(!result.includes("ToolSearch"));
234
+ assert.ok(!result.includes("bash"));
235
+ assert.ok(!result.includes("read"));
236
+ assert.ok(!result.includes("edit"));
237
+ assert.ok(!result.includes("write"));
238
+ assert.ok(!result.includes("gsd_exec"));
239
+ assert.ok(!result.includes("gsd_summary_save"));
240
+ assert.ok(!result.includes("gsd_save_gate_result"));
201
241
  assert.ok(!result.includes("gsd_task_complete"));
202
242
  });
203
243
 
@@ -218,6 +258,40 @@ test("buildMinimalAutoGsdToolSet prefers MCP browser tools for run-uat when avai
218
258
  assert.ok(!result.includes("browser_click"));
219
259
  });
220
260
 
261
+ test("buildMinimalAutoGsdToolSet honors provider-compatible registered tools for run-uat", () => {
262
+ registerToolCompatibility("browser_screenshot", { producesImages: true });
263
+ const registered = [
264
+ "bash",
265
+ "read",
266
+ "ToolSearch",
267
+ "browser_navigate",
268
+ "browser_click",
269
+ "browser_screenshot",
270
+ "gsd_uat_exec",
271
+ "gsd_uat_result_save",
272
+ "gsd_resume",
273
+ "gsd_milestone_status",
274
+ "gsd_journal_query",
275
+ "gsd_exec",
276
+ "gsd_summary_save",
277
+ "gsd_save_gate_result",
278
+ ];
279
+ const providerCompatible = filterToolsForProvider(registered, "openai-responses").compatible;
280
+ const result = buildMinimalAutoGsdToolSet(["gsd_uat_exec"], "run-uat", providerCompatible);
281
+
282
+ assert.ok(result.includes("gsd_uat_exec"));
283
+ assert.ok(result.includes("gsd_uat_result_save"));
284
+ assert.ok(result.includes("browser_navigate"));
285
+ assert.ok(result.includes("browser_click"));
286
+ assert.ok(!result.includes("browser_screenshot"), "provider-filtered screenshot tool must stay filtered");
287
+ assert.ok(!result.includes("ToolSearch"));
288
+ assert.ok(!result.includes("bash"));
289
+ assert.ok(!result.includes("read"));
290
+ assert.ok(!result.includes("gsd_exec"));
291
+ assert.ok(!result.includes("gsd_summary_save"));
292
+ assert.ok(!result.includes("gsd_save_gate_result"));
293
+ });
294
+
221
295
  test("buildMinimalAutoGsdToolSet includes discuss-slice persistence tools", () => {
222
296
  const result = buildMinimalAutoGsdToolSet([
223
297
  "bash",
@@ -39,6 +39,12 @@ const RENAME_MAP: Array<{ canonical: string; alias: string }> = [
39
39
  { canonical: "gsd_milestone_reopen", alias: "gsd_reopen_milestone" },
40
40
  ];
41
41
 
42
+ const STANDALONE_TOOLS = [
43
+ "gsd_save_gate_result",
44
+ "gsd_skip_slice",
45
+ "gsd_uat_result_save",
46
+ ];
47
+
42
48
  // ─── Registration count ──────────────────────────────────────────────────────
43
49
 
44
50
  console.log('\n── Tool naming: registration count ──');
@@ -48,10 +54,14 @@ registerDbTools(pi);
48
54
 
49
55
  assert.deepStrictEqual(
50
56
  pi.tools.length,
51
- RENAME_MAP.length * 2 + 2,
52
- 'Should register canonical/alias tool pairs plus 1 gate tool and 1 gsd_skip_slice',
57
+ RENAME_MAP.length * 2 + STANDALONE_TOOLS.length,
58
+ 'Should register canonical/alias tool pairs plus standalone DB tools',
53
59
  );
54
60
 
61
+ for (const name of STANDALONE_TOOLS) {
62
+ assert.ok(pi.tools.some((t: any) => t.name === name), `Standalone tool "${name}" should be registered`);
63
+ }
64
+
55
65
  // ─── Both names exist for each pair ──────────────────────────────────────────
56
66
 
57
67
  console.log('\n── Tool naming: canonical and alias names exist ──');
@@ -7,12 +7,17 @@
7
7
 
8
8
  import test from "node:test";
9
9
  import assert from "node:assert/strict";
10
- import { mkdirSync, rmSync } from "node:fs";
10
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { tmpdir } from "node:os";
13
13
  import { visibleWidth } from "@gsd/pi-tui";
14
14
 
15
- import { setCompletionProgressWidget, updateProgressWidget } from "../auto-dashboard.ts";
15
+ import {
16
+ _resetWidgetModeForTests,
17
+ setCompletionProgressWidget,
18
+ setWidgetMode,
19
+ updateProgressWidget,
20
+ } from "../auto-dashboard.ts";
16
21
  import type { GSDState } from "../types.ts";
17
22
 
18
23
  interface CapturedSetHeader {
@@ -143,10 +148,18 @@ test("updateProgressWidget gracefully no-ops when ctx.ui lacks setHeader/setStat
143
148
 
144
149
  // ── NEXT-mode footer guidance ───────────────────────────────────────────
145
150
 
146
- test("auto-dashboard widget render output includes /gsd next guidance when isStepMode is true", (t) => {
151
+ test("auto-dashboard full widget render output includes /gsd next guidance when isStepMode is true", (t) => {
147
152
  const dir = makeTempDir("step-hint");
148
153
  mkdirSync(join(dir, ".gsd"), { recursive: true });
149
- t.after(() => cleanup(dir));
154
+ const projectPrefsPath = join(dir, ".gsd", "preferences.md");
155
+ const globalPrefsPath = join(dir, ".gsd", "global-preferences.md");
156
+ writeFileSync(projectPrefsPath, "---\nversion: 1\n---\n", "utf-8");
157
+ _resetWidgetModeForTests();
158
+ setWidgetMode("full", projectPrefsPath, globalPrefsPath);
159
+ t.after(() => {
160
+ _resetWidgetModeForTests();
161
+ cleanup(dir);
162
+ });
150
163
 
151
164
  let widgetFactory: ((tui: unknown, theme: unknown) => any) | undefined;
152
165
 
@@ -173,6 +186,7 @@ test("auto-dashboard widget render output includes /gsd next guidance when isSte
173
186
  bold: (text: string) => text,
174
187
  };
175
188
  const component = widgetFactory!(fakeTui, fakeTheme);
189
+ t.after(() => component.dispose?.());
176
190
  const lines = component.render(120);
177
191
 
178
192
  const hasStepHint = lines.some((line: string) => line.includes("/gsd next to advance one step"));
@@ -181,10 +195,18 @@ test("auto-dashboard widget render output includes /gsd next guidance when isSte
181
195
  if (component.dispose) component.dispose();
182
196
  });
183
197
 
184
- test("auto-dashboard widget render output omits /gsd next guidance when isStepMode is false", (t) => {
198
+ test("auto-dashboard full widget render output omits /gsd next guidance when isStepMode is false", (t) => {
185
199
  const dir = makeTempDir("no-step-hint");
186
200
  mkdirSync(join(dir, ".gsd"), { recursive: true });
187
- t.after(() => cleanup(dir));
201
+ const projectPrefsPath = join(dir, ".gsd", "preferences.md");
202
+ const globalPrefsPath = join(dir, ".gsd", "global-preferences.md");
203
+ writeFileSync(projectPrefsPath, "---\nversion: 1\n---\n", "utf-8");
204
+ _resetWidgetModeForTests();
205
+ setWidgetMode("full", projectPrefsPath, globalPrefsPath);
206
+ t.after(() => {
207
+ _resetWidgetModeForTests();
208
+ cleanup(dir);
209
+ });
188
210
 
189
211
  let widgetFactory: ((tui: unknown, theme: unknown) => any) | undefined;
190
212
 
@@ -211,6 +233,7 @@ test("auto-dashboard widget render output omits /gsd next guidance when isStepMo
211
233
  bold: (text: string) => text,
212
234
  };
213
235
  const component = widgetFactory!(fakeTui, fakeTheme);
236
+ t.after(() => component.dispose?.());
214
237
  const lines = component.render(120);
215
238
 
216
239
  const hasStepHint = lines.some((line: string) => line.includes("/gsd next to advance one step"));
@@ -344,6 +344,7 @@ test("#5843: run-uat uses verification tools policy so build/test commands can r
344
344
  const manifest = UNIT_MANIFESTS["run-uat"];
345
345
 
346
346
  assert.strictEqual(manifest.tools.mode, "verification");
347
+ assert.deepEqual(manifest.tools.allowedSubagents, ["mnemo", "scout", "reviewer", "tester"]);
347
348
 
348
349
  const buildResult = shouldBlockPlanningUnit(
349
350
  "bash",
@@ -367,6 +368,20 @@ test("#5843: run-uat uses verification tools policy so build/test commands can r
367
368
  );
368
369
  assert.strictEqual(sourceWriteResult.block, true);
369
370
  assert.match(sourceWriteResult.reason!, /tools-policy "verification"/);
371
+
372
+ const subagentResult = shouldBlockPlanningUnit(
373
+ "subagent",
374
+ "",
375
+ process.cwd(),
376
+ "run-uat",
377
+ manifest.tools,
378
+ ["mnemo"],
379
+ );
380
+ assert.strictEqual(
381
+ subagentResult.block,
382
+ false,
383
+ `run-uat must allow listed verification subagents: ${subagentResult.reason}`,
384
+ );
370
385
  });
371
386
 
372
387
  test("planning-dispatch hard block message omits internal tracker references", () => {
@@ -420,6 +435,11 @@ test('Unit Tool Contract exposes subagent dispatch permissions', () => {
420
435
  allowedSubagents: ["reviewer", "security", "tester"],
421
436
  toolsMode: "planning-dispatch",
422
437
  });
438
+ assert.deepEqual(resolveSubagentPermissionContract("run-uat"), {
439
+ allowed: true,
440
+ allowedSubagents: ["mnemo", "scout", "reviewer", "tester"],
441
+ toolsMode: "verification",
442
+ });
423
443
  assert.deepEqual(resolveSubagentPermissionContract("discuss-milestone"), {
424
444
  allowed: false,
425
445
  allowedSubagents: [],
@@ -427,21 +447,24 @@ test('Unit Tool Contract exposes subagent dispatch permissions', () => {
427
447
  });
428
448
  });
429
449
 
430
- test('planning-dispatch manifests declare non-empty allowedSubagents lists', () => {
450
+ test('dispatch-capable manifests declare globally allowed subagents', () => {
431
451
  for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
432
- if (manifest.tools.mode !== "planning-dispatch") continue;
452
+ const allowedSubagents = "allowedSubagents" in manifest.tools
453
+ ? manifest.tools.allowedSubagents
454
+ : undefined;
455
+ if (!allowedSubagents) continue;
433
456
  assert.ok(
434
- Array.isArray(manifest.tools.allowedSubagents) && manifest.tools.allowedSubagents.length > 0,
435
- `manifest "${unitType}" has planning-dispatch policy but no allowedSubagents — explicit allowlist is required for runtime dispatch gating`,
457
+ Array.isArray(allowedSubagents) && allowedSubagents.length > 0,
458
+ `manifest "${unitType}" enables subagent dispatch but has no allowedSubagents — explicit allowlist is required for runtime dispatch gating`,
436
459
  );
437
- for (const agent of manifest.tools.allowedSubagents) {
460
+ for (const agent of allowedSubagents) {
438
461
  assert.ok(
439
462
  typeof agent === "string" && agent.length > 0,
440
463
  `manifest "${unitType}" has empty/invalid allowedSubagents entry: ${JSON.stringify(agent)}`,
441
464
  );
442
465
  assert.ok(
443
466
  ALLOWED_PLANNING_DISPATCH_AGENTS.has(agent),
444
- `manifest "${unitType}" allows "${agent}", but the runtime planning-dispatch registry will hard-block it`,
467
+ `manifest "${unitType}" allows "${agent}", but the runtime controlled-dispatch registry will hard-block it`,
445
468
  );
446
469
  }
447
470
  }
@@ -54,6 +54,8 @@ test("validation block allows recovery, diagnostics, and unrelated commands", ()
54
54
  "status",
55
55
  "verdict pass --rationale ok",
56
56
  "validate-milestone",
57
+ "dispatch reassess",
58
+ "dispatch reassess-roadmap",
57
59
  "dispatch validate",
58
60
  "dispatch validate-milestone",
59
61
  "park M006",
@@ -122,3 +124,22 @@ test("validation block message includes attempted command and recovery options",
122
124
  assert.match(message, /\/gsd verdict pass --rationale/);
123
125
  assert.match(message, /\/gsd park M006/);
124
126
  });
127
+
128
+ test("validation block message can guide remediation through dispatch reassess", () => {
129
+ const message = formatValidationBlockedMessage({
130
+ ...blockedState(),
131
+ blockers: [
132
+ [
133
+ "Milestone M006 is blocked because milestone validation returned needs-remediation, but all slices are complete.",
134
+ "Fix options:",
135
+ "1. Run `/gsd dispatch reassess` to add remediation slices, then run `/gsd auto`",
136
+ "2. If the finding is acceptable, override it: `/gsd verdict pass --rationale \"why this is okay\"`",
137
+ "3. If this should wait, defer it explicitly: `/gsd park M006`",
138
+ ].join("\n"),
139
+ ],
140
+ }, "auto");
141
+
142
+ assert.ok(message);
143
+ assert.match(message, /\/gsd dispatch reassess/);
144
+ assert.doesNotMatch(message, /gsd_reassess_roadmap/);
145
+ });
@@ -19,6 +19,14 @@ test("shouldAutoPrepareWorkflowMcp enables prep for externalCli local transport"
19
19
  assert.equal(result, true);
20
20
  });
21
21
 
22
+ test("shouldAutoPrepareWorkflowMcp enables prep when Claude Code provider is known before auth mode settles", () => {
23
+ const result = shouldAutoPrepareWorkflowMcp({
24
+ model: { provider: "claude-code", baseUrl: "local://claude-code" },
25
+ });
26
+
27
+ assert.equal(result, true);
28
+ });
29
+
22
30
  test("prepareWorkflowMcpForProject uses the selected unit model when session provider differs", (t) => {
23
31
  const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-unit-model-"));
24
32
  const notifications: Array<{ message: string; level: "info" | "warning" | "error" | "success" }> = [];
@@ -46,7 +54,7 @@ test("prepareWorkflowMcpForProject uses the selected unit model when session pro
46
54
 
47
55
  assert.equal(result?.status, "created");
48
56
  assert.equal(existsSync(join(projectRoot, ".mcp.json")), true);
49
- assert.match(notifications.map((entry) => entry.message).join("\n"), /Claude Code MCP prepared/);
57
+ assert.match(notifications.map((entry) => entry.message).join("\n"), /GSD MCP Server Prepared/);
50
58
  });
51
59
 
52
60
  test("shouldAutoPrepareWorkflowMcp stays disabled for non-Claude active provider even when claude-code is ready", () => {
@@ -163,7 +171,14 @@ test("before_agent_start auto-prepares project workflow MCP for Claude Code CLI"
163
171
  };
164
172
  assert.ok(parsed.mcpServers?.[GSD_WORKFLOW_MCP_SERVER_NAME]);
165
173
  assert.ok(parsed.mcpServers?.[GSD_BROWSER_MCP_SERVER_NAME]);
166
- assert.match(notifications.join("\n"), /Claude Code MCP prepared/);
174
+ const settings = JSON.parse(readFileSync(join(projectRoot, ".claude", "settings.local.json"), "utf-8")) as {
175
+ enabledMcpjsonServers?: string[];
176
+ };
177
+ assert.deepEqual(settings.enabledMcpjsonServers, [
178
+ GSD_WORKFLOW_MCP_SERVER_NAME,
179
+ GSD_BROWSER_MCP_SERVER_NAME,
180
+ ]);
181
+ assert.match(notifications.join("\n"), /GSD MCP Server Prepared/);
167
182
  });
168
183
 
169
184
  test("before_agent_start returns discovered skill fallback without project .gsd", async (t) => {
@@ -30,6 +30,7 @@ import {
30
30
  executeSliceComplete,
31
31
  executeSliceReopen,
32
32
  executeValidateMilestone,
33
+ executeUatResultSave,
33
34
  } from "../tools/workflow-tool-executors.ts";
34
35
 
35
36
  function makeTmpBase(): string {
@@ -504,6 +505,88 @@ test("executePlanSlice marks validation failures with isError", async () => {
504
505
  }
505
506
  });
506
507
 
508
+ test("executeUatResultSave accepts gsd_uat_exec evidence written in a milestone worktree", async () => {
509
+ const base = makeTmpBase();
510
+ const worktree = join(base, ".gsd", "worktrees", "M001");
511
+ const worktreeExecDir = join(worktree, ".gsd", "exec");
512
+ const browserTimelineDir = join(base, ".artifacts", "browser", "session");
513
+ const evidenceId = "worktree-uat-evidence";
514
+ const browserTimelinePath = join(browserTimelineDir, "s02-uat-browser-timeline.json");
515
+ try {
516
+ openTestDb(base);
517
+ seedMilestone("M001", "Milestone One");
518
+ seedSlice("M001", "S02", "complete");
519
+ mkdirSync(worktreeExecDir, { recursive: true });
520
+ mkdirSync(browserTimelineDir, { recursive: true });
521
+ writeFileSync(browserTimelinePath, JSON.stringify({ summary: "browser timeline evidence" }), "utf-8");
522
+ writeFileSync(
523
+ join(worktreeExecDir, `${evidenceId}.meta.json`),
524
+ JSON.stringify({
525
+ id: evidenceId,
526
+ metadata: {
527
+ kind: "uat_exec",
528
+ milestoneId: "M001",
529
+ sliceId: "S02",
530
+ checkId: "UAT-01",
531
+ intent: "uat-runtime-check",
532
+ },
533
+ }),
534
+ "utf-8",
535
+ );
536
+
537
+ const result = await inProjectDir(worktree, () => executeUatResultSave({
538
+ milestoneId: "M001",
539
+ sliceId: "S02",
540
+ uatType: "runtime-executable",
541
+ verdict: "PASS",
542
+ checks: [{
543
+ id: "UAT-01",
544
+ description: "Runtime path C:\\tmp|uat evidence was captured in the active worktree",
545
+ mode: "runtime",
546
+ result: "PASS",
547
+ evidence: [
548
+ { kind: "gsd_uat_exec", ref: evidenceId },
549
+ { kind: "browser", ref: browserTimelinePath },
550
+ ],
551
+ notes: "Worktree-local gsd_uat_exec metadata should resolve with backslash \\ and pipe |.",
552
+ }],
553
+ presentation: {
554
+ surface: "mcp",
555
+ presentedTools: [
556
+ "gsd_uat_exec",
557
+ "gsd_uat_result_save",
558
+ "gsd_resume",
559
+ "gsd_milestone_status",
560
+ "gsd_journal_query",
561
+ ],
562
+ blockedTools: [
563
+ { name: "gsd_exec", reason: "forbidden during run-uat" },
564
+ { name: "gsd_summary_save", reason: "forbidden during run-uat" },
565
+ { name: "gsd_save_gate_result", reason: "forbidden during run-uat" },
566
+ ],
567
+ },
568
+ notes: "UAT passed with worktree-local evidence.",
569
+ }, worktree));
570
+
571
+ assert.equal(result.isError, undefined);
572
+ assert.equal(result.details.operation, "save_uat_result");
573
+ assert.equal(result.details.verdict, "PASS");
574
+ assert.ok(
575
+ existsSync(join(base, ".gsd", "uat", "M001", "S02", "attempt-1.json")),
576
+ "attempt JSON should be persisted under the authoritative project .gsd",
577
+ );
578
+ const assessment = readFileSync(
579
+ join(base, ".gsd", "milestones", "M001", "slices", "S02", "S02-ASSESSMENT.md"),
580
+ "utf-8",
581
+ );
582
+ assert.match(assessment, /Runtime path C:\\\\tmp\\\|uat evidence/);
583
+ assert.match(assessment, /backslash \\\\ and pipe \\\|/);
584
+ } finally {
585
+ closeDatabase();
586
+ cleanup(base);
587
+ }
588
+ });
589
+
507
590
  test("executeSliceComplete coerces string enrichment entries and writes summary/UAT artifacts", async () => {
508
591
  const base = makeTmpBase();
509
592
  try {
@@ -27,6 +27,10 @@ const PLANNING_DISPATCH_REVIEW: ToolsPolicy = {
27
27
  const READ_ONLY: ToolsPolicy = { mode: 'read-only' };
28
28
  const ALL: ToolsPolicy = { mode: 'all' };
29
29
  const VERIFICATION: ToolsPolicy = { mode: 'verification' };
30
+ const VERIFICATION_UAT: ToolsPolicy = {
31
+ mode: 'verification',
32
+ allowedSubagents: ['mnemo', 'scout', 'reviewer', 'tester'],
33
+ };
30
34
  const DOCS: ToolsPolicy = {
31
35
  mode: 'docs',
32
36
  allowedPathGlobs: ['docs/**', 'README.md', 'README.*.md', 'CHANGELOG.md', '*.md'],
@@ -469,6 +473,27 @@ test('verification-mode: run-uat still blocks subagent dispatch', () => {
469
473
  assert.match(r.reason!, /subagent dispatch is not permitted/);
470
474
  });
471
475
 
476
+ test('verification-mode: run-uat allows explicit UAT specialist subagents', () => {
477
+ for (const agent of ['mnemo', 'scout', 'reviewer', 'tester']) {
478
+ const r = shouldBlockPlanningUnit('subagent', '', BASE, 'run-uat', VERIFICATION_UAT, [agent]);
479
+ assert.strictEqual(r.block, false, `expected ${agent} to be allowed: ${r.reason}`);
480
+ }
481
+ });
482
+
483
+ test('verification-mode: run-uat blocks implementation-tier subagents', () => {
484
+ const r = shouldBlockPlanningUnit('subagent', '', BASE, 'run-uat', VERIFICATION_UAT, ['worker']);
485
+ assert.strictEqual(r.block, true);
486
+ assert.match(r.reason!, /"worker"/);
487
+ assert.match(r.reason!, /read-only specialists/);
488
+ });
489
+
490
+ test('verification-mode: run-uat blocks read-only specialists not listed by policy', () => {
491
+ const r = shouldBlockPlanningUnit('subagent', '', BASE, 'run-uat', VERIFICATION_UAT, ['security']);
492
+ assert.strictEqual(r.block, true);
493
+ assert.match(r.reason!, /"security"/);
494
+ assert.match(r.reason!, /ToolsPolicy\.allowedSubagents|permitted agents for this unit/);
495
+ });
496
+
472
497
  // ─── read-only mode ───────────────────────────────────────────────────────
473
498
 
474
499
  test('read-only: blocks any edit even to .gsd/', () => {