gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.bbb5216
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/bundled-resource-path.d.ts +8 -0
- package/dist/bundled-resource-path.js +14 -0
- package/dist/headless-query.js +6 -6
- package/dist/resources/extensions/gsd/auto/session.js +27 -32
- package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
- package/dist/resources/extensions/gsd/auto-loop.js +956 -0
- package/dist/resources/extensions/gsd/auto-observability.js +4 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
- package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
- package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
- package/dist/resources/extensions/gsd/auto-start.js +330 -309
- package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
- package/dist/resources/extensions/gsd/auto-timers.js +3 -4
- package/dist/resources/extensions/gsd/auto-verification.js +35 -73
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
- package/dist/resources/extensions/gsd/auto.js +283 -1013
- package/dist/resources/extensions/gsd/captures.js +10 -4
- package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
- package/dist/resources/extensions/gsd/git-service.js +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +296 -151
- package/dist/resources/extensions/gsd/index.js +92 -228
- package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
- package/dist/resources/extensions/gsd/progress-score.js +61 -156
- package/dist/resources/extensions/gsd/quick.js +98 -122
- package/dist/resources/extensions/gsd/session-lock.js +13 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/undo.js +43 -48
- package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
- package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
- package/dist/resources/extensions/gsd/verification-gate.js +6 -35
- package/dist/resources/extensions/gsd/worktree-command.js +30 -24
- package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
- package/dist/resources/extensions/gsd/worktree.js +7 -44
- package/dist/tool-bootstrap.js +59 -11
- package/dist/worktree-cli.js +7 -7
- package/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +735 -2588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/src/models.generated.ts +1039 -2892
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/session.ts +47 -30
- package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
- package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
- package/src/resources/extensions/gsd/auto-observability.ts +4 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
- package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
- package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
- package/src/resources/extensions/gsd/auto-start.ts +440 -354
- package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
- package/src/resources/extensions/gsd/auto-timers.ts +3 -4
- package/src/resources/extensions/gsd/auto-verification.ts +76 -90
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
- package/src/resources/extensions/gsd/auto.ts +515 -1199
- package/src/resources/extensions/gsd/captures.ts +10 -4
- package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
- package/src/resources/extensions/gsd/git-service.ts +8 -1
- package/src/resources/extensions/gsd/gitignore.ts +4 -2
- package/src/resources/extensions/gsd/gsd-db.ts +375 -180
- package/src/resources/extensions/gsd/index.ts +104 -263
- package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
- package/src/resources/extensions/gsd/progress-score.ts +65 -200
- package/src/resources/extensions/gsd/quick.ts +121 -125
- package/src/resources/extensions/gsd/session-lock.ts +11 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
- package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
- package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
- package/src/resources/extensions/gsd/types.ts +90 -81
- package/src/resources/extensions/gsd/undo.ts +42 -46
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
- package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
- package/src/resources/extensions/gsd/verification-gate.ts +6 -39
- package/src/resources/extensions/gsd/worktree-command.ts +36 -24
- package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
- package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
- package/src/resources/extensions/gsd/worktree.ts +7 -44
- package/dist/resources/extensions/gsd/auto-constants.js +0 -5
- package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
- package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
- package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
- package/src/resources/extensions/gsd/auto-constants.ts +0 -6
- package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
- package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
- package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
- package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
- package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
- package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
- package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
- package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
|
@@ -149,6 +149,16 @@ function ensureExitHandler(gsdDir: string): void {
|
|
|
149
149
|
export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
150
150
|
const lp = lockPath(basePath);
|
|
151
151
|
|
|
152
|
+
// Re-entrant acquire on the same path: release our current OS lock first so
|
|
153
|
+
// proper-lockfile clears its update timer before we acquire a fresh lock.
|
|
154
|
+
if (_releaseFunction && _lockedPath === basePath) {
|
|
155
|
+
try { _releaseFunction(); } catch { /* may already be released */ }
|
|
156
|
+
_releaseFunction = null;
|
|
157
|
+
_lockedPath = null;
|
|
158
|
+
_lockPid = 0;
|
|
159
|
+
_lockCompromised = false;
|
|
160
|
+
}
|
|
161
|
+
|
|
152
162
|
// Ensure the directory exists
|
|
153
163
|
mkdirSync(dirname(lp), { recursive: true });
|
|
154
164
|
|
|
@@ -234,6 +244,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
234
244
|
_releaseFunction = release;
|
|
235
245
|
_lockedPath = basePath;
|
|
236
246
|
_lockPid = process.pid;
|
|
247
|
+
_lockCompromised = false;
|
|
237
248
|
|
|
238
249
|
// Safety net — uses centralized handler to avoid double-registration
|
|
239
250
|
ensureExitHandler(gsdDir);
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* agent-end-retry.test.ts —
|
|
2
|
+
* agent-end-retry.test.ts — Regression checks for the post-#1419 agent_end model.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Instead, it should queue a retry via pendingAgentEndRetry so the completed
|
|
8
|
-
* unit's agent_end is processed after the current handler finishes.
|
|
9
|
-
*
|
|
10
|
-
* Without this, auto-mode can stall permanently in the "summarizing" phase
|
|
11
|
-
* with no unit running and no watchdog set.
|
|
4
|
+
* The old recursive handleAgentEnd retry path is gone. The loop now keeps
|
|
5
|
+
* pendingResolve + pendingAgentEndQueue on AutoSession, and handleAgentEnd is
|
|
6
|
+
* only a thin compatibility wrapper around resolveAgentEnd().
|
|
12
7
|
*/
|
|
13
8
|
|
|
14
9
|
import test from "node:test";
|
|
@@ -29,79 +24,57 @@ function getSessionTsSource(): string {
|
|
|
29
24
|
return readFileSync(SESSION_TS_PATH, "utf-8");
|
|
30
25
|
}
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
test("AutoSession declares pendingAgentEndRetry field", () => {
|
|
27
|
+
test("AutoSession declares pending agent_end queue state", () => {
|
|
35
28
|
const source = getSessionTsSource();
|
|
36
29
|
assert.ok(
|
|
37
|
-
source.includes("
|
|
38
|
-
"AutoSession
|
|
30
|
+
source.includes("pendingResolve"),
|
|
31
|
+
"AutoSession must declare pendingResolve for the in-flight unit promise",
|
|
32
|
+
);
|
|
33
|
+
assert.ok(
|
|
34
|
+
source.includes("pendingAgentEndQueue"),
|
|
35
|
+
"AutoSession must declare pendingAgentEndQueue for between-iteration agent_end events",
|
|
39
36
|
);
|
|
40
37
|
});
|
|
41
38
|
|
|
42
|
-
test("AutoSession
|
|
39
|
+
test("AutoSession reset clears pending agent_end queue state", () => {
|
|
43
40
|
const source = getSessionTsSource();
|
|
44
|
-
// Find the reset() method — it's declared as "reset(): void {"
|
|
45
41
|
const resetIdx = source.indexOf("reset(): void");
|
|
46
42
|
assert.ok(resetIdx > -1, "AutoSession must have a reset() method");
|
|
47
|
-
const resetBlock = source.slice(resetIdx, resetIdx +
|
|
43
|
+
const resetBlock = source.slice(resetIdx, resetIdx + 4000);
|
|
48
44
|
assert.ok(
|
|
49
|
-
resetBlock.includes("
|
|
50
|
-
"reset() must clear
|
|
45
|
+
resetBlock.includes("this.pendingResolve = null"),
|
|
46
|
+
"reset() must clear pendingResolve",
|
|
51
47
|
);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// ── handleAgentEnd reentrancy guard must queue retry ─────────────────────────
|
|
55
|
-
|
|
56
|
-
test("handleAgentEnd sets pendingAgentEndRetry when reentrant", () => {
|
|
57
|
-
const source = getAutoTsSource();
|
|
58
|
-
// Find the handleAgentEnd function
|
|
59
|
-
const fnIdx = source.indexOf("export async function handleAgentEnd");
|
|
60
|
-
assert.ok(fnIdx > -1, "handleAgentEnd must exist in auto.ts");
|
|
61
|
-
|
|
62
|
-
// The reentrancy guard section (within ~500 chars of the function start)
|
|
63
|
-
const guardBlock = source.slice(fnIdx, fnIdx + 800);
|
|
64
48
|
assert.ok(
|
|
65
|
-
|
|
66
|
-
"
|
|
49
|
+
resetBlock.includes("this.pendingAgentEndQueue = []"),
|
|
50
|
+
"reset() must clear pendingAgentEndQueue",
|
|
67
51
|
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("legacy pendingAgentEndRetry state is gone", () => {
|
|
55
|
+
const source = getSessionTsSource();
|
|
68
56
|
assert.ok(
|
|
69
|
-
|
|
70
|
-
"
|
|
57
|
+
!source.includes("pendingAgentEndRetry"),
|
|
58
|
+
"AutoSession should no longer use legacy pendingAgentEndRetry state",
|
|
71
59
|
);
|
|
72
60
|
});
|
|
73
61
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
test("handleAgentEnd finally block retries if pendingAgentEndRetry is set", () => {
|
|
62
|
+
test("handleAgentEnd is a thin compatibility wrapper", () => {
|
|
77
63
|
const source = getAutoTsSource();
|
|
78
64
|
const fnIdx = source.indexOf("export async function handleAgentEnd");
|
|
79
|
-
assert.ok(fnIdx > -1, "handleAgentEnd must exist");
|
|
80
|
-
|
|
81
|
-
// Find the finally block within handleAgentEnd (search for the closing pattern)
|
|
65
|
+
assert.ok(fnIdx > -1, "handleAgentEnd must exist in auto.ts");
|
|
82
66
|
const fnBlock = source.slice(fnIdx, source.indexOf("\n// ─── ", fnIdx + 100));
|
|
67
|
+
|
|
83
68
|
assert.ok(
|
|
84
|
-
fnBlock.includes("
|
|
85
|
-
"handleAgentEnd
|
|
86
|
-
);
|
|
87
|
-
assert.ok(
|
|
88
|
-
fnBlock.includes("setImmediate"),
|
|
89
|
-
"deferred retry must use setImmediate to avoid stack overflow (#1072)",
|
|
69
|
+
fnBlock.includes("resolveAgentEnd("),
|
|
70
|
+
"handleAgentEnd must delegate to resolveAgentEnd",
|
|
90
71
|
);
|
|
91
72
|
assert.ok(
|
|
92
|
-
fnBlock.includes("
|
|
93
|
-
"
|
|
73
|
+
!fnBlock.includes("pendingAgentEndRetry"),
|
|
74
|
+
"handleAgentEnd must not use legacy retry state",
|
|
94
75
|
);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// ── Regression: reentrancy guard must NOT silently return ─────────────────────
|
|
98
|
-
|
|
99
|
-
test("reentrancy guard references issue #1072", () => {
|
|
100
|
-
const source = getAutoTsSource();
|
|
101
|
-
const fnIdx = source.indexOf("export async function handleAgentEnd");
|
|
102
|
-
const guardBlock = source.slice(fnIdx, fnIdx + 800);
|
|
103
76
|
assert.ok(
|
|
104
|
-
|
|
105
|
-
"
|
|
77
|
+
!fnBlock.includes("dispatchNextUnit"),
|
|
78
|
+
"handleAgentEnd must not dispatch recursively",
|
|
106
79
|
);
|
|
107
80
|
});
|
|
@@ -12,7 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
import test from "node:test";
|
|
14
14
|
import assert from "node:assert/strict";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
mkdtempSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
rmSync,
|
|
19
|
+
writeFileSync,
|
|
20
|
+
existsSync,
|
|
21
|
+
realpathSync,
|
|
22
|
+
readFileSync,
|
|
23
|
+
} from "node:fs";
|
|
16
24
|
import { join, dirname } from "node:path";
|
|
17
25
|
import { tmpdir } from "node:os";
|
|
18
26
|
import { execSync } from "node:child_process";
|
|
@@ -28,11 +36,17 @@ import {
|
|
|
28
36
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
37
|
|
|
30
38
|
function run(command: string, cwd: string): string {
|
|
31
|
-
return execSync(command, {
|
|
39
|
+
return execSync(command, {
|
|
40
|
+
cwd,
|
|
41
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
42
|
+
encoding: "utf-8",
|
|
43
|
+
}).trim();
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
function createTempRepo(): string {
|
|
35
|
-
const dir = realpathSync(
|
|
47
|
+
const dir = realpathSync(
|
|
48
|
+
mkdtempSync(join(tmpdir(), "gsd-all-complete-test-")),
|
|
49
|
+
);
|
|
36
50
|
run("git init", dir);
|
|
37
51
|
run("git config user.email test@test.com", dir);
|
|
38
52
|
run("git config user.name Test", dir);
|
|
@@ -63,41 +77,54 @@ function createMilestoneArtifacts(dir: string, mid: string): void {
|
|
|
63
77
|
|
|
64
78
|
// ─── Source-level: verify the merge code exists in the "all complete" path ────
|
|
65
79
|
|
|
66
|
-
test("auto
|
|
67
|
-
const
|
|
80
|
+
test("auto-loop 'all milestones complete' path merges before stopping (#962)", () => {
|
|
81
|
+
const loopSrc = readFileSync(join(__dirname, "..", "auto-loop.ts"), "utf-8");
|
|
82
|
+
const resolverSrc = readFileSync(
|
|
83
|
+
join(__dirname, "..", "worktree-resolver.ts"),
|
|
84
|
+
"utf-8",
|
|
85
|
+
);
|
|
68
86
|
|
|
69
87
|
// Find the "incomplete.length === 0" block
|
|
70
|
-
const incompleteIdx =
|
|
71
|
-
assert.ok(
|
|
88
|
+
const incompleteIdx = loopSrc.indexOf("incomplete.length === 0");
|
|
89
|
+
assert.ok(
|
|
90
|
+
incompleteIdx > -1,
|
|
91
|
+
"auto-loop.ts should have 'incomplete.length === 0' check",
|
|
92
|
+
);
|
|
72
93
|
|
|
73
94
|
// The merge call must appear BETWEEN the incomplete check and the stopAuto call.
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
const blockAfterIncomplete = loopSrc.slice(
|
|
96
|
+
incompleteIdx,
|
|
97
|
+
incompleteIdx + 3000,
|
|
98
|
+
);
|
|
76
99
|
|
|
77
100
|
assert.ok(
|
|
78
|
-
blockAfterIncomplete.includes("
|
|
79
|
-
"auto.ts should call
|
|
101
|
+
blockAfterIncomplete.includes("deps.resolver.mergeAndExit"),
|
|
102
|
+
"auto-loop.ts should call resolver.mergeAndExit in the 'all milestones complete' path",
|
|
80
103
|
);
|
|
81
104
|
|
|
82
105
|
// The merge should come before stopAuto in this block
|
|
83
|
-
const mergePos = blockAfterIncomplete.indexOf("
|
|
106
|
+
const mergePos = blockAfterIncomplete.indexOf("deps.resolver.mergeAndExit");
|
|
84
107
|
const stopPos = blockAfterIncomplete.indexOf("stopAuto");
|
|
85
108
|
assert.ok(
|
|
86
109
|
mergePos < stopPos,
|
|
87
|
-
"
|
|
110
|
+
"resolver.mergeAndExit should be called before stopAuto in the 'all complete' path",
|
|
88
111
|
);
|
|
89
112
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
113
|
+
const helperIdx = resolverSrc.indexOf("mergeAndExit(milestoneId");
|
|
114
|
+
assert.ok(
|
|
115
|
+
helperIdx > -1,
|
|
116
|
+
"WorktreeResolver.mergeAndExit helper should exist",
|
|
117
|
+
);
|
|
118
|
+
const helperBlock = resolverSrc.slice(helperIdx, helperIdx + 2600);
|
|
94
119
|
assert.ok(
|
|
95
|
-
helperBlock.includes("
|
|
96
|
-
|
|
120
|
+
helperBlock.includes('mode === "worktree"') ||
|
|
121
|
+
helperBlock.includes('mode: "worktree"'),
|
|
122
|
+
"WorktreeResolver.mergeAndExit should handle worktree mode",
|
|
97
123
|
);
|
|
98
124
|
assert.ok(
|
|
99
|
-
helperBlock.includes("
|
|
100
|
-
|
|
125
|
+
helperBlock.includes('mode === "branch"') ||
|
|
126
|
+
helperBlock.includes('mode: "branch"'),
|
|
127
|
+
"WorktreeResolver.mergeAndExit should handle branch mode",
|
|
101
128
|
);
|
|
102
129
|
});
|
|
103
130
|
|
|
@@ -124,23 +151,38 @@ test("single milestone worktree is merged to main when all complete (#962)", ()
|
|
|
124
151
|
run('git commit -m "feat(M001): add feature"', wt);
|
|
125
152
|
|
|
126
153
|
// Simulate the fix: merge before stopping (what the "all complete" path now does)
|
|
127
|
-
const roadmapPath = join(
|
|
154
|
+
const roadmapPath = join(
|
|
155
|
+
tempDir,
|
|
156
|
+
".gsd",
|
|
157
|
+
"milestones",
|
|
158
|
+
"M001",
|
|
159
|
+
"M001-ROADMAP.md",
|
|
160
|
+
);
|
|
128
161
|
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
129
162
|
const mergeResult = mergeMilestoneToMain(tempDir, "M001", roadmapContent);
|
|
130
163
|
|
|
131
164
|
// Verify work is on main
|
|
132
|
-
assert.ok(
|
|
165
|
+
assert.ok(
|
|
166
|
+
existsSync(join(tempDir, "feature.ts")),
|
|
167
|
+
"feature.ts should be on main after merge",
|
|
168
|
+
);
|
|
133
169
|
assert.equal(process.cwd(), tempDir, "cwd restored to project root");
|
|
134
170
|
assert.ok(!isInAutoWorktree(tempDir), "no longer in auto-worktree");
|
|
135
171
|
assert.equal(getAutoWorktreeOriginalBase(), null, "originalBase cleared");
|
|
136
172
|
|
|
137
173
|
// Verify milestone branch was cleaned up
|
|
138
174
|
const branches = run("git branch", tempDir);
|
|
139
|
-
assert.ok(
|
|
175
|
+
assert.ok(
|
|
176
|
+
!branches.includes("milestone/M001"),
|
|
177
|
+
"milestone branch should be deleted",
|
|
178
|
+
);
|
|
140
179
|
|
|
141
180
|
// Verify squash commit on main
|
|
142
181
|
const log = run("git log --oneline -3", tempDir);
|
|
143
|
-
assert.ok(
|
|
182
|
+
assert.ok(
|
|
183
|
+
log.includes("M001"),
|
|
184
|
+
"squash commit on main should reference M001",
|
|
185
|
+
);
|
|
144
186
|
|
|
145
187
|
assert.ok(mergeResult.commitMessage.length > 0, "commit message returned");
|
|
146
188
|
} finally {
|
|
@@ -171,7 +213,10 @@ test("last milestone worktree is merged when it's the final one (#962)", () => {
|
|
|
171
213
|
writeFileSync(join(wt1, "m001-work.ts"), "export const m001 = true;\n");
|
|
172
214
|
run("git add .", wt1);
|
|
173
215
|
run('git commit -m "feat(M001): m001 work"', wt1);
|
|
174
|
-
const roadmap1 = readFileSync(
|
|
216
|
+
const roadmap1 = readFileSync(
|
|
217
|
+
join(tempDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
|
|
218
|
+
"utf-8",
|
|
219
|
+
);
|
|
175
220
|
mergeMilestoneToMain(tempDir, "M001", roadmap1);
|
|
176
221
|
|
|
177
222
|
// Now complete M002 (the LAST milestone — this is the #962 scenario)
|
|
@@ -179,7 +224,10 @@ test("last milestone worktree is merged when it's the final one (#962)", () => {
|
|
|
179
224
|
writeFileSync(join(wt2, "m002-work.ts"), "export const m002 = true;\n");
|
|
180
225
|
run("git add .", wt2);
|
|
181
226
|
run('git commit -m "feat(M002): m002 work"', wt2);
|
|
182
|
-
const roadmap2 = readFileSync(
|
|
227
|
+
const roadmap2 = readFileSync(
|
|
228
|
+
join(tempDir, ".gsd", "milestones", "M002", "M002-ROADMAP.md"),
|
|
229
|
+
"utf-8",
|
|
230
|
+
);
|
|
183
231
|
mergeMilestoneToMain(tempDir, "M002", roadmap2);
|
|
184
232
|
|
|
185
233
|
// Both features should now be on main
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
getBudgetAlertLevel,
|
|
6
6
|
getBudgetEnforcementAction,
|
|
7
7
|
getNewBudgetAlertLevel,
|
|
8
|
-
} from "../auto
|
|
8
|
+
} from "../auto.js";
|
|
9
9
|
|
|
10
10
|
test("getBudgetAlertLevel returns the expected threshold bucket", () => {
|
|
11
11
|
assert.equal(getBudgetAlertLevel(0.10), 0);
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { mkdirSync, mkdtempSync, writeFileSync, existsSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import { tmpdir } from "node:os";
|
|
6
7
|
|
|
7
8
|
import { writeLock, readCrashLock, clearLock, isLockProcessAlive } from "../crash-recovery.ts";
|
|
9
|
+
import { acquireSessionLock, releaseSessionLock } from "../session-lock.ts";
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
|
|
13
|
+
function hasProperLockfile(): boolean {
|
|
14
|
+
try {
|
|
15
|
+
require("proper-lockfile");
|
|
16
|
+
return true;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const properLockfileAvailable = hasProperLockfile();
|
|
8
23
|
|
|
9
24
|
// ─── writeLock creates auto.lock in .gsd/ ────────────────────────────────
|
|
10
25
|
|
|
@@ -95,6 +110,28 @@ test("clearLock is safe when no lock file exists", () => {
|
|
|
95
110
|
rmSync(dir, { recursive: true, force: true });
|
|
96
111
|
});
|
|
97
112
|
|
|
113
|
+
test("bootstrap cleanup releases session lock artifacts", () => {
|
|
114
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-lock-test-"));
|
|
115
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const result = acquireSessionLock(dir);
|
|
119
|
+
assert.equal(result.acquired, true, "session lock should be acquired");
|
|
120
|
+
assert.ok(existsSync(join(dir, ".gsd", "auto.lock")), "auto.lock should exist while lock is held");
|
|
121
|
+
if (properLockfileAvailable) {
|
|
122
|
+
assert.ok(existsSync(join(dir, ".gsd.lock")), ".gsd.lock should exist while lock is held");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
releaseSessionLock(dir);
|
|
126
|
+
clearLock(dir);
|
|
127
|
+
|
|
128
|
+
assert.ok(!existsSync(join(dir, ".gsd", "auto.lock")), "auto.lock should be removed by bootstrap cleanup");
|
|
129
|
+
assert.ok(!existsSync(join(dir, ".gsd.lock")), ".gsd.lock should be removed by bootstrap cleanup");
|
|
130
|
+
} finally {
|
|
131
|
+
rmSync(dir, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
98
135
|
// ─── isLockProcessAlive detects live vs dead PIDs ────────────────────────
|
|
99
136
|
|
|
100
137
|
test("isLockProcessAlive returns false for dead PID", () => {
|