@opengsd/gsd-pi 1.1.1-dev.616a1a1 → 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/claude-code-cli/stream-adapter.js +167 -16
- 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-dispatch.js +39 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +113 -7
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -4
- package/dist/resources/extensions/gsd/auto-start.js +94 -15
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
- package/dist/resources/extensions/gsd/auto.js +22 -4
- 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/catalog.js +6 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +6 -2
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +7 -3
- package/dist/resources/extensions/gsd/commands-maintenance.js +172 -2
- package/dist/resources/extensions/gsd/commands-mcp-status.js +107 -59
- 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/gsd-db.js +37 -4
- package/dist/resources/extensions/gsd/guided-flow.js +1 -1
- package/dist/resources/extensions/gsd/mcp-filter.js +3 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +67 -8
- package/dist/resources/extensions/gsd/migration-auto-check.js +2 -2
- 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/artifact-db.js +4 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +1 -1
- 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 +15 -12
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +120 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -9
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
- 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 +3 -1
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -0
- package/dist/resources/extensions/mcp-client/manager.js +31 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
- 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 +4 -4
- 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 +72 -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-extension-dialogs.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.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 +338 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +412 -112
- 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/claude-code-cli/stream-adapter.ts +196 -16
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +239 -63
- 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-dispatch.ts +48 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +138 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +4 -4
- package/src/resources/extensions/gsd/auto-start.ts +112 -17
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
- package/src/resources/extensions/gsd/auto.ts +35 -3
- 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/catalog.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -2
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +7 -3
- package/src/resources/extensions/gsd/commands-maintenance.ts +197 -2
- package/src/resources/extensions/gsd/commands-mcp-status.ts +134 -57
- 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/gsd-db.ts +41 -6
- package/src/resources/extensions/gsd/guided-flow.ts +1 -1
- package/src/resources/extensions/gsd/mcp-filter.ts +3 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +92 -10
- package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
- 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/artifact-db.ts +4 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +1 -1
- 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 +16 -12
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +143 -2
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +24 -2
- 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 +50 -13
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +60 -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/gsd-rebuild.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +13 -6
- package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +39 -1
- 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/start-auto-detached.test.ts +6 -2
- 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 +17 -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/tool-presentation-plan.ts +167 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -9
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
- 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 +2 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +26 -0
- package/src/resources/extensions/mcp-client/manager.ts +33 -1
- package/src/resources/extensions/mcp-client/tests/manager.test.ts +35 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
|
@@ -118,6 +118,37 @@ function makeRepoWithMultipleStrandedMilestones(): string {
|
|
|
118
118
|
return base;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
function makeRepoWithActiveMismatchAndStrandedTarget(): string {
|
|
122
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-targeted-stranded-bootstrap-"));
|
|
123
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
124
|
+
mkdirSync(join(base, ".gsd", "milestones", "M002"), { recursive: true });
|
|
125
|
+
writeFileSync(
|
|
126
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
127
|
+
"---\ngit:\n isolation: \"worktree\"\n---\n",
|
|
128
|
+
);
|
|
129
|
+
runGit(base, ["init"]);
|
|
130
|
+
runGit(base, ["config", "user.email", "test@test.com"]);
|
|
131
|
+
runGit(base, ["config", "user.name", "Test"]);
|
|
132
|
+
writeFileSync(join(base, "README.md"), "# test\n");
|
|
133
|
+
runGit(base, ["add", "-A"]);
|
|
134
|
+
runGit(base, ["commit", "-m", "init"]);
|
|
135
|
+
runGit(base, ["branch", "-M", "main"]);
|
|
136
|
+
|
|
137
|
+
runGit(base, ["checkout", "-b", "milestone/M002"]);
|
|
138
|
+
writeFileSync(join(base, "m002.txt"), "target stranded work\n");
|
|
139
|
+
runGit(base, ["add", "-A"]);
|
|
140
|
+
runGit(base, ["commit", "-m", "feat: M002 in progress"]);
|
|
141
|
+
runGit(base, ["checkout", "main"]);
|
|
142
|
+
runGit(base, ["worktree", "add", ".gsd/worktrees/M002", "milestone/M002"]);
|
|
143
|
+
|
|
144
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
145
|
+
insertMilestone({ id: "M001", title: "Incorrect active milestone", status: "active" });
|
|
146
|
+
insertMilestone({ id: "M002", title: "Target stranded milestone", status: "active" });
|
|
147
|
+
closeDatabase();
|
|
148
|
+
|
|
149
|
+
return base;
|
|
150
|
+
}
|
|
151
|
+
|
|
121
152
|
function makeRepoWithRecoveredCleanupAndStrandedMismatch(): string {
|
|
122
153
|
const base = mkdtempSync(join(tmpdir(), "gsd-headless-stranded-bootstrap-"));
|
|
123
154
|
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
@@ -321,6 +352,9 @@ test("headless bootstrap checks stranded work before recovered-complete shortcut
|
|
|
321
352
|
const messages = notifications.map((entry) => entry.message).join("\n");
|
|
322
353
|
assert.equal(ready, false);
|
|
323
354
|
assert.match(messages, /Stranded work for M002 blocks auto-mode/);
|
|
355
|
+
assert.match(messages, /\/gsd auto M002/);
|
|
356
|
+
assert.match(messages, /\/gsd park M002 "reason"/);
|
|
357
|
+
assert.match(messages, /\/gsd rethink/);
|
|
324
358
|
assert.doesNotMatch(messages, /all milestones complete/);
|
|
325
359
|
} finally {
|
|
326
360
|
if (previousHeadless === undefined) {
|
|
@@ -409,6 +443,9 @@ test("bootstrap blocks active stranded recovery when another open milestone also
|
|
|
409
443
|
assert.equal(ready, false);
|
|
410
444
|
assert.deepEqual(adoptCalls, []);
|
|
411
445
|
assert.match(messages, /Stranded work for M002 blocks auto-mode before M001/);
|
|
446
|
+
assert.match(messages, /\/gsd auto M002/);
|
|
447
|
+
assert.match(messages, /\/gsd park M002 "reason"/);
|
|
448
|
+
assert.match(messages, /explicitly discard M002/);
|
|
412
449
|
} finally {
|
|
413
450
|
try {
|
|
414
451
|
closeDatabase();
|
|
@@ -418,6 +455,102 @@ test("bootstrap blocks active stranded recovery when another open milestone also
|
|
|
418
455
|
}
|
|
419
456
|
});
|
|
420
457
|
|
|
458
|
+
test("bootstrap honors explicit solo milestone lock when recovering stranded target worktree", async () => {
|
|
459
|
+
const base = makeRepoWithActiveMismatchAndStrandedTarget();
|
|
460
|
+
const previousCwd = process.cwd();
|
|
461
|
+
const previousLock = process.env.GSD_MILESTONE_LOCK;
|
|
462
|
+
const previousWorker = process.env.GSD_PARALLEL_WORKER;
|
|
463
|
+
const s = new AutoSession();
|
|
464
|
+
const adoptCalls: Array<{ milestoneId: string; mode: string }> = [];
|
|
465
|
+
const notifications: Array<{ message: string; level?: string }> = [];
|
|
466
|
+
|
|
467
|
+
try {
|
|
468
|
+
delete process.env.GSD_PARALLEL_WORKER;
|
|
469
|
+
process.env.GSD_MILESTONE_LOCK = "M002";
|
|
470
|
+
|
|
471
|
+
const ready = await bootstrapAutoSession(
|
|
472
|
+
s,
|
|
473
|
+
makeCtx(notifications) as any,
|
|
474
|
+
{
|
|
475
|
+
getThinkingLevel: () => "medium",
|
|
476
|
+
getActiveTools: () => [],
|
|
477
|
+
events: { emit: () => {} },
|
|
478
|
+
} as any,
|
|
479
|
+
base,
|
|
480
|
+
false,
|
|
481
|
+
false,
|
|
482
|
+
{
|
|
483
|
+
shouldUseWorktreeIsolation: () => false,
|
|
484
|
+
registerSigtermHandler: () => {},
|
|
485
|
+
registerAutoWorkerForSession: () => {},
|
|
486
|
+
lockBase: () => base,
|
|
487
|
+
buildLifecycle: () => ({
|
|
488
|
+
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
489
|
+
s.basePath = sessionBase;
|
|
490
|
+
if (originalBase !== undefined) {
|
|
491
|
+
s.originalBasePath = originalBase;
|
|
492
|
+
} else if (!s.originalBasePath) {
|
|
493
|
+
s.originalBasePath = sessionBase;
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
enterMilestone: () => ({ ok: true, mode: "worktree", path: base }),
|
|
497
|
+
adoptStrandedMilestone: (
|
|
498
|
+
milestoneId: string,
|
|
499
|
+
sessionBase: string,
|
|
500
|
+
_ctx: unknown,
|
|
501
|
+
opts: { mode: "worktree" | "branch" },
|
|
502
|
+
) => {
|
|
503
|
+
adoptCalls.push({ milestoneId, mode: opts.mode });
|
|
504
|
+
s.basePath = sessionBase;
|
|
505
|
+
s.originalBasePath = sessionBase;
|
|
506
|
+
s.strandedRecoveryIsolationMode = opts.mode;
|
|
507
|
+
return { ok: true, mode: opts.mode, path: sessionBase };
|
|
508
|
+
},
|
|
509
|
+
adoptOrphanWorktree: <T extends { merged: boolean }>(
|
|
510
|
+
_mid: string,
|
|
511
|
+
_base: string,
|
|
512
|
+
run: () => T,
|
|
513
|
+
): T => run(),
|
|
514
|
+
}) as any,
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
classification: "none",
|
|
518
|
+
lock: null,
|
|
519
|
+
pausedSession: null,
|
|
520
|
+
state: null,
|
|
521
|
+
recovery: null,
|
|
522
|
+
recoveryPrompt: null,
|
|
523
|
+
recoveryToolCallCount: 0,
|
|
524
|
+
artifactSatisfied: false,
|
|
525
|
+
hasResumableDiskState: false,
|
|
526
|
+
isBootstrapCrash: false,
|
|
527
|
+
},
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
const messages = notifications.map((entry) => entry.message).join("\n");
|
|
531
|
+
assert.equal(ready, true);
|
|
532
|
+
assert.deepEqual(adoptCalls, [{ milestoneId: "M002", mode: "worktree" }]);
|
|
533
|
+
assert.equal(s.currentMilestoneId, "M002");
|
|
534
|
+
assert.match(messages, /Resuming saved milestone work for M002/);
|
|
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
|
+
);
|
|
541
|
+
} finally {
|
|
542
|
+
if (previousLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
|
|
543
|
+
else process.env.GSD_MILESTONE_LOCK = previousLock;
|
|
544
|
+
if (previousWorker === undefined) delete process.env.GSD_PARALLEL_WORKER;
|
|
545
|
+
else process.env.GSD_PARALLEL_WORKER = previousWorker;
|
|
546
|
+
try {
|
|
547
|
+
closeDatabase();
|
|
548
|
+
} catch {}
|
|
549
|
+
process.chdir(previousCwd);
|
|
550
|
+
rmSync(base, { recursive: true, force: true });
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
|
|
421
554
|
test("bootstrap adopts stranded active branch even when isolation is none", async () => {
|
|
422
555
|
const base = makeRepoWithStrandedActiveMilestone();
|
|
423
556
|
const previousCwd = process.cwd();
|
|
@@ -496,7 +629,11 @@ test("bootstrap adopts stranded active branch even when isolation is none", asyn
|
|
|
496
629
|
assert.equal(s.strandedRecoveryIsolationMode, "branch");
|
|
497
630
|
assert.match(
|
|
498
631
|
notifications.map((entry) => entry.message).join("\n"),
|
|
499
|
-
/
|
|
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",
|
|
500
637
|
);
|
|
501
638
|
} finally {
|
|
502
639
|
try {
|
|
@@ -585,7 +722,11 @@ test("bootstrap adopts stranded active branch before deep project setup", async
|
|
|
585
722
|
assert.equal(s.strandedRecoveryIsolationMode, "branch");
|
|
586
723
|
assert.match(
|
|
587
724
|
notifications.map((entry) => entry.message).join("\n"),
|
|
588
|
-
/
|
|
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",
|
|
589
730
|
);
|
|
590
731
|
} finally {
|
|
591
732
|
try {
|
|
@@ -4,13 +4,35 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
|
|
7
|
-
import { closeDatabase, getAllMilestones, insertMilestone, isDbAvailable, openDatabase } from "../gsd-db.ts";
|
|
8
|
-
import { reconcileProjectMilestonesFromDisk } from "../auto-start.ts";
|
|
7
|
+
import { closeDatabase, getAllMilestones, getMilestone, insertMilestone, isDbAvailable, openDatabase } from "../gsd-db.ts";
|
|
8
|
+
import { reconcileMergedMilestonesFromJournal, reconcileProjectMilestonesFromDisk } from "../auto-start.ts";
|
|
9
|
+
import { emitWorktreeMerged } from "../worktree-telemetry.ts";
|
|
9
10
|
|
|
10
11
|
test.afterEach(() => {
|
|
11
12
|
if (isDbAvailable()) closeDatabase();
|
|
12
13
|
});
|
|
13
14
|
|
|
15
|
+
test("bootstrap reconciliation treats a successful worktree merge as milestone closed", () => {
|
|
16
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-merged-reconcile-"));
|
|
17
|
+
try {
|
|
18
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
19
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
20
|
+
insertMilestone({ id: "M001", title: "Merged Milestone", status: "active" });
|
|
21
|
+
|
|
22
|
+
emitWorktreeMerged(base, "M001", { reason: "milestone-complete", conflict: false });
|
|
23
|
+
|
|
24
|
+
const closed = reconcileMergedMilestonesFromJournal(base);
|
|
25
|
+
const row = getMilestone("M001");
|
|
26
|
+
|
|
27
|
+
assert.equal(closed, 1);
|
|
28
|
+
assert.equal(row?.status, "complete");
|
|
29
|
+
assert.ok(row?.completed_at);
|
|
30
|
+
} finally {
|
|
31
|
+
if (isDbAvailable()) closeDatabase();
|
|
32
|
+
rmSync(base, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
14
36
|
test("#5389: bootstrap reconciles PROJECT.md milestones that are missing from DB", () => {
|
|
15
37
|
const base = mkdtempSync(join(tmpdir(), "gsd-project-reconcile-"));
|
|
16
38
|
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 {
|
|
@@ -473,13 +473,13 @@ describe('derive-state-helpers', () => {
|
|
|
473
473
|
}
|
|
474
474
|
});
|
|
475
475
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
476
|
+
test('getActiveMilestoneId: DB lock path ignores PARKED flag projection', async () => {
|
|
477
|
+
const base = createFixtureBase();
|
|
478
|
+
const previousLock = process.env.GSD_MILESTONE_LOCK;
|
|
479
|
+
const previousWorker = process.env.GSD_PARALLEL_WORKER;
|
|
480
|
+
try {
|
|
481
|
+
process.env.GSD_MILESTONE_LOCK = 'M001';
|
|
482
|
+
process.env.GSD_PARALLEL_WORKER = '1';
|
|
483
483
|
writeFile(base, 'milestones/M001/M001-PARKED.md', '# Parked on disk');
|
|
484
484
|
|
|
485
485
|
openDatabase(':memory:');
|
|
@@ -487,12 +487,41 @@ describe('derive-state-helpers', () => {
|
|
|
487
487
|
|
|
488
488
|
const id = await getActiveMilestoneId(base);
|
|
489
489
|
assert.equal(id, 'M001', 'DB status remains authoritative despite PARKED projection');
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
490
|
+
} finally {
|
|
491
|
+
if (previousLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
|
|
492
|
+
else process.env.GSD_MILESTONE_LOCK = previousLock;
|
|
493
|
+
if (previousWorker === undefined) delete process.env.GSD_PARALLEL_WORKER;
|
|
494
|
+
else process.env.GSD_PARALLEL_WORKER = previousWorker;
|
|
495
|
+
closeDatabase();
|
|
496
|
+
cleanup(base);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test('deriveStateFromDb: solo milestone lock scopes state to the requested milestone', async () => {
|
|
501
|
+
const base = createFixtureBase();
|
|
502
|
+
const previousLock = process.env.GSD_MILESTONE_LOCK;
|
|
503
|
+
const previousWorker = process.env.GSD_PARALLEL_WORKER;
|
|
504
|
+
try {
|
|
505
|
+
delete process.env.GSD_PARALLEL_WORKER;
|
|
506
|
+
process.env.GSD_MILESTONE_LOCK = 'M002';
|
|
507
|
+
|
|
508
|
+
openDatabase(':memory:');
|
|
509
|
+
insertMilestone({ id: 'M001', title: 'Earlier', status: 'active' });
|
|
510
|
+
insertMilestone({ id: 'M002', title: 'Requested', status: 'active' });
|
|
511
|
+
|
|
512
|
+
invalidateStateCache();
|
|
513
|
+
const state = await deriveStateFromDb(base);
|
|
514
|
+
const id = await getActiveMilestoneId(base);
|
|
515
|
+
|
|
516
|
+
assert.equal(state.activeMilestone?.id, 'M002', 'explicit lock chooses requested milestone');
|
|
517
|
+
assert.deepEqual(state.registry.map((entry) => entry.id), ['M002'], 'registry is scoped to requested milestone');
|
|
518
|
+
assert.equal(id, 'M002', 'active milestone helper honors solo lock');
|
|
519
|
+
} finally {
|
|
520
|
+
if (previousLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
|
|
521
|
+
else process.env.GSD_MILESTONE_LOCK = previousLock;
|
|
522
|
+
if (previousWorker === undefined) delete process.env.GSD_PARALLEL_WORKER;
|
|
523
|
+
else process.env.GSD_PARALLEL_WORKER = previousWorker;
|
|
524
|
+
closeDatabase();
|
|
496
525
|
cleanup(base);
|
|
497
526
|
}
|
|
498
527
|
});
|
|
@@ -526,6 +555,14 @@ describe('derive-state-helpers', () => {
|
|
|
526
555
|
state.blockers.some(b => b.includes('needs-remediation') && b.includes('M001')),
|
|
527
556
|
'remediation-stuck: blocker message mentions milestone and verdict',
|
|
528
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
|
+
);
|
|
529
566
|
} finally {
|
|
530
567
|
closeDatabase();
|
|
531
568
|
cleanup(base);
|
|
@@ -19,6 +19,7 @@ import type { DispatchContext } from "../auto-dispatch.ts";
|
|
|
19
19
|
import type { AutoSession } from "../auto/session.ts";
|
|
20
20
|
import type { GSDState } from "../types.ts";
|
|
21
21
|
import { enableDebug, disableDebug, getDebugLogPath } from "../debug-logger.ts";
|
|
22
|
+
import { closeDatabase, insertMilestone, isDbAvailable, openDatabase } from "../gsd-db.ts";
|
|
22
23
|
|
|
23
24
|
function makeState(overrides: Partial<GSDState> = {}): GSDState {
|
|
24
25
|
return {
|
|
@@ -123,6 +124,29 @@ test("dispatch: missing task plan triggers plan-slice (not stop) — issue #909"
|
|
|
123
124
|
`unitId should be M002/S03, got: ${result.action === "dispatch" ? result.unitId : "(stop)"}`);
|
|
124
125
|
});
|
|
125
126
|
|
|
127
|
+
test("dispatch: closed milestone is not implicitly recovered or reopened", async (t) => {
|
|
128
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-closed-dispatch-"));
|
|
129
|
+
t.after(() => {
|
|
130
|
+
if (isDbAvailable()) closeDatabase();
|
|
131
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (isDbAvailable()) closeDatabase();
|
|
135
|
+
mkdirSync(join(tmp, ".gsd"), { recursive: true });
|
|
136
|
+
openDatabase(join(tmp, ".gsd", "gsd.db"));
|
|
137
|
+
insertMilestone({ id: "M002", title: "Closed Milestone", status: "complete" });
|
|
138
|
+
scaffoldMilestoneContext(tmp, "M002");
|
|
139
|
+
scaffoldSlicePlan(tmp, "M002", "S03");
|
|
140
|
+
|
|
141
|
+
const result = await resolveDispatch(makeContext(tmp));
|
|
142
|
+
|
|
143
|
+
assert.equal(result.action, "stop");
|
|
144
|
+
assert.ok(result.action === "stop");
|
|
145
|
+
assert.equal(result.level, "warning");
|
|
146
|
+
assert.match(result.reason, /Milestone M002 is closed/);
|
|
147
|
+
assert.match(result.reason, /will not reopen or recover it implicitly/);
|
|
148
|
+
});
|
|
149
|
+
|
|
126
150
|
test("dispatch: present task plan proceeds to execute-task normally", async (t) => {
|
|
127
151
|
const tmp = mkdtempSync(join(tmpdir(), "gsd-909-ok-"));
|
|
128
152
|
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
@@ -141,6 +165,42 @@ test("dispatch: present task plan proceeds to execute-task normally", async (t)
|
|
|
141
165
|
`unitId should be M002/S03/T01, got: ${result.action === "dispatch" ? result.unitId : "(stop)"}`);
|
|
142
166
|
});
|
|
143
167
|
|
|
168
|
+
test("dispatch: session milestone mismatch stops before missing-task-plan recovery", async (t) => {
|
|
169
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-session-milestone-mismatch-"));
|
|
170
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
171
|
+
|
|
172
|
+
const worktreeRoot = join(tmp, ".gsd", "worktrees", "M002");
|
|
173
|
+
mkdirSync(worktreeRoot, { recursive: true });
|
|
174
|
+
|
|
175
|
+
const ctx = makeContextFor(tmp, "M001", "S01", "T01", {
|
|
176
|
+
basePath: worktreeRoot,
|
|
177
|
+
originalBasePath: tmp,
|
|
178
|
+
currentMilestoneId: "M002",
|
|
179
|
+
});
|
|
180
|
+
const result = await resolveDispatch(ctx);
|
|
181
|
+
|
|
182
|
+
assert.equal(result.action, "stop");
|
|
183
|
+
assert.ok(result.action === "stop");
|
|
184
|
+
assert.equal(result.level, "warning");
|
|
185
|
+
assert.match(result.reason, /context mid "M001" does not match session\.currentMilestoneId "M002"/);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("dispatch: worktree path mismatch stops before planning a different milestone", async (t) => {
|
|
189
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-worktree-path-milestone-mismatch-"));
|
|
190
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
191
|
+
|
|
192
|
+
const worktreeRoot = join(tmp, ".gsd", "worktrees", "M002");
|
|
193
|
+
mkdirSync(worktreeRoot, { recursive: true });
|
|
194
|
+
|
|
195
|
+
const ctx = makeContextFor(worktreeRoot, "M001", "S01", "T01");
|
|
196
|
+
const result = await resolveDispatch(ctx);
|
|
197
|
+
|
|
198
|
+
assert.equal(result.action, "stop");
|
|
199
|
+
assert.ok(result.action === "stop");
|
|
200
|
+
assert.equal(result.level, "warning");
|
|
201
|
+
assert.match(result.reason, /context mid "M001" does not match basePath worktree "M002"/);
|
|
202
|
+
});
|
|
203
|
+
|
|
144
204
|
test("dispatch: executing recovery checks active milestone worktree task plans before re-dispatching plan-slice", async (t) => {
|
|
145
205
|
const tmp = mkdtempSync(join(tmpdir(), "gsd-6192-"));
|
|
146
206
|
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
@@ -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
|
+
});
|