@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
@@ -114,6 +114,18 @@ const UNIT_TYPE_SKILL_MANIFEST = {
114
114
  "review",
115
115
  "accessibility",
116
116
  ],
117
+ // Slice closeout — the "closer" role: verify assembled task work, write the
118
+ // downstream-ready summary + UAT, optionally drive reviewer/security/tester
119
+ // subagents. Predictable skill set, mirrors `complete-milestone`.
120
+ "complete-slice": [
121
+ "verify-before-complete",
122
+ "test",
123
+ "review",
124
+ "security-review",
125
+ "write-docs",
126
+ "observability",
127
+ "handoff",
128
+ ],
117
129
  // `execute-task` intentionally omitted — implementation hot path covers a
118
130
  // wide surface of technologies; wildcard fallback preserves today's
119
131
  // behavior until per-task skill hints can be derived from task-plan
@@ -2,8 +2,10 @@
2
2
  // File Purpose: ADR-015 Tool Contract module for Unit prompt, policy, and tool parity.
3
3
  import { resolveManifest, } from "./unit-context-manifest.js";
4
4
  import { getRequiredWorkflowToolsForAutoUnit } from "./workflow-mcp.js";
5
+ import { getUnitToolSurfaceContract } from "./unit-tool-contracts.js";
5
6
  export function compileUnitToolContract(unitType) {
6
7
  const manifest = resolveManifest(unitType);
8
+ const surfaceContract = getUnitToolSurfaceContract(unitType);
7
9
  if (!manifest) {
8
10
  return {
9
11
  ok: false,
@@ -12,7 +14,9 @@ export function compileUnitToolContract(unitType) {
12
14
  };
13
15
  }
14
16
  const requiredWorkflowTools = getRequiredWorkflowToolsForAutoUnit(unitType);
15
- const closeoutTools = requiredWorkflowTools.filter((tool) => /^gsd_(?:task|slice|milestone|complete|validate|save|summary)/.test(tool));
17
+ const forbiddenWorkflowTools = Object.entries(surfaceContract?.forbiddenGsdTools ?? {})
18
+ .map(([name, reason]) => ({ name, reason }));
19
+ const closeoutTools = requiredWorkflowTools.filter((tool) => /^gsd_(?:task|slice|milestone|complete|validate|save|summary|uat)/.test(tool));
16
20
  if (requiresCloseoutTool(unitType) && closeoutTools.length === 0) {
17
21
  return {
18
22
  ok: false,
@@ -27,6 +31,7 @@ export function compileUnitToolContract(unitType) {
27
31
  contextMode: manifest.contextMode,
28
32
  toolsPolicy: manifest.tools,
29
33
  requiredWorkflowTools,
34
+ forbiddenWorkflowTools,
30
35
  promptObligations: [
31
36
  `context-mode:${manifest.contextMode}`,
32
37
  `tools-policy:${manifest.tools.mode}`,
@@ -1,12 +1,7 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: Resolve phase-aware tool surfaces for GSD model presentations.
3
- export const RUN_UAT_WORKFLOW_TOOL_NAMES = [
4
- "gsd_uat_exec",
5
- "gsd_uat_result_save",
6
- "gsd_resume",
7
- "gsd_milestone_status",
8
- "gsd_journal_query",
9
- ];
3
+ import { RUN_UAT_BROWSER_TOOL_NAMES, RUN_UAT_READ_ONLY_TOOL_NAMES, RUN_UAT_TOOL_PRESENTATION_PLAN_ID, RUN_UAT_WORKFLOW_TOOL_NAMES, } from "./unit-tool-contracts.js";
4
+ export { RUN_UAT_BROWSER_TOOL_NAMES, RUN_UAT_READ_ONLY_TOOL_NAMES, RUN_UAT_TOOL_PRESENTATION_PLAN_ID, RUN_UAT_WORKFLOW_TOOL_NAMES, } from "./unit-tool-contracts.js";
10
5
  export const RUN_UAT_FORBIDDEN_TOOL_NAMES = [
11
6
  "edit",
12
7
  "write",
@@ -74,9 +69,54 @@ function addBlockedTool(blocked, name, reason) {
74
69
  export function buildRunUatCanonicalToolNames(options = {}) {
75
70
  return dedupe([
76
71
  ...RUN_UAT_WORKFLOW_TOOL_NAMES,
72
+ ...RUN_UAT_READ_ONLY_TOOL_NAMES,
77
73
  ...(options.includeBrowserTools ?? []),
78
74
  ]);
79
75
  }
76
+ // UAT modes whose run-uat instructions direct the runner to exercise the live
77
+ // app in a browser. These modes receive the browser tool surface so the runner
78
+ // can actually drive the page instead of silently deferring browser checks to a
79
+ // human. See run-uat.md automation rules: `browser-executable`, `live-runtime`,
80
+ // and `mixed` are all told to drive a browser/runtime path, and
81
+ // `human-experience` is told to capture screenshots. Without this, a webpage
82
+ // UAT classified as anything but `browser-executable` had no browser tools and
83
+ // downgraded its live checks to NEEDS-HUMAN (M001/S03 regression).
84
+ export const BROWSER_INCLUSIVE_UAT_TYPES = [
85
+ "browser-executable",
86
+ "live-runtime",
87
+ "mixed",
88
+ "human-experience",
89
+ ];
90
+ function uatTypeIncludesBrowser(uatType) {
91
+ return uatType !== undefined && BROWSER_INCLUSIVE_UAT_TYPES.includes(uatType);
92
+ }
93
+ export function runUatBrowserToolsForType(uatType) {
94
+ return uatTypeIncludesBrowser(uatType) ? RUN_UAT_BROWSER_TOOL_NAMES : [];
95
+ }
96
+ export function runUatPresentationSurfaceForType(uatType) {
97
+ return uatTypeIncludesBrowser(uatType) ? "hybrid" : "mcp";
98
+ }
99
+ export function buildRunUatPresentationForType(uatType, options = {}) {
100
+ return buildRunUatResultPresentation({
101
+ ...options,
102
+ surface: options.surface ?? runUatPresentationSurfaceForType(uatType),
103
+ includeBrowserTools: runUatBrowserToolsForType(uatType),
104
+ });
105
+ }
106
+ export function buildRunUatResultPresentation(options = {}) {
107
+ const presentedTools = options.presentedTools
108
+ ? dedupe(options.presentedTools)
109
+ : buildRunUatCanonicalToolNames({ includeBrowserTools: options.includeBrowserTools });
110
+ const blockedTools = RUN_UAT_FORBIDDEN_TOOL_NAMES
111
+ .filter((toolName) => !toolName.includes("*"))
112
+ .map((name) => ({ name, reason: "forbidden during run-uat" }));
113
+ return {
114
+ surface: options.surface ?? "mcp",
115
+ presentedTools,
116
+ blockedTools,
117
+ toolPresentationPlanId: RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
118
+ };
119
+ }
80
120
  export function resolveToolPresentationPlan(options) {
81
121
  const requested = options.requestedToolNames ?? (options.phase === "run-uat"
82
122
  ? buildRunUatCanonicalToolNames({ includeBrowserTools: options.includeBrowserTools })
@@ -17,7 +17,8 @@ import { getGatesForTurn } from "../gate-registry.js";
17
17
  import { gsdProjectionRoot, clearPathCache, resolveMilestoneFile } from "../paths.js";
18
18
  import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
19
19
  import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
20
- import { saveFile, clearParseCache } from "../files.js";
20
+ import { saveFile, clearParseCache, extractUatType } from "../files.js";
21
+ import { hasBrowserRequiredText } from "../browser-evidence.js";
21
22
  import { invalidateStateCache } from "../state.js";
22
23
  import { renderRoadmapFromDb } from "../markdown-renderer.js";
23
24
  import { parseRoadmap } from "../parsers-legacy.js";
@@ -267,6 +268,32 @@ export async function handleCompleteSlice(params, basePath) {
267
268
  if (BLOCKED_SIGNALS.test(params.verification || "") || BLOCKED_SIGNALS.test(params.uatContent || "")) {
268
269
  return { error: `slice verification indicates blocked/failed state — do not complete a slice that has not passed verification. Address the blockers and re-verify first.` };
269
270
  }
271
+ // ── Browser/web UAT classification gate ────────────────────────────────
272
+ // A UAT that drives a running web UI (opening a page in a browser,
273
+ // navigating to a page/localhost) must declare a browser-capable mode so the
274
+ // run-uat runner surfaces browser tools and actually launches a browser.
275
+ // Otherwise the browser checks get silently deferred to a human and the slice
276
+ // passes on static checks alone (M001/S03 regression). `browser-executable`,
277
+ // `live-runtime`, and `mixed` all receive browser tools (see
278
+ // BROWSER_INCLUSIVE_UAT_TYPES); only the non-browser modes are rejected here.
279
+ //
280
+ // Reuse the canonical hasBrowserRequiredText detector (also used by dispatch
281
+ // and milestone validation): it skips Not-Proven/Out-of-Scope disclaimer
282
+ // sections and only treats verbs like navigate/open as web when they sit next
283
+ // to browser/page/localhost — avoiding false positives on CLI/file/API steps.
284
+ //
285
+ // Only `artifact-driven` is gated. It is the one mode that performs no
286
+ // execution at all (static/file checks), so a browser-requiring UAT under it
287
+ // genuinely defers verification to a human. Every other mode has a real
288
+ // verification path: `runtime-executable` runs browser test commands like
289
+ // `npx playwright test` via gsd_uat_exec, and live-runtime/mixed/
290
+ // browser-executable receive browser tools (BROWSER_INCLUSIVE_UAT_TYPES).
291
+ const declaredUatMode = extractUatType(params.uatContent || "") ?? "artifact-driven";
292
+ if (declaredUatMode === "artifact-driven" && hasBrowserRequiredText(params.uatContent || "")) {
293
+ return {
294
+ error: `UAT requires browser verification (opening a page in a browser, navigating to a page or localhost, screenshots) but declares "UAT mode: artifact-driven", which only runs static/file checks and would defer the browser work to a human. Use a mode that actually verifies the UI: "browser-executable" (interactive browser tools), "runtime-executable" (a browser test command such as playwright), or a browser-inclusive "mixed"/"live-runtime". Re-author the UAT Type section and complete the slice again.`,
295
+ };
296
+ }
270
297
  // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
271
298
  const completedAt = new Date().toISOString();
272
299
  let guardError = null;
@@ -6,8 +6,9 @@ import { loadWriteGateSnapshot, shouldBlockContextArtifactSaveInSnapshot, should
6
6
  import { getActiveRequirements, insertMilestone, getMilestone, getSliceStatusSummary, getSliceTaskCounts, insertGateRun, readTransaction, saveGateResult, upsertQualityGate, } from "../gsd-db.js";
7
7
  import { GATE_REGISTRY } from "../gate-registry.js";
8
8
  import { generateRequirementsMd, saveArtifactToDb } from "../db-writer.js";
9
- import { clearPathCache, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "../paths.js";
9
+ import { clearPathCache, relSliceFile, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "../paths.js";
10
10
  import { saveFile, clearParseCache } from "../files.js";
11
+ import { buildManualValidationGuidance, resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
11
12
  import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
12
13
  import { isAbsolute, join, resolve } from "node:path";
13
14
  import { handleCompleteMilestone } from "./complete-milestone.js";
@@ -26,7 +27,7 @@ import { invalidateStateCache } from "../state.js";
26
27
  import { loadEffectiveGSDPreferences } from "../preferences.js";
27
28
  import { parseProject } from "../schemas/parsers.js";
28
29
  import { getAutoRuntimeSnapshot } from "../auto-runtime-state.js";
29
- import { canonicalWorkflowToolName, parseMcpToolName, RUN_UAT_FORBIDDEN_TOOL_NAMES, RUN_UAT_WORKFLOW_TOOL_NAMES, } from "../tool-presentation-plan.js";
30
+ import { buildRunUatPresentationForType, canonicalWorkflowToolName, parseMcpToolName, RUN_UAT_FORBIDDEN_TOOL_NAMES, RUN_UAT_TOOL_PRESENTATION_PLAN_ID, RUN_UAT_WORKFLOW_TOOL_NAMES, } from "../tool-presentation-plan.js";
30
31
  export const SUPPORTED_SUMMARY_ARTIFACT_TYPES = [
31
32
  "SUMMARY",
32
33
  "RESEARCH",
@@ -53,7 +54,7 @@ function blockIfWrongAutoUnit(requiredUnitType, operation) {
53
54
  return null;
54
55
  if (snapshot.currentUnit.type === requiredUnitType)
55
56
  return null;
56
- const error = `HARD BLOCK: ${operation} may only run from ${requiredUnitType}; active unit is ${snapshot.currentUnit.type}. The orchestrator owns phase transitions.`;
57
+ const error = `HARD BLOCK: Tool Contract failure: ${operation} may only run from ${requiredUnitType}; active unit is ${snapshot.currentUnit.type}. Fix unit-tool-contracts.ts or the active Unit prompt. The orchestrator owns phase transitions.`;
57
58
  return {
58
59
  content: [{ type: "text", text: error }],
59
60
  details: { operation, error },
@@ -121,7 +122,11 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
121
122
  if (rootArtifactGuard.block) {
122
123
  return {
123
124
  content: [{ type: "text", text: `Error saving artifact: ${rootArtifactGuard.reason ?? "root artifact write blocked"}` }],
124
- details: { operation: "save_summary", error: "root_artifact_write_blocked" },
125
+ details: {
126
+ operation: "save_summary",
127
+ error: "root_artifact_write_blocked",
128
+ displayReason: "Approval confirmation required before saving final project setup artifacts.",
129
+ },
125
130
  isError: true,
126
131
  };
127
132
  }
@@ -129,7 +134,11 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
129
134
  if (contextGuard.block) {
130
135
  return {
131
136
  content: [{ type: "text", text: `Error saving artifact: ${contextGuard.reason ?? "context write blocked"}` }],
132
- details: { operation: "save_summary", error: "context_write_blocked" },
137
+ details: {
138
+ operation: "save_summary",
139
+ error: "context_write_blocked",
140
+ displayReason: "Depth check required before writing milestone context.",
141
+ },
133
142
  isError: true,
134
143
  };
135
144
  }
@@ -808,6 +817,52 @@ function errorResult(operation, message, error) {
808
817
  function isNonEmptyString(value) {
809
818
  return typeof value === "string" && value.trim().length > 0;
810
819
  }
820
+ function mergeBlockedTools(current, canonical) {
821
+ const merged = new Map();
822
+ for (const entry of [...(current ?? []), ...canonical]) {
823
+ merged.set(canonicalWorkflowToolName(parseMcpToolName(entry.name)?.tool ?? entry.name), entry);
824
+ }
825
+ return [...merged.values()];
826
+ }
827
+ function mergePresentedTools(current, canonical) {
828
+ return [...new Set([...(current ?? []), ...canonical])];
829
+ }
830
+ function normalizeUatVerdict(params) {
831
+ const raw = params;
832
+ if (typeof raw.verdict === "string") {
833
+ return { ...params, verdict: raw.verdict.toUpperCase() };
834
+ }
835
+ return params;
836
+ }
837
+ function supplyDefaultPresentation(params) {
838
+ const raw = params;
839
+ if (!raw.presentation) {
840
+ return { ...params, presentation: buildRunUatPresentationForType(params.uatType) };
841
+ }
842
+ return params;
843
+ }
844
+ function mergeCanonicalPresentation(params) {
845
+ const canonicalPresentation = buildRunUatPresentationForType(params.uatType);
846
+ const providedPresentation = params.presentation;
847
+ return {
848
+ ...params,
849
+ presentation: {
850
+ ...providedPresentation,
851
+ surface: providedPresentation.surface ?? canonicalPresentation.surface,
852
+ presentedTools: mergePresentedTools(providedPresentation.presentedTools, canonicalPresentation.presentedTools),
853
+ blockedTools: mergeBlockedTools(providedPresentation.blockedTools, canonicalPresentation.blockedTools),
854
+ toolPresentationPlanId: RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
855
+ },
856
+ };
857
+ }
858
+ const VALID_UAT_TYPES = [
859
+ "artifact-driven",
860
+ "browser-executable",
861
+ "runtime-executable",
862
+ "live-runtime",
863
+ "mixed",
864
+ "human-experience",
865
+ ];
811
866
  function ensureUatRequiredFields(params) {
812
867
  if (!isNonEmptyString(params.milestoneId))
813
868
  return "milestoneId is required";
@@ -815,6 +870,9 @@ function ensureUatRequiredFields(params) {
815
870
  return "sliceId is required";
816
871
  if (!isNonEmptyString(params.uatType))
817
872
  return "uatType is required";
873
+ if (!VALID_UAT_TYPES.includes(params.uatType)) {
874
+ return `uatType must be one of: ${VALID_UAT_TYPES.join(", ")}`;
875
+ }
818
876
  if (!["PASS", "FAIL", "PARTIAL"].includes(params.verdict))
819
877
  return "verdict must be PASS, FAIL, or PARTIAL";
820
878
  if (!Array.isArray(params.checks) || params.checks.length === 0)
@@ -951,6 +1009,12 @@ function validateUatChecks(basePath, params) {
951
1009
  }
952
1010
  return null;
953
1011
  }
1012
+ function validateFreshUatOwnedEvidence(params) {
1013
+ const hasFreshUatEvidence = params.checks.some((check) => (check.evidence ?? []).some((evidence) => evidence.kind === "gsd_uat_exec"));
1014
+ return hasFreshUatEvidence
1015
+ ? null
1016
+ : "UAT Assessment requires at least one fresh gsd_uat_exec evidence reference from run-uat";
1017
+ }
954
1018
  function validateUatMode(params) {
955
1019
  const modes = new Set(params.checks.map((check) => check.mode));
956
1020
  const hasHuman = params.checks.some((check) => check.result === "NEEDS-HUMAN");
@@ -1043,7 +1107,7 @@ function escapeMarkdownTableCell(value) {
1043
1107
  .replace(/[\\|]/g, (char) => `\\${char}`)
1044
1108
  .replace(/\r?\n/g, "<br>");
1045
1109
  }
1046
- function renderUatAssessment(params, attempt, gateVerdict) {
1110
+ function renderUatAssessment(params, attempt, gateVerdict, basePath) {
1047
1111
  const lines = [
1048
1112
  "---",
1049
1113
  `sliceId: ${params.sliceId}`,
@@ -1078,6 +1142,18 @@ function renderUatAssessment(params, attempt, gateVerdict) {
1078
1142
  "",
1079
1143
  `Aggregate UAT gate saved as ${gateVerdict}.`,
1080
1144
  ];
1145
+ // When any check still needs a human, point them at the exact checkout to
1146
+ // validate — critical for worktree milestones whose code sits under a hidden
1147
+ // `.gsd/worktrees/` path the reviewer would otherwise have to hunt for.
1148
+ const hasHuman = params.checks.some((check) => check.result === "NEEDS-HUMAN");
1149
+ if (hasHuman) {
1150
+ const guidance = buildManualValidationGuidance(basePath, params.milestoneId, {
1151
+ uatPath: relSliceFile(basePath, params.milestoneId, params.sliceId, "UAT"),
1152
+ });
1153
+ if (guidance) {
1154
+ lines.push("", "## Manual Validation", "", "One or more checks are marked `NEEDS-HUMAN` and require a person to validate:", "", ...guidance.split("\n").map((line) => `- ${line}`));
1155
+ }
1156
+ }
1081
1157
  return `${lines.join("\n")}\n`;
1082
1158
  }
1083
1159
  async function saveUatAttemptArtifact(basePath, params, attempt) {
@@ -1087,18 +1163,32 @@ async function saveUatAttemptArtifact(basePath, params, attempt) {
1087
1163
  return relativePath;
1088
1164
  }
1089
1165
  export async function executeUatResultSave(params, basePath = process.cwd()) {
1166
+ const unitGuard = blockIfWrongAutoUnit("run-uat", "save_uat_result");
1167
+ if (unitGuard)
1168
+ return unitGuard;
1169
+ // Phase 1: normalize verdict and supply the canonical presentation when none was provided.
1170
+ params = normalizeUatVerdict(params);
1171
+ params = supplyDefaultPresentation(params);
1090
1172
  const dbAvailable = await ensureDbOpen(basePath);
1091
1173
  if (!dbAvailable)
1092
1174
  return errorResult("save_uat_result", "GSD database is not available.", "db_unavailable");
1175
+ // Phase 2: validate the submitted presentation before the canonical merge so that
1176
+ // presentations missing required workflow tools are rejected rather than silently patched.
1093
1177
  const requiredError = ensureUatRequiredFields(params);
1094
1178
  if (requiredError)
1095
1179
  return errorResult("save_uat_result", requiredError, "invalid_params");
1096
1180
  const presentationError = validateCanonicalPresentation(params);
1097
1181
  if (presentationError)
1098
1182
  return errorResult("save_uat_result", presentationError, "alias_tool_name");
1183
+ // Phase 3: merge in the canonical plan ID and read-only audit tools so the persisted
1184
+ // artifact always carries the full audit surface even when the provider omitted them.
1185
+ params = mergeCanonicalPresentation(params);
1099
1186
  const checkError = validateUatChecks(basePath, params);
1100
1187
  if (checkError)
1101
1188
  return errorResult("save_uat_result", checkError, "invalid_evidence");
1189
+ const freshEvidenceError = validateFreshUatOwnedEvidence(params);
1190
+ if (freshEvidenceError)
1191
+ return errorResult("save_uat_result", freshEvidenceError, "missing_fresh_uat_evidence");
1102
1192
  const modeError = validateUatMode(params);
1103
1193
  if (modeError)
1104
1194
  return errorResult("save_uat_result", modeError, "uat_mode_mismatch");
@@ -1113,7 +1203,7 @@ export async function executeUatResultSave(params, basePath = process.cwd()) {
1113
1203
  }
1114
1204
  const gateVerdict = params.verdict === "PASS" ? "pass" : "flag";
1115
1205
  const rationale = params.notes ?? `UAT ${params.verdict} for ${params.sliceId}.`;
1116
- const assessment = renderUatAssessment(params, attempt, gateVerdict);
1206
+ const assessment = renderUatAssessment(params, attempt, gateVerdict, basePath);
1117
1207
  const summary = await executeSummarySave({
1118
1208
  milestone_id: params.milestoneId,
1119
1209
  slice_id: params.sliceId,
@@ -1155,8 +1245,20 @@ export async function executeUatResultSave(params, basePath = process.cwd()) {
1155
1245
  evaluatedAt,
1156
1246
  });
1157
1247
  invalidateStateCache();
1248
+ // Surface where to validate when checks are left for a human, so the path
1249
+ // (often a buried worktree checkout) reaches the reviewer, not just the file.
1250
+ const hasHuman = params.checks.some((check) => check.result === "NEEDS-HUMAN");
1251
+ const manualGuidance = hasHuman
1252
+ ? buildManualValidationGuidance(basePath, params.milestoneId, {
1253
+ uatPath: relSliceFile(basePath, params.milestoneId, params.sliceId, "UAT"),
1254
+ })
1255
+ : null;
1256
+ const savedText = `UAT result saved for ${params.milestoneId}/${params.sliceId}: ${params.verdict}`;
1158
1257
  return {
1159
- content: [{ type: "text", text: `UAT result saved for ${params.milestoneId}/${params.sliceId}: ${params.verdict}` }],
1258
+ content: [{
1259
+ type: "text",
1260
+ text: manualGuidance ? `${savedText}\n\nManual validation needed:\n${manualGuidance}` : savedText,
1261
+ }],
1160
1262
  details: {
1161
1263
  operation: "save_uat_result",
1162
1264
  milestoneId: params.milestoneId,
@@ -1166,6 +1268,9 @@ export async function executeUatResultSave(params, basePath = process.cwd()) {
1166
1268
  attempt,
1167
1269
  attemptPath,
1168
1270
  recommendedNextUnit: params.verdict === "PASS" ? null : "reactive-execute",
1271
+ ...(hasHuman
1272
+ ? { manualValidationPath: resolveCanonicalMilestoneRoot(basePath, params.milestoneId) }
1273
+ : {}),
1169
1274
  },
1170
1275
  };
1171
1276
  }
@@ -0,0 +1,193 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Central Unit-to-tool contracts for phase-aware GSD tool surfaces.
3
+ export const RUN_UAT_WORKFLOW_TOOL_NAMES = [
4
+ "gsd_uat_exec",
5
+ "gsd_uat_result_save",
6
+ "gsd_resume",
7
+ "gsd_milestone_status",
8
+ "gsd_journal_query",
9
+ ];
10
+ export const RUN_UAT_READ_ONLY_TOOL_NAMES = [
11
+ "find",
12
+ "glob",
13
+ "grep",
14
+ "ls",
15
+ "read",
16
+ ];
17
+ export const RUN_UAT_BROWSER_TOOL_NAMES = [
18
+ "browser_navigate",
19
+ "browser_click",
20
+ "browser_type",
21
+ "browser_fill_form",
22
+ "browser_click_ref",
23
+ "browser_fill_ref",
24
+ "browser_wait_for",
25
+ "browser_assert",
26
+ "browser_verify",
27
+ "browser_screenshot",
28
+ "browser_snapshot_refs",
29
+ "browser_find",
30
+ "browser_get_console_logs",
31
+ "browser_get_network_logs",
32
+ "browser_evaluate",
33
+ "browser_reload",
34
+ "browser_batch",
35
+ "browser_act",
36
+ ];
37
+ export const RUN_UAT_TOOL_PRESENTATION_PLAN_ID = "run-uat/default-v1";
38
+ export const UNIT_TOOL_CONTRACTS = {
39
+ "research-milestone": {
40
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
41
+ requiredWorkflowTools: ["gsd_summary_save"],
42
+ },
43
+ "plan-milestone": {
44
+ allowedGsdTools: [
45
+ "gsd_milestone_status",
46
+ "gsd_plan_milestone",
47
+ "gsd_plan_slice",
48
+ "gsd_decision_save",
49
+ "gsd_requirement_update",
50
+ ],
51
+ requiredWorkflowTools: ["gsd_milestone_status", "gsd_plan_milestone", "gsd_plan_slice"],
52
+ },
53
+ "discuss-milestone": {
54
+ allowedGsdTools: [
55
+ "gsd_summary_save",
56
+ "gsd_decision_save",
57
+ "gsd_requirement_save",
58
+ "gsd_requirement_update",
59
+ "gsd_plan_milestone",
60
+ "gsd_milestone_generate_id",
61
+ ],
62
+ requiredWorkflowTools: [
63
+ "gsd_summary_save",
64
+ "gsd_requirement_save",
65
+ "gsd_requirement_update",
66
+ "gsd_plan_milestone",
67
+ "gsd_milestone_generate_id",
68
+ ],
69
+ },
70
+ "discuss-slice": {
71
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
72
+ requiredWorkflowTools: ["gsd_summary_save"],
73
+ },
74
+ "validate-milestone": {
75
+ allowedGsdTools: ["gsd_milestone_status", "gsd_validate_milestone", "gsd_reassess_roadmap", "subagent"],
76
+ requiredWorkflowTools: ["gsd_milestone_status", "gsd_validate_milestone", "gsd_reassess_roadmap"],
77
+ },
78
+ "complete-milestone": {
79
+ allowedGsdTools: [
80
+ "gsd_milestone_status",
81
+ "gsd_requirement_update",
82
+ "gsd_summary_save",
83
+ "gsd_complete_milestone",
84
+ "subagent",
85
+ ],
86
+ requiredWorkflowTools: [
87
+ "gsd_milestone_status",
88
+ "gsd_requirement_update",
89
+ "gsd_summary_save",
90
+ "gsd_complete_milestone",
91
+ ],
92
+ },
93
+ "research-slice": {
94
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
95
+ requiredWorkflowTools: ["gsd_summary_save"],
96
+ },
97
+ "plan-slice": {
98
+ allowedGsdTools: ["gsd_plan_slice", "gsd_reassess_roadmap", "gsd_decision_save"],
99
+ requiredWorkflowTools: ["gsd_plan_slice", "gsd_reassess_roadmap"],
100
+ },
101
+ "refine-slice": {
102
+ allowedGsdTools: ["gsd_plan_slice", "gsd_decision_save"],
103
+ requiredWorkflowTools: ["gsd_plan_slice"],
104
+ },
105
+ "replan-slice": {
106
+ allowedGsdTools: ["gsd_replan_slice", "gsd_decision_save"],
107
+ requiredWorkflowTools: ["gsd_replan_slice"],
108
+ },
109
+ "complete-slice": {
110
+ allowedGsdTools: [
111
+ "gsd_slice_complete",
112
+ "gsd_task_reopen",
113
+ "gsd_replan_slice",
114
+ "gsd_decision_save",
115
+ "gsd_requirement_update",
116
+ "gsd_summary_save",
117
+ "subagent",
118
+ ],
119
+ requiredWorkflowTools: [
120
+ "gsd_slice_complete",
121
+ "gsd_task_reopen",
122
+ "gsd_replan_slice",
123
+ "gsd_requirement_update",
124
+ "gsd_summary_save",
125
+ ],
126
+ forbiddenGsdTools: {
127
+ gsd_uat_result_save: "Run UAT owns persisted UAT Assessment.",
128
+ },
129
+ },
130
+ "reassess-roadmap": {
131
+ allowedGsdTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
132
+ requiredWorkflowTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
133
+ },
134
+ "execute-task": {
135
+ allowedGsdTools: ["gsd_task_complete", "gsd_decision_save"],
136
+ requiredWorkflowTools: ["gsd_task_complete"],
137
+ },
138
+ "execute-task-simple": {
139
+ allowedGsdTools: ["gsd_task_complete", "gsd_decision_save"],
140
+ requiredWorkflowTools: ["gsd_task_complete"],
141
+ },
142
+ "reactive-execute": {
143
+ allowedGsdTools: ["gsd_task_complete", "gsd_summary_save", "gsd_decision_save"],
144
+ requiredWorkflowTools: ["gsd_task_complete", "gsd_summary_save"],
145
+ },
146
+ "run-uat": {
147
+ allowedGsdTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent"],
148
+ requiredWorkflowTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES],
149
+ forbiddenGsdTools: {
150
+ gsd_exec: "Use gsd_uat_exec so acceptance evidence is typed as UAT-owned.",
151
+ gsd_save_gate_result: "gsd_uat_result_save owns the aggregate UAT gate.",
152
+ gsd_summary_save: "gsd_uat_result_save owns persisted UAT Assessment writes.",
153
+ },
154
+ },
155
+ "gate-evaluate": {
156
+ allowedGsdTools: ["gsd_save_gate_result"],
157
+ requiredWorkflowTools: ["gsd_save_gate_result"],
158
+ },
159
+ "rewrite-docs": {
160
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
161
+ requiredWorkflowTools: [],
162
+ },
163
+ "workflow-preferences": {
164
+ allowedGsdTools: ["gsd_summary_save"],
165
+ requiredWorkflowTools: [],
166
+ },
167
+ "discuss-project": {
168
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save", "gsd_requirement_save"],
169
+ requiredWorkflowTools: ["ask_user_questions", "gsd_summary_save"],
170
+ },
171
+ "discuss-requirements": {
172
+ allowedGsdTools: ["gsd_requirement_save", "gsd_summary_save"],
173
+ requiredWorkflowTools: ["ask_user_questions", "gsd_requirement_save", "gsd_summary_save"],
174
+ },
175
+ "research-decision": {
176
+ allowedGsdTools: ["gsd_summary_save"],
177
+ requiredWorkflowTools: ["ask_user_questions"],
178
+ },
179
+ "research-project": {
180
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
181
+ requiredWorkflowTools: [],
182
+ },
183
+ };
184
+ export const AUTO_UNIT_SCOPED_TOOLS = Object.fromEntries(Object.entries(UNIT_TOOL_CONTRACTS).map(([unitType, contract]) => [unitType, contract.allowedGsdTools]));
185
+ export function getUnitToolSurfaceContract(unitType) {
186
+ return UNIT_TOOL_CONTRACTS[unitType];
187
+ }
188
+ export function getRequiredWorkflowToolsForUnit(unitType) {
189
+ return [...(UNIT_TOOL_CONTRACTS[unitType]?.requiredWorkflowTools ?? [])];
190
+ }
191
+ export function getForbiddenGsdToolReason(unitType, toolName) {
192
+ return UNIT_TOOL_CONTRACTS[unitType]?.forbiddenGsdTools?.[toolName];
193
+ }