gsd-pi 2.82.0-dev.3709f22a5 → 2.82.0-dev.4285182e8
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/README.md +2 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +14 -1
- package/dist/resources/extensions/gsd/auto/phases.js +53 -29
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +3 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +13 -19
- package/dist/resources/extensions/gsd/auto-post-unit.js +13 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +40 -13
- package/dist/resources/extensions/gsd/auto-start.js +3 -3
- package/dist/resources/extensions/gsd/auto-verification.js +17 -4
- package/dist/resources/extensions/gsd/auto-worktree.js +65 -9
- package/dist/resources/extensions/gsd/auto.js +14 -8
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +7 -2
- package/dist/resources/extensions/gsd/commands-verdict.js +139 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +16 -4
- package/dist/resources/extensions/gsd/db/milestone-leases.js +24 -0
- package/dist/resources/extensions/gsd/forensics.js +3 -3
- package/dist/resources/extensions/gsd/git-service.js +6 -2
- package/dist/resources/extensions/gsd/gsd-db.js +20 -6
- package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -3
- package/dist/resources/extensions/gsd/guided-flow.js +8 -5
- package/dist/resources/extensions/gsd/markdown-renderer.js +10 -8
- package/dist/resources/extensions/gsd/paths.js +4 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +30 -13
- package/dist/resources/extensions/gsd/state.js +3 -3
- package/dist/resources/extensions/gsd/status-guards.js +7 -0
- package/dist/resources/extensions/gsd/templates/plan.md +1 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -5
- package/dist/resources/extensions/gsd/workflow-mcp.js +17 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +1 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.js +3 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-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/api/browse-directories/route.js +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 +11 -11
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-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/dist/web/standalone/.next/static/chunks/8359.65b24fac92188a6b.js +10 -0
- package/dist/web/standalone/.next/static/chunks/9441.ff70bb53f6835771.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-9a4db269f9ed63ad.js → webpack-855d616060cb6e59.js} +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +5 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js +41 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js.map +1 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.test.ts +49 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +7 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +44 -3
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +71 -97
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +25 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +24 -10
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +53 -3
- package/packages/pi-coding-agent/src/core/sdk.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -102
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +30 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +29 -10
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js +103 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js.map +1 -0
- package/packages/pi-tui/dist/terminal.d.ts +2 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +12 -0
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/src/__tests__/terminal.test.ts +121 -0
- package/packages/pi-tui/src/terminal.ts +11 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +1 -1
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +9 -0
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +60 -36
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +5 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +13 -19
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +45 -11
- package/src/resources/extensions/gsd/auto-start.ts +2 -3
- package/src/resources/extensions/gsd/auto-verification.ts +22 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +74 -9
- package/src/resources/extensions/gsd/auto.ts +13 -8
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +8 -3
- package/src/resources/extensions/gsd/commands-verdict.ts +202 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +16 -2
- package/src/resources/extensions/gsd/db/milestone-leases.ts +26 -0
- package/src/resources/extensions/gsd/forensics.ts +3 -3
- package/src/resources/extensions/gsd/git-service.ts +6 -3
- package/src/resources/extensions/gsd/gsd-db.ts +18 -6
- package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -3
- package/src/resources/extensions/gsd/guided-flow.ts +8 -5
- package/src/resources/extensions/gsd/markdown-renderer.ts +10 -8
- package/src/resources/extensions/gsd/paths.ts +5 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +31 -13
- package/src/resources/extensions/gsd/state.ts +3 -3
- package/src/resources/extensions/gsd/status-guards.ts +8 -0
- package/src/resources/extensions/gsd/templates/plan.md +1 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +139 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +76 -5
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/quality-gates.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +13 -1
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +29 -2
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +18 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -4
- package/src/resources/extensions/gsd/workflow-mcp.ts +18 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +1 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +5 -1
- package/dist/web/standalone/.next/static/chunks/8359.7eb3bb8f8ecf4c01.js +0 -10
- package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +0 -1
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → 78uanrILNOKG-Jpi4itAE}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → 78uanrILNOKG-Jpi4itAE}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
chmodSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { delimiter, join } from "node:path";
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
import { mergeMilestoneToMain } from "../auto-worktree.ts";
|
|
18
|
+
import { closeDatabase, openDatabase } from "../gsd-db.ts";
|
|
19
|
+
import { GIT_NO_PROMPT_ENV } from "../git-constants.js";
|
|
20
|
+
import { _clearGsdRootCache } from "../paths.ts";
|
|
21
|
+
import { _resetServiceCache } from "../worktree.ts";
|
|
22
|
+
import { worktreePath } from "../worktree-manager.ts";
|
|
23
|
+
|
|
24
|
+
function git(args: string[], cwd: string): string {
|
|
25
|
+
return execFileSync("git", args, {
|
|
26
|
+
cwd,
|
|
27
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
}).trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function withPlatform<T>(platform: NodeJS.Platform, fn: () => T): T {
|
|
33
|
+
const descriptor = Object.getOwnPropertyDescriptor(process, "platform");
|
|
34
|
+
Object.defineProperty(process, "platform", { value: platform });
|
|
35
|
+
try {
|
|
36
|
+
return fn();
|
|
37
|
+
} finally {
|
|
38
|
+
if (descriptor) {
|
|
39
|
+
Object.defineProperty(process, "platform", descriptor);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function realGitPath(): string {
|
|
45
|
+
const gitExecPath = execFileSync("git", ["--exec-path"], {
|
|
46
|
+
encoding: "utf-8",
|
|
47
|
+
}).trim();
|
|
48
|
+
return join(gitExecPath, process.platform === "win32" ? "git.exe" : "git");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function installGitShim(bin: string, probePath: string): void {
|
|
52
|
+
const shim = join(bin, "git-proxy.cjs");
|
|
53
|
+
writeFileSync(
|
|
54
|
+
shim,
|
|
55
|
+
`
|
|
56
|
+
const { appendFileSync, existsSync } = require("node:fs");
|
|
57
|
+
const { join } = require("node:path");
|
|
58
|
+
const { spawnSync } = require("node:child_process");
|
|
59
|
+
|
|
60
|
+
const realGit = ${JSON.stringify(realGitPath())};
|
|
61
|
+
const probePath = ${JSON.stringify(probePath)};
|
|
62
|
+
const args = process.argv.slice(2);
|
|
63
|
+
|
|
64
|
+
if (args[0] === "merge" && args[1] === "--squash") {
|
|
65
|
+
const sidecars = [
|
|
66
|
+
join(process.cwd(), ".gsd", "gsd.db-wal"),
|
|
67
|
+
join(process.cwd(), ".gsd", "gsd.db-shm"),
|
|
68
|
+
];
|
|
69
|
+
const locked = sidecars.find((path) => existsSync(path));
|
|
70
|
+
if (locked) {
|
|
71
|
+
appendFileSync(probePath, "blocked:" + locked + "\\n");
|
|
72
|
+
console.error("error: local changes would be overwritten by merge");
|
|
73
|
+
console.error("\\t" + locked);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
appendFileSync(probePath, "clean\\n");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = spawnSync(realGit, args, { stdio: "inherit", env: process.env });
|
|
80
|
+
process.exit(result.status ?? 1);
|
|
81
|
+
`,
|
|
82
|
+
"utf-8",
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (process.platform === "win32") {
|
|
86
|
+
writeFileSync(join(bin, "git.cmd"), `@echo off\r\nnode "%~dp0git-proxy.cjs" %*\r\n`, "utf-8");
|
|
87
|
+
} else {
|
|
88
|
+
const executable = join(bin, "git");
|
|
89
|
+
writeFileSync(executable, `#!/bin/sh\nexec node "${shim}" "$@"\n`, "utf-8");
|
|
90
|
+
chmodSync(executable, 0o755);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function createRepo(root: string): { repo: string; worktree: string } {
|
|
95
|
+
const repo = join(root, "repo");
|
|
96
|
+
mkdirSync(repo, { recursive: true });
|
|
97
|
+
git(["init"], repo);
|
|
98
|
+
git(["config", "user.email", "test@test.com"], repo);
|
|
99
|
+
git(["config", "user.name", "Test"], repo);
|
|
100
|
+
writeFileSync(join(repo, ".gitignore"), ".gsd/\n", "utf-8");
|
|
101
|
+
writeFileSync(join(repo, "README.md"), "# test\n", "utf-8");
|
|
102
|
+
git(["add", "."], repo);
|
|
103
|
+
git(["commit", "-m", "init"], repo);
|
|
104
|
+
git(["branch", "-M", "main"], repo);
|
|
105
|
+
|
|
106
|
+
git(["checkout", "-b", "milestone/M001"], repo);
|
|
107
|
+
writeFileSync(join(repo, "feature.txt"), "milestone change\n", "utf-8");
|
|
108
|
+
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
|
109
|
+
writeFileSync(join(repo, ".gsd", "gsd.db-shm"), "milestone placeholder\n", "utf-8");
|
|
110
|
+
git(["add", "feature.txt"], repo);
|
|
111
|
+
git(["add", "-f", ".gsd/gsd.db-shm"], repo);
|
|
112
|
+
git(["commit", "-m", "feat: milestone change"], repo);
|
|
113
|
+
git(["checkout", "main"], repo);
|
|
114
|
+
|
|
115
|
+
const wt = worktreePath(repo, "M001");
|
|
116
|
+
mkdirSync(join(repo, ".gsd", "worktrees"), { recursive: true });
|
|
117
|
+
git(["worktree", "add", wt, "milestone/M001"], repo);
|
|
118
|
+
return { repo, worktree: wt };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
test("mergeMilestoneToMain keeps the Windows DB cycle closed through squash merge", () => {
|
|
122
|
+
const savedCwd = process.cwd();
|
|
123
|
+
const originalPath = process.env.PATH ?? "";
|
|
124
|
+
const gitEnv = GIT_NO_PROMPT_ENV as NodeJS.ProcessEnv;
|
|
125
|
+
const originalGitEnvPath = gitEnv.PATH;
|
|
126
|
+
const originalHome = process.env.HOME;
|
|
127
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
128
|
+
|
|
129
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-db-cycle-")));
|
|
130
|
+
const fakeHome = join(root, "home");
|
|
131
|
+
const bin = join(root, "bin");
|
|
132
|
+
const probePath = join(root, "merge-probe.txt");
|
|
133
|
+
mkdirSync(fakeHome, { recursive: true });
|
|
134
|
+
mkdirSync(bin, { recursive: true });
|
|
135
|
+
installGitShim(bin, probePath);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
process.env.HOME = fakeHome;
|
|
139
|
+
process.env.GSD_HOME = join(fakeHome, ".gsd");
|
|
140
|
+
_clearGsdRootCache();
|
|
141
|
+
_resetServiceCache();
|
|
142
|
+
|
|
143
|
+
const { repo, worktree } = createRepo(root);
|
|
144
|
+
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
|
145
|
+
|
|
146
|
+
withPlatform("win32", () => {
|
|
147
|
+
assert.equal(openDatabase(join(repo, ".gsd", "gsd.db")), true);
|
|
148
|
+
assert.equal(existsSync(join(repo, ".gsd", "gsd.db-shm")), true);
|
|
149
|
+
|
|
150
|
+
process.env.PATH = `${bin}${delimiter}${originalPath}`;
|
|
151
|
+
gitEnv.PATH = process.env.PATH;
|
|
152
|
+
process.chdir(worktree);
|
|
153
|
+
|
|
154
|
+
const result = mergeMilestoneToMain(repo, "M001", "# M001: Windows DB cycle\n");
|
|
155
|
+
assert.equal(result.codeFilesChanged, true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
assert.equal(git(["show", "HEAD:feature.txt"], repo), "milestone change");
|
|
159
|
+
assert.equal(readFileSync(probePath, "utf-8"), "clean\n");
|
|
160
|
+
} finally {
|
|
161
|
+
closeDatabase();
|
|
162
|
+
process.chdir(savedCwd);
|
|
163
|
+
process.env.PATH = originalPath;
|
|
164
|
+
gitEnv.PATH = originalGitEnvPath;
|
|
165
|
+
if (originalHome === undefined) {
|
|
166
|
+
delete process.env.HOME;
|
|
167
|
+
} else {
|
|
168
|
+
process.env.HOME = originalHome;
|
|
169
|
+
}
|
|
170
|
+
if (originalGsdHome === undefined) {
|
|
171
|
+
delete process.env.GSD_HOME;
|
|
172
|
+
} else {
|
|
173
|
+
process.env.GSD_HOME = originalGsdHome;
|
|
174
|
+
}
|
|
175
|
+
_clearGsdRootCache();
|
|
176
|
+
_resetServiceCache();
|
|
177
|
+
if (existsSync(root)) rmSync(root, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
@@ -243,7 +243,8 @@ test("#4781 phase 2: validate-milestone rule writes pass-through VALIDATION for
|
|
|
243
243
|
const content = readFileSync(validationPath, "utf-8");
|
|
244
244
|
assert.match(content, /verdict: pass/);
|
|
245
245
|
assert.match(content, /skip_validation: true/);
|
|
246
|
-
assert.match(content, /trivial-scope pipeline variant
|
|
246
|
+
assert.match(content, /trivial-scope pipeline variant/);
|
|
247
|
+
assert.doesNotMatch(content, /#[0-9]{3,}/, "validation output must not include tracker-style refs");
|
|
247
248
|
});
|
|
248
249
|
|
|
249
250
|
test("#4781 phase 2: validate-milestone skip path does not persist gates without a real slice", async (t) => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import test from 'node:test';
|
|
4
4
|
import assert from 'node:assert/strict';
|
|
5
|
-
import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync, writeFileSync, realpathSync } from 'node:fs';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
8
|
|
|
@@ -105,6 +105,31 @@ test('handlePlanSlice writes slice/task planning state and renders plan artifact
|
|
|
105
105
|
}
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
test('handlePlanSlice renders plan artifacts under worktree-local .gsd while using project DB', async () => {
|
|
109
|
+
const base = makeTmpBase();
|
|
110
|
+
const worktree = join(base, '.gsd', 'worktrees', 'M001');
|
|
111
|
+
mkdirSync(join(worktree, '.gsd'), { recursive: true });
|
|
112
|
+
mkdirSync(join(worktree, 'src', 'resources', 'extensions', 'gsd', 'tools'), { recursive: true });
|
|
113
|
+
writeFileSync(join(worktree, 'src', 'resources', 'extensions', 'gsd', 'tools', 'plan-milestone.ts'), '// fixture\n', 'utf-8');
|
|
114
|
+
writeFileSync(join(worktree, 'src', 'resources', 'extensions', 'gsd', 'tools', 'plan-task.ts'), '// fixture\n', 'utf-8');
|
|
115
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
seedParentSlice();
|
|
119
|
+
|
|
120
|
+
const result = await handlePlanSlice(validParams(), worktree);
|
|
121
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
122
|
+
|
|
123
|
+
const worktreePlan = join(worktree, '.gsd', 'milestones', 'M001', 'slices', 'S02', 'S02-PLAN.md');
|
|
124
|
+
const projectPlan = join(base, '.gsd', 'milestones', 'M001', 'slices', 'S02', 'S02-PLAN.md');
|
|
125
|
+
assert.ok(existsSync(worktreePlan), 'slice plan should be rendered to worktree-local .gsd');
|
|
126
|
+
assert.ok(!existsSync(projectPlan), 'slice plan should not be rendered to project .gsd');
|
|
127
|
+
assert.equal(result.planPath, realpathSync(worktreePlan));
|
|
128
|
+
} finally {
|
|
129
|
+
cleanup(base);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
108
133
|
test('handlePlanSlice advances DB-derived state out of planning immediately', async () => {
|
|
109
134
|
const base = makeTmpBase();
|
|
110
135
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -8,8 +8,32 @@ import assert from "node:assert/strict";
|
|
|
8
8
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
11
12
|
import { AutoSession } from "../auto/session.ts";
|
|
12
13
|
import { postUnitPreVerification } from "../auto-post-unit.ts";
|
|
14
|
+
import {
|
|
15
|
+
_getAdapter,
|
|
16
|
+
closeDatabase,
|
|
17
|
+
getTask,
|
|
18
|
+
insertMilestone,
|
|
19
|
+
insertSlice,
|
|
20
|
+
insertTask,
|
|
21
|
+
openDatabase,
|
|
22
|
+
} from "../gsd-db.ts";
|
|
23
|
+
|
|
24
|
+
const _require = createRequire(import.meta.url);
|
|
25
|
+
|
|
26
|
+
function openRawSqliteForTest(dbPath: string): { exec(sql: string): void; close(): void } {
|
|
27
|
+
try {
|
|
28
|
+
const mod = _require("node:sqlite") as { DatabaseSync: new (path: string) => { exec(sql: string): void; close(): void } };
|
|
29
|
+
return new mod.DatabaseSync(dbPath);
|
|
30
|
+
} catch {
|
|
31
|
+
type SqliteCtor = new (path: string) => { exec(sql: string): void; close(): void };
|
|
32
|
+
const mod = _require("better-sqlite3") as SqliteCtor | { default: SqliteCtor };
|
|
33
|
+
const DatabaseCtor: SqliteCtor = typeof mod === "function" ? mod : mod.default;
|
|
34
|
+
return new DatabaseCtor(dbPath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
13
37
|
|
|
14
38
|
test("postUnitPreVerification rebuilds STATE.md after a completed unit", async () => {
|
|
15
39
|
const base = mkdtempSync(join(tmpdir(), "gsd-post-unit-state-"));
|
|
@@ -47,3 +71,63 @@ test("postUnitPreVerification rebuilds STATE.md after a completed unit", async (
|
|
|
47
71
|
rmSync(base, { recursive: true, force: true });
|
|
48
72
|
}
|
|
49
73
|
});
|
|
74
|
+
|
|
75
|
+
test("postUnitPreVerification refreshes DB before checking execute-task completion", async () => {
|
|
76
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-post-unit-db-refresh-"));
|
|
77
|
+
try {
|
|
78
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
79
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
80
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
81
|
+
writeFileSync(
|
|
82
|
+
join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
|
|
83
|
+
"# Roadmap\n\n## Slices\n\n- [ ] **S01: Slice** `risk:low` `depends:[]`\n",
|
|
84
|
+
);
|
|
85
|
+
writeFileSync(
|
|
86
|
+
join(sliceDir, "S01-PLAN.md"),
|
|
87
|
+
"# S01: Slice\n\n## Tasks\n\n- [ ] **T01: Do work** `est:30m`\n",
|
|
88
|
+
);
|
|
89
|
+
writeFileSync(
|
|
90
|
+
join(tasksDir, "T01-SUMMARY.md"),
|
|
91
|
+
"---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01\nDone.\n",
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
95
|
+
openDatabase(dbPath);
|
|
96
|
+
insertMilestone({ id: "M001", title: "Milestone", status: "active" });
|
|
97
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "pending" });
|
|
98
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Do work", status: "pending" });
|
|
99
|
+
const adapterBefore = _getAdapter();
|
|
100
|
+
|
|
101
|
+
const externalDb = openRawSqliteForTest(dbPath);
|
|
102
|
+
try {
|
|
103
|
+
externalDb.exec("UPDATE tasks SET status = 'complete', completed_at = '2026-05-14T00:00:00.000Z' WHERE milestone_id = 'M001' AND slice_id = 'S01' AND id = 'T01'");
|
|
104
|
+
} finally {
|
|
105
|
+
externalDb.close();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const s = new AutoSession();
|
|
109
|
+
s.basePath = base;
|
|
110
|
+
s.originalBasePath = base;
|
|
111
|
+
s.currentMilestoneId = "M001";
|
|
112
|
+
s.currentUnit = { type: "execute-task", id: "M001/S01/T01", startedAt: Date.now() };
|
|
113
|
+
|
|
114
|
+
const result = await postUnitPreVerification({
|
|
115
|
+
s,
|
|
116
|
+
ctx: { ui: { notify() {} } } as any,
|
|
117
|
+
pi: {} as any,
|
|
118
|
+
buildSnapshotOpts: () => ({}),
|
|
119
|
+
lockBase: () => base,
|
|
120
|
+
stopAuto: async () => {},
|
|
121
|
+
pauseAuto: async () => {},
|
|
122
|
+
updateProgressWidget: () => {},
|
|
123
|
+
}, { skipSettleDelay: true, skipWorktreeSync: true });
|
|
124
|
+
|
|
125
|
+
assert.equal(result, "continue");
|
|
126
|
+
assert.notEqual(_getAdapter(), adapterBefore, "post-unit flow must reopen the DB before deriving state");
|
|
127
|
+
assert.equal(getTask("M001", "S01", "T01")?.status, "complete");
|
|
128
|
+
assert.equal(s.pendingVerificationRetry, null);
|
|
129
|
+
} finally {
|
|
130
|
+
closeDatabase();
|
|
131
|
+
rmSync(base, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
@@ -141,3 +141,62 @@ test("category summaries expose the wizard menu surface for configured prefs", (
|
|
|
141
141
|
assert.match(summaries.integrations, /remote: C123/);
|
|
142
142
|
assert.match(summaries.verification, /1 cmd/);
|
|
143
143
|
});
|
|
144
|
+
|
|
145
|
+
test("models wizard offers discovered models for enabled providers", async () => {
|
|
146
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-prefs-wizard-"));
|
|
147
|
+
const prefsPath = join(dir, "PREFERENCES.md");
|
|
148
|
+
const choices = [
|
|
149
|
+
"Models",
|
|
150
|
+
"local (2 models)",
|
|
151
|
+
"discovered-model",
|
|
152
|
+
"(keep current)",
|
|
153
|
+
"(keep current)",
|
|
154
|
+
"(keep current)",
|
|
155
|
+
"(keep current)",
|
|
156
|
+
"(keep current)",
|
|
157
|
+
"(keep current)",
|
|
158
|
+
"(keep current)",
|
|
159
|
+
];
|
|
160
|
+
const ctx = {
|
|
161
|
+
modelRegistry: {
|
|
162
|
+
getAvailable: () => [{ provider: "local", id: "baseline-model" }],
|
|
163
|
+
getAllWithDiscovered: () => [
|
|
164
|
+
{ provider: "local", id: "baseline-model" },
|
|
165
|
+
{ provider: "local", id: "discovered-model" },
|
|
166
|
+
{ provider: "disabled", id: "hidden-model" },
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
ui: {
|
|
170
|
+
notify() {},
|
|
171
|
+
select: async (label: string, options: string[]) => {
|
|
172
|
+
const choice = choices.shift();
|
|
173
|
+
if (!choice && label === "GSD Preferences") return "── Save & Exit ──";
|
|
174
|
+
if (!choice && options.includes("(keep current)")) return "(keep current)";
|
|
175
|
+
if (!choice && options.includes("Done")) return "Done";
|
|
176
|
+
assert.ok(choice, `Unexpected prompt: ${label}`);
|
|
177
|
+
if (choice === "Models") {
|
|
178
|
+
const modelsOption = options.find((option) => option.startsWith("Models"));
|
|
179
|
+
assert.ok(modelsOption, "Expected Models category option");
|
|
180
|
+
return modelsOption;
|
|
181
|
+
}
|
|
182
|
+
assert.ok(options.includes(choice), `"${choice}" must be offered by "${label}"`);
|
|
183
|
+
assert.ok(!options.includes("hidden-model"), "models from disabled providers must not be offered");
|
|
184
|
+
return choice;
|
|
185
|
+
},
|
|
186
|
+
input: async () => null,
|
|
187
|
+
},
|
|
188
|
+
waitForIdle: async () => {},
|
|
189
|
+
reload: async () => {},
|
|
190
|
+
} as any;
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
await handlePrefsWizard(ctx, "project", {}, { pathOverride: prefsPath });
|
|
194
|
+
|
|
195
|
+
assert.equal(choices.length, 0, "Expected all queued wizard choices to be consumed");
|
|
196
|
+
const saved = readFileSync(prefsPath, "utf-8");
|
|
197
|
+
assert.match(saved, /research:\s+local\/discovered-model/);
|
|
198
|
+
assert.doesNotMatch(saved, /hidden-model/);
|
|
199
|
+
} finally {
|
|
200
|
+
rmSync(dir, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
@@ -10,7 +10,12 @@ import assert from "node:assert/strict";
|
|
|
10
10
|
import { classifyError, isTransient, isTransientNetworkError } from "../error-classifier.ts";
|
|
11
11
|
import { pauseAutoForProviderError } from "../provider-error-pause.ts";
|
|
12
12
|
import { resumeAutoAfterProviderDelay } from "../bootstrap/provider-error-resume.ts";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
MAX_TRANSIENT_AUTO_RESUMES,
|
|
15
|
+
isTerminalDeletedWorktreeProviderError,
|
|
16
|
+
resetTransientRetryState,
|
|
17
|
+
shouldDeferTransientErrorToCoreRetry,
|
|
18
|
+
} from "../bootstrap/agent-end-recovery.ts";
|
|
14
19
|
import { _buildCancelledUnitStopReason } from "../auto/phases.ts";
|
|
15
20
|
import { getNextFallbackModel } from "../preferences.ts";
|
|
16
21
|
// Zero-import module — imported by path rather than through the package
|
|
@@ -678,3 +683,15 @@ test("agent-session retryable error regex matches server_error (underscore)", ()
|
|
|
678
683
|
// "temporarily backed off" must NOT be matched (intentional exclusion #3429)
|
|
679
684
|
assert.ok(!RETRYABLE_ERROR_RE.test("temporarily backed off"));
|
|
680
685
|
});
|
|
686
|
+
|
|
687
|
+
test("exhausted retry errors are not deferred back to core retry handling", () => {
|
|
688
|
+
const cls = classifyError("Retry failed after 3 attempts: 500 empty_stream: upstream stream closed before first payload");
|
|
689
|
+
assert.equal(cls.kind, "server");
|
|
690
|
+
assert.equal(
|
|
691
|
+
shouldDeferTransientErrorToCoreRetry(
|
|
692
|
+
cls,
|
|
693
|
+
"Retry failed after 3 attempts: 500 empty_stream: upstream stream closed before first payload",
|
|
694
|
+
),
|
|
695
|
+
false,
|
|
696
|
+
);
|
|
697
|
+
});
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Validates planning and task template quality-gate content.
|
|
3
|
+
|
|
1
4
|
import { readFileSync } from "node:fs";
|
|
2
5
|
import { join, dirname } from "node:path";
|
|
3
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -27,11 +30,14 @@ console.log("\n=== Level 1: Templates contain quality gate headings ===");
|
|
|
27
30
|
const plan = loadTemplate("plan");
|
|
28
31
|
assertTrue(plan.includes("## Threat Surface"), "plan.md contains ## Threat Surface");
|
|
29
32
|
assertTrue(plan.includes("## Requirement Impact"), "plan.md contains ## Requirement Impact");
|
|
33
|
+
assertTrue(plan.includes("node --test"), "plan.md instructs using node --test for verification");
|
|
30
34
|
|
|
31
35
|
const taskPlan = loadTemplate("task-plan");
|
|
32
36
|
assertTrue(taskPlan.includes("## Failure Modes"), "task-plan.md contains ## Failure Modes");
|
|
33
37
|
assertTrue(taskPlan.includes("## Load Profile"), "task-plan.md contains ## Load Profile");
|
|
34
38
|
assertTrue(taskPlan.includes("## Negative Tests"), "task-plan.md contains ## Negative Tests");
|
|
39
|
+
assertTrue(taskPlan.includes("node --test"), "task-plan.md instructs using node --test for verification");
|
|
40
|
+
assertTrue(taskPlan.includes("node -e"), "task-plan.md mentions inline node -e as disallowed guidance");
|
|
35
41
|
|
|
36
42
|
const sliceSummary = loadTemplate("slice-summary");
|
|
37
43
|
assertTrue(sliceSummary.includes("## Operational Readiness"), "slice-summary.md contains ## Operational Readiness");
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { showQueueReorder } from "../queue-reorder-ui.ts";
|
|
5
|
+
|
|
6
|
+
const fakeTheme = {
|
|
7
|
+
fg: (_color: string, text: string) => text,
|
|
8
|
+
bold: (text: string) => text,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe("queue-reorder-ui", () => {
|
|
12
|
+
test("keeps cursor visible while scrolling long queue with arrow keys (#4656)", async () => {
|
|
13
|
+
const originalRowsDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "rows");
|
|
14
|
+
Object.defineProperty(process.stdout, "rows", { value: 20, configurable: true });
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const pending = Array.from({ length: 20 }, (_, idx) => ({
|
|
18
|
+
id: `M${String(idx + 1).padStart(3, "0")}`,
|
|
19
|
+
title: `Milestone ${idx + 1}`,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
let resolved: { order: string[]; depsToRemove: Array<{ milestone: string; dep: string }> } | null = null;
|
|
23
|
+
let lastRender: string[] = [];
|
|
24
|
+
|
|
25
|
+
const ctx = {
|
|
26
|
+
hasUI: true,
|
|
27
|
+
ui: {
|
|
28
|
+
custom: async (factory: any) => {
|
|
29
|
+
const component = factory({ requestRender() {} }, fakeTheme, null, (value: any) => {
|
|
30
|
+
resolved = value;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < 15; i++) component.handleInput("\u001b[B");
|
|
34
|
+
lastRender = component.render(100);
|
|
35
|
+
component.handleInput("\r");
|
|
36
|
+
return resolved;
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
} as any;
|
|
40
|
+
|
|
41
|
+
await showQueueReorder(ctx, [], pending);
|
|
42
|
+
|
|
43
|
+
const joined = lastRender.join("\n");
|
|
44
|
+
assert.ok(joined.includes("M016"), "selected item should stay visible after scrolling");
|
|
45
|
+
assert.ok(lastRender.length <= 16, `overlay should fit terminal max-height, got ${lastRender.length}`);
|
|
46
|
+
} finally {
|
|
47
|
+
if (originalRowsDescriptor) {
|
|
48
|
+
Object.defineProperty(process.stdout, "rows", originalRowsDescriptor);
|
|
49
|
+
} else {
|
|
50
|
+
delete (process.stdout as { rows?: number }).rows;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -110,6 +110,49 @@ test("completing-milestone blocks when VALIDATION verdict is needs-attention (#5
|
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
+
test("completing-milestone blocks when VALIDATION verdict is fail (#5920)", async () => {
|
|
114
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-fail-verdict-"));
|
|
115
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
writeFileSync(
|
|
119
|
+
join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md"),
|
|
120
|
+
[
|
|
121
|
+
"---",
|
|
122
|
+
"verdict: fail",
|
|
123
|
+
"---",
|
|
124
|
+
"",
|
|
125
|
+
"# Validation Report",
|
|
126
|
+
"",
|
|
127
|
+
"Blocking failures remain unresolved.",
|
|
128
|
+
].join("\n"),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const ctx = {
|
|
132
|
+
mid: "M001",
|
|
133
|
+
midTitle: "Test Milestone",
|
|
134
|
+
basePath: base,
|
|
135
|
+
state: { phase: "completing-milestone" } as any,
|
|
136
|
+
prefs: {} as any,
|
|
137
|
+
session: undefined,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const result = await completingRule!.match(ctx);
|
|
141
|
+
|
|
142
|
+
assert.ok(result !== null, "rule should match");
|
|
143
|
+
assert.equal(result!.action, "stop", "should return stop action");
|
|
144
|
+
if (result!.action === "stop") {
|
|
145
|
+
assert.equal(result!.level, "warning", "should be warning level (pausable)");
|
|
146
|
+
assert.ok(
|
|
147
|
+
result!.reason.includes('verdict is "fail"'),
|
|
148
|
+
"reason should mention fail verdict",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
} finally {
|
|
152
|
+
rmSync(base, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
113
156
|
test("completing-milestone proceeds normally when VALIDATION verdict is pass (#2675 guard)", async () => {
|
|
114
157
|
const base = mkdtempSync(join(tmpdir(), "gsd-remediation-"));
|
|
115
158
|
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
@@ -36,7 +36,7 @@ function makeUatProject(): string {
|
|
|
36
36
|
return base;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
test("run-uat dispatch
|
|
39
|
+
test("run-uat dispatch skips after three attempts without a verdict", async () => {
|
|
40
40
|
const basePath = makeUatProject();
|
|
41
41
|
const rule = DISPATCH_RULES.find((r) => r.name === "run-uat (post-completion)");
|
|
42
42
|
assert.ok(rule, "run-uat dispatch rule is registered");
|
|
@@ -58,8 +58,7 @@ test("run-uat dispatch stops after three attempts without a verdict", async () =
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
const capped = await rule.match(ctx as any);
|
|
61
|
-
assert.equal(capped?.action, "
|
|
62
|
-
assert.match(capped?.reason ?? "", /dispatched 3 times/);
|
|
61
|
+
assert.equal(capped?.action, "skip");
|
|
63
62
|
assert.equal(getUatCount(basePath, "M001", "S01"), 4);
|
|
64
63
|
} finally {
|
|
65
64
|
rmSync(basePath, { recursive: true, force: true });
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import test from 'node:test';
|
|
4
4
|
import assert from 'node:assert/strict';
|
|
5
5
|
|
|
6
|
-
import { isClosedStatus } from '../status-guards.ts';
|
|
6
|
+
import { isClosedStatus, isFutureMilestoneStatus } from '../status-guards.ts';
|
|
7
7
|
|
|
8
8
|
test('isClosedStatus: "complete" returns true', () => {
|
|
9
9
|
assert.equal(isClosedStatus('complete'), true);
|
|
@@ -32,3 +32,15 @@ test('isClosedStatus: "active" returns false', () => {
|
|
|
32
32
|
test('isClosedStatus: "" (empty string) returns false', () => {
|
|
33
33
|
assert.equal(isClosedStatus(''), false);
|
|
34
34
|
});
|
|
35
|
+
|
|
36
|
+
test('isFutureMilestoneStatus includes future milestone aliases', () => {
|
|
37
|
+
assert.equal(isFutureMilestoneStatus('pending'), true);
|
|
38
|
+
assert.equal(isFutureMilestoneStatus('queued'), true);
|
|
39
|
+
assert.equal(isFutureMilestoneStatus('planned'), true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('isFutureMilestoneStatus excludes active and closed milestones', () => {
|
|
43
|
+
assert.equal(isFutureMilestoneStatus('active'), false);
|
|
44
|
+
assert.equal(isFutureMilestoneStatus('complete'), false);
|
|
45
|
+
assert.equal(isFutureMilestoneStatus('parked'), false);
|
|
46
|
+
});
|
|
@@ -301,6 +301,23 @@ test("#5843: run-uat uses verification tools policy so build/test commands can r
|
|
|
301
301
|
assert.match(sourceWriteResult.reason!, /tools-policy "verification"/);
|
|
302
302
|
});
|
|
303
303
|
|
|
304
|
+
test("planning-dispatch hard block message omits internal tracker references", () => {
|
|
305
|
+
const manifest = UNIT_MANIFESTS["validate-milestone"];
|
|
306
|
+
assert.strictEqual(manifest.tools.mode, "planning-dispatch");
|
|
307
|
+
|
|
308
|
+
const result = shouldBlockPlanningUnit(
|
|
309
|
+
"task",
|
|
310
|
+
"scout",
|
|
311
|
+
process.cwd(),
|
|
312
|
+
"validate-milestone",
|
|
313
|
+
manifest.tools,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
assert.strictEqual(result.block, true);
|
|
317
|
+
assert.ok(result.reason, "blocked dispatch should include a user-facing reason");
|
|
318
|
+
assert.doesNotMatch(result.reason!, /#[0-9]{3,}/);
|
|
319
|
+
});
|
|
320
|
+
|
|
304
321
|
test('planning-dispatch mode is reserved for slice-level decomposition and completion units', () => {
|
|
305
322
|
const allowedDispatchUnits = new Set([
|
|
306
323
|
"plan-slice",
|
|
@@ -162,7 +162,7 @@ describe("validate-milestone stuck-loop guard (#4094)", () => {
|
|
|
162
162
|
assert.equal(pauseAutoMock.mock.callCount(), 0);
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
test("
|
|
165
|
+
test("retries when no VALIDATION file exists yet", async () => {
|
|
166
166
|
insertMilestone({ id: "M001" });
|
|
167
167
|
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice 1", status: "complete" });
|
|
168
168
|
|
|
@@ -173,7 +173,34 @@ describe("validate-milestone stuck-loop guard (#4094)", () => {
|
|
|
173
173
|
|
|
174
174
|
const result = await runPostUnitVerification({ s, ctx, pi } as VerificationContext, pauseAutoMock);
|
|
175
175
|
|
|
176
|
-
assert.equal(result, "
|
|
176
|
+
assert.equal(result, "retry");
|
|
177
|
+
assert.equal(pauseAutoMock.mock.callCount(), 0);
|
|
178
|
+
assert.ok(s.pendingVerificationRetry);
|
|
179
|
+
assert.equal(s.pendingVerificationRetry!.unitId, "M001");
|
|
180
|
+
assert.match(s.pendingVerificationRetry!.failureContext, /gsd_validate_milestone/);
|
|
181
|
+
assert.equal(s.pendingVerificationRetry!.attempt, 1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("retries when VALIDATION file exists but is empty", async () => {
|
|
185
|
+
insertMilestone({ id: "M001" });
|
|
186
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice 1", status: "complete" });
|
|
187
|
+
|
|
188
|
+
const path = join(tempDir, ".gsd", "milestones", "M001", "M001-VALIDATION.md");
|
|
189
|
+
writeFileSync(path, "", "utf-8");
|
|
190
|
+
invalidateAllCaches();
|
|
191
|
+
|
|
192
|
+
const ctx = makeMockCtx();
|
|
193
|
+
const pi = makeMockPi();
|
|
194
|
+
const pauseAutoMock = mock.fn(async () => {});
|
|
195
|
+
const s = makeMockSession(tempDir, "validate-milestone", "M001");
|
|
196
|
+
|
|
197
|
+
const result = await runPostUnitVerification({ s, ctx, pi } as VerificationContext, pauseAutoMock);
|
|
198
|
+
|
|
199
|
+
assert.equal(result, "retry");
|
|
177
200
|
assert.equal(pauseAutoMock.mock.callCount(), 0);
|
|
201
|
+
assert.ok(s.pendingVerificationRetry);
|
|
202
|
+
assert.equal(s.pendingVerificationRetry!.unitId, "M001");
|
|
203
|
+
assert.match(s.pendingVerificationRetry!.failureContext, /exists but is empty/);
|
|
204
|
+
assert.equal(s.pendingVerificationRetry!.attempt, 1);
|
|
178
205
|
});
|
|
179
206
|
});
|