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

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 (145) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/gsd/auto-dispatch.js +11 -0
  3. package/dist/resources/extensions/gsd/auto-prompts.js +4 -0
  4. package/dist/resources/extensions/gsd/auto-recovery.js +3 -4
  5. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +18 -66
  6. package/dist/resources/extensions/gsd/auto-worktree.js +18 -5
  7. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +16 -10
  8. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +19 -8
  9. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +18 -29
  10. package/dist/resources/extensions/gsd/closeout-consistency-gate.js +61 -0
  11. package/dist/resources/extensions/gsd/guided-flow.js +89 -107
  12. package/dist/resources/extensions/gsd/milestone-closeout.js +3 -1
  13. package/dist/resources/extensions/gsd/pending-auto-start.js +0 -1
  14. package/dist/resources/extensions/gsd/prompts/run-uat.md +3 -17
  15. package/dist/resources/extensions/gsd/recovery-classification.js +20 -0
  16. package/dist/resources/extensions/gsd/tool-contract.js +5 -0
  17. package/dist/resources/extensions/gsd/tool-presentation-plan.js +17 -7
  18. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +81 -4
  19. package/dist/resources/extensions/gsd/unit-tool-contracts.js +169 -0
  20. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -75
  21. package/dist/web/standalone/.next/BUILD_ID +1 -1
  22. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  23. package/dist/web/standalone/.next/build-manifest.json +2 -2
  24. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  25. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.html +1 -1
  42. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  49. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  50. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  52. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  53. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  54. package/package.json +1 -1
  55. package/packages/cloud-mcp-gateway/package.json +2 -2
  56. package/packages/contracts/package.json +1 -1
  57. package/packages/daemon/package.json +4 -4
  58. package/packages/gsd-agent-core/package.json +5 -5
  59. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  60. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
  61. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  62. package/packages/gsd-agent-modes/package.json +7 -7
  63. package/packages/mcp-server/package.json +3 -3
  64. package/packages/native/package.json +1 -1
  65. package/packages/pi-agent-core/dist/agent-loop.js +4 -3
  66. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  67. package/packages/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  68. package/packages/pi-agent-core/dist/harness/agent-harness.js +3 -1
  69. package/packages/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  70. package/packages/pi-agent-core/dist/harness/types.d.ts +1 -0
  71. package/packages/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  72. package/packages/pi-agent-core/dist/harness/types.js.map +1 -1
  73. package/packages/pi-agent-core/dist/types.d.ts +3 -1
  74. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  75. package/packages/pi-agent-core/dist/types.js.map +1 -1
  76. package/packages/pi-agent-core/package.json +1 -1
  77. package/packages/pi-ai/dist/models.generated.d.ts +6 -6
  78. package/packages/pi-ai/dist/models.generated.js +6 -6
  79. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  80. package/packages/pi-ai/package.json +1 -1
  81. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +3 -0
  82. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
  83. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
  84. package/packages/pi-coding-agent/dist/core/tools/bash.js +2 -2
  85. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  86. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/tools/edit.js +3 -2
  88. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  89. package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts +1 -0
  90. package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts.map +1 -1
  91. package/packages/pi-coding-agent/dist/core/tools/render-utils.js +6 -0
  92. package/packages/pi-coding-agent/dist/core/tools/render-utils.js.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/tools/write.js +3 -2
  95. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  96. package/packages/pi-coding-agent/package.json +7 -7
  97. package/packages/pi-tui/package.json +1 -1
  98. package/packages/rpc-client/package.json +2 -2
  99. package/pkg/package.json +1 -1
  100. package/src/resources/extensions/gsd/auto-dispatch.ts +14 -0
  101. package/src/resources/extensions/gsd/auto-prompts.ts +4 -0
  102. package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
  103. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -74
  104. package/src/resources/extensions/gsd/auto-worktree.ts +23 -5
  105. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +16 -10
  106. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +23 -8
  107. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +50 -54
  108. package/src/resources/extensions/gsd/closeout-consistency-gate.ts +137 -0
  109. package/src/resources/extensions/gsd/guided-flow.ts +124 -134
  110. package/src/resources/extensions/gsd/milestone-closeout.ts +3 -1
  111. package/src/resources/extensions/gsd/pending-auto-start.ts +0 -2
  112. package/src/resources/extensions/gsd/prompts/run-uat.md +3 -17
  113. package/src/resources/extensions/gsd/recovery-classification.ts +20 -0
  114. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +10 -2
  115. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +4 -1
  116. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +12 -2
  117. package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +9 -15
  118. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +26 -16
  119. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +21 -0
  120. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +40 -1
  121. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +31 -79
  122. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +5 -3
  123. package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +40 -4
  124. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +8 -0
  125. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +16 -0
  126. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +3 -0
  127. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +63 -0
  128. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +10 -1
  129. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +9 -1
  130. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +23 -5
  131. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +44 -0
  132. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +4 -0
  133. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +36 -0
  134. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -4
  135. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +221 -0
  136. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
  137. package/src/resources/extensions/gsd/tool-contract.ts +6 -0
  138. package/src/resources/extensions/gsd/tool-presentation-plan.ts +38 -8
  139. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +100 -5
  140. package/src/resources/extensions/gsd/unit-tool-contracts.ts +186 -0
  141. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -75
  142. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +0 -246
  143. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +0 -218
  144. /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → 9y3LeeR2uGr2yRj9RjY3D}/_buildManifest.js +0 -0
  145. /package/dist/web/standalone/.next/static/{jBtwT9v1u2lUA3UEOy_ZH → 9y3LeeR2uGr2yRj9RjY3D}/_ssgManifest.js +0 -0
@@ -1,6 +1,18 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: Resolve phase-aware tool surfaces for GSD model presentations.
3
3
 
4
+ import {
5
+ RUN_UAT_READ_ONLY_TOOL_NAMES,
6
+ RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
7
+ RUN_UAT_WORKFLOW_TOOL_NAMES,
8
+ } from "./unit-tool-contracts.js";
9
+
10
+ export {
11
+ RUN_UAT_READ_ONLY_TOOL_NAMES,
12
+ RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
13
+ RUN_UAT_WORKFLOW_TOOL_NAMES,
14
+ } from "./unit-tool-contracts.js";
15
+
4
16
  export type ToolPresentationSurface = "provider-tools" | "claude-code-sdk" | "mcp" | "hybrid";
5
17
 
6
18
  export interface ToolPresentationModel {
@@ -20,14 +32,6 @@ export interface ToolPresentationPlan {
20
32
  diagnostics: string[];
21
33
  }
22
34
 
23
- export const RUN_UAT_WORKFLOW_TOOL_NAMES = [
24
- "gsd_uat_exec",
25
- "gsd_uat_result_save",
26
- "gsd_resume",
27
- "gsd_milestone_status",
28
- "gsd_journal_query",
29
- ] as const;
30
-
31
35
  export const RUN_UAT_FORBIDDEN_TOOL_NAMES = [
32
36
  "edit",
33
37
  "write",
@@ -105,10 +109,36 @@ function addBlockedTool(
105
109
  export function buildRunUatCanonicalToolNames(options: { includeBrowserTools?: readonly string[] } = {}): string[] {
106
110
  return dedupe([
107
111
  ...RUN_UAT_WORKFLOW_TOOL_NAMES,
112
+ ...RUN_UAT_READ_ONLY_TOOL_NAMES,
108
113
  ...(options.includeBrowserTools ?? []),
109
114
  ]);
110
115
  }
111
116
 
117
+ export function buildRunUatResultPresentation(options: {
118
+ surface?: ToolPresentationSurface;
119
+ includeBrowserTools?: readonly string[];
120
+ presentedTools?: readonly string[];
121
+ } = {}): {
122
+ surface: ToolPresentationSurface;
123
+ presentedTools: string[];
124
+ blockedTools: Array<{ name: string; reason: string }>;
125
+ toolPresentationPlanId: string;
126
+ } {
127
+ const presentedTools = options.presentedTools
128
+ ? dedupe(options.presentedTools)
129
+ : buildRunUatCanonicalToolNames({ includeBrowserTools: options.includeBrowserTools });
130
+ const blockedTools = RUN_UAT_FORBIDDEN_TOOL_NAMES
131
+ .filter((toolName) => !toolName.includes("*"))
132
+ .map((name) => ({ name, reason: "forbidden during run-uat" }));
133
+
134
+ return {
135
+ surface: options.surface ?? "mcp",
136
+ presentedTools,
137
+ blockedTools,
138
+ toolPresentationPlanId: RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
139
+ };
140
+ }
141
+
112
142
  export function resolveToolPresentationPlan(options: {
113
143
  phase: string;
114
144
  surface: ToolPresentationSurface;
@@ -48,9 +48,11 @@ import { loadEffectiveGSDPreferences } from "../preferences.js";
48
48
  import { parseProject } from "../schemas/parsers.js";
49
49
  import { getAutoRuntimeSnapshot } from "../auto-runtime-state.js";
50
50
  import {
51
+ buildRunUatResultPresentation,
51
52
  canonicalWorkflowToolName,
52
53
  parseMcpToolName,
53
54
  RUN_UAT_FORBIDDEN_TOOL_NAMES,
55
+ RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
54
56
  RUN_UAT_WORKFLOW_TOOL_NAMES,
55
57
  } from "../tool-presentation-plan.js";
56
58
 
@@ -90,7 +92,7 @@ function blockIfWrongAutoUnit(requiredUnitType: string, operation: string): Tool
90
92
  if (!snapshot.active || !snapshot.currentUnit) return null;
91
93
  if (snapshot.currentUnit.type === requiredUnitType) return null;
92
94
 
93
- const error = `HARD BLOCK: ${operation} may only run from ${requiredUnitType}; active unit is ${snapshot.currentUnit.type}. The orchestrator owns phase transitions.`;
95
+ 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.`;
94
96
  return {
95
97
  content: [{ type: "text", text: error }],
96
98
  details: { operation, error },
@@ -178,7 +180,11 @@ export async function executeSummarySave(
178
180
  if (rootArtifactGuard.block) {
179
181
  return {
180
182
  content: [{ type: "text", text: `Error saving artifact: ${rootArtifactGuard.reason ?? "root artifact write blocked"}` }],
181
- details: { operation: "save_summary", error: "root_artifact_write_blocked" },
183
+ details: {
184
+ operation: "save_summary",
185
+ error: "root_artifact_write_blocked",
186
+ displayReason: "Approval confirmation required before saving final project setup artifacts.",
187
+ },
182
188
  isError: true,
183
189
  };
184
190
  }
@@ -191,9 +197,13 @@ export async function executeSummarySave(
191
197
  if (contextGuard.block) {
192
198
  return {
193
199
  content: [{ type: "text", text: `Error saving artifact: ${contextGuard.reason ?? "context write blocked"}` }],
194
- details: { operation: "save_summary", error: "context_write_blocked" },
195
- isError: true,
196
- };
200
+ details: {
201
+ operation: "save_summary",
202
+ error: "context_write_blocked",
203
+ displayReason: "Depth check required before writing milestone context.",
204
+ },
205
+ isError: true,
206
+ };
197
207
  }
198
208
  try {
199
209
  let relativePath: string;
@@ -441,6 +451,9 @@ export interface UatEvidenceRef {
441
451
  kind: "gsd_uat_exec" | "gsd_exec" | "screenshot" | "log" | "url" | "browser";
442
452
  ref: string;
443
453
  note?: string;
454
+ unitType?: string;
455
+ tool?: string;
456
+ executionId?: string;
444
457
  }
445
458
 
446
459
  export interface UatCheckResultInput {
@@ -1008,10 +1021,68 @@ function isNonEmptyString(value: unknown): value is string {
1008
1021
  return typeof value === "string" && value.trim().length > 0;
1009
1022
  }
1010
1023
 
1024
+ function mergeBlockedTools(
1025
+ current: UatPresentationInput["blockedTools"] | undefined,
1026
+ canonical: UatPresentationInput["blockedTools"],
1027
+ ): UatPresentationInput["blockedTools"] {
1028
+ const merged = new Map<string, { name: string; reason: string }>();
1029
+ for (const entry of [...(current ?? []), ...canonical]) {
1030
+ merged.set(canonicalWorkflowToolName(parseMcpToolName(entry.name)?.tool ?? entry.name), entry);
1031
+ }
1032
+ return [...merged.values()];
1033
+ }
1034
+
1035
+ function mergePresentedTools(current: readonly string[] | undefined, canonical: readonly string[]): string[] {
1036
+ return [...new Set([...(current ?? []), ...canonical])];
1037
+ }
1038
+
1039
+ function normalizeUatVerdict(params: UatResultSaveParams): UatResultSaveParams {
1040
+ const raw = params as Partial<UatResultSaveParams> & Record<string, unknown>;
1041
+ if (typeof raw.verdict === "string") {
1042
+ return { ...params, verdict: raw.verdict.toUpperCase() as UatVerdict };
1043
+ }
1044
+ return params;
1045
+ }
1046
+
1047
+ function supplyDefaultPresentation(params: UatResultSaveParams): UatResultSaveParams {
1048
+ const raw = params as Partial<UatResultSaveParams> & Record<string, unknown>;
1049
+ if (!raw.presentation) {
1050
+ return { ...params, presentation: buildRunUatResultPresentation() };
1051
+ }
1052
+ return params;
1053
+ }
1054
+
1055
+ function mergeCanonicalPresentation(params: UatResultSaveParams): UatResultSaveParams {
1056
+ const canonicalPresentation = buildRunUatResultPresentation();
1057
+ const providedPresentation = params.presentation as Partial<UatPresentationInput>;
1058
+ return {
1059
+ ...params,
1060
+ presentation: {
1061
+ ...providedPresentation,
1062
+ surface: providedPresentation.surface ?? canonicalPresentation.surface,
1063
+ presentedTools: mergePresentedTools(providedPresentation.presentedTools, canonicalPresentation.presentedTools),
1064
+ blockedTools: mergeBlockedTools(providedPresentation.blockedTools, canonicalPresentation.blockedTools),
1065
+ toolPresentationPlanId: RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
1066
+ } as UatPresentationInput,
1067
+ };
1068
+ }
1069
+
1070
+ const VALID_UAT_TYPES: readonly UatType[] = [
1071
+ "artifact-driven",
1072
+ "browser-executable",
1073
+ "runtime-executable",
1074
+ "live-runtime",
1075
+ "mixed",
1076
+ "human-experience",
1077
+ ];
1078
+
1011
1079
  function ensureUatRequiredFields(params: UatResultSaveParams): string | null {
1012
1080
  if (!isNonEmptyString(params.milestoneId)) return "milestoneId is required";
1013
1081
  if (!isNonEmptyString(params.sliceId)) return "sliceId is required";
1014
1082
  if (!isNonEmptyString(params.uatType)) return "uatType is required";
1083
+ if (!(VALID_UAT_TYPES as readonly string[]).includes(params.uatType)) {
1084
+ return `uatType must be one of: ${VALID_UAT_TYPES.join(", ")}`;
1085
+ }
1015
1086
  if (!["PASS", "FAIL", "PARTIAL"].includes(params.verdict)) return "verdict must be PASS, FAIL, or PARTIAL";
1016
1087
  if (!Array.isArray(params.checks) || params.checks.length === 0) return "checks must contain at least one UAT check";
1017
1088
  if (!params.presentation || !Array.isArray(params.presentation.presentedTools)) return "presentation.presentedTools is required";
@@ -1147,6 +1218,15 @@ function validateUatChecks(basePath: string, params: UatResultSaveParams): strin
1147
1218
  return null;
1148
1219
  }
1149
1220
 
1221
+ function validateFreshUatOwnedEvidence(params: UatResultSaveParams): string | null {
1222
+ const hasFreshUatEvidence = params.checks.some((check) =>
1223
+ (check.evidence ?? []).some((evidence) => evidence.kind === "gsd_uat_exec")
1224
+ );
1225
+ return hasFreshUatEvidence
1226
+ ? null
1227
+ : "UAT Assessment requires at least one fresh gsd_uat_exec evidence reference from run-uat";
1228
+ }
1229
+
1150
1230
  function validateUatMode(params: UatResultSaveParams): string | null {
1151
1231
  const modes = new Set(params.checks.map((check) => check.mode));
1152
1232
  const hasHuman = params.checks.some((check) => check.result === "NEEDS-HUMAN");
@@ -1306,15 +1386,30 @@ export async function executeUatResultSave(
1306
1386
  params: UatResultSaveParams,
1307
1387
  basePath: string = process.cwd(),
1308
1388
  ): Promise<ToolExecutionResult> {
1389
+ const unitGuard = blockIfWrongAutoUnit("run-uat", "save_uat_result");
1390
+ if (unitGuard) return unitGuard;
1391
+
1392
+ // Phase 1: normalize verdict and supply the canonical presentation when none was provided.
1393
+ params = normalizeUatVerdict(params);
1394
+ params = supplyDefaultPresentation(params);
1395
+
1309
1396
  const dbAvailable = await ensureDbOpen(basePath);
1310
1397
  if (!dbAvailable) return errorResult("save_uat_result", "GSD database is not available.", "db_unavailable");
1311
1398
 
1399
+ // Phase 2: validate the submitted presentation before the canonical merge so that
1400
+ // presentations missing required workflow tools are rejected rather than silently patched.
1312
1401
  const requiredError = ensureUatRequiredFields(params);
1313
1402
  if (requiredError) return errorResult("save_uat_result", requiredError, "invalid_params");
1314
1403
  const presentationError = validateCanonicalPresentation(params);
1315
1404
  if (presentationError) return errorResult("save_uat_result", presentationError, "alias_tool_name");
1405
+
1406
+ // Phase 3: merge in the canonical plan ID and read-only audit tools so the persisted
1407
+ // artifact always carries the full audit surface even when the provider omitted them.
1408
+ params = mergeCanonicalPresentation(params);
1316
1409
  const checkError = validateUatChecks(basePath, params);
1317
1410
  if (checkError) return errorResult("save_uat_result", checkError, "invalid_evidence");
1411
+ const freshEvidenceError = validateFreshUatOwnedEvidence(params);
1412
+ if (freshEvidenceError) return errorResult("save_uat_result", freshEvidenceError, "missing_fresh_uat_evidence");
1318
1413
  const modeError = validateUatMode(params);
1319
1414
  if (modeError) return errorResult("save_uat_result", modeError, "uat_mode_mismatch");
1320
1415
 
@@ -0,0 +1,186 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Central Unit-to-tool contracts for phase-aware GSD tool surfaces.
3
+
4
+ export interface UnitToolSurfaceContract {
5
+ allowedGsdTools: readonly string[];
6
+ requiredWorkflowTools: readonly string[];
7
+ forbiddenGsdTools?: Readonly<Record<string, string>>;
8
+ }
9
+
10
+ export const RUN_UAT_WORKFLOW_TOOL_NAMES = [
11
+ "gsd_uat_exec",
12
+ "gsd_uat_result_save",
13
+ "gsd_resume",
14
+ "gsd_milestone_status",
15
+ "gsd_journal_query",
16
+ ] as const;
17
+
18
+ export const RUN_UAT_READ_ONLY_TOOL_NAMES = [
19
+ "find",
20
+ "glob",
21
+ "grep",
22
+ "ls",
23
+ "read",
24
+ ] as const;
25
+
26
+ export const RUN_UAT_BROWSER_TOOL_NAMES = [
27
+ "browser_navigate",
28
+ "browser_click",
29
+ "browser_type",
30
+ "browser_fill_form",
31
+ "browser_click_ref",
32
+ "browser_fill_ref",
33
+ "browser_wait_for",
34
+ "browser_assert",
35
+ "browser_verify",
36
+ "browser_screenshot",
37
+ "browser_snapshot_refs",
38
+ "browser_find",
39
+ "browser_get_console_logs",
40
+ "browser_get_network_logs",
41
+ "browser_evaluate",
42
+ "browser_reload",
43
+ "browser_batch",
44
+ "browser_act",
45
+ ] as const;
46
+
47
+ export const RUN_UAT_TOOL_PRESENTATION_PLAN_ID = "run-uat/default-v1";
48
+
49
+ export const UNIT_TOOL_CONTRACTS: Record<string, UnitToolSurfaceContract> = {
50
+ "research-milestone": {
51
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
52
+ requiredWorkflowTools: ["gsd_summary_save"],
53
+ },
54
+ "plan-milestone": {
55
+ allowedGsdTools: ["gsd_plan_milestone", "gsd_decision_save", "gsd_requirement_update"],
56
+ requiredWorkflowTools: ["gsd_plan_milestone"],
57
+ },
58
+ "discuss-milestone": {
59
+ allowedGsdTools: [
60
+ "gsd_summary_save",
61
+ "gsd_decision_save",
62
+ "gsd_requirement_save",
63
+ "gsd_requirement_update",
64
+ "gsd_plan_milestone",
65
+ "gsd_milestone_generate_id",
66
+ ],
67
+ requiredWorkflowTools: [
68
+ "gsd_summary_save",
69
+ "gsd_requirement_save",
70
+ "gsd_requirement_update",
71
+ "gsd_plan_milestone",
72
+ "gsd_milestone_generate_id",
73
+ ],
74
+ },
75
+ "discuss-slice": {
76
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
77
+ requiredWorkflowTools: ["gsd_summary_save"],
78
+ },
79
+ "validate-milestone": {
80
+ allowedGsdTools: ["gsd_validate_milestone", "gsd_reassess_roadmap", "subagent"],
81
+ requiredWorkflowTools: ["gsd_milestone_status", "gsd_validate_milestone", "gsd_reassess_roadmap"],
82
+ },
83
+ "complete-milestone": {
84
+ allowedGsdTools: ["gsd_complete_milestone", "subagent"],
85
+ requiredWorkflowTools: ["gsd_milestone_status", "gsd_complete_milestone"],
86
+ },
87
+ "research-slice": {
88
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
89
+ requiredWorkflowTools: ["gsd_summary_save"],
90
+ },
91
+ "plan-slice": {
92
+ allowedGsdTools: ["gsd_plan_slice", "gsd_plan_task", "gsd_decision_save"],
93
+ requiredWorkflowTools: ["gsd_plan_slice"],
94
+ },
95
+ "refine-slice": {
96
+ allowedGsdTools: ["gsd_plan_slice", "gsd_plan_task", "gsd_decision_save"],
97
+ requiredWorkflowTools: [],
98
+ },
99
+ "replan-slice": {
100
+ allowedGsdTools: ["gsd_replan_slice", "gsd_plan_task", "gsd_decision_save"],
101
+ requiredWorkflowTools: ["gsd_replan_slice"],
102
+ },
103
+ "complete-slice": {
104
+ allowedGsdTools: [
105
+ "gsd_slice_complete",
106
+ "gsd_task_reopen",
107
+ "gsd_replan_slice",
108
+ "gsd_decision_save",
109
+ "gsd_requirement_update",
110
+ "subagent",
111
+ ],
112
+ requiredWorkflowTools: ["gsd_slice_complete", "gsd_task_reopen", "gsd_replan_slice"],
113
+ forbiddenGsdTools: {
114
+ gsd_uat_result_save: "Run UAT owns persisted UAT Assessment.",
115
+ },
116
+ },
117
+ "reassess-roadmap": {
118
+ allowedGsdTools: ["gsd_reassess_roadmap"],
119
+ requiredWorkflowTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
120
+ },
121
+ "execute-task": {
122
+ allowedGsdTools: ["gsd_task_complete", "gsd_decision_save"],
123
+ requiredWorkflowTools: ["gsd_task_complete"],
124
+ },
125
+ "execute-task-simple": {
126
+ allowedGsdTools: ["gsd_task_complete", "gsd_decision_save"],
127
+ requiredWorkflowTools: ["gsd_task_complete"],
128
+ },
129
+ "reactive-execute": {
130
+ allowedGsdTools: ["gsd_task_complete", "gsd_decision_save"],
131
+ requiredWorkflowTools: ["gsd_task_complete"],
132
+ },
133
+ "run-uat": {
134
+ allowedGsdTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent"],
135
+ requiredWorkflowTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES],
136
+ forbiddenGsdTools: {
137
+ gsd_exec: "Use gsd_uat_exec so acceptance evidence is typed as UAT-owned.",
138
+ gsd_save_gate_result: "gsd_uat_result_save owns the aggregate UAT gate.",
139
+ gsd_summary_save: "gsd_uat_result_save owns persisted UAT Assessment writes.",
140
+ },
141
+ },
142
+ "gate-evaluate": {
143
+ allowedGsdTools: ["gsd_save_gate_result"],
144
+ requiredWorkflowTools: ["gsd_save_gate_result"],
145
+ },
146
+ "rewrite-docs": {
147
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
148
+ requiredWorkflowTools: [],
149
+ },
150
+ "workflow-preferences": {
151
+ allowedGsdTools: ["gsd_summary_save"],
152
+ requiredWorkflowTools: [],
153
+ },
154
+ "discuss-project": {
155
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save", "gsd_requirement_save"],
156
+ requiredWorkflowTools: ["ask_user_questions", "gsd_summary_save"],
157
+ },
158
+ "discuss-requirements": {
159
+ allowedGsdTools: ["gsd_requirement_save", "gsd_summary_save"],
160
+ requiredWorkflowTools: ["ask_user_questions", "gsd_requirement_save", "gsd_summary_save"],
161
+ },
162
+ "research-decision": {
163
+ allowedGsdTools: ["gsd_summary_save"],
164
+ requiredWorkflowTools: ["ask_user_questions"],
165
+ },
166
+ "research-project": {
167
+ allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
168
+ requiredWorkflowTools: [],
169
+ },
170
+ };
171
+
172
+ export const AUTO_UNIT_SCOPED_TOOLS: Record<string, readonly string[]> = Object.fromEntries(
173
+ Object.entries(UNIT_TOOL_CONTRACTS).map(([unitType, contract]) => [unitType, contract.allowedGsdTools]),
174
+ );
175
+
176
+ export function getUnitToolSurfaceContract(unitType: string): UnitToolSurfaceContract | undefined {
177
+ return UNIT_TOOL_CONTRACTS[unitType];
178
+ }
179
+
180
+ export function getRequiredWorkflowToolsForUnit(unitType: string): string[] {
181
+ return [...(UNIT_TOOL_CONTRACTS[unitType]?.requiredWorkflowTools ?? [])];
182
+ }
183
+
184
+ export function getForbiddenGsdToolReason(unitType: string, toolName: string): string | undefined {
185
+ return UNIT_TOOL_CONTRACTS[unitType]?.forbiddenGsdTools?.[toolName];
186
+ }
@@ -2,7 +2,7 @@ import { execSync } from "node:child_process";
2
2
  import { existsSync, realpathSync } from "node:fs";
3
3
  import { dirname, resolve, sep } from "node:path";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
- import { RUN_UAT_WORKFLOW_TOOL_NAMES } from "./tool-presentation-plan.js";
5
+ import { getRequiredWorkflowToolsForUnit } from "./unit-tool-contracts.js";
6
6
 
7
7
  type WorkflowExecutorsModule = typeof import("./tools/workflow-tool-executors.js");
8
8
 
@@ -414,83 +414,11 @@ export function buildWorkflowMcpServers(
414
414
  }
415
415
 
416
416
  export function getRequiredWorkflowToolsForGuidedUnit(unitType: string): string[] {
417
- switch (unitType) {
418
- case "discuss-project":
419
- return ["ask_user_questions", "gsd_summary_save"];
420
- case "discuss-requirements":
421
- return ["ask_user_questions", "gsd_requirement_save", "gsd_summary_save"];
422
- case "research-decision":
423
- return ["ask_user_questions"];
424
- case "discuss-milestone":
425
- return [
426
- "gsd_summary_save",
427
- "gsd_requirement_save",
428
- "gsd_requirement_update",
429
- "gsd_plan_milestone",
430
- "gsd_milestone_generate_id",
431
- ];
432
- case "discuss-slice":
433
- return ["gsd_summary_save"];
434
- case "research-milestone":
435
- case "research-slice":
436
- return ["gsd_summary_save"];
437
- case "plan-milestone":
438
- return ["gsd_plan_milestone"];
439
- case "plan-slice":
440
- return ["gsd_plan_slice"];
441
- case "execute-task":
442
- return ["gsd_task_complete"];
443
- case "complete-slice":
444
- return ["gsd_slice_complete", "gsd_task_reopen", "gsd_replan_slice"];
445
- default:
446
- return [];
447
- }
417
+ return getRequiredWorkflowToolsForUnit(unitType);
448
418
  }
449
419
 
450
420
  export function getRequiredWorkflowToolsForAutoUnit(unitType: string): string[] {
451
- switch (unitType) {
452
- case "discuss-project":
453
- return ["ask_user_questions", "gsd_summary_save"];
454
- case "discuss-requirements":
455
- return ["ask_user_questions", "gsd_requirement_save", "gsd_summary_save"];
456
- case "research-decision":
457
- return ["ask_user_questions"];
458
- case "discuss-milestone":
459
- return [
460
- "gsd_summary_save",
461
- "gsd_requirement_save",
462
- "gsd_requirement_update",
463
- "gsd_plan_milestone",
464
- "gsd_milestone_generate_id",
465
- ];
466
- case "research-milestone":
467
- case "research-slice":
468
- return ["gsd_summary_save"];
469
- case "run-uat":
470
- return [...RUN_UAT_WORKFLOW_TOOL_NAMES];
471
- case "plan-milestone":
472
- return ["gsd_plan_milestone"];
473
- case "plan-slice":
474
- return ["gsd_plan_slice"];
475
- case "execute-task":
476
- case "execute-task-simple":
477
- case "reactive-execute":
478
- return ["gsd_task_complete"];
479
- case "complete-slice":
480
- return ["gsd_slice_complete", "gsd_task_reopen", "gsd_replan_slice"];
481
- case "replan-slice":
482
- return ["gsd_replan_slice"];
483
- case "reassess-roadmap":
484
- return ["gsd_milestone_status", "gsd_reassess_roadmap"];
485
- case "gate-evaluate":
486
- return ["gsd_save_gate_result"];
487
- case "validate-milestone":
488
- return ["gsd_milestone_status", "gsd_validate_milestone", "gsd_reassess_roadmap"];
489
- case "complete-milestone":
490
- return ["gsd_milestone_status", "gsd_complete_milestone"];
491
- default:
492
- return [];
493
- }
421
+ return getRequiredWorkflowToolsForUnit(unitType);
494
422
  }
495
423
 
496
424
  export function usesWorkflowMcpTransport(