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.
Files changed (135) hide show
  1. package/dist/bundled-resource-path.d.ts +8 -0
  2. package/dist/bundled-resource-path.js +14 -0
  3. package/dist/headless-query.js +6 -6
  4. package/dist/resources/extensions/gsd/auto/session.js +27 -32
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
  8. package/dist/resources/extensions/gsd/auto-loop.js +956 -0
  9. package/dist/resources/extensions/gsd/auto-observability.js +4 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
  11. package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
  12. package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
  13. package/dist/resources/extensions/gsd/auto-start.js +330 -309
  14. package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
  15. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
  16. package/dist/resources/extensions/gsd/auto-timers.js +3 -4
  17. package/dist/resources/extensions/gsd/auto-verification.js +35 -73
  18. package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
  19. package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
  20. package/dist/resources/extensions/gsd/auto.js +283 -1013
  21. package/dist/resources/extensions/gsd/captures.js +10 -4
  22. package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  24. package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
  25. package/dist/resources/extensions/gsd/git-service.js +1 -1
  26. package/dist/resources/extensions/gsd/gsd-db.js +296 -151
  27. package/dist/resources/extensions/gsd/index.js +92 -228
  28. package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
  29. package/dist/resources/extensions/gsd/progress-score.js +61 -156
  30. package/dist/resources/extensions/gsd/quick.js +98 -122
  31. package/dist/resources/extensions/gsd/session-lock.js +13 -0
  32. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  33. package/dist/resources/extensions/gsd/undo.js +43 -48
  34. package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
  35. package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
  36. package/dist/resources/extensions/gsd/verification-gate.js +6 -35
  37. package/dist/resources/extensions/gsd/worktree-command.js +30 -24
  38. package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
  39. package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
  40. package/dist/resources/extensions/gsd/worktree.js +7 -44
  41. package/dist/tool-bootstrap.js +59 -11
  42. package/dist/worktree-cli.js +7 -7
  43. package/package.json +1 -1
  44. package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
  45. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/models.generated.js +735 -2588
  47. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  48. package/packages/pi-ai/src/models.generated.ts +1039 -2892
  49. package/packages/pi-coding-agent/package.json +1 -1
  50. package/pkg/package.json +1 -1
  51. package/src/resources/extensions/gsd/auto/session.ts +47 -30
  52. package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
  53. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
  54. package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
  55. package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
  56. package/src/resources/extensions/gsd/auto-observability.ts +4 -2
  57. package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
  58. package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
  59. package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
  60. package/src/resources/extensions/gsd/auto-start.ts +440 -354
  61. package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
  62. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
  63. package/src/resources/extensions/gsd/auto-timers.ts +3 -4
  64. package/src/resources/extensions/gsd/auto-verification.ts +76 -90
  65. package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
  66. package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
  67. package/src/resources/extensions/gsd/auto.ts +515 -1199
  68. package/src/resources/extensions/gsd/captures.ts +10 -4
  69. package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
  70. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  71. package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
  72. package/src/resources/extensions/gsd/git-service.ts +8 -1
  73. package/src/resources/extensions/gsd/gitignore.ts +4 -2
  74. package/src/resources/extensions/gsd/gsd-db.ts +375 -180
  75. package/src/resources/extensions/gsd/index.ts +104 -263
  76. package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
  77. package/src/resources/extensions/gsd/progress-score.ts +65 -200
  78. package/src/resources/extensions/gsd/quick.ts +121 -125
  79. package/src/resources/extensions/gsd/session-lock.ts +11 -0
  80. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  81. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
  82. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
  83. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  84. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
  85. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
  86. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
  87. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
  88. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
  89. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
  90. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  91. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
  92. package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
  93. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
  94. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
  95. package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
  96. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
  97. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
  98. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
  99. package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
  100. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
  101. package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
  102. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
  103. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
  104. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  105. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  106. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
  107. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
  108. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
  109. package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
  110. package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
  111. package/src/resources/extensions/gsd/types.ts +90 -81
  112. package/src/resources/extensions/gsd/undo.ts +42 -46
  113. package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
  114. package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
  115. package/src/resources/extensions/gsd/verification-gate.ts +6 -39
  116. package/src/resources/extensions/gsd/worktree-command.ts +36 -24
  117. package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
  118. package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
  119. package/src/resources/extensions/gsd/worktree.ts +7 -44
  120. package/dist/resources/extensions/gsd/auto-constants.js +0 -5
  121. package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
  122. package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
  123. package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
  124. package/src/resources/extensions/gsd/auto-constants.ts +0 -6
  125. package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
  126. package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
  127. package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
  128. package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
  129. package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
  130. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
  131. package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
  132. package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
  133. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
  134. package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
  135. package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
@@ -1,434 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { mkdirSync, mkdtempSync, writeFileSync, existsSync, readFileSync, rmSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { tmpdir } from "node:os";
6
-
7
- import {
8
- acquireSessionLock,
9
- releaseSessionLock,
10
- updateSessionLock,
11
- validateSessionLock,
12
- readSessionLockData,
13
- isSessionLockHeld,
14
- isSessionLockProcessAlive,
15
- cleanupStrayLockFiles,
16
- } from "../session-lock.ts";
17
-
18
- // ─── acquireSessionLock ──────────────────────────────────────────────────
19
-
20
- test("acquireSessionLock succeeds on empty directory", () => {
21
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
22
- mkdirSync(join(dir, ".gsd"), { recursive: true });
23
-
24
- const result = acquireSessionLock(dir);
25
- assert.equal(result.acquired, true, "should acquire lock on empty dir");
26
-
27
- // Verify lock file was created with correct data
28
- const lockPath = join(dir, ".gsd", "auto.lock");
29
- assert.ok(existsSync(lockPath), "auto.lock should exist after acquire");
30
-
31
- const data = JSON.parse(readFileSync(lockPath, "utf-8"));
32
- assert.equal(data.pid, process.pid, "lock should contain current PID");
33
- assert.equal(data.unitType, "starting", "initial unit type should be 'starting'");
34
-
35
- releaseSessionLock(dir);
36
- rmSync(dir, { recursive: true, force: true });
37
- });
38
-
39
- test("acquireSessionLock rejects when another live process holds lock", () => {
40
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
41
- mkdirSync(join(dir, ".gsd"), { recursive: true });
42
-
43
- // Simulate another process holding the lock by writing a lock with parent PID
44
- const fakeLockData = {
45
- pid: process.ppid,
46
- startedAt: new Date().toISOString(),
47
- unitType: "execute-task",
48
- unitId: "M001/S01/T01",
49
- unitStartedAt: new Date().toISOString(),
50
- completedUnits: 2,
51
- };
52
- writeFileSync(join(dir, ".gsd", "auto.lock"), JSON.stringify(fakeLockData, null, 2));
53
-
54
- // First acquire to set up proper-lockfile state
55
- const result1 = acquireSessionLock(dir);
56
-
57
- // If proper-lockfile is available, it should manage the OS lock.
58
- // If not (fallback mode), the PID check should detect the live process.
59
- // Either way, we can't fully simulate another process holding an OS lock
60
- // from within the same process, so we test the fallback path.
61
- if (result1.acquired) {
62
- // We got the lock (proper-lockfile saw no OS lock from another process)
63
- // This is expected since we're in the same process
64
- releaseSessionLock(dir);
65
- }
66
-
67
- rmSync(dir, { recursive: true, force: true });
68
- });
69
-
70
- test("acquireSessionLock takes over stale lock from dead process", () => {
71
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
72
- mkdirSync(join(dir, ".gsd"), { recursive: true });
73
-
74
- // Write a lock from a dead process
75
- const staleLockData = {
76
- pid: 9999999,
77
- startedAt: "2026-03-01T00:00:00Z",
78
- unitType: "execute-task",
79
- unitId: "M001/S01/T01",
80
- unitStartedAt: "2026-03-01T00:00:00Z",
81
- completedUnits: 0,
82
- };
83
- writeFileSync(join(dir, ".gsd", "auto.lock"), JSON.stringify(staleLockData, null, 2));
84
-
85
- const result = acquireSessionLock(dir);
86
- assert.equal(result.acquired, true, "should take over lock from dead process");
87
-
88
- // Verify our PID is now in the lock
89
- const data = readSessionLockData(dir);
90
- assert.ok(data, "lock data should exist after acquire");
91
- assert.equal(data!.pid, process.pid, "lock should contain our PID now");
92
-
93
- releaseSessionLock(dir);
94
- rmSync(dir, { recursive: true, force: true });
95
- });
96
-
97
- // ─── releaseSessionLock ─────────────────────────────────────────────────
98
-
99
- test("releaseSessionLock removes the lock file", () => {
100
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
101
- mkdirSync(join(dir, ".gsd"), { recursive: true });
102
-
103
- const result = acquireSessionLock(dir);
104
- assert.equal(result.acquired, true);
105
-
106
- releaseSessionLock(dir);
107
-
108
- const lockPath = join(dir, ".gsd", "auto.lock");
109
- assert.ok(!existsSync(lockPath), "auto.lock should be removed after release");
110
-
111
- rmSync(dir, { recursive: true, force: true });
112
- });
113
-
114
- test("releaseSessionLock is safe when no lock exists", () => {
115
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
116
- mkdirSync(join(dir, ".gsd"), { recursive: true });
117
-
118
- // Should not throw
119
- releaseSessionLock(dir);
120
-
121
- rmSync(dir, { recursive: true, force: true });
122
- });
123
-
124
- // ─── updateSessionLock ──────────────────────────────────────────────────
125
-
126
- test("updateSessionLock updates the lock data without re-acquiring", () => {
127
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
128
- mkdirSync(join(dir, ".gsd"), { recursive: true });
129
-
130
- const result = acquireSessionLock(dir);
131
- assert.equal(result.acquired, true);
132
-
133
- updateSessionLock(dir, "execute-task", "M001/S01/T02", 3, "/tmp/session.jsonl");
134
-
135
- const data = readSessionLockData(dir);
136
- assert.ok(data, "lock data should exist after update");
137
- assert.equal(data!.pid, process.pid, "PID should still be ours");
138
- assert.equal(data!.unitType, "execute-task", "unit type should be updated");
139
- assert.equal(data!.unitId, "M001/S01/T02", "unit ID should be updated");
140
- assert.equal(data!.completedUnits, 3, "completed count should be updated");
141
- assert.equal(data!.sessionFile, "/tmp/session.jsonl", "session file should be recorded");
142
-
143
- releaseSessionLock(dir);
144
- rmSync(dir, { recursive: true, force: true });
145
- });
146
-
147
- // ─── validateSessionLock ────────────────────────────────────────────────
148
-
149
- test("validateSessionLock returns true when we hold the lock", () => {
150
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
151
- mkdirSync(join(dir, ".gsd"), { recursive: true });
152
-
153
- const result = acquireSessionLock(dir);
154
- assert.equal(result.acquired, true);
155
-
156
- assert.equal(validateSessionLock(dir), true, "should validate when we hold the lock");
157
-
158
- releaseSessionLock(dir);
159
- rmSync(dir, { recursive: true, force: true });
160
- });
161
-
162
- test("validateSessionLock returns false after release", () => {
163
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
164
- mkdirSync(join(dir, ".gsd"), { recursive: true });
165
-
166
- const result = acquireSessionLock(dir);
167
- assert.equal(result.acquired, true);
168
- assert.equal(validateSessionLock(dir), true, "should be valid while held");
169
-
170
- // Release the lock — both OS lock and lock file are removed
171
- releaseSessionLock(dir);
172
-
173
- // After release, _lockedPath is cleared and lock file is gone
174
- assert.equal(isSessionLockHeld(dir), false, "should not be held after release");
175
-
176
- rmSync(dir, { recursive: true, force: true });
177
- });
178
-
179
- test("validateSessionLock returns false when another PID owns the lock", () => {
180
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
181
- mkdirSync(join(dir, ".gsd"), { recursive: true });
182
-
183
- // Write lock data with a different PID (parent process)
184
- const foreignLockData = {
185
- pid: process.ppid,
186
- startedAt: new Date().toISOString(),
187
- unitType: "execute-task",
188
- unitId: "M001/S01/T01",
189
- unitStartedAt: new Date().toISOString(),
190
- completedUnits: 0,
191
- };
192
- writeFileSync(join(dir, ".gsd", "auto.lock"), JSON.stringify(foreignLockData, null, 2));
193
-
194
- // Without holding the OS lock, validate should check PID
195
- assert.equal(validateSessionLock(dir), false, "should fail when another PID owns lock");
196
-
197
- rmSync(dir, { recursive: true, force: true });
198
- });
199
-
200
- // ─── isSessionLockHeld ──────────────────────────────────────────────────
201
-
202
- test("isSessionLockHeld returns true after acquire", () => {
203
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
204
- mkdirSync(join(dir, ".gsd"), { recursive: true });
205
-
206
- acquireSessionLock(dir);
207
- assert.equal(isSessionLockHeld(dir), true);
208
-
209
- releaseSessionLock(dir);
210
- assert.equal(isSessionLockHeld(dir), false, "should return false after release");
211
-
212
- rmSync(dir, { recursive: true, force: true });
213
- });
214
-
215
- // ─── isSessionLockProcessAlive ──────────────────────────────────────────
216
-
217
- test("isSessionLockProcessAlive returns false for dead PID", () => {
218
- const data = {
219
- pid: 9999999,
220
- startedAt: new Date().toISOString(),
221
- unitType: "starting",
222
- unitId: "bootstrap",
223
- unitStartedAt: new Date().toISOString(),
224
- completedUnits: 0,
225
- };
226
- assert.equal(isSessionLockProcessAlive(data), false);
227
- });
228
-
229
- test("isSessionLockProcessAlive returns false for own PID (recycled)", () => {
230
- const data = {
231
- pid: process.pid,
232
- startedAt: new Date().toISOString(),
233
- unitType: "starting",
234
- unitId: "bootstrap",
235
- unitStartedAt: new Date().toISOString(),
236
- completedUnits: 0,
237
- };
238
- // Own PID returns false because it means the lock is from a recycled PID
239
- assert.equal(isSessionLockProcessAlive(data), false);
240
- });
241
-
242
- // ─── readSessionLockData ────────────────────────────────────────────────
243
-
244
- test("readSessionLockData returns null when no lock exists", () => {
245
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
246
- mkdirSync(join(dir, ".gsd"), { recursive: true });
247
-
248
- const data = readSessionLockData(dir);
249
- assert.equal(data, null);
250
-
251
- rmSync(dir, { recursive: true, force: true });
252
- });
253
-
254
- test("readSessionLockData reads existing lock data", () => {
255
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
256
- mkdirSync(join(dir, ".gsd"), { recursive: true });
257
-
258
- const lockData = {
259
- pid: 12345,
260
- startedAt: "2026-03-18T00:00:00Z",
261
- unitType: "execute-task",
262
- unitId: "M001/S01/T01",
263
- unitStartedAt: "2026-03-18T00:01:00Z",
264
- completedUnits: 2,
265
- sessionFile: "/tmp/session.jsonl",
266
- };
267
- writeFileSync(join(dir, ".gsd", "auto.lock"), JSON.stringify(lockData, null, 2));
268
-
269
- const data = readSessionLockData(dir);
270
- assert.ok(data, "should read lock data");
271
- assert.equal(data!.pid, 12345);
272
- assert.equal(data!.unitType, "execute-task");
273
- assert.equal(data!.unitId, "M001/S01/T01");
274
- assert.equal(data!.completedUnits, 2);
275
- assert.equal(data!.sessionFile, "/tmp/session.jsonl");
276
-
277
- rmSync(dir, { recursive: true, force: true });
278
- });
279
-
280
- // ─── Acquire → Release → Re-Acquire lifecycle ──────────────────────────
281
-
282
- test("session lock supports acquire → release → re-acquire cycle", () => {
283
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
284
- mkdirSync(join(dir, ".gsd"), { recursive: true });
285
-
286
- // First acquire
287
- const r1 = acquireSessionLock(dir);
288
- assert.equal(r1.acquired, true, "first acquire should succeed");
289
- assert.equal(isSessionLockHeld(dir), true);
290
-
291
- // Release
292
- releaseSessionLock(dir);
293
- assert.equal(isSessionLockHeld(dir), false);
294
-
295
- // Re-acquire
296
- const r2 = acquireSessionLock(dir);
297
- assert.equal(r2.acquired, true, "re-acquire after release should succeed");
298
- assert.equal(isSessionLockHeld(dir), true);
299
-
300
- releaseSessionLock(dir);
301
- rmSync(dir, { recursive: true, force: true });
302
- });
303
-
304
- // ─── Lock creates .gsd/ directory if needed ─────────────────────────────
305
-
306
- test("acquireSessionLock creates .gsd/ if it does not exist", () => {
307
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
308
- // Do NOT create .gsd/ — let the lock function do it
309
-
310
- const result = acquireSessionLock(dir);
311
- assert.equal(result.acquired, true, "should succeed even without .gsd/");
312
- assert.ok(existsSync(join(dir, ".gsd")), ".gsd/ should be created");
313
-
314
- releaseSessionLock(dir);
315
- rmSync(dir, { recursive: true, force: true });
316
- });
317
-
318
- // ─── cleanupStrayLockFiles (#1315) ──────────────────────────────────────
319
-
320
- test("cleanupStrayLockFiles removes numbered lock variants but preserves auto.lock", () => {
321
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
322
- const gsdDir = join(dir, ".gsd");
323
- mkdirSync(gsdDir, { recursive: true });
324
-
325
- // Create canonical lock file + numbered variants
326
- writeFileSync(join(gsdDir, "auto.lock"), '{"pid":1}');
327
- writeFileSync(join(gsdDir, "auto 2.lock"), '{"pid":2}');
328
- writeFileSync(join(gsdDir, "auto 3.lock"), '{"pid":3}');
329
- writeFileSync(join(gsdDir, "auto 4.lock"), '{"pid":4}');
330
-
331
- cleanupStrayLockFiles(dir);
332
-
333
- assert.ok(existsSync(join(gsdDir, "auto.lock")), "canonical auto.lock should be preserved");
334
- assert.ok(!existsSync(join(gsdDir, "auto 2.lock")), "auto 2.lock should be removed");
335
- assert.ok(!existsSync(join(gsdDir, "auto 3.lock")), "auto 3.lock should be removed");
336
- assert.ok(!existsSync(join(gsdDir, "auto 4.lock")), "auto 4.lock should be removed");
337
-
338
- rmSync(dir, { recursive: true, force: true });
339
- });
340
-
341
- test("cleanupStrayLockFiles handles parenthesized variants", () => {
342
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
343
- const gsdDir = join(dir, ".gsd");
344
- mkdirSync(gsdDir, { recursive: true });
345
-
346
- // macOS sometimes uses parenthesized format: "auto (2).lock"
347
- writeFileSync(join(gsdDir, "auto.lock"), '{"pid":1}');
348
- writeFileSync(join(gsdDir, "auto (2).lock"), '{"pid":2}');
349
-
350
- cleanupStrayLockFiles(dir);
351
-
352
- assert.ok(existsSync(join(gsdDir, "auto.lock")), "canonical auto.lock should be preserved");
353
- assert.ok(!existsSync(join(gsdDir, "auto (2).lock")), "auto (2).lock should be removed");
354
-
355
- rmSync(dir, { recursive: true, force: true });
356
- });
357
-
358
- test("cleanupStrayLockFiles does not remove unrelated files", () => {
359
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
360
- const gsdDir = join(dir, ".gsd");
361
- mkdirSync(gsdDir, { recursive: true });
362
-
363
- // Create unrelated files that should NOT be removed
364
- writeFileSync(join(gsdDir, "auto.lock"), '{"pid":1}');
365
- writeFileSync(join(gsdDir, "config.json"), '{}');
366
- writeFileSync(join(gsdDir, "other.lock"), '{}');
367
-
368
- cleanupStrayLockFiles(dir);
369
-
370
- assert.ok(existsSync(join(gsdDir, "auto.lock")), "auto.lock should be preserved");
371
- assert.ok(existsSync(join(gsdDir, "config.json")), "config.json should be preserved");
372
- assert.ok(existsSync(join(gsdDir, "other.lock")), "other.lock should be preserved");
373
-
374
- rmSync(dir, { recursive: true, force: true });
375
- });
376
-
377
- test("cleanupStrayLockFiles is safe on empty directory", () => {
378
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
379
- const gsdDir = join(dir, ".gsd");
380
- mkdirSync(gsdDir, { recursive: true });
381
-
382
- // Should not throw
383
- cleanupStrayLockFiles(dir);
384
-
385
- rmSync(dir, { recursive: true, force: true });
386
- });
387
-
388
- test("cleanupStrayLockFiles is safe when .gsd/ does not exist", () => {
389
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
390
-
391
- // Should not throw even without .gsd/
392
- cleanupStrayLockFiles(dir);
393
-
394
- rmSync(dir, { recursive: true, force: true });
395
- });
396
-
397
- test("acquireSessionLock cleans stray lock files before acquiring", () => {
398
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
399
- const gsdDir = join(dir, ".gsd");
400
- mkdirSync(gsdDir, { recursive: true });
401
-
402
- // Plant stray lock files before acquire
403
- writeFileSync(join(gsdDir, "auto 2.lock"), '{"pid":9999999}');
404
- writeFileSync(join(gsdDir, "auto 3.lock"), '{"pid":9999998}');
405
-
406
- const result = acquireSessionLock(dir);
407
- assert.equal(result.acquired, true, "should acquire lock");
408
-
409
- // Stray files should be cleaned up
410
- assert.ok(!existsSync(join(gsdDir, "auto 2.lock")), "auto 2.lock should be removed during acquire");
411
- assert.ok(!existsSync(join(gsdDir, "auto 3.lock")), "auto 3.lock should be removed during acquire");
412
-
413
- releaseSessionLock(dir);
414
- rmSync(dir, { recursive: true, force: true });
415
- });
416
-
417
- test("releaseSessionLock cleans stray lock files after releasing", () => {
418
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-lock-"));
419
- const gsdDir = join(dir, ".gsd");
420
- mkdirSync(gsdDir, { recursive: true });
421
-
422
- const result = acquireSessionLock(dir);
423
- assert.equal(result.acquired, true);
424
-
425
- // Plant stray lock files (simulating cloud sync creating them during session)
426
- writeFileSync(join(gsdDir, "auto 2.lock"), '{"pid":9999999}');
427
-
428
- releaseSessionLock(dir);
429
-
430
- assert.ok(!existsSync(join(gsdDir, "auto 2.lock")), "auto 2.lock should be removed during release");
431
- assert.ok(!existsSync(join(gsdDir, "auto.lock")), "auto.lock should also be removed");
432
-
433
- rmSync(dir, { recursive: true, force: true });
434
- });