@opengsd/gsd-pi 1.1.1-dev.3ea310e → 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.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +4 -3
- package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +111 -5
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
- package/dist/resources/extensions/gsd/auto-start.js +41 -12
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
- package/dist/resources/extensions/gsd/auto.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
- package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +2 -1
- package/dist/resources/extensions/gsd/error-classifier.js +2 -1
- package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/dist/resources/extensions/gsd/prompts/system.md +3 -1
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
- package/dist/resources/extensions/gsd/skill-activation.js +20 -3
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
- package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +1 -1
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +14 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +16 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +69 -31
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +82 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +15 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +15 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +35 -1
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +53 -19
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/terminal.d.ts +1 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +8 -4
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/phases.ts +5 -3
- package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +136 -5
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
- package/src/resources/extensions/gsd/auto-start.ts +54 -14
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
- package/src/resources/extensions/gsd/auto.ts +3 -2
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
- package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +3 -1
- package/src/resources/extensions/gsd/error-classifier.ts +2 -1
- package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/src/resources/extensions/gsd/prompts/system.md +3 -1
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
- package/src/resources/extensions/gsd/skill-activation.ts +20 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +16 -3
- package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
- package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
- /package/dist/web/standalone/.next/static/{xACmObbrDjwLriepRgaa9 → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{xACmObbrDjwLriepRgaa9 → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
|
@@ -10,15 +10,17 @@ import {
|
|
|
10
10
|
getMilestone,
|
|
11
11
|
getSliceStatusSummary,
|
|
12
12
|
getSliceTaskCounts,
|
|
13
|
+
insertGateRun,
|
|
13
14
|
readTransaction,
|
|
14
15
|
saveGateResult,
|
|
16
|
+
upsertQualityGate,
|
|
15
17
|
} from "../gsd-db.js";
|
|
16
18
|
import { GATE_REGISTRY } from "../gate-registry.js";
|
|
17
19
|
import { generateRequirementsMd, saveArtifactToDb } from "../db-writer.js";
|
|
18
20
|
import { clearPathCache, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "../paths.js";
|
|
19
21
|
import { saveFile, clearParseCache } from "../files.js";
|
|
20
|
-
import { unlinkSync } from "node:fs";
|
|
21
|
-
import { join } from "node:path";
|
|
22
|
+
import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
23
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
22
24
|
import type { CompleteMilestoneParams } from "./complete-milestone.js";
|
|
23
25
|
import { handleCompleteMilestone } from "./complete-milestone.js";
|
|
24
26
|
import { handleCompleteTask } from "./complete-task.js";
|
|
@@ -45,6 +47,12 @@ import { invalidateStateCache } from "../state.js";
|
|
|
45
47
|
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
46
48
|
import { parseProject } from "../schemas/parsers.js";
|
|
47
49
|
import { getAutoRuntimeSnapshot } from "../auto-runtime-state.js";
|
|
50
|
+
import {
|
|
51
|
+
canonicalWorkflowToolName,
|
|
52
|
+
parseMcpToolName,
|
|
53
|
+
RUN_UAT_FORBIDDEN_TOOL_NAMES,
|
|
54
|
+
RUN_UAT_WORKFLOW_TOOL_NAMES,
|
|
55
|
+
} from "../tool-presentation-plan.js";
|
|
48
56
|
|
|
49
57
|
export const SUPPORTED_SUMMARY_ARTIFACT_TYPES = [
|
|
50
58
|
"SUMMARY",
|
|
@@ -409,6 +417,56 @@ export interface SaveGateResultParams {
|
|
|
409
417
|
findings?: string;
|
|
410
418
|
}
|
|
411
419
|
|
|
420
|
+
export type UatType =
|
|
421
|
+
| "artifact-driven"
|
|
422
|
+
| "browser-executable"
|
|
423
|
+
| "runtime-executable"
|
|
424
|
+
| "live-runtime"
|
|
425
|
+
| "mixed"
|
|
426
|
+
| "human-experience";
|
|
427
|
+
|
|
428
|
+
export type UatVerdict = "PASS" | "FAIL" | "PARTIAL";
|
|
429
|
+
export type UatCheckResult = "PASS" | "FAIL" | "NEEDS-HUMAN";
|
|
430
|
+
|
|
431
|
+
export interface UatEvidenceRef {
|
|
432
|
+
kind: "gsd_uat_exec" | "gsd_exec" | "screenshot" | "log" | "url" | "browser";
|
|
433
|
+
ref: string;
|
|
434
|
+
note?: string;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export interface UatCheckResultInput {
|
|
438
|
+
id: string;
|
|
439
|
+
description: string;
|
|
440
|
+
mode: "artifact" | "runtime" | "browser" | "human-follow-up";
|
|
441
|
+
result: UatCheckResult;
|
|
442
|
+
evidence?: UatEvidenceRef[];
|
|
443
|
+
notes?: string;
|
|
444
|
+
nonAutomatable?: boolean;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export interface UatPresentationInput {
|
|
448
|
+
surface: "provider-tools" | "claude-code-sdk" | "mcp" | "hybrid";
|
|
449
|
+
model?: { provider?: string; api?: string; id?: string };
|
|
450
|
+
presentedTools: string[];
|
|
451
|
+
blockedTools: Array<{ name: string; reason: string }>;
|
|
452
|
+
aliases?: Array<{ requested: string; canonical: string }>;
|
|
453
|
+
fallbackToolsUsed?: string[];
|
|
454
|
+
toolPresentationPlanId?: string;
|
|
455
|
+
notes?: string;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export interface UatResultSaveParams {
|
|
459
|
+
milestoneId: string;
|
|
460
|
+
sliceId: string;
|
|
461
|
+
uatType: UatType;
|
|
462
|
+
verdict: UatVerdict;
|
|
463
|
+
checks: UatCheckResultInput[];
|
|
464
|
+
presentation: UatPresentationInput;
|
|
465
|
+
notes?: string;
|
|
466
|
+
attempt?: number | string | "auto";
|
|
467
|
+
previousAttemptId?: string;
|
|
468
|
+
}
|
|
469
|
+
|
|
412
470
|
export async function executeTaskComplete(
|
|
413
471
|
params: TaskCompleteParams,
|
|
414
472
|
basePath: string = process.cwd(),
|
|
@@ -907,6 +965,386 @@ export async function executeSaveGateResult(
|
|
|
907
965
|
}
|
|
908
966
|
}
|
|
909
967
|
|
|
968
|
+
function errorResult(operation: string, message: string, error: string): ToolExecutionResult {
|
|
969
|
+
return {
|
|
970
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
971
|
+
details: { operation, error },
|
|
972
|
+
isError: true,
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
977
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function ensureUatRequiredFields(params: UatResultSaveParams): string | null {
|
|
981
|
+
if (!isNonEmptyString(params.milestoneId)) return "milestoneId is required";
|
|
982
|
+
if (!isNonEmptyString(params.sliceId)) return "sliceId is required";
|
|
983
|
+
if (!isNonEmptyString(params.uatType)) return "uatType is required";
|
|
984
|
+
if (!["PASS", "FAIL", "PARTIAL"].includes(params.verdict)) return "verdict must be PASS, FAIL, or PARTIAL";
|
|
985
|
+
if (!Array.isArray(params.checks) || params.checks.length === 0) return "checks must contain at least one UAT check";
|
|
986
|
+
if (!params.presentation || !Array.isArray(params.presentation.presentedTools)) return "presentation.presentedTools is required";
|
|
987
|
+
if (!Array.isArray(params.presentation.blockedTools)) return "presentation.blockedTools is required";
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function approvedEvidenceRoots(basePath: string): string[] {
|
|
992
|
+
const contract = resolveGsdPathContract(basePath);
|
|
993
|
+
return [contract.worktreeGsd, contract.projectGsd].filter((root): root is string => typeof root === "string");
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function approvedBrowserArtifactRoots(basePath: string): string[] {
|
|
997
|
+
const contract = resolveGsdPathContract(basePath);
|
|
998
|
+
const roots = [contract.workRoot, contract.projectRoot].map((root) => join(root, ".artifacts", "browser"));
|
|
999
|
+
return [...new Set(roots)];
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function pathStartsWithin(parent: string, target: string): boolean {
|
|
1003
|
+
const normalizedParent = parent.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1004
|
+
const normalizedTarget = target.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1005
|
+
return normalizedTarget === normalizedParent || normalizedTarget.startsWith(`${normalizedParent}/`);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function pushUnique(paths: string[], candidate: string): void {
|
|
1009
|
+
if (!paths.includes(candidate)) paths.push(candidate);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function execMetaPathCandidates(basePath: string, ref: string): string[] {
|
|
1013
|
+
const trimmed = ref.trim();
|
|
1014
|
+
const candidates: string[] = [];
|
|
1015
|
+
const execDirs = approvedEvidenceRoots(basePath).map((root) => join(root, "exec"));
|
|
1016
|
+
const normalizedRef = trimmed.replace(/\\/g, "/");
|
|
1017
|
+
const pathLike = normalizedRef.endsWith(".meta.json") || normalizedRef.includes("/.gsd/exec/");
|
|
1018
|
+
|
|
1019
|
+
if (pathLike) {
|
|
1020
|
+
const rawPath = isAbsolute(trimmed) ? resolve(trimmed) : resolve(basePath, trimmed);
|
|
1021
|
+
pushUnique(candidates, rawPath);
|
|
1022
|
+
|
|
1023
|
+
const relativeExecMarker = ".gsd/exec/";
|
|
1024
|
+
const markerIndex = normalizedRef.indexOf(relativeExecMarker);
|
|
1025
|
+
if (markerIndex >= 0) {
|
|
1026
|
+
const execRelative = normalizedRef.slice(markerIndex + relativeExecMarker.length);
|
|
1027
|
+
for (const execDir of execDirs) {
|
|
1028
|
+
pushUnique(candidates, join(execDir, execRelative));
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
return candidates.filter((candidate) =>
|
|
1033
|
+
execDirs.some((execDir) => pathStartsWithin(execDir, candidate))
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
for (const execDir of execDirs) {
|
|
1038
|
+
pushUnique(candidates, join(execDir, `${trimmed}.meta.json`));
|
|
1039
|
+
}
|
|
1040
|
+
return candidates;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function resolveExecMetaPath(basePath: string, ref: string): string | null {
|
|
1044
|
+
for (const candidate of execMetaPathCandidates(basePath, ref)) {
|
|
1045
|
+
if (existsSync(candidate)) return candidate;
|
|
1046
|
+
}
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function evidencePathIsApproved(basePath: string, ref: string): boolean {
|
|
1051
|
+
const normalizedRef = ref.replace(/\\/g, "/");
|
|
1052
|
+
if (normalizedRef.startsWith(".gsd/exec/") || normalizedRef.startsWith(".gsd/uat/")) return true;
|
|
1053
|
+
if (normalizedRef.startsWith(".artifacts/browser/")) {
|
|
1054
|
+
const resolvedRef = resolve(basePath, ref);
|
|
1055
|
+
return approvedBrowserArtifactRoots(basePath).some((root) => pathStartsWithin(root, resolvedRef));
|
|
1056
|
+
}
|
|
1057
|
+
const gsdEvidenceApproved = approvedEvidenceRoots(basePath).some((root) => {
|
|
1058
|
+
return pathStartsWithin(join(root, "exec"), ref) || pathStartsWithin(join(root, "uat"), ref);
|
|
1059
|
+
});
|
|
1060
|
+
if (gsdEvidenceApproved) return true;
|
|
1061
|
+
return approvedBrowserArtifactRoots(basePath).some((root) => pathStartsWithin(root, ref));
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function validateEvidenceRef(basePath: string, evidence: UatEvidenceRef): string | null {
|
|
1065
|
+
if (!isNonEmptyString(evidence.ref)) return "evidence.ref is required";
|
|
1066
|
+
if (evidence.kind === "gsd_uat_exec" || evidence.kind === "gsd_exec") {
|
|
1067
|
+
const path = resolveExecMetaPath(basePath, evidence.ref.trim());
|
|
1068
|
+
if (!path) return `missing gsd_exec metadata for evidence id "${evidence.ref}"`;
|
|
1069
|
+
if (evidence.kind === "gsd_uat_exec") {
|
|
1070
|
+
try {
|
|
1071
|
+
const meta = JSON.parse(readFileSync(path, "utf-8")) as { metadata?: { kind?: unknown } };
|
|
1072
|
+
if (meta.metadata?.kind !== "uat_exec") return `evidence id "${evidence.ref}" is not typed as uat_exec`;
|
|
1073
|
+
} catch {
|
|
1074
|
+
return `invalid gsd_exec metadata JSON for evidence id "${evidence.ref}"`;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
if (evidence.kind === "url") {
|
|
1080
|
+
try {
|
|
1081
|
+
const parsed = new URL(evidence.ref);
|
|
1082
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:"
|
|
1083
|
+
? null
|
|
1084
|
+
: `invalid URL evidence ref "${evidence.ref}"`;
|
|
1085
|
+
} catch {
|
|
1086
|
+
return `invalid URL evidence ref "${evidence.ref}"`;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return evidencePathIsApproved(basePath, evidence.ref)
|
|
1090
|
+
? null
|
|
1091
|
+
: `evidence ref "${evidence.ref}" is outside approved evidence locations`;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function validateUatChecks(basePath: string, params: UatResultSaveParams): string | null {
|
|
1095
|
+
for (const check of params.checks) {
|
|
1096
|
+
if (!isNonEmptyString(check.id)) return "every check must have a non-empty id";
|
|
1097
|
+
if (!isNonEmptyString(check.description)) return `check ${check.id} must have a description`;
|
|
1098
|
+
if (!["artifact", "runtime", "browser", "human-follow-up"].includes(check.mode)) {
|
|
1099
|
+
return `check ${check.id} has invalid mode "${check.mode}"`;
|
|
1100
|
+
}
|
|
1101
|
+
if (!["PASS", "FAIL", "NEEDS-HUMAN"].includes(check.result)) {
|
|
1102
|
+
return `check ${check.id} has invalid result "${check.result}"`;
|
|
1103
|
+
}
|
|
1104
|
+
if (check.result === "PASS" || check.result === "FAIL") {
|
|
1105
|
+
if (!Array.isArray(check.evidence) || check.evidence.length === 0) {
|
|
1106
|
+
return `check ${check.id} is ${check.result} but has no objective evidence`;
|
|
1107
|
+
}
|
|
1108
|
+
for (const evidence of check.evidence) {
|
|
1109
|
+
const error = validateEvidenceRef(basePath, evidence);
|
|
1110
|
+
if (error) return `check ${check.id}: ${error}`;
|
|
1111
|
+
}
|
|
1112
|
+
} else if (!isNonEmptyString(check.notes)) {
|
|
1113
|
+
return `check ${check.id} is NEEDS-HUMAN but has no manual instruction or reason`;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function validateUatMode(params: UatResultSaveParams): string | null {
|
|
1120
|
+
const modes = new Set(params.checks.map((check) => check.mode));
|
|
1121
|
+
const hasHuman = params.checks.some((check) => check.result === "NEEDS-HUMAN");
|
|
1122
|
+
if (
|
|
1123
|
+
hasHuman &&
|
|
1124
|
+
params.verdict === "PASS" &&
|
|
1125
|
+
!["human-experience", "mixed", "live-runtime"].includes(params.uatType) &&
|
|
1126
|
+
!params.checks.every((check) => check.result !== "NEEDS-HUMAN" || check.nonAutomatable === true)
|
|
1127
|
+
) {
|
|
1128
|
+
return "NEEDS-HUMAN checks can only coexist with PASS for human-experience, mixed, live-runtime, or explicitly non-automatable checks";
|
|
1129
|
+
}
|
|
1130
|
+
if (params.uatType === "runtime-executable" && !modes.has("runtime")) {
|
|
1131
|
+
return "runtime-executable UAT requires at least one runtime check";
|
|
1132
|
+
}
|
|
1133
|
+
if (params.uatType === "browser-executable" && !modes.has("browser")) {
|
|
1134
|
+
return "browser-executable UAT requires at least one browser check";
|
|
1135
|
+
}
|
|
1136
|
+
if (params.uatType === "live-runtime" && !modes.has("runtime") && !modes.has("browser")) {
|
|
1137
|
+
return "live-runtime UAT requires runtime or browser evidence";
|
|
1138
|
+
}
|
|
1139
|
+
if (params.uatType === "artifact-driven" && hasHuman && params.verdict === "PASS") {
|
|
1140
|
+
return "artifact-driven UAT cannot PASS with human-only checks";
|
|
1141
|
+
}
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function validateCanonicalPresentation(params: UatResultSaveParams): string | null {
|
|
1146
|
+
const aliasHints: Record<string, string> = {
|
|
1147
|
+
gsd_save_summary: "gsd_summary_save",
|
|
1148
|
+
gsd_complete_task: "gsd_task_complete",
|
|
1149
|
+
gsd_complete_slice: "gsd_slice_complete",
|
|
1150
|
+
gsd_milestone_complete: "gsd_complete_milestone",
|
|
1151
|
+
};
|
|
1152
|
+
for (const toolName of params.presentation.presentedTools) {
|
|
1153
|
+
const baseName = parseMcpToolName(toolName)?.tool ?? toolName;
|
|
1154
|
+
const canonical = aliasHints[baseName];
|
|
1155
|
+
if (canonical) return `presentation tool "${toolName}" uses an alias; use canonical "${canonical}"`;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const presentedCanonical = new Set(
|
|
1159
|
+
params.presentation.presentedTools.map((toolName) =>
|
|
1160
|
+
canonicalWorkflowToolName(parseMcpToolName(toolName)?.tool ?? toolName)
|
|
1161
|
+
),
|
|
1162
|
+
);
|
|
1163
|
+
for (const requiredTool of RUN_UAT_WORKFLOW_TOOL_NAMES) {
|
|
1164
|
+
if (!presentedCanonical.has(requiredTool)) {
|
|
1165
|
+
return `presentation is missing required UAT tool "${requiredTool}"`;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
const forbiddenCanonical = new Set(
|
|
1170
|
+
RUN_UAT_FORBIDDEN_TOOL_NAMES
|
|
1171
|
+
.filter((toolName) => !toolName.includes("*"))
|
|
1172
|
+
.map((toolName) => canonicalWorkflowToolName(parseMcpToolName(toolName)?.tool ?? toolName)),
|
|
1173
|
+
);
|
|
1174
|
+
for (const toolName of params.presentation.presentedTools) {
|
|
1175
|
+
const canonical = canonicalWorkflowToolName(parseMcpToolName(toolName)?.tool ?? toolName);
|
|
1176
|
+
if (toolName === "mcp__gsd-workflow__*" || forbiddenCanonical.has(canonical)) {
|
|
1177
|
+
return `presentation includes forbidden run-uat tool "${toolName}"`;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const blockedCanonical = new Set(
|
|
1182
|
+
params.presentation.blockedTools.map((entry) =>
|
|
1183
|
+
canonicalWorkflowToolName(parseMcpToolName(entry.name)?.tool ?? entry.name)
|
|
1184
|
+
),
|
|
1185
|
+
);
|
|
1186
|
+
for (const blockedTool of ["gsd_exec", "gsd_summary_save", "gsd_save_gate_result"]) {
|
|
1187
|
+
if (!blockedCanonical.has(blockedTool)) {
|
|
1188
|
+
return `presentation must record "${blockedTool}" as blocked during run-uat`;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function nextUatAttempt(basePath: string, milestoneId: string, sliceId: string): number {
|
|
1195
|
+
const contract = resolveGsdPathContract(basePath);
|
|
1196
|
+
const dir = join(contract.projectGsd, "uat", milestoneId, sliceId);
|
|
1197
|
+
if (!existsSync(dir)) return 1;
|
|
1198
|
+
let max = 0;
|
|
1199
|
+
for (const entry of readdirSync(dir)) {
|
|
1200
|
+
const match = /^attempt-(\d+)\.json$/.exec(entry);
|
|
1201
|
+
if (match) max = Math.max(max, Number(match[1]));
|
|
1202
|
+
}
|
|
1203
|
+
return max + 1;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function escapeMarkdownTableCell(value: unknown): string {
|
|
1207
|
+
return String(value ?? "")
|
|
1208
|
+
.replace(/[\\|]/g, (char) => `\\${char}`)
|
|
1209
|
+
.replace(/\r?\n/g, "<br>");
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function renderUatAssessment(params: UatResultSaveParams, attempt: number, gateVerdict: "pass" | "flag"): string {
|
|
1213
|
+
const lines = [
|
|
1214
|
+
"---",
|
|
1215
|
+
`sliceId: ${params.sliceId}`,
|
|
1216
|
+
`uatType: ${params.uatType}`,
|
|
1217
|
+
`verdict: ${params.verdict}`,
|
|
1218
|
+
`attempt: ${attempt}`,
|
|
1219
|
+
`date: ${new Date().toISOString()}`,
|
|
1220
|
+
"---",
|
|
1221
|
+
"",
|
|
1222
|
+
`# UAT Result - ${params.sliceId}`,
|
|
1223
|
+
"",
|
|
1224
|
+
"## Checks",
|
|
1225
|
+
"",
|
|
1226
|
+
"| Check | Mode | Result | Evidence | Notes |",
|
|
1227
|
+
"|-------|------|--------|----------|-------|",
|
|
1228
|
+
...params.checks.map((check) => {
|
|
1229
|
+
const evidence = (check.evidence ?? []).map((entry) => `${entry.kind}:${entry.ref}`).join("<br>") || "-";
|
|
1230
|
+
return `| ${escapeMarkdownTableCell(check.description)} | ${escapeMarkdownTableCell(check.mode)} | ${escapeMarkdownTableCell(check.result)} | ${escapeMarkdownTableCell(evidence)} | ${escapeMarkdownTableCell(check.notes)} |`;
|
|
1231
|
+
}),
|
|
1232
|
+
"",
|
|
1233
|
+
"## Overall Verdict",
|
|
1234
|
+
"",
|
|
1235
|
+
`${params.verdict} - ${params.notes ?? "UAT result saved."}`,
|
|
1236
|
+
"",
|
|
1237
|
+
"## Tool Presentation",
|
|
1238
|
+
"",
|
|
1239
|
+
"```json",
|
|
1240
|
+
JSON.stringify(params.presentation, null, 2),
|
|
1241
|
+
"```",
|
|
1242
|
+
"",
|
|
1243
|
+
"## Gate",
|
|
1244
|
+
"",
|
|
1245
|
+
`Aggregate UAT gate saved as ${gateVerdict}.`,
|
|
1246
|
+
];
|
|
1247
|
+
return `${lines.join("\n")}\n`;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
async function saveUatAttemptArtifact(basePath: string, params: UatResultSaveParams, attempt: number): Promise<string> {
|
|
1251
|
+
const contract = resolveGsdPathContract(basePath);
|
|
1252
|
+
const relativePath = `uat/${params.milestoneId}/${params.sliceId}/attempt-${attempt}.json`;
|
|
1253
|
+
await saveFile(join(contract.projectGsd, relativePath), `${JSON.stringify({ ...params, attempt }, null, 2)}\n`);
|
|
1254
|
+
return relativePath;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
export async function executeUatResultSave(
|
|
1258
|
+
params: UatResultSaveParams,
|
|
1259
|
+
basePath: string = process.cwd(),
|
|
1260
|
+
): Promise<ToolExecutionResult> {
|
|
1261
|
+
const dbAvailable = await ensureDbOpen(basePath);
|
|
1262
|
+
if (!dbAvailable) return errorResult("save_uat_result", "GSD database is not available.", "db_unavailable");
|
|
1263
|
+
|
|
1264
|
+
const requiredError = ensureUatRequiredFields(params);
|
|
1265
|
+
if (requiredError) return errorResult("save_uat_result", requiredError, "invalid_params");
|
|
1266
|
+
const presentationError = validateCanonicalPresentation(params);
|
|
1267
|
+
if (presentationError) return errorResult("save_uat_result", presentationError, "alias_tool_name");
|
|
1268
|
+
const checkError = validateUatChecks(basePath, params);
|
|
1269
|
+
if (checkError) return errorResult("save_uat_result", checkError, "invalid_evidence");
|
|
1270
|
+
const modeError = validateUatMode(params);
|
|
1271
|
+
if (modeError) return errorResult("save_uat_result", modeError, "uat_mode_mismatch");
|
|
1272
|
+
|
|
1273
|
+
try {
|
|
1274
|
+
const attempt = params.attempt === "auto" || params.attempt === undefined
|
|
1275
|
+
? nextUatAttempt(basePath, params.milestoneId, params.sliceId)
|
|
1276
|
+
: typeof params.attempt === "string"
|
|
1277
|
+
? Number.parseInt(params.attempt, 10)
|
|
1278
|
+
: params.attempt;
|
|
1279
|
+
if (!Number.isInteger(attempt) || attempt < 1) {
|
|
1280
|
+
return errorResult("save_uat_result", "attempt must be a positive integer or auto", "invalid_attempt");
|
|
1281
|
+
}
|
|
1282
|
+
const gateVerdict = params.verdict === "PASS" ? "pass" : "flag";
|
|
1283
|
+
const rationale = params.notes ?? `UAT ${params.verdict} for ${params.sliceId}.`;
|
|
1284
|
+
const assessment = renderUatAssessment(params, attempt, gateVerdict);
|
|
1285
|
+
const summary = await executeSummarySave(
|
|
1286
|
+
{
|
|
1287
|
+
milestone_id: params.milestoneId,
|
|
1288
|
+
slice_id: params.sliceId,
|
|
1289
|
+
artifact_type: "ASSESSMENT",
|
|
1290
|
+
content: assessment,
|
|
1291
|
+
},
|
|
1292
|
+
basePath,
|
|
1293
|
+
);
|
|
1294
|
+
if (summary.isError) return summary;
|
|
1295
|
+
const attemptPath = await saveUatAttemptArtifact(basePath, params, attempt);
|
|
1296
|
+
const evaluatedAt = new Date().toISOString();
|
|
1297
|
+
upsertQualityGate({
|
|
1298
|
+
milestoneId: params.milestoneId,
|
|
1299
|
+
sliceId: params.sliceId,
|
|
1300
|
+
gateId: "UAT",
|
|
1301
|
+
scope: "slice",
|
|
1302
|
+
taskId: "",
|
|
1303
|
+
status: "complete",
|
|
1304
|
+
verdict: gateVerdict,
|
|
1305
|
+
rationale,
|
|
1306
|
+
findings: assessment,
|
|
1307
|
+
evaluatedAt,
|
|
1308
|
+
});
|
|
1309
|
+
insertGateRun({
|
|
1310
|
+
traceId: `uat:${params.milestoneId}:${params.sliceId}`,
|
|
1311
|
+
turnId: `uat:${params.sliceId}:attempt-${attempt}`,
|
|
1312
|
+
gateId: "UAT",
|
|
1313
|
+
gateType: "uat",
|
|
1314
|
+
unitType: "run-uat",
|
|
1315
|
+
unitId: `run-uat:${params.milestoneId}/${params.sliceId}`,
|
|
1316
|
+
milestoneId: params.milestoneId,
|
|
1317
|
+
sliceId: params.sliceId,
|
|
1318
|
+
outcome: params.verdict === "PASS" ? "pass" : "fail",
|
|
1319
|
+
failureClass: params.verdict === "PASS" ? "none" : "verification",
|
|
1320
|
+
rationale,
|
|
1321
|
+
findings: assessment,
|
|
1322
|
+
attempt,
|
|
1323
|
+
maxAttempts: attempt,
|
|
1324
|
+
retryable: params.verdict !== "PASS",
|
|
1325
|
+
evaluatedAt,
|
|
1326
|
+
});
|
|
1327
|
+
invalidateStateCache();
|
|
1328
|
+
return {
|
|
1329
|
+
content: [{ type: "text", text: `UAT result saved for ${params.milestoneId}/${params.sliceId}: ${params.verdict}` }],
|
|
1330
|
+
details: {
|
|
1331
|
+
operation: "save_uat_result",
|
|
1332
|
+
milestoneId: params.milestoneId,
|
|
1333
|
+
sliceId: params.sliceId,
|
|
1334
|
+
verdict: params.verdict,
|
|
1335
|
+
gateVerdict,
|
|
1336
|
+
attempt,
|
|
1337
|
+
attemptPath,
|
|
1338
|
+
recommendedNextUnit: params.verdict === "PASS" ? null : "reactive-execute",
|
|
1339
|
+
},
|
|
1340
|
+
};
|
|
1341
|
+
} catch (err) {
|
|
1342
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1343
|
+
logError("tool", `gsd_uat_result_save failed: ${msg}`, { tool: "gsd_uat_result_save", error: String(err) });
|
|
1344
|
+
return errorResult("save_uat_result", `saving UAT result failed: ${msg}`, msg);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
910
1348
|
export async function executePlanMilestone(
|
|
911
1349
|
params: PlanMilestoneExecutorParams,
|
|
912
1350
|
basePath: string = process.cwd(),
|
|
@@ -137,7 +137,9 @@ export type ContextModePolicy =
|
|
|
137
137
|
* edits project markdown outside .gsd/.
|
|
138
138
|
* - "verification"
|
|
139
139
|
* — Read tools + Bash for verification commands, writes
|
|
140
|
-
* restricted to .gsd
|
|
140
|
+
* restricted to .gsd/**. Subagent dispatch is denied unless
|
|
141
|
+
* `allowedSubagents` opts a unit into controlled read-only
|
|
142
|
+
* specialist delegation.
|
|
141
143
|
*
|
|
142
144
|
* The allowlist for "docs" is declared per-manifest rather than hardcoded so
|
|
143
145
|
* projects with non-standard doc layouts can extend it without forking the
|
|
@@ -150,7 +152,7 @@ export type ToolsPolicy =
|
|
|
150
152
|
| { readonly mode: "planning" }
|
|
151
153
|
| { readonly mode: "planning-dispatch"; readonly allowedSubagents: readonly string[] }
|
|
152
154
|
| { readonly mode: "docs"; readonly allowedPathGlobs: readonly string[] }
|
|
153
|
-
| { readonly mode: "verification" };
|
|
155
|
+
| { readonly mode: "verification"; readonly allowedSubagents?: readonly string[] };
|
|
154
156
|
|
|
155
157
|
// ─── Computed-artifact registry (#4924 v2 contract) ───────────────────────
|
|
156
158
|
|
|
@@ -305,6 +307,10 @@ const COMMON_BUDGET_SMALL = 250_000; // ~65K tokens
|
|
|
305
307
|
const TOOLS_ALL: ToolsPolicy = { mode: "all" };
|
|
306
308
|
const TOOLS_PLANNING: ToolsPolicy = { mode: "planning" };
|
|
307
309
|
const TOOLS_VERIFICATION: ToolsPolicy = { mode: "verification" };
|
|
310
|
+
const TOOLS_VERIFICATION_DISPATCH_UAT: ToolsPolicy = {
|
|
311
|
+
mode: "verification",
|
|
312
|
+
allowedSubagents: ["mnemo", "scout", "reviewer", "tester"],
|
|
313
|
+
};
|
|
308
314
|
// Like TOOLS_PLANNING but permits dispatch to read-only recon/planning
|
|
309
315
|
// specialists. Runtime-enforced by write-gate.ts before the subagent tool runs.
|
|
310
316
|
const TOOLS_PLANNING_DISPATCH_RECON: ToolsPolicy = {
|
|
@@ -601,7 +607,7 @@ export const UNIT_MANIFESTS: Record<UnitType, UnitContextManifest> = {
|
|
|
601
607
|
codebaseMap: false,
|
|
602
608
|
preferences: "active-only",
|
|
603
609
|
contextMode: "verification",
|
|
604
|
-
tools:
|
|
610
|
+
tools: TOOLS_VERIFICATION_DISPATCH_UAT,
|
|
605
611
|
artifacts: {
|
|
606
612
|
inline: ["slice-uat"],
|
|
607
613
|
excerpt: ["slice-summary"],
|
|
@@ -792,9 +798,12 @@ export function compileSubagentPermissionContract(
|
|
|
792
798
|
if (policy.mode === "all") {
|
|
793
799
|
return { allowed: true, allowedSubagents: ["*"], toolsMode: policy.mode };
|
|
794
800
|
}
|
|
795
|
-
if (
|
|
801
|
+
if (
|
|
802
|
+
(policy.mode === "planning-dispatch" || policy.mode === "verification") &&
|
|
803
|
+
Array.isArray(policy.allowedSubagents)
|
|
804
|
+
) {
|
|
796
805
|
return {
|
|
797
|
-
allowed:
|
|
806
|
+
allowed: policy.allowedSubagents.length > 0,
|
|
798
807
|
allowedSubagents: [...policy.allowedSubagents],
|
|
799
808
|
toolsMode: policy.mode,
|
|
800
809
|
};
|
|
@@ -14,6 +14,8 @@ const VALIDATION_BLOCK_RE =
|
|
|
14
14
|
/milestone validation returned needs-(?:attention|remediation)|validation verdict is needs-(?:attention|remediation)/i;
|
|
15
15
|
|
|
16
16
|
const VALIDATION_SAFE_DISPATCH_COMMANDS = new Set([
|
|
17
|
+
"reassess",
|
|
18
|
+
"reassess-roadmap",
|
|
17
19
|
"validate",
|
|
18
20
|
"validate-milestone",
|
|
19
21
|
]);
|
|
@@ -73,7 +73,7 @@ export function prepareWorkflowMcpForProject(
|
|
|
73
73
|
try {
|
|
74
74
|
const result = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
75
75
|
if (result.status !== "unchanged") {
|
|
76
|
-
prepCtx.ui?.notify?.(`
|
|
76
|
+
prepCtx.ui?.notify?.(`GSD MCP Server Prepared at ${result.configPath}`, "info");
|
|
77
77
|
}
|
|
78
78
|
return result;
|
|
79
79
|
} catch (err) {
|
|
@@ -2,6 +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
6
|
|
|
6
7
|
type WorkflowExecutorsModule = typeof import("./tools/workflow-tool-executors.js");
|
|
7
8
|
|
|
@@ -100,6 +101,8 @@ const MCP_WORKFLOW_TOOL_SURFACE = new Set([
|
|
|
100
101
|
"gsd_task_complete",
|
|
101
102
|
"gsd_task_reopen",
|
|
102
103
|
"gsd_update_requirement",
|
|
104
|
+
"gsd_uat_exec",
|
|
105
|
+
"gsd_uat_result_save",
|
|
103
106
|
"gsd_validate_milestone",
|
|
104
107
|
]);
|
|
105
108
|
|
|
@@ -462,8 +465,9 @@ export function getRequiredWorkflowToolsForAutoUnit(unitType: string): string[]
|
|
|
462
465
|
];
|
|
463
466
|
case "research-milestone":
|
|
464
467
|
case "research-slice":
|
|
465
|
-
case "run-uat":
|
|
466
468
|
return ["gsd_summary_save"];
|
|
469
|
+
case "run-uat":
|
|
470
|
+
return [...RUN_UAT_WORKFLOW_TOOL_NAMES];
|
|
467
471
|
case "plan-milestone":
|
|
468
472
|
return ["gsd_plan_milestone"];
|
|
469
473
|
case "plan-slice":
|
|
File without changes
|
|
File without changes
|