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.
- package/README.md +3 -1
- package/dist/cli.js +7 -2
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +13 -1
- package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
- package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
- package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
- package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
- package/dist/resources/extensions/bg-shell/types.js +0 -2
- package/dist/resources/extensions/cmux/index.js +321 -0
- package/dist/resources/extensions/context7/index.js +5 -0
- package/dist/resources/extensions/get-secrets-from-user.js +2 -30
- package/dist/resources/extensions/google-search/index.js +5 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +334 -104
- package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
- package/dist/resources/extensions/gsd/auto-loop.js +28 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +15 -3
- package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
- package/dist/resources/extensions/gsd/auto-start.js +35 -2
- package/dist/resources/extensions/gsd/auto.js +75 -4
- package/dist/resources/extensions/gsd/commands-cmux.js +120 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
- package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands-rate.js +31 -0
- package/dist/resources/extensions/gsd/commands.js +94 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
- package/dist/resources/extensions/gsd/files.js +11 -2
- package/dist/resources/extensions/gsd/gitignore.js +54 -7
- package/dist/resources/extensions/gsd/guided-flow.js +8 -2
- package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
- package/dist/resources/extensions/gsd/health-widget.js +97 -46
- package/dist/resources/extensions/gsd/index.js +31 -33
- package/dist/resources/extensions/gsd/migrate-external.js +55 -2
- package/dist/resources/extensions/gsd/milestone-ids.js +3 -2
- package/dist/resources/extensions/gsd/notifications.js +10 -1
- package/dist/resources/extensions/gsd/paths.js +74 -7
- package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
- package/dist/resources/extensions/gsd/preferences.js +15 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
- package/dist/resources/extensions/gsd/session-lock.js +53 -2
- package/dist/resources/extensions/gsd/state.js +2 -1
- package/dist/resources/extensions/gsd/templates/plan.md +8 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +6 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
- package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
- package/dist/resources/extensions/search-the-web/native-search.js +45 -4
- package/dist/resources/extensions/shared/mod.js +1 -1
- package/dist/resources/extensions/shared/sanitize.js +30 -0
- package/dist/resources/extensions/shared/terminal.js +5 -0
- package/dist/resources/extensions/subagent/index.js +186 -74
- package/dist/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/dist/resources/skills/github-workflows/SKILL.md +0 -2
- package/dist/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/package.json +2 -1
- package/packages/pi-agent-core/dist/agent.d.ts +10 -2
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +19 -8
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.ts +31 -10
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-responses.ts +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +20 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +13 -2
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -12
- package/packages/pi-coding-agent/src/core/resource-loader.ts +13 -2
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +4 -0
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/src/terminal-image.ts +5 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
- package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
- package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
- package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
- package/src/resources/extensions/bg-shell/types.ts +0 -12
- package/src/resources/extensions/cmux/index.ts +384 -0
- package/src/resources/extensions/context7/index.ts +7 -0
- package/src/resources/extensions/get-secrets-from-user.ts +2 -35
- package/src/resources/extensions/google-search/index.ts +7 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +363 -116
- package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
- package/src/resources/extensions/gsd/auto-loop.ts +64 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +23 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
- package/src/resources/extensions/gsd/auto-start.ts +42 -2
- package/src/resources/extensions/gsd/auto.ts +82 -3
- package/src/resources/extensions/gsd/commands-cmux.ts +143 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
- package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands-rate.ts +55 -0
- package/src/resources/extensions/gsd/commands.ts +97 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
- package/src/resources/extensions/gsd/files.ts +12 -2
- package/src/resources/extensions/gsd/gitignore.ts +54 -7
- package/src/resources/extensions/gsd/guided-flow.ts +8 -2
- package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
- package/src/resources/extensions/gsd/health-widget.ts +103 -59
- package/src/resources/extensions/gsd/index.ts +37 -32
- package/src/resources/extensions/gsd/migrate-external.ts +47 -2
- package/src/resources/extensions/gsd/milestone-ids.ts +3 -2
- package/src/resources/extensions/gsd/notifications.ts +10 -1
- package/src/resources/extensions/gsd/paths.ts +73 -7
- package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
- package/src/resources/extensions/gsd/preferences-types.ts +13 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +42 -1
- package/src/resources/extensions/gsd/preferences.ts +18 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
- package/src/resources/extensions/gsd/session-lock.ts +59 -2
- package/src/resources/extensions/gsd/state.ts +2 -1
- package/src/resources/extensions/gsd/templates/plan.md +8 -0
- package/src/resources/extensions/gsd/templates/preferences.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
- package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
- package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
- package/src/resources/extensions/search-the-web/native-search.ts +50 -4
- package/src/resources/extensions/shared/mod.ts +1 -1
- package/src/resources/extensions/shared/sanitize.ts +36 -0
- package/src/resources/extensions/shared/terminal.ts +5 -0
- package/src/resources/extensions/subagent/index.ts +242 -91
- package/src/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/src/resources/skills/github-workflows/SKILL.md +0 -2
- package/src/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/dist/resources/extensions/shared/wizard-ui.js +0 -478
- package/dist/resources/skills/swiftui/SKILL.md +0 -208
- package/dist/resources/skills/swiftui/references/animations.md +0 -921
- package/dist/resources/skills/swiftui/references/architecture.md +0 -1561
- package/dist/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/dist/resources/skills/swiftui/references/navigation.md +0 -1492
- package/dist/resources/skills/swiftui/references/networking-async.md +0 -214
- package/dist/resources/skills/swiftui/references/performance.md +0 -1706
- package/dist/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/dist/resources/skills/swiftui/references/state-management.md +0 -1443
- package/dist/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/dist/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/dist/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/dist/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/dist/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/dist/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/dist/resources/skills/swiftui/workflows/write-tests.md +0 -235
- package/src/resources/extensions/shared/wizard-ui.ts +0 -551
- package/src/resources/skills/swiftui/SKILL.md +0 -208
- package/src/resources/skills/swiftui/references/animations.md +0 -921
- package/src/resources/skills/swiftui/references/architecture.md +0 -1561
- package/src/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/src/resources/skills/swiftui/references/navigation.md +0 -1492
- package/src/resources/skills/swiftui/references/networking-async.md +0 -214
- package/src/resources/skills/swiftui/references/performance.md +0 -1706
- package/src/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/src/resources/skills/swiftui/references/state-management.md +0 -1443
- package/src/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/src/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/src/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/src/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/src/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/src/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/src/resources/skills/swiftui/workflows/ship-app.md +0 -203
- 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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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 ?? "~", ".
|
|
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 ?? "~", ".
|
|
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
|
-
|
|
36
|
-
|
|
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",
|