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:
parent
261e2a6d5f
commit
01a1295e4d
2 changed files with 71 additions and 0 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue