gsd-pi 2.32.0 → 2.33.0-dev.69bff0f
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 +22 -20
- package/dist/resource-loader.js +13 -3
- package/dist/resources/extensions/gsd/auto-dashboard.ts +3 -1
- package/dist/resources/extensions/gsd/auto-dispatch.ts +40 -12
- package/dist/resources/extensions/gsd/auto-idempotency.ts +3 -2
- package/dist/resources/extensions/gsd/auto-observability.ts +2 -4
- package/dist/resources/extensions/gsd/auto-post-unit.ts +5 -5
- package/dist/resources/extensions/gsd/auto-prompts.ts +46 -44
- package/dist/resources/extensions/gsd/auto-recovery.ts +8 -22
- package/dist/resources/extensions/gsd/auto-start.ts +8 -6
- package/dist/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
- package/dist/resources/extensions/gsd/auto-supervisor.ts +10 -5
- package/dist/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
- package/dist/resources/extensions/gsd/auto-timers.ts +3 -2
- package/dist/resources/extensions/gsd/auto-verification.ts +6 -6
- package/dist/resources/extensions/gsd/auto-worktree.ts +140 -5
- package/dist/resources/extensions/gsd/auto.ts +108 -182
- package/dist/resources/extensions/gsd/commands-inspect.ts +2 -1
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +2 -1
- package/dist/resources/extensions/gsd/commands.ts +14 -2
- package/dist/resources/extensions/gsd/complexity-classifier.ts +5 -7
- package/dist/resources/extensions/gsd/crash-recovery.ts +15 -2
- package/dist/resources/extensions/gsd/dispatch-guard.ts +2 -1
- package/dist/resources/extensions/gsd/error-utils.ts +6 -0
- package/dist/resources/extensions/gsd/export.ts +2 -1
- package/dist/resources/extensions/gsd/git-service.ts +3 -2
- package/dist/resources/extensions/gsd/guided-flow.ts +3 -2
- package/dist/resources/extensions/gsd/index.ts +12 -5
- package/dist/resources/extensions/gsd/key-manager.ts +2 -1
- package/dist/resources/extensions/gsd/marketplace-discovery.ts +4 -3
- package/dist/resources/extensions/gsd/metrics.ts +3 -3
- package/dist/resources/extensions/gsd/migrate-external.ts +21 -4
- package/dist/resources/extensions/gsd/milestone-ids.ts +2 -1
- package/dist/resources/extensions/gsd/native-git-bridge.ts +2 -1
- package/dist/resources/extensions/gsd/parallel-merge.ts +2 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +8 -9
- package/dist/resources/extensions/gsd/quick.ts +58 -3
- package/dist/resources/extensions/gsd/repo-identity.ts +22 -1
- package/dist/resources/extensions/gsd/session-lock.ts +86 -11
- package/dist/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +14 -11
- package/dist/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +691 -0
- package/dist/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +317 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/loop-regression.test.ts +877 -0
- package/dist/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +358 -0
- package/dist/resources/extensions/gsd/tests/session-lock-regression.test.ts +216 -0
- package/dist/resources/extensions/gsd/tests/session-lock.test.ts +119 -0
- package/dist/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +206 -0
- package/dist/resources/extensions/gsd/undo.ts +5 -7
- package/dist/resources/extensions/gsd/unit-id.ts +14 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +2 -1
- package/dist/resources/extensions/gsd/worktree-command.ts +8 -7
- package/package.json +3 -2
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +3 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +40 -12
- package/src/resources/extensions/gsd/auto-idempotency.ts +3 -2
- package/src/resources/extensions/gsd/auto-observability.ts +2 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +5 -5
- package/src/resources/extensions/gsd/auto-prompts.ts +46 -44
- package/src/resources/extensions/gsd/auto-recovery.ts +8 -22
- package/src/resources/extensions/gsd/auto-start.ts +8 -6
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
- package/src/resources/extensions/gsd/auto-supervisor.ts +10 -5
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
- package/src/resources/extensions/gsd/auto-timers.ts +3 -2
- package/src/resources/extensions/gsd/auto-verification.ts +6 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +140 -5
- package/src/resources/extensions/gsd/auto.ts +108 -182
- package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +2 -1
- package/src/resources/extensions/gsd/commands.ts +14 -2
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -7
- package/src/resources/extensions/gsd/crash-recovery.ts +15 -2
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
- package/src/resources/extensions/gsd/error-utils.ts +6 -0
- package/src/resources/extensions/gsd/export.ts +2 -1
- package/src/resources/extensions/gsd/git-service.ts +3 -2
- package/src/resources/extensions/gsd/guided-flow.ts +3 -2
- package/src/resources/extensions/gsd/index.ts +12 -5
- package/src/resources/extensions/gsd/key-manager.ts +2 -1
- package/src/resources/extensions/gsd/marketplace-discovery.ts +4 -3
- package/src/resources/extensions/gsd/metrics.ts +3 -3
- package/src/resources/extensions/gsd/migrate-external.ts +21 -4
- package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +2 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +2 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +8 -9
- package/src/resources/extensions/gsd/quick.ts +58 -3
- package/src/resources/extensions/gsd/repo-identity.ts +22 -1
- package/src/resources/extensions/gsd/session-lock.ts +86 -11
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +14 -11
- package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +691 -0
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +317 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/loop-regression.test.ts +877 -0
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +358 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +216 -0
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +206 -0
- package/src/resources/extensions/gsd/undo.ts +5 -7
- package/src/resources/extensions/gsd/unit-id.ts +14 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +2 -1
- package/src/resources/extensions/gsd/worktree-command.ts +8 -7
- package/dist/resources/extensions/mcporter/extension-manifest.json +0 -12
- package/src/resources/extensions/mcporter/extension-manifest.json +0 -12
|
@@ -10,7 +10,7 @@ import { createHash } from "node:crypto";
|
|
|
10
10
|
import { execFileSync } from "node:child_process";
|
|
11
11
|
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, symlinkSync } from "node:fs";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
|
-
import { join, resolve } from "node:path";
|
|
13
|
+
import { join, resolve, sep } from "node:path";
|
|
14
14
|
|
|
15
15
|
// ─── Repo Identity ──────────────────────────────────────────────────────────
|
|
16
16
|
|
|
@@ -37,6 +37,27 @@ function getRemoteUrl(basePath: string): string {
|
|
|
37
37
|
*/
|
|
38
38
|
function resolveGitRoot(basePath: string): string {
|
|
39
39
|
try {
|
|
40
|
+
// In a worktree, --show-toplevel returns the worktree path, not the main
|
|
41
|
+
// repo root. Use --git-common-dir to find the shared .git directory,
|
|
42
|
+
// then derive the main repo root from it (#1288).
|
|
43
|
+
const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
44
|
+
cwd: basePath,
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
47
|
+
timeout: 5_000,
|
|
48
|
+
}).trim();
|
|
49
|
+
|
|
50
|
+
// If commonDir ends with .git/worktrees/<name>, the main repo is two
|
|
51
|
+
// levels up from the worktrees dir. If it's just .git, resolve normally.
|
|
52
|
+
if (commonDir.includes(`${sep}worktrees${sep}`) || commonDir.includes("/worktrees/")) {
|
|
53
|
+
// e.g., /path/to/project/.gsd/worktrees/M001/.git → /path/to/project
|
|
54
|
+
// or /path/to/project/.git/worktrees/M001 → /path/to/project
|
|
55
|
+
const gitDir = commonDir.replace(/[/\\]worktrees[/\\][^/\\]+$/, "");
|
|
56
|
+
const mainRoot = resolve(gitDir, "..");
|
|
57
|
+
return mainRoot;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Not in a worktree — use --show-toplevel as usual
|
|
40
61
|
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
41
62
|
cwd: basePath,
|
|
42
63
|
encoding: "utf-8",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { createRequire } from "node:module";
|
|
20
|
-
import { existsSync, readFileSync, mkdirSync, unlinkSync, rmSync, statSync } from "node:fs";
|
|
20
|
+
import { existsSync, readFileSync, readdirSync, mkdirSync, unlinkSync, rmSync, statSync } from "node:fs";
|
|
21
21
|
import { join, dirname } from "node:path";
|
|
22
22
|
import { gsdRoot } from "./paths.js";
|
|
23
23
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
@@ -54,12 +54,81 @@ let _lockPid: number = 0;
|
|
|
54
54
|
/** Set to true when proper-lockfile fires onCompromised (mtime drift, sleep, etc.). */
|
|
55
55
|
let _lockCompromised: boolean = false;
|
|
56
56
|
|
|
57
|
+
/** Whether we've already registered a process.on('exit') handler. */
|
|
58
|
+
let _exitHandlerRegistered: boolean = false;
|
|
59
|
+
|
|
57
60
|
const LOCK_FILE = "auto.lock";
|
|
58
61
|
|
|
59
62
|
function lockPath(basePath: string): string {
|
|
60
63
|
return join(gsdRoot(basePath), LOCK_FILE);
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
// ─── Stray Lock Cleanup ─────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Remove numbered lock file variants (e.g. "auto 2.lock", "auto 3.lock")
|
|
70
|
+
* that accumulate from macOS file conflict resolution (iCloud/Dropbox/OneDrive)
|
|
71
|
+
* or other filesystem-level copy-on-conflict behavior (#1315).
|
|
72
|
+
*
|
|
73
|
+
* Also removes stray proper-lockfile directories beyond the canonical `.gsd.lock/`.
|
|
74
|
+
*/
|
|
75
|
+
export function cleanupStrayLockFiles(basePath: string): void {
|
|
76
|
+
const gsdDir = gsdRoot(basePath);
|
|
77
|
+
|
|
78
|
+
// Clean numbered auto lock files inside .gsd/
|
|
79
|
+
try {
|
|
80
|
+
if (existsSync(gsdDir)) {
|
|
81
|
+
for (const entry of readdirSync(gsdDir)) {
|
|
82
|
+
// Match "auto <N>.lock" or "auto (<N>).lock" variants but NOT the canonical "auto.lock"
|
|
83
|
+
if (entry !== LOCK_FILE && /^auto\s.+\.lock$/i.test(entry)) {
|
|
84
|
+
try { unlinkSync(join(gsdDir, entry)); } catch { /* best-effort */ }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch { /* non-fatal: directory read failure */ }
|
|
89
|
+
|
|
90
|
+
// Clean stray proper-lockfile directories (e.g. ".gsd 2.lock/")
|
|
91
|
+
// The canonical one is ".gsd.lock/" — anything else is stray.
|
|
92
|
+
try {
|
|
93
|
+
const parentDir = dirname(gsdDir);
|
|
94
|
+
const gsdDirName = gsdDir.split("/").pop() || ".gsd";
|
|
95
|
+
if (existsSync(parentDir)) {
|
|
96
|
+
for (const entry of readdirSync(parentDir)) {
|
|
97
|
+
// Match ".gsd <N>.lock" or ".gsd (<N>).lock" directories but NOT ".gsd.lock"
|
|
98
|
+
if (entry !== `${gsdDirName}.lock` && entry.startsWith(gsdDirName) && entry.endsWith(".lock")) {
|
|
99
|
+
const fullPath = join(parentDir, entry);
|
|
100
|
+
try {
|
|
101
|
+
const stat = statSync(fullPath);
|
|
102
|
+
if (stat.isDirectory()) {
|
|
103
|
+
rmSync(fullPath, { recursive: true, force: true });
|
|
104
|
+
}
|
|
105
|
+
} catch { /* best-effort */ }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch { /* non-fatal */ }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Register a single process exit handler that cleans up lock state.
|
|
114
|
+
* Uses module-level references so it always operates on current state.
|
|
115
|
+
* Only registers once — subsequent calls are no-ops.
|
|
116
|
+
*/
|
|
117
|
+
function ensureExitHandler(gsdDir: string): void {
|
|
118
|
+
if (_exitHandlerRegistered) return;
|
|
119
|
+
_exitHandlerRegistered = true;
|
|
120
|
+
|
|
121
|
+
process.once("exit", () => {
|
|
122
|
+
try {
|
|
123
|
+
if (_releaseFunction) { _releaseFunction(); _releaseFunction = null; }
|
|
124
|
+
} catch { /* best-effort */ }
|
|
125
|
+
try {
|
|
126
|
+
const lockDir = join(gsdDir + ".lock");
|
|
127
|
+
if (existsSync(lockDir)) rmSync(lockDir, { recursive: true, force: true });
|
|
128
|
+
} catch { /* best-effort */ }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
63
132
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
64
133
|
|
|
65
134
|
/**
|
|
@@ -77,6 +146,9 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
77
146
|
// Ensure the directory exists
|
|
78
147
|
mkdirSync(dirname(lp), { recursive: true });
|
|
79
148
|
|
|
149
|
+
// Clean up numbered lock file variants from cloud sync conflicts (#1315)
|
|
150
|
+
cleanupStrayLockFiles(basePath);
|
|
151
|
+
|
|
80
152
|
// Write our lock data first (the content is informational; the OS lock is the real guard)
|
|
81
153
|
const lockData: SessionLockData = {
|
|
82
154
|
pid: process.pid,
|
|
@@ -124,15 +196,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
124
196
|
|
|
125
197
|
// Safety net: clean up lock dir on process exit if _releaseFunction
|
|
126
198
|
// wasn't called (e.g., normal exit after clean completion) (#1245).
|
|
127
|
-
|
|
128
|
-
process.once("exit", () => {
|
|
129
|
-
try {
|
|
130
|
-
if (_releaseFunction) { _releaseFunction(); _releaseFunction = null; }
|
|
131
|
-
} catch { /* best-effort */ }
|
|
132
|
-
try {
|
|
133
|
-
if (existsSync(lockDirForCleanup)) rmSync(lockDirForCleanup, { recursive: true, force: true });
|
|
134
|
-
} catch { /* best-effort */ }
|
|
135
|
-
});
|
|
199
|
+
ensureExitHandler(gsdDir);
|
|
136
200
|
|
|
137
201
|
// Write the informational lock data
|
|
138
202
|
atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
|
|
@@ -154,12 +218,20 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
154
218
|
// Retry acquisition after cleanup
|
|
155
219
|
const release = lockfile.lockSync(gsdDir, {
|
|
156
220
|
realpath: false,
|
|
157
|
-
stale:
|
|
221
|
+
stale: 1_800_000, // 30 minutes — match primary lock settings
|
|
158
222
|
update: 10_000,
|
|
223
|
+
onCompromised: () => {
|
|
224
|
+
_lockCompromised = true;
|
|
225
|
+
_releaseFunction = null;
|
|
226
|
+
},
|
|
159
227
|
});
|
|
160
228
|
_releaseFunction = release;
|
|
161
229
|
_lockedPath = basePath;
|
|
162
230
|
_lockPid = process.pid;
|
|
231
|
+
|
|
232
|
+
// Safety net — uses centralized handler to avoid double-registration
|
|
233
|
+
ensureExitHandler(gsdDir);
|
|
234
|
+
|
|
163
235
|
atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
|
|
164
236
|
return { acquired: true };
|
|
165
237
|
} catch {
|
|
@@ -299,6 +371,9 @@ export function releaseSessionLock(basePath: string): void {
|
|
|
299
371
|
// Non-fatal
|
|
300
372
|
}
|
|
301
373
|
|
|
374
|
+
// Clean up numbered lock file variants from cloud sync conflicts (#1315)
|
|
375
|
+
cleanupStrayLockFiles(basePath);
|
|
376
|
+
|
|
302
377
|
_lockedPath = null;
|
|
303
378
|
_lockPid = 0;
|
|
304
379
|
_lockCompromised = false;
|
|
@@ -70,31 +70,34 @@ test("auto.ts 'all milestones complete' path merges before stopping (#962)", ()
|
|
|
70
70
|
const incompleteIdx = autoSrc.indexOf("incomplete.length === 0");
|
|
71
71
|
assert.ok(incompleteIdx > -1, "auto.ts should have 'incomplete.length === 0' check");
|
|
72
72
|
|
|
73
|
-
// The merge call must appear BETWEEN the incomplete check and the stopAuto call
|
|
74
|
-
//
|
|
73
|
+
// The merge call must appear BETWEEN the incomplete check and the stopAuto call.
|
|
74
|
+
// After the #1308 refactor, the merge is delegated to tryMergeMilestone.
|
|
75
75
|
const blockAfterIncomplete = autoSrc.slice(incompleteIdx, incompleteIdx + 3000);
|
|
76
76
|
|
|
77
77
|
assert.ok(
|
|
78
|
-
blockAfterIncomplete.includes("
|
|
79
|
-
"auto.ts should call
|
|
78
|
+
blockAfterIncomplete.includes("tryMergeMilestone"),
|
|
79
|
+
"auto.ts should call tryMergeMilestone in the 'all milestones complete' path",
|
|
80
80
|
);
|
|
81
81
|
|
|
82
82
|
// The merge should come before stopAuto in this block
|
|
83
|
-
const mergePos = blockAfterIncomplete.indexOf("
|
|
83
|
+
const mergePos = blockAfterIncomplete.indexOf("tryMergeMilestone");
|
|
84
84
|
const stopPos = blockAfterIncomplete.indexOf("stopAuto");
|
|
85
85
|
assert.ok(
|
|
86
86
|
mergePos < stopPos,
|
|
87
|
-
"
|
|
87
|
+
"tryMergeMilestone should be called before stopAuto in the 'all complete' path",
|
|
88
88
|
);
|
|
89
89
|
|
|
90
|
-
//
|
|
90
|
+
// Verify tryMergeMilestone handles both worktree and branch isolation
|
|
91
|
+
const helperIdx = autoSrc.indexOf("function tryMergeMilestone");
|
|
92
|
+
assert.ok(helperIdx > -1, "tryMergeMilestone helper should exist");
|
|
93
|
+
const helperBlock = autoSrc.slice(helperIdx, helperIdx + 2000);
|
|
91
94
|
assert.ok(
|
|
92
|
-
|
|
93
|
-
"should check isInAutoWorktree for worktree mode",
|
|
95
|
+
helperBlock.includes("isInAutoWorktree"),
|
|
96
|
+
"tryMergeMilestone should check isInAutoWorktree for worktree mode",
|
|
94
97
|
);
|
|
95
98
|
assert.ok(
|
|
96
|
-
|
|
97
|
-
"should check
|
|
99
|
+
helperBlock.includes("getIsolationMode") || helperBlock.includes("isolationMode"),
|
|
100
|
+
"tryMergeMilestone should check isolation mode for branch mode",
|
|
98
101
|
);
|
|
99
102
|
});
|
|
100
103
|
|