@opengsd/gsd-pi 1.0.2-dev.5961fbf → 1.0.2-dev.5f7864c

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 (223) hide show
  1. package/README.md +63 -12
  2. package/dist/onboarding.js +22 -3
  3. package/dist/resource-loader.d.ts +2 -0
  4. package/dist/resource-loader.js +18 -1
  5. package/dist/resources/.managed-resources-content-hash +1 -1
  6. package/dist/resources/extensions/context7/index.js +12 -2
  7. package/dist/resources/extensions/get-secrets-from-user.js +16 -16
  8. package/dist/resources/extensions/google-cli/index.js +30 -0
  9. package/dist/resources/extensions/google-cli/models.js +55 -0
  10. package/dist/resources/extensions/google-cli/package.json +11 -0
  11. package/dist/resources/extensions/google-cli/readiness.js +12 -0
  12. package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
  13. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  14. package/dist/resources/extensions/gsd/auto-start.js +232 -49
  15. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
  16. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -15
  17. package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
  18. package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
  19. package/dist/resources/extensions/gsd/commands-handlers.js +3 -0
  20. package/dist/resources/extensions/gsd/commands-usage.js +105 -1
  21. package/dist/resources/extensions/gsd/config-overlay.js +20 -14
  22. package/dist/resources/extensions/gsd/context-overlay.js +22 -16
  23. package/dist/resources/extensions/gsd/dashboard-overlay.js +10 -23
  24. package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
  25. package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
  26. package/dist/resources/extensions/gsd/guided-flow.js +1 -1
  27. package/dist/resources/extensions/gsd/key-manager.js +45 -13
  28. package/dist/resources/extensions/gsd/notification-overlay.js +8 -9
  29. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +15 -13
  30. package/dist/resources/extensions/gsd/prompt-loader.js +2 -0
  31. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -2
  32. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  33. package/dist/resources/extensions/gsd/queue-reorder-ui.js +28 -18
  34. package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
  35. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -1
  36. package/dist/resources/extensions/gsd/tui/render-kit.js +51 -0
  37. package/dist/resources/extensions/gsd/vision-ask.js +22 -0
  38. package/dist/resources/extensions/gsd/visualizer-overlay.js +8 -36
  39. package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
  40. package/dist/resources/extensions/search-the-web/native-search.js +57 -8
  41. package/dist/resources/extensions/shared/confirm-ui.js +9 -6
  42. package/dist/resources/extensions/shared/dialog-frame.js +42 -0
  43. package/dist/resources/extensions/shared/interview-ui.js +42 -30
  44. package/dist/resources/extensions/shared/next-action-ui.js +6 -6
  45. package/dist/resources/shared/package-manager-detection.js +36 -0
  46. package/dist/update-check.d.ts +6 -2
  47. package/dist/update-check.js +7 -3
  48. package/dist/web/standalone/.next/BUILD_ID +1 -1
  49. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  50. package/dist/web/standalone/.next/build-manifest.json +2 -2
  51. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  52. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/index.html +1 -1
  70. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  77. package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
  78. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  80. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  81. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  82. package/package.json +1 -1
  83. package/packages/cloud-mcp-gateway/package.json +2 -2
  84. package/packages/contracts/package.json +1 -1
  85. package/packages/daemon/package.json +4 -4
  86. package/packages/gsd-agent-core/package.json +5 -5
  87. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts +12 -0
  88. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts.map +1 -0
  89. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js +45 -0
  90. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js.map +1 -0
  91. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts +3 -2
  92. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  93. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js +11 -11
  94. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js.map +1 -1
  95. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts +3 -3
  96. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  97. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js +13 -11
  98. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js.map +1 -1
  99. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts +3 -3
  100. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  101. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js +12 -10
  102. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js.map +1 -1
  103. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts +1 -0
  104. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts.map +1 -1
  105. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js +1 -0
  106. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js.map +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  108. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
  110. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
  111. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
  112. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  113. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
  114. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  115. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  116. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
  117. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  118. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
  119. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  120. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
  121. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  122. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
  123. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
  124. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
  125. package/packages/gsd-agent-modes/package.json +7 -7
  126. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  127. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  128. package/packages/mcp-server/package.json +3 -3
  129. package/packages/native/package.json +1 -1
  130. package/packages/pi-agent-core/dist/agent-loop.js +13 -13
  131. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  132. package/packages/pi-agent-core/package.json +1 -1
  133. package/packages/pi-ai/dist/models.generated.d.ts +57 -17
  134. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  135. package/packages/pi-ai/dist/models.generated.js +64 -28
  136. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  137. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  138. package/packages/pi-ai/dist/providers/anthropic.js +50 -0
  139. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  140. package/packages/pi-ai/dist/types.d.ts +2 -0
  141. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  142. package/packages/pi-ai/dist/types.js.map +1 -1
  143. package/packages/pi-ai/package.json +1 -1
  144. package/packages/pi-coding-agent/package.json +7 -7
  145. package/packages/pi-tui/package.json +1 -1
  146. package/packages/rpc-client/package.json +2 -2
  147. package/pkg/package.json +1 -1
  148. package/scripts/install/detect-existing.js +17 -3
  149. package/scripts/install/npm-global.js +103 -33
  150. package/scripts/install.js +1 -0
  151. package/src/resources/extensions/context7/index.ts +15 -2
  152. package/src/resources/extensions/get-secrets-from-user.ts +17 -16
  153. package/src/resources/extensions/google-cli/index.ts +34 -0
  154. package/src/resources/extensions/google-cli/models.ts +57 -0
  155. package/src/resources/extensions/google-cli/package.json +11 -0
  156. package/src/resources/extensions/google-cli/readiness.ts +15 -0
  157. package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
  158. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  159. package/src/resources/extensions/gsd/auto-start.ts +307 -56
  160. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
  161. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -15
  162. package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
  163. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
  164. package/src/resources/extensions/gsd/commands-handlers.ts +2 -0
  165. package/src/resources/extensions/gsd/commands-usage.ts +110 -5
  166. package/src/resources/extensions/gsd/config-overlay.ts +19 -16
  167. package/src/resources/extensions/gsd/context-overlay.ts +24 -19
  168. package/src/resources/extensions/gsd/dashboard-overlay.ts +14 -27
  169. package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
  170. package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
  171. package/src/resources/extensions/gsd/guided-flow.ts +1 -1
  172. package/src/resources/extensions/gsd/key-manager.ts +57 -14
  173. package/src/resources/extensions/gsd/notification-overlay.ts +12 -11
  174. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +16 -12
  175. package/src/resources/extensions/gsd/prompt-loader.ts +2 -0
  176. package/src/resources/extensions/gsd/prompts/discuss.md +4 -2
  177. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  178. package/src/resources/extensions/gsd/queue-reorder-ui.ts +29 -20
  179. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
  180. package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
  181. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +31 -0
  182. package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
  183. package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
  184. package/src/resources/extensions/gsd/tests/commands-usage.test.ts +97 -0
  185. package/src/resources/extensions/gsd/tests/context-chart.test.ts +9 -0
  186. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +25 -0
  187. package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +4 -2
  188. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
  189. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +6 -0
  190. package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
  191. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +6 -1
  192. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
  193. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +7 -1
  194. package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +46 -0
  195. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +4 -0
  196. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
  197. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
  198. package/src/resources/extensions/gsd/tests/tui-border-assertions.ts +28 -0
  199. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +14 -0
  200. package/src/resources/extensions/gsd/tests/vision-ask.test.ts +23 -0
  201. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -1
  202. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +60 -0
  203. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +54 -0
  204. package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
  205. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
  206. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
  207. package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
  208. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +56 -4
  209. package/src/resources/extensions/gsd/tui/render-kit.ts +82 -0
  210. package/src/resources/extensions/gsd/vision-ask.ts +28 -0
  211. package/src/resources/extensions/gsd/visualizer-overlay.ts +12 -40
  212. package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
  213. package/src/resources/extensions/search-the-web/native-search.ts +60 -8
  214. package/src/resources/extensions/shared/confirm-ui.ts +8 -12
  215. package/src/resources/extensions/shared/dialog-frame.ts +71 -0
  216. package/src/resources/extensions/shared/interview-ui.ts +43 -42
  217. package/src/resources/extensions/shared/next-action-ui.ts +6 -6
  218. package/src/resources/extensions/shared/tests/confirm-ui.test.ts +57 -0
  219. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +163 -0
  220. package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +55 -0
  221. package/src/resources/shared/package-manager-detection.ts +39 -0
  222. /package/dist/web/standalone/.next/static/{spUYLkQXoHJyxYOMH9VQy → IjxvcC7sl_MHNKXsUZrAy}/_buildManifest.js +0 -0
  223. /package/dist/web/standalone/.next/static/{spUYLkQXoHJyxYOMH9VQy → IjxvcC7sl_MHNKXsUZrAy}/_ssgManifest.js +0 -0
@@ -48,6 +48,7 @@ import {
48
48
  nativeBranchDelete,
49
49
  nativeWorktreeRemove,
50
50
  nativeCommitCountBetween,
51
+ nativeHasChanges,
51
52
  } from "./native-git-bridge.js";
52
53
  import { GitServiceImpl } from "./git-service.js";
53
54
  import {
@@ -242,24 +243,126 @@ export function resolveSurvivorRecoveryIsolationMode(
242
243
  return isolationMode;
243
244
  }
244
245
 
246
+ export type StrandedWorkRecoveryMode = "worktree" | "branch";
247
+
248
+ export type OrphanAuditActionKind =
249
+ | "in-progress-stranded-work"
250
+ | "complete-merged-branch"
251
+ | "complete-merged-worktree"
252
+ | "complete-unmerged-branch"
253
+ | "complete-branchless-worktree";
254
+
255
+ export interface OrphanAuditAction {
256
+ kind: OrphanAuditActionKind;
257
+ milestoneId: string;
258
+ message: string;
259
+ severity: "info" | "warning";
260
+ branch?: string;
261
+ commitsAhead?: number;
262
+ dirtyWorktree?: boolean;
263
+ worktreeDirExists?: boolean;
264
+ recoveryMode?: StrandedWorkRecoveryMode;
265
+ blocksAuto: boolean;
266
+ }
267
+
268
+ export interface OrphanAuditResult {
269
+ recovered: string[];
270
+ warnings: string[];
271
+ actions: OrphanAuditAction[];
272
+ blockingStrandedWork: OrphanAuditAction | null;
273
+ }
274
+
275
+ function isBlockingStrandedWorkAction(action: OrphanAuditAction): boolean {
276
+ return action.kind === "in-progress-stranded-work" && action.blocksAuto;
277
+ }
278
+
279
+ function detectWorktreeEvidence(
280
+ basePath: string,
281
+ milestoneId: string,
282
+ hasChanges: typeof nativeHasChanges,
283
+ ): { path: string | null; dirExists: boolean; dirty: boolean } {
284
+ const wtDir = getWorktreeDir(basePath, milestoneId);
285
+ const wtPath = getAutoWorktreePath(basePath, milestoneId);
286
+ let dirty = false;
287
+ if (wtPath) {
288
+ try {
289
+ dirty = hasChanges(wtPath);
290
+ } catch {
291
+ dirty = false;
292
+ }
293
+ }
294
+ return {
295
+ path: wtPath,
296
+ dirExists: existsSync(wtDir),
297
+ dirty,
298
+ };
299
+ }
300
+
301
+ function strandedWorkMessage(args: {
302
+ milestoneId: string;
303
+ branch?: string;
304
+ commitsAhead: number;
305
+ mainBranch: string;
306
+ dirtyWorktree: boolean;
307
+ worktreeDirExists: boolean;
308
+ recoveryMode: StrandedWorkRecoveryMode;
309
+ }): string {
310
+ const evidence: string[] = [];
311
+ if (args.branch && args.commitsAhead > 0) {
312
+ evidence.push(
313
+ `branch ${args.branch} has ${args.commitsAhead} commit(s) ahead of ${args.mainBranch}`,
314
+ );
315
+ }
316
+ if (args.dirtyWorktree) {
317
+ evidence.push("the worktree has uncommitted changes");
318
+ }
319
+ if (evidence.length === 0) {
320
+ evidence.push("physical git evidence exists");
321
+ }
322
+
323
+ const wtSuffix = args.worktreeDirExists
324
+ ? ` Worktree directory at .gsd/worktrees/${args.milestoneId}/ holds live work.`
325
+ : "";
326
+ const recovery = args.recoveryMode === "worktree"
327
+ ? "Recovering will adopt the existing worktree."
328
+ : "Recovering will adopt the milestone branch.";
329
+
330
+ return (
331
+ `Stranded work for in-progress milestone ${args.milestoneId}: ${evidence.join("; ")}.` +
332
+ wtSuffix +
333
+ ` ${recovery} Park or discard explicitly if abandoning.`
334
+ );
335
+ }
336
+
245
337
  export function auditOrphanedMilestoneBranches(
246
338
  basePath: string,
247
- isolationMode: "worktree" | "branch" | "none",
339
+ _isolationMode: "worktree" | "branch" | "none",
248
340
  gitDeps: {
249
341
  branchList?: typeof nativeBranchList;
250
342
  branchExists?: typeof nativeBranchExists;
343
+ hasChanges?: typeof nativeHasChanges;
251
344
  } = {},
252
- ): { recovered: string[]; warnings: string[] } {
345
+ ): OrphanAuditResult {
253
346
  const recovered: string[] = [];
254
347
  const warnings: string[] = [];
348
+ const actions: OrphanAuditAction[] = [];
255
349
  const branchList = gitDeps.branchList ?? nativeBranchList;
256
350
  const branchExists = gitDeps.branchExists ?? nativeBranchExists;
351
+ const hasChanges = gitDeps.hasChanges ?? nativeHasChanges;
257
352
 
258
- // Skip in none mode no milestone branches are created
259
- if (isolationMode === "none") return { recovered, warnings };
353
+ const pushAction = (action: OrphanAuditAction): void => {
354
+ actions.push(action);
355
+ if (action.severity === "info") {
356
+ recovered.push(action.message);
357
+ } else {
358
+ warnings.push(action.message);
359
+ }
360
+ };
260
361
 
261
362
  // Skip if DB not available — can't determine completion status
262
- if (!isDbAvailable()) return { recovered, warnings };
363
+ if (!isDbAvailable()) {
364
+ return { recovered, warnings, actions, blockingStrandedWork: null };
365
+ }
263
366
 
264
367
  let milestoneBranches: string[];
265
368
  let milestoneBranchListAvailable = true;
@@ -295,6 +398,7 @@ export function auditOrphanedMilestoneBranches(
295
398
  if (!milestone) continue;
296
399
 
297
400
  const isMerged = mergedBranches.has(branch);
401
+ const worktreeEvidence = detectWorktreeEvidence(basePath, milestoneId, hasChanges);
298
402
 
299
403
  // #4762 — in-progress milestone branch with unmerged commits ahead of
300
404
  // main. This is the pre-completion orphan case: auto-mode exited without
@@ -307,33 +411,45 @@ export function auditOrphanedMilestoneBranches(
307
411
  // Parked/other closed statuses go through the legacy complete/unmerged
308
412
  // path below where appropriate.
309
413
  if (!isClosedStatus(milestone.status)) {
310
- if (isMerged) continue; // nothing to recover
311
414
  let commitsAhead = 0;
312
415
  try {
313
416
  commitsAhead = nativeCommitCountBetween(basePath, mainBranch, branch);
314
417
  } catch {
315
- // Rev-walk failure — skip rather than noise
316
- continue;
418
+ commitsAhead = 0;
317
419
  }
318
- if (commitsAhead === 0) continue;
319
-
320
- const wtDir = getWorktreeDir(basePath, milestoneId);
321
- const wtDirExists = existsSync(wtDir);
322
- const wtSuffix = wtDirExists
323
- ? ` Worktree directory at .gsd/worktrees/${milestoneId}/ holds the live work.`
324
- : "";
325
- warnings.push(
326
- `Branch ${branch} has ${commitsAhead} commit(s) ahead of ${mainBranch} for in-progress milestone ${milestoneId}.` +
327
- wtSuffix +
328
- ` Run \`/gsd auto\` to resume, or merge manually if abandoning.`,
329
- );
420
+ if ((isMerged || commitsAhead === 0) && !worktreeEvidence.dirty) continue;
421
+
422
+ const recoveryMode: StrandedWorkRecoveryMode = worktreeEvidence.path
423
+ ? "worktree"
424
+ : "branch";
425
+ const message = strandedWorkMessage({
426
+ milestoneId,
427
+ branch,
428
+ commitsAhead,
429
+ mainBranch,
430
+ dirtyWorktree: worktreeEvidence.dirty,
431
+ worktreeDirExists: worktreeEvidence.dirExists,
432
+ recoveryMode,
433
+ });
434
+ pushAction({
435
+ kind: "in-progress-stranded-work",
436
+ milestoneId,
437
+ branch,
438
+ commitsAhead,
439
+ dirtyWorktree: worktreeEvidence.dirty,
440
+ worktreeDirExists: worktreeEvidence.dirExists,
441
+ recoveryMode,
442
+ message,
443
+ severity: "warning",
444
+ blocksAuto: true,
445
+ });
330
446
 
331
447
  // #4764 telemetry
332
448
  try {
333
449
  emitWorktreeOrphaned(basePath, milestoneId, {
334
450
  reason: "in-progress-unmerged",
335
451
  commitsAhead,
336
- worktreeDirExists: wtDirExists,
452
+ worktreeDirExists: worktreeEvidence.dirExists,
337
453
  });
338
454
  } catch (err) {
339
455
  logWarning("engine", `worktree-orphaned telemetry failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`);
@@ -351,7 +467,14 @@ export function auditOrphanedMilestoneBranches(
351
467
  // Branch is merged — safe to delete branch and clean up worktree dir
352
468
  try {
353
469
  nativeBranchDelete(basePath, branch, true);
354
- recovered.push(`Deleted merged branch ${branch} for completed milestone ${milestoneId}.`);
470
+ pushAction({
471
+ kind: "complete-merged-branch",
472
+ milestoneId,
473
+ branch,
474
+ message: `Deleted merged branch ${branch} for completed milestone ${milestoneId}.`,
475
+ severity: "info",
476
+ blocksAuto: false,
477
+ });
355
478
  } catch (err) {
356
479
  warnings.push(`Failed to delete merged branch ${branch}: ${err instanceof Error ? err.message : String(err)}`);
357
480
  }
@@ -374,7 +497,15 @@ export function auditOrphanedMilestoneBranches(
374
497
  if (isInsideWorktreesDir(basePath, wtDir)) {
375
498
  try {
376
499
  rmSync(wtDir, { recursive: true, force: true });
377
- recovered.push(`Removed orphaned worktree directory for ${milestoneId}.`);
500
+ pushAction({
501
+ kind: "complete-merged-worktree",
502
+ milestoneId,
503
+ branch,
504
+ worktreeDirExists: true,
505
+ message: `Removed orphaned worktree directory for ${milestoneId}.`,
506
+ severity: "info",
507
+ blocksAuto: false,
508
+ });
378
509
  } catch (err2) {
379
510
  warnings.push(`Failed to remove worktree directory for ${milestoneId}: ${err2 instanceof Error ? err2.message : String(err2)}`);
380
511
  }
@@ -382,15 +513,30 @@ export function auditOrphanedMilestoneBranches(
382
513
  warnings.push(`Orphaned worktree directory for ${milestoneId} is outside .gsd/worktrees/ — skipping removal for safety.`);
383
514
  }
384
515
  } else {
385
- recovered.push(`Removed orphaned worktree directory for ${milestoneId}.`);
516
+ pushAction({
517
+ kind: "complete-merged-worktree",
518
+ milestoneId,
519
+ branch,
520
+ worktreeDirExists: true,
521
+ message: `Removed orphaned worktree directory for ${milestoneId}.`,
522
+ severity: "info",
523
+ blocksAuto: false,
524
+ });
386
525
  }
387
526
  }
388
527
  } else {
389
528
  // Branch is NOT merged — preserve for safety, warn the user
390
- warnings.push(
391
- `Branch ${branch} exists for completed milestone ${milestoneId} but is NOT merged into ${mainBranch}. ` +
392
- `This may contain unmerged work. Merge manually or run \`/gsd doctor fix\` to resolve.`,
393
- );
529
+ pushAction({
530
+ kind: "complete-unmerged-branch",
531
+ milestoneId,
532
+ branch,
533
+ worktreeDirExists: worktreeEvidence.dirExists,
534
+ message:
535
+ `Branch ${branch} exists for completed milestone ${milestoneId} but is NOT merged into ${mainBranch}. ` +
536
+ `This may contain unmerged work. Merge manually or run \`/gsd doctor fix\` to resolve.`,
537
+ severity: "warning",
538
+ blocksAuto: false,
539
+ });
394
540
 
395
541
  // #4764 telemetry
396
542
  try {
@@ -428,6 +574,41 @@ export function auditOrphanedMilestoneBranches(
428
574
  completedMilestones = [];
429
575
  }
430
576
  for (const m of completedMilestones) {
577
+ if (!isClosedStatus(m.status)) {
578
+ if (seenMilestoneIds.has(m.id)) continue;
579
+ const worktreeEvidence = detectWorktreeEvidence(basePath, m.id, hasChanges);
580
+ if (!worktreeEvidence.dirty) continue;
581
+ const message = strandedWorkMessage({
582
+ milestoneId: m.id,
583
+ commitsAhead: 0,
584
+ mainBranch,
585
+ dirtyWorktree: true,
586
+ worktreeDirExists: worktreeEvidence.dirExists,
587
+ recoveryMode: "worktree",
588
+ });
589
+ pushAction({
590
+ kind: "in-progress-stranded-work",
591
+ milestoneId: m.id,
592
+ commitsAhead: 0,
593
+ dirtyWorktree: true,
594
+ worktreeDirExists: worktreeEvidence.dirExists,
595
+ recoveryMode: "worktree",
596
+ message,
597
+ severity: "warning",
598
+ blocksAuto: true,
599
+ });
600
+ try {
601
+ emitWorktreeOrphaned(basePath, m.id, {
602
+ reason: "in-progress-unmerged",
603
+ commitsAhead: 0,
604
+ worktreeDirExists: worktreeEvidence.dirExists,
605
+ });
606
+ } catch (err) {
607
+ logWarning("engine", `worktree-orphaned telemetry failed for ${m.id}: ${err instanceof Error ? err.message : String(err)}`);
608
+ }
609
+ continue;
610
+ }
611
+
431
612
  if (m.status !== "complete") continue;
432
613
  if (seenMilestoneIds.has(m.id)) continue; // already processed in the branch loop
433
614
  if (!milestoneBranchListAvailable) {
@@ -461,18 +642,37 @@ export function auditOrphanedMilestoneBranches(
461
642
  if (existsSync(wtDir)) {
462
643
  try {
463
644
  rmSync(wtDir, { recursive: true, force: true });
464
- recovered.push(`Removed orphaned worktree directory for ${m.id} (branch already deleted).`);
645
+ pushAction({
646
+ kind: "complete-branchless-worktree",
647
+ milestoneId: m.id,
648
+ worktreeDirExists: true,
649
+ message: `Removed orphaned worktree directory for ${m.id} (branch already deleted).`,
650
+ severity: "info",
651
+ blocksAuto: false,
652
+ });
465
653
  } catch (err) {
466
654
  warnings.push(
467
655
  `Failed to remove orphaned worktree directory for ${m.id}: ${err instanceof Error ? err.message : String(err)}`,
468
656
  );
469
657
  }
470
658
  } else {
471
- recovered.push(`Removed orphaned worktree directory for ${m.id} (branch already deleted).`);
659
+ pushAction({
660
+ kind: "complete-branchless-worktree",
661
+ milestoneId: m.id,
662
+ worktreeDirExists: true,
663
+ message: `Removed orphaned worktree directory for ${m.id} (branch already deleted).`,
664
+ severity: "info",
665
+ blocksAuto: false,
666
+ });
472
667
  }
473
668
  }
474
669
 
475
- return { recovered, warnings };
670
+ return {
671
+ recovered,
672
+ warnings,
673
+ actions,
674
+ blockingStrandedWork: actions.find(isBlockingStrandedWorkAction) ?? null,
675
+ };
476
676
  }
477
677
 
478
678
  /**
@@ -894,17 +1094,27 @@ export async function bootstrapAutoSession(
894
1094
  // was lost due to session ending between completion and teardown.
895
1095
  // Must run after DB open and before worktree entry.
896
1096
  let orphanAuditRecovered = false;
1097
+ let strandedRecoveryActions: OrphanAuditAction[] = [];
1098
+ let strandedRecoveryAction: OrphanAuditAction | null = null;
897
1099
  try {
898
1100
  const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode(base));
1101
+ strandedRecoveryActions = auditResult.actions.filter(isBlockingStrandedWorkAction);
1102
+ strandedRecoveryAction = strandedRecoveryActions[0] ?? null;
899
1103
  for (const msg of auditResult.recovered) {
900
1104
  ctx.ui.notify(`Orphan audit: ${msg}`, "info");
901
1105
  }
902
1106
  for (const msg of auditResult.warnings) {
903
- ctx.ui.notify(`Orphan audit: ${msg}`, "warning");
1107
+ const prefix = msg.startsWith("Stranded work") ? "" : "Orphan audit: ";
1108
+ ctx.ui.notify(`${prefix}${msg}`, "warning");
904
1109
  }
905
1110
  if (auditResult.recovered.length > 0) {
906
1111
  orphanAuditRecovered = true;
907
- debugLog("orphan-audit", { recovered: auditResult.recovered, warnings: auditResult.warnings });
1112
+ debugLog("orphan-audit", {
1113
+ recovered: auditResult.recovered,
1114
+ warnings: auditResult.warnings,
1115
+ strandedRecoveryAction,
1116
+ strandedRecoveryActions,
1117
+ });
908
1118
  }
909
1119
  } catch (err) {
910
1120
  // Non-fatal — the audit is defensive, never block bootstrap
@@ -946,6 +1156,46 @@ export async function bootstrapAutoSession(
946
1156
 
947
1157
  let state = await deriveState(base);
948
1158
 
1159
+ // Stale worktree state recovery (#654)
1160
+ if (
1161
+ state.activeMilestone &&
1162
+ shouldUseWorktreeIsolation(base) &&
1163
+ !detectWorktreeName(base)
1164
+ ) {
1165
+ const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
1166
+ if (wtPath) {
1167
+ state = await deriveState(wtPath);
1168
+ }
1169
+ }
1170
+
1171
+ const blockingStrandedRecoveryAction = state.activeMilestone
1172
+ ? strandedRecoveryActions.find(
1173
+ (action) => action.milestoneId !== state.activeMilestone?.id,
1174
+ ) ?? strandedRecoveryAction
1175
+ : strandedRecoveryAction;
1176
+
1177
+ if (blockingStrandedRecoveryAction) {
1178
+ if (!state.activeMilestone) {
1179
+ ctx.ui.notify(
1180
+ `Stranded work for ${blockingStrandedRecoveryAction.milestoneId} blocks auto-mode, but that milestone is not active in project state. Park or discard it explicitly before continuing.`,
1181
+ "error",
1182
+ );
1183
+ return releaseLockAndReturn();
1184
+ }
1185
+ if (state.activeMilestone.id !== blockingStrandedRecoveryAction.milestoneId) {
1186
+ ctx.ui.notify(
1187
+ `Stranded work for ${blockingStrandedRecoveryAction.milestoneId} blocks auto-mode before ${state.activeMilestone.id}. Recover, park, or discard ${blockingStrandedRecoveryAction.milestoneId} explicitly before continuing.`,
1188
+ "error",
1189
+ );
1190
+ return releaseLockAndReturn();
1191
+ }
1192
+ strandedRecoveryAction = blockingStrandedRecoveryAction;
1193
+ ctx.ui.notify(
1194
+ `Recovering stranded work for ${strandedRecoveryAction.milestoneId} before dispatching new units.`,
1195
+ "info",
1196
+ );
1197
+ }
1198
+
949
1199
  if (
950
1200
  process.env.GSD_HEADLESS === "1" &&
951
1201
  orphanAuditRecovered &&
@@ -959,18 +1209,6 @@ export async function bootstrapAutoSession(
959
1209
  return releaseLockAndReturn();
960
1210
  }
961
1211
 
962
- // Stale worktree state recovery (#654)
963
- if (
964
- state.activeMilestone &&
965
- shouldUseWorktreeIsolation(base) &&
966
- !detectWorktreeName(base)
967
- ) {
968
- const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
969
- if (wtPath) {
970
- state = await deriveState(wtPath);
971
- }
972
- }
973
-
974
1212
  // Milestone branch recovery (#601, #2358)
975
1213
  // Detect survivor milestone branches in both pre-planning and complete phases.
976
1214
  // In phase=complete, the milestone artifacts exist but finalization (merge,
@@ -1005,7 +1243,10 @@ export async function bootstrapAutoSession(
1005
1243
  // The worktree/branch was created but the milestone only has CONTEXT-DRAFT.md.
1006
1244
  // Route to the interactive discussion handler instead of falling through to
1007
1245
  // auto-mode, which would immediately stop with "needs discussion".
1008
- if (decideSurvivorAction(hasSurvivorBranch, state.phase) === "discuss") {
1246
+ if (
1247
+ !strandedRecoveryAction &&
1248
+ decideSurvivorAction(hasSurvivorBranch, state.phase) === "discuss"
1249
+ ) {
1009
1250
  const { showSmartEntry } = await import("./guided-flow.js");
1010
1251
  await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
1011
1252
 
@@ -1101,7 +1342,7 @@ export async function bootstrapAutoSession(
1101
1342
  { hasSurvivorBranch },
1102
1343
  );
1103
1344
 
1104
- if (deepProjectStagePending) {
1345
+ if (deepProjectStagePending && !strandedRecoveryAction) {
1105
1346
  // Deep project-level setup runs before the first milestone exists. Let
1106
1347
  // the auto loop dispatch workflow-preferences / project / requirements
1107
1348
  // units instead of recursing back through showSmartEntry while this
@@ -1109,7 +1350,7 @@ export async function bootstrapAutoSession(
1109
1350
  s.currentMilestoneId = null;
1110
1351
  }
1111
1352
 
1112
- if (!hasSurvivorBranch && !deepProjectStagePending) {
1353
+ if (!hasSurvivorBranch && !deepProjectStagePending && !strandedRecoveryAction) {
1113
1354
  // No active work — start a new milestone via discuss flow
1114
1355
  if (!state.activeMilestone || state.phase === "complete") {
1115
1356
  // Guard against recursive dialog loop (#1348):
@@ -1185,7 +1426,7 @@ export async function bootstrapAutoSession(
1185
1426
  }
1186
1427
 
1187
1428
  // Unreachable safety check
1188
- if (!state.activeMilestone && !deepProjectStagePending) {
1429
+ if (!state.activeMilestone && !deepProjectStagePending && !strandedRecoveryAction) {
1189
1430
  const { showSmartEntry } = await import("./guided-flow.js");
1190
1431
  await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
1191
1432
  return releaseLockAndReturn();
@@ -1222,7 +1463,9 @@ export async function bootstrapAutoSession(
1222
1463
  s.resourceVersionOnStart = readResourceVersion();
1223
1464
  s.pendingQuickTasks = [];
1224
1465
  s.currentUnit = null;
1225
- s.currentMilestoneId ??= deepProjectStagePending ? null : state.activeMilestone?.id ?? null;
1466
+ s.currentMilestoneId ??=
1467
+ strandedRecoveryAction?.milestoneId ??
1468
+ (deepProjectStagePending ? null : state.activeMilestone?.id ?? null);
1226
1469
  s.originalModelId = startModelSnapshot?.id ?? ctx.model?.id ?? null;
1227
1470
  s.originalModelProvider = startModelSnapshot?.provider ?? ctx.model?.provider ?? null;
1228
1471
  s.originalThinkingLevel = startThinkingSnapshot ?? null;
@@ -1232,7 +1475,7 @@ export async function bootstrapAutoSession(
1232
1475
 
1233
1476
  // Capture integration branch
1234
1477
  if (s.currentMilestoneId) {
1235
- if (getIsolationMode(base) !== "none") {
1478
+ if (getIsolationMode(base) !== "none" || strandedRecoveryAction) {
1236
1479
  captureIntegrationBranch(base, s.currentMilestoneId);
1237
1480
  }
1238
1481
  setActiveMilestoneId(base, s.currentMilestoneId);
@@ -1243,7 +1486,7 @@ export async function bootstrapAutoSession(
1243
1486
  // milestone/<MID>. Auto-checkout back to the integration branch.
1244
1487
  const isolationMode = getIsolationMode(base);
1245
1488
  const isRepo = nativeIsRepo(base);
1246
- if (isolationMode === "none" && isRepo) {
1489
+ if (isolationMode === "none" && isRepo && !strandedRecoveryAction) {
1247
1490
  try {
1248
1491
  const currentBranch = nativeGetCurrentBranch(base);
1249
1492
  const integrationBranch = nativeDetectMainBranch(base);
@@ -1282,13 +1525,21 @@ export async function bootstrapAutoSession(
1282
1525
 
1283
1526
  if (
1284
1527
  s.currentMilestoneId &&
1285
- getIsolationMode(base) !== "none" &&
1528
+ (getIsolationMode(base) !== "none" || strandedRecoveryAction?.recoveryMode) &&
1286
1529
  !detectWorktreeName(base) &&
1287
1530
  !isUnderGsdWorktrees(base)
1288
1531
  ) {
1289
- const enterResult = buildLifecycle().enterMilestone(s.currentMilestoneId, {
1290
- notify: ctx.ui.notify.bind(ctx.ui),
1291
- });
1532
+ const lifecycle = buildLifecycle();
1533
+ const enterResult = strandedRecoveryAction?.recoveryMode
1534
+ ? lifecycle.adoptStrandedMilestone(
1535
+ s.currentMilestoneId,
1536
+ base,
1537
+ { notify: ctx.ui.notify.bind(ctx.ui) },
1538
+ { mode: strandedRecoveryAction.recoveryMode },
1539
+ )
1540
+ : lifecycle.enterMilestone(s.currentMilestoneId, {
1541
+ notify: ctx.ui.notify.bind(ctx.ui),
1542
+ });
1292
1543
  if (!enterResult.ok) {
1293
1544
  s.active = false;
1294
1545
  if (enterResult.reason === "lease-conflict") {
@@ -715,8 +715,9 @@ export function registerDbTools(pi: ExtensionAPI): void {
715
715
  promptSnippet: "Complete a GSD task (DB write + summary render + checkbox toggle)",
716
716
  promptGuidelines: [
717
717
  "Use gsd_task_complete (or gsd_complete_task) when a task is finished and needs to be recorded.",
718
- "All string fields are required. verificationEvidence is an array of objects with command, exitCode, verdict, durationMs.",
719
- "The tool validates required fields and returns an error message if any are missing.",
718
+ "Include verification whenever possible. If verification is omitted, the executor derives it from verificationEvidence when possible.",
719
+ "verificationEvidence is an array of objects with command, exitCode, verdict, durationMs.",
720
+ "The tool validates required fields and returns an error message if verification cannot be derived.",
720
721
  "On success, returns the summaryPath where the SUMMARY.md was written.",
721
722
  "Idempotent — calling with the same params twice will upsert (INSERT OR REPLACE) without error.",
722
723
  ],
@@ -727,7 +728,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
727
728
  milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
728
729
  oneLiner: Type.String({ description: "One-line summary of what was accomplished" }),
729
730
  narrative: Type.String({ description: "Detailed narrative of what happened during the task" }),
730
- verification: Type.String({ description: "What was verified and how — commands run, tests passed, behavior confirmed" }),
731
+ verification: Type.Optional(Type.String({ description: "What was verified and how — commands run, tests passed, behavior confirmed. If omitted, derived from verificationEvidence when possible." })),
731
732
  // ── Enrichment metadata (optional — defaults to empty) ────────────
732
733
  deviations: Type.Optional(Type.String({ description: "Deviations from the task plan, or 'None.'" })),
733
734
  knownIssues: Type.Optional(Type.String({ description: "Known issues discovered but not fixed, or 'None.'" })),
@@ -507,6 +507,21 @@ function initSessionNotifications(ctx: ExtensionContext): void {
507
507
  initNotificationWidget(ctx);
508
508
  }
509
509
 
510
+ async function prepareWorkflowMcpForHookContext(
511
+ ctx: ExtensionContext,
512
+ basePath: string,
513
+ ): Promise<void> {
514
+ // Skip MCP auto-prep when running inside an auto-worktree. The worktree
515
+ // already has .mcp.json from createAutoWorktree, and re-running the writer
516
+ // post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
517
+ // CLI path resolution), dirtying the tree and breaking the milestone merge.
518
+ const { isInAutoWorktree } = await import("../auto-worktree.js");
519
+ if (isInAutoWorktree(basePath)) return;
520
+
521
+ const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
522
+ prepareWorkflowMcpForProject(ctx, basePath);
523
+ }
524
+
510
525
  export function registerHooks(
511
526
  pi: ExtensionAPI,
512
527
  ecosystemHandlers: GSDEcosystemBeforeAgentStartHandler[],
@@ -532,12 +547,7 @@ export function registerHooks(
532
547
  await syncServiceTierStatus(ctx);
533
548
  await applyDisabledModelProviderPolicy(ctx);
534
549
  await applyCompactionThresholdOverride(ctx);
535
- // Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
536
- const { isInAutoWorktree } = await import("../auto-worktree.js");
537
- if (!isInAutoWorktree(basePath)) {
538
- const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
539
- prepareWorkflowMcpForProject(ctx, basePath);
540
- }
550
+ await prepareWorkflowMcpForHookContext(ctx, basePath);
541
551
 
542
552
  // Apply show_token_cost preference (#1515)
543
553
  try {
@@ -563,15 +573,7 @@ export function registerHooks(
563
573
  await syncServiceTierStatus(ctx);
564
574
  await applyDisabledModelProviderPolicy(ctx);
565
575
  await applyCompactionThresholdOverride(ctx);
566
- // Skip MCP auto-prep when running inside an auto-worktree. The worktree
567
- // already has .mcp.json from createAutoWorktree, and re-running the writer
568
- // post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
569
- // CLI path resolution), dirtying the tree and breaking the milestone merge.
570
- const { isInAutoWorktree } = await import("../auto-worktree.js");
571
- if (!isInAutoWorktree(basePath)) {
572
- const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
573
- prepareWorkflowMcpForProject(ctx, basePath);
574
- }
576
+ await prepareWorkflowMcpForHookContext(ctx, basePath);
575
577
  await loadToolApiKeysForSession();
576
578
  if (!isAutoActive()) {
577
579
  ctx.ui.setWidget("gsd-progress", undefined);
@@ -608,6 +610,11 @@ export function registerHooks(
608
610
  }
609
611
  clearDeferredApprovalGate(beforeAgentBasePath);
610
612
 
613
+ // session_start can fire before the active provider has settled. By
614
+ // before_agent_start, Claude Code CLI sessions should get the same
615
+ // project MCP config that /gsd mcp init would write.
616
+ await prepareWorkflowMcpForHookContext(ctx, beforeAgentBasePath);
617
+
611
618
  // GSD's own context injection (existing behavior — unchanged).
612
619
  const { buildBeforeAgentStartResult } = await import("./system-context.js");
613
620
  const gsdResult = await buildBeforeAgentStartResult(event, ctx);
@@ -205,7 +205,12 @@ export function getCloseoutManualResolveBlocker(basePath: string): string | null
205
205
  return `Unmerged paths remain in ${basePath}: ${conflictProbe.unmerged.slice(0, 5).join(", ")}`;
206
206
  }
207
207
 
208
- const status = runGit(basePath, ["status", "--porcelain"]);
208
+ let status: string;
209
+ try {
210
+ status = runGit(basePath, ["status", "--porcelain"]);
211
+ } catch {
212
+ return `Could not inspect git status in ${basePath}.`;
213
+ }
209
214
  if (status) {
210
215
  return `Working tree still has uncommitted changes in ${basePath}. Commit, stash, or run /gsd closeout retry first.`;
211
216
  }
@@ -208,7 +208,15 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
208
208
 
209
209
  if (trimmed === "") {
210
210
  if (!(await guardRemoteSession(ctx, pi))) return true;
211
- if (await hasUnresolvedCloseoutBlocker(ctx, projectRoot())) return true;
211
+ const basePath = projectRoot();
212
+ const { hasGsdBootstrapArtifacts } = await import("../../detection.js");
213
+ const { gsdRoot } = await import("../../paths.js");
214
+ if (!hasGsdBootstrapArtifacts(gsdRoot(basePath))) {
215
+ const { showSmartEntry } = await import("../../guided-flow.js");
216
+ await showSmartEntry(ctx, pi, basePath, { step: true });
217
+ return true;
218
+ }
219
+ if (await hasUnresolvedCloseoutBlocker(ctx, basePath)) return true;
212
220
  const { showGsdHome } = await import("../../gsd-command-home.js");
213
221
  await showGsdHome(ctx, pi, projectRoot());
214
222
  return true;
@@ -26,6 +26,7 @@ import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
26
26
  import { getAutoWorktreePath } from "./auto-worktree.js";
27
27
  import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
28
28
  import { loadPrompt } from "./prompt-loader.js";
29
+ import { isPnpmInstall } from "../../shared/package-manager-detection.js";
29
30
  import {
30
31
  buildDoctorHealIssuePayload,
31
32
  buildDoctorHealSummary,
@@ -57,6 +58,7 @@ function isBunInstall(argv1: string | undefined = process.argv[1]): boolean {
57
58
 
58
59
  function resolveInstallCommand(pkg: string): string {
59
60
  if (isBunInstall()) return `bun add -g ${pkg}`;
61
+ if (isPnpmInstall()) return `pnpm add -g ${pkg}`;
60
62
  return `npm install -g ${pkg}`;
61
63
  }
62
64