gsd-pi 2.82.0-dev.3709f22a5 → 2.82.0-dev.57fd453e4
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 +12 -18
- 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-prefs-wizard.js +7 -2
- 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 +1 -1
- 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 +14 -14
- 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 +14 -14
- 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 +12 -0
- 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.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +19 -8
- 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 +14 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +23 -8
- 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 +12 -18
- 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-prefs-wizard.ts +8 -3
- 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 +1 -1
- 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/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 → ky6ieNHfZXB_oHPklwTJb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → ky6ieNHfZXB_oHPklwTJb}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
|
|
8
|
+
import { checkoutBranchWithStashGuard } from "../auto-worktree.ts";
|
|
9
|
+
|
|
10
|
+
function git(args: string[], cwd: string): string {
|
|
11
|
+
return execFileSync("git", args, {
|
|
12
|
+
cwd,
|
|
13
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function createRepo(t: { after: (fn: () => void) => void }): string {
|
|
19
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "checkout-stash-guard-")));
|
|
20
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
21
|
+
git(["init"], dir);
|
|
22
|
+
git(["config", "user.email", "test@example.com"], dir);
|
|
23
|
+
git(["config", "user.name", "Test User"], dir);
|
|
24
|
+
writeFileSync(join(dir, "note.txt"), "base\n");
|
|
25
|
+
git(["add", "note.txt"], dir);
|
|
26
|
+
git(["commit", "-m", "init"], dir);
|
|
27
|
+
git(["branch", "-M", "main"], dir);
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("checkoutBranchWithStashGuard", () => {
|
|
32
|
+
test("restores dirty working tree after successful checkout", (t) => {
|
|
33
|
+
const repo = createRepo(t);
|
|
34
|
+
git(["checkout", "-b", "milestone/M001"], repo);
|
|
35
|
+
git(["checkout", "main"], repo);
|
|
36
|
+
|
|
37
|
+
writeFileSync(join(repo, "note.txt"), "dirty\n");
|
|
38
|
+
|
|
39
|
+
checkoutBranchWithStashGuard(repo, "milestone/M001", "test-success");
|
|
40
|
+
|
|
41
|
+
const branch = git(["branch", "--show-current"], repo).trim();
|
|
42
|
+
assert.equal(branch, "milestone/M001");
|
|
43
|
+
const content = git(["show", "HEAD:note.txt"], repo).trim();
|
|
44
|
+
assert.equal(content, "base");
|
|
45
|
+
const wtContent = readFileSync(join(repo, "note.txt"), "utf8");
|
|
46
|
+
assert.equal(wtContent, "dirty\n");
|
|
47
|
+
const status = git(["status", "--porcelain"], repo);
|
|
48
|
+
assert.match(status, /note\.txt/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("restores dirty working tree when checkout throws", (t) => {
|
|
52
|
+
const repo = createRepo(t);
|
|
53
|
+
writeFileSync(join(repo, "note.txt"), "dirty\n");
|
|
54
|
+
|
|
55
|
+
assert.throws(
|
|
56
|
+
() => checkoutBranchWithStashGuard(repo, "milestone/DOES-NOT-EXIST", "test-failure"),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const status = git(["status", "--porcelain"], repo);
|
|
60
|
+
assert.match(status, /note\.txt/);
|
|
61
|
+
const stashList = git(["stash", "list"], repo).trim();
|
|
62
|
+
assert.equal(stashList, "");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("surfaces distinct error when checkout succeeds but stash pop conflicts", (t) => {
|
|
66
|
+
const repo = createRepo(t);
|
|
67
|
+
// Branch B has a divergent version of note.txt so popping a stash made
|
|
68
|
+
// against main will conflict after the checkout to B.
|
|
69
|
+
git(["checkout", "-b", "milestone/B"], repo);
|
|
70
|
+
writeFileSync(join(repo, "note.txt"), "B-version\n");
|
|
71
|
+
git(["add", "note.txt"], repo);
|
|
72
|
+
git(["commit", "-m", "B"], repo);
|
|
73
|
+
git(["checkout", "main"], repo);
|
|
74
|
+
|
|
75
|
+
writeFileSync(join(repo, "note.txt"), "local\n");
|
|
76
|
+
|
|
77
|
+
assert.throws(
|
|
78
|
+
() => checkoutBranchWithStashGuard(repo, "milestone/B", "test-pop-failure"),
|
|
79
|
+
/checkout to 'milestone\/B' succeeded but stash restore failed/,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const branch = git(["branch", "--show-current"], repo).trim();
|
|
83
|
+
assert.equal(branch, "milestone/B");
|
|
84
|
+
const stashList = git(["stash", "list"], repo).trim();
|
|
85
|
+
assert.match(stashList, /gsd: checkout stash/);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -225,6 +225,25 @@ test("clearLock removes the session_file row for the active worker", (t) => {
|
|
|
225
225
|
"session_file row deleted by clearLock");
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
+
test("clearLock marks stale worker crashed when no current-process worker matches", (t) => {
|
|
229
|
+
const base = makeBase();
|
|
230
|
+
t.after(() => cleanup(base));
|
|
231
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
232
|
+
const projectRoot = normalizeRealPath(base);
|
|
233
|
+
const workerId = registerAutoWorker({ projectRootRealpath: projectRoot });
|
|
234
|
+
|
|
235
|
+
setRuntimeKv("worker", workerId, "session_file", "/tmp/stale-session.jsonl");
|
|
236
|
+
setWorkerPid(workerId, 99999);
|
|
237
|
+
expireWorker(workerId);
|
|
238
|
+
assert.ok(readCrashLock(base), "stale worker is detected before clearLock");
|
|
239
|
+
|
|
240
|
+
clearLock(base);
|
|
241
|
+
|
|
242
|
+
assert.equal(getAutoWorker(workerId)?.status, "crashed");
|
|
243
|
+
assert.equal(getRuntimeKv("worker", workerId, "session_file"), null);
|
|
244
|
+
assert.equal(readCrashLock(base), null);
|
|
245
|
+
});
|
|
246
|
+
|
|
228
247
|
test("clearStaleWorkerLock crashes stale worker and cancels latest active dispatch", (t) => {
|
|
229
248
|
const base = makeBase();
|
|
230
249
|
t.after(() => cleanup(base));
|
|
@@ -264,3 +283,27 @@ test("clearStaleWorkerLock crashes stale worker and cancels latest active dispat
|
|
|
264
283
|
assert.equal(getRuntimeKv("worker", workerId, "session_file"), null);
|
|
265
284
|
assert.equal(readCrashLock(base), null);
|
|
266
285
|
});
|
|
286
|
+
|
|
287
|
+
test("clearLock marks stale worker crashed and releases held milestone lease", (t) => {
|
|
288
|
+
const base = makeBase();
|
|
289
|
+
t.after(() => cleanup(base));
|
|
290
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
291
|
+
insertMilestone({ id: "M001", title: "T", status: "active" });
|
|
292
|
+
const projectRoot = normalizeRealPath(base);
|
|
293
|
+
const workerId = registerAutoWorker({ projectRootRealpath: projectRoot });
|
|
294
|
+
const lease = claimMilestoneLease(workerId, "M001");
|
|
295
|
+
assert.equal(lease.ok, true);
|
|
296
|
+
if (!lease.ok) return;
|
|
297
|
+
|
|
298
|
+
setWorkerPid(workerId, 99999);
|
|
299
|
+
expireWorker(workerId);
|
|
300
|
+
assert.ok(readCrashLock(base), "stale worker is detected before clearLock");
|
|
301
|
+
|
|
302
|
+
clearLock(base);
|
|
303
|
+
|
|
304
|
+
assert.equal(getAutoWorker(workerId)?.status, "crashed");
|
|
305
|
+
const leaseRow = _getAdapter()!.prepare(
|
|
306
|
+
`SELECT status FROM milestone_leases WHERE milestone_id = :m`,
|
|
307
|
+
).get({ ":m": "M001" }) as { status: string } | undefined;
|
|
308
|
+
assert.equal(leaseRow?.status, "released");
|
|
309
|
+
});
|
|
@@ -10,6 +10,7 @@ import assert from "node:assert/strict";
|
|
|
10
10
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
13
14
|
|
|
14
15
|
import { DISPATCH_RULES, type DispatchContext } from "../auto-dispatch.ts";
|
|
15
16
|
import { closeDatabase, insertMilestone, openDatabase } from "../gsd-db.ts";
|
|
@@ -23,6 +24,14 @@ function makeBase(): string {
|
|
|
23
24
|
return base;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
function initGitRepo(base: string): void {
|
|
28
|
+
execFileSync("git", ["init"], { cwd: base, stdio: "ignore" });
|
|
29
|
+
execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
|
|
30
|
+
execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
|
|
31
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
32
|
+
execFileSync("git", ["commit", "-m", "initial"], { cwd: base, stdio: "ignore" });
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
function buildDispatchCtx(basePath: string): DispatchContext {
|
|
27
36
|
return {
|
|
28
37
|
basePath,
|
|
@@ -76,6 +85,24 @@ describe("completing-milestone dispatch guard (#4324)", () => {
|
|
|
76
85
|
assert.equal(result?.unitType, "complete-milestone");
|
|
77
86
|
assert.equal(result?.unitId, "M001");
|
|
78
87
|
});
|
|
88
|
+
|
|
89
|
+
test("dispatches complete-milestone when only .gsd/ files exist in git history (#5097)", async () => {
|
|
90
|
+
base = makeBase();
|
|
91
|
+
rmSync(join(base, "implementation.txt"), { force: true });
|
|
92
|
+
initGitRepo(base);
|
|
93
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\n");
|
|
94
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
95
|
+
execFileSync("git", ["commit", "-m", "chore: planning artifacts only"], { cwd: base, stdio: "ignore" });
|
|
96
|
+
|
|
97
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
98
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
|
|
99
|
+
|
|
100
|
+
const result = await rule.match(buildDispatchCtx(base));
|
|
101
|
+
|
|
102
|
+
assert.equal(result?.action, "dispatch");
|
|
103
|
+
assert.equal(result?.unitType, "complete-milestone");
|
|
104
|
+
assert.equal(result?.unitId, "M001");
|
|
105
|
+
});
|
|
79
106
|
});
|
|
80
107
|
|
|
81
108
|
describe("complete phase dispatch guard (#5683)", () => {
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
checkpointDatabase,
|
|
38
38
|
refreshOpenDatabaseFromDisk,
|
|
39
39
|
tryCreateMemoriesFts,
|
|
40
|
+
_isLikelyWslDrvFsPathForTest,
|
|
40
41
|
} from '../gsd-db.ts';
|
|
41
42
|
import { _resetLogs, peekLogs, setStderrLoggingEnabled } from '../workflow-logger.ts';
|
|
42
43
|
|
|
@@ -345,6 +346,16 @@ describe('gsd-db', () => {
|
|
|
345
346
|
});
|
|
346
347
|
});
|
|
347
348
|
|
|
349
|
+
test('gsd-db: detects WSL DrvFs mount paths for conservative pragmas', () => {
|
|
350
|
+
withPlatform('linux', () => {
|
|
351
|
+
assert.equal(_isLikelyWslDrvFsPathForTest('/mnt/d/code/project/.gsd/gsd.db'), true);
|
|
352
|
+
assert.equal(_isLikelyWslDrvFsPathForTest('/tmp/gsd.db'), false);
|
|
353
|
+
});
|
|
354
|
+
withPlatform('darwin', () => {
|
|
355
|
+
assert.equal(_isLikelyWslDrvFsPathForTest('/mnt/d/code/project/.gsd/gsd.db'), false);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
348
359
|
test('gsd-db: transaction rollback on error', () => {
|
|
349
360
|
openDatabase(':memory:');
|
|
350
361
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* gsdRoot() must return the canonical project .gsd directory when basePath
|
|
5
5
|
* is inside a .gsd/worktrees/<name>/ structure. Worktree-local .gsd folders
|
|
6
|
-
* are
|
|
6
|
+
* are projection roots; runtime/control state stays DB-authoritative at the
|
|
7
7
|
* project .gsd.
|
|
8
8
|
*
|
|
9
9
|
* The bug: when a git worktree lives at /project/.gsd/worktrees/M008/,
|
|
@@ -21,7 +21,7 @@ import { mkdtempSync, realpathSync } from "node:fs";
|
|
|
21
21
|
import { tmpdir } from "node:os";
|
|
22
22
|
import { spawnSync } from "node:child_process";
|
|
23
23
|
|
|
24
|
-
import { gsdRoot, resolveGsdPathContract, _clearGsdRootCache } from "../paths.ts";
|
|
24
|
+
import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, _clearGsdRootCache } from "../paths.ts";
|
|
25
25
|
|
|
26
26
|
describe("gsdRoot() worktree detection (#2594)", () => {
|
|
27
27
|
let projectRoot: string;
|
|
@@ -76,6 +76,7 @@ describe("gsdRoot() worktree detection (#2594)", () => {
|
|
|
76
76
|
`Expected canonical project .gsd (${projectGsd}), got ${result}.`,
|
|
77
77
|
);
|
|
78
78
|
assert.equal(resolveGsdPathContract(worktreeBase).worktreeGsd, worktreeGsd);
|
|
79
|
+
assert.equal(gsdProjectionRoot(worktreeBase), worktreeGsd);
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
test("returns project .gsd when worktree .gsd does not exist yet", () => {
|
|
@@ -89,6 +90,7 @@ describe("gsdRoot() worktree detection (#2594)", () => {
|
|
|
89
90
|
projectGsd,
|
|
90
91
|
`Expected canonical project .gsd (${projectGsd}), got ${result}.`,
|
|
91
92
|
);
|
|
93
|
+
assert.equal(gsdProjectionRoot(worktreeBase), join(worktreeBase, ".gsd"));
|
|
92
94
|
});
|
|
93
95
|
|
|
94
96
|
test("returns project .gsd when basePath is a real git worktree inside .gsd/worktrees/", () => {
|
|
@@ -116,6 +118,7 @@ describe("gsdRoot() worktree detection (#2594)", () => {
|
|
|
116
118
|
projectGsd,
|
|
117
119
|
`Expected canonical project .gsd (${projectGsd}), got ${gsdResult}`,
|
|
118
120
|
);
|
|
121
|
+
assert.equal(gsdProjectionRoot(worktreeBase), join(worktreeBase, ".gsd"));
|
|
119
122
|
|
|
120
123
|
// Cleanup worktree
|
|
121
124
|
spawnSync("git", ["worktree", "remove", "--force", worktreeBase], {
|
|
@@ -1168,6 +1168,15 @@ describe('git-service', async () => {
|
|
|
1168
1168
|
rmSync(repo, { recursive: true, force: true });
|
|
1169
1169
|
});
|
|
1170
1170
|
|
|
1171
|
+
test('Integration branch: rejects milestone branches', () => {
|
|
1172
|
+
const repo = initBranchTestRepo();
|
|
1173
|
+
|
|
1174
|
+
writeIntegrationBranch(repo, "M001", "milestone/M001");
|
|
1175
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "milestone branches are not recorded as integration branch");
|
|
1176
|
+
|
|
1177
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1171
1180
|
// ─── writeIntegrationBranch: still records legitimate branches ────────
|
|
1172
1181
|
|
|
1173
1182
|
test('Integration branch: records non-ephemeral gsd branches', () => {
|
|
@@ -22,6 +22,7 @@ import type { IterationContext, LoopState, PreDispatchData, IterationData } from
|
|
|
22
22
|
import type { SessionLockStatus } from "../session-lock.js";
|
|
23
23
|
import { runDispatch, runUnitPhase, runPreDispatch, runFinalize } from "../auto/phases.js";
|
|
24
24
|
import { readUnitRuntimeRecord } from "../unit-runtime.js";
|
|
25
|
+
import { ModelPolicyDispatchBlockedError } from "../auto-model-selection.js";
|
|
25
26
|
import {
|
|
26
27
|
closeDatabase,
|
|
27
28
|
insertMilestone,
|
|
@@ -160,6 +161,8 @@ function makeIC(
|
|
|
160
161
|
pi: {
|
|
161
162
|
sendMessage: () => {},
|
|
162
163
|
setModel: async () => true,
|
|
164
|
+
getThinkingLevel: () => "off",
|
|
165
|
+
setThinkingLevel: () => {},
|
|
163
166
|
} as any,
|
|
164
167
|
s: makeSession(),
|
|
165
168
|
deps,
|
|
@@ -868,6 +871,49 @@ test("runUnitPhase increments unitDispatchCount for repeated artifact-missing re
|
|
|
868
871
|
assert.equal(ic.s.unitDispatchCount.get("execute-task/M001/S01/T01"), 2);
|
|
869
872
|
});
|
|
870
873
|
|
|
874
|
+
test("runUnitPhase pre-dispatch model validation failures do not emit unit-start or dispatch runtime state", async (t) => {
|
|
875
|
+
const capture = createEventCapture();
|
|
876
|
+
const base = mkdtempSync(join(tmpdir(), `gsd-pre-dispatch-block-${randomUUID()}`));
|
|
877
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
878
|
+
|
|
879
|
+
const deps = makeMockDeps(capture, {
|
|
880
|
+
selectAndApplyModel: async () => {
|
|
881
|
+
throw new ModelPolicyDispatchBlockedError("execute-task", "M001/S01/T01", []);
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
const ic = makeIC(deps, {
|
|
885
|
+
s: {
|
|
886
|
+
...makeSession(),
|
|
887
|
+
basePath: base,
|
|
888
|
+
} as any,
|
|
889
|
+
});
|
|
890
|
+
const iterData: IterationData = {
|
|
891
|
+
unitType: "execute-task",
|
|
892
|
+
unitId: "M001/S01/T01",
|
|
893
|
+
prompt: "do stuff",
|
|
894
|
+
finalPrompt: "do stuff",
|
|
895
|
+
pauseAfterUatDispatch: false,
|
|
896
|
+
state: { phase: "executing", activeMilestone: { id: "M001" }, activeSlice: { id: "S01" }, registry: [], blockers: [] } as any,
|
|
897
|
+
mid: "M001",
|
|
898
|
+
midTitle: "Test",
|
|
899
|
+
isRetry: false,
|
|
900
|
+
previousTier: undefined,
|
|
901
|
+
};
|
|
902
|
+
const loopState: LoopState = { recentUnits: [{ key: "execute-task/M001/S01/T01" }], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 };
|
|
903
|
+
|
|
904
|
+
await assert.rejects(() => runUnitPhase(ic, iterData, loopState), ModelPolicyDispatchBlockedError);
|
|
905
|
+
await assert.rejects(() => runUnitPhase(ic, iterData, loopState), ModelPolicyDispatchBlockedError);
|
|
906
|
+
|
|
907
|
+
const startEvents = capture.events.filter(e => e.eventType === "unit-start");
|
|
908
|
+
assert.equal(startEvents.length, 0, "pre-dispatch validation failures must not emit unit-start");
|
|
909
|
+
assert.equal(ic.s.unitDispatchCount.get("execute-task/M001/S01/T01") ?? 0, 0, "dispatch count must not increment on pre-dispatch validation failure");
|
|
910
|
+
assert.equal(
|
|
911
|
+
readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01"),
|
|
912
|
+
null,
|
|
913
|
+
"pre-dispatch validation failures must not persist a dispatched runtime record",
|
|
914
|
+
);
|
|
915
|
+
});
|
|
916
|
+
|
|
871
917
|
test("all events from a mock iteration have monotonically increasing seq and same flowId", async () => {
|
|
872
918
|
const capture = createEventCapture();
|
|
873
919
|
const { resolveAgentEnd, _resetPendingResolve } = await import("../auto/resolve.js");
|
|
@@ -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'));
|