fix(gsd): inject S##-CONTEXT.md from slice discussion into all prompt builders

S##-CONTEXT.md files produced by /gsd discuss (require_slice_discussion)
are never injected into downstream prompt builders. Discussed
requirements, acceptance criteria, and design decisions are silently
dropped — the researcher, planner, completer, replanner, and
reassessor never see them.

Add resolveSliceFile(base, mid, sid, "CONTEXT") + inlineFileOptional()
to all 5 affected builders:

  1. buildResearchSlicePrompt
  2. buildPlanSlicePrompt
  3. buildCompleteSlicePrompt
  4. buildReplanSlicePrompt
  5. buildReassessRoadmapPrompt

The slice CONTEXT is placed immediately after the roadmap and before
other context (research, decisions, requirements) so the discussed
scope is visible before detailed planning artifacts.

Uses the existing inlineFileOptional() pattern — if no S##-CONTEXT.md
exists, nothing is injected (zero cost for projects not using slice
discussion).

Adds 5 regression tests verifying each builder resolves and inlines
the slice CONTEXT file.

Closes #3452
This commit is contained in:
Tibsfox 2026-04-05 05:41:14 -07:00
parent 261e2a6d5f
commit 01a1295e4d
2 changed files with 71 additions and 0 deletions

View file

@ -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);

View file

@ -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`,
);
});
}
});