gsd-pi 2.67.0-dev.509bd95 → 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 (227) 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 +10 -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 +121 -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 +37 -0
  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 +90 -19
  20. package/dist/web/standalone/.next/BUILD_ID +1 -1
  21. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  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 +15 -15
  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 +4 -2
  61. package/packages/mcp-server/dist/cli.d.ts +9 -0
  62. package/packages/mcp-server/dist/cli.d.ts.map +1 -0
  63. package/packages/mcp-server/dist/cli.js +58 -0
  64. package/packages/mcp-server/dist/cli.js.map +1 -0
  65. package/packages/mcp-server/dist/index.d.ts +20 -0
  66. package/packages/mcp-server/dist/index.d.ts.map +1 -0
  67. package/packages/mcp-server/dist/index.js +14 -0
  68. package/packages/mcp-server/dist/index.js.map +1 -0
  69. package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
  70. package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
  71. package/packages/mcp-server/dist/readers/captures.js +67 -0
  72. package/packages/mcp-server/dist/readers/captures.js.map +1 -0
  73. package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
  74. package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
  75. package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
  76. package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
  77. package/packages/mcp-server/dist/readers/index.d.ts +14 -0
  78. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
  79. package/packages/mcp-server/dist/readers/index.js +10 -0
  80. package/packages/mcp-server/dist/readers/index.js.map +1 -0
  81. package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
  82. package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
  83. package/packages/mcp-server/dist/readers/knowledge.js +82 -0
  84. package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
  85. package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
  86. package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
  87. package/packages/mcp-server/dist/readers/metrics.js +74 -0
  88. package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
  89. package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
  90. package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
  91. package/packages/mcp-server/dist/readers/paths.js +199 -0
  92. package/packages/mcp-server/dist/readers/paths.js.map +1 -0
  93. package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
  94. package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
  95. package/packages/mcp-server/dist/readers/roadmap.js +194 -0
  96. package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
  97. package/packages/mcp-server/dist/readers/state.d.ts +43 -0
  98. package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
  99. package/packages/mcp-server/dist/readers/state.js +184 -0
  100. package/packages/mcp-server/dist/readers/state.js.map +1 -0
  101. package/packages/mcp-server/dist/server.d.ts +28 -0
  102. package/packages/mcp-server/dist/server.d.ts.map +1 -0
  103. package/packages/mcp-server/dist/server.js +319 -0
  104. package/packages/mcp-server/dist/server.js.map +1 -0
  105. package/packages/mcp-server/dist/session-manager.d.ts +54 -0
  106. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
  107. package/packages/mcp-server/dist/session-manager.js +284 -0
  108. package/packages/mcp-server/dist/session-manager.js.map +1 -0
  109. package/packages/mcp-server/dist/types.d.ts +61 -0
  110. package/packages/mcp-server/dist/types.d.ts.map +1 -0
  111. package/packages/mcp-server/dist/types.js +11 -0
  112. package/packages/mcp-server/dist/types.js.map +1 -0
  113. package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
  114. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
  115. package/packages/mcp-server/dist/workflow-tools.js +532 -0
  116. package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
  117. package/packages/mcp-server/src/workflow-tools.ts +13 -2
  118. package/packages/mcp-server/tsconfig.json +1 -1
  119. package/packages/pi-agent-core/dist/agent-loop.js +14 -6
  120. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  121. package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
  122. package/packages/pi-agent-core/src/agent-loop.ts +20 -6
  123. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts +43 -0
  124. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
  125. package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
  126. package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
  127. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
  128. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
  129. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
  130. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
  131. package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
  132. package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/index.js +1 -0
  134. package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
  147. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
  149. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -12
  153. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  154. package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
  155. package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
  156. package/packages/pi-coding-agent/src/core/index.ts +2 -0
  157. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
  158. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
  159. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
  160. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
  161. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
  162. package/packages/rpc-client/dist/index.d.ts +10 -0
  163. package/packages/rpc-client/dist/index.d.ts.map +1 -0
  164. package/packages/rpc-client/dist/index.js +9 -0
  165. package/packages/rpc-client/dist/index.js.map +1 -0
  166. package/packages/rpc-client/dist/jsonl.d.ts +17 -0
  167. package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
  168. package/packages/rpc-client/dist/jsonl.js +54 -0
  169. package/packages/rpc-client/dist/jsonl.js.map +1 -0
  170. package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
  171. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
  172. package/packages/rpc-client/dist/rpc-client.js +541 -0
  173. package/packages/rpc-client/dist/rpc-client.js.map +1 -0
  174. package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
  175. package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
  176. package/packages/rpc-client/dist/rpc-client.test.js +477 -0
  177. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
  178. package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
  179. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
  180. package/packages/rpc-client/dist/rpc-types.js +12 -0
  181. package/packages/rpc-client/dist/rpc-types.js.map +1 -0
  182. package/scripts/ensure-workspace-builds.cjs +2 -0
  183. package/scripts/link-workspace-packages.cjs +21 -14
  184. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +190 -93
  185. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +89 -116
  186. package/src/resources/extensions/gsd/auto/session.ts +10 -0
  187. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  188. package/src/resources/extensions/gsd/auto-start.ts +23 -55
  189. package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
  190. package/src/resources/extensions/gsd/auto.ts +133 -64
  191. package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
  192. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  193. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
  194. package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
  195. package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
  196. package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
  197. package/src/resources/extensions/gsd/doctor.ts +9 -5
  198. package/src/resources/extensions/gsd/guided-flow.ts +42 -36
  199. package/src/resources/extensions/gsd/init-wizard.ts +40 -0
  200. package/src/resources/extensions/gsd/interrupted-session.ts +224 -0
  201. package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
  202. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
  203. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +668 -2
  204. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
  205. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
  206. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
  207. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
  208. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
  209. package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
  210. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
  211. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
  212. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
  213. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
  214. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -0
  215. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
  216. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
  217. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +11 -0
  218. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +212 -13
  219. package/src/resources/extensions/gsd/workflow-mcp.ts +106 -19
  220. package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
  221. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
  222. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
  224. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
  225. package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
  226. /package/dist/web/standalone/.next/static/{mHJZ3Z8yGRzZ32BmQs-I7 → yh2vT27L1E6PChb_C1N_F}/_buildManifest.js +0 -0
  227. /package/dist/web/standalone/.next/static/{mHJZ3Z8yGRzZ32BmQs-I7 → yh2vT27L1E6PChb_C1N_F}/_ssgManifest.js +0 -0
@@ -70,7 +70,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
70
70
  { cmd: "templates", desc: "List available workflow templates" },
71
71
  { cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
72
72
  { cmd: "fast", desc: "Toggle OpenAI service tier (on/off/flex/status)" },
73
- { cmd: "mcp", desc: "MCP server status and connectivity check (status, check <server>)" },
73
+ { cmd: "mcp", desc: "MCP server status, connectivity, and local config bootstrap (status, check, init)" },
74
74
  { cmd: "rethink", desc: "Conversational project reorganization — reorder, park, discard, add milestones" },
75
75
  { cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
76
76
  { cmd: "codebase", desc: "Generate, refresh, and inspect the codebase map cache (.gsd/CODEBASE.md)" },
@@ -201,6 +201,7 @@ const NESTED_COMPLETIONS: CompletionMap = {
201
201
  mcp: [
202
202
  { cmd: "status", desc: "Show all MCP server statuses (default)" },
203
203
  { cmd: "check", desc: "Detailed status for a specific server" },
204
+ { cmd: "init", desc: "Write .mcp.json for the local GSD workflow MCP server" },
204
205
  ],
205
206
  doctor: [
206
207
  { cmd: "fix", desc: "Auto-fix detected issues" },
@@ -60,7 +60,7 @@ export function showHelp(ctx: ExtensionCommandContext): void {
60
60
  " /gsd hooks Show post-unit hook configuration",
61
61
  " /gsd extensions Manage extensions [list|enable|disable|info]",
62
62
  " /gsd fast Toggle OpenAI service tier [on|off|flex|status]",
63
- " /gsd mcp MCP server status and connectivity [status|check <server>]",
63
+ " /gsd mcp MCP server status and connectivity [status|check <server>|init [dir]]",
64
64
  "",
65
65
  "MAINTENANCE",
66
66
  " /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
@@ -7,12 +7,15 @@
7
7
  * /gsd mcp — Overview of all servers (alias: /gsd mcp status)
8
8
  * /gsd mcp status — Same as bare /gsd mcp
9
9
  * /gsd mcp check <srv> — Detailed status for a specific server
10
+ * /gsd mcp init [dir] — Write project-local GSD workflow MCP config
10
11
  */
11
12
 
12
13
  import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
13
14
 
14
15
  import { existsSync, readFileSync } from "node:fs";
15
- import { join } from "node:path";
16
+ import { join, resolve } from "node:path";
17
+
18
+ import { ensureProjectWorkflowMcpConfig } from "./mcp-project-config.js";
16
19
 
17
20
  // ─── Types ──────────────────────────────────────────────────────────────────
18
21
 
@@ -28,6 +31,28 @@ export interface McpServerDetail extends McpServerStatus {
28
31
  tools: string[];
29
32
  }
30
33
 
34
+ export function formatMcpInitResult(
35
+ status: "created" | "updated" | "unchanged",
36
+ configPath: string,
37
+ targetPath: string,
38
+ ): string {
39
+ const summary =
40
+ status === "created"
41
+ ? "Created project MCP config."
42
+ : status === "updated"
43
+ ? "Updated project MCP config."
44
+ : "Project MCP config is already up to date.";
45
+
46
+ return [
47
+ summary,
48
+ "",
49
+ `Project: ${targetPath}`,
50
+ `Config: ${configPath}`,
51
+ "",
52
+ "Claude Code can now load the GSD workflow MCP server from this folder.",
53
+ ].join("\n");
54
+ }
55
+
31
56
  // ─── Config reader (standalone — does not import mcp-client internals) ──────
32
57
 
33
58
  interface McpServerRawConfig {
@@ -94,6 +119,7 @@ export function formatMcpStatusReport(servers: McpServerStatus[]): string {
94
119
  "No MCP servers configured.",
95
120
  "",
96
121
  "Add servers to .mcp.json or .gsd/mcp.json to enable MCP integrations.",
122
+ "Tip: run /gsd mcp init . to write the local GSD workflow MCP config.",
97
123
  "See: https://modelcontextprotocol.io/quickstart",
98
124
  ].join("\n");
99
125
  }
@@ -153,12 +179,31 @@ export async function handleMcpStatus(
153
179
  args: string,
154
180
  ctx: ExtensionCommandContext,
155
181
  ): Promise<void> {
156
- const trimmed = args.trim().toLowerCase();
182
+ const trimmed = args.trim();
183
+ const lowered = trimmed.toLowerCase();
157
184
  const configs = readMcpConfigs();
158
185
 
186
+ // /gsd mcp init [dir]
187
+ if (!lowered || lowered === "status") {
188
+ // handled below
189
+ } else if (lowered === "init" || lowered.startsWith("init ")) {
190
+ const rawPath = trimmed.slice("init".length).trim();
191
+ const targetPath = resolve(rawPath || ".");
192
+ try {
193
+ const result = ensureProjectWorkflowMcpConfig(targetPath);
194
+ ctx.ui.notify(formatMcpInitResult(result.status, result.configPath, targetPath), "info");
195
+ } catch (err) {
196
+ ctx.ui.notify(
197
+ `Failed to prepare MCP config for ${targetPath}: ${err instanceof Error ? err.message : String(err)}`,
198
+ "error",
199
+ );
200
+ }
201
+ return;
202
+ }
203
+
159
204
  // /gsd mcp check <server>
160
- if (trimmed.startsWith("check ")) {
161
- const serverName = args.trim().slice("check ".length).trim();
205
+ if (lowered.startsWith("check ")) {
206
+ const serverName = trimmed.slice("check ".length).trim();
162
207
  const config = configs.find((c) => c.name === serverName);
163
208
  if (!config) {
164
209
  const available = configs.map((c) => c.name).join(", ") || "(none)";
@@ -202,7 +247,7 @@ export async function handleMcpStatus(
202
247
  }
203
248
 
204
249
  // /gsd mcp or /gsd mcp status
205
- if (!trimmed || trimmed === "status") {
250
+ if (!lowered || lowered === "status") {
206
251
  // Build status for each server
207
252
  const statuses: McpServerStatus[] = [];
208
253
 
@@ -239,9 +284,10 @@ export async function handleMcpStatus(
239
284
 
240
285
  // Unknown subcommand
241
286
  ctx.ui.notify(
242
- "Usage: /gsd mcp [status|check <server>]\n\n" +
287
+ "Usage: /gsd mcp [status|check <server>|init [dir]]\n\n" +
243
288
  " status Show all MCP server statuses (default)\n" +
244
- " check <server> Detailed status for a specific server",
289
+ " check <server> Detailed status for a specific server\n" +
290
+ " init [dir] Write .mcp.json for the local GSD workflow MCP server",
245
291
  "warning",
246
292
  );
247
293
  }
@@ -10,7 +10,7 @@ import { deriveState, isMilestoneComplete } from "./state.js";
10
10
  import { listWorktrees, resolveGitDir, worktreesDir } from "./worktree-manager.js";
11
11
  import { abortAndReset } from "./git-self-heal.js";
12
12
  import { RUNTIME_EXCLUSION_PATHS, resolveMilestoneIntegrationBranch, writeIntegrationBranch } from "./git-service.js";
13
- import { nativeIsRepo, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddAllWithExclusions, nativeCommit } from "./native-git-bridge.js";
13
+ import { nativeIsRepo, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddTracked, nativeCommit } from "./native-git-bridge.js";
14
14
  import { getAllWorktreeHealth } from "./worktree-health.js";
15
15
  import { loadEffectiveGSDPreferences } from "./preferences.js";
16
16
 
@@ -386,19 +386,19 @@ export async function checkGitHealth(
386
386
  code: "stale_uncommitted_changes",
387
387
  scope: "project",
388
388
  unitId: "project",
389
- message: `Uncommitted changes detected with no commit in ${mins} minute${mins === 1 ? "" : "s"} (threshold: ${thresholdMinutes}m). Snapshotting uncommitted changes.`,
389
+ message: `Uncommitted changes detected with no commit in ${mins} minute${mins === 1 ? "" : "s"} (threshold: ${thresholdMinutes}m). Snapshotting tracked files.`,
390
390
  fixable: true,
391
391
  });
392
392
 
393
393
  if (shouldFix("stale_uncommitted_changes")) {
394
394
  try {
395
- nativeAddAllWithExclusions(basePath, RUNTIME_EXCLUSION_PATHS);
395
+ nativeAddTracked(basePath);
396
396
  const commitMsg = `gsd snapshot: uncommitted changes after ${mins}m inactivity`;
397
397
  const result = nativeCommit(basePath, commitMsg);
398
398
  if (result) {
399
399
  fixesApplied.push(`created gsd snapshot after ${mins}m of uncommitted changes`);
400
400
  } else {
401
- fixesApplied.push("gsd snapshot skipped — nothing to commit after staging changes");
401
+ fixesApplied.push("gsd snapshot skipped — nothing to commit after staging tracked files");
402
402
  }
403
403
  } catch {
404
404
  fixesApplied.push("failed to create gsd snapshot commit");
@@ -21,8 +21,8 @@ import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.j
21
21
  import { abortAndReset } from "./git-self-heal.js";
22
22
  import { rebuildState } from "./doctor.js";
23
23
  import { deriveState } from "./state.js";
24
- import { RUNTIME_EXCLUSION_PATHS, resolveMilestoneIntegrationBranch } from "./git-service.js";
25
- import { nativeIsRepo, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddAllWithExclusions, nativeCommit } from "./native-git-bridge.js";
24
+ import { resolveMilestoneIntegrationBranch } from "./git-service.js";
25
+ import { nativeIsRepo, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddTracked, nativeCommit } from "./native-git-bridge.js";
26
26
  import { loadEffectiveGSDPreferences } from "./preferences.js";
27
27
  import { runEnvironmentChecks } from "./doctor-environment.js";
28
28
 
@@ -312,7 +312,7 @@ export async function preDispatchHealthGate(basePath: string): Promise<PreDispat
312
312
  if (minutesSinceCommit >= thresholdMinutes) {
313
313
  const mins = Math.floor(minutesSinceCommit);
314
314
  try {
315
- nativeAddAllWithExclusions(basePath, RUNTIME_EXCLUSION_PATHS);
315
+ nativeAddTracked(basePath);
316
316
  const commitMsg = `gsd snapshot: pre-dispatch, uncommitted changes after ${mins}m inactivity`;
317
317
  const result = nativeCommit(basePath, commitMsg);
318
318
  if (result) {
@@ -8,6 +8,7 @@ import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSl
8
8
  import { deriveState, isMilestoneComplete } from "./state.js";
9
9
  import { invalidateAllCaches } from "./cache.js";
10
10
  import { loadEffectiveGSDPreferences, type GSDPreferences } from "./preferences.js";
11
+ import { isClosedStatus } from "./status-guards.js";
11
12
 
12
13
  import type { DoctorIssue, DoctorIssueCode, DoctorReport } from "./doctor-types.js";
13
14
  import { GLOBAL_STATE_CODES } from "./doctor-types.js";
@@ -474,15 +475,16 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
474
475
  if (!roadmapContent) continue;
475
476
 
476
477
  // Normalize slices: prefer DB, fall back to parser
477
- type NormSlice = RoadmapSliceEntry & { pending?: boolean };
478
+ type NormSlice = RoadmapSliceEntry & { pending?: boolean; skipped?: boolean };
478
479
  let slices: NormSlice[];
479
480
  if (isDbAvailable()) {
480
481
  const dbSlices = getMilestoneSlices(milestoneId);
481
482
  slices = dbSlices.map(s => ({
482
483
  id: s.id,
483
484
  title: s.title,
484
- done: s.status === "complete",
485
+ done: isClosedStatus(s.status),
485
486
  pending: s.status === "pending",
487
+ skipped: s.status === "skipped",
486
488
  risk: (s.risk || "medium") as RoadmapSliceEntry["risk"],
487
489
  depends: s.depends,
488
490
  demo: s.demo,
@@ -578,8 +580,9 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
578
580
  const slicePath = resolveSlicePath(basePath, milestoneId, slice.id);
579
581
  if (!slicePath) {
580
582
  // Pending slices haven't been planned yet — directories are created
581
- // lazily by ensurePreconditions() at dispatch time. Skip them.
582
- if (slice.pending) continue;
583
+ // lazily by ensurePreconditions() at dispatch time. Skipped slices are
584
+ // intentionally allowed to remain summary-less and directory-less.
585
+ if (slice.pending || slice.skipped) continue;
583
586
  const expectedPath = relSlicePath(basePath, milestoneId, slice.id);
584
587
  issues.push({
585
588
  severity: slice.done ? "warning" : "error",
@@ -603,7 +606,8 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
603
606
  const tasksDir = resolveTasksDir(basePath, milestoneId, slice.id);
604
607
  if (!tasksDir) {
605
608
  // Pending slices haven't been planned yet — tasks/ is created on demand.
606
- if (slice.pending) continue;
609
+ // Skipped slices may legitimately never create tasks/.
610
+ if (slice.pending || slice.skipped) continue;
607
611
  issues.push({
608
612
  severity: slice.done ? "warning" : "error",
609
613
  code: "missing_tasks_dir",
@@ -16,7 +16,12 @@ import { buildSkillActivationBlock } from "./auto-prompts.js";
16
16
  import { deriveState } from "./state.js";
17
17
  import { invalidateAllCaches } from "./cache.js";
18
18
  import { startAuto } from "./auto.js";
19
- import { readCrashLock, clearLock, formatCrashInfo } from "./crash-recovery.js";
19
+ import { clearLock } from "./crash-recovery.js";
20
+ import {
21
+ assessInterruptedSession,
22
+ formatInterruptedSessionRunningMessage,
23
+ formatInterruptedSessionSummary,
24
+ } from "./interrupted-session.js";
20
25
  import { listUnitRuntimeRecords, clearUnitRuntimeRecord } from "./unit-runtime.js";
21
26
  import { resolveExpectedArtifactPath } from "./auto.js";
22
27
  import {
@@ -215,17 +220,9 @@ export function checkAutoStartAfterDiscuss(): boolean {
215
220
 
216
221
  // Gate 4: Discussion manifest process verification (multi-milestone only)
217
222
  // The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
218
- // If the project is multi-milestone, the manifest is required. When it is
219
- // missing, fail closed instead of assuming the discussion finished.
223
+ // When it exists, validate it before auto-starting. Project history alone is
224
+ // not a reliable signal for the current discussion mode.
220
225
  const manifestPath = join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json");
221
- const requiresManifest = projectIds.length > 1 || findMilestoneIds(basePath).length > 1;
222
- if (requiresManifest && !existsSync(manifestPath)) {
223
- ctx.ui.notify(
224
- "Multi-milestone discussion manifest is missing. Auto-start will remain paused until the manifest is written.",
225
- "warning",
226
- );
227
- return false;
228
- }
229
226
  if (existsSync(manifestPath)) {
230
227
  try {
231
228
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -1322,36 +1319,45 @@ export async function showSmartEntry(
1322
1319
  // ── Self-heal stale runtime records from crashed auto-mode sessions ──
1323
1320
  selfHealRuntimeRecords(basePath, ctx);
1324
1321
 
1325
- // Check for crash from previous auto-mode session.
1326
- // Skip if the lock was written by the current process — acquireSessionLock()
1327
- // writes to the same file, so we'd always false-positive (#1398).
1328
- const crashLock = readCrashLock(basePath);
1329
- if (crashLock && crashLock.pid !== process.pid) {
1330
- clearLock(basePath);
1322
+ const interrupted = await assessInterruptedSession(basePath);
1323
+ if (interrupted.classification === "running") {
1324
+ ctx.ui.notify(formatInterruptedSessionRunningMessage(interrupted), "error");
1325
+ return;
1326
+ }
1331
1327
 
1332
- // Bootstrap crash with zero completed units = no work was lost.
1333
- // Auto-discard instead of prompting the user — this commonly happens
1334
- // when the user exits during init wizard or discuss phase before any
1335
- // real auto-mode work begins.
1336
- const isBootstrapCrash = crashLock.unitType === "starting"
1337
- && crashLock.unitId === "bootstrap";
1338
-
1339
- if (!isBootstrapCrash) {
1340
- const resume = await showNextAction(ctx, {
1341
- title: "GSD — Interrupted Session Detected",
1342
- summary: [formatCrashInfo(crashLock)],
1343
- actions: [
1344
- { id: "resume", label: "Resume with /gsd auto", description: "Pick up where it left off", recommended: true },
1345
- { id: "continue", label: "Continue manually", description: "Open the wizard as normal" },
1346
- ],
1347
- });
1348
- if (resume === "resume") {
1349
- await startAuto(ctx, pi, basePath, false);
1350
- return;
1328
+ if (interrupted.classification === "stale") {
1329
+ clearLock(basePath);
1330
+ if (interrupted.pausedSession) {
1331
+ try {
1332
+ unlinkSync(join(gsdRoot(basePath), "runtime", "paused-session.json"));
1333
+ } catch (e) {
1334
+ logWarning("guided", `stale pause file cleanup failed: ${(e as Error).message}`, { file: "guided-flow.ts" });
1351
1335
  }
1352
1336
  }
1337
+ } else if (interrupted.classification === "recoverable") {
1338
+ if (interrupted.lock) clearLock(basePath);
1339
+ const resumeLabel = interrupted.pausedSession?.stepMode
1340
+ ? "Resume with /gsd next"
1341
+ : "Resume with /gsd auto";
1342
+ const resume = await showNextAction(ctx, {
1343
+ title: "GSD — Interrupted Session Detected",
1344
+ summary: formatInterruptedSessionSummary(interrupted),
1345
+ actions: [
1346
+ { id: "resume", label: resumeLabel, description: "Pick up where it left off", recommended: true },
1347
+ { id: "continue", label: "Continue manually", description: "Open the wizard as normal" },
1348
+ ],
1349
+ });
1350
+ if (resume === "resume") {
1351
+ await startAuto(ctx, pi, basePath, false, {
1352
+ interrupted,
1353
+ step: interrupted.pausedSession?.stepMode ?? false,
1354
+ });
1355
+ return;
1356
+ }
1353
1357
  }
1354
1358
 
1359
+ // Always derive from the project root — the assessment may have derived
1360
+ // state from a worktree path that was cleaned up in the stale branch above.
1355
1361
  const state = await deriveState(basePath);
1356
1362
 
1357
1363
  // Rebuild STATE.md from derived state before any dispatch (#3475).
@@ -235,6 +235,16 @@ export async function showProjectInit(
235
235
  // ── Step 9: Bootstrap .gsd/ ────────────────────────────────────────────────
236
236
  bootstrapGsdDirectory(basePath, prefs, signals);
237
237
 
238
+ // Initialize SQLite database so GSD starts in full-capability mode (#3880).
239
+ // Without this, isDbAvailable() returns false and GSD enters degraded
240
+ // markdown-only mode until a tool handler happens to call ensureDbOpen().
241
+ try {
242
+ const { ensureDbOpen } = await import("./bootstrap/dynamic-tools.js");
243
+ await ensureDbOpen(basePath);
244
+ } catch {
245
+ // Non-fatal — DB creation failure should not block project init
246
+ }
247
+
238
248
  // Ensure .gitignore
239
249
  ensureGitignore(basePath);
240
250
  untrackRuntimeFiles(basePath);
@@ -250,6 +260,35 @@ export async function showProjectInit(
250
260
  // Non-fatal — codebase map generation failure should never block project init
251
261
  }
252
262
 
263
+ // Write initial STATE.md so it exists before the first /gsd invocation.
264
+ // The explicit /gsd init path (ops.ts) returns without entering showSmartEntry(),
265
+ // which would otherwise generate STATE.md at guided-flow.ts:1358.
266
+ try {
267
+ const { deriveState } = await import("./state.js");
268
+ const { buildStateMarkdown } = await import("./doctor.js");
269
+ const { saveFile } = await import("./files.js");
270
+ const { resolveGsdRootFile } = await import("./paths.js");
271
+ const state = await deriveState(basePath);
272
+ await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
273
+ } catch {
274
+ // Non-fatal — STATE.md will be regenerated on next /gsd invocation
275
+ }
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
+ }
290
+ }
291
+
253
292
  ctx.ui.notify("GSD initialized. Starting your first milestone...", "info");
254
293
 
255
294
  return { completed: true, bootstrapped: true };
@@ -433,6 +472,7 @@ function bootstrapGsdDirectory(
433
472
 
434
473
  const gsd = gsdRoot(basePath);
435
474
  mkdirSync(join(gsd, "milestones"), { recursive: true });
475
+ mkdirSync(join(gsd, "runtime"), { recursive: true });
436
476
 
437
477
  // Write PREFERENCES.md from wizard answers
438
478
  const preferencesContent = buildPreferencesFile(prefs);
@@ -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
+ }