diff --git a/src/resources/extensions/gsd/auto-worktree.ts b/src/resources/extensions/gsd/auto-worktree.ts index b788e6a79..1b0494b3b 100644 --- a/src/resources/extensions/gsd/auto-worktree.ts +++ b/src/resources/extensions/gsd/auto-worktree.ts @@ -14,6 +14,7 @@ import { removeWorktree, worktreePath, } from "./worktree-manager.js"; +import { detectWorktreeName } from "./worktree.js"; import { MergeConflictError, } from "./git-service.js"; @@ -224,6 +225,27 @@ export function getAutoWorktreeOriginalBase(): string | null { return originalBase; } +export function getActiveAutoWorktreeContext(): { + originalBase: string; + worktreeName: string; + branch: string; +} | null { + if (!originalBase) return null; + const cwd = process.cwd(); + const resolvedBase = existsSync(originalBase) ? realpathSync(originalBase) : originalBase; + const wtDir = join(resolvedBase, ".gsd", "worktrees"); + if (!cwd.startsWith(wtDir)) return null; + const worktreeName = detectWorktreeName(cwd); + if (!worktreeName) return null; + const branch = nativeGetCurrentBranch(cwd); + if (!branch.startsWith("milestone/")) return null; + return { + originalBase, + worktreeName, + branch, + }; +} + // ─── Merge Milestone -> Main ─────────────────────────────────────────────── /** diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index 2d57c60b2..0e919b110 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -591,17 +591,17 @@ export async function startAuto( ctx.ui.setFooter(hideFooter); ctx.ui.notify(stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info"); // Restore hook state from disk in case session was interrupted - restoreHookState(base); + restoreHookState(basePath); // Rebuild disk state before resuming — user interaction during pause may have changed files - try { await rebuildState(base); } catch { /* non-fatal */ } + try { await rebuildState(basePath); } catch { /* non-fatal */ } try { - const report = await runGSDDoctor(base, { fix: true }); + const report = await runGSDDoctor(basePath, { fix: true }); if (report.fixesApplied.length > 0) { ctx.ui.notify(`Resume: applied ${report.fixesApplied.length} fix(es) to state.`, "info"); } } catch { /* non-fatal */ } // Self-heal: clear stale runtime records where artifacts already exist - await selfHealRuntimeRecords(base, ctx, completedKeySet); + await selfHealRuntimeRecords(basePath, ctx, completedKeySet); invalidateAllCaches(); await dispatchNextUnit(ctx, pi); return; diff --git a/src/resources/extensions/gsd/index.ts b/src/resources/extensions/gsd/index.ts index b66083f8a..0813dd7e6 100644 --- a/src/resources/extensions/gsd/index.ts +++ b/src/resources/extensions/gsd/index.ts @@ -28,6 +28,7 @@ import { createBashTool, createWriteTool, createReadTool, createEditTool, isTool import { registerGSDCommand, loadToolApiKeys } from "./commands.js"; import { registerExitCommand } from "./exit-command.js"; import { registerWorktreeCommand, getWorktreeOriginalCwd, getActiveWorktreeName } from "./worktree-command.js"; +import { getActiveAutoWorktreeContext } from "./auto-worktree.js"; import { saveFile, formatContinue, loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection } from "./files.js"; import { loadPrompt } from "./prompt-loader.js"; import { deriveState } from "./state.js"; @@ -302,6 +303,7 @@ export default function (pi: ExtensionAPI) { let worktreeBlock = ""; const worktreeName = getActiveWorktreeName(); const worktreeMainCwd = getWorktreeOriginalCwd(); + const autoWorktree = getActiveAutoWorktreeContext(); if (worktreeName && worktreeMainCwd) { worktreeBlock = [ "", @@ -319,6 +321,23 @@ export default function (pi: ExtensionAPI) { "All file operations, bash commands, and GSD state resolve against the worktree path above.", "Use /worktree merge to merge changes back. Use /worktree return to switch back to the main tree.", ].join("\n"); + } else if (autoWorktree) { + worktreeBlock = [ + "", + "", + "[WORKTREE CONTEXT — OVERRIDES CURRENT WORKING DIRECTORY ABOVE]", + `IMPORTANT: Ignore the "Current working directory" shown earlier in this prompt.`, + `The actual current working directory is: ${process.cwd()}`, + "", + "You are working inside a GSD auto-worktree.", + `- Milestone worktree: ${autoWorktree.worktreeName}`, + `- Worktree path (this is the real cwd): ${process.cwd()}`, + `- Main project: ${autoWorktree.originalBase}`, + `- Branch: ${autoWorktree.branch}`, + "", + "All file operations, bash commands, and GSD state resolve against the worktree path above.", + "Write every .gsd artifact in the worktree path above, never in the main project tree.", + ].join("\n"); } return { diff --git a/src/resources/extensions/gsd/tests/auto-worktree.test.ts b/src/resources/extensions/gsd/tests/auto-worktree.test.ts index b6b4a4498..abb93baa2 100644 --- a/src/resources/extensions/gsd/tests/auto-worktree.test.ts +++ b/src/resources/extensions/gsd/tests/auto-worktree.test.ts @@ -17,6 +17,7 @@ import { getAutoWorktreePath, enterAutoWorktree, getAutoWorktreeOriginalBase, + getActiveAutoWorktreeContext, } from "../auto-worktree.ts"; import { createTestContext } from "./test-helpers.ts"; @@ -76,6 +77,15 @@ async function main(): Promise { // ─── getAutoWorktreeOriginalBase ───────────────────────────────── assertEq(getAutoWorktreeOriginalBase(), tempDir, "originalBase returns temp dir"); + assertEq( + getActiveAutoWorktreeContext(), + { + originalBase: tempDir, + worktreeName: "M003", + branch: "milestone/M003", + }, + "active auto-worktree context reflects the worktree cwd", + ); // ─── getAutoWorktreePath ───────────────────────────────────────── assertEq(getAutoWorktreePath(tempDir, "M003"), wtPath, "getAutoWorktreePath returns correct path"); @@ -88,6 +98,7 @@ async function main(): Promise { assertTrue(!existsSync(wtPath), "worktree directory removed after teardown"); assertTrue(!isInAutoWorktree(tempDir), "isInAutoWorktree returns false after teardown"); assertEq(getAutoWorktreeOriginalBase(), null, "originalBase is null after teardown"); + assertEq(getActiveAutoWorktreeContext(), null, "active auto-worktree context clears after teardown"); // ─── Re-entry: create again, exit without teardown, re-enter ───── console.log("\n=== re-entry ==="); @@ -103,6 +114,15 @@ async function main(): Promise { assertEq(process.cwd(), entered, "re-entered worktree via enterAutoWorktree"); assertEq(getAutoWorktreeOriginalBase(), tempDir, "originalBase restored on re-entry"); assertTrue(isInAutoWorktree(tempDir), "isInAutoWorktree true after re-entry"); + assertEq( + getActiveAutoWorktreeContext(), + { + originalBase: tempDir, + worktreeName: "M003", + branch: "milestone/M003", + }, + "active auto-worktree context is restored on re-entry", + ); // Cleanup teardownAutoWorktree(tempDir, "M003");