@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
|
@@ -9,6 +9,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
9
9
|
import {
|
|
10
10
|
getMilestone,
|
|
11
11
|
getMilestoneSlices,
|
|
12
|
+
getSliceTasks,
|
|
12
13
|
isDbAvailable,
|
|
13
14
|
} from "../../gsd-db.js";
|
|
14
15
|
import { renderRoadmapFromDb } from "../../markdown-renderer.js";
|
|
@@ -30,6 +31,19 @@ function arraysEqual(a: readonly string[], b: readonly string[]): boolean {
|
|
|
30
31
|
return true;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
function getSlicesReadyForDivergenceCheck(
|
|
35
|
+
milestoneId: string,
|
|
36
|
+
dbSlices: ReturnType<typeof getMilestoneSlices>,
|
|
37
|
+
): Set<string> {
|
|
38
|
+
const ready = new Set<string>();
|
|
39
|
+
for (const slice of dbSlices) {
|
|
40
|
+
if (isClosedStatus(slice.status) || getSliceTasks(milestoneId, slice.id).length > 0) {
|
|
41
|
+
ready.add(slice.id);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return ready;
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
function milestoneHasDivergence(
|
|
34
48
|
basePath: string,
|
|
35
49
|
milestoneId: string,
|
|
@@ -46,6 +60,10 @@ function milestoneHasDivergence(
|
|
|
46
60
|
|
|
47
61
|
const dbSlices = getMilestoneSlices(milestoneId);
|
|
48
62
|
const dbSliceMap = new Map(dbSlices.map((s) => [s.id, s]));
|
|
63
|
+
const readySliceIds = getSlicesReadyForDivergenceCheck(milestoneId, dbSlices);
|
|
64
|
+
if (dbSlices.length > 0 && readySliceIds.size === 0) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
49
67
|
const roadmapSliceIds = new Set<string>();
|
|
50
68
|
|
|
51
69
|
for (let i = 0; i < roadmap.slices.length; i++) {
|
|
@@ -54,11 +72,13 @@ function milestoneHasDivergence(
|
|
|
54
72
|
const expectedSequence = i + 1;
|
|
55
73
|
const dbSlice = dbSliceMap.get(roadmapSlice.id);
|
|
56
74
|
if (!dbSlice) return true; // Roadmap has a slice the DB doesn't.
|
|
75
|
+
if (!readySliceIds.has(dbSlice.id)) continue;
|
|
57
76
|
if (dbSlice.sequence !== expectedSequence) return true;
|
|
58
77
|
if (!arraysEqual(dbSlice.depends, roadmapSlice.depends)) return true;
|
|
59
78
|
if (isClosedStatus(dbSlice.status) !== roadmapSlice.done) return true;
|
|
60
79
|
}
|
|
61
80
|
for (const dbSlice of dbSlices) {
|
|
81
|
+
if (!readySliceIds.has(dbSlice.id)) continue;
|
|
62
82
|
if (!roadmapSliceIds.has(dbSlice.id)) return true;
|
|
63
83
|
}
|
|
64
84
|
return false;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
deriveState as defaultDeriveState,
|
|
7
7
|
invalidateStateCache as defaultInvalidate,
|
|
8
8
|
} from "../state.js";
|
|
9
|
+
import { clearParseCache as defaultClearParseCache } from "../files.js";
|
|
9
10
|
import type { GSDState } from "../types.js";
|
|
10
11
|
|
|
11
12
|
import {
|
|
@@ -37,6 +38,7 @@ const MAX_PASSES = 2;
|
|
|
37
38
|
const defaultDeps: ReconciliationDeps = {
|
|
38
39
|
invalidateStateCache: defaultInvalidate,
|
|
39
40
|
deriveState: defaultDeriveState,
|
|
41
|
+
clearParseCache: defaultClearParseCache,
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -58,6 +60,7 @@ export async function reconcileBeforeDispatch(
|
|
|
58
60
|
deps: ReconciliationDeps = defaultDeps,
|
|
59
61
|
): Promise<ReconciliationResult> {
|
|
60
62
|
const registry = deps.registry ?? DRIFT_REGISTRY;
|
|
63
|
+
const clearParseCache = deps.clearParseCache ?? defaultClearParseCache;
|
|
61
64
|
const repaired: DriftRecord[] = [];
|
|
62
65
|
|
|
63
66
|
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
@@ -103,6 +106,9 @@ export async function reconcileBeforeDispatch(
|
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
if (repairedThisPass) {
|
|
110
|
+
clearParseCache();
|
|
111
|
+
}
|
|
106
112
|
if (blockers.length > 0) {
|
|
107
113
|
let blockerState = stateSnapshot;
|
|
108
114
|
if (repairedThisPass) {
|
|
@@ -101,6 +101,7 @@ export interface ReconciliationDeps {
|
|
|
101
101
|
basePath: string,
|
|
102
102
|
opts?: DeriveStateOptions,
|
|
103
103
|
) => Promise<GSDState>;
|
|
104
|
+
clearParseCache?: () => void;
|
|
104
105
|
/**
|
|
105
106
|
* Override of the drift handler catalog. Defaults to DRIFT_REGISTRY. Each
|
|
106
107
|
* handler is parameterized over its own DriftRecord variant; the union of
|
|
@@ -83,7 +83,7 @@ function formatNeedsRemediationBlocker(milestoneId: string): string {
|
|
|
83
83
|
return [
|
|
84
84
|
`Milestone ${milestoneId} is blocked because milestone validation returned needs-remediation, but all slices are complete.`,
|
|
85
85
|
`Fix options:`,
|
|
86
|
-
`1.
|
|
86
|
+
`1. Run \`/gsd dispatch reassess\` to add remediation slices, then run \`/gsd auto\``,
|
|
87
87
|
`2. If the finding is acceptable, override it: \`/gsd verdict pass --rationale "why this is okay"\``,
|
|
88
88
|
`3. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
|
|
89
89
|
].join("\n");
|
|
@@ -16,11 +16,13 @@ import {
|
|
|
16
16
|
buildPhaseHandoffOutcome,
|
|
17
17
|
updateProgressWidget,
|
|
18
18
|
setAutoOutcomeWidget,
|
|
19
|
+
setAutoActiveStatus,
|
|
19
20
|
setCompletionProgressWidget,
|
|
20
21
|
getRoadmapSlicesSync,
|
|
21
22
|
clearSliceProgressCache,
|
|
22
23
|
getWidgetMode,
|
|
23
24
|
cycleWidgetMode,
|
|
25
|
+
setWidgetMode,
|
|
24
26
|
_resetWidgetModeForTests,
|
|
25
27
|
_resetLastCommitCacheForTests,
|
|
26
28
|
_refreshLastCommitForTests,
|
|
@@ -78,6 +80,26 @@ test("unitVerb handles hook types", () => {
|
|
|
78
80
|
assert.equal(unitVerb("hook/"), "hook: ");
|
|
79
81
|
});
|
|
80
82
|
|
|
83
|
+
test("setAutoActiveStatus clears stale outcome surfaces", () => {
|
|
84
|
+
const statusCalls: Array<[string, string]> = [];
|
|
85
|
+
const widgetCalls: Array<[string, unknown]> = [];
|
|
86
|
+
|
|
87
|
+
setAutoActiveStatus({
|
|
88
|
+
hasUI: true,
|
|
89
|
+
ui: {
|
|
90
|
+
setStatus: (key: string, value: string) => {
|
|
91
|
+
statusCalls.push([key, value]);
|
|
92
|
+
},
|
|
93
|
+
setWidget: (key: string, value: unknown) => {
|
|
94
|
+
widgetCalls.push([key, value]);
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
} as any, "next");
|
|
98
|
+
|
|
99
|
+
assert.deepEqual(statusCalls, [["gsd-auto", "next"]]);
|
|
100
|
+
assert.deepEqual(widgetCalls, [["gsd-outcome", undefined]]);
|
|
101
|
+
});
|
|
102
|
+
|
|
81
103
|
// ─── unitPhaseLabel ───────────────────────────────────────────────────────
|
|
82
104
|
|
|
83
105
|
test("unitPhaseLabel maps known types to labels", () => {
|
|
@@ -571,14 +593,21 @@ test("updateProgressWidget refreshes slice progress cache immediately", (t) => {
|
|
|
571
593
|
test("updateProgressWidget full mode keeps footer-owned signals out of auto deck", (t) => {
|
|
572
594
|
const dir = makeTempDir("command-deck");
|
|
573
595
|
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
596
|
+
const projectPrefsPath = join(dir, ".gsd", "preferences.md");
|
|
597
|
+
const globalPrefsPath = join(dir, ".gsd", "global-preferences.md");
|
|
598
|
+
writeFileSync(projectPrefsPath, "---\nversion: 1\n---\n", "utf-8");
|
|
574
599
|
let widget: { render(width: number): string[]; dispose?: () => void } | null = null;
|
|
575
600
|
|
|
576
601
|
t.after(() => {
|
|
577
602
|
widget?.dispose?.();
|
|
603
|
+
_resetWidgetModeForTests();
|
|
578
604
|
clearSliceProgressCache();
|
|
579
605
|
cleanup(dir);
|
|
580
606
|
});
|
|
581
607
|
|
|
608
|
+
_resetWidgetModeForTests();
|
|
609
|
+
setWidgetMode("full", projectPrefsPath, globalPrefsPath);
|
|
610
|
+
|
|
582
611
|
updateProgressWidget(
|
|
583
612
|
{
|
|
584
613
|
hasUI: true,
|
|
@@ -812,3 +841,25 @@ test("widget mode respects project preference precedence and persists there", (t
|
|
|
812
841
|
assert.match(projectPrefs, /widget_mode:\s*min/);
|
|
813
842
|
assert.match(globalPrefs, /widget_mode:\s*off/);
|
|
814
843
|
});
|
|
844
|
+
|
|
845
|
+
test("widget mode defaults to small when preferences do not set it", (t) => {
|
|
846
|
+
const homeDir = makeTempDir("home-no-widget-pref");
|
|
847
|
+
const projectDir = makeTempDir("project-no-widget-pref");
|
|
848
|
+
const globalPrefsPath = join(homeDir, ".gsd", "preferences.md");
|
|
849
|
+
const projectPrefsPath = join(projectDir, ".gsd", "preferences.md");
|
|
850
|
+
|
|
851
|
+
mkdirSync(join(homeDir, ".gsd"), { recursive: true });
|
|
852
|
+
mkdirSync(join(projectDir, ".gsd"), { recursive: true });
|
|
853
|
+
writeFileSync(globalPrefsPath, "---\nversion: 1\n---\n", "utf-8");
|
|
854
|
+
writeFileSync(projectPrefsPath, "---\nversion: 1\n---\n", "utf-8");
|
|
855
|
+
|
|
856
|
+
t.after(() => {
|
|
857
|
+
cleanup(homeDir);
|
|
858
|
+
cleanup(projectDir);
|
|
859
|
+
_resetWidgetModeForTests();
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
_resetWidgetModeForTests();
|
|
863
|
+
|
|
864
|
+
assert.equal(getWidgetMode(projectPrefsPath, globalPrefsPath), "small");
|
|
865
|
+
});
|
|
@@ -531,8 +531,13 @@ test("bootstrap honors explicit solo milestone lock when recovering stranded tar
|
|
|
531
531
|
assert.equal(ready, true);
|
|
532
532
|
assert.deepEqual(adoptCalls, [{ milestoneId: "M002", mode: "worktree" }]);
|
|
533
533
|
assert.equal(s.currentMilestoneId, "M002");
|
|
534
|
-
assert.match(messages, /
|
|
534
|
+
assert.match(messages, /Resuming saved milestone work for M002/);
|
|
535
535
|
assert.doesNotMatch(messages, /blocks auto-mode before M001/);
|
|
536
|
+
assert.doesNotMatch(messages, /Stranded work for in-progress milestone M002/);
|
|
537
|
+
assert.ok(
|
|
538
|
+
notifications.some((entry) => entry.level === "info" && entry.message.includes("Resuming saved milestone work for M002")),
|
|
539
|
+
"active recovery should be presented as an info-level resume",
|
|
540
|
+
);
|
|
536
541
|
} finally {
|
|
537
542
|
if (previousLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
|
|
538
543
|
else process.env.GSD_MILESTONE_LOCK = previousLock;
|
|
@@ -624,7 +629,11 @@ test("bootstrap adopts stranded active branch even when isolation is none", asyn
|
|
|
624
629
|
assert.equal(s.strandedRecoveryIsolationMode, "branch");
|
|
625
630
|
assert.match(
|
|
626
631
|
notifications.map((entry) => entry.message).join("\n"),
|
|
627
|
-
/
|
|
632
|
+
/Resuming saved milestone work for M001/,
|
|
633
|
+
);
|
|
634
|
+
assert.ok(
|
|
635
|
+
notifications.every((entry) => entry.level !== "warning" || !entry.message.includes("Stranded work for in-progress milestone M001")),
|
|
636
|
+
"adopting the active milestone should not emit a scary stranded-work warning",
|
|
628
637
|
);
|
|
629
638
|
} finally {
|
|
630
639
|
try {
|
|
@@ -713,7 +722,11 @@ test("bootstrap adopts stranded active branch before deep project setup", async
|
|
|
713
722
|
assert.equal(s.strandedRecoveryIsolationMode, "branch");
|
|
714
723
|
assert.match(
|
|
715
724
|
notifications.map((entry) => entry.message).join("\n"),
|
|
716
|
-
/
|
|
725
|
+
/Resuming saved milestone work for M001/,
|
|
726
|
+
);
|
|
727
|
+
assert.ok(
|
|
728
|
+
notifications.every((entry) => entry.level !== "warning" || !entry.message.includes("Stranded work for in-progress milestone M001")),
|
|
729
|
+
"adopting the active milestone should not emit a scary stranded-work warning",
|
|
717
730
|
);
|
|
718
731
|
} finally {
|
|
719
732
|
try {
|
|
@@ -40,10 +40,12 @@ function makeMockCtx(base: string): {
|
|
|
40
40
|
calls: NotifyCall[];
|
|
41
41
|
widgets: Array<[string, unknown]>;
|
|
42
42
|
statuses: Array<[string, string | undefined]>;
|
|
43
|
+
newSessions: Array<{ workspaceRoot?: string }>;
|
|
43
44
|
} {
|
|
44
45
|
const calls: NotifyCall[] = [];
|
|
45
46
|
const widgets: Array<[string, unknown]> = [];
|
|
46
47
|
const statuses: Array<[string, string | undefined]> = [];
|
|
48
|
+
const newSessions: Array<{ workspaceRoot?: string }> = [];
|
|
47
49
|
return {
|
|
48
50
|
ctx: {
|
|
49
51
|
cwd: base,
|
|
@@ -58,10 +60,15 @@ function makeMockCtx(base: string): {
|
|
|
58
60
|
statuses.push([key, value]);
|
|
59
61
|
},
|
|
60
62
|
},
|
|
63
|
+
newSession: async (options?: { workspaceRoot?: string }) => {
|
|
64
|
+
newSessions.push(options ?? {});
|
|
65
|
+
return { cancelled: false };
|
|
66
|
+
},
|
|
61
67
|
},
|
|
62
68
|
calls,
|
|
63
69
|
widgets,
|
|
64
70
|
statuses,
|
|
71
|
+
newSessions,
|
|
65
72
|
};
|
|
66
73
|
}
|
|
67
74
|
|
|
@@ -77,7 +84,10 @@ function makeMockPi(): { pi: any; messages: SentMessage[] } {
|
|
|
77
84
|
};
|
|
78
85
|
}
|
|
79
86
|
|
|
80
|
-
function seedValidationBlockedMilestone(
|
|
87
|
+
function seedValidationBlockedMilestone(
|
|
88
|
+
base: string,
|
|
89
|
+
status: "needs-attention" | "needs-remediation" = "needs-attention",
|
|
90
|
+
): void {
|
|
81
91
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
82
92
|
insertMilestone({ id: "M006", title: "Mark All Complete", status: "active" });
|
|
83
93
|
insertSlice({
|
|
@@ -91,9 +101,9 @@ function seedValidationBlockedMilestone(base: string): void {
|
|
|
91
101
|
insertAssessment({
|
|
92
102
|
path: "milestones/M006/M006-VALIDATION.md",
|
|
93
103
|
milestoneId: "M006",
|
|
94
|
-
status
|
|
104
|
+
status,
|
|
95
105
|
scope: "milestone-validation",
|
|
96
|
-
fullContent:
|
|
106
|
+
fullContent: `verdict: ${status}`,
|
|
97
107
|
});
|
|
98
108
|
invalidateStateCache();
|
|
99
109
|
}
|
|
@@ -155,6 +165,31 @@ test("dispatcher blocks workflow-advancing aliases while validation is blocked",
|
|
|
155
165
|
}
|
|
156
166
|
});
|
|
157
167
|
|
|
168
|
+
test("dispatcher allows reassess dispatch while validation needs remediation", async () => {
|
|
169
|
+
const base = makeBase();
|
|
170
|
+
try {
|
|
171
|
+
seedValidationBlockedMilestone(base, "needs-remediation");
|
|
172
|
+
const { ctx, calls, newSessions } = makeMockCtx(base);
|
|
173
|
+
const { pi, messages } = makeMockPi();
|
|
174
|
+
|
|
175
|
+
await handleGSDCommand("dispatch reassess", ctx, pi);
|
|
176
|
+
|
|
177
|
+
assert.equal(messages.length, 1);
|
|
178
|
+
assert.equal(messages[0].customType, "gsd-dispatch");
|
|
179
|
+
assert.equal(messages[0].display, false);
|
|
180
|
+
assert.match(messages[0].content, /UNIT: Reassess Roadmap/);
|
|
181
|
+
assert.ok(
|
|
182
|
+
calls.some((call) => call.kind === "info" && /Dispatching reassess-roadmap for M006\/S01/.test(call.message)),
|
|
183
|
+
`expected reassess dispatch notification, got: ${JSON.stringify(calls)}`,
|
|
184
|
+
);
|
|
185
|
+
assert.deepEqual(newSessions, [{ workspaceRoot: base }]);
|
|
186
|
+
} finally {
|
|
187
|
+
closeDatabase();
|
|
188
|
+
invalidateStateCache();
|
|
189
|
+
cleanup(base);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
158
193
|
test("dispatcher still allows recovery commands while validation is blocked", async () => {
|
|
159
194
|
const base = makeBase();
|
|
160
195
|
try {
|
|
@@ -399,8 +399,12 @@ test("handleVerdict needs-remediation override with --rationale rewrites verdict
|
|
|
399
399
|
assert.match(rewritten, /found missing slice/);
|
|
400
400
|
|
|
401
401
|
assert.ok(
|
|
402
|
-
calls.some((c) =>
|
|
403
|
-
"needs-remediation override should suggest
|
|
402
|
+
calls.some((c) => /\/gsd dispatch reassess/.test(c.message)),
|
|
403
|
+
"needs-remediation override should suggest the reassess dispatch follow-up",
|
|
404
|
+
);
|
|
405
|
+
assert.ok(
|
|
406
|
+
calls.every((c) => !/gsd_reassess_roadmap/.test(c.message)),
|
|
407
|
+
"needs-remediation override should not expose the internal tool name",
|
|
404
408
|
);
|
|
405
409
|
} finally {
|
|
406
410
|
closeDatabase();
|
|
@@ -868,6 +868,14 @@ describe('derive-state-db', async () => {
|
|
|
868
868
|
dbState.blockers.some(b => b.includes('needs-remediation') && b.includes('M001')),
|
|
869
869
|
'remediation-stuck-db: blocker message mentions milestone and verdict',
|
|
870
870
|
);
|
|
871
|
+
assert.ok(
|
|
872
|
+
dbState.blockers.some(b => b.includes('/gsd dispatch reassess')),
|
|
873
|
+
'remediation-stuck-db: blocker message points users to the reassess command',
|
|
874
|
+
);
|
|
875
|
+
assert.ok(
|
|
876
|
+
dbState.blockers.every(b => !b.includes('gsd_reassess_roadmap')),
|
|
877
|
+
'remediation-stuck-db: blocker message does not expose the internal tool name',
|
|
878
|
+
);
|
|
871
879
|
|
|
872
880
|
closeDatabase();
|
|
873
881
|
} finally {
|
|
@@ -555,6 +555,14 @@ describe('derive-state-helpers', () => {
|
|
|
555
555
|
state.blockers.some(b => b.includes('needs-remediation') && b.includes('M001')),
|
|
556
556
|
'remediation-stuck: blocker message mentions milestone and verdict',
|
|
557
557
|
);
|
|
558
|
+
assert.ok(
|
|
559
|
+
state.blockers.some(b => b.includes('/gsd dispatch reassess')),
|
|
560
|
+
'remediation-stuck: blocker message points users to the reassess command',
|
|
561
|
+
);
|
|
562
|
+
assert.ok(
|
|
563
|
+
state.blockers.every(b => !b.includes('gsd_reassess_roadmap')),
|
|
564
|
+
'remediation-stuck: blocker message does not expose the internal tool name',
|
|
565
|
+
);
|
|
558
566
|
} finally {
|
|
559
567
|
closeDatabase();
|
|
560
568
|
cleanup(base);
|
|
@@ -53,6 +53,24 @@ test('runExecSandbox: captures stdout, persists artifacts, returns digest', asyn
|
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
test('runExecSandbox: persists optional request metadata', async () => {
|
|
57
|
+
const base = freshBase();
|
|
58
|
+
try {
|
|
59
|
+
const result = await runExecSandbox(
|
|
60
|
+
{
|
|
61
|
+
runtime: 'bash',
|
|
62
|
+
script: 'echo metadata-ok',
|
|
63
|
+
metadata: { kind: 'uat_exec', intent: 'uat-artifact-check' },
|
|
64
|
+
},
|
|
65
|
+
baseOpts(base),
|
|
66
|
+
);
|
|
67
|
+
const meta = JSON.parse(readFileSync(result.meta_path, 'utf-8')) as Record<string, unknown>;
|
|
68
|
+
assert.deepEqual(meta.metadata, { kind: 'uat_exec', intent: 'uat-artifact-check' });
|
|
69
|
+
} finally {
|
|
70
|
+
cleanup(base);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
56
74
|
test('runExecSandbox: enforces stdout cap and marks truncation', async () => {
|
|
57
75
|
const base = freshBase();
|
|
58
76
|
try {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { registerExecTools } from "../bootstrap/exec-tools.ts";
|
|
5
|
+
import { executeUatExec } from "../tools/exec-tool.ts";
|
|
6
|
+
import type { ExecSandboxRequest, ExecSandboxResult } from "../exec-sandbox.ts";
|
|
7
|
+
|
|
8
|
+
function makeExecResult(request: ExecSandboxRequest): ExecSandboxResult {
|
|
9
|
+
return {
|
|
10
|
+
id: "exec-1",
|
|
11
|
+
runtime: request.runtime,
|
|
12
|
+
exit_code: 0,
|
|
13
|
+
signal: null,
|
|
14
|
+
timed_out: false,
|
|
15
|
+
duration_ms: 1,
|
|
16
|
+
stdout_bytes: 12,
|
|
17
|
+
stderr_bytes: 0,
|
|
18
|
+
stdout_truncated: false,
|
|
19
|
+
stderr_truncated: false,
|
|
20
|
+
stdout_path: ".gsd/exec/exec-1.stdout",
|
|
21
|
+
stderr_path: ".gsd/exec/exec-1.stderr",
|
|
22
|
+
meta_path: ".gsd/exec/exec-1.meta.json",
|
|
23
|
+
digest: "check passed",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test("executeUatExec accepts evidence-mode aliases for intent", async () => {
|
|
28
|
+
const requests: ExecSandboxRequest[] = [];
|
|
29
|
+
const result = await executeUatExec(
|
|
30
|
+
{
|
|
31
|
+
milestoneId: "M001",
|
|
32
|
+
sliceId: "S01",
|
|
33
|
+
checkId: "UAT-PRE",
|
|
34
|
+
intent: "artifact",
|
|
35
|
+
runtime: "bash",
|
|
36
|
+
script: "printf ok",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
baseDir: "/tmp/gsd-uat-exec-test",
|
|
40
|
+
preferences: null,
|
|
41
|
+
run: async (request) => {
|
|
42
|
+
requests.push(request);
|
|
43
|
+
return makeExecResult(request);
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
assert.equal(result.isError, false);
|
|
49
|
+
assert.equal(result.details?.operation, "gsd_uat_exec");
|
|
50
|
+
assert.equal(result.details?.intent, "uat-artifact-check");
|
|
51
|
+
assert.equal(requests[0]?.metadata?.intent, "uat-artifact-check");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("registerExecTools exposes gsd_uat_exec intent as recoverable string schema", () => {
|
|
55
|
+
const tools: Array<{ name: string; parameters: any }> = [];
|
|
56
|
+
registerExecTools({
|
|
57
|
+
registerTool: (tool: { name: string; parameters: any }) => {
|
|
58
|
+
tools.push(tool);
|
|
59
|
+
},
|
|
60
|
+
} as any);
|
|
61
|
+
|
|
62
|
+
const tool = tools.find((registeredTool) => registeredTool.name === "gsd_uat_exec");
|
|
63
|
+
assert.ok(tool, "gsd_uat_exec should be registered");
|
|
64
|
+
const intentSchema = tool.parameters.properties.intent;
|
|
65
|
+
assert.equal(intentSchema.type, "string");
|
|
66
|
+
assert.equal("anyOf" in intentSchema, false);
|
|
67
|
+
assert.match(intentSchema.description, /uat-artifact-check/);
|
|
68
|
+
assert.match(intentSchema.description, /artifact/);
|
|
69
|
+
});
|
|
@@ -22,23 +22,31 @@ import { tmpdir } from "node:os";
|
|
|
22
22
|
|
|
23
23
|
import { loadSkills } from "@gsd/pi-coding-agent";
|
|
24
24
|
import {
|
|
25
|
-
|
|
25
|
+
buildCompleteSlicePrompt,
|
|
26
26
|
buildParallelResearchSlicesPrompt,
|
|
27
|
+
buildResearchSlicePrompt,
|
|
27
28
|
} from "../auto-prompts.ts";
|
|
28
29
|
|
|
29
30
|
const SKILL_NAME = "testskill";
|
|
31
|
+
const COMPLETE_SLICE_SKILL_NAME = "complete-slice-policies";
|
|
30
32
|
const SKILL_ACTIVATION_SUBSTRING = `Call Skill({ skill: '${SKILL_NAME}' })`;
|
|
33
|
+
const COMPLETE_SLICE_SKILL_ACTIVATION_SUBSTRING = `Call Skill({ skill: '${COMPLETE_SLICE_SKILL_NAME}' })`;
|
|
31
34
|
|
|
32
35
|
const tmpDirs: string[] = [];
|
|
33
36
|
let savedCwd: string | undefined;
|
|
34
37
|
|
|
35
|
-
function setupProjectWithSkill(
|
|
38
|
+
function setupProjectWithSkill(options: {
|
|
39
|
+
skillName?: string;
|
|
40
|
+
preferencesLines?: string[];
|
|
41
|
+
} = {}): string {
|
|
42
|
+
const skillName = options.skillName ?? SKILL_NAME;
|
|
36
43
|
const base = mkdtempSync(join(tmpdir(), "gsd-worker-skill-int-"));
|
|
37
44
|
tmpDirs.push(base);
|
|
38
45
|
|
|
39
46
|
// Milestone roadmap — buildResearchSlicePrompt inlines the roadmap excerpt.
|
|
40
47
|
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
41
|
-
|
|
48
|
+
const sliceOneDir = join(milestoneDir, "slices", "S01");
|
|
49
|
+
mkdirSync(join(sliceOneDir, "tasks"), { recursive: true });
|
|
42
50
|
mkdirSync(join(milestoneDir, "slices", "S02"), { recursive: true });
|
|
43
51
|
writeFileSync(
|
|
44
52
|
join(milestoneDir, "M001-ROADMAP.md"),
|
|
@@ -55,27 +63,41 @@ function setupProjectWithSkill(): string {
|
|
|
55
63
|
].join("\n"),
|
|
56
64
|
"utf-8",
|
|
57
65
|
);
|
|
66
|
+
writeFileSync(
|
|
67
|
+
join(sliceOneDir, "S01-PLAN.md"),
|
|
68
|
+
[
|
|
69
|
+
"# S01: Alpha",
|
|
70
|
+
"",
|
|
71
|
+
"**Goal:** Verify worker x skill prompt plumbing.",
|
|
72
|
+
"**Demo:** Rendered prompts include the skill activation block.",
|
|
73
|
+
"",
|
|
74
|
+
"## Tasks",
|
|
75
|
+
"- [x] **T01: Task** `est:10m`",
|
|
76
|
+
"",
|
|
77
|
+
].join("\n"),
|
|
78
|
+
"utf-8",
|
|
79
|
+
);
|
|
58
80
|
|
|
59
81
|
// Project preferences — buildSkillActivationBlock picks these up via
|
|
60
82
|
// loadEffectiveGSDPreferences(), which reads from `${cwd}/.gsd/PREFERENCES.md`.
|
|
61
83
|
writeFileSync(
|
|
62
84
|
join(base, ".gsd", "PREFERENCES.md"),
|
|
63
|
-
["---", `always_use_skills:`, ` - ${
|
|
85
|
+
["---", ...(options.preferencesLines ?? [`always_use_skills:`, ` - ${skillName}`]), "---", ""].join("\n"),
|
|
64
86
|
"utf-8",
|
|
65
87
|
);
|
|
66
88
|
|
|
67
89
|
// Project-scoped skill — resolveSkillReference scans `${cwd}/.agents/skills/`.
|
|
68
|
-
const skillDir = join(base, ".agents", "skills",
|
|
90
|
+
const skillDir = join(base, ".agents", "skills", skillName);
|
|
69
91
|
mkdirSync(skillDir, { recursive: true });
|
|
70
92
|
writeFileSync(
|
|
71
93
|
join(skillDir, "SKILL.md"),
|
|
72
94
|
[
|
|
73
95
|
"---",
|
|
74
|
-
`name: ${
|
|
96
|
+
`name: ${skillName}`,
|
|
75
97
|
`description: Integration-test skill for worker × skill prompt plumbing.`,
|
|
76
98
|
"---",
|
|
77
99
|
"",
|
|
78
|
-
`# ${
|
|
100
|
+
`# ${skillName}`,
|
|
79
101
|
"",
|
|
80
102
|
"Test skill body.",
|
|
81
103
|
].join("\n"),
|
|
@@ -122,6 +144,31 @@ test("worker prompt (buildResearchSlicePrompt) includes <skill_activation> from
|
|
|
122
144
|
);
|
|
123
145
|
});
|
|
124
146
|
|
|
147
|
+
test("complete-slice prompt includes <skill_activation> from unit-specific skill_rules", async () => {
|
|
148
|
+
const base = setupProjectWithSkill({
|
|
149
|
+
skillName: COMPLETE_SLICE_SKILL_NAME,
|
|
150
|
+
preferencesLines: [
|
|
151
|
+
"skill_rules:",
|
|
152
|
+
" - when: complete-slice",
|
|
153
|
+
" use:",
|
|
154
|
+
` - ${COMPLETE_SLICE_SKILL_NAME}`,
|
|
155
|
+
],
|
|
156
|
+
});
|
|
157
|
+
savedCwd = process.cwd();
|
|
158
|
+
process.chdir(base);
|
|
159
|
+
|
|
160
|
+
const prompt = await buildCompleteSlicePrompt("M001", "Test Milestone", "S01", "Alpha", base, "minimal");
|
|
161
|
+
|
|
162
|
+
assert.ok(
|
|
163
|
+
prompt.includes("<skill_activation>"),
|
|
164
|
+
"complete-slice prompt should contain a <skill_activation> block",
|
|
165
|
+
);
|
|
166
|
+
assert.ok(
|
|
167
|
+
prompt.includes(COMPLETE_SLICE_SKILL_ACTIVATION_SUBSTRING),
|
|
168
|
+
`complete-slice prompt should reference the skill-rule skill '${COMPLETE_SLICE_SKILL_NAME}'`,
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
125
172
|
test("subagent dispatch prompt (buildParallelResearchSlicesPrompt) carries <skill_activation> into each embedded per-slice section", async () => {
|
|
126
173
|
const base = setupProjectWithSkill();
|
|
127
174
|
savedCwd = process.cwd();
|
|
@@ -32,6 +32,16 @@ test("run-uat prompt branches on dynamic UAT mode and supports runtime evidence"
|
|
|
32
32
|
assert.doesNotMatch(prompt, /uatType:\s*artifact-driven/);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
test("run-uat prompt lists canonical gsd_uat_exec intent values", () => {
|
|
36
|
+
const prompt = readPrompt("run-uat");
|
|
37
|
+
assert.match(prompt, /`uat-artifact-check`/);
|
|
38
|
+
assert.match(prompt, /`uat-runtime-check`/);
|
|
39
|
+
assert.match(prompt, /`uat-browser-check`/);
|
|
40
|
+
assert.match(prompt, /`uat-service-start`/);
|
|
41
|
+
assert.match(prompt, /`uat-log-inspection`/);
|
|
42
|
+
assert.match(prompt, /do not use `artifact`, `runtime`, or `human-follow-up` as `intent`/i);
|
|
43
|
+
});
|
|
44
|
+
|
|
35
45
|
test("workflow-start prompt defaults to autonomy instead of per-phase confirmation", () => {
|
|
36
46
|
const prompt = readPrompt("workflow-start");
|
|
37
47
|
assert.match(prompt, /Keep moving by default/i);
|
|
@@ -17,7 +17,10 @@ import {
|
|
|
17
17
|
shouldDeferTransientErrorToCoreRetry,
|
|
18
18
|
suppressTerminalDeletedWorktreeMessageEnd,
|
|
19
19
|
} from "../bootstrap/agent-end-recovery.ts";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
_buildCancelledUnitStopReason,
|
|
22
|
+
_classifyZeroToolProviderMessageForTest,
|
|
23
|
+
} from "../auto/phases.ts";
|
|
21
24
|
import { autoSession } from "../auto-runtime-state.ts";
|
|
22
25
|
import { getNextFallbackModel } from "../preferences.ts";
|
|
23
26
|
// Zero-import module — imported by path rather than through the package
|
|
@@ -53,6 +56,20 @@ test("classifyError treats usage-limit phrasing as transient rate-limit (#4373)"
|
|
|
53
56
|
assert.equal(result.kind, "rate-limit");
|
|
54
57
|
});
|
|
55
58
|
|
|
59
|
+
test("zero-tool provider classifier treats Claude session-limit wording as transient rate-limit (#371)", () => {
|
|
60
|
+
const result = _classifyZeroToolProviderMessageForTest("Claude Code session limit reached. Limit resets at 5 PM.");
|
|
61
|
+
assert.ok(result, "session-limit wording should be recognized");
|
|
62
|
+
assert.ok(isTransient(result));
|
|
63
|
+
assert.equal(result.kind, "rate-limit");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("zero-tool provider classifier treats weekly limit wording as transient rate-limit (#371)", () => {
|
|
67
|
+
const result = _classifyZeroToolProviderMessageForTest("You've reached your weekly limit. Try again later.");
|
|
68
|
+
assert.ok(result, "weekly limit wording should be recognized");
|
|
69
|
+
assert.ok(isTransient(result));
|
|
70
|
+
assert.equal(result.kind, "rate-limit");
|
|
71
|
+
});
|
|
72
|
+
|
|
56
73
|
test("classifyError treats extra-usage phrasing as transient rate-limit (#4397)", () => {
|
|
57
74
|
const result = classifyError("You are out of extra usage. Please wait before retrying.");
|
|
58
75
|
assert.ok(isTransient(result));
|
|
@@ -17,6 +17,10 @@ import { validatePreferences } from "../preferences-validation.ts";
|
|
|
17
17
|
import type { ReactiveExecutionState } from "../types.ts";
|
|
18
18
|
import { parseUnitId } from "../unit-id.ts";
|
|
19
19
|
import { resolveDispatch } from "../auto-dispatch.ts";
|
|
20
|
+
import {
|
|
21
|
+
_getPlannedKeyFilesForTest,
|
|
22
|
+
_parseReactiveBatchTaskIdsForTest,
|
|
23
|
+
} from "../auto-post-unit.ts";
|
|
20
24
|
|
|
21
25
|
// ─── Preference Validation ────────────────────────────────────────────────
|
|
22
26
|
|
|
@@ -71,6 +75,38 @@ test("reactive_execution validation warns on unknown keys", () => {
|
|
|
71
75
|
assert.ok(result.warnings.some((w) => w.includes("unknown_thing")));
|
|
72
76
|
});
|
|
73
77
|
|
|
78
|
+
test("reactive batch unit ids are parsed and deduped for commit context", () => {
|
|
79
|
+
assert.deepEqual(
|
|
80
|
+
_parseReactiveBatchTaskIdsForTest("M001/S01/reactive+T01,t02,T01"),
|
|
81
|
+
["T01", "T02"],
|
|
82
|
+
);
|
|
83
|
+
assert.deepEqual(_parseReactiveBatchTaskIdsForTest("M001/S01/T01"), []);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("reactive commit context key files include planned output, files, and key_files once", () => {
|
|
87
|
+
const result = _getPlannedKeyFilesForTest([
|
|
88
|
+
{
|
|
89
|
+
expected_output: ["src/new.ts", "src/shared.ts"],
|
|
90
|
+
files: ["src/input.ts", "src/shared.ts"],
|
|
91
|
+
key_files: ["src/key.ts"],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
expected_output: ["src/new.ts"],
|
|
95
|
+
files: ["src/other.ts"],
|
|
96
|
+
key_files: ["src/key.ts", "src/final.ts"],
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
assert.deepEqual(result, [
|
|
101
|
+
"src/new.ts",
|
|
102
|
+
"src/shared.ts",
|
|
103
|
+
"src/input.ts",
|
|
104
|
+
"src/key.ts",
|
|
105
|
+
"src/other.ts",
|
|
106
|
+
"src/final.ts",
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
74
110
|
// ─── Dispatch Rule Matching Logic ─────────────────────────────────────────
|
|
75
111
|
|
|
76
112
|
test("reactive dispatch requires enabled config and multiple ready tasks", async () => {
|