@auto-engineer/component-implementor-react 1.95.0 → 1.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +6 -6
  3. package/.turbo/turbo-type-check.log +1 -1
  4. package/CHANGELOG.md +72 -0
  5. package/dist/src/commands/implement-component.d.ts.map +1 -1
  6. package/dist/src/commands/implement-component.js +13 -16
  7. package/dist/src/commands/implement-component.js.map +1 -1
  8. package/dist/src/commands/implement-component.test.js +14 -5
  9. package/dist/src/commands/implement-component.test.js.map +1 -1
  10. package/dist/src/extract-code-block.d.ts +1 -0
  11. package/dist/src/extract-code-block.d.ts.map +1 -1
  12. package/dist/src/extract-code-block.js +12 -0
  13. package/dist/src/extract-code-block.js.map +1 -1
  14. package/dist/src/extract-code-block.test.js +28 -1
  15. package/dist/src/extract-code-block.test.js.map +1 -1
  16. package/dist/src/generate-component.d.ts +2 -13
  17. package/dist/src/generate-component.d.ts.map +1 -1
  18. package/dist/src/generate-component.js +4 -29
  19. package/dist/src/generate-component.js.map +1 -1
  20. package/dist/src/generate-component.test.js +18 -22
  21. package/dist/src/generate-component.test.js.map +1 -1
  22. package/dist/src/generate-story.d.ts +2 -12
  23. package/dist/src/generate-story.d.ts.map +1 -1
  24. package/dist/src/generate-story.js +4 -25
  25. package/dist/src/generate-story.js.map +1 -1
  26. package/dist/src/generate-story.test.js +17 -21
  27. package/dist/src/generate-story.test.js.map +1 -1
  28. package/dist/src/generate-test.d.ts +2 -12
  29. package/dist/src/generate-test.d.ts.map +1 -1
  30. package/dist/src/generate-test.js +4 -28
  31. package/dist/src/generate-test.js.map +1 -1
  32. package/dist/src/generate-test.test.js +17 -6
  33. package/dist/src/generate-test.test.js.map +1 -1
  34. package/dist/src/prompt.d.ts +64 -0
  35. package/dist/src/prompt.d.ts.map +1 -0
  36. package/dist/src/prompt.js +481 -0
  37. package/dist/src/prompt.js.map +1 -0
  38. package/dist/src/prompt.test.d.ts +2 -0
  39. package/dist/src/prompt.test.d.ts.map +1 -0
  40. package/dist/src/prompt.test.js +136 -0
  41. package/dist/src/prompt.test.js.map +1 -0
  42. package/dist/src/reconcile.d.ts +8 -0
  43. package/dist/src/reconcile.d.ts.map +1 -0
  44. package/dist/src/reconcile.js +18 -0
  45. package/dist/src/reconcile.js.map +1 -0
  46. package/dist/src/reconcile.test.d.ts +2 -0
  47. package/dist/src/reconcile.test.d.ts.map +1 -0
  48. package/dist/src/reconcile.test.js +108 -0
  49. package/dist/src/reconcile.test.js.map +1 -0
  50. package/dist/src/run.d.ts +2 -0
  51. package/dist/src/run.d.ts.map +1 -0
  52. package/dist/src/run.js +86 -0
  53. package/dist/src/run.js.map +1 -0
  54. package/dist/src/spec-contract.d.ts +9 -0
  55. package/dist/src/spec-contract.d.ts.map +1 -0
  56. package/dist/src/spec-contract.js +16 -0
  57. package/dist/src/spec-contract.js.map +1 -0
  58. package/dist/tsconfig.tsbuildinfo +1 -1
  59. package/improvement-prompt.md +208 -0
  60. package/inputs/action-button/spec.json +50 -0
  61. package/inputs/command-palette/spec.json +62 -0
  62. package/inputs/data-card/spec.json +59 -0
  63. package/inputs/editable-data-table/spec.json +70 -0
  64. package/inputs/multi-step-form/spec.json +66 -0
  65. package/inputs/notification-center/spec.json +67 -0
  66. package/inputs/search-input/spec.json +62 -0
  67. package/inputs/status-badge/spec.json +46 -0
  68. package/package.json +4 -3
  69. package/scripts/improve.ts +592 -0
  70. package/src/commands/implement-component.test.ts +14 -5
  71. package/src/commands/implement-component.ts +13 -17
  72. package/src/extract-code-block.test.ts +33 -1
  73. package/src/extract-code-block.ts +13 -0
  74. package/src/generate-component.test.ts +22 -26
  75. package/src/generate-component.ts +5 -46
  76. package/src/generate-story.test.ts +17 -21
  77. package/src/generate-story.ts +5 -40
  78. package/src/generate-test.test.ts +22 -7
  79. package/src/generate-test.ts +5 -44
  80. package/src/prompt.test.ts +163 -0
  81. package/src/prompt.ts +581 -0
  82. package/src/reconcile.test.ts +127 -0
  83. package/src/reconcile.ts +27 -0
  84. package/src/run.ts +106 -0
  85. package/src/spec-contract.ts +22 -0
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { extractCodeBlock } from './extract-code-block';
2
+ import { extractCodeBlock, extractCodeBlocks } from './extract-code-block';
3
3
 
4
4
  describe('extractCodeBlock', () => {
5
5
  it('returns plain text unchanged', () => {
@@ -31,3 +31,35 @@ describe('extractCodeBlock', () => {
31
31
  expect(extractCodeBlock(input)).toBe('const x = 1;');
32
32
  });
33
33
  });
34
+
35
+ describe('extractCodeBlocks', () => {
36
+ it('extracts multiple code blocks', () => {
37
+ const input = '```tsx\ncomponent code\n```\n\nsome text\n\n```tsx\nstory code\n```';
38
+ const blocks = extractCodeBlocks(input);
39
+ expect(blocks).toEqual(['component code', 'story code']);
40
+ });
41
+
42
+ it('extracts blocks with different language tags', () => {
43
+ const input = '```tsx\nfirst block\n```\n```ts\nsecond block\n```';
44
+ const blocks = extractCodeBlocks(input);
45
+ expect(blocks).toEqual(['first block', 'second block']);
46
+ });
47
+
48
+ it('extracts bare code blocks', () => {
49
+ const input = '```\nbare block\n```';
50
+ const blocks = extractCodeBlocks(input);
51
+ expect(blocks).toEqual(['bare block']);
52
+ });
53
+
54
+ it('falls back to full text when no code blocks found', () => {
55
+ const input = 'plain text content';
56
+ const blocks = extractCodeBlocks(input);
57
+ expect(blocks).toEqual(['plain text content']);
58
+ });
59
+
60
+ it('trims content within each block', () => {
61
+ const input = '```tsx\n spaced content \n```';
62
+ const blocks = extractCodeBlocks(input);
63
+ expect(blocks).toEqual(['spaced content']);
64
+ });
65
+ });
@@ -4,3 +4,16 @@ export function extractCodeBlock(text: string): string {
4
4
  .replace(/```/g, '')
5
5
  .trim();
6
6
  }
7
+
8
+ export function extractCodeBlocks(text: string): string[] {
9
+ const blocks: string[] = [];
10
+ const regex = /```(?:tsx?|typescript)?\n([\s\S]*?)```/g;
11
+ let match: RegExpExecArray | null;
12
+ while ((match = regex.exec(text)) !== null) {
13
+ blocks.push(match[1].trim());
14
+ }
15
+ if (blocks.length === 0) {
16
+ blocks.push(text.trim());
17
+ }
18
+ return blocks;
19
+ }
@@ -32,29 +32,42 @@ describe('generateComponentFile', () => {
32
32
  const result = await generateComponentFile({
33
33
  componentName: 'Button',
34
34
  specDeltas,
35
- testCode: 'import { render } from "@testing-library/react";',
36
35
  });
37
36
 
38
37
  expect(result).toBe('export function Button() { return <button />; }');
39
38
  });
40
39
 
41
- it('includes spec deltas and test code in the prompt', async () => {
40
+ it('passes system prompt with methodology, rules, and checklist', async () => {
42
41
  const mockGenerateText = vi.mocked(generateText);
43
42
  mockGenerateText.mockResolvedValue({
44
43
  text: 'component code',
45
44
  } as Awaited<ReturnType<typeof generateText>>);
46
45
 
47
- await generateComponentFile({
48
- componentName: 'Button',
49
- specDeltas,
50
- testCode: 'describe("Button", () => {})',
51
- });
46
+ await generateComponentFile({ componentName: 'Button', specDeltas });
47
+
48
+ const system = mockGenerateText.mock.calls[0][0].system as string;
49
+ expect(system).toContain('staff-level frontend engineer');
50
+ expect(system).toContain('METHODOLOGY');
51
+ expect(system).toContain('RULES');
52
+ expect(system).toContain('QUALITY CHECKLIST');
53
+ expect(system).toContain('Named exports');
54
+ expect(system).toContain('Tailwind CSS');
55
+ expect(system).toContain('ARIA roles');
56
+ });
57
+
58
+ it('includes spec deltas in the user prompt', async () => {
59
+ const mockGenerateText = vi.mocked(generateText);
60
+ mockGenerateText.mockResolvedValue({
61
+ text: 'component code',
62
+ } as Awaited<ReturnType<typeof generateText>>);
63
+
64
+ await generateComponentFile({ componentName: 'Button', specDeltas });
52
65
 
53
66
  const prompt = mockGenerateText.mock.calls[0][0].prompt as string;
67
+ expect(prompt).toContain('## Spec Deltas');
54
68
  expect(prompt).toContain('## Structure');
55
69
  expect(prompt).toContain('renders a Button element');
56
- expect(prompt).toContain('## Test File');
57
- expect(prompt).toContain('describe("Button", () => {})');
70
+ expect(prompt).toContain('**Button**');
58
71
  });
59
72
 
60
73
  it('includes existing component code when provided', async () => {
@@ -66,7 +79,6 @@ describe('generateComponentFile', () => {
66
79
  await generateComponentFile({
67
80
  componentName: 'Button',
68
81
  specDeltas,
69
- testCode: 'test code',
70
82
  existingComponent: 'export function Button() {}',
71
83
  });
72
84
 
@@ -74,20 +86,4 @@ describe('generateComponentFile', () => {
74
86
  expect(prompt).toContain('## Existing Component');
75
87
  expect(prompt).toContain('export function Button() {}');
76
88
  });
77
-
78
- it('uses system prompt referencing React component generation', async () => {
79
- const mockGenerateText = vi.mocked(generateText);
80
- mockGenerateText.mockResolvedValue({
81
- text: 'code',
82
- } as Awaited<ReturnType<typeof generateText>>);
83
-
84
- await generateComponentFile({
85
- componentName: 'Button',
86
- specDeltas,
87
- testCode: 'test',
88
- });
89
-
90
- const system = mockGenerateText.mock.calls[0][0].system as string;
91
- expect(system).toContain('React');
92
- });
93
89
  });
@@ -1,57 +1,16 @@
1
1
  import { createModelFromEnv } from '@auto-engineer/model-factory';
2
2
  import { generateText } from 'ai';
3
3
  import { extractCodeBlock } from './extract-code-block';
4
+ import { buildComponentPrompt, type ComponentPromptInput } from './prompt';
4
5
 
5
- type SpecDeltas = {
6
- structure: string[];
7
- rendering: string[];
8
- interaction: string[];
9
- styling: string[];
10
- };
11
-
12
- type GenerateComponentInput = {
13
- componentName: string;
14
- specDeltas: SpecDeltas;
15
- testCode: string;
16
- existingComponent?: string;
17
- };
18
-
19
- function buildSpecSection(heading: string, items: string[]): string {
20
- if (items.length === 0) return '';
21
- return `## ${heading}\n${items.map((i) => `- ${i}`).join('\n')}`;
22
- }
23
-
24
- function buildPrompt(input: GenerateComponentInput): string {
25
- const sections = [
26
- buildSpecSection('Structure', input.specDeltas.structure),
27
- buildSpecSection('Rendering', input.specDeltas.rendering),
28
- buildSpecSection('Interaction', input.specDeltas.interaction),
29
- buildSpecSection('Styling', input.specDeltas.styling),
30
- ]
31
- .filter(Boolean)
32
- .join('\n\n');
33
-
34
- const existingContext = input.existingComponent
35
- ? `\n\n## Existing Component\n\`\`\`tsx\n${input.existingComponent}\n\`\`\``
36
- : '';
37
-
38
- return `Component: ${input.componentName}\n\n${sections}${existingContext}\n\n## Test File\n\`\`\`tsx\n${input.testCode}\n\`\`\``;
39
- }
40
-
41
- const SYSTEM_PROMPT = `You are a React component expert. Write a React component (.tsx) that satisfies the spec and passes the provided test.
42
-
43
- Rules:
44
- - Use functional components with TypeScript
45
- - Export the component as a named export
46
- - Use Tailwind CSS classes for styling
47
- - The component must pass the provided test
48
- - Return ONLY the component file code, no commentary`;
6
+ export type GenerateComponentInput = ComponentPromptInput;
49
7
 
50
8
  export async function generateComponentFile(input: GenerateComponentInput): Promise<string> {
9
+ const { system, prompt } = buildComponentPrompt(input);
51
10
  const { text } = await generateText({
52
11
  model: createModelFromEnv(),
53
- system: SYSTEM_PROMPT,
54
- prompt: buildPrompt(input),
12
+ system,
13
+ prompt,
55
14
  });
56
15
  return extractCodeBlock(text);
57
16
  }
@@ -32,44 +32,40 @@ describe('generateStoryFile', () => {
32
32
  const result = await generateStoryFile({
33
33
  componentName: 'Button',
34
34
  specDeltas,
35
- componentCode: 'export function Button() { return <button />; }',
36
35
  });
37
36
 
38
37
  expect(result).toBe('import type { Meta } from "@storybook/react";');
39
38
  });
40
39
 
41
- it('includes spec deltas and component code in the prompt', async () => {
40
+ it('passes system prompt with methodology, rules, and checklist', async () => {
42
41
  const mockGenerateText = vi.mocked(generateText);
43
42
  mockGenerateText.mockResolvedValue({
44
43
  text: 'story code',
45
44
  } as Awaited<ReturnType<typeof generateText>>);
46
45
 
47
- await generateStoryFile({
48
- componentName: 'Button',
49
- specDeltas,
50
- componentCode: 'export function Button() {}',
51
- });
46
+ await generateStoryFile({ componentName: 'Button', specDeltas });
52
47
 
53
- const prompt = mockGenerateText.mock.calls[0][0].prompt as string;
54
- expect(prompt).toContain('## Structure');
55
- expect(prompt).toContain('renders a Button element');
56
- expect(prompt).toContain('## Component Code');
57
- expect(prompt).toContain('export function Button() {}');
48
+ const system = mockGenerateText.mock.calls[0][0].system as string;
49
+ expect(system).toContain('design systems engineer');
50
+ expect(system).toContain('METHODOLOGY');
51
+ expect(system).toContain('RULES');
52
+ expect(system).toContain('QUALITY CHECKLIST');
53
+ expect(system).toContain('CSF3 format');
54
+ expect(system).toContain('Named import');
58
55
  });
59
56
 
60
- it('uses system prompt referencing Storybook', async () => {
57
+ it('includes spec deltas in the user prompt but not component code', async () => {
61
58
  const mockGenerateText = vi.mocked(generateText);
62
59
  mockGenerateText.mockResolvedValue({
63
- text: 'code',
60
+ text: 'story code',
64
61
  } as Awaited<ReturnType<typeof generateText>>);
65
62
 
66
- await generateStoryFile({
67
- componentName: 'Button',
68
- specDeltas,
69
- componentCode: 'code',
70
- });
63
+ await generateStoryFile({ componentName: 'Button', specDeltas });
71
64
 
72
- const system = mockGenerateText.mock.calls[0][0].system as string;
73
- expect(system).toContain('Storybook');
65
+ const prompt = mockGenerateText.mock.calls[0][0].prompt as string;
66
+ expect(prompt).toContain('## Spec Deltas');
67
+ expect(prompt).toContain('## Structure');
68
+ expect(prompt).toContain('renders a Button element');
69
+ expect(prompt).not.toContain('## Component Code');
74
70
  });
75
71
  });
@@ -1,51 +1,16 @@
1
1
  import { createModelFromEnv } from '@auto-engineer/model-factory';
2
2
  import { generateText } from 'ai';
3
3
  import { extractCodeBlock } from './extract-code-block';
4
+ import { buildStoryPrompt, type StoryPromptInput } from './prompt';
4
5
 
5
- type SpecDeltas = {
6
- structure: string[];
7
- rendering: string[];
8
- interaction: string[];
9
- styling: string[];
10
- };
11
-
12
- type GenerateStoryInput = {
13
- componentName: string;
14
- specDeltas: SpecDeltas;
15
- componentCode: string;
16
- };
17
-
18
- function buildSpecSection(heading: string, items: string[]): string {
19
- if (items.length === 0) return '';
20
- return `## ${heading}\n${items.map((i) => `- ${i}`).join('\n')}`;
21
- }
22
-
23
- function buildPrompt(input: GenerateStoryInput): string {
24
- const sections = [
25
- buildSpecSection('Structure', input.specDeltas.structure),
26
- buildSpecSection('Rendering', input.specDeltas.rendering),
27
- buildSpecSection('Interaction', input.specDeltas.interaction),
28
- buildSpecSection('Styling', input.specDeltas.styling),
29
- ]
30
- .filter(Boolean)
31
- .join('\n\n');
32
-
33
- return `Component: ${input.componentName}\n\n${sections}\n\n## Component Code\n\`\`\`tsx\n${input.componentCode}\n\`\`\``;
34
- }
35
-
36
- const SYSTEM_PROMPT = `You are a Storybook expert. Write a Storybook story file (.stories.tsx) for the described React component.
37
-
38
- Rules:
39
- - Use CSF3 format (export default meta, named story exports)
40
- - Import Meta and StoryObj from @storybook/react
41
- - Create stories that showcase different states from the spec
42
- - Return ONLY the story file code, no commentary`;
6
+ export type GenerateStoryInput = StoryPromptInput;
43
7
 
44
8
  export async function generateStoryFile(input: GenerateStoryInput): Promise<string> {
9
+ const { system, prompt } = buildStoryPrompt(input);
45
10
  const { text } = await generateText({
46
11
  model: createModelFromEnv(),
47
- system: SYSTEM_PROMPT,
48
- prompt: buildPrompt(input),
12
+ system,
13
+ prompt,
49
14
  });
50
15
  return extractCodeBlock(text);
51
16
  }
@@ -22,7 +22,8 @@ describe('generateTestFile', () => {
22
22
  afterEach(() => {
23
23
  vi.clearAllMocks();
24
24
  });
25
- it('returns generated test code from AI with spec deltas in prompt', async () => {
25
+
26
+ it('returns generated test code from AI', async () => {
26
27
  const mockGenerateText = vi.mocked(generateText);
27
28
  mockGenerateText.mockResolvedValue({
28
29
  text: '```tsx\nimport { render } from "@testing-library/react";\n```',
@@ -34,14 +35,26 @@ describe('generateTestFile', () => {
34
35
  });
35
36
 
36
37
  expect(result).toBe('import { render } from "@testing-library/react";');
37
- expect(mockGenerateText).toHaveBeenCalledWith({
38
- model: 'mock-model',
39
- system: expect.stringContaining('vitest'),
40
- prompt: expect.stringContaining('renders a Button element'),
41
- });
42
38
  });
43
39
 
44
- it('includes all spec delta sections in the prompt', async () => {
40
+ it('passes system prompt with methodology, rules, and checklist', async () => {
41
+ const mockGenerateText = vi.mocked(generateText);
42
+ mockGenerateText.mockResolvedValue({
43
+ text: 'test code',
44
+ } as Awaited<ReturnType<typeof generateText>>);
45
+
46
+ await generateTestFile({ componentName: 'Button', specDeltas });
47
+
48
+ const system = mockGenerateText.mock.calls[0][0].system as string;
49
+ expect(system).toContain('senior frontend engineer who specializes in testing');
50
+ expect(system).toContain('METHODOLOGY');
51
+ expect(system).toContain('RULES');
52
+ expect(system).toContain('QUALITY CHECKLIST');
53
+ expect(system).toContain('Semantic queries first');
54
+ expect(system).toContain('source of truth');
55
+ });
56
+
57
+ it('includes all spec delta sections in the user prompt', async () => {
45
58
  const mockGenerateText = vi.mocked(generateText);
46
59
  mockGenerateText.mockResolvedValue({
47
60
  text: 'test code',
@@ -50,6 +63,7 @@ describe('generateTestFile', () => {
50
63
  await generateTestFile({ componentName: 'Button', specDeltas });
51
64
 
52
65
  const prompt = mockGenerateText.mock.calls[0][0].prompt as string;
66
+ expect(prompt).toContain('## Spec Deltas');
53
67
  expect(prompt).toContain('## Structure');
54
68
  expect(prompt).toContain('renders a Button element');
55
69
  expect(prompt).toContain('## Rendering');
@@ -73,6 +87,7 @@ describe('generateTestFile', () => {
73
87
  });
74
88
 
75
89
  const prompt = mockGenerateText.mock.calls[0][0].prompt as string;
90
+ expect(prompt).toContain('## Existing Component');
76
91
  expect(prompt).toContain('export function Button() { return <button />; }');
77
92
  });
78
93
  });
@@ -1,55 +1,16 @@
1
1
  import { createModelFromEnv } from '@auto-engineer/model-factory';
2
2
  import { generateText } from 'ai';
3
3
  import { extractCodeBlock } from './extract-code-block';
4
+ import { buildTestPrompt, type TestPromptInput } from './prompt';
4
5
 
5
- type SpecDeltas = {
6
- structure: string[];
7
- rendering: string[];
8
- interaction: string[];
9
- styling: string[];
10
- };
11
-
12
- type GenerateTestInput = {
13
- componentName: string;
14
- specDeltas: SpecDeltas;
15
- existingComponent?: string;
16
- };
17
-
18
- function buildSpecSection(heading: string, items: string[]): string {
19
- if (items.length === 0) return '';
20
- return `## ${heading}\n${items.map((i) => `- ${i}`).join('\n')}`;
21
- }
22
-
23
- function buildPrompt(input: GenerateTestInput): string {
24
- const sections = [
25
- buildSpecSection('Structure', input.specDeltas.structure),
26
- buildSpecSection('Rendering', input.specDeltas.rendering),
27
- buildSpecSection('Interaction', input.specDeltas.interaction),
28
- buildSpecSection('Styling', input.specDeltas.styling),
29
- ]
30
- .filter(Boolean)
31
- .join('\n\n');
32
-
33
- const existingContext = input.existingComponent
34
- ? `\n\n## Existing Component\n\`\`\`tsx\n${input.existingComponent}\n\`\`\``
35
- : '';
36
-
37
- return `Component: ${input.componentName}\n\n${sections}${existingContext}`;
38
- }
39
-
40
- const SYSTEM_PROMPT = `You are a React testing expert. Write a vitest test file for the described component using @testing-library/react.
41
-
42
- Rules:
43
- - Import from vitest (describe, it, expect) and @testing-library/react (render, screen, fireEvent)
44
- - Use describe/it blocks
45
- - Test each specified behavior
46
- - Return ONLY the test file code, no commentary`;
6
+ export type GenerateTestInput = TestPromptInput;
47
7
 
48
8
  export async function generateTestFile(input: GenerateTestInput): Promise<string> {
9
+ const { system, prompt } = buildTestPrompt(input);
49
10
  const { text } = await generateText({
50
11
  model: createModelFromEnv(),
51
- system: SYSTEM_PROMPT,
52
- prompt: buildPrompt(input),
12
+ system,
13
+ prompt,
53
14
  });
54
15
  return extractCodeBlock(text);
55
16
  }
@@ -0,0 +1,163 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ buildComponentPrompt,
4
+ buildReconcilerPrompt,
5
+ buildStoryPrompt,
6
+ buildTestPrompt,
7
+ componentPromptSections,
8
+ reconcilerPromptSections,
9
+ storyPromptSections,
10
+ testPromptSections,
11
+ } from './prompt';
12
+
13
+ const specDeltas = {
14
+ structure: ['renders a Card element'],
15
+ rendering: ['shows skeleton when loading'],
16
+ interaction: ['calls onSelect when clicked'],
17
+ styling: ['applies elevated style when raised=true'],
18
+ };
19
+
20
+ describe('prompt builders', () => {
21
+ describe('buildComponentPrompt', () => {
22
+ it('returns system and prompt', () => {
23
+ const result = buildComponentPrompt({ componentName: 'Card', specDeltas });
24
+ expect(result.system).toBeTruthy();
25
+ expect(result.prompt).toBeTruthy();
26
+ });
27
+
28
+ it('system prompt contains all sections', () => {
29
+ const { system } = buildComponentPrompt({ componentName: 'Card', specDeltas });
30
+ expect(system).toContain('YOUR INPUT');
31
+ expect(system).toContain('YOUR OUTPUT');
32
+ expect(system).toContain('METHODOLOGY');
33
+ expect(system).toContain('RULES');
34
+ expect(system).toContain('QUALITY CHECKLIST');
35
+ });
36
+
37
+ it('user prompt includes component name and spec deltas', () => {
38
+ const { prompt } = buildComponentPrompt({ componentName: 'Card', specDeltas });
39
+ expect(prompt).toContain('**Card**');
40
+ expect(prompt).toContain('renders a Card element');
41
+ expect(prompt).toContain('shows skeleton when loading');
42
+ });
43
+
44
+ it('user prompt includes existing component when provided', () => {
45
+ const { prompt } = buildComponentPrompt({
46
+ componentName: 'Card',
47
+ specDeltas,
48
+ existingComponent: 'export function Card() {}',
49
+ });
50
+ expect(prompt).toContain('## Existing Component');
51
+ expect(prompt).toContain('export function Card() {}');
52
+ });
53
+
54
+ it('user prompt omits existing component section when not provided', () => {
55
+ const { prompt } = buildComponentPrompt({ componentName: 'Card', specDeltas });
56
+ expect(prompt).not.toContain('## Existing Component');
57
+ });
58
+ });
59
+
60
+ describe('buildTestPrompt', () => {
61
+ it('system prompt establishes tests as source of truth', () => {
62
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas });
63
+ expect(system).toContain('source of truth');
64
+ });
65
+
66
+ it('user prompt includes spec deltas', () => {
67
+ const { prompt } = buildTestPrompt({ componentName: 'Card', specDeltas });
68
+ expect(prompt).toContain('## Spec Deltas');
69
+ expect(prompt).toContain('calls onSelect when clicked');
70
+ });
71
+
72
+ it('user prompt includes existing component as context', () => {
73
+ const { prompt } = buildTestPrompt({
74
+ componentName: 'Card',
75
+ specDeltas,
76
+ existingComponent: 'existing code',
77
+ });
78
+ expect(prompt).toContain('## Existing Component');
79
+ expect(prompt).toContain('existing code');
80
+ });
81
+ });
82
+
83
+ describe('buildStoryPrompt', () => {
84
+ it('system prompt covers CSF3 and story conventions', () => {
85
+ const { system } = buildStoryPrompt({ componentName: 'Card', specDeltas });
86
+ expect(system).toContain('CSF3');
87
+ expect(system).toContain('Meta');
88
+ expect(system).toContain('StoryObj');
89
+ });
90
+
91
+ it('user prompt includes spec deltas without component code', () => {
92
+ const { prompt } = buildStoryPrompt({ componentName: 'Card', specDeltas });
93
+ expect(prompt).toContain('## Spec Deltas');
94
+ expect(prompt).toContain('applies elevated style when raised=true');
95
+ expect(prompt).not.toContain('## Component Code');
96
+ });
97
+ });
98
+
99
+ describe('buildReconcilerPrompt', () => {
100
+ const reconcileInput = {
101
+ componentName: 'Card',
102
+ specDeltas,
103
+ componentCode: 'component source',
104
+ testCode: 'test source',
105
+ storyCode: 'story source',
106
+ };
107
+
108
+ it('system prompt establishes test immutability', () => {
109
+ const { system } = buildReconcilerPrompt(reconcileInput);
110
+ expect(system).toContain('Tests are immutable');
111
+ expect(system).toContain('source of truth');
112
+ });
113
+
114
+ it('user prompt includes all three code artifacts', () => {
115
+ const { prompt } = buildReconcilerPrompt(reconcileInput);
116
+ expect(prompt).toContain('## Component Code');
117
+ expect(prompt).toContain('component source');
118
+ expect(prompt).toContain('## Test File');
119
+ expect(prompt).toContain('test source');
120
+ expect(prompt).toContain('## Story File');
121
+ expect(prompt).toContain('story source');
122
+ });
123
+
124
+ it('user prompt includes existing component when provided', () => {
125
+ const { prompt } = buildReconcilerPrompt({
126
+ ...reconcileInput,
127
+ existingComponent: 'old code',
128
+ });
129
+ expect(prompt).toContain('## Existing Component');
130
+ expect(prompt).toContain('old code');
131
+ });
132
+ });
133
+
134
+ describe('exported prompt sections', () => {
135
+ it('component sections are all non-empty strings', () => {
136
+ for (const value of Object.values(componentPromptSections)) {
137
+ expect(typeof value).toBe('string');
138
+ expect(value.length).toBeGreaterThan(0);
139
+ }
140
+ });
141
+
142
+ it('test sections are all non-empty strings', () => {
143
+ for (const value of Object.values(testPromptSections)) {
144
+ expect(typeof value).toBe('string');
145
+ expect(value.length).toBeGreaterThan(0);
146
+ }
147
+ });
148
+
149
+ it('story sections are all non-empty strings', () => {
150
+ for (const value of Object.values(storyPromptSections)) {
151
+ expect(typeof value).toBe('string');
152
+ expect(value.length).toBeGreaterThan(0);
153
+ }
154
+ });
155
+
156
+ it('reconciler sections are all non-empty strings', () => {
157
+ for (const value of Object.values(reconcilerPromptSections)) {
158
+ expect(typeof value).toBe('string');
159
+ expect(value.length).toBeGreaterThan(0);
160
+ }
161
+ });
162
+ });
163
+ });