fix(sdk): pass phaseDir to executor prompt so SUMMARY.md lands in .planning/
The SDK's buildExecutorPrompt told executors to "Create a SUMMARY.md file"
with no directory path, causing them to write it in cwd (project root)
instead of .planning/phases/{phase}/. Thread phaseDir from PhaseRunner
through PromptFactory and into the completion instructions so the executor
gets an explicit path like `.planning/phases/01-auth/01-01-SUMMARY.md`.
Backward compatible — buildExecutorPrompt still accepts a plain string
(agentDef) for existing callers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -296,6 +296,7 @@ export type { GSDConfig } from './config.js';
|
||||
export { GSDTools, GSDToolsError, resolveGsdToolsPath } from './gsd-tools.js';
|
||||
export { runPlanSession, runPhaseStepSession } from './session-runner.js';
|
||||
export { buildExecutorPrompt, parseAgentTools } from './prompt-builder.js';
|
||||
export type { ExecutorPromptOptions } from './prompt-builder.js';
|
||||
export * from './types.js';
|
||||
|
||||
// S02: Event stream, context, prompt, and logging modules
|
||||
|
||||
@@ -110,11 +110,12 @@ export class PromptFactory {
|
||||
phaseType: PhaseType,
|
||||
plan: ParsedPlan | null,
|
||||
contextFiles: ContextFiles,
|
||||
phaseDir?: string,
|
||||
): Promise<string> {
|
||||
// Execute phase with a plan: delegate to existing buildExecutorPrompt
|
||||
if (phaseType === PhaseType.Execute && plan) {
|
||||
const agentDef = await this.loadAgentDef(phaseType);
|
||||
return sanitizePrompt(buildExecutorPrompt(plan, agentDef), this.projectDir);
|
||||
return sanitizePrompt(buildExecutorPrompt(plan, { agentDef, phaseDir }), this.projectDir);
|
||||
}
|
||||
|
||||
// Prompt assembly order is cache-optimized (#1614):
|
||||
|
||||
@@ -790,7 +790,7 @@ export class PhaseRunner {
|
||||
|
||||
const phaseType = PhaseType.Execute;
|
||||
const contextFiles = await this.contextEngine.resolveContextFiles(phaseType);
|
||||
const prompt = await this.promptFactory.buildPrompt(phaseType, parsedPlan, contextFiles);
|
||||
const prompt = await this.promptFactory.buildPrompt(phaseType, parsedPlan, contextFiles, phaseOp.phase_dir);
|
||||
|
||||
return await runPhaseStepSession(
|
||||
prompt,
|
||||
|
||||
@@ -156,11 +156,23 @@ describe('buildExecutorPrompt', () => {
|
||||
expect(prompt).toContain('Refresh tokens rotate correctly');
|
||||
});
|
||||
|
||||
it('includes SUMMARY.md creation instruction', () => {
|
||||
it('includes SUMMARY.md creation instruction with derived filename', () => {
|
||||
const plan = makePlan();
|
||||
const prompt = buildExecutorPrompt(plan);
|
||||
expect(prompt).toContain('01-01-SUMMARY.md');
|
||||
});
|
||||
|
||||
it('includes phaseDir in SUMMARY path when provided', () => {
|
||||
const plan = makePlan();
|
||||
const prompt = buildExecutorPrompt(plan, { phaseDir: '.planning/phases/01-auth' });
|
||||
expect(prompt).toContain('.planning/phases/01-auth/01-01-SUMMARY.md');
|
||||
});
|
||||
|
||||
it('uses bare SUMMARY.md when phase/plan numbers missing', () => {
|
||||
const plan = makePlan({ frontmatter: { phase: '', plan: '', type: 'execute', wave: 1, depends_on: [], files_modified: [], autonomous: true, requirements: [], must_haves: { truths: [], artifacts: [], key_links: [] } } });
|
||||
const prompt = buildExecutorPrompt(plan);
|
||||
expect(prompt).toContain('SUMMARY.md');
|
||||
expect(prompt).toContain('Create a SUMMARY.md file');
|
||||
expect(prompt).not.toContain('/SUMMARY.md');
|
||||
});
|
||||
|
||||
it('includes sequential execution instruction', () => {
|
||||
|
||||
@@ -87,6 +87,16 @@ function formatTask(task: PlanTask, index: number): string {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for buildExecutorPrompt beyond the required plan.
|
||||
*/
|
||||
export interface ExecutorPromptOptions {
|
||||
/** Raw content of gsd-executor.md agent definition. */
|
||||
agentDef?: string;
|
||||
/** Phase directory relative to project root (e.g. `.planning/phases/01-auth`). */
|
||||
phaseDir?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the executor prompt from a parsed plan and optional agent definition.
|
||||
*
|
||||
@@ -97,10 +107,14 @@ function formatTask(task: PlanTask, index: number): string {
|
||||
* 4. Produce a SUMMARY.md file on completion
|
||||
*
|
||||
* @param plan - Parsed plan structure from plan-parser
|
||||
* @param agentDef - Raw content of gsd-executor.md agent definition (optional)
|
||||
* @param agentDefOrOpts - Raw agent definition string (legacy) or ExecutorPromptOptions
|
||||
* @returns Assembled prompt string
|
||||
*/
|
||||
export function buildExecutorPrompt(plan: ParsedPlan, agentDef?: string): string {
|
||||
export function buildExecutorPrompt(plan: ParsedPlan, agentDefOrOpts?: string | ExecutorPromptOptions): string {
|
||||
const opts: ExecutorPromptOptions = typeof agentDefOrOpts === 'string'
|
||||
? { agentDef: agentDefOrOpts }
|
||||
: agentDefOrOpts ?? {};
|
||||
const { agentDef, phaseDir } = opts;
|
||||
const sections: string[] = [];
|
||||
|
||||
// ── Role instructions from agent definition ──
|
||||
@@ -173,11 +187,22 @@ export function buildExecutorPrompt(plan: ParsedPlan, agentDef?: string): string
|
||||
}
|
||||
|
||||
// ── Completion instructions ──
|
||||
// Derive the SUMMARY filename from plan frontmatter (e.g. "01-01-SUMMARY.md")
|
||||
// Phase may be "01-auth" or "01" — extract leading number, zero-pad to 2 digits.
|
||||
const phaseNum = (plan.frontmatter.phase || '').match(/^(\d+)/)?.[1] || '';
|
||||
const planNum = (plan.frontmatter.plan || '').match(/^(\d+)/)?.[1] || '';
|
||||
const summaryName = phaseNum && planNum
|
||||
? `${phaseNum.padStart(2, '0')}-${planNum.padStart(2, '0')}-SUMMARY.md`
|
||||
: 'SUMMARY.md';
|
||||
const summaryPath = phaseDir
|
||||
? `${phaseDir}/${summaryName}`
|
||||
: summaryName;
|
||||
|
||||
sections.push(
|
||||
`## Completion\n\n` +
|
||||
`After all tasks are complete:\n` +
|
||||
`1. Run any overall verification or success criteria checks\n` +
|
||||
`2. Create a SUMMARY.md file documenting:\n` +
|
||||
`2. Create \`${summaryPath}\` documenting:\n` +
|
||||
` - One-line summary of what was accomplished\n` +
|
||||
` - Tasks completed with commit hashes\n` +
|
||||
` - Any deviations from the plan\n` +
|
||||
|
||||
@@ -59,9 +59,10 @@ export async function runPlanSession(
|
||||
agentDef?: string,
|
||||
eventStream?: GSDEventStream,
|
||||
streamContext?: EventStreamContext,
|
||||
phaseDir?: string,
|
||||
): Promise<PlanResult> {
|
||||
// Build the executor prompt
|
||||
const executorPrompt = buildExecutorPrompt(plan, agentDef);
|
||||
const executorPrompt = buildExecutorPrompt(plan, { agentDef, phaseDir });
|
||||
|
||||
// Resolve allowed tools — from agent definition or defaults
|
||||
const allowedTools = options?.allowedTools ??
|
||||
|
||||
Reference in New Issue
Block a user