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
|
@@ -12,14 +12,16 @@
|
|
|
12
12
|
import type { GSDState } from "./types.js";
|
|
13
13
|
import type { GSDPreferences } from "./preferences.js";
|
|
14
14
|
import type { UatType } from "./files.js";
|
|
15
|
-
import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
|
|
15
|
+
import { loadFile, extractUatType, loadActiveOverrides, parseRoadmap } from "./files.js";
|
|
16
16
|
import {
|
|
17
17
|
resolveMilestoneFile,
|
|
18
18
|
resolveMilestonePath,
|
|
19
19
|
resolveSliceFile,
|
|
20
|
+
resolveSlicePath,
|
|
20
21
|
resolveTaskFile,
|
|
21
22
|
relSliceFile,
|
|
22
23
|
buildMilestoneFileName,
|
|
24
|
+
buildSliceFileName,
|
|
23
25
|
} from "./paths.js";
|
|
24
26
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
25
27
|
import { join } from "node:path";
|
|
@@ -369,6 +371,30 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
369
371
|
name: "validating-milestone → validate-milestone",
|
|
370
372
|
match: async ({ state, mid, midTitle, basePath, prefs }) => {
|
|
371
373
|
if (state.phase !== "validating-milestone") return null;
|
|
374
|
+
|
|
375
|
+
// Safety guard (#1368): verify all roadmap slices have SUMMARY files before
|
|
376
|
+
// allowing milestone validation. If any slice lacks a summary, the milestone
|
|
377
|
+
// is not genuinely complete — something skipped earlier slices.
|
|
378
|
+
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
|
379
|
+
const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null;
|
|
380
|
+
if (roadmapContent) {
|
|
381
|
+
const roadmap = parseRoadmap(roadmapContent);
|
|
382
|
+
const missingSlices: string[] = [];
|
|
383
|
+
for (const slice of roadmap.slices) {
|
|
384
|
+
const summaryPath = resolveSliceFile(basePath, mid, slice.id, "SUMMARY");
|
|
385
|
+
if (!summaryPath || !existsSync(summaryPath)) {
|
|
386
|
+
missingSlices.push(slice.id);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (missingSlices.length > 0) {
|
|
390
|
+
return {
|
|
391
|
+
action: "stop",
|
|
392
|
+
reason: `Cannot validate milestone ${mid}: slices ${missingSlices.join(", ")} are missing SUMMARY files. These slices may have been skipped.`,
|
|
393
|
+
level: "error",
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
372
398
|
// Skip preference: write a minimal pass-through VALIDATION file
|
|
373
399
|
if (prefs?.phases?.skip_milestone_validation) {
|
|
374
400
|
const mDir = resolveMilestonePath(basePath, mid);
|
|
@@ -404,6 +430,28 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
404
430
|
name: "completing-milestone → complete-milestone",
|
|
405
431
|
match: async ({ state, mid, midTitle, basePath }) => {
|
|
406
432
|
if (state.phase !== "completing-milestone") return null;
|
|
433
|
+
|
|
434
|
+
// Safety guard (#1368): verify all roadmap slices have SUMMARY files.
|
|
435
|
+
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
|
436
|
+
const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null;
|
|
437
|
+
if (roadmapContent) {
|
|
438
|
+
const roadmap = parseRoadmap(roadmapContent);
|
|
439
|
+
const missingSlices: string[] = [];
|
|
440
|
+
for (const slice of roadmap.slices) {
|
|
441
|
+
const summaryPath = resolveSliceFile(basePath, mid, slice.id, "SUMMARY");
|
|
442
|
+
if (!summaryPath || !existsSync(summaryPath)) {
|
|
443
|
+
missingSlices.push(slice.id);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (missingSlices.length > 0) {
|
|
447
|
+
return {
|
|
448
|
+
action: "stop",
|
|
449
|
+
reason: `Cannot complete milestone ${mid}: slices ${missingSlices.join(", ")} are missing SUMMARY files. Run /gsd doctor to diagnose.`,
|
|
450
|
+
level: "error",
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
407
455
|
return {
|
|
408
456
|
action: "dispatch",
|
|
409
457
|
unitType: "complete-milestone",
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
import type { DispatchAction } from "./auto-dispatch.js";
|
|
26
26
|
import type { WorktreeResolver } from "./worktree-resolver.js";
|
|
27
27
|
import { debugLog } from "./debug-logger.js";
|
|
28
|
+
import type { CmuxLogLevel } from "../cmux/index.js";
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Maximum total loop iterations before forced stop. Prevents runaway loops
|
|
@@ -221,6 +222,15 @@ export async function runUnit(
|
|
|
221
222
|
s.pendingResolve = resolve;
|
|
222
223
|
});
|
|
223
224
|
|
|
225
|
+
// Ensure cwd matches basePath before dispatch (#1389).
|
|
226
|
+
// async_bash and background jobs can drift cwd away from the worktree.
|
|
227
|
+
// Realigning here prevents commits from landing on the wrong branch.
|
|
228
|
+
try {
|
|
229
|
+
if (process.cwd() !== s.basePath) {
|
|
230
|
+
process.chdir(s.basePath);
|
|
231
|
+
}
|
|
232
|
+
} catch { /* non-fatal — chdir may fail if dir was removed */ }
|
|
233
|
+
|
|
224
234
|
// ── Send the prompt ──
|
|
225
235
|
debugLog("runUnit", { phase: "send-message", unitType, unitId });
|
|
226
236
|
|
|
@@ -267,6 +277,12 @@ export interface LoopDeps {
|
|
|
267
277
|
unitId: string,
|
|
268
278
|
state: GSDState,
|
|
269
279
|
) => void;
|
|
280
|
+
syncCmuxSidebar: (preferences: GSDPreferences | undefined, state: GSDState) => void;
|
|
281
|
+
logCmuxEvent: (
|
|
282
|
+
preferences: GSDPreferences | undefined,
|
|
283
|
+
message: string,
|
|
284
|
+
level?: CmuxLogLevel,
|
|
285
|
+
) => void;
|
|
270
286
|
|
|
271
287
|
// State and cache functions
|
|
272
288
|
invalidateAllCaches: () => void;
|
|
@@ -344,6 +360,7 @@ export interface LoopDeps {
|
|
|
344
360
|
getManifestStatus: (
|
|
345
361
|
basePath: string,
|
|
346
362
|
mid: string | undefined,
|
|
363
|
+
projectRoot?: string,
|
|
347
364
|
) => Promise<{ pending: unknown[] } | null>;
|
|
348
365
|
collectSecretsFromManifest: (
|
|
349
366
|
basePath: string,
|
|
@@ -446,6 +463,7 @@ export interface LoopDeps {
|
|
|
446
463
|
prefs: GSDPreferences | undefined,
|
|
447
464
|
verbose: boolean,
|
|
448
465
|
startModel: { provider: string; id: string } | null,
|
|
466
|
+
retryContext?: { isRetry: boolean; previousTier?: string },
|
|
449
467
|
) => Promise<{ routing: { tier: string; modelDowngraded: boolean } | null }>;
|
|
450
468
|
startUnitSupervision: (sctx: {
|
|
451
469
|
s: AutoSession;
|
|
@@ -598,6 +616,7 @@ export async function autoLoop(
|
|
|
598
616
|
|
|
599
617
|
// Derive state
|
|
600
618
|
let state = await deps.deriveState(s.basePath);
|
|
619
|
+
deps.syncCmuxSidebar(deps.loadEffectiveGSDPreferences()?.preferences, state);
|
|
601
620
|
let mid = state.activeMilestone?.id;
|
|
602
621
|
let midTitle = state.activeMilestone?.title;
|
|
603
622
|
debugLog("autoLoop", {
|
|
@@ -619,6 +638,11 @@ export async function autoLoop(
|
|
|
619
638
|
"success",
|
|
620
639
|
"milestone",
|
|
621
640
|
);
|
|
641
|
+
deps.logCmuxEvent(
|
|
642
|
+
deps.loadEffectiveGSDPreferences()?.preferences,
|
|
643
|
+
`Milestone ${s.currentMilestoneId} complete. Advancing to ${mid}.`,
|
|
644
|
+
"success",
|
|
645
|
+
);
|
|
622
646
|
|
|
623
647
|
const vizPrefs = deps.loadEffectiveGSDPreferences()?.preferences;
|
|
624
648
|
if (vizPrefs?.auto_visualize) {
|
|
@@ -756,12 +780,18 @@ export async function autoLoop(
|
|
|
756
780
|
"success",
|
|
757
781
|
"milestone",
|
|
758
782
|
);
|
|
783
|
+
deps.logCmuxEvent(
|
|
784
|
+
deps.loadEffectiveGSDPreferences()?.preferences,
|
|
785
|
+
"All milestones complete.",
|
|
786
|
+
"success",
|
|
787
|
+
);
|
|
759
788
|
await deps.stopAuto(ctx, pi, "All milestones complete");
|
|
760
789
|
} else if (state.phase === "blocked") {
|
|
761
790
|
const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
|
|
762
791
|
await deps.stopAuto(ctx, pi, blockerMsg);
|
|
763
792
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
764
793
|
deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
794
|
+
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
|
|
765
795
|
} else {
|
|
766
796
|
const ids = incomplete.map((m: { id: string }) => m.id).join(", ");
|
|
767
797
|
const diag = `basePath=${s.basePath}, milestones=[${state.registry.map((m: { id: string; status: string }) => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
|
|
@@ -839,6 +869,11 @@ export async function autoLoop(
|
|
|
839
869
|
"success",
|
|
840
870
|
"milestone",
|
|
841
871
|
);
|
|
872
|
+
deps.logCmuxEvent(
|
|
873
|
+
deps.loadEffectiveGSDPreferences()?.preferences,
|
|
874
|
+
`Milestone ${mid} complete.`,
|
|
875
|
+
"success",
|
|
876
|
+
);
|
|
842
877
|
await deps.stopAuto(ctx, pi, `Milestone ${mid} complete`);
|
|
843
878
|
debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
|
|
844
879
|
break;
|
|
@@ -860,6 +895,7 @@ export async function autoLoop(
|
|
|
860
895
|
await deps.stopAuto(ctx, pi, blockerMsg);
|
|
861
896
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
862
897
|
deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
898
|
+
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
|
|
863
899
|
debugLog("autoLoop", { phase: "exit", reason: "blocked" });
|
|
864
900
|
break;
|
|
865
901
|
}
|
|
@@ -903,12 +939,14 @@ export async function autoLoop(
|
|
|
903
939
|
"warning",
|
|
904
940
|
);
|
|
905
941
|
deps.sendDesktopNotification("GSD", msg, "warning", "budget");
|
|
942
|
+
deps.logCmuxEvent(prefs, msg, "warning");
|
|
906
943
|
await deps.pauseAuto(ctx, pi);
|
|
907
944
|
debugLog("autoLoop", { phase: "exit", reason: "budget-pause" });
|
|
908
945
|
break;
|
|
909
946
|
}
|
|
910
947
|
ctx.ui.notify(`${msg} Continuing (enforcement: warn).`, "warning");
|
|
911
948
|
deps.sendDesktopNotification("GSD", msg, "warning", "budget");
|
|
949
|
+
deps.logCmuxEvent(prefs, msg, "warning");
|
|
912
950
|
} else if (newBudgetAlertLevel === 90) {
|
|
913
951
|
s.lastBudgetAlertLevel =
|
|
914
952
|
newBudgetAlertLevel as AutoSession["lastBudgetAlertLevel"];
|
|
@@ -922,6 +960,11 @@ export async function autoLoop(
|
|
|
922
960
|
"warning",
|
|
923
961
|
"budget",
|
|
924
962
|
);
|
|
963
|
+
deps.logCmuxEvent(
|
|
964
|
+
prefs,
|
|
965
|
+
`Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`,
|
|
966
|
+
"warning",
|
|
967
|
+
);
|
|
925
968
|
} else if (newBudgetAlertLevel === 80) {
|
|
926
969
|
s.lastBudgetAlertLevel =
|
|
927
970
|
newBudgetAlertLevel as AutoSession["lastBudgetAlertLevel"];
|
|
@@ -935,6 +978,11 @@ export async function autoLoop(
|
|
|
935
978
|
"warning",
|
|
936
979
|
"budget",
|
|
937
980
|
);
|
|
981
|
+
deps.logCmuxEvent(
|
|
982
|
+
prefs,
|
|
983
|
+
`Budget 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`,
|
|
984
|
+
"warning",
|
|
985
|
+
);
|
|
938
986
|
} else if (newBudgetAlertLevel === 75) {
|
|
939
987
|
s.lastBudgetAlertLevel =
|
|
940
988
|
newBudgetAlertLevel as AutoSession["lastBudgetAlertLevel"];
|
|
@@ -948,6 +996,11 @@ export async function autoLoop(
|
|
|
948
996
|
"info",
|
|
949
997
|
"budget",
|
|
950
998
|
);
|
|
999
|
+
deps.logCmuxEvent(
|
|
1000
|
+
prefs,
|
|
1001
|
+
`Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`,
|
|
1002
|
+
"progress",
|
|
1003
|
+
);
|
|
951
1004
|
} else if (budgetAlertLevel === 0) {
|
|
952
1005
|
s.lastBudgetAlertLevel = 0;
|
|
953
1006
|
}
|
|
@@ -983,7 +1036,7 @@ export async function autoLoop(
|
|
|
983
1036
|
|
|
984
1037
|
// Secrets re-check gate
|
|
985
1038
|
try {
|
|
986
|
-
const manifestStatus = await deps.getManifestStatus(s.basePath, mid);
|
|
1039
|
+
const manifestStatus = await deps.getManifestStatus(s.basePath, mid, s.originalBasePath);
|
|
987
1040
|
if (manifestStatus && manifestStatus.pending.length > 0) {
|
|
988
1041
|
const result = await deps.collectSecretsFromManifest(
|
|
989
1042
|
s.basePath,
|
|
@@ -1172,6 +1225,14 @@ export async function autoLoop(
|
|
|
1172
1225
|
unitId,
|
|
1173
1226
|
});
|
|
1174
1227
|
|
|
1228
|
+
// Detect retry and capture previous tier for escalation
|
|
1229
|
+
const isRetry = !!(
|
|
1230
|
+
s.currentUnit &&
|
|
1231
|
+
s.currentUnit.type === unitType &&
|
|
1232
|
+
s.currentUnit.id === unitId
|
|
1233
|
+
);
|
|
1234
|
+
const previousTier = s.currentUnitRouting?.tier;
|
|
1235
|
+
|
|
1175
1236
|
// Closeout previous unit
|
|
1176
1237
|
if (s.currentUnit) {
|
|
1177
1238
|
await deps.closeoutUnit(
|
|
@@ -1325,7 +1386,7 @@ export async function autoLoop(
|
|
|
1325
1386
|
);
|
|
1326
1387
|
}
|
|
1327
1388
|
|
|
1328
|
-
// Select and apply model
|
|
1389
|
+
// Select and apply model (with tier escalation on retry)
|
|
1329
1390
|
const modelResult = await deps.selectAndApplyModel(
|
|
1330
1391
|
ctx,
|
|
1331
1392
|
pi,
|
|
@@ -1335,6 +1396,7 @@ export async function autoLoop(
|
|
|
1335
1396
|
prefs,
|
|
1336
1397
|
s.verbose,
|
|
1337
1398
|
s.autoModeStartModel,
|
|
1399
|
+
{ isRetry, previousTier },
|
|
1338
1400
|
);
|
|
1339
1401
|
s.currentUnitRouting =
|
|
1340
1402
|
modelResult.routing as AutoSession["currentUnitRouting"];
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
8
8
|
import type { GSDPreferences } from "./preferences.js";
|
|
9
9
|
import { resolveModelWithFallbacksForUnit, resolveDynamicRoutingConfig } from "./preferences.js";
|
|
10
|
+
import type { ComplexityTier } from "./complexity-classifier.js";
|
|
10
11
|
import { classifyUnitComplexity, tierLabel } from "./complexity-classifier.js";
|
|
11
|
-
import { resolveModelForComplexity } from "./model-router.js";
|
|
12
|
+
import { resolveModelForComplexity, escalateTier } from "./model-router.js";
|
|
12
13
|
import { getLedger, getProjectTotals } from "./metrics.js";
|
|
13
14
|
import { unitPhaseLabel } from "./auto-dashboard.js";
|
|
14
15
|
|
|
@@ -33,6 +34,7 @@ export async function selectAndApplyModel(
|
|
|
33
34
|
prefs: GSDPreferences | undefined,
|
|
34
35
|
verbose: boolean,
|
|
35
36
|
autoModeStartModel: { provider: string; id: string } | null,
|
|
37
|
+
retryContext?: { isRetry: boolean; previousTier?: string },
|
|
36
38
|
): Promise<ModelSelectionResult> {
|
|
37
39
|
const modelConfig = resolveModelWithFallbacksForUnit(unitType);
|
|
38
40
|
let routing: { tier: string; modelDowngraded: boolean } | null = null;
|
|
@@ -60,8 +62,27 @@ export async function selectAndApplyModel(
|
|
|
60
62
|
const shouldClassify = !isHook || routingConfig.hooks !== false;
|
|
61
63
|
|
|
62
64
|
if (shouldClassify) {
|
|
63
|
-
|
|
65
|
+
let classification = classifyUnitComplexity(unitType, unitId, basePath, budgetPct);
|
|
64
66
|
const availableModelIds = availableModels.map(m => m.id);
|
|
67
|
+
|
|
68
|
+
// Escalate tier on retry when escalate_on_failure is enabled (default: true)
|
|
69
|
+
if (
|
|
70
|
+
retryContext?.isRetry &&
|
|
71
|
+
retryContext.previousTier &&
|
|
72
|
+
routingConfig.escalate_on_failure !== false
|
|
73
|
+
) {
|
|
74
|
+
const escalated = escalateTier(retryContext.previousTier as ComplexityTier);
|
|
75
|
+
if (escalated) {
|
|
76
|
+
classification = { ...classification, tier: escalated, reason: "escalated after failure" };
|
|
77
|
+
if (verbose) {
|
|
78
|
+
ctx.ui.notify(
|
|
79
|
+
`Tier escalation: ${retryContext.previousTier} → ${escalated} (retry after failure)`,
|
|
80
|
+
"info",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
65
86
|
const routingResult = resolveModelForComplexity(classification, modelConfig, routingConfig, availableModelIds);
|
|
66
87
|
|
|
67
88
|
if (routingResult.wasDowngraded) {
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
11
|
+
import { parseUnitId } from "./unit-id.js";
|
|
12
|
+
import { atomicWriteSync } from "./atomic-write.js";
|
|
11
13
|
import { clearUnitRuntimeRecord } from "./unit-runtime.js";
|
|
12
14
|
import { clearParseCache, parseRoadmap, parsePlan } from "./files.js";
|
|
13
15
|
import { isValidationTerminal } from "./state.js";
|
|
@@ -35,6 +37,7 @@ import {
|
|
|
35
37
|
clearPathCache,
|
|
36
38
|
resolveGsdRootFile,
|
|
37
39
|
} from "./paths.js";
|
|
40
|
+
import { markSliceDoneInRoadmap } from "./roadmap-mutations.js";
|
|
38
41
|
import {
|
|
39
42
|
existsSync,
|
|
40
43
|
mkdirSync,
|
|
@@ -499,6 +502,42 @@ export async function selfHealRuntimeRecords(
|
|
|
499
502
|
for (const record of records) {
|
|
500
503
|
const { unitType, unitId } = record;
|
|
501
504
|
|
|
505
|
+
// Case 0: complete-slice with SUMMARY + UAT but unchecked roadmap (#1350).
|
|
506
|
+
// If a complete-slice was interrupted after writing artifacts but before
|
|
507
|
+
// flipping the roadmap checkbox, the verification fails and the dispatch
|
|
508
|
+
// loop relaunches the same unit forever. Auto-fix the checkbox.
|
|
509
|
+
if (unitType === "complete-slice") {
|
|
510
|
+
const { milestone: mid, slice: sid } = parseUnitId(unitId);
|
|
511
|
+
if (mid && sid) {
|
|
512
|
+
const dir = resolveSlicePath(base, mid, sid);
|
|
513
|
+
if (dir) {
|
|
514
|
+
const summaryPath = join(dir, buildSliceFileName(sid, "SUMMARY"));
|
|
515
|
+
const uatPath = join(dir, buildSliceFileName(sid, "UAT"));
|
|
516
|
+
if (existsSync(summaryPath) && existsSync(uatPath)) {
|
|
517
|
+
const roadmapFile = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
518
|
+
if (roadmapFile && existsSync(roadmapFile)) {
|
|
519
|
+
try {
|
|
520
|
+
const roadmapContent = readFileSync(roadmapFile, "utf-8");
|
|
521
|
+
const roadmap = parseRoadmap(roadmapContent);
|
|
522
|
+
const slice = (roadmap.slices ?? []).find(s => s.id === sid);
|
|
523
|
+
if (slice && !slice.done) {
|
|
524
|
+
// Auto-fix: flip the checkbox using shared utility
|
|
525
|
+
if (markSliceDoneInRoadmap(base, mid, sid)) {
|
|
526
|
+
ctx.ui.notify(
|
|
527
|
+
`Self-heal: marked ${sid} done in roadmap (SUMMARY + UAT exist but checkbox was stale).`,
|
|
528
|
+
"info",
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
} catch {
|
|
533
|
+
// Roadmap parse failure — don't block self-heal
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
502
541
|
// Clear stale dispatched records (dispatched > 1h ago, process crashed)
|
|
503
542
|
const age = now - (record.startedAt ?? 0);
|
|
504
543
|
if (record.phase === "dispatched" && age > STALE_THRESHOLD_MS) {
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
resolveSkillDiscoveryMode,
|
|
21
21
|
getIsolationMode,
|
|
22
22
|
} from "./preferences.js";
|
|
23
|
+
import { ensureGsdSymlink } from "./repo-identity.js";
|
|
24
|
+
import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
|
|
23
25
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
24
26
|
import { gsdRoot, resolveMilestoneFile, milestonesDir } from "./paths.js";
|
|
25
27
|
import { invalidateAllCaches } from "./cache.js";
|
|
@@ -92,6 +94,13 @@ export interface BootstrapDeps {
|
|
|
92
94
|
* Returns false if the bootstrap aborted (e.g., guided flow returned,
|
|
93
95
|
* concurrent session detected). Returns true when ready to dispatch.
|
|
94
96
|
*/
|
|
97
|
+
|
|
98
|
+
/** Guard: tracks consecutive bootstrap attempts that found phase === "complete".
|
|
99
|
+
* Prevents the recursive dialog loop described in #1348 where
|
|
100
|
+
* bootstrapAutoSession → showSmartEntry → checkAutoStartAfterDiscuss → startAuto
|
|
101
|
+
* cycles indefinitely when the discuss workflow doesn't produce a milestone. */
|
|
102
|
+
let _consecutiveCompleteBootstraps = 0;
|
|
103
|
+
const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
|
|
95
104
|
export async function bootstrapAutoSession(
|
|
96
105
|
s: AutoSession,
|
|
97
106
|
ctx: ExtensionCommandContext,
|
|
@@ -128,7 +137,20 @@ export async function bootstrapAutoSession(
|
|
|
128
137
|
nativeInit(base, mainBranch);
|
|
129
138
|
}
|
|
130
139
|
|
|
131
|
-
//
|
|
140
|
+
// Migrate legacy in-project .gsd/ to external state directory.
|
|
141
|
+
// Migration MUST run before ensureGitignore to avoid adding ".gsd" to
|
|
142
|
+
// .gitignore when .gsd/ is git-tracked (data-loss bug #1364).
|
|
143
|
+
recoverFailedMigration(base);
|
|
144
|
+
const migration = migrateToExternalState(base);
|
|
145
|
+
if (migration.error) {
|
|
146
|
+
ctx.ui.notify(`External state migration warning: ${migration.error}`, "warning");
|
|
147
|
+
}
|
|
148
|
+
// Ensure symlink exists (handles fresh projects and post-migration)
|
|
149
|
+
ensureGsdSymlink(base);
|
|
150
|
+
|
|
151
|
+
// Ensure .gitignore has baseline patterns.
|
|
152
|
+
// ensureGitignore checks for git-tracked .gsd/ files and skips the
|
|
153
|
+
// ".gsd" pattern if the project intentionally tracks .gsd/ in git.
|
|
132
154
|
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
133
155
|
const commitDocs = gitPrefs?.commit_docs;
|
|
134
156
|
const manageGitignore = gitPrefs?.manage_gitignore;
|
|
@@ -286,6 +308,20 @@ export async function bootstrapAutoSession(
|
|
|
286
308
|
if (!hasSurvivorBranch) {
|
|
287
309
|
// No active work — start a new milestone via discuss flow
|
|
288
310
|
if (!state.activeMilestone || state.phase === "complete") {
|
|
311
|
+
// Guard against recursive dialog loop (#1348):
|
|
312
|
+
// If we've entered this branch multiple times in quick succession,
|
|
313
|
+
// the discuss workflow isn't producing a milestone. Break the cycle.
|
|
314
|
+
_consecutiveCompleteBootstraps++;
|
|
315
|
+
if (_consecutiveCompleteBootstraps > MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS) {
|
|
316
|
+
_consecutiveCompleteBootstraps = 0;
|
|
317
|
+
ctx.ui.notify(
|
|
318
|
+
"All milestones are complete and the discussion didn't produce a new one. " +
|
|
319
|
+
"Run /gsd to start a new milestone manually.",
|
|
320
|
+
"warning",
|
|
321
|
+
);
|
|
322
|
+
return releaseLockAndReturn();
|
|
323
|
+
}
|
|
324
|
+
|
|
289
325
|
const { showSmartEntry } = await import("./guided-flow.js");
|
|
290
326
|
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
291
327
|
|
|
@@ -296,6 +332,7 @@ export async function bootstrapAutoSession(
|
|
|
296
332
|
postState.phase !== "complete" &&
|
|
297
333
|
postState.phase !== "pre-planning"
|
|
298
334
|
) {
|
|
335
|
+
_consecutiveCompleteBootstraps = 0; // Successfully advanced past "complete"
|
|
299
336
|
state = postState;
|
|
300
337
|
} else if (
|
|
301
338
|
postState.activeMilestone &&
|
|
@@ -352,6 +389,9 @@ export async function bootstrapAutoSession(
|
|
|
352
389
|
return releaseLockAndReturn();
|
|
353
390
|
}
|
|
354
391
|
|
|
392
|
+
// Successfully resolved an active milestone — reset the re-entry guard
|
|
393
|
+
_consecutiveCompleteBootstraps = 0;
|
|
394
|
+
|
|
355
395
|
// ── Initialize session state ──
|
|
356
396
|
s.active = true;
|
|
357
397
|
s.stepMode = requestedStepMode;
|
|
@@ -484,7 +524,7 @@ export async function bootstrapAutoSession(
|
|
|
484
524
|
// Secrets collection gate
|
|
485
525
|
const mid = state.activeMilestone!.id;
|
|
486
526
|
try {
|
|
487
|
-
const manifestStatus = await getManifestStatus(base, mid);
|
|
527
|
+
const manifestStatus = await getManifestStatus(base, mid, s.originalBasePath || base);
|
|
488
528
|
if (manifestStatus && manifestStatus.pending.length > 0) {
|
|
489
529
|
const result = await collectSecretsFromManifest(base, mid, ctx);
|
|
490
530
|
if (
|
|
@@ -127,7 +127,7 @@ import {
|
|
|
127
127
|
formatTokenCount,
|
|
128
128
|
} from "./metrics.js";
|
|
129
129
|
import { join } from "node:path";
|
|
130
|
-
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
130
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
|
|
131
131
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
132
132
|
import {
|
|
133
133
|
autoCommitCurrentBranch,
|
|
@@ -184,6 +184,7 @@ import {
|
|
|
184
184
|
} from "./auto-supervisor.js";
|
|
185
185
|
import { isDbAvailable } from "./gsd-db.js";
|
|
186
186
|
import { countPendingCaptures } from "./captures.js";
|
|
187
|
+
import { clearCmuxSidebar, logCmuxEvent, syncCmuxSidebar } from "../cmux/index.js";
|
|
187
188
|
|
|
188
189
|
// ── Extracted modules ──────────────────────────────────────────────────────
|
|
189
190
|
import { startUnitSupervision } from "./auto-timers.js";
|
|
@@ -466,6 +467,7 @@ function handleLostSessionLock(ctx?: ExtensionContext): void {
|
|
|
466
467
|
s.paused = false;
|
|
467
468
|
clearUnitTimeout();
|
|
468
469
|
deregisterSigtermHandler();
|
|
470
|
+
clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
|
|
469
471
|
ctx?.ui.notify(
|
|
470
472
|
"Session lock lost — another GSD process appears to have taken over. Stopping gracefully.",
|
|
471
473
|
"error",
|
|
@@ -481,6 +483,7 @@ export async function stopAuto(
|
|
|
481
483
|
reason?: string,
|
|
482
484
|
): Promise<void> {
|
|
483
485
|
if (!s.active && !s.paused) return;
|
|
486
|
+
const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
|
|
484
487
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
485
488
|
clearUnitTimeout();
|
|
486
489
|
if (lockBase()) clearLock(lockBase());
|
|
@@ -543,6 +546,13 @@ export async function stopAuto(
|
|
|
543
546
|
}
|
|
544
547
|
}
|
|
545
548
|
|
|
549
|
+
clearCmuxSidebar(loadedPreferences);
|
|
550
|
+
logCmuxEvent(
|
|
551
|
+
loadedPreferences,
|
|
552
|
+
`Auto-mode stopped${reasonSuffix || ""}.`,
|
|
553
|
+
reason?.startsWith("Blocked:") ? "warning" : "info",
|
|
554
|
+
);
|
|
555
|
+
|
|
546
556
|
if (isDebugEnabled()) {
|
|
547
557
|
const logPath = writeDebugSummary();
|
|
548
558
|
if (logPath) {
|
|
@@ -554,6 +564,13 @@ export async function stopAuto(
|
|
|
554
564
|
resetRoutingHistory();
|
|
555
565
|
resetHookState();
|
|
556
566
|
if (s.basePath) clearPersistedHookState(s.basePath);
|
|
567
|
+
|
|
568
|
+
// Remove paused-session metadata if present (#1383)
|
|
569
|
+
try {
|
|
570
|
+
const pausedPath = join(gsdRoot(s.originalBasePath || s.basePath), "runtime", "paused-session.json");
|
|
571
|
+
if (existsSync(pausedPath)) unlinkSync(pausedPath);
|
|
572
|
+
} catch { /* non-fatal */ }
|
|
573
|
+
|
|
557
574
|
s.active = false;
|
|
558
575
|
s.paused = false;
|
|
559
576
|
s.stepMode = false;
|
|
@@ -607,8 +624,32 @@ export async function pauseAuto(
|
|
|
607
624
|
|
|
608
625
|
s.pausedSessionFile = ctx?.sessionManager?.getSessionFile() ?? null;
|
|
609
626
|
|
|
610
|
-
|
|
611
|
-
|
|
627
|
+
// Persist paused-session metadata so resume survives /exit (#1383).
|
|
628
|
+
// The fresh-start bootstrap checks for this file and restores worktree context.
|
|
629
|
+
try {
|
|
630
|
+
const pausedMeta = {
|
|
631
|
+
milestoneId: s.currentMilestoneId,
|
|
632
|
+
worktreePath: isInAutoWorktree(s.basePath) ? s.basePath : null,
|
|
633
|
+
originalBasePath: s.originalBasePath,
|
|
634
|
+
stepMode: s.stepMode,
|
|
635
|
+
pausedAt: new Date().toISOString(),
|
|
636
|
+
sessionFile: s.pausedSessionFile,
|
|
637
|
+
};
|
|
638
|
+
const runtimeDir = join(gsdRoot(s.originalBasePath || s.basePath), "runtime");
|
|
639
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
640
|
+
writeFileSync(
|
|
641
|
+
join(runtimeDir, "paused-session.json"),
|
|
642
|
+
JSON.stringify(pausedMeta, null, 2),
|
|
643
|
+
"utf-8",
|
|
644
|
+
);
|
|
645
|
+
} catch {
|
|
646
|
+
// Non-fatal — resume will still work via full bootstrap, just without worktree context
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (lockBase()) {
|
|
650
|
+
releaseSessionLock(lockBase());
|
|
651
|
+
clearLock(lockBase());
|
|
652
|
+
}
|
|
612
653
|
|
|
613
654
|
deregisterSigtermHandler();
|
|
614
655
|
|
|
@@ -677,6 +718,8 @@ function buildLoopDeps(): LoopDeps {
|
|
|
677
718
|
pauseAuto,
|
|
678
719
|
clearUnitTimeout,
|
|
679
720
|
updateProgressWidget,
|
|
721
|
+
syncCmuxSidebar,
|
|
722
|
+
logCmuxEvent,
|
|
680
723
|
|
|
681
724
|
// State and cache
|
|
682
725
|
invalidateAllCaches,
|
|
@@ -792,6 +835,30 @@ export async function startAuto(
|
|
|
792
835
|
base = escapeStaleWorktree(base);
|
|
793
836
|
|
|
794
837
|
// If resuming from paused state, just re-activate and dispatch next unit.
|
|
838
|
+
// Check persisted paused-session first (#1383) — survives /exit.
|
|
839
|
+
if (!s.paused) {
|
|
840
|
+
try {
|
|
841
|
+
const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json");
|
|
842
|
+
if (existsSync(pausedPath)) {
|
|
843
|
+
const meta = JSON.parse(readFileSync(pausedPath, "utf-8"));
|
|
844
|
+
if (meta.milestoneId) {
|
|
845
|
+
s.currentMilestoneId = meta.milestoneId;
|
|
846
|
+
s.originalBasePath = meta.originalBasePath || base;
|
|
847
|
+
s.stepMode = meta.stepMode ?? requestedStepMode;
|
|
848
|
+
s.paused = true;
|
|
849
|
+
// Clean up the persisted file — we're consuming it
|
|
850
|
+
try { unlinkSync(pausedPath); } catch { /* non-fatal */ }
|
|
851
|
+
ctx.ui.notify(
|
|
852
|
+
`Resuming paused session for ${meta.milestoneId}${meta.worktreePath ? ` (worktree)` : ""}.`,
|
|
853
|
+
"info",
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
} catch {
|
|
858
|
+
// Malformed or missing — proceed with fresh bootstrap
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
795
862
|
if (s.paused) {
|
|
796
863
|
const resumeLock = acquireSessionLock(base);
|
|
797
864
|
if (!resumeLock.acquired) {
|
|
@@ -835,6 +902,7 @@ export async function startAuto(
|
|
|
835
902
|
restoreHookState(s.basePath);
|
|
836
903
|
try {
|
|
837
904
|
await rebuildState(s.basePath);
|
|
905
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
838
906
|
} catch (e) {
|
|
839
907
|
debugLog("resume-rebuild-state-failed", {
|
|
840
908
|
error: e instanceof Error ? e.message : String(e),
|
|
@@ -886,6 +954,7 @@ export async function startAuto(
|
|
|
886
954
|
s.currentMilestoneId ?? "unknown",
|
|
887
955
|
s.completedUnits.length,
|
|
888
956
|
);
|
|
957
|
+
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
889
958
|
|
|
890
959
|
await autoLoop(ctx, pi, s, buildLoopDeps());
|
|
891
960
|
return;
|
|
@@ -910,6 +979,13 @@ export async function startAuto(
|
|
|
910
979
|
);
|
|
911
980
|
if (!ready) return;
|
|
912
981
|
|
|
982
|
+
try {
|
|
983
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
984
|
+
} catch {
|
|
985
|
+
// Best-effort only — sidebar sync must never block auto-mode startup
|
|
986
|
+
}
|
|
987
|
+
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
988
|
+
|
|
913
989
|
// Dispatch the first unit
|
|
914
990
|
await autoLoop(ctx, pi, s, buildLoopDeps());
|
|
915
991
|
}
|
|
@@ -1145,6 +1221,9 @@ export async function dispatchHookUnit(
|
|
|
1145
1221
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
1146
1222
|
ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
|
|
1147
1223
|
|
|
1224
|
+
// Ensure cwd matches basePath before hook dispatch (#1389)
|
|
1225
|
+
try { if (process.cwd() !== s.basePath) process.chdir(s.basePath); } catch {}
|
|
1226
|
+
|
|
1148
1227
|
debugLog("dispatchHookUnit", {
|
|
1149
1228
|
phase: "send-message",
|
|
1150
1229
|
promptLength: hookPrompt.length,
|