gsd-pi 2.71.0-dev.977c553 → 2.71.0-dev.e17e0ce

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 (258) hide show
  1. package/dist/cli.js +12 -3
  2. package/dist/headless-events.d.ts +2 -0
  3. package/dist/headless-events.js +7 -0
  4. package/dist/headless.js +16 -3
  5. package/dist/mcp-server.js +6 -6
  6. package/dist/provider-migrations.d.ts +10 -0
  7. package/dist/provider-migrations.js +12 -0
  8. package/dist/resource-loader.js +139 -13
  9. package/dist/resources/GSD-WORKFLOW.md +1 -1
  10. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -4
  11. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  12. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  13. package/dist/resources/extensions/gsd/auto/phases.js +1 -1
  14. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  15. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  16. package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
  17. package/dist/resources/extensions/gsd/auto-start.js +13 -6
  18. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  19. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  20. package/dist/resources/extensions/gsd/auto.js +52 -0
  21. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +66 -51
  23. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
  24. package/dist/resources/extensions/gsd/commands/context.js +15 -6
  25. package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
  26. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  27. package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
  28. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  29. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  30. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  31. package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
  32. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  33. package/dist/resources/extensions/gsd/forensics.js +19 -6
  34. package/dist/resources/extensions/gsd/guided-flow.js +5 -10
  35. package/dist/resources/extensions/gsd/metrics.js +1 -0
  36. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  37. package/dist/resources/extensions/gsd/notification-overlay.js +20 -5
  38. package/dist/resources/extensions/gsd/notification-store.js +51 -1
  39. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  40. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  41. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
  42. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  43. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  44. package/dist/resources/extensions/gsd/prompts/execute-task.md +20 -19
  45. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  47. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  48. package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
  49. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  50. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
  51. package/dist/resources/extensions/gsd/session-model-override.js +25 -0
  52. package/dist/resources/extensions/gsd/shortcut-defs.js +34 -0
  53. package/dist/resources/skills/create-skill/SKILL.md +2 -0
  54. package/dist/web/standalone/.next/BUILD_ID +1 -1
  55. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  56. package/dist/web/standalone/.next/build-manifest.json +2 -2
  57. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  58. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.html +1 -1
  75. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  82. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  84. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  85. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  86. package/package.json +1 -1
  87. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  88. package/packages/mcp-server/dist/workflow-tools.js +21 -11
  89. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  90. package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
  91. package/packages/mcp-server/src/workflow-tools.ts +31 -11
  92. package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
  93. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  94. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  95. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  96. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  97. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  98. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
  99. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  100. package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
  101. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  102. package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
  103. package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
  105. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  106. package/packages/pi-ai/dist/providers/anthropic.js +7 -4
  107. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  109. package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
  110. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  111. package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
  112. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  113. package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
  114. package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
  115. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  116. package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
  117. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  118. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  119. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  120. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  121. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  123. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  125. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  127. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  129. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
  131. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
  132. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
  133. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
  134. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
  136. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  138. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  140. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  142. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  143. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  144. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  145. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  146. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/index.js +1 -1
  148. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  161. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
  163. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
  166. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  168. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  169. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  170. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  171. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  172. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  173. package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
  174. package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
  175. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  176. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  177. package/packages/pi-coding-agent/src/index.ts +1 -0
  178. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
  179. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
  180. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
  181. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
  182. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
  183. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  184. package/src/resources/GSD-WORKFLOW.md +1 -1
  185. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +13 -5
  186. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +56 -4
  187. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  188. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  189. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  190. package/src/resources/extensions/gsd/auto/phases.ts +2 -0
  191. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  192. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  193. package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
  194. package/src/resources/extensions/gsd/auto-start.ts +13 -6
  195. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  196. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  197. package/src/resources/extensions/gsd/auto.ts +68 -0
  198. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -0
  199. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +82 -60
  200. package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
  201. package/src/resources/extensions/gsd/commands/context.ts +16 -5
  202. package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
  203. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  204. package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
  205. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  206. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  207. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  208. package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
  209. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  210. package/src/resources/extensions/gsd/forensics.ts +23 -7
  211. package/src/resources/extensions/gsd/guided-flow.ts +5 -10
  212. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  213. package/src/resources/extensions/gsd/metrics.ts +12 -1
  214. package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
  215. package/src/resources/extensions/gsd/notification-overlay.ts +24 -7
  216. package/src/resources/extensions/gsd/notification-store.ts +49 -1
  217. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  218. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  219. package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
  220. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  221. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  222. package/src/resources/extensions/gsd/prompts/execute-task.md +20 -19
  223. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  224. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  225. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  226. package/src/resources/extensions/gsd/prompts/queue.md +3 -2
  227. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  228. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
  229. package/src/resources/extensions/gsd/session-model-override.ts +36 -0
  230. package/src/resources/extensions/gsd/shortcut-defs.ts +49 -0
  231. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +11 -9
  232. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
  233. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
  234. package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
  235. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  236. package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
  237. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  238. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +15 -0
  239. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
  240. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  241. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
  242. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
  243. package/src/resources/extensions/gsd/tests/notification-store.test.ts +35 -0
  244. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +26 -0
  245. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  246. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  247. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  248. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
  249. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
  250. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  251. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +62 -5
  252. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
  253. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  254. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
  255. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
  256. package/src/resources/skills/create-skill/SKILL.md +2 -0
  257. /package/dist/web/standalone/.next/static/{4xyaXTn7-shVHaGMcl75o → cYPZv_bAhZk2ms-Pz6vsY}/_buildManifest.js +0 -0
  258. /package/dist/web/standalone/.next/static/{4xyaXTn7-shVHaGMcl75o → cYPZv_bAhZk2ms-Pz6vsY}/_ssgManifest.js +0 -0
@@ -293,6 +293,11 @@ function buildWorktreeContextBlock(): string {
293
293
  const RESUME_INTENT_PATTERNS = /^(continue|resume|ok|go|go ahead|proceed|keep going|carry on|next|yes|yeah|yep|sure|do it|let's go|pick up where you left off)$/;
294
294
 
295
295
  async function buildGuidedExecuteContextInjection(prompt: string, basePath: string): Promise<string | null> {
296
+ const ensureStateDbOpen = async () => {
297
+ const { ensureDbOpen } = await import("./dynamic-tools.js");
298
+ await ensureDbOpen();
299
+ };
300
+
296
301
  const executeMatch = prompt.match(/Execute the next task:\s+(T\d+)\s+\("([^"]+)"\)\s+in slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
297
302
  if (executeMatch) {
298
303
  const [, taskId, taskTitle, sliceId, milestoneId] = executeMatch;
@@ -302,6 +307,7 @@ async function buildGuidedExecuteContextInjection(prompt: string, basePath: stri
302
307
  const resumeMatch = prompt.match(/Resume interrupted work\.[\s\S]*?slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
303
308
  if (resumeMatch) {
304
309
  const [, sliceId, milestoneId] = resumeMatch;
310
+ await ensureStateDbOpen();
305
311
  const state = await deriveState(basePath);
306
312
  if (state.activeMilestone?.id === milestoneId && state.activeSlice?.id === sliceId && state.activeTask) {
307
313
  return buildTaskExecutionContextInjection(basePath, milestoneId, sliceId, state.activeTask.id, state.activeTask.title);
@@ -317,6 +323,7 @@ async function buildGuidedExecuteContextInjection(prompt: string, basePath: stri
317
323
  // replanning, gate evaluation, or other non-execution phases.
318
324
  const trimmed = prompt.trim().toLowerCase().replace(/[.!?,]+$/g, "");
319
325
  if (RESUME_INTENT_PATTERNS.test(trimmed)) {
326
+ await ensureStateDbOpen();
320
327
  const state = await deriveState(basePath);
321
328
  if (state.phase === "executing" && state.activeTask && state.activeMilestone && state.activeSlice) {
322
329
  return buildTaskExecutionContextInjection(
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
2
2
 
3
3
  import { checkRemoteAutoSession, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js";
4
- import { assertSafeDirectory } from "../validate-directory.js";
4
+ import { validateDirectory } from "../validate-directory.js";
5
5
  import { resolveProjectRoot } from "../worktree.js";
6
6
  import { showNextAction } from "../../shared/tui.js";
7
7
  import { handleStatus } from "./handlers/core.js";
@@ -12,6 +12,17 @@ export interface GsdDispatchContext {
12
12
  trimmed: string;
13
13
  }
14
14
 
15
+ /**
16
+ * Typed error for when GSD is run outside a valid project directory.
17
+ * Command handlers catch this to show a friendly message instead of a raw exception.
18
+ */
19
+ export class GSDNoProjectError extends Error {
20
+ constructor(reason: string) {
21
+ super(reason);
22
+ this.name = "GSDNoProjectError";
23
+ }
24
+ }
25
+
15
26
  export function projectRoot(): string {
16
27
  let cwd: string;
17
28
  try {
@@ -21,10 +32,10 @@ export function projectRoot(): string {
21
32
  cwd = process.env.HOME ?? "/";
22
33
  }
23
34
  const root = resolveProjectRoot(cwd);
24
- if (root !== cwd) {
25
- assertSafeDirectory(cwd);
26
- } else {
27
- assertSafeDirectory(root);
35
+ const pathToCheck = root !== cwd ? cwd : root;
36
+ const result = validateDirectory(pathToCheck);
37
+ if (result.severity === "blocked") {
38
+ throw new GSDNoProjectError(result.reason ?? "GSD must be run inside a project directory.");
28
39
  }
29
40
  return root;
30
41
  }
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
2
2
 
3
+ import { GSDNoProjectError } from "./context.js";
3
4
  import { handleAutoCommand } from "./handlers/auto.js";
4
5
  import { handleCoreCommand } from "./handlers/core.js";
5
6
  import { handleOpsCommand } from "./handlers/ops.js";
@@ -21,10 +22,21 @@ export async function handleGSDCommand(
21
22
  () => handleOpsCommand(trimmed, ctx, pi),
22
23
  ];
23
24
 
24
- for (const handler of handlers) {
25
- if (await handler()) {
25
+ try {
26
+ for (const handler of handlers) {
27
+ if (await handler()) {
28
+ return;
29
+ }
30
+ }
31
+ } catch (err) {
32
+ if (err instanceof GSDNoProjectError) {
33
+ ctx.ui.notify(
34
+ `${err.message} \`cd\` into a project directory first.`,
35
+ "warning",
36
+ );
26
37
  return;
27
38
  }
39
+ throw err;
28
40
  }
29
41
 
30
42
  ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
@@ -4,7 +4,7 @@ import { existsSync, readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
5
 
6
6
  import { enableDebug } from "../../debug-logger.js";
7
- import { getAutoDashboardData, isAutoActive, isAutoPaused, pauseAuto, startAuto, stopAuto, stopAutoRemote } from "../../auto.js";
7
+ import { getAutoDashboardData, isAutoActive, isAutoPaused, pauseAuto, startAutoDetached, stopAuto, stopAutoRemote } from "../../auto.js";
8
8
  import { handleRate } from "../../commands-rate.js";
9
9
  import { guardRemoteSession, projectRoot } from "../context.js";
10
10
  import { findMilestoneIds } from "../../milestone-id-utils.js";
@@ -42,26 +42,6 @@ export function parseMilestoneTarget(input: string): { milestoneId: string | nul
42
42
  return { milestoneId: match[1], rest };
43
43
  }
44
44
 
45
- /**
46
- * Set GSD_MILESTONE_LOCK to target a specific milestone, then run `fn`.
47
- * Clears the env var when `fn` resolves or rejects, so the lock does not
48
- * leak into subsequent commands in the same process.
49
- */
50
- async function withMilestoneLock(milestoneId: string, fn: () => Promise<void>): Promise<void> {
51
- const previous = process.env.GSD_MILESTONE_LOCK;
52
- process.env.GSD_MILESTONE_LOCK = milestoneId;
53
- try {
54
- await fn();
55
- } finally {
56
- // Restore previous value (undefined → delete, else restore).
57
- if (previous === undefined) {
58
- delete process.env.GSD_MILESTONE_LOCK;
59
- } else {
60
- process.env.GSD_MILESTONE_LOCK = previous;
61
- }
62
- }
63
- }
64
-
65
45
  export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<boolean> {
66
46
  if (trimmed === "next" || trimmed.startsWith("next ")) {
67
47
  if (trimmed.includes("--dry-run")) {
@@ -84,13 +64,10 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
84
64
  }
85
65
  }
86
66
 
87
- if (milestoneId) {
88
- await withMilestoneLock(milestoneId, () =>
89
- startAuto(ctx, pi, projectRoot(), verboseMode, { step: true }),
90
- );
91
- } else {
92
- await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
93
- }
67
+ startAutoDetached(ctx, pi, projectRoot(), verboseMode, {
68
+ step: true,
69
+ milestoneLock: milestoneId,
70
+ });
94
71
  return true;
95
72
  }
96
73
 
@@ -128,13 +105,11 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
128
105
  const { showHeadlessMilestoneCreation } = await import("../../guided-flow.js");
129
106
  await showHeadlessMilestoneCreation(ctx, pi, projectRoot(), seedContent);
130
107
  } else if (milestoneId) {
131
- // Target a specific milestone — use GSD_MILESTONE_LOCK so state
132
- // derivation only sees this milestone (#2521).
133
- await withMilestoneLock(milestoneId, () =>
134
- startAuto(ctx, pi, projectRoot(), verboseMode),
135
- );
108
+ startAutoDetached(ctx, pi, projectRoot(), verboseMode, {
109
+ milestoneLock: milestoneId,
110
+ });
136
111
  } else {
137
- await startAuto(ctx, pi, projectRoot(), verboseMode);
112
+ startAutoDetached(ctx, pi, projectRoot(), verboseMode);
138
113
  }
139
114
  return true;
140
115
  }
@@ -175,10 +150,9 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
175
150
 
176
151
  if (trimmed === "") {
177
152
  if (!(await guardRemoteSession(ctx, pi))) return true;
178
- await startAuto(ctx, pi, projectRoot(), false, { step: true });
153
+ startAutoDetached(ctx, pi, projectRoot(), false, { step: true });
179
154
  return true;
180
155
  }
181
156
 
182
157
  return false;
183
158
  }
184
-
@@ -8,11 +8,45 @@ import { ensurePreferencesFile, handlePrefs, handlePrefsMode, handlePrefsWizard
8
8
  import { runEnvironmentChecks } from "../../doctor-environment.js";
9
9
  import { deriveState } from "../../state.js";
10
10
  import { handleCmux } from "../../commands-cmux.js";
11
+ import { setSessionModelOverride } from "../../session-model-override.js";
11
12
  import { projectRoot } from "../context.js";
12
- import { formatShortcut } from "../../files.js";
13
+ import { formattedShortcutPair } from "../../shortcut-defs.js";
13
14
 
14
- export function showHelp(ctx: ExtensionCommandContext): void {
15
- const lines = [
15
+ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
16
+ const summaryLines = [
17
+ "GSD — Get Shit Done\n",
18
+ "QUICK START",
19
+ " /gsd start <tpl> Start a workflow template",
20
+ " /gsd Run next unit (same as /gsd next)",
21
+ " /gsd auto Run all queued units continuously",
22
+ " /gsd pause Pause auto-mode",
23
+ " /gsd stop Stop auto-mode gracefully",
24
+ "",
25
+ "VISIBILITY",
26
+ ` /gsd status Dashboard (${formattedShortcutPair("dashboard")})`,
27
+ ` /gsd parallel watch Parallel monitor (${formattedShortcutPair("parallel")})`,
28
+ ` /gsd notifications Notification history (${formattedShortcutPair("notifications")})`,
29
+ " /gsd visualize Interactive 10-tab TUI",
30
+ " /gsd queue Show queued/dispatched units",
31
+ "",
32
+ "COURSE CORRECTION",
33
+ " /gsd steer <desc> Apply user override to active work",
34
+ " /gsd capture <text> Quick-capture a thought to CAPTURES.md",
35
+ " /gsd triage Classify and route pending captures",
36
+ " /gsd undo Revert last completed unit [--force]",
37
+ " /gsd rethink Conversational project reorganization",
38
+ "",
39
+ "SETUP",
40
+ " /gsd init Project init wizard",
41
+ " /gsd setup Global setup status [llm|search|remote|keys|prefs]",
42
+ " /gsd model Switch active session model",
43
+ " /gsd prefs Manage preferences",
44
+ " /gsd doctor Diagnose and repair .gsd/ state",
45
+ "",
46
+ "Use /gsd help full for the complete command reference.",
47
+ ];
48
+
49
+ const fullLines = [
16
50
  "GSD — Get Shit Done\n",
17
51
  "WORKFLOW",
18
52
  " /gsd start <tpl> Start a workflow template (bugfix, spike, feature, hotfix, etc.)",
@@ -26,12 +60,13 @@ export function showHelp(ctx: ExtensionCommandContext): void {
26
60
  " /gsd new-milestone Create milestone from headless context (used by gsd headless)",
27
61
  "",
28
62
  "VISIBILITY",
29
- ` /gsd status Show progress dashboard (${formatShortcut("Ctrl+Alt+G")})`,
63
+ ` /gsd status Show progress dashboard (${formattedShortcutPair("dashboard")})`,
64
+ ` /gsd parallel watch Open parallel worker monitor (${formattedShortcutPair("parallel")})`,
30
65
  " /gsd visualize Interactive 10-tab TUI (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)",
31
66
  " /gsd queue Show queued/dispatched units and execution order",
32
67
  " /gsd history View execution history [--cost] [--phase] [--model] [N]",
33
68
  " /gsd changelog Show categorized release notes [version]",
34
- ` /gsd notifications View persistent notification history [clear|tail|filter] (${formatShortcut("Ctrl+Alt+N")})`,
69
+ ` /gsd notifications View persistent notification history [clear|tail|filter] (${formattedShortcutPair("notifications")})`,
35
70
  "",
36
71
  "COURSE CORRECTION",
37
72
  " /gsd steer <desc> Apply user override to active work",
@@ -71,7 +106,8 @@ export function showHelp(ctx: ExtensionCommandContext): void {
71
106
  " /gsd inspect Show SQLite DB diagnostics (schema, row counts, recent entries)",
72
107
  " /gsd update Update GSD to the latest version via npm",
73
108
  ];
74
- ctx.ui.notify(lines.join("\n"), "info");
109
+ const full = ["full", "--full", "all"].includes(args.trim().toLowerCase());
110
+ ctx.ui.notify((full ? fullLines : summaryLines).join("\n"), "info");
75
111
  }
76
112
 
77
113
  export async function handleStatus(ctx: ExtensionCommandContext): Promise<void> {
@@ -92,9 +128,9 @@ export async function handleStatus(ctx: ExtensionCommandContext): Promise<void>
92
128
  {
93
129
  overlay: true,
94
130
  overlayOptions: {
95
- width: "70%",
96
- minWidth: 60,
97
- maxHeight: "90%",
131
+ width: "90%",
132
+ minWidth: 80,
133
+ maxHeight: "92%",
98
134
  anchor: "center",
99
135
  },
100
136
  },
@@ -301,6 +337,17 @@ async function handleModel(trimmedArgs: string, ctx: ExtensionCommandContext, pi
301
337
  return;
302
338
  }
303
339
 
340
+ // /gsd model is an explicit per-session pin for GSD dispatches.
341
+ // This is captured at auto bootstrap so it survives internal session
342
+ // switches during /gsd auto and /gsd next runs.
343
+ const sessionId = ctx.sessionManager?.getSessionId?.();
344
+ if (sessionId) {
345
+ setSessionModelOverride(sessionId, {
346
+ provider: targetModel.provider,
347
+ id: targetModel.id,
348
+ });
349
+ }
350
+
304
351
  ctx.ui.notify(`Model: ${targetModel.provider}/${targetModel.id}`, "info");
305
352
  }
306
353
 
@@ -309,8 +356,8 @@ export async function handleCoreCommand(
309
356
  ctx: ExtensionCommandContext,
310
357
  pi?: ExtensionAPI,
311
358
  ): Promise<boolean> {
312
- if (trimmed === "help" || trimmed === "h" || trimmed === "?") {
313
- showHelp(ctx);
359
+ if (trimmed === "help" || trimmed === "h" || trimmed === "?" || trimmed.startsWith("help ")) {
360
+ showHelp(ctx, trimmed.startsWith("help ") ? trimmed.slice(5).trim() : "");
314
361
  return true;
315
362
  }
316
363
  if (trimmed === "status") {
@@ -13,6 +13,8 @@ import {
13
13
  } from "../../notification-store.js";
14
14
  import { GSDNotificationOverlay } from "../../notification-overlay.js";
15
15
 
16
+ const MAX_INLINE_ENTRIES = 40;
17
+
16
18
  function severityIcon(severity: NotifySeverity): string {
17
19
  switch (severity) {
18
20
  case "error": return "✗";
@@ -54,8 +56,9 @@ export async function handleNotificationsCommand(
54
56
  if (args === "tail" || args.startsWith("tail ")) {
55
57
  const countStr = args.replace(/^tail\s*/, "").trim();
56
58
  const count = countStr ? parseInt(countStr, 10) : 20;
57
- const n = isNaN(count) || count < 1 ? 20 : Math.min(count, 100);
58
- const entries = readNotifications().slice(0, n);
59
+ const all = readNotifications();
60
+ const n = isNaN(count) || count < 1 ? 20 : Math.min(count, MAX_INLINE_ENTRIES);
61
+ const entries = all.slice(0, n);
59
62
 
60
63
  if (entries.length === 0) {
61
64
  ctx.ui.notify("No notifications.", "info");
@@ -65,7 +68,10 @@ export async function handleNotificationsCommand(
65
68
  const lines = entries.map((e) =>
66
69
  `${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`,
67
70
  );
68
- ctx.ui.notify(`Last ${entries.length} notification(s):\n${lines.join("\n")}`, "info");
71
+ const suffix = all.length > entries.length
72
+ ? `\n... and ${all.length - entries.length} more (open /gsd notifications to browse all)`
73
+ : "";
74
+ ctx.ui.notify(`Last ${entries.length} notification(s):\n${lines.join("\n")}${suffix}`, "info");
69
75
  return true;
70
76
  }
71
77
 
@@ -86,7 +92,9 @@ export async function handleNotificationsCommand(
86
92
  const lines = entries.slice(0, 20).map((e) =>
87
93
  `${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`,
88
94
  );
89
- const suffix = entries.length > 20 ? `\n... and ${entries.length - 20} more` : "";
95
+ const suffix = entries.length > 20
96
+ ? `\n... and ${entries.length - 20} more (open /gsd notifications to browse all)`
97
+ : "";
90
98
  ctx.ui.notify(`${severity} notifications (${entries.length}):\n${lines.join("\n")}${suffix}`, "info");
91
99
  return true;
92
100
  }
@@ -96,8 +104,8 @@ export async function handleNotificationsCommand(
96
104
  // Try overlay first (TUI mode)
97
105
  if (ctx.hasUI) {
98
106
  try {
99
- await ctx.ui.custom<void>(
100
- (tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done()),
107
+ const result = await ctx.ui.custom<boolean>(
108
+ (tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)),
101
109
  {
102
110
  overlay: true,
103
111
  overlayOptions: {
@@ -109,7 +117,9 @@ export async function handleNotificationsCommand(
109
117
  },
110
118
  },
111
119
  );
112
- return true;
120
+ if (result !== undefined) {
121
+ return true;
122
+ }
113
123
  } catch {
114
124
  // Fall through to text output if overlay fails
115
125
  }
@@ -18,7 +18,7 @@ import { createRun, listRuns } from "../../run-manager.js";
18
18
  import {
19
19
  setActiveEngineId,
20
20
  setActiveRunDir,
21
- startAuto,
21
+ startAutoDetached,
22
22
  pauseAuto,
23
23
  isAutoActive,
24
24
  getActiveEngineId,
@@ -77,7 +77,7 @@ async function handleCustomWorkflow(
77
77
  setActiveEngineId("custom");
78
78
  setActiveRunDir(runDir);
79
79
  ctx.ui.notify(`Created workflow run: ${defName}\nRun dir: ${runDir}`, "info");
80
- await startAuto(ctx, pi, base, false);
80
+ startAutoDetached(ctx, pi, base, false);
81
81
  } catch (err) {
82
82
  // Clean up engine state so a failed workflow run doesn't pollute the next /gsd auto
83
83
  setActiveEngineId(null);
@@ -157,13 +157,8 @@ async function handleCustomWorkflow(
157
157
  ctx.ui.notify("No custom workflow to resume. Use /gsd auto for dev workflow.", "warning");
158
158
  return true;
159
159
  }
160
- try {
161
- await startAuto(ctx, pi, projectRoot(), false);
162
- ctx.ui.notify("Custom workflow resumed.", "info");
163
- } catch (err) {
164
- const msg = err instanceof Error ? err.message : String(err);
165
- ctx.ui.notify(`Failed to resume workflow: ${msg}`, "error");
166
- }
160
+ startAutoDetached(ctx, pi, projectRoot(), false);
161
+ ctx.ui.notify("Custom workflow resumed.", "info");
167
162
  return true;
168
163
  }
169
164
 
@@ -278,4 +273,3 @@ export function getNextMilestoneId(basePath: string): string {
278
273
  const uniqueIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
279
274
  return nextMilestoneId(milestoneIds, uniqueIds);
280
275
  }
281
-
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Full-screen overlay showing auto-mode progress: milestone/slice/task
5
5
  * breakdown, current unit, completed units, timing, and activity log.
6
- * Toggled with Ctrl+Alt+G (⌃⌥G on macOS) or opened from /gsd status.
6
+ * Toggled with Ctrl+Alt+G (⌃⌥G on macOS), Ctrl+Shift+G fallback,
7
+ * or opened from /gsd status.
7
8
  */
8
9
 
9
10
  import type { Theme } from "@gsd/pi-coding-agent";
@@ -26,6 +27,7 @@ import { formatDuration, padRight, joinColumns, centerLine, fitColumns, STATUS_G
26
27
  import { estimateTimeRemaining } from "./auto-dashboard.js";
27
28
  import { computeProgressScore, formatProgressLine } from "./progress-score.js";
28
29
  import { runEnvironmentChecks, type EnvironmentCheckResult } from "./doctor-environment.js";
30
+ import { formattedShortcutPair } from "./shortcut-defs.js";
29
31
 
30
32
  function unitLabel(type: string): string {
31
33
  switch (type) {
@@ -203,7 +205,12 @@ export class GSDDashboardOverlay {
203
205
  }
204
206
 
205
207
  handleInput(data: string): void {
206
- if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.ctrlAlt("g"))) {
208
+ if (
209
+ matchesKey(data, Key.escape) ||
210
+ matchesKey(data, Key.ctrl("c")) ||
211
+ matchesKey(data, Key.ctrlAlt("g")) ||
212
+ matchesKey(data, Key.ctrlShift("g"))
213
+ ) {
207
214
  this.dispose();
208
215
  this.onClose();
209
216
  return;
@@ -587,7 +594,7 @@ export class GSDDashboardOverlay {
587
594
 
588
595
  lines.push(blank());
589
596
  lines.push(hr());
590
- lines.push(centered(th.fg("dim", "↑↓ scroll · g/G top/end · esc close")));
597
+ lines.push(centered(th.fg("dim", `↑↓ scroll · g/G top/end · Esc/${formattedShortcutPair("dashboard")} close`)));
591
598
 
592
599
  return lines;
593
600
  }
@@ -107,10 +107,27 @@ export function getPriorSliceCompletionBlocker(
107
107
  // it may be a cross-milestone reference handled elsewhere.
108
108
  }
109
109
  } else {
110
+ // Positional fallback is only a heuristic for legacy slices with no
111
+ // declared dependencies. Skip any earlier slice that depends on the
112
+ // target, directly or transitively, or we can deadlock a valid zero-dep
113
+ // slice behind its own downstream dependents (#3720).
114
+ const reverseDependents = new Set<string>();
115
+ let changed = true;
116
+ while (changed) {
117
+ changed = false;
118
+ for (const slice of slices) {
119
+ if (reverseDependents.has(slice.id)) continue;
120
+ if (slice.depends.some((depId) => depId === targetSid || reverseDependents.has(depId))) {
121
+ reverseDependents.add(slice.id);
122
+ changed = true;
123
+ }
124
+ }
125
+ }
126
+
110
127
  const targetIndex = slices.findIndex((slice) => slice.id === targetSid);
111
128
  const incomplete = slices
112
129
  .slice(0, targetIndex)
113
- .find((slice) => !slice.done);
130
+ .find((slice) => !slice.done && !reverseDependents.has(slice.id));
114
131
  if (incomplete) {
115
132
  return `Cannot dispatch ${unitType} ${unitId}: earlier slice ${targetMid}/${incomplete.id} is not complete.`;
116
133
  }
@@ -47,7 +47,7 @@ const RATE_LIMIT_RE = /rate.?limit|too many requests|429/i;
47
47
  const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns/i;
48
48
  const SERVER_RE = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i;
49
49
  // ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first).
50
- const CONNECTION_RE = /terminated|connection.?refused|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
50
+ const CONNECTION_RE = /terminated|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
51
51
  // Catch-all for V8 JSON.parse errors: all modern variants end with "in JSON at position \d+".
52
52
  // This eliminates the need to enumerate every error message variant individually.
53
53
  const STREAM_RE = /in JSON at position \d+|Unexpected end of JSON|SyntaxError.*JSON/i;
@@ -650,19 +650,33 @@ function getDbCompletionCounts(): DbCompletionCounts | null {
650
650
  * Exported for testability.
651
651
  */
652
652
  export function detectStuckLoops(units: UnitMetrics[], anomalies: ForensicAnomaly[]): void {
653
- // First, collect unique startedAt values per type/id key
654
- const dispatchMap = new Map<string, Set<number>>();
653
+ // First, collect unique startedAt values per type/id key, bucketed by
654
+ // autoSessionKey when available so cross-session recovery does not look
655
+ // like a within-session stuck loop.
656
+ const dispatchMap = new Map<string, Map<string, Set<number>>>();
655
657
  for (const u of units) {
656
658
  const key = `${u.type}/${u.id}`;
657
- let starts = dispatchMap.get(key);
659
+ let sessionBuckets = dispatchMap.get(key);
660
+ if (!sessionBuckets) {
661
+ sessionBuckets = new Map();
662
+ dispatchMap.set(key, sessionBuckets);
663
+ }
664
+
665
+ const sessionKey = u.autoSessionKey ?? "__legacy__";
666
+ let starts = sessionBuckets.get(sessionKey);
658
667
  if (!starts) {
659
668
  starts = new Set();
660
- dispatchMap.set(key, starts);
669
+ sessionBuckets.set(sessionKey, starts);
661
670
  }
662
671
  starts.add(u.startedAt);
663
672
  }
664
- for (const [key, starts] of dispatchMap) {
665
- const count = starts.size;
673
+
674
+ for (const [key, sessionBuckets] of dispatchMap) {
675
+ const hasSessionAwareData = Array.from(sessionBuckets.keys()).some((sessionKey) => sessionKey !== "__legacy__");
676
+ const count = hasSessionAwareData
677
+ ? Math.max(...Array.from(sessionBuckets.values(), (starts) => starts.size))
678
+ : (sessionBuckets.get("__legacy__")?.size ?? 0);
679
+
666
680
  if (count > 1) {
667
681
  const [unitType, ...idParts] = key.split("/");
668
682
  anomalies.push({
@@ -671,7 +685,9 @@ export function detectStuckLoops(units: UnitMetrics[], anomalies: ForensicAnomal
671
685
  unitType,
672
686
  unitId: idParts.join("/"),
673
687
  summary: `Unit ${key} was dispatched ${count} times`,
674
- details: `Repeated dispatch suggests the unit completed but its artifacts weren't verified, or the state machine kept returning it.`,
688
+ details: hasSessionAwareData
689
+ ? `Repeated dispatch within the same auto session suggests the unit completed but its artifacts were not verified, or the state machine kept returning it. Cross-session recovery runs are ignored.`
690
+ : `Repeated dispatch suggests the unit completed but its artifacts weren't verified, or the state machine kept returning it.`,
675
691
  });
676
692
  }
677
693
  }
@@ -15,7 +15,7 @@ import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
15
15
  import { buildSkillActivationBlock } from "./auto-prompts.js";
16
16
  import { deriveState } from "./state.js";
17
17
  import { invalidateAllCaches } from "./cache.js";
18
- import { startAuto } from "./auto.js";
18
+ import { startAutoDetached } from "./auto.js";
19
19
  import { clearLock } from "./crash-recovery.js";
20
20
  import {
21
21
  assessInterruptedSession,
@@ -67,7 +67,6 @@ export {
67
67
  showQueue, handleQueueReorder, showQueueAdd,
68
68
  buildExistingMilestonesContext,
69
69
  } from "./guided-flow-queue.js";
70
- import { getErrorMessage } from "./error-utils.js";
71
70
  import { logWarning } from "./workflow-logger.js";
72
71
 
73
72
  // ─── ID Generation with Reservation ─────────────────────────────────────────
@@ -244,11 +243,7 @@ export function checkAutoStartAfterDiscuss(): boolean {
244
243
 
245
244
  pendingAutoStartMap.delete(basePath);
246
245
  ctx.ui.notify(`Milestone ${milestoneId} ready.`, "info");
247
- startAuto(ctx, pi, basePath, false, { step }).catch((err) => {
248
- ctx.ui.notify(`Auto-start failed: ${getErrorMessage(err)}`, "error");
249
- logWarning("guided", `auto start error: ${getErrorMessage(err)}`);
250
- debugLog("auto-start-failed", { error: getErrorMessage(err) });
251
- });
246
+ startAutoDetached(ctx, pi, basePath, false, { step });
252
247
  return true;
253
248
  }
254
249
 
@@ -1305,7 +1300,7 @@ export async function showSmartEntry(
1305
1300
  ],
1306
1301
  });
1307
1302
  if (resume === "resume") {
1308
- await startAuto(ctx, pi, basePath, false, {
1303
+ startAutoDetached(ctx, pi, basePath, false, {
1309
1304
  interrupted,
1310
1305
  step: interrupted.pausedSession?.stepMode ?? false,
1311
1306
  });
@@ -1647,7 +1642,7 @@ export async function showSmartEntry(
1647
1642
  });
1648
1643
 
1649
1644
  if (choice === "auto") {
1650
- await startAuto(ctx, pi, basePath, false);
1645
+ startAutoDetached(ctx, pi, basePath, false);
1651
1646
  } else if (choice === "status") {
1652
1647
  const { fireStatusViaCommand } = await import("./commands.js");
1653
1648
  await fireStatusViaCommand(ctx);
@@ -1859,7 +1854,7 @@ export async function showSmartEntry(
1859
1854
  });
1860
1855
 
1861
1856
  if (choice === "auto") {
1862
- await startAuto(ctx, pi, basePath, false);
1857
+ startAutoDetached(ctx, pi, basePath, false);
1863
1858
  return;
1864
1859
  }
1865
1860
 
@@ -34,6 +34,7 @@ export interface PausedSessionMetadata {
34
34
  activeEngineId?: string;
35
35
  activeRunDir?: string | null;
36
36
  autoStartTime?: number;
37
+ milestoneLock?: string | null;
37
38
  }
38
39
 
39
40
  export interface InterruptedSessionAssessment {
@@ -41,6 +41,7 @@ export interface UnitMetrics {
41
41
  model: string; // model ID used
42
42
  startedAt: number; // ms timestamp
43
43
  finishedAt: number; // ms timestamp
44
+ autoSessionKey?: string; // identifies one auto-mode run across pause/resume
44
45
  tokens: TokenCounts;
45
46
  cost: number; // total USD cost
46
47
  toolCalls: number;
@@ -133,7 +134,16 @@ export function snapshotUnitMetrics(
133
134
  unitId: string,
134
135
  startedAt: number,
135
136
  model: string,
136
- opts?: { tier?: string; modelDowngraded?: boolean; contextWindowTokens?: number; truncationSections?: number; continueHereFired?: boolean; promptCharCount?: number; baselineCharCount?: number },
137
+ opts?: {
138
+ tier?: string;
139
+ modelDowngraded?: boolean;
140
+ contextWindowTokens?: number;
141
+ truncationSections?: number;
142
+ continueHereFired?: boolean;
143
+ promptCharCount?: number;
144
+ baselineCharCount?: number;
145
+ autoSessionKey?: string;
146
+ },
137
147
  ): UnitMetrics | null {
138
148
  if (!ledger) return null;
139
149
 
@@ -181,6 +191,7 @@ export function snapshotUnitMetrics(
181
191
  model,
182
192
  startedAt,
183
193
  finishedAt: Date.now(),
194
+ ...(opts?.autoSessionKey ? { autoSessionKey: opts.autoSessionKey } : {}),
184
195
  tokens,
185
196
  cost,
186
197
  toolCalls,