diff --git a/src/resources/extensions/gsd/auto-prompts.ts b/src/resources/extensions/gsd/auto-prompts.ts index 97ce5cf3f..92ad153ec 100644 --- a/src/resources/extensions/gsd/auto-prompts.ts +++ b/src/resources/extensions/gsd/auto-prompts.ts @@ -994,10 +994,15 @@ export async function buildResearchSlicePrompt( const milestoneResearchPath = resolveMilestoneFile(base, mid, "RESEARCH"); const milestoneResearchRel = relMilestoneFile(base, mid, "RESEARCH"); + const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT"); + const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT"); + const inlined: string[] = []; inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap")); const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context"); if (contextInline) inlined.push(contextInline); + const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)"); + if (sliceCtxInline) inlined.push(sliceCtxInline); const researchInline = await inlineFileOptional(milestoneResearchPath, milestoneResearchRel, "Milestone Research"); if (researchInline) inlined.push(researchInline); const decisionsInline = await inlineDecisionsFromDb(base, mid); @@ -1045,6 +1050,8 @@ export async function buildPlanSlicePrompt( const roadmapRel = relMilestoneFile(base, mid, "ROADMAP"); const researchPath = resolveSliceFile(base, mid, sid, "RESEARCH"); const researchRel = relSliceFile(base, mid, sid, "RESEARCH"); + const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT"); + const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT"); const inlined: string[] = []; @@ -1053,6 +1060,8 @@ export async function buildPlanSlicePrompt( if (researchSliceAnchor) inlined.push(formatAnchorForPrompt(researchSliceAnchor)); inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap")); + const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)"); + if (sliceCtxInline) inlined.push(sliceCtxInline); const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research"); if (researchInline) inlined.push(researchInline); if (inlineLevel !== "minimal") { @@ -1253,9 +1262,13 @@ export async function buildCompleteSlicePrompt( const roadmapRel = relMilestoneFile(base, mid, "ROADMAP"); const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN"); const slicePlanRel = relSliceFile(base, mid, sid, "PLAN"); + const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT"); + const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT"); const inlined: string[] = []; inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap")); + const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)"); + if (sliceCtxInline) inlined.push(sliceCtxInline); inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Slice Plan")); if (inlineLevel !== "minimal") { const requirementsInline = await inlineRequirementsFromDb(base, sid, inlineLevel); @@ -1510,9 +1523,13 @@ export async function buildReplanSlicePrompt( const roadmapRel = relMilestoneFile(base, mid, "ROADMAP"); const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN"); const slicePlanRel = relSliceFile(base, mid, sid, "PLAN"); + const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT"); + const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT"); const inlined: string[] = []; inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap")); + const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)"); + if (sliceCtxInline) inlined.push(sliceCtxInline); inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Current Slice Plan")); // Find the blocker task summary — the completed task with blocker_discovered: true @@ -1627,9 +1644,13 @@ export async function buildReassessRoadmapPrompt( const roadmapRel = relMilestoneFile(base, mid, "ROADMAP"); const summaryPath = resolveSliceFile(base, mid, completedSliceId, "SUMMARY"); const summaryRel = relSliceFile(base, mid, completedSliceId, "SUMMARY"); + const sliceContextPath = resolveSliceFile(base, mid, completedSliceId, "CONTEXT"); + const sliceContextRel = relSliceFile(base, mid, completedSliceId, "CONTEXT"); const inlined: string[] = []; inlined.push(await inlineFile(roadmapPath, roadmapRel, "Current Roadmap")); + const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)"); + if (sliceCtxInline) inlined.push(sliceCtxInline); inlined.push(await inlineFile(summaryPath, summaryRel, `${completedSliceId} Summary`)); if (inlineLevel !== "minimal") { const projectInline = await inlineProjectFromDb(base); diff --git a/src/resources/extensions/gsd/tests/slice-context-injection.test.ts b/src/resources/extensions/gsd/tests/slice-context-injection.test.ts new file mode 100644 index 000000000..bb7cd2005 --- /dev/null +++ b/src/resources/extensions/gsd/tests/slice-context-injection.test.ts @@ -0,0 +1,50 @@ +/** + * Regression test: S##-CONTEXT.md from slice discussion must be + * injected into all 5 downstream prompt builders (#3452). + * + * Scans auto-prompts.ts for the 5 builder functions and verifies + * each one resolves and inlines the slice-level CONTEXT file. + */ + +import { describe, test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const autoPromptsPath = join(__dirname, "..", "auto-prompts.ts"); +const source = readFileSync(autoPromptsPath, "utf-8"); + +const BUILDERS = [ + "buildResearchSlicePrompt", + "buildPlanSlicePrompt", + "buildCompleteSlicePrompt", + "buildReplanSlicePrompt", + "buildReassessRoadmapPrompt", +]; + +describe("slice CONTEXT.md injection into prompt builders (#3452)", () => { + for (const builder of BUILDERS) { + test(`${builder} resolves slice CONTEXT file`, () => { + // Find the function body + const fnStart = source.indexOf(`export async function ${builder}`); + assert.ok(fnStart !== -1, `${builder} should exist in auto-prompts.ts`); + + // Get a reasonable chunk after the function start (enough to cover the inlining section) + const chunk = source.slice(fnStart, fnStart + 3000); + + // Must resolve the slice CONTEXT path + assert.ok( + chunk.includes('resolveSliceFile(base, mid,') && chunk.includes('"CONTEXT"'), + `${builder} should call resolveSliceFile with "CONTEXT"`, + ); + + // Must inline it with inlineFileOptional + assert.ok( + chunk.includes('Slice Context'), + `${builder} should inline slice CONTEXT with a "Slice Context" label`, + ); + }); + } +});