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
@@ -84,6 +84,9 @@ export class AutoSession {
84
84
  // ── Paths ────────────────────────────────────────────────────────────────
85
85
  basePath = "";
86
86
  originalBasePath = "";
87
+ previousProjectRootEnv: string | null = null;
88
+ hadProjectRootEnv = false;
89
+ projectRootEnvCaptured = false;
87
90
  gitService: GitServiceImpl | null = null;
88
91
 
89
92
  // ── Dispatch counters ────────────────────────────────────────────────────
@@ -116,6 +119,8 @@ export class AutoSession {
116
119
  pendingVerificationRetry: PendingVerificationRetry | null = null;
117
120
  readonly verificationRetryCount = new Map<string, number>();
118
121
  pausedSessionFile: string | null = null;
122
+ pausedUnitType: string | null = null;
123
+ pausedUnitId: string | null = null;
119
124
  resourceVersionOnStart: string | null = null;
120
125
  lastStateRebuildAt = 0;
121
126
 
@@ -192,6 +197,9 @@ export class AutoSession {
192
197
  // Paths
193
198
  this.basePath = "";
194
199
  this.originalBasePath = "";
200
+ this.previousProjectRootEnv = null;
201
+ this.hadProjectRootEnv = false;
202
+ this.projectRootEnvCaptured = false;
195
203
  this.gitService = null;
196
204
 
197
205
  // Dispatch
@@ -217,6 +225,8 @@ export class AutoSession {
217
225
  this.pendingVerificationRetry = null;
218
226
  this.verificationRetryCount.clear();
219
227
  this.pausedSessionFile = null;
228
+ this.pausedUnitType = null;
229
+ this.pausedUnitId = null;
220
230
  this.resourceVersionOnStart = null;
221
231
  this.lastStateRebuildAt = 0;
222
232
 
@@ -178,7 +178,7 @@ export function incrementUatCount(basePath: string, mid: string, sid: string): n
178
178
  export function isVerificationNotApplicable(value: string): boolean {
179
179
  const v = (value ?? "").toLowerCase().trim().replace(/[.\s]+$/, "");
180
180
  if (!v || v === "none") return true;
181
- return /^(?:none[\s._-]*(?:required|needed|planned)?|n\/?a|not[\s._-]+(?:applicable|required|needed|provided)|no[\s._-]+operational[\s\S]*)$/i.test(v);
181
+ return /^(?:none(?:[\s._\u2014-]+[\s\S]*)?|n\/?a|not[\s._-]+(?:applicable|required|needed|provided)|no[\s._-]+operational[\s\S]*)$/i.test(v);
182
182
  }
183
183
 
184
184
  // ─── Rules ────────────────────────────────────────────────────────────────
@@ -15,6 +15,7 @@ import type {
15
15
  } from "@gsd/pi-coding-agent";
16
16
  import { deriveState } from "./state.js";
17
17
  import { loadFile, getManifestStatus } from "./files.js";
18
+ import type { InterruptedSessionAssessment } from "./interrupted-session.js";
18
19
  import {
19
20
  loadEffectiveGSDPreferences,
20
21
  resolveSkillDiscoveryMode,
@@ -23,16 +24,9 @@ import {
23
24
  import { ensureGsdSymlink, isInheritedRepo, validateProjectId } from "./repo-identity.js";
24
25
  import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
25
26
  import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
26
- import { gsdRoot, resolveMilestoneFile, milestonesDir } from "./paths.js";
27
+ import { gsdRoot, resolveMilestoneFile } from "./paths.js";
27
28
  import { invalidateAllCaches } from "./cache.js";
28
- import { synthesizeCrashRecovery } from "./session-forensics.js";
29
- import {
30
- writeLock,
31
- clearLock,
32
- readCrashLock,
33
- formatCrashInfo,
34
- isLockProcessAlive,
35
- } from "./crash-recovery.js";
29
+ import { writeLock, clearLock } from "./crash-recovery.js";
36
30
  import {
37
31
  acquireSessionLock,
38
32
  releaseSessionLock,
@@ -248,6 +242,7 @@ export async function bootstrapAutoSession(
248
242
  verboseMode: boolean,
249
243
  requestedStepMode: boolean,
250
244
  deps: BootstrapDeps,
245
+ interrupted: InterruptedSessionAssessment,
251
246
  ): Promise<boolean> {
252
247
  const {
253
248
  shouldUseWorktreeIsolation,
@@ -340,57 +335,27 @@ export async function bootstrapAutoSession(
340
335
  }
341
336
  }
342
337
 
338
+ if (ctx.model?.provider === "claude-code") {
339
+ try {
340
+ const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
341
+ const result = ensureProjectWorkflowMcpConfig(base);
342
+ if (result.status !== "unchanged") {
343
+ ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
344
+ }
345
+ } catch (err) {
346
+ ctx.ui.notify(
347
+ `Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
348
+ "warning",
349
+ );
350
+ }
351
+ }
352
+
343
353
  // Initialize GitServiceImpl
344
354
  s.gitService = new GitServiceImpl(
345
355
  s.basePath,
346
356
  loadEffectiveGSDPreferences()?.preferences?.git ?? {},
347
357
  );
348
358
 
349
- // Check for crash from previous session. Skip our own fresh bootstrap lock.
350
- const crashLock = readCrashLock(base);
351
- if (crashLock && crashLock.pid !== process.pid) {
352
- if (isLockProcessAlive(crashLock)) {
353
- ctx.ui.notify(
354
- `Another auto-mode session (PID ${crashLock.pid}) appears to be running.\nStop it with \`kill ${crashLock.pid}\` before starting a new session.`,
355
- "error",
356
- );
357
- return releaseLockAndReturn();
358
- }
359
- const recoveredMid = parseUnitId(crashLock.unitId).milestone;
360
- const milestoneAlreadyComplete = recoveredMid
361
- ? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
362
- : false;
363
-
364
- if (milestoneAlreadyComplete) {
365
- ctx.ui.notify(
366
- `Crash recovery: discarding stale context for ${crashLock.unitId} — milestone ${recoveredMid} is already complete.`,
367
- "info",
368
- );
369
- } else {
370
- const activityDir = join(gsdRoot(base), "activity");
371
- const recovery = synthesizeCrashRecovery(
372
- base,
373
- crashLock.unitType,
374
- crashLock.unitId,
375
- crashLock.sessionFile,
376
- activityDir,
377
- );
378
- if (recovery && recovery.trace.toolCallCount > 0) {
379
- s.pendingCrashRecovery = recovery.prompt;
380
- ctx.ui.notify(
381
- `${formatCrashInfo(crashLock)}\nRecovered ${recovery.trace.toolCallCount} tool calls from crashed session. Resuming with full context.`,
382
- "warning",
383
- );
384
- } else {
385
- ctx.ui.notify(
386
- `${formatCrashInfo(crashLock)}\nNo session data recovered. Resuming from disk state.`,
387
- "warning",
388
- );
389
- }
390
- }
391
- clearLock(base);
392
- }
393
-
394
359
  // ── Debug mode ──
395
360
  if (!isDebugEnabled() && process.env.GSD_DEBUG === "1") {
396
361
  enableDebug(base);
@@ -410,6 +375,10 @@ export async function bootstrapAutoSession(
410
375
  ctx.ui.notify(`Debug logging enabled → ${getDebugLogPath()}`, "info");
411
376
  }
412
377
 
378
+ if (interrupted.classification !== "recoverable") {
379
+ s.pendingCrashRecovery = null;
380
+ }
381
+
413
382
  // Invalidate caches before initial state derivation
414
383
  invalidateAllCaches();
415
384
 
@@ -909,4 +878,3 @@ export async function bootstrapAutoSession(
909
878
  throw err;
910
879
  }
911
880
  }
912
-
@@ -1137,6 +1137,7 @@ function copyPlanningArtifacts(srcBase: string, wtPath: string): void {
1137
1137
  const srcGsd = join(srcBase, ".gsd");
1138
1138
  const dstGsd = join(wtPath, ".gsd");
1139
1139
  if (!existsSync(srcGsd)) return;
1140
+ if (isSamePath(srcGsd, dstGsd)) return;
1140
1141
 
1141
1142
  // Copy milestones/ directory (planning files, roadmaps, plans, research)
1142
1143
  safeCopyRecursive(join(srcGsd, "milestones"), join(dstGsd, "milestones"), {
@@ -1420,8 +1421,31 @@ export function mergeMilestoneToMain(
1420
1421
  const worktreeCwd = process.cwd();
1421
1422
  const milestoneBranch = autoWorktreeBranch(milestoneId);
1422
1423
 
1423
- // 1. Auto-commit dirty state in worktree before leaving
1424
- autoCommitDirtyState(worktreeCwd);
1424
+ // 1. Auto-commit dirty state before leaving.
1425
+ // Guard: when we entered through an auto-worktree (originalBase is set),
1426
+ // only auto-commit when cwd is on the milestone branch. In parallel mode,
1427
+ // cwd may be on the integration branch after a prior merge's
1428
+ // MergeConflictError left cwd unrestored. Auto-committing on the
1429
+ // integration branch captures dirty files from OTHER milestones under a
1430
+ // misleading commit message, contaminating the main branch (#2929).
1431
+ //
1432
+ // When originalBase is null (branch mode, no worktree), autoCommitDirtyState
1433
+ // runs unconditionally — the caller is responsible for cwd placement.
1434
+ {
1435
+ let shouldAutoCommit = true;
1436
+ if (originalBase !== null) {
1437
+ try {
1438
+ const currentBranch = nativeGetCurrentBranch(worktreeCwd);
1439
+ shouldAutoCommit = currentBranch === milestoneBranch;
1440
+ } catch {
1441
+ // If we can't determine the branch, skip the auto-commit to be safe
1442
+ shouldAutoCommit = false;
1443
+ }
1444
+ }
1445
+ if (shouldAutoCommit) {
1446
+ autoCommitDirtyState(worktreeCwd);
1447
+ }
1448
+ }
1425
1449
 
1426
1450
  // Reconcile worktree DB into main DB before leaving worktree context.
1427
1451
  // Skip when both paths resolve to the same physical file (shared WAL /
@@ -1778,6 +1802,12 @@ export function mergeMilestoneToMain(
1778
1802
  }
1779
1803
  }
1780
1804
  restoreShelter();
1805
+ // Restore cwd so the caller is not stranded on the integration branch.
1806
+ // Without this, the next mergeMilestoneToMain call in a parallel merge
1807
+ // sequence uses process.cwd() (now the project root) as worktreeCwd,
1808
+ // causing autoCommitDirtyState to commit unrelated milestone files to
1809
+ // the integration branch (#2929).
1810
+ process.chdir(previousCwd);
1781
1811
  throw new MergeConflictError(
1782
1812
  codeConflicts,
1783
1813
  "squash",
@@ -1975,23 +2005,38 @@ export function mergeMilestoneToMain(
1975
2005
  // changes (e.g. nativeHasChanges cache returned stale false, or auto-commit
1976
2006
  // silently failed), force one final commit so code is not destroyed by
1977
2007
  // `git worktree remove --force`.
2008
+ //
2009
+ // Guard: only run when worktreeCwd is on the milestone branch (#2929).
2010
+ // In parallel mode or branch-mode merges, worktreeCwd may be the project
2011
+ // root on the integration branch. Committing dirty state there would
2012
+ // capture unrelated files from other milestones.
1978
2013
  if (existsSync(worktreeCwd)) {
2014
+ let preTeardownBranch: string | null = null;
1979
2015
  try {
1980
- const dirtyCheck = nativeWorkingTreeStatus(worktreeCwd);
1981
- if (dirtyCheck) {
2016
+ preTeardownBranch = nativeGetCurrentBranch(worktreeCwd);
2017
+ } catch (err) {
2018
+ debugLog("mergeMilestoneToMain", { phase: "pre-teardown-branch-detect-failed", error: String(err) });
2019
+ }
2020
+ const isOnMilestoneBranch = preTeardownBranch === milestoneBranch;
2021
+
2022
+ if (isOnMilestoneBranch) {
2023
+ try {
2024
+ const dirtyCheck = nativeWorkingTreeStatus(worktreeCwd);
2025
+ if (dirtyCheck) {
2026
+ debugLog("mergeMilestoneToMain", {
2027
+ phase: "pre-teardown-dirty",
2028
+ worktreeCwd,
2029
+ status: dirtyCheck.slice(0, 200),
2030
+ });
2031
+ nativeAddAllWithExclusions(worktreeCwd, RUNTIME_EXCLUSION_PATHS);
2032
+ nativeCommit(worktreeCwd, "chore: pre-teardown auto-commit of uncommitted worktree changes");
2033
+ }
2034
+ } catch (e) {
1982
2035
  debugLog("mergeMilestoneToMain", {
1983
- phase: "pre-teardown-dirty",
1984
- worktreeCwd,
1985
- status: dirtyCheck.slice(0, 200),
2036
+ phase: "pre-teardown-commit-error",
2037
+ error: String(e),
1986
2038
  });
1987
- nativeAddAllWithExclusions(worktreeCwd, RUNTIME_EXCLUSION_PATHS);
1988
- nativeCommit(worktreeCwd, "chore: pre-teardown auto-commit of uncommitted worktree changes");
1989
2039
  }
1990
- } catch (e) {
1991
- debugLog("mergeMilestoneToMain", {
1992
- phase: "pre-teardown-commit-error",
1993
- error: String(e),
1994
- });
1995
2040
  }
1996
2041
  }
1997
2042
 
@@ -2020,4 +2065,3 @@ export function mergeMilestoneToMain(
2020
2065
 
2021
2066
  return { commitMessage, pushed, prCreated, codeFilesChanged };
2022
2067
  }
2023
-
@@ -19,6 +19,11 @@ import type {
19
19
  import { deriveState } from "./state.js";
20
20
  import { parseUnitId } from "./unit-id.js";
21
21
  import type { GSDState } from "./types.js";
22
+ import {
23
+ assessInterruptedSession,
24
+ readPausedSessionMetadata,
25
+ type InterruptedSessionAssessment,
26
+ } from "./interrupted-session.js";
22
27
  import { getManifestStatus } from "./files.js";
23
28
  export { inlinePriorMilestoneSummary } from "./files.js";
24
29
  import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
@@ -46,6 +51,7 @@ import {
46
51
  clearLock,
47
52
  readCrashLock,
48
53
  isLockProcessAlive,
54
+ formatCrashInfo,
49
55
  } from "./crash-recovery.js";
50
56
  import {
51
57
  acquireSessionLock,
@@ -118,6 +124,7 @@ import {
118
124
  formatTokenCount,
119
125
  } from "./metrics.js";
120
126
  import { setLogBasePath, logWarning, logError } from "./workflow-logger.js";
127
+ import { homedir } from "node:os";
121
128
  import { join } from "node:path";
122
129
  import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
123
130
  import { atomicWriteSync } from "./atomic-write.js";
@@ -241,6 +248,29 @@ const s = new AutoSession();
241
248
  /** Throttle STATE.md rebuilds — at most once per 30 seconds */
242
249
  const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
243
250
 
251
+ function captureProjectRootEnv(projectRoot: string): void {
252
+ if (!s.projectRootEnvCaptured) {
253
+ s.hadProjectRootEnv = Object.prototype.hasOwnProperty.call(process.env, "GSD_PROJECT_ROOT");
254
+ s.previousProjectRootEnv = process.env.GSD_PROJECT_ROOT ?? null;
255
+ s.projectRootEnvCaptured = true;
256
+ }
257
+ process.env.GSD_PROJECT_ROOT = projectRoot;
258
+ }
259
+
260
+ function restoreProjectRootEnv(): void {
261
+ if (!s.projectRootEnvCaptured) return;
262
+
263
+ if (s.hadProjectRootEnv && s.previousProjectRootEnv !== null) {
264
+ process.env.GSD_PROJECT_ROOT = s.previousProjectRootEnv;
265
+ } else {
266
+ delete process.env.GSD_PROJECT_ROOT;
267
+ }
268
+
269
+ s.previousProjectRootEnv = null;
270
+ s.hadProjectRootEnv = false;
271
+ s.projectRootEnvCaptured = false;
272
+ }
273
+
244
274
  export function shouldUseWorktreeIsolation(): boolean {
245
275
  const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
246
276
  if (prefs?.isolation === "worktree") return true;
@@ -542,6 +572,7 @@ function handleLostSessionLock(
542
572
  s.active = false;
543
573
  s.paused = false;
544
574
  clearUnitTimeout();
575
+ restoreProjectRootEnv();
545
576
  deregisterSigtermHandler();
546
577
  clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
547
578
  const base = lockBase();
@@ -577,6 +608,7 @@ function cleanupAfterLoopExit(ctx: ExtensionContext): void {
577
608
  s.currentUnit = null;
578
609
  s.active = false;
579
610
  clearUnitTimeout();
611
+ restoreProjectRootEnv();
580
612
 
581
613
  // Clear crash lock and release session lock so the next `/gsd next` does
582
614
  // not see a stale lock with the current PID and treat it as a "remote"
@@ -846,6 +878,7 @@ export async function stopAuto(
846
878
  ctx?.ui.setStatus("gsd-auto", undefined);
847
879
  ctx?.ui.setWidget("gsd-progress", undefined);
848
880
  ctx?.ui.setFooter(undefined);
881
+ restoreProjectRootEnv();
849
882
 
850
883
  // Reset all session state in one call
851
884
  s.reset();
@@ -894,6 +927,8 @@ export async function pauseAuto(
894
927
  stepMode: s.stepMode,
895
928
  pausedAt: new Date().toISOString(),
896
929
  sessionFile: s.pausedSessionFile,
930
+ unitType: s.currentUnit?.type ?? undefined,
931
+ unitId: s.currentUnit?.id ?? undefined,
897
932
  activeEngineId: s.activeEngineId,
898
933
  activeRunDir: s.activeRunDir,
899
934
  autoStartTime: s.autoStartTime,
@@ -934,6 +969,7 @@ export async function pauseAuto(
934
969
 
935
970
  s.active = false;
936
971
  s.paused = true;
972
+ restoreProjectRootEnv();
937
973
  s.pendingVerificationRetry = null;
938
974
  s.verificationRetryCount.clear();
939
975
  ctx?.ui.setStatus("gsd-auto", "paused");
@@ -1114,7 +1150,10 @@ export async function startAuto(
1114
1150
  pi: ExtensionAPI,
1115
1151
  base: string,
1116
1152
  verboseMode: boolean,
1117
- options?: { step?: boolean },
1153
+ options?: {
1154
+ step?: boolean;
1155
+ interrupted?: InterruptedSessionAssessment;
1156
+ },
1118
1157
  ): Promise<void> {
1119
1158
  if (s.active) {
1120
1159
  debugLog("startAuto", { phase: "already-active", skipping: true });
@@ -1122,41 +1161,60 @@ export async function startAuto(
1122
1161
  }
1123
1162
 
1124
1163
  const requestedStepMode = options?.step ?? false;
1164
+ const interruptedAssessment = options?.interrupted ?? null;
1125
1165
 
1126
1166
  // Escape stale worktree cwd from a previous milestone (#608).
1127
1167
  base = escapeStaleWorktree(base);
1128
1168
 
1169
+ const freshStartAssessment = interruptedAssessment
1170
+ ?? await assessInterruptedSession(base);
1171
+
1172
+ if (freshStartAssessment.classification === "running") {
1173
+ const pid = freshStartAssessment.lock?.pid;
1174
+ ctx.ui.notify(
1175
+ pid
1176
+ ? `Another auto-mode session (PID ${pid}) appears to be running.\nStop it with \`kill ${pid}\` before starting a new session.`
1177
+ : "Another auto-mode session appears to be running.",
1178
+ "error",
1179
+ );
1180
+ return;
1181
+ }
1182
+
1129
1183
  // If resuming from paused state, just re-activate and dispatch next unit.
1130
1184
  // Check persisted paused-session first (#1383) — survives /exit.
1131
1185
  if (!s.paused) {
1132
1186
  try {
1187
+ const meta = freshStartAssessment.pausedSession ?? readPausedSessionMetadata(base);
1133
1188
  const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json");
1134
- if (existsSync(pausedPath)) {
1135
- const meta = JSON.parse(readFileSync(pausedPath, "utf-8"));
1136
- if (meta.activeEngineId && meta.activeEngineId !== "dev") {
1137
- // Custom workflow resume — restore engine state
1138
- s.activeEngineId = meta.activeEngineId;
1139
- s.activeRunDir = meta.activeRunDir ?? null;
1140
- s.originalBasePath = meta.originalBasePath || base;
1141
- s.stepMode = meta.stepMode ?? requestedStepMode;
1142
- s.autoStartTime = meta.autoStartTime || Date.now();
1143
- s.paused = true;
1144
- // Don't delete pause file yet defer until lock is acquired.
1145
- // If lock fails, the file must survive for retry.
1146
- s.pausedSessionFile = pausedPath;
1147
- ctx.ui.notify(
1148
- `Resuming paused custom workflow${meta.activeRunDir ? ` (${meta.activeRunDir})` : ""}.`,
1149
- "info",
1189
+ if (meta?.activeEngineId && meta.activeEngineId !== "dev") {
1190
+ // Custom workflow resume — restore engine state
1191
+ s.activeEngineId = meta.activeEngineId;
1192
+ s.activeRunDir = meta.activeRunDir ?? null;
1193
+ s.originalBasePath = meta.originalBasePath || base;
1194
+ s.stepMode = meta.stepMode ?? requestedStepMode;
1195
+ s.autoStartTime = meta.autoStartTime || Date.now();
1196
+ s.paused = true;
1197
+ try { unlinkSync(pausedPath); } catch (e) { logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" }); }
1198
+ ctx.ui.notify(
1199
+ `Resuming paused custom workflow${meta.activeRunDir ? ` (${meta.activeRunDir})` : ""}.`,
1200
+ "info",
1201
+ );
1202
+ } else if (meta?.milestoneId) {
1203
+ const shouldResumePausedSession =
1204
+ freshStartAssessment.classification === "recoverable"
1205
+ && (
1206
+ freshStartAssessment.hasResumableDiskState
1207
+ || !!freshStartAssessment.recoveryPrompt
1208
+ || !!freshStartAssessment.lock
1150
1209
  );
1151
- } else if (meta.milestoneId) {
1210
+ if (shouldResumePausedSession) {
1152
1211
  // Validate the milestone still exists and isn't already complete (#1664).
1153
1212
  const mDir = resolveMilestonePath(base, meta.milestoneId);
1154
1213
  const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
1155
1214
  if (!mDir || summaryFile) {
1156
- // Stale milestone clean up and fall through to fresh bootstrap
1157
- try { unlinkSync(pausedPath); } catch (err) { /* non-fatal */
1158
- logWarning("session", `pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
1159
- }
1215
+ try { unlinkSync(pausedPath); } catch (err) {
1216
+ logWarning("session", `pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
1217
+ }
1160
1218
  ctx.ui.notify(
1161
1219
  `Paused milestone ${meta.milestoneId} is ${!mDir ? "missing" : "already complete"}. Starting fresh.`,
1162
1220
  "info",
@@ -1165,22 +1223,54 @@ export async function startAuto(
1165
1223
  s.currentMilestoneId = meta.milestoneId;
1166
1224
  s.originalBasePath = meta.originalBasePath || base;
1167
1225
  s.stepMode = meta.stepMode ?? requestedStepMode;
1226
+ s.pausedSessionFile = meta.sessionFile ?? null;
1227
+ s.pausedUnitType = meta.unitType ?? null;
1228
+ s.pausedUnitId = meta.unitId ?? null;
1168
1229
  s.autoStartTime = meta.autoStartTime || Date.now();
1169
1230
  s.paused = true;
1170
- // Don't delete pause file yet defer until lock is acquired.
1171
- // If lock fails, the file must survive for retry.
1172
- s.pausedSessionFile = pausedPath;
1231
+ try { unlinkSync(pausedPath); } catch (e) { logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" }); }
1173
1232
  ctx.ui.notify(
1174
- `Resuming paused session for ${meta.milestoneId}${meta.worktreePath ? ` (worktree)` : ""}.`,
1233
+ `Resuming paused session for ${meta.milestoneId}${meta.worktreePath && existsSync(meta.worktreePath) ? ` (worktree)` : ""}.`,
1175
1234
  "info",
1176
1235
  );
1177
1236
  }
1237
+ } else if (existsSync(pausedPath)) {
1238
+ try { unlinkSync(pausedPath); } catch (e) { logWarning("session", `stale pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" }); }
1178
1239
  }
1179
1240
  }
1180
1241
  } catch (err) {
1181
1242
  // Malformed or missing — proceed with fresh bootstrap
1182
1243
  logWarning("session", `paused-session restore failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
1183
1244
  }
1245
+ // Guard against zero/missing autoStartTime after resume (#3585)
1246
+ if (!s.autoStartTime || s.autoStartTime <= 0) s.autoStartTime = Date.now();
1247
+ }
1248
+
1249
+ if (!s.paused) {
1250
+ s.stepMode = requestedStepMode;
1251
+ }
1252
+
1253
+ if (freshStartAssessment.lock) {
1254
+ clearLock(base);
1255
+ }
1256
+
1257
+ if (!s.paused) {
1258
+ s.pendingCrashRecovery =
1259
+ freshStartAssessment.classification === "recoverable"
1260
+ ? freshStartAssessment.recoveryPrompt
1261
+ : null;
1262
+
1263
+ if (freshStartAssessment.classification === "recoverable" && freshStartAssessment.lock) {
1264
+ const info = formatCrashInfo(freshStartAssessment.lock);
1265
+ if (freshStartAssessment.recoveryToolCallCount > 0) {
1266
+ ctx.ui.notify(
1267
+ `${info}\nRecovered ${freshStartAssessment.recoveryToolCallCount} tool calls from crashed session. Resuming with full context.`,
1268
+ "warning",
1269
+ );
1270
+ } else if (freshStartAssessment.hasResumableDiskState) {
1271
+ ctx.ui.notify(`${info}\nResuming from disk state.`, "warning");
1272
+ }
1273
+ }
1184
1274
  }
1185
1275
 
1186
1276
  if (s.paused) {
@@ -1205,26 +1295,19 @@ export async function startAuto(
1205
1295
  s.active = true;
1206
1296
  s.verbose = verboseMode;
1207
1297
  s.stepMode = requestedStepMode;
1208
- // Preserve the original cmdCtx (ExtensionCommandContext with newSession)
1209
- // when resuming from a provider-error pause. The resume callback receives
1210
- // an ExtensionContext (from the agent_end hook) which lacks newSession —
1211
- // using it would crash runUnit with "newSession is not a function".
1212
- // Only override if the new ctx actually has newSession (user-initiated resume).
1213
- if ("newSession" in ctx && typeof (ctx as any).newSession === "function") {
1214
- s.cmdCtx = ctx;
1215
- } else if (!s.cmdCtx) {
1216
- // No saved cmdCtx — this shouldn't happen, but handle gracefully
1217
- s.cmdCtx = ctx as ExtensionCommandContext;
1218
- }
1219
- // else: keep existing s.cmdCtx which has the real newSession
1298
+ s.cmdCtx = ctx;
1220
1299
  s.basePath = base;
1221
- setLogBasePath(base);
1222
- if (!s.autoStartTime || s.autoStartTime <= 0) s.autoStartTime = Date.now();
1223
1300
  s.unitDispatchCount.clear();
1224
1301
  s.unitLifetimeDispatches.clear();
1225
1302
  if (!getLedger()) initMetrics(base);
1226
1303
  if (s.currentMilestoneId) setActiveMilestoneId(base, s.currentMilestoneId);
1227
1304
 
1305
+ // Re-register health level notification callback lost across process restart
1306
+ setLevelChangeCallback((_from, to, summary) => {
1307
+ const level = to === "red" ? "error" : to === "yellow" ? "warning" : "info";
1308
+ ctx.ui.notify(summary, level as "info" | "warning" | "error");
1309
+ });
1310
+
1228
1311
  // ── Auto-worktree: re-enter worktree on resume ──
1229
1312
  if (
1230
1313
  s.currentMilestoneId &&
@@ -1248,6 +1331,11 @@ export async function startAuto(
1248
1331
  "info",
1249
1332
  );
1250
1333
  restoreHookState(s.basePath);
1334
+ // Re-sync managed resources on resume so long-lived auto sessions pick up
1335
+ // bundled extension updates before resume-time verification/state logic runs.
1336
+ const agentDir = process.env.GSD_CODING_AGENT_DIR || join(process.env.GSD_HOME || homedir(), ".gsd", "agent");
1337
+ const { initResources } = await import("../../../" + "resource-loader.js");
1338
+ initResources(agentDir);
1251
1339
  // Open the project DB before rebuild/derive so resume uses DB-backed
1252
1340
  // state instead of falling back to stale markdown parsing (#2940).
1253
1341
  await openProjectDbIfPresent(s.basePath);
@@ -1278,8 +1366,8 @@ export async function startAuto(
1278
1366
  const activityDir = join(gsdRoot(s.basePath), "activity");
1279
1367
  const recovery = synthesizeCrashRecovery(
1280
1368
  s.basePath,
1281
- s.currentUnit?.type ?? "unknown",
1282
- s.currentUnit?.id ?? "unknown",
1369
+ s.currentUnit?.type ?? s.pausedUnitType ?? "unknown",
1370
+ s.currentUnit?.id ?? s.pausedUnitId ?? "unknown",
1283
1371
  s.pausedSessionFile ?? undefined,
1284
1372
  activityDir,
1285
1373
  );
@@ -1305,6 +1393,7 @@ export async function startAuto(
1305
1393
  );
1306
1394
  logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
1307
1395
 
1396
+ captureProjectRootEnv(s.originalBasePath || s.basePath);
1308
1397
  await autoLoop(ctx, pi, s, buildLoopDeps());
1309
1398
  cleanupAfterLoopExit(ctx);
1310
1399
  return;
@@ -1326,9 +1415,11 @@ export async function startAuto(
1326
1415
  verboseMode,
1327
1416
  requestedStepMode,
1328
1417
  bootstrapDeps,
1418
+ freshStartAssessment,
1329
1419
  );
1330
1420
  if (!ready) return;
1331
1421
 
1422
+ captureProjectRootEnv(s.originalBasePath || s.basePath);
1332
1423
  try {
1333
1424
  syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
1334
1425
  } catch (err) {
@@ -1438,27 +1529,6 @@ function ensurePreconditions(
1438
1529
  }
1439
1530
  }
1440
1531
 
1441
- // ─── Diagnostics ──────────────────────────────────────────────────────────────
1442
-
1443
- /** Build recovery context from module state for recoverTimedOutUnit */
1444
- function buildRecoveryContext(): import("./auto-timeout-recovery.js").RecoveryContext {
1445
- return {
1446
- basePath: s.basePath,
1447
- verbose: s.verbose,
1448
- currentUnitStartedAt: s.currentUnit?.startedAt ?? Date.now(),
1449
- unitRecoveryCount: s.unitRecoveryCount,
1450
- };
1451
- }
1452
-
1453
- /**
1454
- * Test-only: expose skip-loop state for unit tests.
1455
- * Not part of the public API.
1456
- */
1457
-
1458
- /**
1459
- * Dispatch a hook unit directly, bypassing normal pre-dispatch hooks.
1460
- * Used for manual hook triggers via /gsd run-hook.
1461
- */
1462
1532
  export async function dispatchHookUnit(
1463
1533
  ctx: ExtensionContext,
1464
1534
  pi: ExtensionAPI,
@@ -1569,4 +1639,3 @@ export {
1569
1639
  buildLoopRemediationSteps,
1570
1640
  } from "./auto-recovery.js";
1571
1641
  export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
1572
-
@@ -168,7 +168,7 @@ export async function buildBeforeAgentStartResult(
168
168
  const injection = await buildGuidedExecuteContextInjection(event.prompt, process.cwd());
169
169
 
170
170
  // Re-inject forensics context on follow-up turns (#2941)
171
- const forensicsInjection = !injection ? buildForensicsContextInjection(process.cwd()) : null;
171
+ const forensicsInjection = !injection ? buildForensicsContextInjection(process.cwd(), event.prompt) : null;
172
172
 
173
173
  const worktreeBlock = buildWorktreeContextBlock();
174
174
  const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${codebaseBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
@@ -481,7 +481,7 @@ function oneLine(text: string): string {
481
481
  * Check for an active forensics session and return the prompt content
482
482
  * so it can be re-injected on follow-up turns.
483
483
  */
484
- function buildForensicsContextInjection(basePath: string): string | null {
484
+ export function buildForensicsContextInjection(basePath: string, prompt: string): string | null {
485
485
  const marker = readForensicsMarker(basePath);
486
486
  if (!marker) return null;
487
487
 
@@ -492,6 +492,12 @@ function buildForensicsContextInjection(basePath: string): string | null {
492
492
  return null;
493
493
  }
494
494
 
495
+ const trimmed = prompt.trim().toLowerCase().replace(/[.!?,]+$/g, "");
496
+ if (trimmed && !RESUME_INTENT_PATTERNS.test(trimmed)) {
497
+ clearForensicsMarker(basePath);
498
+ return null;
499
+ }
500
+
495
501
  return marker.promptContent;
496
502
  }
497
503