Files
msd-core/sdk/src/init-e2e.integration.test.ts
TÂCHES 38c18ac68a feat: Headless prompt overhaul — SDK drives full lifecycle without interactive patterns (#1419)
* fix: Created 10 headless prompt files (5 workflows + 5 agents) in sdk/p…

- "sdk/prompts/workflows/execute-plan.md"
- "sdk/prompts/workflows/research-phase.md"
- "sdk/prompts/workflows/plan-phase.md"
- "sdk/prompts/workflows/verify-phase.md"
- "sdk/prompts/workflows/discuss-phase.md"
- "sdk/prompts/agents/gsd-executor.md"
- "sdk/prompts/agents/gsd-phase-researcher.md"
- "sdk/prompts/agents/gsd-planner.md"

GSD-Task: S01/T02

* feat: Created prompt-sanitizer.ts, wired headless prompt loading into P…

- "sdk/src/prompt-sanitizer.ts"
- "sdk/src/phase-prompt.ts"
- "sdk/src/gsd-tools.ts"
- "sdk/src/gsd-tools.test.ts"
- "sdk/src/phase-runner-types.test.ts"

GSD-Task: S01/T01

* test: Added 111 unit tests covering sanitizePrompt(), headless prompt l…

- "sdk/src/prompt-sanitizer.test.ts"
- "sdk/src/headless-prompts.test.ts"
- "sdk/src/phase-prompt.test.ts"

GSD-Task: S01/T03

* feat: Wired sdkPromptsDir preference and sanitizePrompt into InitRunner…

- "sdk/src/init-runner.ts"
- "sdk/package.json"

GSD-Task: S02/T01

* feat: add --init flag to auto command for single-command PRD-to-execution

gsd-sdk auto --init @path/to/prd.md now bootstraps the project (init)
then immediately runs the autonomous phase execution loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: add remaining headless prompt files and templates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: Extended init-runner.test.ts with 7 sdkPromptsDir preference and…

- "sdk/src/init-runner.test.ts"

GSD-Task: S02/T03

* test: Created sdk/src/assembled-prompts.test.ts with 74 tests verifying…

- "sdk/src/assembled-prompts.test.ts"

GSD-Task: S03/T01

* chore: auto-commit after complete-milestone

GSD-Unit: M003-75c8bo

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 01:00:04 -06:00

136 lines
5.2 KiB
TypeScript

/**
* E2E integration test — proves InitRunner.run() drives real Agent SDK
* sessions for the gsd-sdk init workflow.
*
* Requires Claude Code CLI (`claude`) installed and authenticated.
* Skips gracefully if CLI is unavailable.
*
* This test proves the headless init pipeline can bootstrap a real project
* without human intervention: setup → config → PROJECT.md → research →
* synthesis → requirements → roadmap.
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { execSync } from 'node:child_process';
import { mkdtemp, rm, readFile, stat } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import { homedir } from 'node:os';
import { InitRunner } from './init-runner.js';
import { GSDTools } from './gsd-tools.js';
import { GSDEventStream } from './event-stream.js';
import { GSDEventType } from './types.js';
import type { GSDEvent } from './types.js';
// ─── CLI availability check ─────────────────────────────────────────────────
let cliAvailable = false;
try {
execSync('which claude', { stdio: 'ignore' });
cliAvailable = true;
} catch {
cliAvailable = false;
}
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const sdkPromptsDir = join(__dirname, '..', 'prompts');
const GSD_TOOLS_PATH = join(homedir(), '.claude', 'get-shit-done', 'bin', 'gsd-tools.cjs');
// ─── Test suite ──────────────────────────────────────────────────────────────
describe.skipIf(!cliAvailable)('E2E: InitRunner.run() full workflow', () => {
let tmpDir: string;
let events: GSDEvent[];
beforeAll(async () => {
tmpDir = await mkdtemp(join(tmpdir(), 'gsd-sdk-init-e2e-'));
// Initialize git in the temp dir (required by InitRunner)
execSync('git init', { cwd: tmpDir, stdio: 'ignore' });
execSync('git config user.email "test@test.com"', { cwd: tmpDir, stdio: 'ignore' });
execSync('git config user.name "Test"', { cwd: tmpDir, stdio: 'ignore' });
}, 30_000);
afterAll(async () => {
if (tmpDir) {
await rm(tmpDir, { recursive: true, force: true });
}
});
it('InitRunner.run() bootstraps a project without human intervention', async () => {
events = [];
const eventStream = new GSDEventStream();
eventStream.on('event', (e: GSDEvent) => events.push(e));
const tools = new GSDTools({
projectDir: tmpDir,
gsdToolsPath: GSD_TOOLS_PATH,
timeoutMs: 30_000,
});
const runner = new InitRunner({
projectDir: tmpDir,
tools,
eventStream,
config: {
maxBudgetPerSession: 1.0,
maxTurnsPerSession: 15,
},
sdkPromptsDir,
});
const result = await runner.run('Build a CLI tool that prints hello world');
// ── Assert: pipeline executed (success OR at least 3+ steps completed) ──
const completedSteps = result.steps.filter(s => s.success);
const pipelineProgressed = result.success || completedSteps.length >= 3;
expect(pipelineProgressed).toBe(true);
// ── Assert: config.json artifact created ──
// config.json is written directly by InitRunner (not by Claude session)
// so it should always exist if the config step succeeded
const configStep = result.steps.find(s => s.step === 'config');
if (configStep?.success) {
const configPath = join(tmpDir, '.planning', 'config.json');
const configStat = await stat(configPath).catch(() => null);
expect(configStat).not.toBeNull();
if (configStat) {
const configContent = JSON.parse(await readFile(configPath, 'utf-8'));
expect(configContent.workflow.auto_advance).toBe(true);
}
}
// ── Assert: PROJECT.md created if project step succeeded ──
const projectStep = result.steps.find(s => s.step === 'project');
if (projectStep?.success) {
const projectPath = join(tmpDir, '.planning', 'PROJECT.md');
const projectStat = await stat(projectPath).catch(() => null);
expect(projectStat).not.toBeNull();
}
// ── Assert: events captured include InitStart and at least one InitStepComplete ──
const initStartEvents = events.filter(e => e.type === GSDEventType.InitStart);
expect(initStartEvents.length).toBe(1);
const stepCompleteEvents = events.filter(e => e.type === GSDEventType.InitStepComplete);
expect(stepCompleteEvents.length).toBeGreaterThanOrEqual(1);
// ── Assert: InitComplete event emitted ──
const initCompleteEvents = events.filter(e => e.type === GSDEventType.InitComplete);
expect(initCompleteEvents.length).toBe(1);
// ── Assert: cost and duration are tracked ──
expect(result.totalDurationMs).toBeGreaterThan(0);
expect(typeof result.totalCostUsd).toBe('number');
// ── Assert: artifacts list is populated ──
if (result.success) {
expect(result.artifacts.length).toBeGreaterThan(0);
expect(result.artifacts).toContain('.planning/config.json');
}
}, 600_000); // 10 minute timeout for the full 7-session init workflow
});