From 88bdf9bc8d0e7f9bc50799d9c5155c035a463d57 Mon Sep 17 00:00:00 2001 From: Juan Francisco Lebrero <101231690+frizynn@users.noreply.github.com> Date: Mon, 16 Mar 2026 11:18:53 -0300 Subject: [PATCH] fix: use absolute paths for write-target variables in auto-mode prompts (#627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In worktree contexts, the LLM received relative output paths like `.gsd/milestones/M002/slices/S01/S01-RESEARCH.md` combined with a working directory containing `.gsd/worktrees/M002`. The double .gsd in the resulting path confused the LLM, which resolved the relative path against the project root instead of the worktree — writing artifacts to the wrong location and triggering loop detection. All write-target path variables (outputPath, taskSummaryPath, sliceSummaryPath, milestoneSummaryPath, replanPath, planPath, uatResultPath, assessmentPath, secretsOutputPath) are now passed as absolute paths via join(base, relPath), eliminating the need for the LLM to do path arithmetic in confusing worktree layouts. --- src/resources/extensions/gsd/auto-prompts.ts | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/resources/extensions/gsd/auto-prompts.ts b/src/resources/extensions/gsd/auto-prompts.ts index 7baa56541..4c415b418 100644 --- a/src/resources/extensions/gsd/auto-prompts.ts +++ b/src/resources/extensions/gsd/auto-prompts.ts @@ -389,7 +389,7 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string milestoneId: mid, milestoneTitle: midTitle, milestonePath: relMilestonePath(base, mid), contextPath: contextRel, - outputPath: outputRelPath, + outputPath: join(base, outputRelPath), inlinedContext, ...buildSkillDiscoveryVars(), }); @@ -432,14 +432,14 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; const outputRelPath = relMilestoneFile(base, mid, "ROADMAP"); - const secretsOutputPath = relMilestoneFile(base, mid, "SECRETS"); + const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS")); return loadPrompt("plan-milestone", { workingDirectory: base, milestoneId: mid, milestoneTitle: midTitle, milestonePath: relMilestonePath(base, mid), contextPath: contextRel, researchPath: researchRel, - outputPath: outputRelPath, + outputPath: join(base, outputRelPath), secretsOutputPath, inlinedContext, }); @@ -484,7 +484,7 @@ export async function buildResearchSlicePrompt( roadmapPath: roadmapRel, contextPath: contextRel, milestoneResearchPath: milestoneResearchRel, - outputPath: outputRelPath, + outputPath: join(base, outputRelPath), inlinedContext, dependencySummaries: depContent, ...buildSkillDiscoveryVars(), @@ -531,7 +531,7 @@ export async function buildPlanSlicePrompt( slicePath: relSlicePath(base, mid, sid), roadmapPath: roadmapRel, researchPath: researchRel, - outputPath: outputRelPath, + outputPath: join(base, outputRelPath), inlinedContext, dependencySummaries: depContent, }); @@ -598,7 +598,7 @@ export async function buildExecuteTaskPrompt( ...(knowledgeInlineET ? [knowledgeInlineET] : []), ].join("\n\n---\n\n"); - const taskSummaryPath = `${relSlicePath(base, mid, sid)}/tasks/${tid}-SUMMARY.md`; + const taskSummaryPath = join(base, `${relSlicePath(base, mid, sid)}/tasks/${tid}-SUMMARY.md`); const activeOverrides = await loadActiveOverrides(base); const overridesSection = formatOverridesSection(activeOverrides); @@ -607,7 +607,7 @@ export async function buildExecuteTaskPrompt( overridesSection, workingDirectory: base, milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle, - planPath: relSliceFile(base, mid, sid, "PLAN"), + planPath: join(base, relSliceFile(base, mid, sid, "PLAN")), slicePath: relSlicePath(base, mid, sid), taskPlanPath: taskPlanRelPath, taskPlanInline, @@ -665,14 +665,14 @@ export async function buildCompleteSlicePrompt( const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; const sliceRel = relSlicePath(base, mid, sid); - const sliceSummaryPath = `${sliceRel}/${sid}-SUMMARY.md`; - const sliceUatPath = `${sliceRel}/${sid}-UAT.md`; + const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`); + const sliceUatPath = join(base, `${sliceRel}/${sid}-UAT.md`); return loadPrompt("complete-slice", { workingDirectory: base, milestoneId: mid, sliceId: sid, sliceTitle: sTitle, slicePath: sliceRel, - roadmapPath: roadmapRel, + roadmapPath: join(base, roadmapRel), inlinedContext, sliceSummaryPath, sliceUatPath, @@ -723,7 +723,7 @@ export async function buildCompleteMilestonePrompt( const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; - const milestoneSummaryPath = `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`; + const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`); return loadPrompt("complete-milestone", { workingDirectory: base, @@ -775,7 +775,7 @@ export async function buildReplanSlicePrompt( const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; - const replanPath = `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`; + const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`); // Build capture context for replan prompt (captures that triggered this replan) let captureContext = "(none)"; @@ -797,7 +797,7 @@ export async function buildReplanSlicePrompt( sliceId: sid, sliceTitle: sTitle, slicePath: relSlicePath(base, mid, sid), - planPath: slicePlanRel, + planPath: join(base, slicePlanRel), blockerTaskId, inlinedContext, replanPath, @@ -823,7 +823,7 @@ export async function buildRunUatPrompt( const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; - const uatResultPath = relSliceFile(base, mid, sliceId, "UAT-RESULT"); + const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT")); const uatType = extractUatType(uatContent) ?? "human-experience"; return loadPrompt("run-uat", { @@ -862,7 +862,7 @@ export async function buildReassessRoadmapPrompt( const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; - const assessmentPath = relSliceFile(base, mid, completedSliceId, "ASSESSMENT"); + const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT")); // Build deferred captures context for reassess prompt let deferredCaptures = "(none)";