gsd-pi 2.35.0-dev.cd3b7ea → 2.36.0-dev.d612764

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 (194) hide show
  1. package/README.md +3 -1
  2. package/dist/cli.js +7 -2
  3. package/dist/resource-loader.d.ts +1 -1
  4. package/dist/resource-loader.js +13 -1
  5. package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
  6. package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
  7. package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
  8. package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
  9. package/dist/resources/extensions/bg-shell/types.js +0 -2
  10. package/dist/resources/extensions/cmux/index.js +321 -0
  11. package/dist/resources/extensions/context7/index.js +5 -0
  12. package/dist/resources/extensions/get-secrets-from-user.js +2 -30
  13. package/dist/resources/extensions/google-search/index.js +5 -0
  14. package/dist/resources/extensions/gsd/auto-dashboard.js +334 -104
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
  16. package/dist/resources/extensions/gsd/auto-loop.js +28 -3
  17. package/dist/resources/extensions/gsd/auto-model-selection.js +15 -3
  18. package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
  19. package/dist/resources/extensions/gsd/auto-start.js +35 -2
  20. package/dist/resources/extensions/gsd/auto.js +75 -4
  21. package/dist/resources/extensions/gsd/commands-cmux.js +120 -0
  22. package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
  23. package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
  24. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  25. package/dist/resources/extensions/gsd/commands-rate.js +31 -0
  26. package/dist/resources/extensions/gsd/commands.js +94 -2
  27. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -0
  28. package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
  29. package/dist/resources/extensions/gsd/files.js +11 -2
  30. package/dist/resources/extensions/gsd/gitignore.js +54 -7
  31. package/dist/resources/extensions/gsd/guided-flow.js +8 -2
  32. package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
  33. package/dist/resources/extensions/gsd/health-widget.js +97 -46
  34. package/dist/resources/extensions/gsd/index.js +31 -33
  35. package/dist/resources/extensions/gsd/migrate-external.js +55 -2
  36. package/dist/resources/extensions/gsd/milestone-ids.js +3 -2
  37. package/dist/resources/extensions/gsd/notifications.js +10 -1
  38. package/dist/resources/extensions/gsd/paths.js +74 -7
  39. package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
  40. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  41. package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
  42. package/dist/resources/extensions/gsd/preferences.js +15 -0
  43. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  44. package/dist/resources/extensions/gsd/prompts/research-milestone.md +4 -3
  45. package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -2
  46. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  47. package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
  48. package/dist/resources/extensions/gsd/session-lock.js +53 -2
  49. package/dist/resources/extensions/gsd/state.js +2 -1
  50. package/dist/resources/extensions/gsd/templates/plan.md +8 -0
  51. package/dist/resources/extensions/gsd/templates/preferences.md +6 -0
  52. package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
  53. package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
  54. package/dist/resources/extensions/search-the-web/native-search.js +45 -4
  55. package/dist/resources/extensions/shared/mod.js +1 -1
  56. package/dist/resources/extensions/shared/sanitize.js +30 -0
  57. package/dist/resources/extensions/shared/terminal.js +5 -0
  58. package/dist/resources/extensions/subagent/index.js +186 -74
  59. package/dist/resources/skills/core-web-vitals/SKILL.md +1 -1
  60. package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
  61. package/dist/resources/skills/github-workflows/SKILL.md +0 -2
  62. package/dist/resources/skills/web-quality-audit/SKILL.md +0 -2
  63. package/package.json +2 -1
  64. package/packages/pi-agent-core/dist/agent.d.ts +10 -2
  65. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  66. package/packages/pi-agent-core/dist/agent.js +19 -8
  67. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  68. package/packages/pi-agent-core/src/agent.ts +31 -10
  69. package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
  70. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  71. package/packages/pi-ai/src/providers/openai-responses.ts +1 -1
  72. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  73. package/packages/pi-coding-agent/dist/core/agent-session.js +20 -4
  74. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  75. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/resource-loader.js +13 -2
  77. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  78. package/packages/pi-coding-agent/package.json +1 -1
  79. package/packages/pi-coding-agent/src/core/agent-session.ts +36 -12
  80. package/packages/pi-coding-agent/src/core/resource-loader.ts +13 -2
  81. package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
  82. package/packages/pi-tui/dist/terminal-image.js +4 -0
  83. package/packages/pi-tui/dist/terminal-image.js.map +1 -1
  84. package/packages/pi-tui/src/terminal-image.ts +5 -0
  85. package/pkg/package.json +1 -1
  86. package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
  87. package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
  88. package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
  89. package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
  90. package/src/resources/extensions/bg-shell/types.ts +0 -12
  91. package/src/resources/extensions/cmux/index.ts +384 -0
  92. package/src/resources/extensions/context7/index.ts +7 -0
  93. package/src/resources/extensions/get-secrets-from-user.ts +2 -35
  94. package/src/resources/extensions/google-search/index.ts +7 -0
  95. package/src/resources/extensions/gsd/auto-dashboard.ts +363 -116
  96. package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
  97. package/src/resources/extensions/gsd/auto-loop.ts +64 -2
  98. package/src/resources/extensions/gsd/auto-model-selection.ts +23 -2
  99. package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
  100. package/src/resources/extensions/gsd/auto-start.ts +42 -2
  101. package/src/resources/extensions/gsd/auto.ts +82 -3
  102. package/src/resources/extensions/gsd/commands-cmux.ts +143 -0
  103. package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
  104. package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
  105. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  106. package/src/resources/extensions/gsd/commands-rate.ts +55 -0
  107. package/src/resources/extensions/gsd/commands.ts +97 -2
  108. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
  109. package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
  110. package/src/resources/extensions/gsd/files.ts +12 -2
  111. package/src/resources/extensions/gsd/gitignore.ts +54 -7
  112. package/src/resources/extensions/gsd/guided-flow.ts +8 -2
  113. package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
  114. package/src/resources/extensions/gsd/health-widget.ts +103 -59
  115. package/src/resources/extensions/gsd/index.ts +37 -32
  116. package/src/resources/extensions/gsd/migrate-external.ts +47 -2
  117. package/src/resources/extensions/gsd/milestone-ids.ts +3 -2
  118. package/src/resources/extensions/gsd/notifications.ts +10 -1
  119. package/src/resources/extensions/gsd/paths.ts +73 -7
  120. package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
  121. package/src/resources/extensions/gsd/preferences-types.ts +13 -0
  122. package/src/resources/extensions/gsd/preferences-validation.ts +42 -1
  123. package/src/resources/extensions/gsd/preferences.ts +18 -1
  124. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  125. package/src/resources/extensions/gsd/prompts/research-milestone.md +4 -3
  126. package/src/resources/extensions/gsd/prompts/research-slice.md +3 -2
  127. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  128. package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
  129. package/src/resources/extensions/gsd/session-lock.ts +59 -2
  130. package/src/resources/extensions/gsd/state.ts +2 -1
  131. package/src/resources/extensions/gsd/templates/plan.md +8 -0
  132. package/src/resources/extensions/gsd/templates/preferences.md +6 -0
  133. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
  134. package/src/resources/extensions/gsd/tests/cmux.test.ts +98 -0
  135. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
  136. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +20 -0
  137. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
  138. package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
  139. package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
  140. package/src/resources/extensions/gsd/tests/preferences.test.ts +35 -2
  141. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +26 -0
  142. package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
  143. package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
  144. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +7 -0
  145. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
  146. package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
  147. package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
  148. package/src/resources/extensions/search-the-web/native-search.ts +50 -4
  149. package/src/resources/extensions/shared/mod.ts +1 -1
  150. package/src/resources/extensions/shared/sanitize.ts +36 -0
  151. package/src/resources/extensions/shared/terminal.ts +5 -0
  152. package/src/resources/extensions/subagent/index.ts +242 -91
  153. package/src/resources/skills/core-web-vitals/SKILL.md +1 -1
  154. package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
  155. package/src/resources/skills/github-workflows/SKILL.md +0 -2
  156. package/src/resources/skills/web-quality-audit/SKILL.md +0 -2
  157. package/dist/resources/extensions/shared/wizard-ui.js +0 -478
  158. package/dist/resources/skills/swiftui/SKILL.md +0 -208
  159. package/dist/resources/skills/swiftui/references/animations.md +0 -921
  160. package/dist/resources/skills/swiftui/references/architecture.md +0 -1561
  161. package/dist/resources/skills/swiftui/references/layout-system.md +0 -1186
  162. package/dist/resources/skills/swiftui/references/navigation.md +0 -1492
  163. package/dist/resources/skills/swiftui/references/networking-async.md +0 -214
  164. package/dist/resources/skills/swiftui/references/performance.md +0 -1706
  165. package/dist/resources/skills/swiftui/references/platform-integration.md +0 -204
  166. package/dist/resources/skills/swiftui/references/state-management.md +0 -1443
  167. package/dist/resources/skills/swiftui/references/swiftdata.md +0 -297
  168. package/dist/resources/skills/swiftui/references/testing-debugging.md +0 -247
  169. package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
  170. package/dist/resources/skills/swiftui/workflows/add-feature.md +0 -191
  171. package/dist/resources/skills/swiftui/workflows/build-new-app.md +0 -311
  172. package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
  173. package/dist/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
  174. package/dist/resources/skills/swiftui/workflows/ship-app.md +0 -203
  175. package/dist/resources/skills/swiftui/workflows/write-tests.md +0 -235
  176. package/src/resources/extensions/shared/wizard-ui.ts +0 -551
  177. package/src/resources/skills/swiftui/SKILL.md +0 -208
  178. package/src/resources/skills/swiftui/references/animations.md +0 -921
  179. package/src/resources/skills/swiftui/references/architecture.md +0 -1561
  180. package/src/resources/skills/swiftui/references/layout-system.md +0 -1186
  181. package/src/resources/skills/swiftui/references/navigation.md +0 -1492
  182. package/src/resources/skills/swiftui/references/networking-async.md +0 -214
  183. package/src/resources/skills/swiftui/references/performance.md +0 -1706
  184. package/src/resources/skills/swiftui/references/platform-integration.md +0 -204
  185. package/src/resources/skills/swiftui/references/state-management.md +0 -1443
  186. package/src/resources/skills/swiftui/references/swiftdata.md +0 -297
  187. package/src/resources/skills/swiftui/references/testing-debugging.md +0 -247
  188. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
  189. package/src/resources/skills/swiftui/workflows/add-feature.md +0 -191
  190. package/src/resources/skills/swiftui/workflows/build-new-app.md +0 -311
  191. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
  192. package/src/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
  193. package/src/resources/skills/swiftui/workflows/ship-app.md +0 -203
  194. package/src/resources/skills/swiftui/workflows/write-tests.md +0 -235
@@ -161,6 +161,15 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt, _prefs) {
161
161
  const unitPromise = new Promise((resolve) => {
162
162
  s.pendingResolve = resolve;
163
163
  });
164
+ // Ensure cwd matches basePath before dispatch (#1389).
165
+ // async_bash and background jobs can drift cwd away from the worktree.
166
+ // Realigning here prevents commits from landing on the wrong branch.
167
+ try {
168
+ if (process.cwd() !== s.basePath) {
169
+ process.chdir(s.basePath);
170
+ }
171
+ }
172
+ catch { /* non-fatal — chdir may fail if dir was removed */ }
164
173
  // ── Send the prompt ──
165
174
  debugLog("runUnit", { phase: "send-message", unitType, unitId });
166
175
  pi.sendMessage({ customType: "gsd-auto", content: prompt, display: s.verbose }, { triggerTurn: true });
@@ -249,6 +258,7 @@ export async function autoLoop(ctx, pi, s, deps) {
249
258
  }
250
259
  // Derive state
251
260
  let state = await deps.deriveState(s.basePath);
261
+ deps.syncCmuxSidebar(deps.loadEffectiveGSDPreferences()?.preferences, state);
252
262
  let mid = state.activeMilestone?.id;
253
263
  let midTitle = state.activeMilestone?.title;
254
264
  debugLog("autoLoop", {
@@ -261,6 +271,7 @@ export async function autoLoop(ctx, pi, s, deps) {
261
271
  if (mid && s.currentMilestoneId && mid !== s.currentMilestoneId) {
262
272
  ctx.ui.notify(`Milestone ${s.currentMilestoneId} complete. Advancing to ${mid}: ${midTitle}.`, "info");
263
273
  deps.sendDesktopNotification("GSD", `Milestone ${s.currentMilestoneId} complete!`, "success", "milestone");
274
+ deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, `Milestone ${s.currentMilestoneId} complete. Advancing to ${mid}.`, "success");
264
275
  const vizPrefs = deps.loadEffectiveGSDPreferences()?.preferences;
265
276
  if (vizPrefs?.auto_visualize) {
266
277
  ctx.ui.notify("Run /gsd visualize to see progress overview.", "info");
@@ -354,6 +365,7 @@ export async function autoLoop(ctx, pi, s, deps) {
354
365
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
355
366
  }
356
367
  deps.sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone");
368
+ deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, "All milestones complete.", "success");
357
369
  await deps.stopAuto(ctx, pi, "All milestones complete");
358
370
  }
359
371
  else if (state.phase === "blocked") {
@@ -361,6 +373,7 @@ export async function autoLoop(ctx, pi, s, deps) {
361
373
  await deps.stopAuto(ctx, pi, blockerMsg);
362
374
  ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
363
375
  deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
376
+ deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
364
377
  }
365
378
  else {
366
379
  const ids = incomplete.map((m) => m.id).join(", ");
@@ -406,6 +419,7 @@ export async function autoLoop(ctx, pi, s, deps) {
406
419
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
407
420
  }
408
421
  deps.sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone");
422
+ deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, `Milestone ${mid} complete.`, "success");
409
423
  await deps.stopAuto(ctx, pi, `Milestone ${mid} complete`);
410
424
  debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
411
425
  break;
@@ -419,6 +433,7 @@ export async function autoLoop(ctx, pi, s, deps) {
419
433
  await deps.stopAuto(ctx, pi, blockerMsg);
420
434
  ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
421
435
  deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
436
+ deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
422
437
  debugLog("autoLoop", { phase: "exit", reason: "blocked" });
423
438
  break;
424
439
  }
@@ -449,30 +464,35 @@ export async function autoLoop(ctx, pi, s, deps) {
449
464
  if (budgetEnforcementAction === "pause") {
450
465
  ctx.ui.notify(`${msg} Pausing auto-mode — /gsd auto to override and continue.`, "warning");
451
466
  deps.sendDesktopNotification("GSD", msg, "warning", "budget");
467
+ deps.logCmuxEvent(prefs, msg, "warning");
452
468
  await deps.pauseAuto(ctx, pi);
453
469
  debugLog("autoLoop", { phase: "exit", reason: "budget-pause" });
454
470
  break;
455
471
  }
456
472
  ctx.ui.notify(`${msg} Continuing (enforcement: warn).`, "warning");
457
473
  deps.sendDesktopNotification("GSD", msg, "warning", "budget");
474
+ deps.logCmuxEvent(prefs, msg, "warning");
458
475
  }
459
476
  else if (newBudgetAlertLevel === 90) {
460
477
  s.lastBudgetAlertLevel =
461
478
  newBudgetAlertLevel;
462
479
  ctx.ui.notify(`Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
463
480
  deps.sendDesktopNotification("GSD", `Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning", "budget");
481
+ deps.logCmuxEvent(prefs, `Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
464
482
  }
465
483
  else if (newBudgetAlertLevel === 80) {
466
484
  s.lastBudgetAlertLevel =
467
485
  newBudgetAlertLevel;
468
486
  ctx.ui.notify(`Approaching budget ceiling — 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
469
487
  deps.sendDesktopNotification("GSD", `Approaching budget ceiling — 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning", "budget");
488
+ deps.logCmuxEvent(prefs, `Budget 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
470
489
  }
471
490
  else if (newBudgetAlertLevel === 75) {
472
491
  s.lastBudgetAlertLevel =
473
492
  newBudgetAlertLevel;
474
493
  ctx.ui.notify(`Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "info");
475
494
  deps.sendDesktopNotification("GSD", `Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "info", "budget");
495
+ deps.logCmuxEvent(prefs, `Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "progress");
476
496
  }
477
497
  else if (budgetAlertLevel === 0) {
478
498
  s.lastBudgetAlertLevel = 0;
@@ -498,7 +518,7 @@ export async function autoLoop(ctx, pi, s, deps) {
498
518
  }
499
519
  // Secrets re-check gate
500
520
  try {
501
- const manifestStatus = await deps.getManifestStatus(s.basePath, mid);
521
+ const manifestStatus = await deps.getManifestStatus(s.basePath, mid, s.originalBasePath);
502
522
  if (manifestStatus && manifestStatus.pending.length > 0) {
503
523
  const result = await deps.collectSecretsFromManifest(s.basePath, mid, ctx);
504
524
  if (result &&
@@ -623,6 +643,11 @@ export async function autoLoop(ctx, pi, s, deps) {
623
643
  unitType,
624
644
  unitId,
625
645
  });
646
+ // Detect retry and capture previous tier for escalation
647
+ const isRetry = !!(s.currentUnit &&
648
+ s.currentUnit.type === unitType &&
649
+ s.currentUnit.id === unitId);
650
+ const previousTier = s.currentUnitRouting?.tier;
626
651
  // Closeout previous unit
627
652
  if (s.currentUnit) {
628
653
  await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
@@ -728,8 +753,8 @@ export async function autoLoop(ctx, pi, s, deps) {
728
753
  const msg = reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
729
754
  process.stderr.write(`[gsd] prompt reorder failed (non-fatal): ${msg}\n`);
730
755
  }
731
- // Select and apply model
732
- const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel);
756
+ // Select and apply model (with tier escalation on retry)
757
+ const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, { isRetry, previousTier });
733
758
  s.currentUnitRouting =
734
759
  modelResult.routing;
735
760
  // Start unit supervision
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { resolveModelWithFallbacksForUnit, resolveDynamicRoutingConfig } from "./preferences.js";
7
7
  import { classifyUnitComplexity, tierLabel } from "./complexity-classifier.js";
8
- import { resolveModelForComplexity } from "./model-router.js";
8
+ import { resolveModelForComplexity, escalateTier } from "./model-router.js";
9
9
  import { getLedger, getProjectTotals } from "./metrics.js";
10
10
  import { unitPhaseLabel } from "./auto-dashboard.js";
11
11
  /**
@@ -15,7 +15,7 @@ import { unitPhaseLabel } from "./auto-dashboard.js";
15
15
  *
16
16
  * Returns routing metadata for metrics tracking.
17
17
  */
18
- export async function selectAndApplyModel(ctx, pi, unitType, unitId, basePath, prefs, verbose, autoModeStartModel) {
18
+ export async function selectAndApplyModel(ctx, pi, unitType, unitId, basePath, prefs, verbose, autoModeStartModel, retryContext) {
19
19
  const modelConfig = resolveModelWithFallbacksForUnit(unitType);
20
20
  let routing = null;
21
21
  if (modelConfig) {
@@ -37,8 +37,20 @@ export async function selectAndApplyModel(ctx, pi, unitType, unitId, basePath, p
37
37
  const isHook = unitType.startsWith("hook/");
38
38
  const shouldClassify = !isHook || routingConfig.hooks !== false;
39
39
  if (shouldClassify) {
40
- const classification = classifyUnitComplexity(unitType, unitId, basePath, budgetPct);
40
+ let classification = classifyUnitComplexity(unitType, unitId, basePath, budgetPct);
41
41
  const availableModelIds = availableModels.map(m => m.id);
42
+ // Escalate tier on retry when escalate_on_failure is enabled (default: true)
43
+ if (retryContext?.isRetry &&
44
+ retryContext.previousTier &&
45
+ routingConfig.escalate_on_failure !== false) {
46
+ const escalated = escalateTier(retryContext.previousTier);
47
+ if (escalated) {
48
+ classification = { ...classification, tier: escalated, reason: "escalated after failure" };
49
+ if (verbose) {
50
+ ctx.ui.notify(`Tier escalation: ${retryContext.previousTier} → ${escalated} (retry after failure)`, "info");
51
+ }
52
+ }
53
+ }
42
54
  const routingResult = resolveModelForComplexity(classification, modelConfig, routingConfig, availableModelIds);
43
55
  if (routingResult.wasDowngraded) {
44
56
  effectiveModelConfig = {
@@ -6,11 +6,13 @@
6
6
  * Pure functions that receive all needed state as parameters — no module-level
7
7
  * globals or AutoContext dependency.
8
8
  */
9
+ import { parseUnitId } from "./unit-id.js";
9
10
  import { clearUnitRuntimeRecord } from "./unit-runtime.js";
10
11
  import { clearParseCache, parseRoadmap, parsePlan } from "./files.js";
11
12
  import { isValidationTerminal } from "./state.js";
12
13
  import { nativeConflictFiles, nativeCommit, nativeCheckoutTheirs, nativeAddPaths, nativeMergeAbort, nativeResetHard, } from "./native-git-bridge.js";
13
14
  import { resolveMilestonePath, resolveSlicePath, resolveSliceFile, resolveTasksDir, relMilestoneFile, relSliceFile, relSlicePath, relTaskFile, buildMilestoneFileName, buildSliceFileName, buildTaskFileName, resolveMilestoneFile, clearPathCache, resolveGsdRootFile, } from "./paths.js";
15
+ import { markSliceDoneInRoadmap } from "./roadmap-mutations.js";
14
16
  import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, } from "node:fs";
15
17
  import { dirname, join } from "node:path";
16
18
  // ─── Artifact Resolution & Verification ───────────────────────────────────────
@@ -432,6 +434,39 @@ export async function selfHealRuntimeRecords(base, ctx) {
432
434
  const now = Date.now();
433
435
  for (const record of records) {
434
436
  const { unitType, unitId } = record;
437
+ // Case 0: complete-slice with SUMMARY + UAT but unchecked roadmap (#1350).
438
+ // If a complete-slice was interrupted after writing artifacts but before
439
+ // flipping the roadmap checkbox, the verification fails and the dispatch
440
+ // loop relaunches the same unit forever. Auto-fix the checkbox.
441
+ if (unitType === "complete-slice") {
442
+ const { milestone: mid, slice: sid } = parseUnitId(unitId);
443
+ if (mid && sid) {
444
+ const dir = resolveSlicePath(base, mid, sid);
445
+ if (dir) {
446
+ const summaryPath = join(dir, buildSliceFileName(sid, "SUMMARY"));
447
+ const uatPath = join(dir, buildSliceFileName(sid, "UAT"));
448
+ if (existsSync(summaryPath) && existsSync(uatPath)) {
449
+ const roadmapFile = resolveMilestoneFile(base, mid, "ROADMAP");
450
+ if (roadmapFile && existsSync(roadmapFile)) {
451
+ try {
452
+ const roadmapContent = readFileSync(roadmapFile, "utf-8");
453
+ const roadmap = parseRoadmap(roadmapContent);
454
+ const slice = (roadmap.slices ?? []).find(s => s.id === sid);
455
+ if (slice && !slice.done) {
456
+ // Auto-fix: flip the checkbox using shared utility
457
+ if (markSliceDoneInRoadmap(base, mid, sid)) {
458
+ ctx.ui.notify(`Self-heal: marked ${sid} done in roadmap (SUMMARY + UAT exist but checkbox was stale).`, "info");
459
+ }
460
+ }
461
+ }
462
+ catch {
463
+ // Roadmap parse failure — don't block self-heal
464
+ }
465
+ }
466
+ }
467
+ }
468
+ }
469
+ }
435
470
  // Clear stale dispatched records (dispatched > 1h ago, process crashed)
436
471
  const age = now - (record.startedAt ?? 0);
437
472
  if (record.phase === "dispatched" && age > STALE_THRESHOLD_MS) {
@@ -11,6 +11,8 @@
11
11
  import { deriveState } from "./state.js";
12
12
  import { loadFile, getManifestStatus } from "./files.js";
13
13
  import { loadEffectiveGSDPreferences, resolveSkillDiscoveryMode, getIsolationMode, } from "./preferences.js";
14
+ import { ensureGsdSymlink } from "./repo-identity.js";
15
+ import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
14
16
  import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
15
17
  import { gsdRoot, resolveMilestoneFile } from "./paths.js";
16
18
  import { invalidateAllCaches } from "./cache.js";
@@ -42,6 +44,12 @@ import { sep as pathSep } from "node:path";
42
44
  * Returns false if the bootstrap aborted (e.g., guided flow returned,
43
45
  * concurrent session detected). Returns true when ready to dispatch.
44
46
  */
47
+ /** Guard: tracks consecutive bootstrap attempts that found phase === "complete".
48
+ * Prevents the recursive dialog loop described in #1348 where
49
+ * bootstrapAutoSession → showSmartEntry → checkAutoStartAfterDiscuss → startAuto
50
+ * cycles indefinitely when the discuss workflow doesn't produce a milestone. */
51
+ let _consecutiveCompleteBootstraps = 0;
52
+ const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
45
53
  export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, deps) {
46
54
  const { shouldUseWorktreeIsolation, registerSigtermHandler, lockBase, buildResolver, } = deps;
47
55
  const lockResult = acquireSessionLock(base);
@@ -60,7 +68,19 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
60
68
  const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
61
69
  nativeInit(base, mainBranch);
62
70
  }
63
- // Ensure .gitignore has baseline patterns
71
+ // Migrate legacy in-project .gsd/ to external state directory.
72
+ // Migration MUST run before ensureGitignore to avoid adding ".gsd" to
73
+ // .gitignore when .gsd/ is git-tracked (data-loss bug #1364).
74
+ recoverFailedMigration(base);
75
+ const migration = migrateToExternalState(base);
76
+ if (migration.error) {
77
+ ctx.ui.notify(`External state migration warning: ${migration.error}`, "warning");
78
+ }
79
+ // Ensure symlink exists (handles fresh projects and post-migration)
80
+ ensureGsdSymlink(base);
81
+ // Ensure .gitignore has baseline patterns.
82
+ // ensureGitignore checks for git-tracked .gsd/ files and skips the
83
+ // ".gsd" pattern if the project intentionally tracks .gsd/ in git.
64
84
  const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
65
85
  const commitDocs = gitPrefs?.commit_docs;
66
86
  const manageGitignore = gitPrefs?.manage_gitignore;
@@ -186,6 +206,16 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
186
206
  if (!hasSurvivorBranch) {
187
207
  // No active work — start a new milestone via discuss flow
188
208
  if (!state.activeMilestone || state.phase === "complete") {
209
+ // Guard against recursive dialog loop (#1348):
210
+ // If we've entered this branch multiple times in quick succession,
211
+ // the discuss workflow isn't producing a milestone. Break the cycle.
212
+ _consecutiveCompleteBootstraps++;
213
+ if (_consecutiveCompleteBootstraps > MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS) {
214
+ _consecutiveCompleteBootstraps = 0;
215
+ ctx.ui.notify("All milestones are complete and the discussion didn't produce a new one. " +
216
+ "Run /gsd to start a new milestone manually.", "warning");
217
+ return releaseLockAndReturn();
218
+ }
189
219
  const { showSmartEntry } = await import("./guided-flow.js");
190
220
  await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
191
221
  invalidateAllCaches();
@@ -193,6 +223,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
193
223
  if (postState.activeMilestone &&
194
224
  postState.phase !== "complete" &&
195
225
  postState.phase !== "pre-planning") {
226
+ _consecutiveCompleteBootstraps = 0; // Successfully advanced past "complete"
196
227
  state = postState;
197
228
  }
198
229
  else if (postState.activeMilestone &&
@@ -237,6 +268,8 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
237
268
  await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
238
269
  return releaseLockAndReturn();
239
270
  }
271
+ // Successfully resolved an active milestone — reset the re-entry guard
272
+ _consecutiveCompleteBootstraps = 0;
240
273
  // ── Initialize session state ──
241
274
  s.active = true;
242
275
  s.stepMode = requestedStepMode;
@@ -345,7 +378,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
345
378
  // Secrets collection gate
346
379
  const mid = state.activeMilestone.id;
347
380
  try {
348
- const manifestStatus = await getManifestStatus(base, mid);
381
+ const manifestStatus = await getManifestStatus(base, mid, s.originalBasePath || base);
349
382
  if (manifestStatus && manifestStatus.pending.length > 0) {
350
383
  const result = await collectSecretsFromManifest(base, mid, ctx);
351
384
  if (result &&
@@ -36,7 +36,7 @@ import { clearSkillSnapshot } from "./skill-discovery.js";
36
36
  import { captureAvailableSkills, resetSkillTelemetry, } from "./skill-telemetry.js";
37
37
  import { initMetrics, resetMetrics, getLedger, getProjectTotals, formatCost, formatTokenCount, } from "./metrics.js";
38
38
  import { join } from "node:path";
39
- import { readFileSync, existsSync, mkdirSync } from "node:fs";
39
+ import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
40
40
  import { atomicWriteSync } from "./atomic-write.js";
41
41
  import { autoCommitCurrentBranch, captureIntegrationBranch, detectWorktreeName, getCurrentBranch, getMainBranch, setActiveMilestoneId, } from "./worktree.js";
42
42
  import { GitServiceImpl } from "./git-service.js";
@@ -50,6 +50,7 @@ import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache
50
50
  import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
51
51
  import { isDbAvailable } from "./gsd-db.js";
52
52
  import { countPendingCaptures } from "./captures.js";
53
+ import { clearCmuxSidebar, logCmuxEvent, syncCmuxSidebar } from "../cmux/index.js";
53
54
  // ── Extracted modules ──────────────────────────────────────────────────────
54
55
  import { startUnitSupervision } from "./auto-timers.js";
55
56
  import { runPostUnitVerification } from "./auto-verification.js";
@@ -248,6 +249,7 @@ function handleLostSessionLock(ctx) {
248
249
  s.paused = false;
249
250
  clearUnitTimeout();
250
251
  deregisterSigtermHandler();
252
+ clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
251
253
  ctx?.ui.notify("Session lock lost — another GSD process appears to have taken over. Stopping gracefully.", "error");
252
254
  ctx?.ui.setStatus("gsd-auto", undefined);
253
255
  ctx?.ui.setWidget("gsd-progress", undefined);
@@ -256,6 +258,7 @@ function handleLostSessionLock(ctx) {
256
258
  export async function stopAuto(ctx, pi, reason) {
257
259
  if (!s.active && !s.paused)
258
260
  return;
261
+ const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
259
262
  const reasonSuffix = reason ? ` — ${reason}` : "";
260
263
  clearUnitTimeout();
261
264
  if (lockBase())
@@ -314,6 +317,8 @@ export async function stopAuto(ctx, pi, reason) {
314
317
  });
315
318
  }
316
319
  }
320
+ clearCmuxSidebar(loadedPreferences);
321
+ logCmuxEvent(loadedPreferences, `Auto-mode stopped${reasonSuffix || ""}.`, reason?.startsWith("Blocked:") ? "warning" : "info");
317
322
  if (isDebugEnabled()) {
318
323
  const logPath = writeDebugSummary();
319
324
  if (logPath) {
@@ -325,6 +330,13 @@ export async function stopAuto(ctx, pi, reason) {
325
330
  resetHookState();
326
331
  if (s.basePath)
327
332
  clearPersistedHookState(s.basePath);
333
+ // Remove paused-session metadata if present (#1383)
334
+ try {
335
+ const pausedPath = join(gsdRoot(s.originalBasePath || s.basePath), "runtime", "paused-session.json");
336
+ if (existsSync(pausedPath))
337
+ unlinkSync(pausedPath);
338
+ }
339
+ catch { /* non-fatal */ }
328
340
  s.active = false;
329
341
  s.paused = false;
330
342
  s.stepMode = false;
@@ -369,10 +381,28 @@ export async function pauseAuto(ctx, _pi) {
369
381
  return;
370
382
  clearUnitTimeout();
371
383
  s.pausedSessionFile = ctx?.sessionManager?.getSessionFile() ?? null;
372
- if (lockBase())
373
- clearLock(lockBase());
374
- if (lockBase())
384
+ // Persist paused-session metadata so resume survives /exit (#1383).
385
+ // The fresh-start bootstrap checks for this file and restores worktree context.
386
+ try {
387
+ const pausedMeta = {
388
+ milestoneId: s.currentMilestoneId,
389
+ worktreePath: isInAutoWorktree(s.basePath) ? s.basePath : null,
390
+ originalBasePath: s.originalBasePath,
391
+ stepMode: s.stepMode,
392
+ pausedAt: new Date().toISOString(),
393
+ sessionFile: s.pausedSessionFile,
394
+ };
395
+ const runtimeDir = join(gsdRoot(s.originalBasePath || s.basePath), "runtime");
396
+ mkdirSync(runtimeDir, { recursive: true });
397
+ writeFileSync(join(runtimeDir, "paused-session.json"), JSON.stringify(pausedMeta, null, 2), "utf-8");
398
+ }
399
+ catch {
400
+ // Non-fatal — resume will still work via full bootstrap, just without worktree context
401
+ }
402
+ if (lockBase()) {
375
403
  releaseSessionLock(lockBase());
404
+ clearLock(lockBase());
405
+ }
376
406
  deregisterSigtermHandler();
377
407
  s.active = false;
378
408
  s.paused = true;
@@ -430,6 +460,8 @@ function buildLoopDeps() {
430
460
  pauseAuto,
431
461
  clearUnitTimeout,
432
462
  updateProgressWidget,
463
+ syncCmuxSidebar,
464
+ logCmuxEvent,
433
465
  // State and cache
434
466
  invalidateAllCaches,
435
467
  deriveState,
@@ -520,6 +552,30 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
520
552
  // Escape stale worktree cwd from a previous milestone (#608).
521
553
  base = escapeStaleWorktree(base);
522
554
  // If resuming from paused state, just re-activate and dispatch next unit.
555
+ // Check persisted paused-session first (#1383) — survives /exit.
556
+ if (!s.paused) {
557
+ try {
558
+ const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json");
559
+ if (existsSync(pausedPath)) {
560
+ const meta = JSON.parse(readFileSync(pausedPath, "utf-8"));
561
+ if (meta.milestoneId) {
562
+ s.currentMilestoneId = meta.milestoneId;
563
+ s.originalBasePath = meta.originalBasePath || base;
564
+ s.stepMode = meta.stepMode ?? requestedStepMode;
565
+ s.paused = true;
566
+ // Clean up the persisted file — we're consuming it
567
+ try {
568
+ unlinkSync(pausedPath);
569
+ }
570
+ catch { /* non-fatal */ }
571
+ ctx.ui.notify(`Resuming paused session for ${meta.milestoneId}${meta.worktreePath ? ` (worktree)` : ""}.`, "info");
572
+ }
573
+ }
574
+ }
575
+ catch {
576
+ // Malformed or missing — proceed with fresh bootstrap
577
+ }
578
+ }
523
579
  if (s.paused) {
524
580
  const resumeLock = acquireSessionLock(base);
525
581
  if (!resumeLock.acquired) {
@@ -556,6 +612,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
556
612
  restoreHookState(s.basePath);
557
613
  try {
558
614
  await rebuildState(s.basePath);
615
+ syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
559
616
  }
560
617
  catch (e) {
561
618
  debugLog("resume-rebuild-state-failed", {
@@ -585,6 +642,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
585
642
  }
586
643
  updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
587
644
  writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
645
+ logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
588
646
  await autoLoop(ctx, pi, s, buildLoopDeps());
589
647
  return;
590
648
  }
@@ -598,6 +656,13 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
598
656
  const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps);
599
657
  if (!ready)
600
658
  return;
659
+ try {
660
+ syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
661
+ }
662
+ catch {
663
+ // Best-effort only — sidebar sync must never block auto-mode startup
664
+ }
665
+ logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
601
666
  // Dispatch the first unit
602
667
  await autoLoop(ctx, pi, s, buildLoopDeps());
603
668
  }
@@ -754,6 +819,12 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
754
819
  }, hookHardTimeoutMs);
755
820
  ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
756
821
  ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
822
+ // Ensure cwd matches basePath before hook dispatch (#1389)
823
+ try {
824
+ if (process.cwd() !== s.basePath)
825
+ process.chdir(s.basePath);
826
+ }
827
+ catch { }
757
828
  debugLog("dispatchHookUnit", {
758
829
  phase: "send-message",
759
830
  promptLength: hookPrompt.length,
@@ -0,0 +1,120 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { clearCmuxSidebar, CmuxClient, detectCmuxEnvironment, resolveCmuxConfig } from "../cmux/index.js";
3
+ import { saveFile } from "./files.js";
4
+ import { getProjectGSDPreferencesPath, loadEffectiveGSDPreferences, loadProjectGSDPreferences, } from "./preferences.js";
5
+ import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
6
+ function extractBodyAfterFrontmatter(content) {
7
+ const start = content.startsWith("---\n") ? 4 : content.startsWith("---\r\n") ? 5 : -1;
8
+ if (start === -1)
9
+ return null;
10
+ const closingIdx = content.indexOf("\n---", start);
11
+ if (closingIdx === -1)
12
+ return null;
13
+ const after = content.slice(closingIdx + 4);
14
+ return after.trim() ? after : null;
15
+ }
16
+ async function writeProjectCmuxPreferences(ctx, updater) {
17
+ const path = getProjectGSDPreferencesPath();
18
+ await ensurePreferencesFile(path, ctx, "project");
19
+ const existing = loadProjectGSDPreferences();
20
+ const prefs = existing?.preferences ? { ...existing.preferences } : { version: 1 };
21
+ updater(prefs);
22
+ prefs.version = prefs.version || 1;
23
+ const frontmatter = serializePreferencesToFrontmatter(prefs);
24
+ let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
25
+ if (existsSync(path)) {
26
+ const preserved = extractBodyAfterFrontmatter(readFileSync(path, "utf-8"));
27
+ if (preserved)
28
+ body = preserved;
29
+ }
30
+ await saveFile(path, `---\n${frontmatter}---${body}`);
31
+ await ctx.waitForIdle();
32
+ await ctx.reload();
33
+ }
34
+ function formatCmuxStatus() {
35
+ const loaded = loadEffectiveGSDPreferences();
36
+ const detected = detectCmuxEnvironment();
37
+ const resolved = resolveCmuxConfig(loaded?.preferences);
38
+ const capabilities = new CmuxClient(resolved).getCapabilities();
39
+ const accessMode = typeof capabilities?.mode === "string"
40
+ ? capabilities.mode
41
+ : typeof capabilities?.access_mode === "string"
42
+ ? capabilities.access_mode
43
+ : "unknown";
44
+ const methods = Array.isArray(capabilities?.methods) ? capabilities.methods.length : 0;
45
+ return [
46
+ "cmux status",
47
+ "",
48
+ `Detected: ${detected.available ? "yes" : "no"}`,
49
+ `Enabled: ${resolved.enabled ? "yes" : "no"}`,
50
+ `CLI available: ${detected.cliAvailable ? "yes" : "no"}`,
51
+ `Socket: ${detected.socketPath}`,
52
+ `Workspace: ${detected.workspaceId ?? "(none)"}`,
53
+ `Surface: ${detected.surfaceId ?? "(none)"}`,
54
+ `Features: notifications=${resolved.notifications ? "on" : "off"}, sidebar=${resolved.sidebar ? "on" : "off"}, splits=${resolved.splits ? "on" : "off"}, browser=${resolved.browser ? "on" : "off"}`,
55
+ `Capabilities: access=${accessMode}, methods=${methods}`,
56
+ ].join("\n");
57
+ }
58
+ function ensureCmuxAvailableForEnable(ctx) {
59
+ const detected = detectCmuxEnvironment();
60
+ if (detected.available)
61
+ return true;
62
+ ctx.ui.notify("cmux not detected. Install it from https://cmux.com and run gsd inside a cmux terminal.", "warning");
63
+ return false;
64
+ }
65
+ export async function handleCmux(args, ctx) {
66
+ const trimmed = args.trim();
67
+ if (!trimmed || trimmed === "status") {
68
+ ctx.ui.notify(formatCmuxStatus(), "info");
69
+ return;
70
+ }
71
+ if (trimmed === "on") {
72
+ if (!ensureCmuxAvailableForEnable(ctx))
73
+ return;
74
+ await writeProjectCmuxPreferences(ctx, (prefs) => {
75
+ prefs.cmux = {
76
+ enabled: true,
77
+ notifications: true,
78
+ sidebar: true,
79
+ splits: false,
80
+ browser: false,
81
+ ...(prefs.cmux ?? {}),
82
+ };
83
+ prefs.cmux.enabled = true;
84
+ });
85
+ ctx.ui.notify("cmux integration enabled in project preferences.", "info");
86
+ return;
87
+ }
88
+ if (trimmed === "off") {
89
+ const effective = loadEffectiveGSDPreferences()?.preferences;
90
+ await writeProjectCmuxPreferences(ctx, (prefs) => {
91
+ prefs.cmux = { ...(prefs.cmux ?? {}), enabled: false };
92
+ });
93
+ clearCmuxSidebar(effective);
94
+ ctx.ui.notify("cmux integration disabled in project preferences.", "info");
95
+ return;
96
+ }
97
+ const parts = trimmed.split(/\s+/);
98
+ if (parts.length === 2 && ["notifications", "sidebar", "splits", "browser"].includes(parts[0]) && ["on", "off"].includes(parts[1])) {
99
+ const feature = parts[0];
100
+ const enabled = parts[1] === "on";
101
+ if (enabled && !ensureCmuxAvailableForEnable(ctx))
102
+ return;
103
+ await writeProjectCmuxPreferences(ctx, (prefs) => {
104
+ const next = { ...(prefs.cmux ?? {}) };
105
+ next[feature] = enabled;
106
+ if (enabled)
107
+ next.enabled = true;
108
+ prefs.cmux = next;
109
+ });
110
+ if (!enabled && feature === "sidebar") {
111
+ clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
112
+ }
113
+ const note = feature === "browser" && enabled
114
+ ? " Browser surfaces are still a follow-up path."
115
+ : "";
116
+ ctx.ui.notify(`cmux ${feature} ${enabled ? "enabled" : "disabled"}.${note}`, "info");
117
+ return;
118
+ }
119
+ ctx.ui.notify("Usage: /gsd cmux <status|on|off|notifications on|notifications off|sidebar on|sidebar off|splits on|splits off|browser on|browser off>", "info");
120
+ }
@@ -15,7 +15,7 @@ import { isAutoActive } from "./auto.js";
15
15
  import { projectRoot } from "./commands.js";
16
16
  import { loadPrompt } from "./prompt-loader.js";
17
17
  export function dispatchDoctorHeal(pi, scope, reportText, structuredIssues) {
18
- const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
18
+ const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
19
19
  const workflow = readFileSync(workflowPath, "utf-8");
20
20
  const prompt = loadPrompt("doctor-heal", {
21
21
  doctorSummary: reportText,
@@ -144,7 +144,7 @@ export async function handleTriage(ctx, pi, basePath) {
144
144
  currentPlan: currentPlan || "(no active slice plan)",
145
145
  roadmapContext: roadmapContext || "(no active roadmap)",
146
146
  });
147
- const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
147
+ const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
148
148
  const workflow = readFileSync(workflowPath, "utf-8");
149
149
  pi.sendMessage({
150
150
  customType: "gsd-triage",
@@ -3,6 +3,9 @@
3
3
  *
4
4
  * Contains: InspectData type, formatInspectOutput, handleInspect
5
5
  */
6
+ import { existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { gsdRoot } from "./paths.js";
6
9
  import { getErrorMessage } from "./error-utils.js";
7
10
  export function formatInspectOutput(data) {
8
11
  const lines = [];
@@ -30,10 +33,14 @@ export function formatInspectOutput(data) {
30
33
  }
31
34
  export async function handleInspect(ctx) {
32
35
  try {
33
- const { isDbAvailable, _getAdapter } = await import("./gsd-db.js");
36
+ const { isDbAvailable, _getAdapter, openDatabase } = await import("./gsd-db.js");
34
37
  if (!isDbAvailable()) {
35
- ctx.ui.notify("No GSD database available. Run /gsd auto to create one.", "info");
36
- return;
38
+ const gsdDir = gsdRoot(process.cwd());
39
+ const dbPath = join(gsdDir, "gsd.db");
40
+ if (!existsSync(gsdDir) || !existsSync(dbPath) || !openDatabase(dbPath)) {
41
+ ctx.ui.notify("No GSD database available. Run /gsd auto to create one.", "info");
42
+ return;
43
+ }
37
44
  }
38
45
  const adapter = _getAdapter();
39
46
  if (!adapter) {
@@ -626,7 +626,7 @@ export function serializePreferencesToFrontmatter(prefs) {
626
626
  "skill_rules", "custom_instructions", "models", "skill_discovery",
627
627
  "skill_staleness_days", "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
628
628
  "budget_ceiling", "budget_enforcement", "context_pause_threshold",
629
- "notifications", "remote_questions", "git",
629
+ "notifications", "cmux", "remote_questions", "git",
630
630
  "post_unit_hooks", "pre_dispatch_hooks",
631
631
  "dynamic_routing", "token_profile", "phases", "parallel",
632
632
  "auto_visualize", "auto_report",