From 5ae8cf88515ad5fa298194a7c0bea32d306aed6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Sun, 15 Mar 2026 15:33:05 -0600 Subject: [PATCH] fix(auto): copy planning artifacts into new auto-worktrees (#516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-worktrees are fresh git checkouts — untracked .gsd/ files don't carry over. Projects with the old blanket .gsd/ gitignore have planning artifacts on disk but not in git. When createAutoWorktree makes a new worktree, the milestones/, DECISIONS.md, REQUIREMENTS.md etc are missing, causing auto-mode to loop on plan-slice (plan file not found in worktree). Copy .gsd/ planning artifacts from the source repo into the new worktree after git worktree add. Skips runtime files and the worktrees/ dir. Co-authored-by: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-worktree.ts | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/auto-worktree.ts b/src/resources/extensions/gsd/auto-worktree.ts index d06d25449..df0efb87c 100644 --- a/src/resources/extensions/gsd/auto-worktree.ts +++ b/src/resources/extensions/gsd/auto-worktree.ts @@ -6,7 +6,7 @@ * manages create, enter, detect, and teardown for auto-mode worktrees. */ -import { existsSync, readFileSync, realpathSync, utimesSync } from "node:fs"; +import { existsSync, cpSync, readFileSync, realpathSync, utimesSync } from "node:fs"; import { join, resolve } from "node:path"; import { execSync, execFileSync } from "node:child_process"; import { @@ -90,6 +90,14 @@ export function autoWorktreeBranch(milestoneId: string): string { export function createAutoWorktree(basePath: string, milestoneId: string): string { const branch = autoWorktreeBranch(milestoneId); const info = createWorktree(basePath, milestoneId, { branch }); + + // Copy .gsd/ planning artifacts from the source repo into the new worktree. + // Worktrees are fresh git checkouts — untracked files don't carry over. + // Planning artifacts may be untracked if the project's .gitignore had a + // blanket .gsd/ rule (pre-v2.14.0). Without this copy, auto-mode loops + // on plan-slice because the plan file doesn't exist in the worktree. + copyPlanningArtifacts(basePath, info.path); + const previousCwd = process.cwd(); try { @@ -107,6 +115,36 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin return info.path; } +/** + * Copy .gsd/ planning artifacts from source repo to a new worktree. + * Copies milestones/, DECISIONS.md, REQUIREMENTS.md, PROJECT.md, QUEUE.md. + * Skips runtime files (auto.lock, metrics.json, etc.) and the worktrees/ dir. + * Best-effort — failures are non-fatal since auto-mode can recreate artifacts. + */ +function copyPlanningArtifacts(srcBase: string, wtPath: string): void { + const srcGsd = join(srcBase, ".gsd"); + const dstGsd = join(wtPath, ".gsd"); + if (!existsSync(srcGsd)) return; + + // Copy milestones/ directory (planning files, roadmaps, plans, research) + const srcMilestones = join(srcGsd, "milestones"); + if (existsSync(srcMilestones)) { + try { + cpSync(srcMilestones, join(dstGsd, "milestones"), { recursive: true, force: true }); + } catch { /* non-fatal */ } + } + + // Copy top-level planning files + for (const file of ["DECISIONS.md", "REQUIREMENTS.md", "PROJECT.md", "QUEUE.md"]) { + const src = join(srcGsd, file); + if (existsSync(src)) { + try { + cpSync(src, join(dstGsd, file), { force: true }); + } catch { /* non-fatal */ } + } + } +} + /** * Teardown an auto-worktree: chdir back to original base, then remove * the worktree and its branch.