gsd-pi 2.67.0-dev.5399650 → 2.67.0-dev.6fc2289
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 +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -70
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-start.js +16 -30
- package/dist/resources/extensions/gsd/auto-worktree.js +62 -15
- package/dist/resources/extensions/gsd/auto.js +94 -59
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +7 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
- package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
- package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
- package/dist/resources/extensions/gsd/doctor.js +8 -4
- package/dist/resources/extensions/gsd/guided-flow.js +40 -31
- package/dist/resources/extensions/gsd/init-wizard.js +15 -12
- package/dist/resources/extensions/gsd/interrupted-session.js +146 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +64 -24
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- 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/page_client-reference-manifest.js +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/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- 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/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
- 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/2826.821e01b07d92e948.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/{page-0c485498795110d6.js → page-f1e30ab6bb269149.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-6e4d7e9a4f57bed4.js} +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +10 -4
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.ts +13 -2
- package/packages/pi-agent-core/dist/agent-loop.js +14 -6
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
- package/packages/pi-agent-core/src/agent-loop.ts +20 -6
- package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts +43 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/index.js +1 -0
- package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.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 +19 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
- 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 +15 -12
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
- package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
- package/packages/pi-coding-agent/src/core/index.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +190 -93
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +89 -116
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-start.ts +23 -55
- package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
- package/src/resources/extensions/gsd/auto.ts +104 -63
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
- package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
- package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
- package/src/resources/extensions/gsd/doctor.ts +9 -5
- package/src/resources/extensions/gsd/guided-flow.ts +42 -36
- package/src/resources/extensions/gsd/init-wizard.ts +17 -11
- package/src/resources/extensions/gsd/interrupted-session.ts +224 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +668 -2
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
- package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +178 -17
- package/src/resources/extensions/gsd/workflow-mcp.ts +76 -23
- package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
- package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
- package/src/resources/extensions/gsd/tests/init-bootstrap-completeness.test.ts +0 -121
- /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → yh2vT27L1E6PChb_C1N_F}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → yh2vT27L1E6PChb_C1N_F}/_ssgManifest.js +0 -0
|
@@ -238,15 +238,11 @@ export async function showProjectInit(
|
|
|
238
238
|
// Initialize SQLite database so GSD starts in full-capability mode (#3880).
|
|
239
239
|
// Without this, isDbAvailable() returns false and GSD enters degraded
|
|
240
240
|
// markdown-only mode until a tool handler happens to call ensureDbOpen().
|
|
241
|
-
let dbReady = false;
|
|
242
241
|
try {
|
|
243
242
|
const { ensureDbOpen } = await import("./bootstrap/dynamic-tools.js");
|
|
244
|
-
|
|
243
|
+
await ensureDbOpen(basePath);
|
|
245
244
|
} catch {
|
|
246
|
-
//
|
|
247
|
-
}
|
|
248
|
-
if (!dbReady) {
|
|
249
|
-
ctx.ui.notify("Warning: database initialization failed — GSD will run in degraded mode until the next /gsd invocation.", "warning");
|
|
245
|
+
// Non-fatal — DB creation failure should not block project init
|
|
250
246
|
}
|
|
251
247
|
|
|
252
248
|
// Ensure .gitignore
|
|
@@ -267,7 +263,6 @@ export async function showProjectInit(
|
|
|
267
263
|
// Write initial STATE.md so it exists before the first /gsd invocation.
|
|
268
264
|
// The explicit /gsd init path (ops.ts) returns without entering showSmartEntry(),
|
|
269
265
|
// which would otherwise generate STATE.md at guided-flow.ts:1358.
|
|
270
|
-
let stateReady = false;
|
|
271
266
|
try {
|
|
272
267
|
const { deriveState } = await import("./state.js");
|
|
273
268
|
const { buildStateMarkdown } = await import("./doctor.js");
|
|
@@ -275,12 +270,23 @@ export async function showProjectInit(
|
|
|
275
270
|
const { resolveGsdRootFile } = await import("./paths.js");
|
|
276
271
|
const state = await deriveState(basePath);
|
|
277
272
|
await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
|
|
278
|
-
stateReady = true;
|
|
279
273
|
} catch {
|
|
280
|
-
//
|
|
274
|
+
// Non-fatal — STATE.md will be regenerated on next /gsd invocation
|
|
281
275
|
}
|
|
282
|
-
|
|
283
|
-
|
|
276
|
+
|
|
277
|
+
if (ctx.model?.provider === "claude-code") {
|
|
278
|
+
try {
|
|
279
|
+
const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
|
|
280
|
+
const result = ensureProjectWorkflowMcpConfig(basePath);
|
|
281
|
+
if (result.status !== "unchanged") {
|
|
282
|
+
ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
|
|
283
|
+
}
|
|
284
|
+
} catch (err) {
|
|
285
|
+
ctx.ui.notify(
|
|
286
|
+
`Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
287
|
+
"warning",
|
|
288
|
+
);
|
|
289
|
+
}
|
|
284
290
|
}
|
|
285
291
|
|
|
286
292
|
ctx.ui.notify("GSD initialized. Starting your first milestone...", "info");
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { verifyExpectedArtifact } from "./auto-recovery.js";
|
|
5
|
+
import {
|
|
6
|
+
formatCrashInfo,
|
|
7
|
+
isLockProcessAlive,
|
|
8
|
+
readCrashLock,
|
|
9
|
+
type LockData,
|
|
10
|
+
} from "./crash-recovery.js";
|
|
11
|
+
import { gsdRoot } from "./paths.js";
|
|
12
|
+
import {
|
|
13
|
+
synthesizeCrashRecovery,
|
|
14
|
+
type RecoveryBriefing,
|
|
15
|
+
} from "./session-forensics.js";
|
|
16
|
+
import { deriveState } from "./state.js";
|
|
17
|
+
import type { GSDState } from "./types.js";
|
|
18
|
+
|
|
19
|
+
export type InterruptedSessionClassification =
|
|
20
|
+
| "none"
|
|
21
|
+
| "running"
|
|
22
|
+
| "recoverable"
|
|
23
|
+
| "stale";
|
|
24
|
+
|
|
25
|
+
export interface PausedSessionMetadata {
|
|
26
|
+
milestoneId?: string;
|
|
27
|
+
worktreePath?: string | null;
|
|
28
|
+
originalBasePath?: string;
|
|
29
|
+
stepMode?: boolean;
|
|
30
|
+
pausedAt?: string;
|
|
31
|
+
sessionFile?: string | null;
|
|
32
|
+
unitType?: string;
|
|
33
|
+
unitId?: string;
|
|
34
|
+
activeEngineId?: string;
|
|
35
|
+
activeRunDir?: string | null;
|
|
36
|
+
autoStartTime?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface InterruptedSessionAssessment {
|
|
40
|
+
classification: InterruptedSessionClassification;
|
|
41
|
+
lock: LockData | null;
|
|
42
|
+
pausedSession: PausedSessionMetadata | null;
|
|
43
|
+
state: GSDState | null;
|
|
44
|
+
recovery: RecoveryBriefing | null;
|
|
45
|
+
recoveryPrompt: string | null;
|
|
46
|
+
recoveryToolCallCount: number;
|
|
47
|
+
artifactSatisfied: boolean;
|
|
48
|
+
hasResumableDiskState: boolean;
|
|
49
|
+
isBootstrapCrash: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function readPausedSessionMetadata(
|
|
53
|
+
basePath: string,
|
|
54
|
+
): PausedSessionMetadata | null {
|
|
55
|
+
const pausedPath = join(gsdRoot(basePath), "runtime", "paused-session.json");
|
|
56
|
+
if (!existsSync(pausedPath)) return null;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(readFileSync(pausedPath, "utf-8")) as PausedSessionMetadata;
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function isBootstrapCrashLock(lock: LockData | null): boolean {
|
|
66
|
+
return !!(
|
|
67
|
+
lock &&
|
|
68
|
+
lock.unitType === "starting" &&
|
|
69
|
+
lock.unitId === "bootstrap"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function hasResumableDerivedState(state: GSDState | null): boolean {
|
|
74
|
+
return !!(state?.activeMilestone && state.phase !== "complete");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function assessInterruptedSession(
|
|
78
|
+
basePath: string,
|
|
79
|
+
): Promise<InterruptedSessionAssessment> {
|
|
80
|
+
const pausedSession = readPausedSessionMetadata(basePath);
|
|
81
|
+
const worktreeExists = pausedSession?.worktreePath
|
|
82
|
+
? existsSync(pausedSession.worktreePath)
|
|
83
|
+
: false;
|
|
84
|
+
const assessmentBasePath = worktreeExists ? pausedSession!.worktreePath! : basePath;
|
|
85
|
+
const rawLock = readCrashLock(basePath);
|
|
86
|
+
const lock = rawLock && rawLock.pid !== process.pid ? rawLock : null;
|
|
87
|
+
|
|
88
|
+
if (!lock && !pausedSession) {
|
|
89
|
+
return {
|
|
90
|
+
classification: "none",
|
|
91
|
+
lock: null,
|
|
92
|
+
pausedSession: null,
|
|
93
|
+
state: null,
|
|
94
|
+
recovery: null,
|
|
95
|
+
recoveryPrompt: null,
|
|
96
|
+
recoveryToolCallCount: 0,
|
|
97
|
+
artifactSatisfied: false,
|
|
98
|
+
hasResumableDiskState: false,
|
|
99
|
+
isBootstrapCrash: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (lock && isLockProcessAlive(lock)) {
|
|
104
|
+
return {
|
|
105
|
+
classification: "running",
|
|
106
|
+
lock,
|
|
107
|
+
pausedSession,
|
|
108
|
+
state: null,
|
|
109
|
+
recovery: null,
|
|
110
|
+
recoveryPrompt: null,
|
|
111
|
+
recoveryToolCallCount: 0,
|
|
112
|
+
artifactSatisfied: false,
|
|
113
|
+
hasResumableDiskState: false,
|
|
114
|
+
isBootstrapCrash: false,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const isBootstrapCrash = isBootstrapCrashLock(lock);
|
|
119
|
+
const state = await deriveState(assessmentBasePath);
|
|
120
|
+
const hasResumableDiskState = hasResumableDerivedState(state);
|
|
121
|
+
const artifactSatisfied = !!(
|
|
122
|
+
lock &&
|
|
123
|
+
!isBootstrapCrash &&
|
|
124
|
+
verifyExpectedArtifact(lock.unitType, lock.unitId, assessmentBasePath)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
let recovery: RecoveryBriefing | null = null;
|
|
128
|
+
if (lock && !isBootstrapCrash && !artifactSatisfied) {
|
|
129
|
+
recovery = synthesizeCrashRecovery(
|
|
130
|
+
assessmentBasePath,
|
|
131
|
+
lock.unitType,
|
|
132
|
+
lock.unitId,
|
|
133
|
+
lock.sessionFile,
|
|
134
|
+
join(gsdRoot(assessmentBasePath), "activity"),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const recoveryToolCallCount = recovery?.trace.toolCallCount ?? 0;
|
|
139
|
+
const recoveryPrompt = recoveryToolCallCount > 0 ? recovery!.prompt : null;
|
|
140
|
+
|
|
141
|
+
if (isBootstrapCrash) {
|
|
142
|
+
return {
|
|
143
|
+
classification: pausedSession ? "recoverable" : "stale",
|
|
144
|
+
lock,
|
|
145
|
+
pausedSession,
|
|
146
|
+
state,
|
|
147
|
+
recovery,
|
|
148
|
+
recoveryPrompt,
|
|
149
|
+
recoveryToolCallCount,
|
|
150
|
+
artifactSatisfied,
|
|
151
|
+
hasResumableDiskState,
|
|
152
|
+
isBootstrapCrash: true,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!hasResumableDiskState && pausedSession && !lock && recoveryToolCallCount === 0) {
|
|
157
|
+
return {
|
|
158
|
+
classification: "stale",
|
|
159
|
+
lock,
|
|
160
|
+
pausedSession,
|
|
161
|
+
state,
|
|
162
|
+
recovery,
|
|
163
|
+
recoveryPrompt,
|
|
164
|
+
recoveryToolCallCount,
|
|
165
|
+
artifactSatisfied,
|
|
166
|
+
hasResumableDiskState,
|
|
167
|
+
isBootstrapCrash: false,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (lock && artifactSatisfied && !hasResumableDiskState && recoveryToolCallCount === 0) {
|
|
172
|
+
return {
|
|
173
|
+
classification: "stale",
|
|
174
|
+
lock,
|
|
175
|
+
pausedSession,
|
|
176
|
+
state,
|
|
177
|
+
recovery,
|
|
178
|
+
recoveryPrompt,
|
|
179
|
+
recoveryToolCallCount,
|
|
180
|
+
artifactSatisfied,
|
|
181
|
+
hasResumableDiskState,
|
|
182
|
+
isBootstrapCrash: false,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const hasStrongRecoverySignal =
|
|
187
|
+
hasResumableDiskState || recoveryToolCallCount > 0;
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
classification: hasStrongRecoverySignal ? "recoverable" : "stale",
|
|
191
|
+
lock,
|
|
192
|
+
pausedSession,
|
|
193
|
+
state,
|
|
194
|
+
recovery,
|
|
195
|
+
recoveryPrompt,
|
|
196
|
+
recoveryToolCallCount,
|
|
197
|
+
artifactSatisfied,
|
|
198
|
+
hasResumableDiskState,
|
|
199
|
+
isBootstrapCrash: false,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function formatInterruptedSessionSummary(
|
|
204
|
+
assessment: InterruptedSessionAssessment,
|
|
205
|
+
): string[] {
|
|
206
|
+
if (assessment.lock) return [formatCrashInfo(assessment.lock)];
|
|
207
|
+
|
|
208
|
+
if (assessment.pausedSession?.milestoneId) {
|
|
209
|
+
return [
|
|
210
|
+
`Paused auto-mode session detected for ${assessment.pausedSession.milestoneId}.`,
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return ["Paused auto-mode session detected."];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function formatInterruptedSessionRunningMessage(
|
|
218
|
+
assessment: InterruptedSessionAssessment,
|
|
219
|
+
): string {
|
|
220
|
+
const pid = assessment.lock?.pid;
|
|
221
|
+
return pid
|
|
222
|
+
? `Another auto-mode session (PID ${pid}) appears to be running.\nStop it with \`kill ${pid}\` before starting a new session.`
|
|
223
|
+
: "Another auto-mode session appears to be running.";
|
|
224
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { assertSafeDirectory } from "./validate-directory.js";
|
|
6
|
+
import { detectWorkflowMcpLaunchConfig } from "./workflow-mcp.js";
|
|
7
|
+
|
|
8
|
+
export const GSD_WORKFLOW_MCP_SERVER_NAME = "gsd-workflow";
|
|
9
|
+
|
|
10
|
+
export interface ProjectMcpServerConfig {
|
|
11
|
+
command?: string;
|
|
12
|
+
args?: string[];
|
|
13
|
+
cwd?: string;
|
|
14
|
+
env?: Record<string, string>;
|
|
15
|
+
url?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface EnsureProjectWorkflowMcpConfigResult {
|
|
19
|
+
configPath: string;
|
|
20
|
+
serverName: string;
|
|
21
|
+
status: "created" | "updated" | "unchanged";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface McpConfigFile {
|
|
25
|
+
mcpServers?: Record<string, ProjectMcpServerConfig>;
|
|
26
|
+
servers?: Record<string, ProjectMcpServerConfig>;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolveBundledGsdCliPath(env: NodeJS.ProcessEnv = process.env): string | null {
|
|
31
|
+
const explicit = env.GSD_CLI_PATH?.trim() || env.GSD_BIN_PATH?.trim();
|
|
32
|
+
if (explicit) return explicit;
|
|
33
|
+
|
|
34
|
+
const candidates = [
|
|
35
|
+
resolve(fileURLToPath(new URL("../../../../scripts/dev-cli.js", import.meta.url))),
|
|
36
|
+
resolve(fileURLToPath(new URL("../../../../dist/loader.js", import.meta.url))),
|
|
37
|
+
resolve(fileURLToPath(new URL("../../../loader.js", import.meta.url))),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
if (existsSync(candidate)) return candidate;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function buildProjectWorkflowMcpServerConfig(
|
|
48
|
+
projectRoot: string,
|
|
49
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
50
|
+
): ProjectMcpServerConfig {
|
|
51
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
52
|
+
const gsdCliPath = resolveBundledGsdCliPath(env);
|
|
53
|
+
const launch = detectWorkflowMcpLaunchConfig(resolvedProjectRoot, {
|
|
54
|
+
...env,
|
|
55
|
+
...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath, GSD_BIN_PATH: gsdCliPath } : {}),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!launch) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"Unable to resolve the GSD workflow MCP server. Build this checkout or install gsd-mcp-server on PATH.",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
command: launch.command,
|
|
66
|
+
...(launch.args && launch.args.length > 0 ? { args: launch.args } : {}),
|
|
67
|
+
...(launch.cwd ? { cwd: launch.cwd } : {}),
|
|
68
|
+
...(launch.env ? { env: launch.env } : {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readExistingConfig(configPath: string): McpConfigFile {
|
|
73
|
+
if (!existsSync(configPath)) return {};
|
|
74
|
+
|
|
75
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(raw) as McpConfigFile;
|
|
78
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
79
|
+
} catch (err) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Failed to parse ${configPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function ensureProjectWorkflowMcpConfig(
|
|
87
|
+
projectRoot: string,
|
|
88
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
89
|
+
): EnsureProjectWorkflowMcpConfigResult {
|
|
90
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
91
|
+
assertSafeDirectory(resolvedProjectRoot);
|
|
92
|
+
|
|
93
|
+
const configPath = resolve(resolvedProjectRoot, ".mcp.json");
|
|
94
|
+
const existing = readExistingConfig(configPath);
|
|
95
|
+
const desiredServer = buildProjectWorkflowMcpServerConfig(resolvedProjectRoot, env);
|
|
96
|
+
const previousServers = existing.mcpServers ?? {};
|
|
97
|
+
const nextServers = {
|
|
98
|
+
...previousServers,
|
|
99
|
+
[GSD_WORKFLOW_MCP_SERVER_NAME]: desiredServer,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const alreadyPresent = existsSync(configPath);
|
|
103
|
+
const unchanged =
|
|
104
|
+
JSON.stringify(previousServers[GSD_WORKFLOW_MCP_SERVER_NAME] ?? null)
|
|
105
|
+
=== JSON.stringify(desiredServer)
|
|
106
|
+
&& existing.mcpServers !== undefined;
|
|
107
|
+
|
|
108
|
+
if (unchanged) {
|
|
109
|
+
return {
|
|
110
|
+
configPath,
|
|
111
|
+
serverName: GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
112
|
+
status: "unchanged",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const nextConfig: McpConfigFile = {
|
|
117
|
+
...existing,
|
|
118
|
+
mcpServers: nextServers,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
configPath,
|
|
125
|
+
serverName: GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
126
|
+
status: alreadyPresent ? "updated" : "created",
|
|
127
|
+
};
|
|
128
|
+
}
|