diff --git a/src/resources/extensions/gsd/auto-worktree.ts b/src/resources/extensions/gsd/auto-worktree.ts index 7dd510d60..e45ae0544 100644 --- a/src/resources/extensions/gsd/auto-worktree.ts +++ b/src/resources/extensions/gsd/auto-worktree.ts @@ -54,7 +54,9 @@ export function shouldUseWorktreeIsolation(basePath: string, overridePrefs?: { i // Legacy detection: check for existing gsd/*/* branches (branch-per-slice pattern) try { - const output = execSync("git branch --list 'gsd/*/*'", { + // Use unquoted glob pattern — single quotes are not interpreted by cmd.exe on Windows, + // causing the pattern to match literally instead of as a glob. + const output = execSync("git branch --list gsd/*/*", { cwd: basePath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8", diff --git a/src/resources/extensions/gsd/doctor.ts b/src/resources/extensions/gsd/doctor.ts index 80bf451f1..16cfd4a58 100644 --- a/src/resources/extensions/gsd/doctor.ts +++ b/src/resources/extensions/gsd/doctor.ts @@ -1,6 +1,6 @@ import { execSync } from "node:child_process"; import { existsSync, mkdirSync } from "node:fs"; -import { join } from "node:path"; +import { join, sep } from "node:path"; import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js"; import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js"; @@ -511,7 +511,7 @@ async function checkGitHealth( if (shouldFix("orphaned_auto_worktree")) { // Never remove a worktree matching current working directory const cwd = process.cwd(); - if (wt.path === cwd || cwd.startsWith(wt.path + "/")) { + if (wt.path === cwd || cwd.startsWith(wt.path + sep)) { fixesApplied.push(`skipped removing worktree at ${wt.path} (is cwd)`); } else { try { @@ -527,7 +527,9 @@ async function checkGitHealth( // ── Stale milestone branches ───────────────────────────────────────── try { - const branchOutput = execSync("git branch --list 'milestone/*'", { cwd: basePath, stdio: "pipe" }).toString().trim(); + // Use unquoted glob — single quotes are not interpreted by cmd.exe on Windows, + // causing the pattern to match literally instead of as a glob. + const branchOutput = execSync("git branch --list milestone/*", { cwd: basePath, stdio: "pipe" }).toString().trim(); if (branchOutput) { const branches = branchOutput.split("\n").map(b => b.trim().replace(/^\*\s*/, "")).filter(Boolean); const worktreeBranches = new Set(milestoneWorktrees.map(wt => wt.branch)); diff --git a/src/resources/extensions/gsd/worktree-manager.ts b/src/resources/extensions/gsd/worktree-manager.ts index 6b08a52bb..6696b7cf8 100644 --- a/src/resources/extensions/gsd/worktree-manager.ts +++ b/src/resources/extensions/gsd/worktree-manager.ts @@ -17,7 +17,7 @@ import { existsSync, mkdirSync, realpathSync } from "node:fs"; import { execSync } from "node:child_process"; -import { join, resolve } from "node:path"; +import { join, resolve, sep } from "node:path"; // ─── Types ───────────────────────────────────────────────────────────────── @@ -213,7 +213,11 @@ export function listWorktrees(basePath: string): WorktreeInfo[] { const entryPath = wtLine.replace("worktree ", ""); const branch = branchLine.replace("branch refs/heads/", ""); - const branchWorktreeName = branch.startsWith("worktree/") ? branch.slice("worktree/".length) : null; + const branchWorktreeName = branch.startsWith("worktree/") + ? branch.slice("worktree/".length) + : branch.startsWith("milestone/") + ? branch.slice("milestone/".length) + : null; const entryVariants = [resolve(entryPath)]; if (existsSync(entryPath)) { entryVariants.push(realpathSync(entryPath)); @@ -272,7 +276,7 @@ export function removeWorktree( // If we're inside the worktree, move out first — git can't remove an in-use directory const cwd = process.cwd(); const resolvedCwd = existsSync(cwd) ? realpathSync(cwd) : cwd; - if (resolvedCwd === resolvedWtPath || resolvedCwd.startsWith(resolvedWtPath + "/")) { + if (resolvedCwd === resolvedWtPath || resolvedCwd.startsWith(resolvedWtPath + sep)) { process.chdir(basePath); }