From 27cfababdbe492e752e0dd755646fab7f9daad17 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 16 Mar 2026 08:35:28 -0400 Subject: [PATCH] fix: worktree created from integration branch, not main (#606) (#607) --- src/resources/extensions/gsd/auto-worktree.ts | 15 +++++++++++---- src/resources/extensions/gsd/worktree-manager.ts | 13 ++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/resources/extensions/gsd/auto-worktree.ts b/src/resources/extensions/gsd/auto-worktree.ts index 1b0494b3b..0bb65ae67 100644 --- a/src/resources/extensions/gsd/auto-worktree.ts +++ b/src/resources/extensions/gsd/auto-worktree.ts @@ -17,6 +17,7 @@ import { import { detectWorktreeName } from "./worktree.js"; import { MergeConflictError, + readIntegrationBranch, } from "./git-service.js"; import { parseRoadmap } from "./files.js"; import { loadEffectiveGSDPreferences } from "./preferences.js"; @@ -91,7 +92,12 @@ export function autoWorktreeBranch(milestoneId: string): string { */ export function createAutoWorktree(basePath: string, milestoneId: string): string { const branch = autoWorktreeBranch(milestoneId); - const info = createWorktree(basePath, milestoneId, { branch }); + + // Use the integration branch recorded in META.json as the start point. + // This ensures the worktree branch is created from the branch the user + // was on when they started the milestone (e.g. f-setup-gsd-2), not main. + const integrationBranch = readIntegrationBranch(basePath, milestoneId) ?? undefined; + const info = createWorktree(basePath, milestoneId, { branch, startPoint: integrationBranch }); // Copy .gsd/ planning artifacts from the source repo into the new worktree. // Worktrees are fresh git checkouts — untracked files don't carry over. @@ -301,11 +307,12 @@ export function mergeMilestoneToMain( const previousCwd = process.cwd(); process.chdir(originalBasePath_); - // 4. Resolve main branch from preferences + // 4. Resolve integration branch — prefer milestone metadata, fall back to preferences / "main" const prefs = loadEffectiveGSDPreferences()?.preferences?.git ?? {}; - const mainBranch = prefs.main_branch || "main"; + const integrationBranch = readIntegrationBranch(originalBasePath_, milestoneId); + const mainBranch = integrationBranch ?? prefs.main_branch ?? "main"; - // 5. Checkout main + // 5. Checkout integration branch nativeCheckoutBranch(originalBasePath_, mainBranch); // 6. Build rich commit message diff --git a/src/resources/extensions/gsd/worktree-manager.ts b/src/resources/extensions/gsd/worktree-manager.ts index 07979b8ad..99fbf003e 100644 --- a/src/resources/extensions/gsd/worktree-manager.ts +++ b/src/resources/extensions/gsd/worktree-manager.ts @@ -94,7 +94,7 @@ export function worktreeBranchName(name: string): string { * * @param opts.branch — override the default `worktree/` branch name */ -export function createWorktree(basePath: string, name: string, opts: { branch?: string } = {}): WorktreeInfo { +export function createWorktree(basePath: string, name: string, opts: { branch?: string; startPoint?: string } = {}): WorktreeInfo { // Validate name: alphanumeric, hyphens, underscores only if (!/^[a-zA-Z0-9_-]+$/.test(name)) { throw new Error(`Invalid worktree name "${name}". Use only letters, numbers, hyphens, and underscores.`); @@ -114,9 +114,12 @@ export function createWorktree(basePath: string, name: string, opts: { branch?: // Prune any stale worktree entries from a previous removal nativeWorktreePrune(basePath); + // Use the explicit start point (e.g. integration branch) if provided, + // otherwise fall back to the repo's detected main branch. + const startPoint = opts.startPoint ?? nativeDetectMainBranch(basePath); + // Check if the branch already exists (leftover from a previous worktree) const branchAlreadyExists = nativeBranchExists(basePath, branch); - const mainBranch = nativeDetectMainBranch(basePath); if (branchAlreadyExists) { // Check if the branch is actively used by an existing worktree. @@ -130,11 +133,11 @@ export function createWorktree(basePath: string, name: string, opts: { branch?: ); } - // Reset the stale branch to current main, then attach worktree to it - nativeBranchForceReset(basePath, branch, mainBranch); + // Reset the stale branch to the start point, then attach worktree to it + nativeBranchForceReset(basePath, branch, startPoint); nativeWorktreeAdd(basePath, wtPath, branch); } else { - nativeWorktreeAdd(basePath, wtPath, branch, true, mainBranch); + nativeWorktreeAdd(basePath, wtPath, branch, true, startPoint); } return {