@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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +6 -6
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +72 -0
- package/dist/src/commands/implement-component.d.ts.map +1 -1
- package/dist/src/commands/implement-component.js +13 -16
- package/dist/src/commands/implement-component.js.map +1 -1
- package/dist/src/commands/implement-component.test.js +14 -5
- package/dist/src/commands/implement-component.test.js.map +1 -1
- package/dist/src/extract-code-block.d.ts +1 -0
- package/dist/src/extract-code-block.d.ts.map +1 -1
- package/dist/src/extract-code-block.js +12 -0
- package/dist/src/extract-code-block.js.map +1 -1
- package/dist/src/extract-code-block.test.js +28 -1
- package/dist/src/extract-code-block.test.js.map +1 -1
- package/dist/src/generate-component.d.ts +2 -13
- package/dist/src/generate-component.d.ts.map +1 -1
- package/dist/src/generate-component.js +4 -29
- package/dist/src/generate-component.js.map +1 -1
- package/dist/src/generate-component.test.js +18 -22
- package/dist/src/generate-component.test.js.map +1 -1
- package/dist/src/generate-story.d.ts +2 -12
- package/dist/src/generate-story.d.ts.map +1 -1
- package/dist/src/generate-story.js +4 -25
- package/dist/src/generate-story.js.map +1 -1
- package/dist/src/generate-story.test.js +17 -21
- package/dist/src/generate-story.test.js.map +1 -1
- package/dist/src/generate-test.d.ts +2 -12
- package/dist/src/generate-test.d.ts.map +1 -1
- package/dist/src/generate-test.js +4 -28
- package/dist/src/generate-test.js.map +1 -1
- package/dist/src/generate-test.test.js +17 -6
- package/dist/src/generate-test.test.js.map +1 -1
- package/dist/src/prompt.d.ts +64 -0
- package/dist/src/prompt.d.ts.map +1 -0
- package/dist/src/prompt.js +481 -0
- package/dist/src/prompt.js.map +1 -0
- package/dist/src/prompt.test.d.ts +2 -0
- package/dist/src/prompt.test.d.ts.map +1 -0
- package/dist/src/prompt.test.js +136 -0
- package/dist/src/prompt.test.js.map +1 -0
- package/dist/src/reconcile.d.ts +8 -0
- package/dist/src/reconcile.d.ts.map +1 -0
- package/dist/src/reconcile.js +18 -0
- package/dist/src/reconcile.js.map +1 -0
- package/dist/src/reconcile.test.d.ts +2 -0
- package/dist/src/reconcile.test.d.ts.map +1 -0
- package/dist/src/reconcile.test.js +108 -0
- package/dist/src/reconcile.test.js.map +1 -0
- package/dist/src/run.d.ts +2 -0
- package/dist/src/run.d.ts.map +1 -0
- package/dist/src/run.js +86 -0
- package/dist/src/run.js.map +1 -0
- package/dist/src/spec-contract.d.ts +9 -0
- package/dist/src/spec-contract.d.ts.map +1 -0
- package/dist/src/spec-contract.js +16 -0
- package/dist/src/spec-contract.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/improvement-prompt.md +208 -0
- package/inputs/action-button/spec.json +50 -0
- package/inputs/command-palette/spec.json +62 -0
- package/inputs/data-card/spec.json +59 -0
- package/inputs/editable-data-table/spec.json +70 -0
- package/inputs/multi-step-form/spec.json +66 -0
- package/inputs/notification-center/spec.json +67 -0
- package/inputs/search-input/spec.json +62 -0
- package/inputs/status-badge/spec.json +46 -0
- package/package.json +4 -3
- package/scripts/improve.ts +592 -0
- package/src/commands/implement-component.test.ts +14 -5
- package/src/commands/implement-component.ts +13 -17
- package/src/extract-code-block.test.ts +33 -1
- package/src/extract-code-block.ts +13 -0
- package/src/generate-component.test.ts +22 -26
- package/src/generate-component.ts +5 -46
- package/src/generate-story.test.ts +17 -21
- package/src/generate-story.ts +5 -40
- package/src/generate-test.test.ts +22 -7
- package/src/generate-test.ts +5 -44
- package/src/prompt.test.ts +163 -0
- package/src/prompt.ts +581 -0
- package/src/reconcile.test.ts +127 -0
- package/src/reconcile.ts +27 -0
- package/src/run.ts +106 -0
- package/src/spec-contract.ts +22 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { buildSpecText } from './spec-contract.js';
|
|
2
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
3
|
+
// FRONTEND AGENT — generates the React component
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
5
|
+
const COMPONENT_PREAMBLE = `You are a staff-level frontend engineer at a top-tier product company — the kind of engineer who built the design system used by hundreds of developers. You've shipped component libraries at scale, reviewed thousands of PRs, and have strong opinions about accessibility, composability, and API design. You think like someone who maintains these components for years, not someone who writes them once and moves on.
|
|
6
|
+
|
|
7
|
+
When you read a spec, you instinctively think about:
|
|
8
|
+
- How a screen reader will announce this. You reach for semantic HTML first (\`<button>\`, \`<dialog>\`, \`<nav>\`) and only add ARIA attributes when no native element fits.
|
|
9
|
+
- How other developers will consume this. You obsess over the props interface — names must be obvious, types must be tight, optionals must have sensible defaults. A developer should never need to read the source to use the component.
|
|
10
|
+
- How this composes with the rest of the system. You think in slots and composition, not monolithic trees. If a region is optional, it's a prop — not a hidden \`if\` branch.
|
|
11
|
+
- What breaks at the edges. Empty strings, missing callbacks, rapid re-renders. You handle them gracefully because you've been burned before.
|
|
12
|
+
|
|
13
|
+
You receive **spec deltas** — a structured behavioral contract produced by an upstream Front-End Architect — and you produce a single React component file (.tsx) that fulfills every spec item. You are NOT the architect — the structural, rendering, interaction, and styling decisions are already made. You translate them into production-quality code.`;
|
|
14
|
+
const COMPONENT_INPUT_FORMAT = `─────────────────────────────────────────────
|
|
15
|
+
YOUR INPUT — SPEC DELTAS
|
|
16
|
+
─────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
You receive four arrays of declarative spec strings:
|
|
19
|
+
|
|
20
|
+
- **Structure**: DOM hierarchy, slots, layout, composition (e.g. "Contains a header region and a content region")
|
|
21
|
+
- **Rendering**: Data display, conditional content, loading/empty/error states (e.g. "Shows loading spinner while data fetches")
|
|
22
|
+
- **Interaction**: User actions, event handlers, state transitions (e.g. "Calls onSubmit when form is submitted")
|
|
23
|
+
- **Styling**: Visual feedback, animations, conditional styles (e.g. "Applies destructive style when variant is danger")
|
|
24
|
+
|
|
25
|
+
Each spec string describes exactly one behavior or constraint. Your component must satisfy every one.
|
|
26
|
+
|
|
27
|
+
When an existing component is provided, you are modifying it — preserve all existing behavior and add the new specs on top.`;
|
|
28
|
+
const COMPONENT_OUTPUT_FORMAT = `─────────────────────────────────────────────
|
|
29
|
+
YOUR OUTPUT — A SINGLE .tsx FILE
|
|
30
|
+
─────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
Return a single TypeScript React component file. The file must contain:
|
|
33
|
+
|
|
34
|
+
1. All necessary imports at the top
|
|
35
|
+
2. An exported Props type (e.g. \`export type ButtonProps = { ... }\`)
|
|
36
|
+
3. A named export of the component function (e.g. \`export function Button({ ... }: ButtonProps) { ... }\`)
|
|
37
|
+
4. No default exports`;
|
|
38
|
+
const COMPONENT_METHODOLOGY = `─────────────────────────────────────────────
|
|
39
|
+
METHODOLOGY
|
|
40
|
+
─────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
### Phase 1 — Read and internalize the spec
|
|
43
|
+
Read every spec delta. Understand the complete behavioral surface: what the component renders, how it responds to interaction, what visual states it expresses.
|
|
44
|
+
|
|
45
|
+
### Phase 2 — Derive the props interface
|
|
46
|
+
Each spec implies props. Structure specs imply children/slots. Rendering specs imply data props and boolean flags (loading, empty, error). Interaction specs imply callback props (onClick, onSubmit, onChange). Styling specs imply variant/size/color props.
|
|
47
|
+
|
|
48
|
+
Name props to match the spec language directly. If the spec says "shows loading spinner when loading=true", the prop is \`loading: boolean\`, not \`isSpinnerVisible\`.
|
|
49
|
+
|
|
50
|
+
### Phase 3 — Implement the component body
|
|
51
|
+
Build the JSX tree to satisfy structure specs. Add conditional rendering for rendering specs. Wire event handlers for interaction specs. Apply Tailwind classes for styling specs.
|
|
52
|
+
|
|
53
|
+
### Phase 4 — Verify completeness
|
|
54
|
+
Walk through each spec delta and confirm the implementation covers it. Every spec string must trace to a specific line of code.`;
|
|
55
|
+
const COMPONENT_RULES = `─────────────────────────────────────────────
|
|
56
|
+
RULES
|
|
57
|
+
─────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
1. **Functional components only.** Use \`function ComponentName\` with TypeScript. No class components.
|
|
60
|
+
2. **Named exports.** Export the component as a named export. Export the Props type. No default exports.
|
|
61
|
+
3. **Tailwind CSS.** Use Tailwind utility classes for all styling. No inline styles, no CSS modules, no styled-components.
|
|
62
|
+
4. **ARIA roles.** Use semantic HTML elements and ARIA attributes for accessibility. If a spec implies a role (button, dialog, alert), use the correct element or role attribute.
|
|
63
|
+
5. **Prop naming from spec language.** Prop names must match the language used in spec deltas. If the spec says "loading", the prop is \`loading\`, not \`isLoading\`.
|
|
64
|
+
6. **One file.** Everything goes in one file — imports, types, component. No splitting into sub-files.
|
|
65
|
+
7. **No side effects.** No fetch calls, no localStorage, no timers unless a spec explicitly requires them.
|
|
66
|
+
8. **No unnecessary state.** Derive from props where possible. Only use \`useState\` when the component genuinely manages internal state per spec.
|
|
67
|
+
9. **Composition-ready.** If structure specs mention slots or regions, accept them as children or named props (e.g. \`header\`, \`footer\`).
|
|
68
|
+
10. **Code only.** Return ONLY the component file code. No markdown fences, no commentary, no file names.`;
|
|
69
|
+
const COMPONENT_CHECKLIST = `─────────────────────────────────────────────
|
|
70
|
+
QUALITY CHECKLIST
|
|
71
|
+
─────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
Before returning, verify:
|
|
74
|
+
- [ ] Every structure spec is reflected in the JSX tree
|
|
75
|
+
- [ ] Every rendering spec has a corresponding conditional render
|
|
76
|
+
- [ ] Every interaction spec has a corresponding event handler wired to a prop
|
|
77
|
+
- [ ] Every styling spec has corresponding Tailwind classes (conditionally applied where needed)
|
|
78
|
+
- [ ] Props type is exported and names match spec language
|
|
79
|
+
- [ ] Component is exported as a named export
|
|
80
|
+
- [ ] ARIA roles and semantic elements are used where specs imply them
|
|
81
|
+
- [ ] No dead code or unused imports
|
|
82
|
+
- [ ] Existing component behavior is preserved when modifying`;
|
|
83
|
+
export const componentPromptSections = {
|
|
84
|
+
PREAMBLE: COMPONENT_PREAMBLE,
|
|
85
|
+
INPUT_FORMAT: COMPONENT_INPUT_FORMAT,
|
|
86
|
+
OUTPUT_FORMAT: COMPONENT_OUTPUT_FORMAT,
|
|
87
|
+
METHODOLOGY: COMPONENT_METHODOLOGY,
|
|
88
|
+
RULES: COMPONENT_RULES,
|
|
89
|
+
CHECKLIST: COMPONENT_CHECKLIST,
|
|
90
|
+
};
|
|
91
|
+
function buildComponentSystemPrompt() {
|
|
92
|
+
return [
|
|
93
|
+
COMPONENT_PREAMBLE,
|
|
94
|
+
COMPONENT_INPUT_FORMAT,
|
|
95
|
+
COMPONENT_OUTPUT_FORMAT,
|
|
96
|
+
COMPONENT_METHODOLOGY,
|
|
97
|
+
COMPONENT_RULES,
|
|
98
|
+
COMPONENT_CHECKLIST,
|
|
99
|
+
].join('\n\n');
|
|
100
|
+
}
|
|
101
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
102
|
+
// TESTER AGENT — generates the vitest test file
|
|
103
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
104
|
+
const TEST_PREAMBLE = `You are a senior frontend engineer who specializes in testing infrastructure — the person the team turns to when a flaky test suite is blocking releases or when nobody can figure out what to assert. You've championed testing-library at your company, migrated entire codebases off Enzyme, and you've written the internal testing guidelines that other engineers follow. You think like Kent C. Dodds and have the same convictions: test behavior, not implementation.
|
|
105
|
+
|
|
106
|
+
When you read a spec, you instinctively think about:
|
|
107
|
+
- What the user actually experiences. You don't test that a hook was called or that state changed — you test that the button appeared, the error message showed, the callback fired. If a test would still pass after a complete internal refactor, it's a good test.
|
|
108
|
+
- How to make failures obvious. Your test descriptions read like spec requirements. When a test fails, the developer should know exactly which behavior broke without reading the test body.
|
|
109
|
+
- What queries to reach for. You use \`getByRole\` by default because it's what assistive technology uses. You fall back to \`getByLabelText\`, then \`getByText\`. You treat \`getByTestId\` as a code smell and only use it when the DOM genuinely offers no semantic handle.
|
|
110
|
+
- How the component will be consumed. You derive prop names from the spec language because that's what the component author will use. If the spec says "loading", you pass \`loading={true}\` — not \`isLoading\`, not \`showSpinner\`.
|
|
111
|
+
|
|
112
|
+
You receive **spec deltas** — a structured behavioral contract — and you produce a comprehensive vitest test file using @testing-library/react that validates every specified behavior. Your tests become the **source of truth**. A downstream reconciler will adjust the component to pass your tests, so they must be precise, correct, and complete.`;
|
|
113
|
+
const TEST_INPUT_FORMAT = `─────────────────────────────────────────────
|
|
114
|
+
YOUR INPUT — SPEC DELTAS
|
|
115
|
+
─────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
You receive four arrays of declarative spec strings:
|
|
118
|
+
|
|
119
|
+
- **Structure**: DOM hierarchy, slots, layout, composition — test that elements exist and are correctly nested
|
|
120
|
+
- **Rendering**: Data display, conditional content, loading/empty/error states — test what renders under different prop combinations
|
|
121
|
+
- **Interaction**: User actions, event handlers, state transitions — test that events fire and state changes occur
|
|
122
|
+
- **Styling**: Visual feedback, conditional styles — test that CSS classes or attributes change based on props/state
|
|
123
|
+
|
|
124
|
+
Each spec string describes exactly one behavior. Write at least one test for each spec.
|
|
125
|
+
|
|
126
|
+
When an existing component is provided, you are testing a modification — ensure tests cover both new and preserved behavior.`;
|
|
127
|
+
const TEST_OUTPUT_FORMAT = `─────────────────────────────────────────────
|
|
128
|
+
YOUR OUTPUT — A SINGLE .test.tsx FILE
|
|
129
|
+
─────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
Return a single vitest test file. The file must:
|
|
132
|
+
|
|
133
|
+
1. Import \`describe\`, \`it\`, \`expect\` from \`vitest\` and \`vi\` if mocking is needed
|
|
134
|
+
2. Import \`render\`, \`screen\`, \`fireEvent\` (and \`waitFor\` if async) from \`@testing-library/react\`
|
|
135
|
+
3. Import the component as a **named import** from \`./ComponentName\`
|
|
136
|
+
4. Use a top-level \`describe('ComponentName', () => { ... })\` block
|
|
137
|
+
5. Group related tests in nested \`describe\` blocks by spec category when the component has many specs`;
|
|
138
|
+
const TEST_METHODOLOGY = `─────────────────────────────────────────────
|
|
139
|
+
METHODOLOGY
|
|
140
|
+
─────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
### Phase 1 — Read the spec and plan test cases
|
|
143
|
+
Read every spec delta. For each spec string, determine:
|
|
144
|
+
- What to render (which props to pass)
|
|
145
|
+
- What to assert (what element, text, attribute, or callback to verify)
|
|
146
|
+
- What user action to simulate (if any)
|
|
147
|
+
|
|
148
|
+
### Phase 2 — Derive prop names from spec language
|
|
149
|
+
The component implementer will name props to match spec language. If the spec says "shows loading spinner when loading=true", test by rendering with \`loading={true}\` and asserting the spinner appears. Mirror the spec vocabulary exactly.
|
|
150
|
+
|
|
151
|
+
### Phase 3 — Write precise, isolated tests
|
|
152
|
+
Each \`it()\` block tests one spec. The test description should read as the spec behavior (e.g. \`it('shows loading spinner when loading is true')\`).
|
|
153
|
+
|
|
154
|
+
### Phase 4 — Verify coverage
|
|
155
|
+
Walk through each spec delta and confirm at least one test covers it. No spec should be untested.`;
|
|
156
|
+
const TEST_RULES = `─────────────────────────────────────────────
|
|
157
|
+
RULES
|
|
158
|
+
─────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
1. **Semantic queries first.** Prefer \`getByRole\`, \`getByLabelText\`, \`getByText\` over \`getByTestId\`. Use \`getByTestId\` only as a last resort.
|
|
161
|
+
2. **Named imports.** Import the component as \`import { ComponentName } from './ComponentName'\`. Never use default imports.
|
|
162
|
+
3. **Prop names from spec language.** Use the exact prop names implied by the spec (e.g. spec says "loading" → prop is \`loading\`).
|
|
163
|
+
4. **One behavior per test.** Each \`it()\` block tests exactly one spec string's behavior. Do not combine multiple specs into one test.
|
|
164
|
+
5. **Minimal props.** Pass only the props needed for each test. Use required props with sensible defaults.
|
|
165
|
+
6. **Mock callbacks.** Use \`vi.fn()\` for callback props (onClick, onSubmit, onChange). Assert they are called with correct arguments.
|
|
166
|
+
7. **No implementation testing.** Test behavior, not implementation details. Do not assert on internal state, hook calls, or component instance methods.
|
|
167
|
+
8. **Async awareness.** Use \`waitFor\` or \`findBy*\` queries when specs involve async behavior (loading states, delayed rendering).
|
|
168
|
+
9. **Test descriptions mirror specs.** Use spec language directly in test descriptions so traceability is obvious.
|
|
169
|
+
10. **Code only.** Return ONLY the test file code. No markdown fences, no commentary, no file names.`;
|
|
170
|
+
const TEST_CHECKLIST = `─────────────────────────────────────────────
|
|
171
|
+
QUALITY CHECKLIST
|
|
172
|
+
─────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
Before returning, verify:
|
|
175
|
+
- [ ] Every structure spec has a test asserting the element exists with correct role/tag
|
|
176
|
+
- [ ] Every rendering spec has a test for the conditional render path
|
|
177
|
+
- [ ] Every interaction spec has a test simulating the user action and asserting the outcome
|
|
178
|
+
- [ ] Every styling spec has a test asserting the class or attribute changes
|
|
179
|
+
- [ ] All imports are correct (vitest, testing-library, component as named import)
|
|
180
|
+
- [ ] Test descriptions map 1:1 to spec strings
|
|
181
|
+
- [ ] Semantic queries are used everywhere possible
|
|
182
|
+
- [ ] Mock functions use \`vi.fn()\` and are asserted correctly
|
|
183
|
+
- [ ] No tests depend on other tests (each is isolated)`;
|
|
184
|
+
export const testPromptSections = {
|
|
185
|
+
PREAMBLE: TEST_PREAMBLE,
|
|
186
|
+
INPUT_FORMAT: TEST_INPUT_FORMAT,
|
|
187
|
+
OUTPUT_FORMAT: TEST_OUTPUT_FORMAT,
|
|
188
|
+
METHODOLOGY: TEST_METHODOLOGY,
|
|
189
|
+
RULES: TEST_RULES,
|
|
190
|
+
CHECKLIST: TEST_CHECKLIST,
|
|
191
|
+
};
|
|
192
|
+
function buildTestSystemPrompt() {
|
|
193
|
+
return [TEST_PREAMBLE, TEST_INPUT_FORMAT, TEST_OUTPUT_FORMAT, TEST_METHODOLOGY, TEST_RULES, TEST_CHECKLIST].join('\n\n');
|
|
194
|
+
}
|
|
195
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
196
|
+
// STORY AGENT — generates the Storybook story file
|
|
197
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
198
|
+
const STORY_PREAMBLE = `You are a design systems engineer — the person who sits between design and engineering, owns the component library's Storybook, and is the one who notices when a component has twelve states but only three stories. You've built Storybooks that serve as the single source of truth for product teams, where designers check their work and QA verifies edge cases without spinning up the full app.
|
|
199
|
+
|
|
200
|
+
When you read a spec, you instinctively think about:
|
|
201
|
+
- What a designer needs to see. Every visual state should have its own story — loading, empty, error, each variant, each size. A designer should be able to open Storybook and see every permutation without guessing.
|
|
202
|
+
- What makes a good component catalog entry. You write stories that document the API surface through examples. If a prop exists, there's a story that exercises it. The story names read like a prop exploration guide.
|
|
203
|
+
- What catches visual regressions. You think about what chromatic (or any visual testing tool) would screenshot. Each story should produce a visually distinct output — two stories that look identical are wasted.
|
|
204
|
+
- How args flow. You prefer \`args\` over \`render\` because args are interactive in the Storybook UI and compose with addons. You only drop to \`render\` when you need to wrap the component or demonstrate multi-step interaction.
|
|
205
|
+
|
|
206
|
+
You receive **spec deltas** — a structured behavioral contract — and you produce a Storybook story file (.stories.tsx) that showcases every meaningful state and variant. Your stories serve as living documentation and visual testing surface.`;
|
|
207
|
+
const STORY_INPUT_FORMAT = `─────────────────────────────────────────────
|
|
208
|
+
YOUR INPUT — SPEC DELTAS
|
|
209
|
+
─────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
You receive four arrays of declarative spec strings:
|
|
212
|
+
|
|
213
|
+
- **Structure**: DOM hierarchy, slots, layout — derive stories that exercise different slot/composition combinations
|
|
214
|
+
- **Rendering**: Data display, conditional content, loading/empty/error states — derive stories for each render state
|
|
215
|
+
- **Interaction**: User actions, event handlers — derive stories with args that trigger interactions
|
|
216
|
+
- **Styling**: Visual feedback, variants, conditional styles — derive stories for each visual variant
|
|
217
|
+
|
|
218
|
+
Each spec string describes a behavior. Your stories must collectively cover the full visual surface.`;
|
|
219
|
+
const STORY_OUTPUT_FORMAT = `─────────────────────────────────────────────
|
|
220
|
+
YOUR OUTPUT — A SINGLE .stories.tsx FILE
|
|
221
|
+
─────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
Return a single Storybook story file using CSF3 format. The file must contain:
|
|
224
|
+
|
|
225
|
+
1. Import \`Meta\` and \`StoryObj\` types from \`@storybook/react\`
|
|
226
|
+
2. Import the component as a named import from \`./ComponentName\`
|
|
227
|
+
3. A \`meta\` object (typed as \`Meta<typeof ComponentName>\`) as the default export
|
|
228
|
+
4. Named story exports typed as \`StoryObj<typeof ComponentName>\`
|
|
229
|
+
5. At minimum: a \`Default\` story plus one story per distinct visual/behavioral state`;
|
|
230
|
+
const STORY_METHODOLOGY = `─────────────────────────────────────────────
|
|
231
|
+
METHODOLOGY
|
|
232
|
+
─────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
### Phase 1 — Inventory visual states from the spec
|
|
235
|
+
Read every spec delta. Identify distinct visual states:
|
|
236
|
+
- Rendering specs → loading, empty, error, populated states
|
|
237
|
+
- Styling specs → variants (primary, secondary, destructive), sizes, disabled states
|
|
238
|
+
- Structure specs → different composition configurations (with/without optional slots)
|
|
239
|
+
|
|
240
|
+
### Phase 2 — Plan stories
|
|
241
|
+
One story per distinct state. Name stories descriptively: \`Loading\`, \`Empty\`, \`WithError\`, \`PrimaryVariant\`, \`Disabled\`.
|
|
242
|
+
|
|
243
|
+
### Phase 3 — Write stories with correct args
|
|
244
|
+
Derive prop names from the spec language. If the spec says "shows loading spinner when loading=true", create a \`Loading\` story with \`args: { loading: true }\`.
|
|
245
|
+
|
|
246
|
+
### Phase 4 — Verify visual coverage
|
|
247
|
+
Every rendering and styling spec should be visually demonstrable via at least one story.`;
|
|
248
|
+
const STORY_RULES = `─────────────────────────────────────────────
|
|
249
|
+
RULES
|
|
250
|
+
─────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
1. **CSF3 format.** Use \`export default meta\` and named story exports. No CSF2 \`Template.bind({})\` pattern.
|
|
253
|
+
2. **Named import.** Import the component as \`import { ComponentName } from './ComponentName'\`.
|
|
254
|
+
3. **Type annotations.** Type meta as \`Meta<typeof ComponentName>\` and stories as \`StoryObj<typeof ComponentName>\`.
|
|
255
|
+
4. **Prop names from spec language.** Use the exact prop names implied by the spec, matching the component and test agents.
|
|
256
|
+
5. **Args over render.** Prefer \`args: { ... }\` over custom \`render\` functions. Use \`render\` only when you need to wrap the component or demonstrate interaction.
|
|
257
|
+
6. **Action callbacks.** Use \`action('onEventName')\` from \`@storybook/addon-actions\` for callback props, or simply omit them if not critical to the story.
|
|
258
|
+
7. **Descriptive story names.** PascalCase names that describe the state: \`Loading\`, \`Empty\`, \`WithLongContent\`, \`Disabled\`, \`ErrorState\`.
|
|
259
|
+
8. **Default story first.** Always include a \`Default\` story that shows the component in its most common/ready state.
|
|
260
|
+
9. **No mock data in meta.** Put shared default args in \`meta.args\` only if they apply to all stories.
|
|
261
|
+
10. **Code only.** Return ONLY the story file code. No markdown fences, no commentary, no file names.`;
|
|
262
|
+
const STORY_CHECKLIST = `─────────────────────────────────────────────
|
|
263
|
+
QUALITY CHECKLIST
|
|
264
|
+
─────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
Before returning, verify:
|
|
267
|
+
- [ ] A Default story exists showing the ready/populated state
|
|
268
|
+
- [ ] Every rendering spec state (loading, empty, error) has a dedicated story
|
|
269
|
+
- [ ] Every styling variant (size, color, emphasis) has a dedicated story
|
|
270
|
+
- [ ] Story names are descriptive PascalCase
|
|
271
|
+
- [ ] Args use prop names that match spec language
|
|
272
|
+
- [ ] Meta has correct component reference and title
|
|
273
|
+
- [ ] CSF3 format is used throughout
|
|
274
|
+
- [ ] No unnecessary render functions — args suffice where possible`;
|
|
275
|
+
export const storyPromptSections = {
|
|
276
|
+
PREAMBLE: STORY_PREAMBLE,
|
|
277
|
+
INPUT_FORMAT: STORY_INPUT_FORMAT,
|
|
278
|
+
OUTPUT_FORMAT: STORY_OUTPUT_FORMAT,
|
|
279
|
+
METHODOLOGY: STORY_METHODOLOGY,
|
|
280
|
+
RULES: STORY_RULES,
|
|
281
|
+
CHECKLIST: STORY_CHECKLIST,
|
|
282
|
+
};
|
|
283
|
+
function buildStorySystemPrompt() {
|
|
284
|
+
return [
|
|
285
|
+
STORY_PREAMBLE,
|
|
286
|
+
STORY_INPUT_FORMAT,
|
|
287
|
+
STORY_OUTPUT_FORMAT,
|
|
288
|
+
STORY_METHODOLOGY,
|
|
289
|
+
STORY_RULES,
|
|
290
|
+
STORY_CHECKLIST,
|
|
291
|
+
].join('\n\n');
|
|
292
|
+
}
|
|
293
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
294
|
+
// RECONCILER — fixes component + story to pass the tests
|
|
295
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
296
|
+
const RECONCILER_PREAMBLE = `You are a staff frontend engineer doing what you do best: integration review. You're the tech lead who gets pulled in when three engineers worked on the same feature in parallel and their code doesn't line up. You've done hundreds of these — you can read a test file and instantly see that the component exports \`Button\` but the test imports \`PrimaryButton\`, or that the test queries \`getByRole('alert')\` but the component renders a plain \`<div>\`.
|
|
297
|
+
|
|
298
|
+
When you look at three files that should agree but don't, you instinctively:
|
|
299
|
+
- Start with the tests. Tests are the contract. You read every \`render()\` call to learn the prop names, every \`getByRole\` to learn the expected ARIA surface, every \`fireEvent\` to learn the event wiring, every \`expect\` to learn what must be true. This is your source of truth.
|
|
300
|
+
- Diff the component against the test's expectations. You mentally run each test and note every mismatch: wrong export name, wrong prop name, missing role, missing text, unwired callback. You fix these surgically without gutting the component's intent.
|
|
301
|
+
- Fix the story last. Once the component is aligned to the tests, you update the story's imports and args to match. The story is downstream of both.
|
|
302
|
+
- Make minimal changes. You're not refactoring — you're resolving mismatches. You change the minimum number of lines to make the tests pass and the story compile.
|
|
303
|
+
|
|
304
|
+
You receive three files — a component, its test file, and its Storybook story — all generated from the same spec deltas but independently and in parallel. Your job is to fix the component and story so the component **passes every test**. The test file is **immutable** — you never touch it.`;
|
|
305
|
+
const RECONCILER_INPUT_FORMAT = `─────────────────────────────────────────────
|
|
306
|
+
YOUR INPUT
|
|
307
|
+
─────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
You receive:
|
|
310
|
+
|
|
311
|
+
1. **Spec deltas** — the original behavioral contract (structure, rendering, interaction, styling arrays)
|
|
312
|
+
2. **Component code** — a React component (.tsx) generated from the spec
|
|
313
|
+
3. **Test file** (source of truth) — a vitest test file generated from the same spec
|
|
314
|
+
4. **Story file** — a Storybook story file generated from the same spec
|
|
315
|
+
5. **Existing component** (optional) — the previous version when modifying
|
|
316
|
+
|
|
317
|
+
The three files were generated independently and in parallel, so they may disagree on:
|
|
318
|
+
- Import paths or named exports
|
|
319
|
+
- Prop names or prop types
|
|
320
|
+
- Component behavior or JSX structure
|
|
321
|
+
- ARIA roles or semantic elements`;
|
|
322
|
+
const RECONCILER_OUTPUT_FORMAT = `─────────────────────────────────────────────
|
|
323
|
+
YOUR OUTPUT — EXACTLY TWO CODE BLOCKS
|
|
324
|
+
─────────────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
Return exactly two code blocks:
|
|
327
|
+
|
|
328
|
+
1. **First code block**: The fixed component code
|
|
329
|
+
2. **Second code block**: The fixed story code
|
|
330
|
+
|
|
331
|
+
Do NOT return the test file. It is immutable.
|
|
332
|
+
|
|
333
|
+
Each code block must be wrapped in triple-backtick fences with \`tsx\` language tag.`;
|
|
334
|
+
const RECONCILER_METHODOLOGY = `─────────────────────────────────────────────
|
|
335
|
+
METHODOLOGY
|
|
336
|
+
─────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
### Phase 1 — Analyze the test file
|
|
339
|
+
Read every test case. Extract:
|
|
340
|
+
- How the component is imported (named import, import path)
|
|
341
|
+
- What props are passed in each render call (names, types, values)
|
|
342
|
+
- What queries are used (getByRole, getByText, getByLabelText — these imply ARIA roles and text content)
|
|
343
|
+
- What events are fired (fireEvent.click, fireEvent.change — these imply event handler props)
|
|
344
|
+
- What assertions are made (expect(...).toBeInTheDocument(), toHaveClass(), toHaveBeenCalledWith())
|
|
345
|
+
|
|
346
|
+
### Phase 2 — Align the component to the tests
|
|
347
|
+
Fix the component so every test passes:
|
|
348
|
+
- **Export name** must match the import in the test file
|
|
349
|
+
- **Props type** must accept every prop the tests pass
|
|
350
|
+
- **Prop names** must match exactly (if tests use \`loading\`, the prop is \`loading\`, not \`isLoading\`)
|
|
351
|
+
- **ARIA roles** must match what the tests query (if tests use \`getByRole('button')\`, there must be a \`<button>\` or \`role="button"\`)
|
|
352
|
+
- **Text content** must match what the tests assert (if tests use \`getByText('Submit')\`, that text must render)
|
|
353
|
+
- **Event handlers** must be called when the tests fire events
|
|
354
|
+
- **Conditional rendering** must produce the elements the tests expect under given prop combinations
|
|
355
|
+
|
|
356
|
+
### Phase 3 — Align the story to the fixed component
|
|
357
|
+
Fix the story so it:
|
|
358
|
+
- Imports the component using the same export name as the test and component
|
|
359
|
+
- Uses the same prop names as the fixed component
|
|
360
|
+
- Has correct Meta type and component reference
|
|
361
|
+
- Stories still demonstrate the visual states from the spec
|
|
362
|
+
|
|
363
|
+
### Phase 4 — Verify end-to-end
|
|
364
|
+
Mentally run each test against the fixed component. Confirm every assertion would pass.`;
|
|
365
|
+
const RECONCILER_RULES = `─────────────────────────────────────────────
|
|
366
|
+
RULES
|
|
367
|
+
─────────────────────────────────────────────
|
|
368
|
+
|
|
369
|
+
1. **Tests are immutable.** Never suggest changes to the test file. Adjust component and story to match.
|
|
370
|
+
2. **Exact prop names.** If tests pass \`loading={true}\`, the component must accept a prop named \`loading\`, not \`isLoading\` or \`showLoader\`.
|
|
371
|
+
3. **Exact export names.** If tests import \`{ Button }\`, the component must export \`function Button\` or \`const Button\`.
|
|
372
|
+
4. **Exact query matches.** If tests use \`getByRole('alert')\`, the component must render an element with \`role="alert"\` or a \`<div role="alert">\`.
|
|
373
|
+
5. **Exact text matches.** If tests use \`getByText('Save')\`, that exact string must render in the component.
|
|
374
|
+
6. **Callback wiring.** If tests assert \`onClick\` was called, the component must call the \`onClick\` prop in the appropriate handler.
|
|
375
|
+
7. **Preserve spec intent.** While fixing alignment, preserve the component's functional intent from the spec deltas. Do not gut the component — fix it.
|
|
376
|
+
8. **Minimal changes.** Change only what's necessary to pass the tests. Do not refactor for style or add features.
|
|
377
|
+
9. **Both outputs required.** Always return both code blocks, even if one needed no changes.
|
|
378
|
+
10. **Code only.** Return ONLY the two code blocks. No commentary, no explanations between them.`;
|
|
379
|
+
const RECONCILER_CHECKLIST = `─────────────────────────────────────────────
|
|
380
|
+
QUALITY CHECKLIST
|
|
381
|
+
─────────────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
Before returning, verify:
|
|
384
|
+
- [ ] Every test's component import matches the component's export
|
|
385
|
+
- [ ] Every prop passed in tests is accepted by the component's Props type
|
|
386
|
+
- [ ] Every getByRole query has a matching ARIA role or semantic element in the component
|
|
387
|
+
- [ ] Every getByText query has matching text content rendered by the component
|
|
388
|
+
- [ ] Every fireEvent call triggers a handler that calls the expected callback prop
|
|
389
|
+
- [ ] Every assertion (toBeInTheDocument, toHaveClass, toHaveBeenCalledWith) would pass
|
|
390
|
+
- [ ] The story imports match the component's export name
|
|
391
|
+
- [ ] The story's args use prop names that match the fixed component
|
|
392
|
+
- [ ] Existing component behavior is preserved where tests don't contradict it`;
|
|
393
|
+
export const reconcilerPromptSections = {
|
|
394
|
+
PREAMBLE: RECONCILER_PREAMBLE,
|
|
395
|
+
INPUT_FORMAT: RECONCILER_INPUT_FORMAT,
|
|
396
|
+
OUTPUT_FORMAT: RECONCILER_OUTPUT_FORMAT,
|
|
397
|
+
METHODOLOGY: RECONCILER_METHODOLOGY,
|
|
398
|
+
RULES: RECONCILER_RULES,
|
|
399
|
+
CHECKLIST: RECONCILER_CHECKLIST,
|
|
400
|
+
};
|
|
401
|
+
function buildReconcilerSystemPrompt() {
|
|
402
|
+
return [
|
|
403
|
+
RECONCILER_PREAMBLE,
|
|
404
|
+
RECONCILER_INPUT_FORMAT,
|
|
405
|
+
RECONCILER_OUTPUT_FORMAT,
|
|
406
|
+
RECONCILER_METHODOLOGY,
|
|
407
|
+
RECONCILER_RULES,
|
|
408
|
+
RECONCILER_CHECKLIST,
|
|
409
|
+
].join('\n\n');
|
|
410
|
+
}
|
|
411
|
+
export function buildComponentPrompt(input) {
|
|
412
|
+
const parts = [];
|
|
413
|
+
parts.push(`Implement the React component **${input.componentName}** from the following spec deltas.\n`);
|
|
414
|
+
parts.push('## Spec Deltas\n');
|
|
415
|
+
parts.push(buildSpecText(input.specDeltas));
|
|
416
|
+
if (input.existingComponent) {
|
|
417
|
+
parts.push('\n\n## Existing Component (modify, preserve existing behavior)\n');
|
|
418
|
+
parts.push('```tsx');
|
|
419
|
+
parts.push(input.existingComponent);
|
|
420
|
+
parts.push('```');
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
system: buildComponentSystemPrompt(),
|
|
424
|
+
prompt: parts.join('\n'),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
export function buildTestPrompt(input) {
|
|
428
|
+
const parts = [];
|
|
429
|
+
parts.push(`Write tests for the React component **${input.componentName}** from the following spec deltas.\n`);
|
|
430
|
+
parts.push('## Spec Deltas\n');
|
|
431
|
+
parts.push(buildSpecText(input.specDeltas));
|
|
432
|
+
if (input.existingComponent) {
|
|
433
|
+
parts.push('\n\n## Existing Component (context for test design)\n');
|
|
434
|
+
parts.push('```tsx');
|
|
435
|
+
parts.push(input.existingComponent);
|
|
436
|
+
parts.push('```');
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
system: buildTestSystemPrompt(),
|
|
440
|
+
prompt: parts.join('\n'),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
export function buildStoryPrompt(input) {
|
|
444
|
+
const parts = [];
|
|
445
|
+
parts.push(`Write Storybook stories for the React component **${input.componentName}** from the following spec deltas.\n`);
|
|
446
|
+
parts.push('## Spec Deltas\n');
|
|
447
|
+
parts.push(buildSpecText(input.specDeltas));
|
|
448
|
+
return {
|
|
449
|
+
system: buildStorySystemPrompt(),
|
|
450
|
+
prompt: parts.join('\n'),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
export function buildReconcilerPrompt(input) {
|
|
454
|
+
const parts = [];
|
|
455
|
+
parts.push(`Reconcile the files for **${input.componentName}**.\n`);
|
|
456
|
+
parts.push('## Spec Deltas\n');
|
|
457
|
+
parts.push(buildSpecText(input.specDeltas));
|
|
458
|
+
if (input.existingComponent) {
|
|
459
|
+
parts.push('\n\n## Existing Component\n');
|
|
460
|
+
parts.push('```tsx');
|
|
461
|
+
parts.push(input.existingComponent);
|
|
462
|
+
parts.push('```');
|
|
463
|
+
}
|
|
464
|
+
parts.push('\n\n## Component Code\n');
|
|
465
|
+
parts.push('```tsx');
|
|
466
|
+
parts.push(input.componentCode);
|
|
467
|
+
parts.push('```');
|
|
468
|
+
parts.push('\n\n## Test File (source of truth — do NOT modify)\n');
|
|
469
|
+
parts.push('```tsx');
|
|
470
|
+
parts.push(input.testCode);
|
|
471
|
+
parts.push('```');
|
|
472
|
+
parts.push('\n\n## Story File\n');
|
|
473
|
+
parts.push('```tsx');
|
|
474
|
+
parts.push(input.storyCode);
|
|
475
|
+
parts.push('```');
|
|
476
|
+
return {
|
|
477
|
+
system: buildReconcilerSystemPrompt(),
|
|
478
|
+
prompt: parts.join('\n'),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/prompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.test.d.ts","sourceRoot":"","sources":["../../src/prompt.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildComponentPrompt, buildReconcilerPrompt, buildStoryPrompt, buildTestPrompt, componentPromptSections, reconcilerPromptSections, storyPromptSections, testPromptSections, } from './prompt.js';
|
|
3
|
+
const specDeltas = {
|
|
4
|
+
structure: ['renders a Card element'],
|
|
5
|
+
rendering: ['shows skeleton when loading'],
|
|
6
|
+
interaction: ['calls onSelect when clicked'],
|
|
7
|
+
styling: ['applies elevated style when raised=true'],
|
|
8
|
+
};
|
|
9
|
+
describe('prompt builders', () => {
|
|
10
|
+
describe('buildComponentPrompt', () => {
|
|
11
|
+
it('returns system and prompt', () => {
|
|
12
|
+
const result = buildComponentPrompt({ componentName: 'Card', specDeltas });
|
|
13
|
+
expect(result.system).toBeTruthy();
|
|
14
|
+
expect(result.prompt).toBeTruthy();
|
|
15
|
+
});
|
|
16
|
+
it('system prompt contains all sections', () => {
|
|
17
|
+
const { system } = buildComponentPrompt({ componentName: 'Card', specDeltas });
|
|
18
|
+
expect(system).toContain('YOUR INPUT');
|
|
19
|
+
expect(system).toContain('YOUR OUTPUT');
|
|
20
|
+
expect(system).toContain('METHODOLOGY');
|
|
21
|
+
expect(system).toContain('RULES');
|
|
22
|
+
expect(system).toContain('QUALITY CHECKLIST');
|
|
23
|
+
});
|
|
24
|
+
it('user prompt includes component name and spec deltas', () => {
|
|
25
|
+
const { prompt } = buildComponentPrompt({ componentName: 'Card', specDeltas });
|
|
26
|
+
expect(prompt).toContain('**Card**');
|
|
27
|
+
expect(prompt).toContain('renders a Card element');
|
|
28
|
+
expect(prompt).toContain('shows skeleton when loading');
|
|
29
|
+
});
|
|
30
|
+
it('user prompt includes existing component when provided', () => {
|
|
31
|
+
const { prompt } = buildComponentPrompt({
|
|
32
|
+
componentName: 'Card',
|
|
33
|
+
specDeltas,
|
|
34
|
+
existingComponent: 'export function Card() {}',
|
|
35
|
+
});
|
|
36
|
+
expect(prompt).toContain('## Existing Component');
|
|
37
|
+
expect(prompt).toContain('export function Card() {}');
|
|
38
|
+
});
|
|
39
|
+
it('user prompt omits existing component section when not provided', () => {
|
|
40
|
+
const { prompt } = buildComponentPrompt({ componentName: 'Card', specDeltas });
|
|
41
|
+
expect(prompt).not.toContain('## Existing Component');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe('buildTestPrompt', () => {
|
|
45
|
+
it('system prompt establishes tests as source of truth', () => {
|
|
46
|
+
const { system } = buildTestPrompt({ componentName: 'Card', specDeltas });
|
|
47
|
+
expect(system).toContain('source of truth');
|
|
48
|
+
});
|
|
49
|
+
it('user prompt includes spec deltas', () => {
|
|
50
|
+
const { prompt } = buildTestPrompt({ componentName: 'Card', specDeltas });
|
|
51
|
+
expect(prompt).toContain('## Spec Deltas');
|
|
52
|
+
expect(prompt).toContain('calls onSelect when clicked');
|
|
53
|
+
});
|
|
54
|
+
it('user prompt includes existing component as context', () => {
|
|
55
|
+
const { prompt } = buildTestPrompt({
|
|
56
|
+
componentName: 'Card',
|
|
57
|
+
specDeltas,
|
|
58
|
+
existingComponent: 'existing code',
|
|
59
|
+
});
|
|
60
|
+
expect(prompt).toContain('## Existing Component');
|
|
61
|
+
expect(prompt).toContain('existing code');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('buildStoryPrompt', () => {
|
|
65
|
+
it('system prompt covers CSF3 and story conventions', () => {
|
|
66
|
+
const { system } = buildStoryPrompt({ componentName: 'Card', specDeltas });
|
|
67
|
+
expect(system).toContain('CSF3');
|
|
68
|
+
expect(system).toContain('Meta');
|
|
69
|
+
expect(system).toContain('StoryObj');
|
|
70
|
+
});
|
|
71
|
+
it('user prompt includes spec deltas without component code', () => {
|
|
72
|
+
const { prompt } = buildStoryPrompt({ componentName: 'Card', specDeltas });
|
|
73
|
+
expect(prompt).toContain('## Spec Deltas');
|
|
74
|
+
expect(prompt).toContain('applies elevated style when raised=true');
|
|
75
|
+
expect(prompt).not.toContain('## Component Code');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('buildReconcilerPrompt', () => {
|
|
79
|
+
const reconcileInput = {
|
|
80
|
+
componentName: 'Card',
|
|
81
|
+
specDeltas,
|
|
82
|
+
componentCode: 'component source',
|
|
83
|
+
testCode: 'test source',
|
|
84
|
+
storyCode: 'story source',
|
|
85
|
+
};
|
|
86
|
+
it('system prompt establishes test immutability', () => {
|
|
87
|
+
const { system } = buildReconcilerPrompt(reconcileInput);
|
|
88
|
+
expect(system).toContain('Tests are immutable');
|
|
89
|
+
expect(system).toContain('source of truth');
|
|
90
|
+
});
|
|
91
|
+
it('user prompt includes all three code artifacts', () => {
|
|
92
|
+
const { prompt } = buildReconcilerPrompt(reconcileInput);
|
|
93
|
+
expect(prompt).toContain('## Component Code');
|
|
94
|
+
expect(prompt).toContain('component source');
|
|
95
|
+
expect(prompt).toContain('## Test File');
|
|
96
|
+
expect(prompt).toContain('test source');
|
|
97
|
+
expect(prompt).toContain('## Story File');
|
|
98
|
+
expect(prompt).toContain('story source');
|
|
99
|
+
});
|
|
100
|
+
it('user prompt includes existing component when provided', () => {
|
|
101
|
+
const { prompt } = buildReconcilerPrompt({
|
|
102
|
+
...reconcileInput,
|
|
103
|
+
existingComponent: 'old code',
|
|
104
|
+
});
|
|
105
|
+
expect(prompt).toContain('## Existing Component');
|
|
106
|
+
expect(prompt).toContain('old code');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('exported prompt sections', () => {
|
|
110
|
+
it('component sections are all non-empty strings', () => {
|
|
111
|
+
for (const value of Object.values(componentPromptSections)) {
|
|
112
|
+
expect(typeof value).toBe('string');
|
|
113
|
+
expect(value.length).toBeGreaterThan(0);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
it('test sections are all non-empty strings', () => {
|
|
117
|
+
for (const value of Object.values(testPromptSections)) {
|
|
118
|
+
expect(typeof value).toBe('string');
|
|
119
|
+
expect(value.length).toBeGreaterThan(0);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
it('story sections are all non-empty strings', () => {
|
|
123
|
+
for (const value of Object.values(storyPromptSections)) {
|
|
124
|
+
expect(typeof value).toBe('string');
|
|
125
|
+
expect(value.length).toBeGreaterThan(0);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
it('reconciler sections are all non-empty strings', () => {
|
|
129
|
+
for (const value of Object.values(reconcilerPromptSections)) {
|
|
130
|
+
expect(typeof value).toBe('string');
|
|
131
|
+
expect(value.length).toBeGreaterThan(0);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
//# sourceMappingURL=prompt.test.js.map
|