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
@@ -8,6 +8,7 @@ import { classifyUnitComplexity, tierLabel } from "./complexity-classifier.js";
8
8
  import { resolveModelForComplexity, escalateTier, getEligibleModels, loadCapabilityOverrides, adjustToolSet } from "./model-router.js";
9
9
  import { getLedger, getProjectTotals } from "./metrics.js";
10
10
  import { unitPhaseLabel } from "./auto-dashboard.js";
11
+ import { getSessionModelOverride } from "./session-model-override.js";
11
12
  export function resolvePreferredModelConfig(unitType, autoModeStartModel,
12
13
  /** When false, only return explicit per-phase model configs — do not
13
14
  * synthesize a routing ceiling from dynamic_routing.tier_models (#3962). */
@@ -44,8 +45,15 @@ isAutoMode = true) {
44
45
  export async function selectAndApplyModel(ctx, pi, unitType, unitId, basePath, prefs, verbose, autoModeStartModel, retryContext,
45
46
  /** When false (interactive/guided-flow), skip dynamic routing and use the session model.
46
47
  * Dynamic routing only applies in auto-mode where cost optimization is expected. (#3962) */
47
- isAutoMode = true) {
48
- const modelConfig = resolvePreferredModelConfig(unitType, autoModeStartModel, isAutoMode);
48
+ isAutoMode = true,
49
+ /** Explicit /gsd model pin captured at bootstrap for long-running auto loops. */
50
+ sessionModelOverride) {
51
+ const effectiveSessionModelOverride = sessionModelOverride === undefined
52
+ ? getSessionModelOverride(ctx.sessionManager.getSessionId())
53
+ : (sessionModelOverride ?? undefined);
54
+ const modelConfig = effectiveSessionModelOverride
55
+ ? undefined
56
+ : resolvePreferredModelConfig(unitType, autoModeStartModel, isAutoMode);
49
57
  let routing = null;
50
58
  let appliedModel = null;
51
59
  if (modelConfig) {
@@ -39,6 +39,7 @@ import { join } from "node:path";
39
39
  import { sep as pathSep } from "node:path";
40
40
  import { resolveProjectRootDbPath } from "./bootstrap/dynamic-tools.js";
41
41
  import { resolveDefaultSessionModel, resolveDynamicRoutingConfig } from "./preferences-models.js";
42
+ import { getSessionModelOverride } from "./session-model-override.js";
42
43
  /**
43
44
  * Bootstrap a fresh auto-mode session. Handles everything from git init
44
45
  * through secrets collection, returning when ready for the first
@@ -187,12 +188,17 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
187
188
  // Capture the user's session model before guided-flow dispatch can apply a
188
189
  // phase-specific planning model for a discuss turn (#2829).
189
190
  //
190
- // GSD PREFERENCES.md takes priority over the session model from settings.json
191
- // (#3517). The session model (ctx.model) comes from findInitialModel() which
192
- // reads defaultProvider/defaultModel from ~/.gsd/agent/settings.json. When
193
- // the user has explicit model preferences in PREFERENCES.md, those should win.
191
+ // Precedence:
192
+ // 1) Explicit session override via /gsd model (this session)
193
+ // 2) GSD model preferences from PREFERENCES.md
194
+ // 3) Current session model from settings/session restore
195
+ //
196
+ // This preserves #3517 defaults while honoring explicit runtime model
197
+ // selection for subsequent /gsd runs in the same session.
198
+ const manualSessionOverride = getSessionModelOverride(ctx.sessionManager.getSessionId());
194
199
  const preferredModel = resolveDefaultSessionModel(ctx.model?.provider);
195
- const startModelSnapshot = preferredModel
200
+ const startModelSnapshot = manualSessionOverride
201
+ ?? preferredModel
196
202
  ?? (ctx.model
197
203
  ? { provider: ctx.model.provider, id: ctx.model.id }
198
204
  : null);
@@ -523,7 +529,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
523
529
  }
524
530
  }
525
531
  // ── DB lifecycle ──
526
- const gsdDbPath = join(s.basePath, ".gsd", "gsd.db");
532
+ const gsdDbPath = resolveProjectRootDbPath(s.basePath);
527
533
  const gsdDirPath = join(s.basePath, ".gsd");
528
534
  if (existsSync(gsdDirPath) && !existsSync(gsdDbPath)) {
529
535
  const hasDecisions = existsSync(join(gsdDirPath, "DECISIONS.md"));
@@ -571,6 +577,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
571
577
  id: startModelSnapshot.id,
572
578
  };
573
579
  }
580
+ s.manualSessionModelOverride = manualSessionOverride ?? null;
574
581
  // Apply worker model override from parallel orchestrator (#worker-model).
575
582
  // GSD_WORKER_MODEL is injected by the coordinator when parallel.worker_model
576
583
  // is configured, so parallel milestone workers use a cheaper model than the
@@ -82,7 +82,7 @@ export function clearInFlightTools() {
82
82
  * handler. When these errors occur, retrying the same unit will produce the same
83
83
  * failure, so the retry loop must be broken.
84
84
  */
85
- const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}' in JSON|Unexpected end of JSON|Unexpected token.*in JSON/i;
85
+ const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}'(?: after property value)?(?: in JSON)?|Unexpected end of JSON|Unexpected token.*in JSON/i;
86
86
  /**
87
87
  * Returns true if the error message indicates a tool invocation failure due to
88
88
  * malformed/truncated arguments (as opposed to a normal tool execution error).
@@ -1795,7 +1795,7 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1795
1795
  // 12. Remove worktree directory first (must happen before branch deletion)
1796
1796
  try {
1797
1797
  removeWorktree(originalBasePath_, milestoneId, {
1798
- branch: null,
1798
+ branch: milestoneBranch,
1799
1799
  deleteBranch: false,
1800
1800
  });
1801
1801
  }
@@ -49,6 +49,7 @@ import { pruneQueueOrder } from "./queue-order.js";
49
49
  import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
50
50
  import { reconcileMergeState, } from "./auto-recovery.js";
51
51
  import { resolveDispatch, DISPATCH_RULES } from "./auto-dispatch.js";
52
+ import { getErrorMessage } from "./error-utils.js";
52
53
  import { initRegistry, convertDispatchRules } from "./rule-registry.js";
53
54
  import { emitJournalEvent as _emitJournalEvent } from "./journal.js";
54
55
  import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache, clearSliceProgressCache, hideFooter, } from "./auto-dashboard.js";
@@ -103,6 +104,40 @@ function restoreProjectRootEnv() {
103
104
  s.hadProjectRootEnv = false;
104
105
  s.projectRootEnvCaptured = false;
105
106
  }
107
+ function captureMilestoneLockEnv(milestoneId) {
108
+ if (!s.milestoneLockEnvCaptured) {
109
+ s.hadMilestoneLockEnv = Object.prototype.hasOwnProperty.call(process.env, "GSD_MILESTONE_LOCK");
110
+ s.previousMilestoneLockEnv = process.env.GSD_MILESTONE_LOCK ?? null;
111
+ s.milestoneLockEnvCaptured = true;
112
+ }
113
+ if (milestoneId) {
114
+ process.env.GSD_MILESTONE_LOCK = milestoneId;
115
+ }
116
+ else {
117
+ delete process.env.GSD_MILESTONE_LOCK;
118
+ }
119
+ }
120
+ function restoreMilestoneLockEnv() {
121
+ if (!s.milestoneLockEnvCaptured)
122
+ return;
123
+ if (s.hadMilestoneLockEnv && s.previousMilestoneLockEnv !== null) {
124
+ process.env.GSD_MILESTONE_LOCK = s.previousMilestoneLockEnv;
125
+ }
126
+ else {
127
+ delete process.env.GSD_MILESTONE_LOCK;
128
+ }
129
+ s.previousMilestoneLockEnv = null;
130
+ s.hadMilestoneLockEnv = false;
131
+ s.milestoneLockEnvCaptured = false;
132
+ }
133
+ export function startAutoDetached(ctx, pi, base, verboseMode, options) {
134
+ void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
135
+ const message = getErrorMessage(err);
136
+ ctx.ui.notify(`Auto-start failed: ${message}`, "error");
137
+ logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
138
+ debugLog("auto-start-failed", { error: message });
139
+ });
140
+ }
106
141
  export function shouldUseWorktreeIsolation() {
107
142
  const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
108
143
  if (prefs?.isolation === "worktree")
@@ -324,6 +359,7 @@ function clearUnitTimeout() {
324
359
  /** Build snapshot metric opts. */
325
360
  function buildSnapshotOpts(_unitType, _unitId) {
326
361
  return {
362
+ ...(s.autoStartTime > 0 ? { autoSessionKey: String(s.autoStartTime) } : {}),
327
363
  promptCharCount: s.lastPromptCharCount,
328
364
  baselineCharCount: s.lastBaselineCharCount,
329
365
  ...(s.currentUnitRouting ?? {}),
@@ -340,6 +376,7 @@ function handleLostSessionLock(ctx, lockStatus) {
340
376
  s.paused = false;
341
377
  clearUnitTimeout();
342
378
  restoreProjectRootEnv();
379
+ restoreMilestoneLockEnv();
343
380
  deregisterSigtermHandler();
344
381
  clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
345
382
  const base = lockBase();
@@ -371,6 +408,7 @@ function cleanupAfterLoopExit(ctx) {
371
408
  s.active = false;
372
409
  clearUnitTimeout();
373
410
  restoreProjectRootEnv();
411
+ restoreMilestoneLockEnv();
374
412
  // Clear crash lock and release session lock so the next `/gsd next` does
375
413
  // not see a stale lock with the current PID and treat it as a "remote"
376
414
  // session (which would cause it to SIGTERM itself). (#2730)
@@ -628,6 +666,7 @@ export async function stopAuto(ctx, pi, reason) {
628
666
  ctx?.ui.setWidget("gsd-progress", undefined);
629
667
  ctx?.ui.setFooter(undefined);
630
668
  restoreProjectRootEnv();
669
+ restoreMilestoneLockEnv();
631
670
  // Reset all session state in one call
632
671
  s.reset();
633
672
  }
@@ -673,6 +712,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
673
712
  activeEngineId: s.activeEngineId,
674
713
  activeRunDir: s.activeRunDir,
675
714
  autoStartTime: s.autoStartTime,
715
+ milestoneLock: s.sessionMilestoneLock ?? undefined,
676
716
  };
677
717
  const runtimeDir = join(gsdRoot(s.originalBasePath || s.basePath), "runtime");
678
718
  mkdirSync(runtimeDir, { recursive: true });
@@ -704,6 +744,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
704
744
  s.active = false;
705
745
  s.paused = true;
706
746
  restoreProjectRootEnv();
747
+ restoreMilestoneLockEnv();
707
748
  s.pendingVerificationRetry = null;
708
749
  s.verificationRetryCount.clear();
709
750
  ctx?.ui.setStatus("gsd-auto", "paused");
@@ -859,6 +900,12 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
859
900
  }
860
901
  const requestedStepMode = options?.step ?? false;
861
902
  const interruptedAssessment = options?.interrupted ?? null;
903
+ if (options?.milestoneLock !== undefined) {
904
+ s.sessionMilestoneLock = options.milestoneLock ?? null;
905
+ }
906
+ if (s.sessionMilestoneLock) {
907
+ captureMilestoneLockEnv(s.sessionMilestoneLock);
908
+ }
862
909
  // Escape stale worktree cwd from a previous milestone (#608).
863
910
  base = escapeStaleWorktree(base);
864
911
  const freshStartAssessment = interruptedAssessment
@@ -883,6 +930,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
883
930
  s.originalBasePath = meta.originalBasePath || base;
884
931
  s.stepMode = meta.stepMode ?? requestedStepMode;
885
932
  s.autoStartTime = meta.autoStartTime || Date.now();
933
+ s.sessionMilestoneLock = meta.milestoneLock ?? null;
886
934
  s.paused = true;
887
935
  try {
888
936
  unlinkSync(pausedPath);
@@ -918,6 +966,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
918
966
  s.pausedUnitType = meta.unitType ?? null;
919
967
  s.pausedUnitId = meta.unitId ?? null;
920
968
  s.autoStartTime = meta.autoStartTime || Date.now();
969
+ s.sessionMilestoneLock = meta.milestoneLock ?? null;
921
970
  s.paused = true;
922
971
  try {
923
972
  unlinkSync(pausedPath);
@@ -946,6 +995,9 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
946
995
  if (!s.autoStartTime || s.autoStartTime <= 0)
947
996
  s.autoStartTime = Date.now();
948
997
  }
998
+ if (s.sessionMilestoneLock) {
999
+ captureMilestoneLockEnv(s.sessionMilestoneLock);
1000
+ }
949
1001
  if (!s.paused) {
950
1002
  s.stepMode = requestedStepMode;
951
1003
  }
@@ -111,6 +111,8 @@ export function registerHooks(pi) {
111
111
  return { cancel: true };
112
112
  }
113
113
  const basePath = process.cwd();
114
+ const { ensureDbOpen } = await import("./dynamic-tools.js");
115
+ await ensureDbOpen();
114
116
  const state = await deriveState(basePath);
115
117
  if (!state.activeMilestone || !state.activeSlice || !state.activeTask)
116
118
  return;
@@ -4,61 +4,76 @@ import { Key } from "@gsd/pi-tui";
4
4
  import { GSDDashboardOverlay } from "../dashboard-overlay.js";
5
5
  import { GSDNotificationOverlay } from "../notification-overlay.js";
6
6
  import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
7
+ import { GSD_SHORTCUTS } from "../shortcut-defs.js";
7
8
  import { projectRoot } from "../commands/context.js";
8
9
  import { shortcutDesc } from "../../shared/mod.js";
9
10
  export function registerShortcuts(pi) {
10
- pi.registerShortcut(Key.ctrlAlt("g"), {
11
- description: shortcutDesc("Open GSD dashboard", "/gsd status"),
12
- handler: async (ctx) => {
13
- const basePath = projectRoot();
14
- if (!existsSync(join(basePath, ".gsd"))) {
15
- ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
16
- return;
17
- }
18
- await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)), {
19
- overlay: true,
20
- overlayOptions: {
21
- width: "90%",
22
- minWidth: 80,
23
- maxHeight: "92%",
24
- anchor: "center",
25
- },
26
- });
27
- },
11
+ const overlayOptions = {
12
+ width: "90%",
13
+ minWidth: 80,
14
+ maxHeight: "92%",
15
+ anchor: "center",
16
+ };
17
+ const openDashboardOverlay = async (ctx) => {
18
+ const basePath = projectRoot();
19
+ if (!existsSync(join(basePath, ".gsd"))) {
20
+ ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
21
+ return;
22
+ }
23
+ await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)), {
24
+ overlay: true,
25
+ overlayOptions,
26
+ });
27
+ };
28
+ const openNotificationsOverlay = async (ctx) => {
29
+ await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)), {
30
+ overlay: true,
31
+ overlayOptions: {
32
+ width: "80%",
33
+ minWidth: 60,
34
+ maxHeight: "88%",
35
+ anchor: "center",
36
+ backdrop: true,
37
+ },
38
+ });
39
+ };
40
+ const openParallelOverlay = async (ctx) => {
41
+ const basePath = projectRoot();
42
+ const parallelDir = join(basePath, ".gsd", "parallel");
43
+ if (!existsSync(parallelDir)) {
44
+ ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
45
+ return;
46
+ }
47
+ await ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(true), basePath), {
48
+ overlay: true,
49
+ overlayOptions,
50
+ });
51
+ };
52
+ pi.registerShortcut(Key.ctrlAlt(GSD_SHORTCUTS.dashboard.key), {
53
+ description: shortcutDesc(GSD_SHORTCUTS.dashboard.action, GSD_SHORTCUTS.dashboard.command),
54
+ handler: openDashboardOverlay,
28
55
  });
29
- pi.registerShortcut(Key.ctrlAlt("n"), {
30
- description: shortcutDesc("Open notification history", "/gsd notifications"),
31
- handler: async (ctx) => {
32
- await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)), {
33
- overlay: true,
34
- overlayOptions: {
35
- width: "80%",
36
- minWidth: 60,
37
- maxHeight: "88%",
38
- anchor: "center",
39
- backdrop: true,
40
- },
41
- });
42
- },
56
+ // Fallback for terminals where Ctrl+Alt letter chords are not forwarded reliably.
57
+ pi.registerShortcut(Key.ctrlShift(GSD_SHORTCUTS.dashboard.key), {
58
+ description: shortcutDesc(`${GSD_SHORTCUTS.dashboard.action} (fallback)`, GSD_SHORTCUTS.dashboard.command),
59
+ handler: openDashboardOverlay,
43
60
  });
44
- pi.registerShortcut(Key.ctrlAlt("p"), {
45
- description: shortcutDesc("Open parallel worker monitor", "/gsd parallel watch"),
46
- handler: async (ctx) => {
47
- const basePath = projectRoot();
48
- const parallelDir = join(basePath, ".gsd", "parallel");
49
- if (!existsSync(parallelDir)) {
50
- ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
51
- return;
52
- }
53
- await ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(true)), {
54
- overlay: true,
55
- overlayOptions: {
56
- width: "90%",
57
- minWidth: 80,
58
- maxHeight: "92%",
59
- anchor: "center",
60
- },
61
- });
62
- },
61
+ pi.registerShortcut(Key.ctrlAlt(GSD_SHORTCUTS.notifications.key), {
62
+ description: shortcutDesc(GSD_SHORTCUTS.notifications.action, GSD_SHORTCUTS.notifications.command),
63
+ handler: openNotificationsOverlay,
64
+ });
65
+ // Fallback for terminals where Ctrl+Alt letter chords are not forwarded reliably.
66
+ pi.registerShortcut(Key.ctrlShift(GSD_SHORTCUTS.notifications.key), {
67
+ description: shortcutDesc(`${GSD_SHORTCUTS.notifications.action} (fallback)`, GSD_SHORTCUTS.notifications.command),
68
+ handler: openNotificationsOverlay,
69
+ });
70
+ pi.registerShortcut(Key.ctrlAlt(GSD_SHORTCUTS.parallel.key), {
71
+ description: shortcutDesc(GSD_SHORTCUTS.parallel.action, GSD_SHORTCUTS.parallel.command),
72
+ handler: openParallelOverlay,
73
+ });
74
+ // Fallback for terminals where Ctrl+Alt letter chords are not forwarded reliably.
75
+ pi.registerShortcut(Key.ctrlShift(GSD_SHORTCUTS.parallel.key), {
76
+ description: shortcutDesc(`${GSD_SHORTCUTS.parallel.action} (fallback)`, GSD_SHORTCUTS.parallel.command),
77
+ handler: openParallelOverlay,
63
78
  });
64
79
  }
@@ -257,6 +257,10 @@ function buildWorktreeContextBlock() {
257
257
  */
258
258
  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)$/;
259
259
  async function buildGuidedExecuteContextInjection(prompt, basePath) {
260
+ const ensureStateDbOpen = async () => {
261
+ const { ensureDbOpen } = await import("./dynamic-tools.js");
262
+ await ensureDbOpen();
263
+ };
260
264
  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);
261
265
  if (executeMatch) {
262
266
  const [, taskId, taskTitle, sliceId, milestoneId] = executeMatch;
@@ -265,6 +269,7 @@ async function buildGuidedExecuteContextInjection(prompt, basePath) {
265
269
  const resumeMatch = prompt.match(/Resume interrupted work\.[\s\S]*?slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
266
270
  if (resumeMatch) {
267
271
  const [, sliceId, milestoneId] = resumeMatch;
272
+ await ensureStateDbOpen();
268
273
  const state = await deriveState(basePath);
269
274
  if (state.activeMilestone?.id === milestoneId && state.activeSlice?.id === sliceId && state.activeTask) {
270
275
  return buildTaskExecutionContextInjection(basePath, milestoneId, sliceId, state.activeTask.id, state.activeTask.title);
@@ -279,6 +284,7 @@ async function buildGuidedExecuteContextInjection(prompt, basePath) {
279
284
  // replanning, gate evaluation, or other non-execution phases.
280
285
  const trimmed = prompt.trim().toLowerCase().replace(/[.!?,]+$/g, "");
281
286
  if (RESUME_INTENT_PATTERNS.test(trimmed)) {
287
+ await ensureStateDbOpen();
282
288
  const state = await deriveState(basePath);
283
289
  if (state.phase === "executing" && state.activeTask && state.activeMilestone && state.activeSlice) {
284
290
  return buildTaskExecutionContextInjection(basePath, state.activeMilestone.id, state.activeSlice.id, state.activeTask.id, state.activeTask.title);
@@ -1,8 +1,18 @@
1
1
  import { checkRemoteAutoSession, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js";
2
- import { assertSafeDirectory } from "../validate-directory.js";
2
+ import { validateDirectory } from "../validate-directory.js";
3
3
  import { resolveProjectRoot } from "../worktree.js";
4
4
  import { showNextAction } from "../../shared/tui.js";
5
5
  import { handleStatus } from "./handlers/core.js";
6
+ /**
7
+ * Typed error for when GSD is run outside a valid project directory.
8
+ * Command handlers catch this to show a friendly message instead of a raw exception.
9
+ */
10
+ export class GSDNoProjectError extends Error {
11
+ constructor(reason) {
12
+ super(reason);
13
+ this.name = "GSDNoProjectError";
14
+ }
15
+ }
6
16
  export function projectRoot() {
7
17
  let cwd;
8
18
  try {
@@ -13,11 +23,10 @@ export function projectRoot() {
13
23
  cwd = process.env.HOME ?? "/";
14
24
  }
15
25
  const root = resolveProjectRoot(cwd);
16
- if (root !== cwd) {
17
- assertSafeDirectory(cwd);
18
- }
19
- else {
20
- assertSafeDirectory(root);
26
+ const pathToCheck = root !== cwd ? cwd : root;
27
+ const result = validateDirectory(pathToCheck);
28
+ if (result.severity === "blocked") {
29
+ throw new GSDNoProjectError(result.reason ?? "GSD must be run inside a project directory.");
21
30
  }
22
31
  return root;
23
32
  }
@@ -1,3 +1,4 @@
1
+ import { GSDNoProjectError } from "./context.js";
1
2
  import { handleAutoCommand } from "./handlers/auto.js";
2
3
  import { handleCoreCommand } from "./handlers/core.js";
3
4
  import { handleOpsCommand } from "./handlers/ops.js";
@@ -12,10 +13,19 @@ export async function handleGSDCommand(args, ctx, pi) {
12
13
  () => handleWorkflowCommand(trimmed, ctx, pi),
13
14
  () => handleOpsCommand(trimmed, ctx, pi),
14
15
  ];
15
- for (const handler of handlers) {
16
- if (await handler()) {
16
+ try {
17
+ for (const handler of handlers) {
18
+ if (await handler()) {
19
+ return;
20
+ }
21
+ }
22
+ }
23
+ catch (err) {
24
+ if (err instanceof GSDNoProjectError) {
25
+ ctx.ui.notify(`${err.message} \`cd\` into a project directory first.`, "warning");
17
26
  return;
18
27
  }
28
+ throw err;
19
29
  }
20
30
  ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
21
31
  }
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { enableDebug } from "../../debug-logger.js";
4
- import { isAutoActive, isAutoPaused, pauseAuto, startAuto, stopAuto, stopAutoRemote } from "../../auto.js";
4
+ import { isAutoActive, isAutoPaused, pauseAuto, startAutoDetached, stopAuto, stopAutoRemote } from "../../auto.js";
5
5
  import { handleRate } from "../../commands-rate.js";
6
6
  import { guardRemoteSession, projectRoot } from "../context.js";
7
7
  import { findMilestoneIds } from "../../milestone-id-utils.js";
@@ -36,27 +36,6 @@ export function parseMilestoneTarget(input) {
36
36
  const rest = input.replace(match[0], "").replace(/\s+/g, " ").trim();
37
37
  return { milestoneId: match[1], rest };
38
38
  }
39
- /**
40
- * Set GSD_MILESTONE_LOCK to target a specific milestone, then run `fn`.
41
- * Clears the env var when `fn` resolves or rejects, so the lock does not
42
- * leak into subsequent commands in the same process.
43
- */
44
- async function withMilestoneLock(milestoneId, fn) {
45
- const previous = process.env.GSD_MILESTONE_LOCK;
46
- process.env.GSD_MILESTONE_LOCK = milestoneId;
47
- try {
48
- await fn();
49
- }
50
- finally {
51
- // Restore previous value (undefined → delete, else restore).
52
- if (previous === undefined) {
53
- delete process.env.GSD_MILESTONE_LOCK;
54
- }
55
- else {
56
- process.env.GSD_MILESTONE_LOCK = previous;
57
- }
58
- }
59
- }
60
39
  export async function handleAutoCommand(trimmed, ctx, pi) {
61
40
  if (trimmed === "next" || trimmed.startsWith("next ")) {
62
41
  if (trimmed.includes("--dry-run")) {
@@ -79,12 +58,10 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
79
58
  return true;
80
59
  }
81
60
  }
82
- if (milestoneId) {
83
- await withMilestoneLock(milestoneId, () => startAuto(ctx, pi, projectRoot(), verboseMode, { step: true }));
84
- }
85
- else {
86
- await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
87
- }
61
+ startAutoDetached(ctx, pi, projectRoot(), verboseMode, {
62
+ step: true,
63
+ milestoneLock: milestoneId,
64
+ });
88
65
  return true;
89
66
  }
90
67
  if (trimmed === "auto" || trimmed.startsWith("auto ")) {
@@ -122,12 +99,12 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
122
99
  await showHeadlessMilestoneCreation(ctx, pi, projectRoot(), seedContent);
123
100
  }
124
101
  else if (milestoneId) {
125
- // Target a specific milestone — use GSD_MILESTONE_LOCK so state
126
- // derivation only sees this milestone (#2521).
127
- await withMilestoneLock(milestoneId, () => startAuto(ctx, pi, projectRoot(), verboseMode));
102
+ startAutoDetached(ctx, pi, projectRoot(), verboseMode, {
103
+ milestoneLock: milestoneId,
104
+ });
128
105
  }
129
106
  else {
130
- await startAuto(ctx, pi, projectRoot(), verboseMode);
107
+ startAutoDetached(ctx, pi, projectRoot(), verboseMode);
131
108
  }
132
109
  return true;
133
110
  }
@@ -168,7 +145,7 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
168
145
  if (trimmed === "") {
169
146
  if (!(await guardRemoteSession(ctx, pi)))
170
147
  return true;
171
- await startAuto(ctx, pi, projectRoot(), false, { step: true });
148
+ startAutoDetached(ctx, pi, projectRoot(), false, { step: true });
172
149
  return true;
173
150
  }
174
151
  return false;
@@ -4,10 +4,43 @@ import { ensurePreferencesFile, handlePrefs, handlePrefsMode, handlePrefsWizard
4
4
  import { runEnvironmentChecks } from "../../doctor-environment.js";
5
5
  import { deriveState } from "../../state.js";
6
6
  import { handleCmux } from "../../commands-cmux.js";
7
+ import { setSessionModelOverride } from "../../session-model-override.js";
7
8
  import { projectRoot } from "../context.js";
8
- import { formatShortcut } from "../../files.js";
9
- export function showHelp(ctx) {
10
- const lines = [
9
+ import { formattedShortcutPair } from "../../shortcut-defs.js";
10
+ export function showHelp(ctx, args = "") {
11
+ const summaryLines = [
12
+ "GSD — Get Shit Done\n",
13
+ "QUICK START",
14
+ " /gsd start <tpl> Start a workflow template",
15
+ " /gsd Run next unit (same as /gsd next)",
16
+ " /gsd auto Run all queued units continuously",
17
+ " /gsd pause Pause auto-mode",
18
+ " /gsd stop Stop auto-mode gracefully",
19
+ "",
20
+ "VISIBILITY",
21
+ ` /gsd status Dashboard (${formattedShortcutPair("dashboard")})`,
22
+ ` /gsd parallel watch Parallel monitor (${formattedShortcutPair("parallel")})`,
23
+ ` /gsd notifications Notification history (${formattedShortcutPair("notifications")})`,
24
+ " /gsd visualize Interactive 10-tab TUI",
25
+ " /gsd queue Show queued/dispatched units",
26
+ "",
27
+ "COURSE CORRECTION",
28
+ " /gsd steer <desc> Apply user override to active work",
29
+ " /gsd capture <text> Quick-capture a thought to CAPTURES.md",
30
+ " /gsd triage Classify and route pending captures",
31
+ " /gsd undo Revert last completed unit [--force]",
32
+ " /gsd rethink Conversational project reorganization",
33
+ "",
34
+ "SETUP",
35
+ " /gsd init Project init wizard",
36
+ " /gsd setup Global setup status [llm|search|remote|keys|prefs]",
37
+ " /gsd model Switch active session model",
38
+ " /gsd prefs Manage preferences",
39
+ " /gsd doctor Diagnose and repair .gsd/ state",
40
+ "",
41
+ "Use /gsd help full for the complete command reference.",
42
+ ];
43
+ const fullLines = [
11
44
  "GSD — Get Shit Done\n",
12
45
  "WORKFLOW",
13
46
  " /gsd start <tpl> Start a workflow template (bugfix, spike, feature, hotfix, etc.)",
@@ -21,12 +54,13 @@ export function showHelp(ctx) {
21
54
  " /gsd new-milestone Create milestone from headless context (used by gsd headless)",
22
55
  "",
23
56
  "VISIBILITY",
24
- ` /gsd status Show progress dashboard (${formatShortcut("Ctrl+Alt+G")})`,
57
+ ` /gsd status Show progress dashboard (${formattedShortcutPair("dashboard")})`,
58
+ ` /gsd parallel watch Open parallel worker monitor (${formattedShortcutPair("parallel")})`,
25
59
  " /gsd visualize Interactive 10-tab TUI (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)",
26
60
  " /gsd queue Show queued/dispatched units and execution order",
27
61
  " /gsd history View execution history [--cost] [--phase] [--model] [N]",
28
62
  " /gsd changelog Show categorized release notes [version]",
29
- ` /gsd notifications View persistent notification history [clear|tail|filter] (${formatShortcut("Ctrl+Alt+N")})`,
63
+ ` /gsd notifications View persistent notification history [clear|tail|filter] (${formattedShortcutPair("notifications")})`,
30
64
  "",
31
65
  "COURSE CORRECTION",
32
66
  " /gsd steer <desc> Apply user override to active work",
@@ -66,7 +100,8 @@ export function showHelp(ctx) {
66
100
  " /gsd inspect Show SQLite DB diagnostics (schema, row counts, recent entries)",
67
101
  " /gsd update Update GSD to the latest version via npm",
68
102
  ];
69
- ctx.ui.notify(lines.join("\n"), "info");
103
+ const full = ["full", "--full", "all"].includes(args.trim().toLowerCase());
104
+ ctx.ui.notify((full ? fullLines : summaryLines).join("\n"), "info");
70
105
  }
71
106
  export async function handleStatus(ctx) {
72
107
  const basePath = projectRoot();
@@ -82,9 +117,9 @@ export async function handleStatus(ctx) {
82
117
  const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)), {
83
118
  overlay: true,
84
119
  overlayOptions: {
85
- width: "70%",
86
- minWidth: 60,
87
- maxHeight: "90%",
120
+ width: "90%",
121
+ minWidth: 80,
122
+ maxHeight: "92%",
88
123
  anchor: "center",
89
124
  },
90
125
  });
@@ -251,11 +286,21 @@ async function handleModel(trimmedArgs, ctx, pi) {
251
286
  ctx.ui.notify(`No API key for ${targetModel.provider}/${targetModel.id}`, "warning");
252
287
  return;
253
288
  }
289
+ // /gsd model is an explicit per-session pin for GSD dispatches.
290
+ // This is captured at auto bootstrap so it survives internal session
291
+ // switches during /gsd auto and /gsd next runs.
292
+ const sessionId = ctx.sessionManager?.getSessionId?.();
293
+ if (sessionId) {
294
+ setSessionModelOverride(sessionId, {
295
+ provider: targetModel.provider,
296
+ id: targetModel.id,
297
+ });
298
+ }
254
299
  ctx.ui.notify(`Model: ${targetModel.provider}/${targetModel.id}`, "info");
255
300
  }
256
301
  export async function handleCoreCommand(trimmed, ctx, pi) {
257
- if (trimmed === "help" || trimmed === "h" || trimmed === "?") {
258
- showHelp(ctx);
302
+ if (trimmed === "help" || trimmed === "h" || trimmed === "?" || trimmed.startsWith("help ")) {
303
+ showHelp(ctx, trimmed.startsWith("help ") ? trimmed.slice(5).trim() : "");
259
304
  return true;
260
305
  }
261
306
  if (trimmed === "status") {