From 0141ea21afa2a2239e0e3b71000ab04599165f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Wed, 18 Mar 2026 17:25:38 -0600 Subject: [PATCH] refactor: extract prompt builder helpers for inlined context and source file lists (#1279) --- src/resources/extensions/gsd/auto-prompts.ts | 63 ++++++++++---------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/src/resources/extensions/gsd/auto-prompts.ts b/src/resources/extensions/gsd/auto-prompts.ts index 808394906..8e9dc6d62 100644 --- a/src/resources/extensions/gsd/auto-prompts.ts +++ b/src/resources/extensions/gsd/auto-prompts.ts @@ -324,6 +324,27 @@ function oneLine(text: string): string { return text.replace(/\s+/g, " ").trim(); } +/** Build the standard inlined-context section used by all prompt builders. */ +function buildInlinedContextSection(inlined: string[]): string { + return `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; +} + +/** Build the formatted list of available GSD source files for planners to read on demand. */ +function buildSourceFileList(base: string, opts?: { includeProject?: boolean }): string { + const paths: string[] = []; + if (opts?.includeProject && existsSync(resolveGsdRootFile(base, "PROJECT"))) + paths.push(`- **Project**: \`${relGsdRootFile("PROJECT")}\``); + if (existsSync(resolveGsdRootFile(base, "REQUIREMENTS"))) + paths.push(`- **Requirements**: \`${relGsdRootFile("REQUIREMENTS")}\``); + if (existsSync(resolveGsdRootFile(base, "DECISIONS"))) + paths.push(`- **Decisions**: \`${relGsdRootFile("DECISIONS")}\``); + if (paths.length === 0) { + const types = opts?.includeProject ? "project/requirements/decisions" : "requirements/decisions"; + return `_No ${types} files found._`; + } + return paths.join("\n"); +} + // ─── Section Builders ────────────────────────────────────────────────────── export function buildResumeSection( @@ -567,7 +588,7 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string if (knowledgeInlineRM) inlined.push(knowledgeInlineRM); inlined.push(inlineTemplate("research", "Research")); - const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + const inlinedContext = buildInlinedContextSection(inlined); const outputRelPath = relMilestoneFile(base, mid, "RESEARCH"); return loadPrompt("research-milestone", { @@ -595,17 +616,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba const { inlinePriorMilestoneSummary } = await import("./files.js"); const priorSummaryInline = await inlinePriorMilestoneSummary(mid, base); if (priorSummaryInline) inlined.push(priorSummaryInline); - // Build source file paths for the planner to read on demand (reduces inlining) - const sourcePaths: string[] = []; - if (existsSync(resolveGsdRootFile(base, "PROJECT"))) - sourcePaths.push(`- **Project**: \`${relGsdRootFile("PROJECT")}\``); - if (existsSync(resolveGsdRootFile(base, "REQUIREMENTS"))) - sourcePaths.push(`- **Requirements**: \`${relGsdRootFile("REQUIREMENTS")}\``); - if (existsSync(resolveGsdRootFile(base, "DECISIONS"))) - sourcePaths.push(`- **Decisions**: \`${relGsdRootFile("DECISIONS")}\``); - const sourceFilePaths = sourcePaths.length > 0 - ? sourcePaths.join("\n") - : "_No project/requirements/decisions files found._"; + const sourceFilePaths = buildSourceFileList(base, { includeProject: true }); const knowledgeInlinePM = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge"); if (knowledgeInlinePM) inlined.push(knowledgeInlinePM); @@ -621,7 +632,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba inlined.push(inlineTemplate("task-plan", "Task Plan")); } - const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + const inlinedContext = buildInlinedContextSection(inlined); const outputRelPath = relMilestoneFile(base, mid, "ROADMAP"); const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS")); @@ -670,7 +681,7 @@ export async function buildResearchSlicePrompt( const overridesInline = formatOverridesSection(activeOverrides); if (overridesInline) inlined.unshift(overridesInline); - const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + const inlinedContext = buildInlinedContextSection(inlined); const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH"); return loadPrompt("research-slice", { @@ -700,15 +711,7 @@ export async function buildPlanSlicePrompt( inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap")); const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research"); if (researchInline) inlined.push(researchInline); - // Build source file paths for the planner to read on demand (reduces inlining) - const sliceSourcePaths: string[] = []; - if (existsSync(resolveGsdRootFile(base, "REQUIREMENTS"))) - sliceSourcePaths.push(`- **Requirements**: \`${relGsdRootFile("REQUIREMENTS")}\``); - if (existsSync(resolveGsdRootFile(base, "DECISIONS"))) - sliceSourcePaths.push(`- **Decisions**: \`${relGsdRootFile("DECISIONS")}\``); - const sliceSourceFilePaths = sliceSourcePaths.length > 0 - ? sliceSourcePaths.join("\n") - : "_No requirements/decisions files found._"; + const sliceSourceFilePaths = buildSourceFileList(base); const knowledgeInlinePS = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge"); if (knowledgeInlinePS) inlined.push(knowledgeInlinePS); @@ -722,7 +725,7 @@ export async function buildPlanSlicePrompt( const planOverridesInline = formatOverridesSection(planActiveOverrides); if (planOverridesInline) inlined.unshift(planOverridesInline); - const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + const inlinedContext = buildInlinedContextSection(inlined); // Build executor context constraints from the budget engine const executorContextConstraints = formatExecutorConstraints(); @@ -897,7 +900,7 @@ export async function buildCompleteSlicePrompt( const completeOverridesInline = formatOverridesSection(completeActiveOverrides); if (completeOverridesInline) inlined.unshift(completeOverridesInline); - const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + const inlinedContext = buildInlinedContextSection(inlined); const sliceRel = relSlicePath(base, mid, sid); const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`); @@ -956,7 +959,7 @@ export async function buildCompleteMilestonePrompt( if (contextInline) inlined.push(contextInline); inlined.push(inlineTemplate("milestone-summary", "Milestone Summary")); - const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + const inlinedContext = buildInlinedContextSection(inlined); const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`); @@ -1027,7 +1030,7 @@ export async function buildValidateMilestonePrompt( const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context"); if (contextInline) inlined.push(contextInline); - const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + const inlinedContext = buildInlinedContextSection(inlined); const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`); const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`; @@ -1081,7 +1084,7 @@ export async function buildReplanSlicePrompt( const replanOverridesInline = formatOverridesSection(replanActiveOverrides); if (replanOverridesInline) inlined.unshift(replanOverridesInline); - const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + const inlinedContext = buildInlinedContextSection(inlined); const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`); @@ -1129,7 +1132,7 @@ export async function buildRunUatPrompt( const projectInline = await inlineProjectFromDb(base); if (projectInline) inlined.push(projectInline); - const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + const inlinedContext = buildInlinedContextSection(inlined); const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT")); @@ -1166,7 +1169,7 @@ export async function buildReassessRoadmapPrompt( const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge"); if (knowledgeInlineRA) inlined.push(knowledgeInlineRA); - const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + const inlinedContext = buildInlinedContextSection(inlined); const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));