@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
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5f6f7839b5420a75
|
|
@@ -51,13 +51,13 @@ import { resolveManifest } from "../unit-context-manifest.js";
|
|
|
51
51
|
import { createWorktreeSafetyModule } from "../worktree-safety.js";
|
|
52
52
|
import { isSuspiciousGhostCompletion } from "../auto-unit-closeout.js";
|
|
53
53
|
import { decideVerificationRetry, verificationRetryKey } from "./verification-retry-policy.js";
|
|
54
|
-
import { buildPhaseHandoffOutcome, setAutoOutcomeWidget } from "../auto-dashboard.js";
|
|
54
|
+
import { buildPhaseHandoffOutcome, setAutoActiveStatus, setAutoOutcomeWidget } from "../auto-dashboard.js";
|
|
55
55
|
import { getConsecutiveDispatchBlocker } from "../dispatch-guard.js";
|
|
56
56
|
import { captureRootDirtySnapshot, detectRootWriteLeak, formatRootWriteLeakMessage, } from "../root-write-leak-guard.js";
|
|
57
57
|
import { classifyError, isTransient } from "../error-classifier.js";
|
|
58
58
|
export const STUCK_WINDOW_SIZE = 6;
|
|
59
59
|
const STUCK_RECOVERY_ATTEMPTS_KEY = "stuck_recovery_attempts";
|
|
60
|
-
const ZERO_TOOL_PROVIDER_ERROR_PREFIX_RE = /^(?:api error(?::|$|\s*\()|provider error(?::|$|\s*\()|request failed\b|(?:http\s*)?(?:429|500|502|503)\b|\b(?:econnreset|etimedout|econnrefused|epipe)\b|socket hang up\b|fetch failed\b|(?:network|connection|server) error(?::|$)|connection (?:reset|refused)(?::|$|\s+by\b)|dns\b.*(?:fail|error|timeout)|unexpected eof\b|stream idle timeout\b|partial response received\b|stream_exhausted\b|terminated(?::|$)|(?:connection|stream|request)\b.{0,40}\bterminated\b|other side closed\b|rate.?limit(?:ed| exceeded| reached| error)|too many requests\b|you(?:'ve| have) hit your limit\b|
|
|
60
|
+
const ZERO_TOOL_PROVIDER_ERROR_PREFIX_RE = /^(?:api error(?::|$|\s*\()|provider error(?::|$|\s*\()|request failed\b|(?:http\s*)?(?:429|500|502|503)\b|\b(?:econnreset|etimedout|econnrefused|epipe)\b|socket hang up\b|fetch failed\b|(?:network|connection|server) error(?::|$)|connection (?:reset|refused)(?::|$|\s+by\b)|dns\b.*(?:fail|error|timeout)|unexpected eof\b|stream idle timeout\b|partial response received\b|stream_exhausted\b|terminated(?::|$)|(?:connection|stream|request)\b.{0,40}\bterminated\b|other side closed\b|rate.?limit(?:ed| exceeded| reached| error)|too many requests\b|you(?:'ve| have) (?:hit|reached) your (?:\w+ )?limit\b|.*\b(?:usage|session|weekly|daily|monthly|quota) limit\b|limit\b.{0,40}\bresets?\b|out of extra usage\b|service.?unavailable\b|internal(?: server)? error(?::|$)|internal(?:[_-]server)?[_-]error\b|server[_-]error\b|(?:provider|server|api|model|codex|claude|openai|anthropic|gemini)\b.{0,80}\boverloaded\b|overloaded\b.{0,80}\b(?:provider|server|api|model)\b|context (?:window|length) exceed|context window exceed)/i;
|
|
61
61
|
const ZERO_TOOL_PROVIDER_ERROR_SIGNAL_RE = /(?:\b(?:http|status(?: code)?|code|error:)\s*(?:429|500|502|503)\b|\b(?:api|provider) error\s*[:(]?\s*(?:429|500|502|503)\b|\b(?:typeerror|error):\s*(?:fetch failed\b|socket hang up\b|terminated(?::|$)|connection (?:reset|refused)(?::|$|\s+by\b)|(?:network|connection|server) error(?::|$)|stream idle timeout\b|partial response received\b|unexpected eof\b)|\b(?:server_error|api_error|stream_exhausted(?:_without_result)?)\b|\b(?:econnreset|etimedout|econnrefused|epipe)\b|context (?:window|length) exceed|context window exceed)/i;
|
|
62
62
|
function classifyZeroToolProviderMessage(message) {
|
|
63
63
|
const firstLine = message.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
@@ -67,6 +67,7 @@ function classifyZeroToolProviderMessage(message) {
|
|
|
67
67
|
return null;
|
|
68
68
|
return classifyError(firstLine);
|
|
69
69
|
}
|
|
70
|
+
export const _classifyZeroToolProviderMessageForTest = classifyZeroToolProviderMessage;
|
|
70
71
|
export function resolveDispatchRecoveryAttempts(unitRecoveryCount, unitType, unitId) {
|
|
71
72
|
return (unitRecoveryCount.get(`${unitType}/${unitId}`) ?? 0) > 0
|
|
72
73
|
? 0
|
|
@@ -1582,7 +1583,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1582
1583
|
const dispatchKey = `${unitType}/${unitId}`;
|
|
1583
1584
|
const nextDispatchCount = (s.unitDispatchCount.get(dispatchKey) ?? 0) + 1;
|
|
1584
1585
|
// Status bar (widget + preconditions deferred until after model selection — see #2899)
|
|
1585
|
-
ctx
|
|
1586
|
+
setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
|
|
1586
1587
|
if (mid)
|
|
1587
1588
|
deps.updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
|
|
1588
1589
|
// ── Safety harness: reset evidence + create checkpoint ──
|
|
@@ -369,8 +369,9 @@ export const hideFooter = (_tui, theme, footerData) => ({
|
|
|
369
369
|
invalidate() { },
|
|
370
370
|
dispose() { },
|
|
371
371
|
});
|
|
372
|
+
export const DEFAULT_WIDGET_MODE = "small";
|
|
372
373
|
const WIDGET_MODES = ["full", "small", "min", "off"];
|
|
373
|
-
let widgetMode =
|
|
374
|
+
let widgetMode = DEFAULT_WIDGET_MODE;
|
|
374
375
|
let widgetModeInitialized = false;
|
|
375
376
|
let widgetModePreferencePath = null;
|
|
376
377
|
function safeReadTextFile(path) {
|
|
@@ -473,10 +474,19 @@ export function getWidgetMode(projectPath, globalPath) {
|
|
|
473
474
|
}
|
|
474
475
|
/** Test-only reset for widget mode caching. */
|
|
475
476
|
export function _resetWidgetModeForTests() {
|
|
476
|
-
widgetMode =
|
|
477
|
+
widgetMode = DEFAULT_WIDGET_MODE;
|
|
477
478
|
widgetModeInitialized = false;
|
|
478
479
|
widgetModePreferencePath = null;
|
|
479
480
|
}
|
|
481
|
+
function clearAutoOutcomeWidget(ctx) {
|
|
482
|
+
if (!ctx.hasUI)
|
|
483
|
+
return;
|
|
484
|
+
ctx.ui.setWidget("gsd-outcome", undefined);
|
|
485
|
+
}
|
|
486
|
+
export function setAutoActiveStatus(ctx, status) {
|
|
487
|
+
ctx.ui.setStatus("gsd-auto", status);
|
|
488
|
+
clearAutoOutcomeWidget(ctx);
|
|
489
|
+
}
|
|
480
490
|
export function updateProgressWidget(ctx, unitType, unitId, state, accessors, tierBadge) {
|
|
481
491
|
if (!ctx.hasUI)
|
|
482
492
|
return;
|
|
@@ -495,7 +505,7 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
495
505
|
ctx.ui.setStatus("gsd-step", undefined);
|
|
496
506
|
}
|
|
497
507
|
if (!accessors.isSessionSwitching()) {
|
|
498
|
-
ctx
|
|
508
|
+
clearAutoOutcomeWidget(ctx);
|
|
499
509
|
}
|
|
500
510
|
const verb = unitVerb(unitType);
|
|
501
511
|
const phaseLabel = unitPhaseLabel(unitType);
|
|
@@ -548,6 +558,7 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
548
558
|
logWarning("dashboard", `DB status update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
549
559
|
}
|
|
550
560
|
}, 15_000);
|
|
561
|
+
progressRefreshTimer.unref?.();
|
|
551
562
|
return {
|
|
552
563
|
render(width) {
|
|
553
564
|
if (cachedLines && cachedWidth === width)
|
|
@@ -789,7 +800,7 @@ export function setCompletionProgressWidget(ctx, snapshot) {
|
|
|
789
800
|
if (!ctx.hasUI)
|
|
790
801
|
return;
|
|
791
802
|
const widgetKey = "gsd-progress";
|
|
792
|
-
ctx
|
|
803
|
+
clearAutoOutcomeWidget(ctx);
|
|
793
804
|
if (typeof ctx.ui?.setHeader === "function") {
|
|
794
805
|
ctx.ui.setHeader(() => ({
|
|
795
806
|
render() { return []; },
|
|
@@ -323,6 +323,41 @@ function stripKnownIdPrefix(value, id) {
|
|
|
323
323
|
return raw.slice(id.length + 1).trim() || undefined;
|
|
324
324
|
return raw;
|
|
325
325
|
}
|
|
326
|
+
function parseReactiveBatchTaskIds(unitId) {
|
|
327
|
+
const { task: batchPart } = parseUnitId(unitId);
|
|
328
|
+
if (!batchPart?.startsWith("reactive+"))
|
|
329
|
+
return [];
|
|
330
|
+
const rawIds = batchPart
|
|
331
|
+
.slice("reactive+".length)
|
|
332
|
+
.split(",")
|
|
333
|
+
.map((taskId) => taskId.trim().toUpperCase())
|
|
334
|
+
.filter(Boolean);
|
|
335
|
+
const unique = new Set();
|
|
336
|
+
for (const taskId of rawIds) {
|
|
337
|
+
unique.add(taskId);
|
|
338
|
+
}
|
|
339
|
+
return [...unique];
|
|
340
|
+
}
|
|
341
|
+
function dedupePaths(values) {
|
|
342
|
+
const seen = new Set();
|
|
343
|
+
const result = [];
|
|
344
|
+
for (const value of values) {
|
|
345
|
+
if (!seen.has(value)) {
|
|
346
|
+
seen.add(value);
|
|
347
|
+
result.push(value);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
function getPlannedKeyFiles(tasks) {
|
|
353
|
+
return dedupePaths(tasks.flatMap((taskRow) => [
|
|
354
|
+
...(taskRow.expected_output ?? []),
|
|
355
|
+
...(taskRow.files ?? []),
|
|
356
|
+
...(taskRow.key_files ?? []),
|
|
357
|
+
]));
|
|
358
|
+
}
|
|
359
|
+
export const _parseReactiveBatchTaskIdsForTest = parseReactiveBatchTaskIds;
|
|
360
|
+
export const _getPlannedKeyFilesForTest = getPlannedKeyFiles;
|
|
326
361
|
function resolveVerificationFailureMarkerPath(unitType, unitId, basePath) {
|
|
327
362
|
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
328
363
|
switch (unitType) {
|
|
@@ -402,6 +437,34 @@ async function buildTaskCommitContextForUnit(basePath, unitId) {
|
|
|
402
437
|
issueNumber: ghIssueNumber,
|
|
403
438
|
};
|
|
404
439
|
}
|
|
440
|
+
async function buildReactiveTaskCommitContext(_basePath, unitId) {
|
|
441
|
+
const { milestone: mid, slice: sid } = parseUnitId(unitId);
|
|
442
|
+
if (!mid || !sid || !isDbAvailable())
|
|
443
|
+
return undefined;
|
|
444
|
+
const batchTaskIds = parseReactiveBatchTaskIds(unitId);
|
|
445
|
+
if (batchTaskIds.length === 0)
|
|
446
|
+
return undefined;
|
|
447
|
+
const milestone = getMilestone(mid);
|
|
448
|
+
const slice = getSlice(mid, sid);
|
|
449
|
+
const taskRows = batchTaskIds
|
|
450
|
+
.map((tid) => getTask(mid, sid, tid))
|
|
451
|
+
.filter((taskRow) => taskRow !== null);
|
|
452
|
+
const keyFiles = getPlannedKeyFiles(taskRows);
|
|
453
|
+
if (taskRows.length === 0 || keyFiles.length === 0)
|
|
454
|
+
return undefined;
|
|
455
|
+
const taskLabel = taskRows.map((row) => row.id).join(",");
|
|
456
|
+
return {
|
|
457
|
+
taskId: `${sid}/${taskLabel}`,
|
|
458
|
+
taskDisplayId: "reactive-batch",
|
|
459
|
+
taskTitle: `Reactive batch: ${taskLabel}`,
|
|
460
|
+
milestoneId: mid,
|
|
461
|
+
milestoneTitle: stripKnownIdPrefix(milestone?.title, mid),
|
|
462
|
+
sliceId: sid,
|
|
463
|
+
sliceTitle: stripKnownIdPrefix(slice?.title, sid),
|
|
464
|
+
oneLiner: `Reactive execute for ${taskLabel}`,
|
|
465
|
+
keyFiles,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
405
468
|
async function runPostUnitGitHubSyncIfNeeded(basePath, unit) {
|
|
406
469
|
if (unit.type === "complete-milestone")
|
|
407
470
|
return;
|
|
@@ -761,6 +824,9 @@ export async function autoCommitUnit(basePath, unitType, unitId, ctx) {
|
|
|
761
824
|
if (unitType === "execute-task") {
|
|
762
825
|
taskContext = await buildTaskCommitContextForUnit(basePath, unitId);
|
|
763
826
|
}
|
|
827
|
+
else if (unitType === "reactive-execute") {
|
|
828
|
+
taskContext = await buildReactiveTaskCommitContext(basePath, unitId);
|
|
829
|
+
}
|
|
764
830
|
_resetHasChangesCache();
|
|
765
831
|
if (LIFECYCLE_ONLY_UNITS.has(unitType)) {
|
|
766
832
|
return null;
|
|
@@ -812,6 +878,22 @@ async function runCloseoutGitAction(pctx, unit, opts) {
|
|
|
812
878
|
targetRepositories = getTask(mid, sid, tid)?.target_repositories;
|
|
813
879
|
}
|
|
814
880
|
}
|
|
881
|
+
else if (turnAction === "commit" && unit.type === "reactive-execute") {
|
|
882
|
+
taskContext = await buildReactiveTaskCommitContext(s.basePath, unit.id);
|
|
883
|
+
const { milestone: mid, slice: sid } = parseUnitId(unit.id);
|
|
884
|
+
if (mid && sid && isDbAvailable()) {
|
|
885
|
+
const repositories = new Set();
|
|
886
|
+
for (const tid of parseReactiveBatchTaskIds(unit.id)) {
|
|
887
|
+
const taskRow = getTask(mid, sid, tid);
|
|
888
|
+
for (const repoId of taskRow?.target_repositories ?? []) {
|
|
889
|
+
repositories.add(repoId);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (repositories.size > 0) {
|
|
893
|
+
targetRepositories = [...repositories];
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
815
897
|
// Invalidate the nativeHasChanges cache before auto-commit (#1853).
|
|
816
898
|
// The cache has a 10-second TTL and is keyed by basePath. A stale
|
|
817
899
|
// `false` result causes autoCommit to skip staging entirely.
|
|
@@ -1206,12 +1288,19 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
1206
1288
|
if (safetyConfig.enabled) {
|
|
1207
1289
|
const { milestone: sMid, slice: sSid, task: sTid } = parseUnitId(s.currentUnit.id);
|
|
1208
1290
|
// File change validation (execute-task only, after unit execution)
|
|
1209
|
-
if (safetyConfig.file_change_validation && s.currentUnit.type === "execute-task" && sMid && sSid && sTid
|
|
1291
|
+
if (safetyConfig.file_change_validation && s.currentUnit.type === "execute-task" && sMid && sSid && sTid) {
|
|
1210
1292
|
try {
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1293
|
+
const sliceTaskRows = isDbAvailable()
|
|
1294
|
+
? getSliceTasks(sMid, sSid).filter((t) => isClosedStatus(t.status) || t.id === sTid)
|
|
1295
|
+
: [];
|
|
1296
|
+
if (sliceTaskRows.length > 0) {
|
|
1297
|
+
const expectedOutput = getPlannedKeyFiles(sliceTaskRows.map((taskRow) => ({
|
|
1298
|
+
expected_output: taskRow.expected_output,
|
|
1299
|
+
files: taskRow.files,
|
|
1300
|
+
})));
|
|
1301
|
+
const plannedFiles = getPlannedKeyFiles(sliceTaskRows.map((taskRow) => ({
|
|
1302
|
+
files: taskRow.files,
|
|
1303
|
+
})));
|
|
1215
1304
|
const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles, safetyConfig.file_change_allowlist);
|
|
1216
1305
|
if (audit && audit.violations.length > 0) {
|
|
1217
1306
|
const warnings = audit.violations.filter(v => v.severity === "warning");
|
|
@@ -1223,6 +1312,23 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
1223
1312
|
}
|
|
1224
1313
|
}
|
|
1225
1314
|
}
|
|
1315
|
+
else {
|
|
1316
|
+
const taskRow = getTask(sMid, sSid, sTid);
|
|
1317
|
+
if (taskRow) {
|
|
1318
|
+
const expectedOutput = taskRow.expected_output ?? [];
|
|
1319
|
+
const plannedFiles = taskRow.files ?? [];
|
|
1320
|
+
const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles, safetyConfig.file_change_allowlist);
|
|
1321
|
+
if (audit && audit.violations.length > 0) {
|
|
1322
|
+
const warnings = audit.violations.filter(v => v.severity === "warning");
|
|
1323
|
+
for (const v of warnings) {
|
|
1324
|
+
logWarning("safety", `file-change: ${v.file} — ${v.reason}`);
|
|
1325
|
+
}
|
|
1326
|
+
if (warnings.length > 0) {
|
|
1327
|
+
ctx.ui.notify(`Safety: ${warnings.length} unexpected file change(s) outside task plan`, "warning");
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1226
1332
|
}
|
|
1227
1333
|
catch (e) {
|
|
1228
1334
|
debugLog("postUnit", { phase: "safety-file-change", error: String(e) });
|
|
@@ -2351,6 +2351,15 @@ export async function buildCompleteSlicePrompt(mid, midTitle, sid, sTitle, base,
|
|
|
2351
2351
|
sliceSummaryPath,
|
|
2352
2352
|
sliceUatPath,
|
|
2353
2353
|
gatesToClose,
|
|
2354
|
+
skillActivation: buildSkillActivationBlock({
|
|
2355
|
+
base,
|
|
2356
|
+
milestoneId: mid,
|
|
2357
|
+
milestoneTitle: midTitle,
|
|
2358
|
+
sliceId: sid,
|
|
2359
|
+
sliceTitle: sTitle,
|
|
2360
|
+
extraContext: [inlinedContext],
|
|
2361
|
+
unitType: "complete-slice",
|
|
2362
|
+
}),
|
|
2354
2363
|
});
|
|
2355
2364
|
}
|
|
2356
2365
|
export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
|
|
@@ -49,6 +49,7 @@ import { resolveProjectRootDbPath } from "./bootstrap/dynamic-tools.js";
|
|
|
49
49
|
import { validateDirectory } from "./validate-directory.js";
|
|
50
50
|
import { isCustomProvider, resolveDefaultSessionModel, resolveDynamicRoutingConfig, } from "./preferences-models.js";
|
|
51
51
|
import { getSessionModelOverride } from "./session-model-override.js";
|
|
52
|
+
import { setAutoActiveStatus } from "./auto-dashboard.js";
|
|
52
53
|
export function resolveIsolationNoneBranchCheckout(currentBranch, integrationBranch, isolationMode, isRepo) {
|
|
53
54
|
if (!isRepo || isolationMode !== "none")
|
|
54
55
|
return null;
|
|
@@ -164,6 +165,19 @@ export function resolveSurvivorRecoveryIsolationMode(isolationMode, phase) {
|
|
|
164
165
|
function isBlockingStrandedWorkAction(action) {
|
|
165
166
|
return action.kind === "in-progress-stranded-work" && action.blocksAuto;
|
|
166
167
|
}
|
|
168
|
+
function strandedWorkEvidence(args) {
|
|
169
|
+
const evidence = [];
|
|
170
|
+
if (args.branch && args.commitsAhead > 0) {
|
|
171
|
+
evidence.push(`branch ${args.branch} has ${args.commitsAhead} commit(s) ahead of ${args.mainBranch}`);
|
|
172
|
+
}
|
|
173
|
+
if (args.dirtyWorktree) {
|
|
174
|
+
evidence.push("the worktree has uncommitted changes");
|
|
175
|
+
}
|
|
176
|
+
if (evidence.length === 0) {
|
|
177
|
+
evidence.push("physical git evidence exists");
|
|
178
|
+
}
|
|
179
|
+
return evidence;
|
|
180
|
+
}
|
|
167
181
|
function detectWorktreeEvidence(basePath, milestoneId, hasChanges) {
|
|
168
182
|
const wtDir = getWorktreeDir(basePath, milestoneId);
|
|
169
183
|
const wtPath = getAutoWorktreePath(basePath, milestoneId);
|
|
@@ -183,16 +197,7 @@ function detectWorktreeEvidence(basePath, milestoneId, hasChanges) {
|
|
|
183
197
|
};
|
|
184
198
|
}
|
|
185
199
|
function strandedWorkMessage(args) {
|
|
186
|
-
const evidence =
|
|
187
|
-
if (args.branch && args.commitsAhead > 0) {
|
|
188
|
-
evidence.push(`branch ${args.branch} has ${args.commitsAhead} commit(s) ahead of ${args.mainBranch}`);
|
|
189
|
-
}
|
|
190
|
-
if (args.dirtyWorktree) {
|
|
191
|
-
evidence.push("the worktree has uncommitted changes");
|
|
192
|
-
}
|
|
193
|
-
if (evidence.length === 0) {
|
|
194
|
-
evidence.push("physical git evidence exists");
|
|
195
|
-
}
|
|
200
|
+
const evidence = strandedWorkEvidence(args);
|
|
196
201
|
const wtSuffix = args.worktreeDirExists
|
|
197
202
|
? ` Worktree directory at .gsd/worktrees/${args.milestoneId}/ holds live work.`
|
|
198
203
|
: "";
|
|
@@ -203,6 +208,23 @@ function strandedWorkMessage(args) {
|
|
|
203
208
|
wtSuffix +
|
|
204
209
|
` ${recovery} Park or discard explicitly if abandoning.`);
|
|
205
210
|
}
|
|
211
|
+
function formatStrandedWorkRecoveryMessage(action) {
|
|
212
|
+
const recoveryMode = action.recoveryMode === "worktree"
|
|
213
|
+
? "existing worktree"
|
|
214
|
+
: "milestone branch";
|
|
215
|
+
const evidence = strandedWorkEvidence({
|
|
216
|
+
branch: action.branch,
|
|
217
|
+
commitsAhead: action.commitsAhead ?? 0,
|
|
218
|
+
mainBranch: action.mainBranch ?? "main",
|
|
219
|
+
dirtyWorktree: action.dirtyWorktree ?? false,
|
|
220
|
+
});
|
|
221
|
+
const wtSuffix = action.worktreeDirExists
|
|
222
|
+
? ` Worktree directory at .gsd/worktrees/${action.milestoneId}/ holds live work.`
|
|
223
|
+
: "";
|
|
224
|
+
return (`Resuming saved milestone work for ${action.milestoneId}: ${evidence.join("; ")}.` +
|
|
225
|
+
wtSuffix +
|
|
226
|
+
` Adopting the ${recoveryMode} before dispatching new units. Park or discard explicitly if abandoning.`);
|
|
227
|
+
}
|
|
206
228
|
function formatStrandedWorkBlockerMessage(action, activeMilestoneId) {
|
|
207
229
|
const target = action.milestoneId;
|
|
208
230
|
const mode = action.recoveryMode === "worktree" ? "existing worktree" : "milestone branch";
|
|
@@ -307,6 +329,7 @@ export function auditOrphanedMilestoneBranches(basePath, _isolationMode, gitDeps
|
|
|
307
329
|
kind: "in-progress-stranded-work",
|
|
308
330
|
milestoneId,
|
|
309
331
|
branch,
|
|
332
|
+
mainBranch,
|
|
310
333
|
commitsAhead,
|
|
311
334
|
dirtyWorktree: worktreeEvidence.dirty,
|
|
312
335
|
worktreeDirExists: worktreeEvidence.dirExists,
|
|
@@ -462,6 +485,7 @@ export function auditOrphanedMilestoneBranches(basePath, _isolationMode, gitDeps
|
|
|
462
485
|
pushAction({
|
|
463
486
|
kind: "in-progress-stranded-work",
|
|
464
487
|
milestoneId: m.id,
|
|
488
|
+
mainBranch,
|
|
465
489
|
commitsAhead: 0,
|
|
466
490
|
dirtyWorktree: true,
|
|
467
491
|
worktreeDirExists: worktreeEvidence.dirExists,
|
|
@@ -890,7 +914,12 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
890
914
|
for (const msg of auditResult.recovered) {
|
|
891
915
|
ctx.ui.notify(`Orphan audit: ${msg}`, "info");
|
|
892
916
|
}
|
|
917
|
+
const deferredStrandedMessages = new Set(auditResult.actions
|
|
918
|
+
.filter(isBlockingStrandedWorkAction)
|
|
919
|
+
.map((action) => action.message));
|
|
893
920
|
for (const msg of auditResult.warnings) {
|
|
921
|
+
if (deferredStrandedMessages.has(msg))
|
|
922
|
+
continue;
|
|
894
923
|
const prefix = msg.startsWith("Stranded work") ? "" : "Orphan audit: ";
|
|
895
924
|
ctx.ui.notify(`${prefix}${msg}`, "warning");
|
|
896
925
|
}
|
|
@@ -958,7 +987,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
958
987
|
return releaseLockAndReturn();
|
|
959
988
|
}
|
|
960
989
|
strandedRecoveryAction = blockingStrandedRecoveryAction;
|
|
961
|
-
ctx.ui.notify(
|
|
990
|
+
ctx.ui.notify(formatStrandedWorkRecoveryMessage(strandedRecoveryAction), "info");
|
|
962
991
|
}
|
|
963
992
|
if (process.env.GSD_HEADLESS === "1" &&
|
|
964
993
|
orphanAuditRecovered &&
|
|
@@ -1343,7 +1372,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
1343
1372
|
if (resolveSkillDiscoveryMode(base) !== "off") {
|
|
1344
1373
|
snapshotSkills();
|
|
1345
1374
|
}
|
|
1346
|
-
ctx
|
|
1375
|
+
setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
|
|
1347
1376
|
ctx.ui.setWidget("gsd-health", undefined);
|
|
1348
1377
|
const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode";
|
|
1349
1378
|
const pendingCount = (state.registry ?? []).filter((m) => m.status !== "complete" && m.status !== "parked").length;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseUnitId } from "./unit-id.js";
|
|
2
|
+
import { RUN_UAT_WORKFLOW_TOOL_NAMES } from "./tool-presentation-plan.js";
|
|
2
3
|
export const RUN_UAT_BROWSER_TOOL_NAMES = [
|
|
3
4
|
"browser_navigate",
|
|
4
5
|
"browser_click",
|
|
@@ -42,7 +43,7 @@ export const AUTO_UNIT_SCOPED_TOOLS = {
|
|
|
42
43
|
"execute-task": ["gsd_task_complete", "gsd_decision_save"],
|
|
43
44
|
"execute-task-simple": ["gsd_task_complete", "gsd_decision_save"],
|
|
44
45
|
"reactive-execute": ["gsd_task_complete", "gsd_decision_save"],
|
|
45
|
-
"run-uat": ["
|
|
46
|
+
"run-uat": [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent", ...RUN_UAT_BROWSER_TOOL_NAMES],
|
|
46
47
|
"gate-evaluate": ["gsd_save_gate_result"],
|
|
47
48
|
"rewrite-docs": ["gsd_summary_save", "gsd_decision_save"],
|
|
48
49
|
"workflow-preferences": ["gsd_summary_save"],
|
|
@@ -64,7 +64,7 @@ import { initRegistry, convertDispatchRules } from "./rule-registry.js";
|
|
|
64
64
|
import { emitJournalEvent as _emitJournalEvent } from "./journal.js";
|
|
65
65
|
import { isClosedStatus } from "./status-guards.js";
|
|
66
66
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
67
|
-
import { updateProgressWidget as _updateProgressWidget, setCompletionProgressWidget, setAutoOutcomeWidget, updateSliceProgressCache, clearSliceProgressCache, unitVerb, } from "./auto-dashboard.js";
|
|
67
|
+
import { updateProgressWidget as _updateProgressWidget, setCompletionProgressWidget, setAutoOutcomeWidget, setAutoActiveStatus, updateSliceProgressCache, clearSliceProgressCache, unitVerb, } from "./auto-dashboard.js";
|
|
68
68
|
import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
|
|
69
69
|
import { isDbAvailable, getMilestone, getMilestoneSlices, getSlice, getTask, refreshOpenDatabaseFromDisk, } from "./gsd-db.js";
|
|
70
70
|
import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
|
|
@@ -2414,7 +2414,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
2414
2414
|
const loopDeps = buildLoopDeps(pi);
|
|
2415
2415
|
ensureOrchestrationModule(ctx, pi, s.basePath || base);
|
|
2416
2416
|
registerSigtermHandler(lockBase());
|
|
2417
|
-
ctx
|
|
2417
|
+
setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
|
|
2418
2418
|
ctx.ui.setWidget("gsd-health", undefined);
|
|
2419
2419
|
ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
|
|
2420
2420
|
restoreHookState(s.basePath);
|
|
@@ -2659,7 +2659,7 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
2659
2659
|
resetHookState();
|
|
2660
2660
|
await pauseAuto(ctx, pi);
|
|
2661
2661
|
}, hookHardTimeoutMs);
|
|
2662
|
-
ctx
|
|
2662
|
+
setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
|
|
2663
2663
|
ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
|
|
2664
2664
|
debugLog("dispatchHookUnit", {
|
|
2665
2665
|
phase: "send-message",
|
|
@@ -396,6 +396,85 @@ export function registerDbTools(pi) {
|
|
|
396
396
|
};
|
|
397
397
|
pi.registerTool(summarySaveTool);
|
|
398
398
|
registerAlias(pi, summarySaveTool, "gsd_save_summary", "gsd_summary_save");
|
|
399
|
+
// ─── gsd_uat_result_save ─────────────────────────────────────────────────
|
|
400
|
+
const uatResultSaveExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
|
|
401
|
+
const { executeUatResultSave } = await loadWorkflowExecutors();
|
|
402
|
+
return executeUatResultSave(params, resolveWorkflowToolBasePath(_ctx, params));
|
|
403
|
+
};
|
|
404
|
+
const uatEvidenceRef = Type.Object({
|
|
405
|
+
kind: StringEnum(["gsd_uat_exec", "gsd_exec", "screenshot", "log", "url", "browser"], { description: "Evidence kind" }),
|
|
406
|
+
ref: Type.String({ description: "Evidence ID, approved .gsd path, or URL" }),
|
|
407
|
+
note: Type.Optional(Type.String({ description: "Short evidence note" })),
|
|
408
|
+
});
|
|
409
|
+
const uatCheck = Type.Object({
|
|
410
|
+
id: Type.String({ description: "Stable check ID from the UAT spec" }),
|
|
411
|
+
description: Type.String({ description: "Check description" }),
|
|
412
|
+
mode: StringEnum(["artifact", "runtime", "browser", "human-follow-up"], { description: "Evidence mode" }),
|
|
413
|
+
result: StringEnum(["PASS", "FAIL", "NEEDS-HUMAN"], { description: "Check result" }),
|
|
414
|
+
evidence: Type.Optional(Type.Array(uatEvidenceRef, { description: "Objective evidence references" })),
|
|
415
|
+
notes: Type.Optional(Type.String({ description: "Observed result, failure notes, or human instruction" })),
|
|
416
|
+
nonAutomatable: Type.Optional(Type.Boolean({ description: "True when the check is explicitly non-automatable" })),
|
|
417
|
+
});
|
|
418
|
+
const toolPresentationBlock = Type.Object({
|
|
419
|
+
surface: StringEnum(["provider-tools", "claude-code-sdk", "mcp", "hybrid"], { description: "Tool presentation surface" }),
|
|
420
|
+
model: Type.Optional(Type.Object({
|
|
421
|
+
provider: Type.Optional(Type.String()),
|
|
422
|
+
api: Type.Optional(Type.String()),
|
|
423
|
+
id: Type.Optional(Type.String()),
|
|
424
|
+
})),
|
|
425
|
+
presentedTools: Type.Array(Type.String(), { description: "Tool names actually presented to the model" }),
|
|
426
|
+
blockedTools: Type.Array(Type.Object({
|
|
427
|
+
name: Type.String(),
|
|
428
|
+
reason: Type.String(),
|
|
429
|
+
}), { description: "Tool names blocked from the model with reasons" }),
|
|
430
|
+
aliases: Type.Optional(Type.Array(Type.Object({
|
|
431
|
+
requested: Type.String(),
|
|
432
|
+
canonical: Type.String(),
|
|
433
|
+
}))),
|
|
434
|
+
fallbackToolsUsed: Type.Optional(Type.Array(Type.String())),
|
|
435
|
+
toolPresentationPlanId: Type.Optional(Type.String()),
|
|
436
|
+
notes: Type.Optional(Type.String()),
|
|
437
|
+
});
|
|
438
|
+
const uatResultSaveTool = {
|
|
439
|
+
name: "gsd_uat_result_save",
|
|
440
|
+
label: "Save UAT Result",
|
|
441
|
+
description: "Save a structured UAT result for a slice. Validates evidence, writes the ASSESSMENT artifact, " +
|
|
442
|
+
"records attempt history, and saves the aggregate UAT gate result.",
|
|
443
|
+
promptSnippet: "Save structured UAT checks, evidence, verdict, and tool-presentation proof",
|
|
444
|
+
promptGuidelines: [
|
|
445
|
+
"Call gsd_uat_result_save once after all UAT checks have been executed.",
|
|
446
|
+
"Every PASS or FAIL check must cite objective evidence, preferably a gsd_uat_exec evidence ID.",
|
|
447
|
+
"Include the presented and blocked tool set in presentation so tool timing is auditable.",
|
|
448
|
+
"Do not use raw gsd_summary_save as a substitute for UAT results.",
|
|
449
|
+
],
|
|
450
|
+
parameters: Type.Object({
|
|
451
|
+
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
452
|
+
sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
|
|
453
|
+
uatType: StringEnum(["artifact-driven", "browser-executable", "runtime-executable", "live-runtime", "mixed", "human-experience"], { description: "Declared UAT mode" }),
|
|
454
|
+
verdict: StringEnum(["PASS", "FAIL", "PARTIAL"], { description: "Overall UAT verdict" }),
|
|
455
|
+
checks: Type.Array(uatCheck, { description: "Structured check results" }),
|
|
456
|
+
presentation: toolPresentationBlock,
|
|
457
|
+
notes: Type.Optional(Type.String({ description: "Overall verdict rationale" })),
|
|
458
|
+
attempt: Type.Optional(Type.String({ description: "Attempt number or auto" })),
|
|
459
|
+
previousAttemptId: Type.Optional(Type.String({ description: "Prior attempt ID, when retrying" })),
|
|
460
|
+
}),
|
|
461
|
+
execute: uatResultSaveExecute,
|
|
462
|
+
renderCall(args, theme) {
|
|
463
|
+
let text = theme.fg("toolTitle", theme.bold("uat_result_save "));
|
|
464
|
+
text += theme.fg("accent", `${args.milestoneId ?? "?"}/${args.sliceId ?? "?"}`);
|
|
465
|
+
if (args.verdict)
|
|
466
|
+
text += theme.fg("dim", ` → ${args.verdict}`);
|
|
467
|
+
return new Text(text, 0, 0);
|
|
468
|
+
},
|
|
469
|
+
renderResult(result, _options, theme) {
|
|
470
|
+
const d = readDetails(result);
|
|
471
|
+
if (result.isError || d?.error) {
|
|
472
|
+
return new Text(theme.fg("error", formatToolErrorText(result, d)), 0, 0);
|
|
473
|
+
}
|
|
474
|
+
return new Text(theme.fg("success", `UAT ${d?.sliceId ?? ""}: ${d?.verdict ?? "saved"}`), 0, 0);
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
pi.registerTool(uatResultSaveTool);
|
|
399
478
|
// ─── gsd_milestone_generate_id (formerly gsd_generate_milestone_id) ────
|
|
400
479
|
const milestoneGenerateIdExecute = async (_toolCallId, _params, _signal, _onUpdate, _ctx) => {
|
|
401
480
|
try {
|
|
@@ -20,6 +20,49 @@ async function loadContextModePreferences(baseDir) {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
export function registerExecTools(pi) {
|
|
23
|
+
pi.registerTool({
|
|
24
|
+
name: "gsd_uat_exec",
|
|
25
|
+
label: "UAT Exec",
|
|
26
|
+
description: "Run a UAT-scoped bash/node/python check with milestone/slice/check metadata. " +
|
|
27
|
+
"Uses the same capped .gsd/exec evidence store as gsd_exec, but rejects commands that mutate dependencies, git state, credentials, or destructive files.",
|
|
28
|
+
promptSnippet: "Run one UAT check and save typed evidence under .gsd/exec",
|
|
29
|
+
promptGuidelines: [
|
|
30
|
+
"Use gsd_uat_exec for each automated UAT check.",
|
|
31
|
+
"Every PASS/FAIL check saved by gsd_uat_result_save must reference objective evidence from this tool or another approved GSD evidence path.",
|
|
32
|
+
"Do not install packages, mutate git state, edit source files, or dump credentials during UAT.",
|
|
33
|
+
],
|
|
34
|
+
parameters: Type.Object({
|
|
35
|
+
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
36
|
+
sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
|
|
37
|
+
checkId: Type.String({ description: "Stable check ID from the UAT spec (e.g. UAT-01)" }),
|
|
38
|
+
intent: Type.String({
|
|
39
|
+
description: "UAT command intent. Use one canonical value: uat-artifact-check, uat-runtime-check, " +
|
|
40
|
+
"uat-browser-check, uat-service-start, or uat-log-inspection. Short aliases such as artifact, " +
|
|
41
|
+
"runtime, browser, service-start, and log-inspection are accepted.",
|
|
42
|
+
}),
|
|
43
|
+
runtime: Type.Optional(Type.String({
|
|
44
|
+
description: "Optional interpreter. Defaults to bash. Supported: bash, node, python; sh/shell, js/nodejs, and py/python3 aliases are accepted.",
|
|
45
|
+
})),
|
|
46
|
+
script: Type.Optional(Type.String({ description: "Script body. Keep output small (log the finding, not the data)." })),
|
|
47
|
+
command: Type.Optional(Type.String({ description: "Alias for script; defaults to bash when runtime is omitted." })),
|
|
48
|
+
cmd: Type.Optional(Type.String({ description: "Short alias for script." })),
|
|
49
|
+
code: Type.Optional(Type.String({ description: "Alias for script, useful for node/python snippets." })),
|
|
50
|
+
expected: Type.Optional(Type.String({ description: "Expected outcome for this UAT check." })),
|
|
51
|
+
timeout_ms: Type.Optional(Type.Number({
|
|
52
|
+
description: "Per-invocation timeout (ms). Capped at 600000. Default from preferences.",
|
|
53
|
+
minimum: 1_000,
|
|
54
|
+
maximum: 600_000,
|
|
55
|
+
})),
|
|
56
|
+
}),
|
|
57
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
58
|
+
const { executeUatExec } = await import("../tools/exec-tool.js");
|
|
59
|
+
const baseDir = resolveCtxCwd(_ctx);
|
|
60
|
+
return executeUatExec(params, {
|
|
61
|
+
baseDir,
|
|
62
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
});
|
|
23
66
|
pi.registerTool({
|
|
24
67
|
name: "gsd_exec",
|
|
25
68
|
label: "Exec (Sandboxed)",
|