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
@@ -4,15 +4,15 @@ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "nod
4
4
  import { join, resolve } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import {
7
+ makeStreamExhaustedErrorMessage,
7
8
  buildPromptFromContext,
8
- buildFinalClaudeCodeContent,
9
9
  buildSdkOptions,
10
+ extractToolResultsFromSdkUserMessage,
10
11
  getClaudeLookupCommand,
11
- makeStreamExhaustedErrorMessage,
12
12
  parseClaudeLookupOutput,
13
- sanitizeClaudeCodeStreamingEvent,
14
13
  } from "../stream-adapter.ts";
15
- import type { AssistantMessage, Context, Message } from "@gsd/pi-ai";
14
+ import type { Context, Message } from "@gsd/pi-ai";
15
+ import type { SDKUserMessage } from "../sdk-types.ts";
16
16
 
17
17
  // ---------------------------------------------------------------------------
18
18
  // Existing tests — exhausted stream fallback (#2575)
@@ -108,6 +108,65 @@ describe("stream-adapter — full context prompt (#2859)", () => {
108
108
  });
109
109
  });
110
110
 
111
+ describe("stream-adapter — Claude Code external tool results", () => {
112
+ test("extractToolResultsFromSdkUserMessage maps tool_result content to tool payloads", () => {
113
+ const message: SDKUserMessage = {
114
+ type: "user",
115
+ session_id: "sess-1",
116
+ parent_tool_use_id: "tool-bash-1",
117
+ message: {
118
+ role: "user",
119
+ content: [
120
+ {
121
+ type: "tool_result",
122
+ tool_use_id: "tool-bash-1",
123
+ content: "line 1\nline 2",
124
+ is_error: false,
125
+ },
126
+ ],
127
+ },
128
+ };
129
+
130
+ const results = extractToolResultsFromSdkUserMessage(message);
131
+ assert.deepEqual(results, [
132
+ {
133
+ toolUseId: "tool-bash-1",
134
+ result: {
135
+ content: [{ type: "text", text: "line 1\nline 2" }],
136
+ details: {},
137
+ isError: false,
138
+ },
139
+ },
140
+ ]);
141
+ });
142
+
143
+ test("extractToolResultsFromSdkUserMessage falls back to tool_use_result", () => {
144
+ const message: SDKUserMessage = {
145
+ type: "user",
146
+ session_id: "sess-1",
147
+ parent_tool_use_id: "tool-read-1",
148
+ message: { role: "user", content: [] },
149
+ tool_use_result: {
150
+ tool_use_id: "tool-read-1",
151
+ content: "file contents",
152
+ is_error: true,
153
+ },
154
+ };
155
+
156
+ const results = extractToolResultsFromSdkUserMessage(message);
157
+ assert.deepEqual(results, [
158
+ {
159
+ toolUseId: "tool-read-1",
160
+ result: {
161
+ content: [{ type: "text", text: "file contents" }],
162
+ details: {},
163
+ isError: true,
164
+ },
165
+ },
166
+ ]);
167
+ });
168
+ });
169
+
111
170
  describe("stream-adapter — session persistence (#2859)", () => {
112
171
  test("buildSdkOptions enables persistSession by default", () => {
113
172
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test prompt");
@@ -149,18 +208,15 @@ describe("stream-adapter — session persistence (#2859)", () => {
149
208
  process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
150
209
 
151
210
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
152
- assert.deepEqual(options.mcpServers, {
153
- "gsd-workflow": {
154
- command: "node",
155
- args: ["packages/mcp-server/dist/cli.js"],
156
- env: {
157
- GSD_CLI_PATH: "/tmp/gsd",
158
- GSD_PERSIST_WRITE_GATE_STATE: "1",
159
- GSD_WORKFLOW_PROJECT_ROOT: "/tmp/project",
160
- },
161
- cwd: "/tmp/project",
162
- },
163
- });
211
+ const mcpServers = options.mcpServers as Record<string, any>;
212
+ assert.ok(mcpServers?.["gsd-workflow"], "expected gsd-workflow server config");
213
+ const srv = mcpServers["gsd-workflow"];
214
+ assert.equal(srv.command, "node");
215
+ assert.deepEqual(srv.args, ["packages/mcp-server/dist/cli.js"]);
216
+ assert.equal(srv.cwd, "/tmp/project");
217
+ assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
218
+ assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
219
+ assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
164
220
  } finally {
165
221
  process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
166
222
  process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
@@ -170,7 +226,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
170
226
  }
171
227
  });
172
228
 
173
- test("buildSdkOptions omits workflow MCP server config when env is unset", () => {
229
+ test("buildSdkOptions auto-discovers bundled MCP server even without env hints", () => {
174
230
  const prev = {
175
231
  GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
176
232
  GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
@@ -190,7 +246,13 @@ describe("stream-adapter — session persistence (#2859)", () => {
190
246
  process.chdir(emptyDir);
191
247
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
192
248
  process.chdir(originalCwd);
193
- assert.equal((options as any).mcpServers, undefined);
249
+ // The bundled CLI may or may not be discoverable depending on
250
+ // whether the build output exists relative to import.meta.url.
251
+ // Either outcome is valid — the key invariant is no crash.
252
+ const mcpServers = (options as any).mcpServers;
253
+ if (mcpServers) {
254
+ assert.ok(mcpServers["gsd-workflow"], "if present, must be gsd-workflow");
255
+ }
194
256
  rmSync(emptyDir, { recursive: true, force: true });
195
257
  } finally {
196
258
  process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
@@ -227,18 +289,15 @@ describe("stream-adapter — session persistence (#2859)", () => {
227
289
  const resolvedRepoDir = realpathSync(repoDir);
228
290
 
229
291
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
230
- assert.deepEqual(options.mcpServers, {
231
- "gsd-workflow": {
232
- command: process.execPath,
233
- args: [realpathSync(resolve(repoDir, "packages", "mcp-server", "dist", "cli.js"))],
234
- env: {
235
- GSD_CLI_PATH: "/tmp/gsd",
236
- GSD_PERSIST_WRITE_GATE_STATE: "1",
237
- GSD_WORKFLOW_PROJECT_ROOT: resolvedRepoDir,
238
- },
239
- cwd: resolvedRepoDir,
240
- },
241
- });
292
+ const mcpServers = options.mcpServers as Record<string, any>;
293
+ assert.ok(mcpServers?.["gsd-workflow"], "expected gsd-workflow server config");
294
+ const srv = mcpServers["gsd-workflow"];
295
+ assert.equal(srv.command, process.execPath);
296
+ assert.deepEqual(srv.args, [realpathSync(resolve(repoDir, "packages", "mcp-server", "dist", "cli.js"))]);
297
+ assert.equal(srv.cwd, resolvedRepoDir);
298
+ assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
299
+ assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
300
+ assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, resolvedRepoDir);
242
301
  } finally {
243
302
  process.chdir(originalCwd);
244
303
  rmSync(repoDir, { recursive: true, force: true });
@@ -252,92 +311,6 @@ describe("stream-adapter — session persistence (#2859)", () => {
252
311
  });
253
312
  });
254
313
 
255
- describe("stream-adapter — final content filtering (#3861)", () => {
256
- test("buildFinalClaudeCodeContent strips intermediate tool calls from the final assistant message", () => {
257
- const finalContent = buildFinalClaudeCodeContent(
258
- [
259
- { type: "toolCall", id: "tc_1", name: "Read", arguments: {} },
260
- { type: "thinking", thinking: "Planning next step" },
261
- { type: "text", text: "Done." },
262
- ] as any,
263
- "",
264
- "",
265
- );
266
-
267
- assert.deepEqual(finalContent, [
268
- { type: "thinking", thinking: "Planning next step" },
269
- { type: "text", text: "Done." },
270
- ]);
271
- });
272
-
273
- test("buildFinalClaudeCodeContent falls back to cached text when the final turn only had tool calls", () => {
274
- const finalContent = buildFinalClaudeCodeContent(
275
- [
276
- { type: "toolCall", id: "tc_2", name: "Edit", arguments: { file_path: "app.ts" } },
277
- ] as any,
278
- "",
279
- "User-facing answer",
280
- );
281
-
282
- assert.deepEqual(finalContent, [{ type: "text", text: "User-facing answer" }]);
283
- });
284
- });
285
-
286
- describe("stream-adapter — streaming content filtering follow-up (#3867)", () => {
287
- function makePartial(content: AssistantMessage["content"]): AssistantMessage {
288
- return {
289
- role: "assistant",
290
- content,
291
- api: "anthropic-messages",
292
- provider: "claude-code",
293
- model: "claude-sonnet-4-20250514",
294
- usage: {
295
- input: 0,
296
- output: 0,
297
- cacheRead: 0,
298
- cacheWrite: 0,
299
- totalTokens: 0,
300
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
301
- },
302
- stopReason: "stop",
303
- timestamp: Date.now(),
304
- };
305
- }
306
-
307
- test("sanitizeClaudeCodeStreamingEvent strips tool calls from streamed partials and remaps contentIndex", () => {
308
- const event = sanitizeClaudeCodeStreamingEvent({
309
- type: "text_delta",
310
- contentIndex: 2,
311
- delta: "Done.",
312
- partial: makePartial([
313
- { type: "toolCall", id: "tc_1", name: "ToolSearch", arguments: {} },
314
- { type: "thinking", thinking: "Planning next step" },
315
- { type: "text", text: "Done." },
316
- ] as any),
317
- });
318
-
319
- assert.ok(event, "text events should still be forwarded");
320
- assert.equal(event!.type, "text_delta");
321
- assert.equal((event! as any).contentIndex, 1);
322
- assert.deepEqual((event! as any).partial.content, [
323
- { type: "thinking", thinking: "Planning next step" },
324
- { type: "text", text: "Done." },
325
- ]);
326
- });
327
-
328
- test("sanitizeClaudeCodeStreamingEvent suppresses internal tool streaming events entirely", () => {
329
- const event = sanitizeClaudeCodeStreamingEvent({
330
- type: "toolcall_start",
331
- contentIndex: 0,
332
- partial: makePartial([
333
- { type: "toolCall", id: "tc_1", name: "Bash", arguments: {} },
334
- ] as any),
335
- });
336
-
337
- assert.equal(event, null);
338
- });
339
- });
340
-
341
314
  describe("stream-adapter — Windows Claude path lookup (#3770)", () => {
342
315
  test("getClaudeLookupCommand uses where on Windows", () => {
343
316
  assert.equal(getClaudeLookupCommand("win32"), "where claude");
@@ -119,6 +119,8 @@ export class AutoSession {
119
119
  pendingVerificationRetry: PendingVerificationRetry | null = null;
120
120
  readonly verificationRetryCount = new Map<string, number>();
121
121
  pausedSessionFile: string | null = null;
122
+ pausedUnitType: string | null = null;
123
+ pausedUnitId: string | null = null;
122
124
  resourceVersionOnStart: string | null = null;
123
125
  lastStateRebuildAt = 0;
124
126
 
@@ -223,6 +225,8 @@ export class AutoSession {
223
225
  this.pendingVerificationRetry = null;
224
226
  this.verificationRetryCount.clear();
225
227
  this.pausedSessionFile = null;
228
+ this.pausedUnitType = null;
229
+ this.pausedUnitId = null;
226
230
  this.resourceVersionOnStart = null;
227
231
  this.lastStateRebuildAt = 0;
228
232
 
@@ -178,7 +178,7 @@ export function incrementUatCount(basePath: string, mid: string, sid: string): n
178
178
  export function isVerificationNotApplicable(value: string): boolean {
179
179
  const v = (value ?? "").toLowerCase().trim().replace(/[.\s]+$/, "");
180
180
  if (!v || v === "none") return true;
181
- return /^(?:none[\s._-]*(?:required|needed|planned)?|n\/?a|not[\s._-]+(?:applicable|required|needed|provided)|no[\s._-]+operational[\s\S]*)$/i.test(v);
181
+ return /^(?:none(?:[\s._\u2014-]+[\s\S]*)?|n\/?a|not[\s._-]+(?:applicable|required|needed|provided)|no[\s._-]+operational[\s\S]*)$/i.test(v);
182
182
  }
183
183
 
184
184
  // ─── Rules ────────────────────────────────────────────────────────────────
@@ -15,6 +15,7 @@ import type {
15
15
  } from "@gsd/pi-coding-agent";
16
16
  import { deriveState } from "./state.js";
17
17
  import { loadFile, getManifestStatus } from "./files.js";
18
+ import type { InterruptedSessionAssessment } from "./interrupted-session.js";
18
19
  import {
19
20
  loadEffectiveGSDPreferences,
20
21
  resolveSkillDiscoveryMode,
@@ -23,16 +24,9 @@ import {
23
24
  import { ensureGsdSymlink, isInheritedRepo, validateProjectId } from "./repo-identity.js";
24
25
  import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
25
26
  import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
26
- import { gsdRoot, resolveMilestoneFile, milestonesDir } from "./paths.js";
27
+ import { gsdRoot, resolveMilestoneFile } from "./paths.js";
27
28
  import { invalidateAllCaches } from "./cache.js";
28
- import { synthesizeCrashRecovery } from "./session-forensics.js";
29
- import {
30
- writeLock,
31
- clearLock,
32
- readCrashLock,
33
- formatCrashInfo,
34
- isLockProcessAlive,
35
- } from "./crash-recovery.js";
29
+ import { writeLock, clearLock } from "./crash-recovery.js";
36
30
  import {
37
31
  acquireSessionLock,
38
32
  releaseSessionLock,
@@ -248,6 +242,7 @@ export async function bootstrapAutoSession(
248
242
  verboseMode: boolean,
249
243
  requestedStepMode: boolean,
250
244
  deps: BootstrapDeps,
245
+ interrupted: InterruptedSessionAssessment,
251
246
  ): Promise<boolean> {
252
247
  const {
253
248
  shouldUseWorktreeIsolation,
@@ -340,57 +335,27 @@ export async function bootstrapAutoSession(
340
335
  }
341
336
  }
342
337
 
338
+ if (ctx.model?.provider === "claude-code") {
339
+ try {
340
+ const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
341
+ const result = ensureProjectWorkflowMcpConfig(base);
342
+ if (result.status !== "unchanged") {
343
+ ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
344
+ }
345
+ } catch (err) {
346
+ ctx.ui.notify(
347
+ `Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
348
+ "warning",
349
+ );
350
+ }
351
+ }
352
+
343
353
  // Initialize GitServiceImpl
344
354
  s.gitService = new GitServiceImpl(
345
355
  s.basePath,
346
356
  loadEffectiveGSDPreferences()?.preferences?.git ?? {},
347
357
  );
348
358
 
349
- // Check for crash from previous session. Skip our own fresh bootstrap lock.
350
- const crashLock = readCrashLock(base);
351
- if (crashLock && crashLock.pid !== process.pid) {
352
- if (isLockProcessAlive(crashLock)) {
353
- ctx.ui.notify(
354
- `Another auto-mode session (PID ${crashLock.pid}) appears to be running.\nStop it with \`kill ${crashLock.pid}\` before starting a new session.`,
355
- "error",
356
- );
357
- return releaseLockAndReturn();
358
- }
359
- const recoveredMid = parseUnitId(crashLock.unitId).milestone;
360
- const milestoneAlreadyComplete = recoveredMid
361
- ? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
362
- : false;
363
-
364
- if (milestoneAlreadyComplete) {
365
- ctx.ui.notify(
366
- `Crash recovery: discarding stale context for ${crashLock.unitId} — milestone ${recoveredMid} is already complete.`,
367
- "info",
368
- );
369
- } else {
370
- const activityDir = join(gsdRoot(base), "activity");
371
- const recovery = synthesizeCrashRecovery(
372
- base,
373
- crashLock.unitType,
374
- crashLock.unitId,
375
- crashLock.sessionFile,
376
- activityDir,
377
- );
378
- if (recovery && recovery.trace.toolCallCount > 0) {
379
- s.pendingCrashRecovery = recovery.prompt;
380
- ctx.ui.notify(
381
- `${formatCrashInfo(crashLock)}\nRecovered ${recovery.trace.toolCallCount} tool calls from crashed session. Resuming with full context.`,
382
- "warning",
383
- );
384
- } else {
385
- ctx.ui.notify(
386
- `${formatCrashInfo(crashLock)}\nNo session data recovered. Resuming from disk state.`,
387
- "warning",
388
- );
389
- }
390
- }
391
- clearLock(base);
392
- }
393
-
394
359
  // ── Debug mode ──
395
360
  if (!isDebugEnabled() && process.env.GSD_DEBUG === "1") {
396
361
  enableDebug(base);
@@ -410,6 +375,10 @@ export async function bootstrapAutoSession(
410
375
  ctx.ui.notify(`Debug logging enabled → ${getDebugLogPath()}`, "info");
411
376
  }
412
377
 
378
+ if (interrupted.classification !== "recoverable") {
379
+ s.pendingCrashRecovery = null;
380
+ }
381
+
413
382
  // Invalidate caches before initial state derivation
414
383
  invalidateAllCaches();
415
384
 
@@ -909,4 +878,3 @@ export async function bootstrapAutoSession(
909
878
  throw err;
910
879
  }
911
880
  }
912
-
@@ -1137,6 +1137,7 @@ function copyPlanningArtifacts(srcBase: string, wtPath: string): void {
1137
1137
  const srcGsd = join(srcBase, ".gsd");
1138
1138
  const dstGsd = join(wtPath, ".gsd");
1139
1139
  if (!existsSync(srcGsd)) return;
1140
+ if (isSamePath(srcGsd, dstGsd)) return;
1140
1141
 
1141
1142
  // Copy milestones/ directory (planning files, roadmaps, plans, research)
1142
1143
  safeCopyRecursive(join(srcGsd, "milestones"), join(dstGsd, "milestones"), {
@@ -1420,8 +1421,31 @@ export function mergeMilestoneToMain(
1420
1421
  const worktreeCwd = process.cwd();
1421
1422
  const milestoneBranch = autoWorktreeBranch(milestoneId);
1422
1423
 
1423
- // 1. Auto-commit dirty state in worktree before leaving
1424
- autoCommitDirtyState(worktreeCwd);
1424
+ // 1. Auto-commit dirty state before leaving.
1425
+ // Guard: when we entered through an auto-worktree (originalBase is set),
1426
+ // only auto-commit when cwd is on the milestone branch. In parallel mode,
1427
+ // cwd may be on the integration branch after a prior merge's
1428
+ // MergeConflictError left cwd unrestored. Auto-committing on the
1429
+ // integration branch captures dirty files from OTHER milestones under a
1430
+ // misleading commit message, contaminating the main branch (#2929).
1431
+ //
1432
+ // When originalBase is null (branch mode, no worktree), autoCommitDirtyState
1433
+ // runs unconditionally — the caller is responsible for cwd placement.
1434
+ {
1435
+ let shouldAutoCommit = true;
1436
+ if (originalBase !== null) {
1437
+ try {
1438
+ const currentBranch = nativeGetCurrentBranch(worktreeCwd);
1439
+ shouldAutoCommit = currentBranch === milestoneBranch;
1440
+ } catch {
1441
+ // If we can't determine the branch, skip the auto-commit to be safe
1442
+ shouldAutoCommit = false;
1443
+ }
1444
+ }
1445
+ if (shouldAutoCommit) {
1446
+ autoCommitDirtyState(worktreeCwd);
1447
+ }
1448
+ }
1425
1449
 
1426
1450
  // Reconcile worktree DB into main DB before leaving worktree context.
1427
1451
  // Skip when both paths resolve to the same physical file (shared WAL /
@@ -1778,6 +1802,12 @@ export function mergeMilestoneToMain(
1778
1802
  }
1779
1803
  }
1780
1804
  restoreShelter();
1805
+ // Restore cwd so the caller is not stranded on the integration branch.
1806
+ // Without this, the next mergeMilestoneToMain call in a parallel merge
1807
+ // sequence uses process.cwd() (now the project root) as worktreeCwd,
1808
+ // causing autoCommitDirtyState to commit unrelated milestone files to
1809
+ // the integration branch (#2929).
1810
+ process.chdir(previousCwd);
1781
1811
  throw new MergeConflictError(
1782
1812
  codeConflicts,
1783
1813
  "squash",
@@ -1975,23 +2005,38 @@ export function mergeMilestoneToMain(
1975
2005
  // changes (e.g. nativeHasChanges cache returned stale false, or auto-commit
1976
2006
  // silently failed), force one final commit so code is not destroyed by
1977
2007
  // `git worktree remove --force`.
2008
+ //
2009
+ // Guard: only run when worktreeCwd is on the milestone branch (#2929).
2010
+ // In parallel mode or branch-mode merges, worktreeCwd may be the project
2011
+ // root on the integration branch. Committing dirty state there would
2012
+ // capture unrelated files from other milestones.
1978
2013
  if (existsSync(worktreeCwd)) {
2014
+ let preTeardownBranch: string | null = null;
1979
2015
  try {
1980
- const dirtyCheck = nativeWorkingTreeStatus(worktreeCwd);
1981
- if (dirtyCheck) {
2016
+ preTeardownBranch = nativeGetCurrentBranch(worktreeCwd);
2017
+ } catch (err) {
2018
+ debugLog("mergeMilestoneToMain", { phase: "pre-teardown-branch-detect-failed", error: String(err) });
2019
+ }
2020
+ const isOnMilestoneBranch = preTeardownBranch === milestoneBranch;
2021
+
2022
+ if (isOnMilestoneBranch) {
2023
+ try {
2024
+ const dirtyCheck = nativeWorkingTreeStatus(worktreeCwd);
2025
+ if (dirtyCheck) {
2026
+ debugLog("mergeMilestoneToMain", {
2027
+ phase: "pre-teardown-dirty",
2028
+ worktreeCwd,
2029
+ status: dirtyCheck.slice(0, 200),
2030
+ });
2031
+ nativeAddAllWithExclusions(worktreeCwd, RUNTIME_EXCLUSION_PATHS);
2032
+ nativeCommit(worktreeCwd, "chore: pre-teardown auto-commit of uncommitted worktree changes");
2033
+ }
2034
+ } catch (e) {
1982
2035
  debugLog("mergeMilestoneToMain", {
1983
- phase: "pre-teardown-dirty",
1984
- worktreeCwd,
1985
- status: dirtyCheck.slice(0, 200),
2036
+ phase: "pre-teardown-commit-error",
2037
+ error: String(e),
1986
2038
  });
1987
- nativeAddAllWithExclusions(worktreeCwd, RUNTIME_EXCLUSION_PATHS);
1988
- nativeCommit(worktreeCwd, "chore: pre-teardown auto-commit of uncommitted worktree changes");
1989
2039
  }
1990
- } catch (e) {
1991
- debugLog("mergeMilestoneToMain", {
1992
- phase: "pre-teardown-commit-error",
1993
- error: String(e),
1994
- });
1995
2040
  }
1996
2041
  }
1997
2042
 
@@ -2020,4 +2065,3 @@ export function mergeMilestoneToMain(
2020
2065
 
2021
2066
  return { commitMessage, pushed, prCreated, codeFilesChanged };
2022
2067
  }
2023
-