* test: Bootstrapped sdk/ as TypeScript ESM package with full GSD-1 PLAN.… - "sdk/package.json" - "sdk/tsconfig.json" - "sdk/vitest.config.ts" - "sdk/src/types.ts" - "sdk/src/plan-parser.ts" - "sdk/src/plan-parser.test.ts" GSD-Task: S01/T01 * test: Implemented config reader and gsd-tools bridge with 25 unit tests… - "sdk/src/config.ts" - "sdk/src/config.test.ts" - "sdk/src/gsd-tools.ts" - "sdk/src/gsd-tools.test.ts" GSD-Task: S01/T02 * test: Built prompt-builder, session-runner, and GSD class — 85 total un… - "sdk/src/prompt-builder.ts" - "sdk/src/prompt-builder.test.ts" - "sdk/src/session-runner.ts" - "sdk/src/index.ts" - "sdk/src/types.ts" GSD-Task: S01/T03 * test: Created E2E integration test with fixtures proving full SDK pipel… - "sdk/src/e2e.integration.test.ts" - "sdk/test-fixtures/sample-plan.md" - "sdk/test-fixtures/.planning/config.json" - "sdk/test-fixtures/.planning/STATE.md" - "vitest.config.ts" - "tsconfig.json" GSD-Task: S01/T04 * test: Added PhaseType/GSDEventType enums, 16-variant GSDEvent union, GS… - "sdk/src/types.ts" - "sdk/src/event-stream.ts" - "sdk/src/logger.ts" - "sdk/src/event-stream.test.ts" - "sdk/src/logger.test.ts" GSD-Task: S02/T01 * test: Built ContextEngine for phase-aware context file resolution, getT… - "sdk/src/context-engine.ts" - "sdk/src/tool-scoping.ts" - "sdk/src/phase-prompt.ts" - "sdk/src/context-engine.test.ts" - "sdk/src/tool-scoping.test.ts" - "sdk/src/phase-prompt.test.ts" GSD-Task: S02/T02 * test: Wired event stream into session runner, added onEvent()/addTransp… - "sdk/src/session-runner.ts" - "sdk/src/index.ts" - "sdk/src/e2e.integration.test.ts" GSD-Task: S02/T03 * feat: Added PhaseStepType enum, PhaseOpInfo interface, phase lifecycle… - "sdk/src/types.ts" - "sdk/src/gsd-tools.ts" - "sdk/src/session-runner.ts" - "sdk/src/index.ts" - "sdk/src/phase-runner-types.test.ts" GSD-Task: S03/T01 * test: Implemented PhaseRunner state machine with 39 unit tests covering… - "sdk/src/phase-runner.ts" - "sdk/src/phase-runner.test.ts" GSD-Task: S03/T02 * test: Wired PhaseRunner into GSD.runPhase() public API with full re-exp… - "sdk/src/index.ts" - "sdk/src/phase-runner.integration.test.ts" - "sdk/src/phase-runner.ts" GSD-Task: S03/T03 * test: Expanded runVerifyStep with full gap closure cycle (plan → execut… - "sdk/src/types.ts" - "sdk/src/phase-runner.ts" - "sdk/src/phase-runner.test.ts" GSD-Task: S04/T02 * fix: Added 3 integration tests proving phasePlanIndex returns correct t… - "sdk/src/phase-runner.integration.test.ts" - "sdk/src/index.ts" GSD-Task: S04/T03 * test: Add milestone-level types, typed roadmapAnalyze(), GSD.run() orch… - "sdk/src/types.ts" - "sdk/src/gsd-tools.ts" - "sdk/src/index.ts" - "sdk/src/milestone-runner.test.ts" GSD-Task: S05/T01 * test: Added CLITransport (structured stdout log lines) and WSTransport… - "sdk/src/cli-transport.ts" - "sdk/src/cli-transport.test.ts" - "sdk/src/ws-transport.ts" - "sdk/src/ws-transport.test.ts" - "sdk/src/index.ts" - "sdk/package.json" GSD-Task: S05/T02 * test: Added gsd-sdk CLI entry point with argument parsing, bin field, p… - "sdk/src/cli.ts" - "sdk/src/cli.test.ts" - "sdk/package.json" GSD-Task: S05/T03 * feat: Add InitNewProjectInfo type, initNewProject()/configSet() GSDTool… - "sdk/src/types.ts" - "sdk/src/gsd-tools.ts" - "sdk/src/cli.ts" - "sdk/src/cli.test.ts" - "sdk/src/gsd-tools.test.ts" GSD-Task: S01/T01 * chore: Created InitRunner orchestrator with setup → config → PROJECT.md… - "sdk/src/init-runner.ts" - "sdk/src/types.ts" - "sdk/src/index.ts" GSD-Task: S01/T02 * test: Wired InitRunner into CLI main() for full gsd-sdk init dispatch a… - "sdk/src/cli.ts" - "sdk/src/init-runner.test.ts" - "sdk/src/cli.test.ts" GSD-Task: S01/T03 * test: Add PlanCheck step, AI self-discuss, and retryOnce wrapper to Pha… - "sdk/src/types.ts" - "sdk/src/phase-runner.ts" - "sdk/src/session-runner.ts" - "sdk/src/phase-runner.test.ts" - "sdk/src/phase-runner-types.test.ts" GSD-Task: S02/T01 * feat: Rewrite CLITransport with ANSI colors, phase banners, spawn indic… - "sdk/src/cli-transport.ts" - "sdk/src/cli-transport.test.ts" GSD-Task: S02/T02 * test: Add `gsd-sdk auto` command with autoMode config override, USAGE t… - "sdk/src/cli.ts" - "sdk/src/cli.test.ts" - "sdk/src/index.ts" - "sdk/src/types.ts" GSD-Task: S02/T03 * fix: CLI shebang + gsd-tools non-JSON output handling Three bugs found during first real gsd-sdk run: 1. cli.ts shebang was commented out — shell executed JS as bash, triggering ImageMagick's import command instead of Node 2. configSet() called exec() which JSON.parse()d the output, but gsd-tools config-set returns 'key=value' text, not JSON. Added execRaw() method for commands that return plain text. 3. Same JSON parse bug affected commit() (returns git SHA), stateLoad(), verifySummary(), initExecutePhase(), stateBeginPhase(), and phaseComplete(). All switched to execRaw(). Tests updated to match real gsd-tools output format (plain text instead of mocked JSON). 376/376 tests pass.
307 lines
10 KiB
TypeScript
307 lines
10 KiB
TypeScript
/**
|
|
* Unit tests for prompt-builder.ts
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
buildExecutorPrompt,
|
|
parseAgentTools,
|
|
parseAgentRole,
|
|
DEFAULT_ALLOWED_TOOLS,
|
|
} from './prompt-builder.js';
|
|
import type { ParsedPlan, PlanFrontmatter, MustHaves } from './types.js';
|
|
|
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
|
|
function makePlan(overrides: Partial<ParsedPlan> = {}): ParsedPlan {
|
|
const defaultFrontmatter: PlanFrontmatter = {
|
|
phase: '01-auth',
|
|
plan: '01',
|
|
type: 'execute',
|
|
wave: 1,
|
|
depends_on: [],
|
|
files_modified: [],
|
|
autonomous: true,
|
|
requirements: ['AUTH-01'],
|
|
must_haves: { truths: [], artifacts: [], key_links: [] },
|
|
};
|
|
|
|
return {
|
|
frontmatter: { ...defaultFrontmatter, ...overrides.frontmatter },
|
|
objective: overrides.objective ?? 'Implement JWT authentication with refresh tokens',
|
|
execution_context: overrides.execution_context ?? [],
|
|
context_refs: overrides.context_refs ?? [],
|
|
tasks: overrides.tasks ?? [
|
|
{
|
|
type: 'auto',
|
|
name: 'Create auth module',
|
|
files: ['src/auth.ts'],
|
|
read_first: ['src/types.ts'],
|
|
action: 'Create the auth module with login and refresh endpoints',
|
|
verify: 'npm test -- --filter auth',
|
|
acceptance_criteria: ['JWT tokens issued on login', 'Refresh tokens rotate correctly'],
|
|
done: 'Auth module created and tests pass',
|
|
},
|
|
{
|
|
type: 'auto',
|
|
name: 'Add middleware',
|
|
files: ['src/middleware.ts'],
|
|
read_first: [],
|
|
action: 'Create auth middleware for protected routes',
|
|
verify: 'npm test -- --filter middleware',
|
|
acceptance_criteria: [],
|
|
done: 'Middleware validates JWT on protected routes',
|
|
},
|
|
],
|
|
raw: '',
|
|
};
|
|
}
|
|
|
|
const SAMPLE_AGENT_DEF = `---
|
|
name: gsd-executor
|
|
description: Executes GSD plans
|
|
tools: Read, Write, Edit, Bash, Grep, Glob
|
|
permissionMode: acceptEdits
|
|
---
|
|
|
|
<role>
|
|
You are a GSD plan executor. You execute PLAN.md files atomically.
|
|
</role>
|
|
|
|
<execution_flow>
|
|
Some flow content
|
|
</execution_flow>`;
|
|
|
|
// ─── parseAgentTools ─────────────────────────────────────────────────────────
|
|
|
|
describe('parseAgentTools', () => {
|
|
it('extracts tools from agent definition frontmatter', () => {
|
|
const tools = parseAgentTools(SAMPLE_AGENT_DEF);
|
|
expect(tools).toEqual(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob']);
|
|
});
|
|
|
|
it('returns defaults when no frontmatter found', () => {
|
|
const tools = parseAgentTools('Just some text without frontmatter');
|
|
expect(tools).toEqual(DEFAULT_ALLOWED_TOOLS);
|
|
});
|
|
|
|
it('returns defaults when frontmatter has no tools key', () => {
|
|
const def = `---\nname: test\n---\nContent`;
|
|
const tools = parseAgentTools(def);
|
|
expect(tools).toEqual(DEFAULT_ALLOWED_TOOLS);
|
|
});
|
|
|
|
it('handles empty tools value', () => {
|
|
const def = `---\ntools: \n---`;
|
|
const tools = parseAgentTools(def);
|
|
expect(tools).toEqual(DEFAULT_ALLOWED_TOOLS);
|
|
});
|
|
});
|
|
|
|
// ─── parseAgentRole ──────────────────────────────────────────────────────────
|
|
|
|
describe('parseAgentRole', () => {
|
|
it('extracts role content from agent definition', () => {
|
|
const role = parseAgentRole(SAMPLE_AGENT_DEF);
|
|
expect(role).toContain('GSD plan executor');
|
|
expect(role).toContain('PLAN.md files atomically');
|
|
});
|
|
|
|
it('returns empty string when no role block', () => {
|
|
expect(parseAgentRole('No role block here')).toBe('');
|
|
});
|
|
});
|
|
|
|
// ─── buildExecutorPrompt ─────────────────────────────────────────────────────
|
|
|
|
describe('buildExecutorPrompt', () => {
|
|
it('includes the objective text', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('Implement JWT authentication with refresh tokens');
|
|
});
|
|
|
|
it('includes all task names', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('Create auth module');
|
|
expect(prompt).toContain('Add middleware');
|
|
});
|
|
|
|
it('includes task actions', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('Create the auth module with login and refresh endpoints');
|
|
expect(prompt).toContain('Create auth middleware for protected routes');
|
|
});
|
|
|
|
it('includes task verification commands', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('npm test -- --filter auth');
|
|
expect(prompt).toContain('npm test -- --filter middleware');
|
|
});
|
|
|
|
it('includes task file references', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('src/auth.ts');
|
|
expect(prompt).toContain('src/types.ts');
|
|
});
|
|
|
|
it('includes acceptance criteria', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('JWT tokens issued on login');
|
|
expect(prompt).toContain('Refresh tokens rotate correctly');
|
|
});
|
|
|
|
it('includes SUMMARY.md creation instruction', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('SUMMARY.md');
|
|
expect(prompt).toContain('Create a SUMMARY.md file');
|
|
});
|
|
|
|
it('includes sequential execution instruction', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('Execute these tasks sequentially');
|
|
});
|
|
|
|
it('handles plan with no tasks gracefully', () => {
|
|
const plan = makePlan({ tasks: [] });
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('No tasks defined');
|
|
expect(prompt).toContain('SUMMARY.md');
|
|
// Should not throw
|
|
expect(prompt.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('includes context references when present', () => {
|
|
const plan = makePlan({
|
|
context_refs: ['src/config.ts', 'docs/architecture.md'],
|
|
});
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('@src/config.ts');
|
|
expect(prompt).toContain('@docs/architecture.md');
|
|
expect(prompt).toContain('Read these files for context');
|
|
});
|
|
|
|
it('omits context section when no refs', () => {
|
|
const plan = makePlan({ context_refs: [] });
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).not.toContain('Context Files');
|
|
});
|
|
|
|
it('includes plan metadata', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('Phase: 01-auth');
|
|
expect(prompt).toContain('Plan: 01');
|
|
});
|
|
|
|
it('includes must-have truths when present', () => {
|
|
const plan = makePlan({
|
|
frontmatter: {
|
|
phase: '01',
|
|
plan: '01',
|
|
type: 'execute',
|
|
wave: 1,
|
|
depends_on: [],
|
|
files_modified: [],
|
|
autonomous: true,
|
|
requirements: [],
|
|
must_haves: {
|
|
truths: ['All endpoints require JWT auth', 'Tokens expire after 15 minutes'],
|
|
artifacts: [],
|
|
key_links: [],
|
|
},
|
|
},
|
|
});
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('All endpoints require JWT auth');
|
|
expect(prompt).toContain('Tokens expire after 15 minutes');
|
|
});
|
|
|
|
it('includes must-have artifacts', () => {
|
|
const plan = makePlan({
|
|
frontmatter: {
|
|
phase: '01',
|
|
plan: '01',
|
|
type: 'execute',
|
|
wave: 1,
|
|
depends_on: [],
|
|
files_modified: [],
|
|
autonomous: true,
|
|
requirements: [],
|
|
must_haves: {
|
|
truths: [],
|
|
artifacts: [{ path: 'src/auth.ts', provides: 'JWT auth module' }],
|
|
key_links: [],
|
|
},
|
|
},
|
|
});
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('`src/auth.ts`');
|
|
expect(prompt).toContain('JWT auth module');
|
|
});
|
|
|
|
it('includes must-have key_links', () => {
|
|
const plan = makePlan({
|
|
frontmatter: {
|
|
phase: '01',
|
|
plan: '01',
|
|
type: 'execute',
|
|
wave: 1,
|
|
depends_on: [],
|
|
files_modified: [],
|
|
autonomous: true,
|
|
requirements: [],
|
|
must_haves: {
|
|
truths: [],
|
|
artifacts: [],
|
|
key_links: [{ from: 'auth.ts', to: 'middleware.ts', via: 'import' }],
|
|
},
|
|
},
|
|
});
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('auth.ts → middleware.ts via import');
|
|
});
|
|
|
|
it('includes role from agent definition when provided', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan, SAMPLE_AGENT_DEF);
|
|
expect(prompt).toContain('## Role');
|
|
expect(prompt).toContain('GSD plan executor');
|
|
});
|
|
|
|
it('works without agent definition', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
// Should still produce a valid prompt without role section
|
|
expect(prompt).toContain('## Objective');
|
|
expect(prompt).toContain('## Tasks');
|
|
expect(prompt).not.toContain('## Role');
|
|
});
|
|
|
|
it('provides fallback objective when plan has empty objective', () => {
|
|
const plan = makePlan({ objective: '' });
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('Execute plan: 01');
|
|
});
|
|
|
|
it('includes done criteria for tasks', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('Auth module created and tests pass');
|
|
expect(prompt).toContain('Middleware validates JWT on protected routes');
|
|
});
|
|
|
|
it('includes commit instruction in completion section', () => {
|
|
const plan = makePlan();
|
|
const prompt = buildExecutorPrompt(plan);
|
|
expect(prompt).toContain('Commit the SUMMARY.md');
|
|
});
|
|
});
|