gsd-pi 2.37.0-dev.b5e7ebc → 2.37.0-dev.c5c85d8
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
CHANGED
|
@@ -16,7 +16,7 @@ This version is different. GSD is now a standalone CLI built on the [Pi SDK](htt
|
|
|
16
16
|
|
|
17
17
|
One command. Walk away. Come back to a built project with clean git history.
|
|
18
18
|
|
|
19
|
-
<pre><code>npm install -g gsd-pi</code></pre>
|
|
19
|
+
<pre><code>npm install -g gsd-pi@latest</code></pre>
|
|
20
20
|
|
|
21
21
|
> **📋 NOTICE: New to Node on Mac?** If you installed Node.js via Homebrew, you may be running a development release instead of LTS. **[Read this guide](./docs/node-lts-macos.md)** to pin Node 24 LTS and avoid compatibility issues.
|
|
22
22
|
|
|
@@ -209,6 +209,29 @@ export function stopAutoRemote(projectRoot) {
|
|
|
209
209
|
return { found: false, error: err.message };
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Check if a remote auto-mode session is running (from a different process).
|
|
214
|
+
* Reads the crash lock, checks PID liveness, and returns session details.
|
|
215
|
+
* Used by the guard in commands.ts to prevent bare /gsd, /gsd next, and
|
|
216
|
+
* /gsd auto from stealing the session lock.
|
|
217
|
+
*/
|
|
218
|
+
export function checkRemoteAutoSession(projectRoot) {
|
|
219
|
+
const lock = readCrashLock(projectRoot);
|
|
220
|
+
if (!lock)
|
|
221
|
+
return { running: false };
|
|
222
|
+
if (!isLockProcessAlive(lock)) {
|
|
223
|
+
// Stale lock from a dead process — not a live remote session
|
|
224
|
+
return { running: false };
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
running: true,
|
|
228
|
+
pid: lock.pid,
|
|
229
|
+
unitType: lock.unitType,
|
|
230
|
+
unitId: lock.unitId,
|
|
231
|
+
startedAt: lock.startedAt,
|
|
232
|
+
completedUnits: lock.completedUnits,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
212
235
|
export function isStepMode() {
|
|
213
236
|
return s.stepMode;
|
|
214
237
|
}
|
|
@@ -12,7 +12,7 @@ import { deriveState } from "./state.js";
|
|
|
12
12
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
13
13
|
import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
|
|
14
14
|
import { showQueue, showDiscuss, showHeadlessMilestoneCreation } from "./guided-flow.js";
|
|
15
|
-
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, stopAutoRemote } from "./auto.js";
|
|
15
|
+
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, stopAutoRemote, checkRemoteAutoSession } from "./auto.js";
|
|
16
16
|
import { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
17
17
|
import { resolveProjectRoot } from "./worktree.js";
|
|
18
18
|
import { assertSafeDirectory } from "./validate-directory.js";
|
|
@@ -36,8 +36,8 @@ import { computeProgressScore, formatProgressLine } from "./progress-score.js";
|
|
|
36
36
|
import { runEnvironmentChecks } from "./doctor-environment.js";
|
|
37
37
|
import { handleLogs } from "./commands-logs.js";
|
|
38
38
|
import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
|
|
39
|
-
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
|
|
40
39
|
import { handleCmux } from "./commands-cmux.js";
|
|
40
|
+
import { showNextAction } from "../shared/mod.js";
|
|
41
41
|
/** Resolve the effective project root, accounting for worktree paths. */
|
|
42
42
|
export function projectRoot() {
|
|
43
43
|
const cwd = process.cwd();
|
|
@@ -57,36 +57,81 @@ export function projectRoot() {
|
|
|
57
57
|
return root;
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
* Returns
|
|
60
|
+
* Guard against starting auto-mode when a remote session is already running.
|
|
61
|
+
* Returns true if the caller should proceed with startAuto, false if handled.
|
|
62
62
|
*/
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
63
|
+
async function guardRemoteSession(ctx, pi) {
|
|
64
|
+
// Local session already active — proceed (startAuto handles re-entrant calls)
|
|
65
|
+
if (isAutoActive() || isAutoPaused())
|
|
66
|
+
return true;
|
|
67
|
+
const remote = checkRemoteAutoSession(projectRoot());
|
|
68
|
+
if (!remote.running || !remote.pid)
|
|
69
|
+
return true;
|
|
70
|
+
const unitLabel = remote.unitType && remote.unitId
|
|
71
|
+
? `${remote.unitType} (${remote.unitId})`
|
|
72
|
+
: "unknown unit";
|
|
73
|
+
const unitsMsg = remote.completedUnits != null
|
|
74
|
+
? `${remote.completedUnits} units completed`
|
|
75
|
+
: "";
|
|
76
|
+
const choice = await showNextAction(ctx, {
|
|
77
|
+
title: `Auto-mode is running in another terminal (PID ${remote.pid})`,
|
|
78
|
+
summary: [
|
|
79
|
+
`Currently executing: ${unitLabel}`,
|
|
80
|
+
...(unitsMsg ? [unitsMsg] : []),
|
|
81
|
+
...(remote.startedAt ? [`Started: ${remote.startedAt}`] : []),
|
|
82
|
+
],
|
|
83
|
+
actions: [
|
|
84
|
+
{
|
|
85
|
+
id: "status",
|
|
86
|
+
label: "View status",
|
|
87
|
+
description: "Show the current GSD progress dashboard.",
|
|
88
|
+
recommended: true,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "steer",
|
|
92
|
+
label: "Steer the session",
|
|
93
|
+
description: "Use /gsd steer <instruction> to redirect the running session.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "stop",
|
|
97
|
+
label: "Stop remote session",
|
|
98
|
+
description: `Send SIGTERM to PID ${remote.pid} to stop it gracefully.`,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "force",
|
|
102
|
+
label: "Force start (steal lock)",
|
|
103
|
+
description: "Start a new session, terminating the existing one.",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
notYetMessage: "Run /gsd when ready.",
|
|
107
|
+
});
|
|
108
|
+
if (choice === "status") {
|
|
109
|
+
await handleStatus(ctx);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (choice === "steer") {
|
|
113
|
+
ctx.ui.notify("Use /gsd steer <instruction> to redirect the running auto-mode session.\n" +
|
|
114
|
+
"Example: /gsd steer Use Postgres instead of SQLite", "info");
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (choice === "stop") {
|
|
118
|
+
const result = stopAutoRemote(projectRoot());
|
|
119
|
+
if (result.found) {
|
|
120
|
+
ctx.ui.notify(`Sent stop signal to auto-mode session (PID ${result.pid}). It will shut down gracefully.`, "info");
|
|
121
|
+
}
|
|
122
|
+
else if (result.error) {
|
|
123
|
+
ctx.ui.notify(`Failed to stop remote auto-mode: ${result.error}`, "error");
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
ctx.ui.notify("Remote session is no longer running.", "info");
|
|
127
|
+
}
|
|
80
128
|
return false;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
` /gsd capture — fire-and-forget thought\n` +
|
|
88
|
-
` /gsd stop — stop auto-mode`, "warning");
|
|
89
|
-
return true;
|
|
129
|
+
}
|
|
130
|
+
if (choice === "force") {
|
|
131
|
+
return true; // Proceed — startAuto will steal the lock
|
|
132
|
+
}
|
|
133
|
+
// "not_yet" or escape
|
|
134
|
+
return false;
|
|
90
135
|
}
|
|
91
136
|
export function registerGSDCommand(pi) {
|
|
92
137
|
pi.registerCommand("gsd", {
|
|
@@ -542,12 +587,12 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
542
587
|
await handleDryRun(ctx, projectRoot());
|
|
543
588
|
return;
|
|
544
589
|
}
|
|
545
|
-
if (notifyRemoteAutoActive(ctx, projectRoot()))
|
|
546
|
-
return;
|
|
547
590
|
const verboseMode = trimmed.includes("--verbose");
|
|
548
591
|
const debugMode = trimmed.includes("--debug");
|
|
549
592
|
if (debugMode)
|
|
550
593
|
enableDebug(projectRoot());
|
|
594
|
+
if (!(await guardRemoteSession(ctx, pi)))
|
|
595
|
+
return;
|
|
551
596
|
await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
|
|
552
597
|
return;
|
|
553
598
|
}
|
|
@@ -556,6 +601,8 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
556
601
|
const debugMode = trimmed.includes("--debug");
|
|
557
602
|
if (debugMode)
|
|
558
603
|
enableDebug(projectRoot());
|
|
604
|
+
if (!(await guardRemoteSession(ctx, pi)))
|
|
605
|
+
return;
|
|
559
606
|
await startAuto(ctx, pi, projectRoot(), verboseMode);
|
|
560
607
|
return;
|
|
561
608
|
}
|
|
@@ -899,7 +946,7 @@ Examples:
|
|
|
899
946
|
return;
|
|
900
947
|
}
|
|
901
948
|
if (trimmed === "") {
|
|
902
|
-
if (
|
|
949
|
+
if (!(await guardRemoteSession(ctx, pi)))
|
|
903
950
|
return;
|
|
904
951
|
await startAuto(ctx, pi, projectRoot(), false, { step: true });
|
|
905
952
|
return;
|
package/package.json
CHANGED
|
@@ -418,6 +418,38 @@ export function stopAutoRemote(projectRoot: string): {
|
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Check if a remote auto-mode session is running (from a different process).
|
|
423
|
+
* Reads the crash lock, checks PID liveness, and returns session details.
|
|
424
|
+
* Used by the guard in commands.ts to prevent bare /gsd, /gsd next, and
|
|
425
|
+
* /gsd auto from stealing the session lock.
|
|
426
|
+
*/
|
|
427
|
+
export function checkRemoteAutoSession(projectRoot: string): {
|
|
428
|
+
running: boolean;
|
|
429
|
+
pid?: number;
|
|
430
|
+
unitType?: string;
|
|
431
|
+
unitId?: string;
|
|
432
|
+
startedAt?: string;
|
|
433
|
+
completedUnits?: number;
|
|
434
|
+
} {
|
|
435
|
+
const lock = readCrashLock(projectRoot);
|
|
436
|
+
if (!lock) return { running: false };
|
|
437
|
+
|
|
438
|
+
if (!isLockProcessAlive(lock)) {
|
|
439
|
+
// Stale lock from a dead process — not a live remote session
|
|
440
|
+
return { running: false };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
running: true,
|
|
445
|
+
pid: lock.pid,
|
|
446
|
+
unitType: lock.unitType,
|
|
447
|
+
unitId: lock.unitId,
|
|
448
|
+
startedAt: lock.startedAt,
|
|
449
|
+
completedUnits: lock.completedUnits,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
421
453
|
export function isStepMode(): boolean {
|
|
422
454
|
return s.stepMode;
|
|
423
455
|
}
|
|
@@ -15,7 +15,7 @@ import { deriveState } from "./state.js";
|
|
|
15
15
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
16
16
|
import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
|
|
17
17
|
import { showQueue, showDiscuss, showHeadlessMilestoneCreation } from "./guided-flow.js";
|
|
18
|
-
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote } from "./auto.js";
|
|
18
|
+
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote, checkRemoteAutoSession } from "./auto.js";
|
|
19
19
|
import { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
20
20
|
import { resolveProjectRoot } from "./worktree.js";
|
|
21
21
|
import { assertSafeDirectory } from "./validate-directory.js";
|
|
@@ -50,6 +50,7 @@ import { handleLogs } from "./commands-logs.js";
|
|
|
50
50
|
import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
|
|
51
51
|
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
|
|
52
52
|
import { handleCmux } from "./commands-cmux.js";
|
|
53
|
+
import { showNextAction } from "../shared/mod.js";
|
|
53
54
|
|
|
54
55
|
|
|
55
56
|
/** Resolve the effective project root, accounting for worktree paths. */
|
|
@@ -72,36 +73,88 @@ export function projectRoot(): string {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
/**
|
|
75
|
-
*
|
|
76
|
-
* Returns
|
|
76
|
+
* Guard against starting auto-mode when a remote session is already running.
|
|
77
|
+
* Returns true if the caller should proceed with startAuto, false if handled.
|
|
77
78
|
*/
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
async function guardRemoteSession(
|
|
80
|
+
ctx: ExtensionCommandContext,
|
|
81
|
+
pi: ExtensionAPI,
|
|
82
|
+
): Promise<boolean> {
|
|
83
|
+
// Local session already active — proceed (startAuto handles re-entrant calls)
|
|
84
|
+
if (isAutoActive() || isAutoPaused()) return true;
|
|
85
|
+
|
|
86
|
+
const remote = checkRemoteAutoSession(projectRoot());
|
|
87
|
+
if (!remote.running || !remote.pid) return true;
|
|
88
|
+
|
|
89
|
+
const unitLabel = remote.unitType && remote.unitId
|
|
90
|
+
? `${remote.unitType} (${remote.unitId})`
|
|
91
|
+
: "unknown unit";
|
|
92
|
+
const unitsMsg = remote.completedUnits != null
|
|
93
|
+
? `${remote.completedUnits} units completed`
|
|
94
|
+
: "";
|
|
95
|
+
|
|
96
|
+
const choice = await showNextAction(ctx, {
|
|
97
|
+
title: `Auto-mode is running in another terminal (PID ${remote.pid})`,
|
|
98
|
+
summary: [
|
|
99
|
+
`Currently executing: ${unitLabel}`,
|
|
100
|
+
...(unitsMsg ? [unitsMsg] : []),
|
|
101
|
+
...(remote.startedAt ? [`Started: ${remote.startedAt}`] : []),
|
|
102
|
+
],
|
|
103
|
+
actions: [
|
|
104
|
+
{
|
|
105
|
+
id: "status",
|
|
106
|
+
label: "View status",
|
|
107
|
+
description: "Show the current GSD progress dashboard.",
|
|
108
|
+
recommended: true,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "steer",
|
|
112
|
+
label: "Steer the session",
|
|
113
|
+
description: "Use /gsd steer <instruction> to redirect the running session.",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "stop",
|
|
117
|
+
label: "Stop remote session",
|
|
118
|
+
description: `Send SIGTERM to PID ${remote.pid} to stop it gracefully.`,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "force",
|
|
122
|
+
label: "Force start (steal lock)",
|
|
123
|
+
description: "Start a new session, terminating the existing one.",
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
notYetMessage: "Run /gsd when ready.",
|
|
127
|
+
});
|
|
85
128
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
129
|
+
if (choice === "status") {
|
|
130
|
+
await handleStatus(ctx);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (choice === "steer") {
|
|
134
|
+
ctx.ui.notify(
|
|
135
|
+
"Use /gsd steer <instruction> to redirect the running auto-mode session.\n" +
|
|
136
|
+
"Example: /gsd steer Use Postgres instead of SQLite",
|
|
137
|
+
"info",
|
|
138
|
+
);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (choice === "stop") {
|
|
142
|
+
const result = stopAutoRemote(projectRoot());
|
|
143
|
+
if (result.found) {
|
|
144
|
+
ctx.ui.notify(`Sent stop signal to auto-mode session (PID ${result.pid}). It will shut down gracefully.`, "info");
|
|
145
|
+
} else if (result.error) {
|
|
146
|
+
ctx.ui.notify(`Failed to stop remote auto-mode: ${result.error}`, "error");
|
|
147
|
+
} else {
|
|
148
|
+
ctx.ui.notify("Remote session is no longer running.", "info");
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
if (choice === "force") {
|
|
153
|
+
return true; // Proceed — startAuto will steal the lock
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// "not_yet" or escape
|
|
157
|
+
return false;
|
|
105
158
|
}
|
|
106
159
|
|
|
107
160
|
export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
@@ -598,10 +651,10 @@ export async function handleGSDCommand(
|
|
|
598
651
|
await handleDryRun(ctx, projectRoot());
|
|
599
652
|
return;
|
|
600
653
|
}
|
|
601
|
-
if (notifyRemoteAutoActive(ctx, projectRoot())) return;
|
|
602
654
|
const verboseMode = trimmed.includes("--verbose");
|
|
603
655
|
const debugMode = trimmed.includes("--debug");
|
|
604
656
|
if (debugMode) enableDebug(projectRoot());
|
|
657
|
+
if (!(await guardRemoteSession(ctx, pi))) return;
|
|
605
658
|
await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
|
|
606
659
|
return;
|
|
607
660
|
}
|
|
@@ -610,6 +663,7 @@ export async function handleGSDCommand(
|
|
|
610
663
|
const verboseMode = trimmed.includes("--verbose");
|
|
611
664
|
const debugMode = trimmed.includes("--debug");
|
|
612
665
|
if (debugMode) enableDebug(projectRoot());
|
|
666
|
+
if (!(await guardRemoteSession(ctx, pi))) return;
|
|
613
667
|
await startAuto(ctx, pi, projectRoot(), verboseMode);
|
|
614
668
|
return;
|
|
615
669
|
}
|
|
@@ -993,7 +1047,7 @@ Examples:
|
|
|
993
1047
|
}
|
|
994
1048
|
|
|
995
1049
|
if (trimmed === "") {
|
|
996
|
-
if (
|
|
1050
|
+
if (!(await guardRemoteSession(ctx, pi))) return;
|
|
997
1051
|
await startAuto(ctx, pi, projectRoot(), false, { step: true });
|
|
998
1052
|
return;
|
|
999
1053
|
}
|