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.
Files changed (151) hide show
  1. package/README.md +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -70
  3. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
  5. package/dist/resources/extensions/gsd/auto-start.js +16 -30
  6. package/dist/resources/extensions/gsd/auto-worktree.js +62 -15
  7. package/dist/resources/extensions/gsd/auto.js +94 -59
  8. package/dist/resources/extensions/gsd/bootstrap/system-context.js +7 -2
  9. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  10. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
  11. package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
  12. package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
  13. package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
  14. package/dist/resources/extensions/gsd/doctor.js +8 -4
  15. package/dist/resources/extensions/gsd/guided-flow.js +40 -31
  16. package/dist/resources/extensions/gsd/init-wizard.js +15 -12
  17. package/dist/resources/extensions/gsd/interrupted-session.js +146 -0
  18. package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
  19. package/dist/resources/extensions/gsd/workflow-mcp.js +64 -24
  20. package/dist/web/standalone/.next/BUILD_ID +1 -1
  21. package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
  22. package/dist/web/standalone/.next/build-manifest.json +3 -3
  23. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  24. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  25. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.html +1 -1
  44. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  45. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  46. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  47. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
  52. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  55. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  56. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  57. package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +9 -0
  58. package/dist/web/standalone/.next/static/chunks/app/{page-0c485498795110d6.js → page-f1e30ab6bb269149.js} +1 -1
  59. package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-6e4d7e9a4f57bed4.js} +1 -1
  60. package/package.json +1 -1
  61. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  62. package/packages/mcp-server/dist/workflow-tools.js +10 -4
  63. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  64. package/packages/mcp-server/src/workflow-tools.ts +13 -2
  65. package/packages/pi-agent-core/dist/agent-loop.js +14 -6
  66. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  67. package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
  68. package/packages/pi-agent-core/src/agent-loop.ts +20 -6
  69. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts +43 -0
  70. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
  71. package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
  72. package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
  73. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
  74. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
  75. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
  76. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
  77. package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
  78. package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/index.js +1 -0
  80. package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
  81. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
  82. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
  83. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
  84. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
  85. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  86. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  87. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
  88. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  89. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  90. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
  91. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  92. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
  93. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
  95. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  98. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -12
  99. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  100. package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
  101. package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
  102. package/packages/pi-coding-agent/src/core/index.ts +2 -0
  103. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
  104. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
  105. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
  106. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
  107. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
  108. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +190 -93
  109. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +89 -116
  110. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  111. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  112. package/src/resources/extensions/gsd/auto-start.ts +23 -55
  113. package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
  114. package/src/resources/extensions/gsd/auto.ts +104 -63
  115. package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
  116. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  117. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
  118. package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
  119. package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
  120. package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
  121. package/src/resources/extensions/gsd/doctor.ts +9 -5
  122. package/src/resources/extensions/gsd/guided-flow.ts +42 -36
  123. package/src/resources/extensions/gsd/init-wizard.ts +17 -11
  124. package/src/resources/extensions/gsd/interrupted-session.ts +224 -0
  125. package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
  126. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +668 -2
  127. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
  128. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
  129. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
  130. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
  131. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
  132. package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
  133. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
  134. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
  135. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
  136. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
  137. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -0
  138. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
  139. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
  140. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +11 -0
  141. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +178 -17
  142. package/src/resources/extensions/gsd/workflow-mcp.ts +76 -23
  143. package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
  144. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
  145. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
  147. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
  148. package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
  149. package/src/resources/extensions/gsd/tests/init-bootstrap-completeness.test.ts +0 -121
  150. /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → yh2vT27L1E6PChb_C1N_F}/_buildManifest.js +0 -0
  151. /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
- dbReady = await ensureDbOpen(basePath);
243
+ await ensureDbOpen(basePath);
245
244
  } catch {
246
- // Swallowedwarning surfaced below
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-fatalDB 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
- // Swallowedwarning surfaced below
274
+ // Non-fatalSTATE.md will be regenerated on next /gsd invocation
281
275
  }
282
- if (!stateReady) {
283
- ctx.ui.notify("Warning: initial STATE.md generation failed — it will be created on the next /gsd invocation.", "warning");
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
+ }