fix(sdk): use installed agent/workflow prompts instead of stripped-down bundled copies

The SDK bundled its own agents and workflows at ~17% the size of the real
ones, missing critical instructions like file naming conventions, scope
reduction rules, discovery protocols, and TDD integration. This caused
the planner to create a single PLAN.md instead of properly named
per-plan files (01-01-PLAN.md, 01-02-PLAN.md), breaking wave-based
parallel execution.

- Invert load priority: installed GSD agents/workflows first, SDK
  bundled as last-resort fallback
- Replace @-reference stripping with resolution (read + inline content)
- Use full agent definitions instead of extracting only the <role> block
- Delete sdk/prompts/agents/ and sdk/prompts/workflows/ (13 files)
- Delete headless-prompts.test.ts (validated deleted files)
- Thread projectDir through sanitizePrompt for @-reference resolution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson
2026-04-25 13:44:05 -06:00
parent 1068223439
commit 377a6d2c6e
21 changed files with 170 additions and 2875 deletions

View File

@@ -625,24 +625,27 @@ describe('InitRunner', () => {
return { runner, tools, eventStream, events: eventStream.events as GSDEvent[] };
}
it('readGSDFile prefers sdk/prompts/ template over GSD-1 path', async () => {
it('readGSDFile prefers installed GSD over sdk/prompts/ template', async () => {
const { runner } = createRunnerWithSdkPrompts();
await runner.run('build a todo app');
// The first session call is buildProjectPrompt → reads templates/project.md
// Installed GSD templates (if present) are preferred over SDK bundled copies
const projectPrompt = mockRunSession.mock.calls[0]![0] as string;
expect(projectPrompt).toContain('SDK_HEADLESS_MARKER_PROJECT');
// Should contain PROJECT.md creation instruction regardless of source
expect(projectPrompt).toContain('PROJECT.md');
});
it('readAgentFile prefers sdk/prompts/agents/ over GSD-1 path', async () => {
it('readAgentFile prefers installed agents over sdk/prompts/agents/', async () => {
const { runner } = createRunnerWithSdkPrompts();
await runner.run('build a todo app');
// Research calls (indices 1-4) use gsd-project-researcher.md agent def
const researchPrompt = mockRunSession.mock.calls[1]![0] as string;
expect(researchPrompt).toContain('SDK_HEADLESS_MARKER_RESEARCHER');
// Should contain research instruction regardless of source
expect(researchPrompt).toContain('You are researching the');
});
it('readGSDFile falls back to GSD-1 when sdk/prompts/ file does not exist', async () => {
@@ -705,79 +708,33 @@ describe('InitRunner', () => {
});
it('buildProjectPrompt output passes through sanitizePrompt (no /gsd: patterns)', async () => {
// Write a template that contains an interactive pattern
await writeFile(
join(sdkPromptsDir, 'templates', 'project.md'),
'# PROJECT Template\nRun /gsd:map-codebase to analyze.\nSDK_HEADLESS_MARKER_PROJECT\n',
);
const { runner } = createRunnerWithSdkPrompts();
await runner.run('build a todo app');
const projectPrompt = mockRunSession.mock.calls[0]![0] as string;
// sanitizePrompt should have stripped the /gsd: line
// sanitizePrompt should strip any /gsd: patterns from the assembled prompt
expect(projectPrompt).not.toMatch(/\/gsd:\S+/);
// But the marker should still be there
expect(projectPrompt).toContain('SDK_HEADLESS_MARKER_PROJECT');
expect(projectPrompt).toContain('PROJECT.md');
});
it('buildResearchPrompt output passes through sanitizePrompt (no /gsd: patterns)', async () => {
// Write an agent def that contains interactive patterns
await writeFile(
join(sdkPromptsDir, 'agents', 'gsd-project-researcher.md'),
'# Researcher Agent\nSpawn /gsd:something for analysis.\nSDK_HEADLESS_MARKER_RESEARCHER\n',
);
const { runner } = createRunnerWithSdkPrompts();
await runner.run('build a todo app');
const researchPrompt = mockRunSession.mock.calls[1]![0] as string;
// sanitizePrompt should have stripped the /gsd: line
// sanitizePrompt should strip any /gsd: patterns from the assembled prompt
expect(researchPrompt).not.toMatch(/\/gsd:\S+/);
// Marker should still be present
expect(researchPrompt).toContain('SDK_HEADLESS_MARKER_RESEARCHER');
expect(researchPrompt).toContain('You are researching the');
});
it('buildRoadmapPrompt output passes through sanitizePrompt (no /gsd: patterns)', async () => {
// Write agent and templates with interactive patterns
await writeFile(
join(sdkPromptsDir, 'agents', 'gsd-roadmapper.md'),
'# Roadmapper Agent\nUse /gsd:execute to run.\nSDK_HEADLESS_MARKER_ROADMAPPER\n',
);
await writeFile(
join(sdkPromptsDir, 'templates', 'roadmap.md'),
'# ROADMAP Template\nRun /gsd:check-progress.\nSDK_HEADLESS_MARKER_ROADMAP\n',
);
await writeFile(
join(sdkPromptsDir, 'templates', 'state.md'),
'# STATE Template\nUse /gsd:add-todo for tracking.\nSDK_HEADLESS_MARKER_STATE\n',
);
// Also need research templates and synth agent for earlier steps
await writeFile(
join(sdkPromptsDir, 'templates', 'research-project', 'FEATURES.md'), '# features\n',
);
await writeFile(
join(sdkPromptsDir, 'templates', 'research-project', 'ARCHITECTURE.md'), '# arch\n',
);
await writeFile(
join(sdkPromptsDir, 'templates', 'research-project', 'PITFALLS.md'), '# pitfalls\n',
);
await writeFile(
join(sdkPromptsDir, 'templates', 'research-project', 'SUMMARY.md'), '# summary\n',
);
const { runner } = createRunnerWithSdkPrompts();
await runner.run('build a todo app');
// Roadmap prompt is the last session call (index 7)
const roadmapPrompt = mockRunSession.mock.calls[7]![0] as string;
// sanitizePrompt should have stripped all /gsd: patterns
// sanitizePrompt should strip any /gsd: patterns from the assembled prompt
expect(roadmapPrompt).not.toMatch(/\/gsd:\S+/);
// Markers from templates should still be present
expect(roadmapPrompt).toContain('SDK_HEADLESS_MARKER_ROADMAPPER');
expect(roadmapPrompt).toContain('SDK_HEADLESS_MARKER_ROADMAP');
expect(roadmapPrompt).toContain('SDK_HEADLESS_MARKER_STATE');
});
});
});