feat(wave3): add @mosaic/prdy — TypeScript PRD wizard

- PRD CRUD: createPrd, loadPrd, savePrd, listPrds
- Interactive wizard using @clack/prompts
- Built-in templates: software, feature, spike
- CLI: prdy init | list | show
- Depends on @mosaic/types workspace:*
This commit is contained in:
2026-03-06 20:21:39 -06:00
parent 7f7109fc09
commit 3106ca8cf8
11 changed files with 690 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
import { promises as fs } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { createPrd, listPrds, loadPrd } from '../src/prd.js';
describe('prd document lifecycle', () => {
it('creates and loads PRD documents', async () => {
const projectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'prdy-project-'));
try {
const created = await createPrd({
name: 'User Authentication',
projectPath: projectDir,
template: 'feature',
});
expect(created.title).toBe('User Authentication');
expect(created.status).toBe('draft');
expect(created.id).toMatch(/^user-authentication-\d{8}-\d{6}$/);
const loaded = await loadPrd(projectDir);
expect(loaded.id).toBe(created.id);
expect(loaded.title).toBe(created.title);
expect(loaded.sections.length).toBeGreaterThan(0);
const listed = await listPrds(projectDir);
expect(listed).toHaveLength(1);
expect(listed[0]?.id).toBe(created.id);
} finally {
await fs.rm(projectDir, { recursive: true, force: true });
}
});
});

View File

@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest';
import { BUILTIN_PRD_TEMPLATES } from '../src/templates.js';
describe('built-in PRD templates', () => {
it('includes software, feature, and spike templates with required fields', () => {
expect(BUILTIN_PRD_TEMPLATES.software).toBeDefined();
expect(BUILTIN_PRD_TEMPLATES.feature).toBeDefined();
expect(BUILTIN_PRD_TEMPLATES.spike).toBeDefined();
for (const template of Object.values(BUILTIN_PRD_TEMPLATES)) {
expect(template.sections.length).toBeGreaterThan(0);
expect(template.fields.length).toBeGreaterThan(0);
for (const section of template.sections) {
expect(section.id.length).toBeGreaterThan(0);
expect(section.title.length).toBeGreaterThan(0);
expect(section.fields.length).toBeGreaterThan(0);
}
}
});
});