diff --git a/src/resources/extensions/gsd/guided-flow.ts b/src/resources/extensions/gsd/guided-flow.ts index c57eb4aea..b70fd3fd9 100644 --- a/src/resources/extensions/gsd/guided-flow.ts +++ b/src/resources/extensions/gsd/guided-flow.ts @@ -544,12 +544,12 @@ export async function showDiscuss( const seed = draftContent ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}` : basePrompt; - pendingAutoStart = { ctx, pi, basePath, milestoneId: mid, step: true }; + pendingAutoStart = { ctx, pi, basePath, milestoneId: mid, step: false }; await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "plan-milestone"); } else if (choice === "discuss_fresh") { const discussMilestoneTemplates = inlineTemplate("context", "Context"); const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false"; - pendingAutoStart = { ctx, pi, basePath, milestoneId: mid, step: true }; + pendingAutoStart = { ctx, pi, basePath, milestoneId: mid, step: false }; await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", { milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable, commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`), diff --git a/src/resources/extensions/gsd/interrupted-session.ts b/src/resources/extensions/gsd/interrupted-session.ts index dca5f9392..7d391b4cd 100644 --- a/src/resources/extensions/gsd/interrupted-session.ts +++ b/src/resources/extensions/gsd/interrupted-session.ts @@ -74,6 +74,7 @@ export async function assessInterruptedSession( basePath: string, ): Promise { const pausedSession = readPausedSessionMetadata(basePath); + const assessmentBasePath = pausedSession?.worktreePath || basePath; const rawLock = readCrashLock(basePath); const lock = rawLock && rawLock.pid !== process.pid ? rawLock : null; @@ -108,22 +109,22 @@ export async function assessInterruptedSession( } const isBootstrapCrash = isBootstrapCrashLock(lock); - const state = await deriveState(basePath); + const state = await deriveState(assessmentBasePath); const hasResumableDiskState = hasResumableDerivedState(state); const artifactSatisfied = !!( lock && !isBootstrapCrash && - verifyExpectedArtifact(lock.unitType, lock.unitId, basePath) + verifyExpectedArtifact(lock.unitType, lock.unitId, assessmentBasePath) ); let recovery: RecoveryBriefing | null = null; if (lock && !isBootstrapCrash && !artifactSatisfied) { recovery = synthesizeCrashRecovery( - basePath, + assessmentBasePath, lock.unitType, lock.unitId, lock.sessionFile, - join(gsdRoot(basePath), "activity"), + join(gsdRoot(assessmentBasePath), "activity"), ); } diff --git a/src/resources/extensions/gsd/tests/crash-recovery.test.ts b/src/resources/extensions/gsd/tests/crash-recovery.test.ts index 1ae8e2fb3..132dbec9b 100644 --- a/src/resources/extensions/gsd/tests/crash-recovery.test.ts +++ b/src/resources/extensions/gsd/tests/crash-recovery.test.ts @@ -98,12 +98,17 @@ function writeCompleteMilestoneSummary(base: string): void { writeFileSync(join(milestoneDir, "M001-SUMMARY.md"), "# Milestone Summary\nDone.\n", "utf-8"); } -function writePausedSession(base: string, milestoneId = "M001", stepMode = false): void { +function writePausedSession( + base: string, + milestoneId = "M001", + stepMode = false, + worktreePath?: string, +): void { const runtimeDir = join(base, ".gsd", "runtime"); mkdirSync(runtimeDir, { recursive: true }); writeFileSync( join(runtimeDir, "paused-session.json"), - JSON.stringify({ milestoneId, originalBasePath: base, stepMode }, null, 2), + JSON.stringify({ milestoneId, originalBasePath: base, stepMode, worktreePath }, null, 2), "utf-8", ); } @@ -227,6 +232,24 @@ test("assessInterruptedSession marks stale paused-session metadata as stale when } }); +test("assessInterruptedSession prefers paused worktree state when worktreePath is recorded", async () => { + const base = makeTmpBase(); + const worktree = join(base, "worktree-copy"); + try { + writeRoadmap(base, false); + writeRoadmap(worktree, true); + writeCompleteSliceArtifacts(worktree); + writeCompleteMilestoneSummary(worktree); + writePausedSession(base, "M001", false, worktree); + + const assessment = await assessInterruptedSession(base); + assert.equal(assessment.classification, "stale"); + assert.equal(assessment.hasResumableDiskState, false); + } finally { + cleanup(base); + } +}); + test("assessInterruptedSession keeps unfinished derived state recoverable without trace", async () => { const base = makeTmpBase(); try { diff --git a/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts b/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts index 67617b721..6a2d0f39d 100644 --- a/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +++ b/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts @@ -127,12 +127,12 @@ test("guided-flow stale paused-session scenario is suppressed when no resumable } }); -test("guided-flow source uses step-aware resume label, step-aware discuss handoff, and stale paused cleanup", () => { +test("guided-flow source uses step-aware resume and clears stale paused metadata without changing discuss handoff semantics", () => { const source = readFileSync(join(import.meta.dirname, "..", "guided-flow.ts"), "utf-8"); assert.ok(source.includes('const interrupted = await assessInterruptedSession(basePath);')); assert.ok(source.includes('resumeLabel = interrupted.pausedSession?.stepMode')); assert.ok(source.includes('step: interrupted.pausedSession?.stepMode ?? false')); assert.ok(source.includes('unlinkSync(join(gsdRoot(basePath), "runtime", "paused-session.json"))')); - assert.ok(source.includes('pendingAutoStart = { ctx, pi, basePath, milestoneId: mid, step: true };')); + assert.ok(source.includes('pendingAutoStart = { ctx, pi, basePath, milestoneId: mid, step: false };')); assert.ok(source.includes('pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: true };')); });