Keep /gsd auto artifact writes scoped to the active milestone worktree (#590)
This commit is contained in:
parent
2ae4633d05
commit
570f6195be
4 changed files with 65 additions and 4 deletions
|
|
@ -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 ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
|||
|
||||
// ─── 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<void> {
|
|||
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<void> {
|
|||
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");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue