@auto-engineer/component-implementor-react 1.102.0 → 1.104.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 (83) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +5 -5
  3. package/.turbo/turbo-type-check.log +1 -1
  4. package/CHANGELOG.md +107 -0
  5. package/dist/src/commands/implement-component.d.ts.map +1 -1
  6. package/dist/src/commands/implement-component.js +4 -0
  7. package/dist/src/commands/implement-component.js.map +1 -1
  8. package/dist/src/commands/implement-component.test.js +52 -0
  9. package/dist/src/commands/implement-component.test.js.map +1 -1
  10. package/dist/src/pipeline/run-pipeline.d.ts +8 -0
  11. package/dist/src/pipeline/run-pipeline.d.ts.map +1 -1
  12. package/dist/src/pipeline/run-pipeline.js +8 -0
  13. package/dist/src/pipeline/run-pipeline.js.map +1 -1
  14. package/dist/src/pipeline/run-pipeline.test.js +39 -3
  15. package/dist/src/pipeline/run-pipeline.test.js.map +1 -1
  16. package/dist/src/pipeline/steps/generate-component.test.js +4 -0
  17. package/dist/src/pipeline/steps/generate-component.test.js.map +1 -1
  18. package/dist/src/pipeline/steps/generate-story.test.js +4 -0
  19. package/dist/src/pipeline/steps/generate-story.test.js.map +1 -1
  20. package/dist/src/pipeline/steps/generate-test.test.js +4 -0
  21. package/dist/src/pipeline/steps/generate-test.test.js.map +1 -1
  22. package/dist/src/pipeline/steps/lint-fix-loop.d.ts.map +1 -1
  23. package/dist/src/pipeline/steps/lint-fix-loop.js +4 -3
  24. package/dist/src/pipeline/steps/lint-fix-loop.js.map +1 -1
  25. package/dist/src/pipeline/steps/lint-fix-loop.test.js +17 -0
  26. package/dist/src/pipeline/steps/lint-fix-loop.test.js.map +1 -1
  27. package/dist/src/pipeline/steps/story-fix-loop.d.ts.map +1 -1
  28. package/dist/src/pipeline/steps/story-fix-loop.js +1 -0
  29. package/dist/src/pipeline/steps/story-fix-loop.js.map +1 -1
  30. package/dist/src/pipeline/steps/story-fix-loop.test.js +4 -0
  31. package/dist/src/pipeline/steps/story-fix-loop.test.js.map +1 -1
  32. package/dist/src/pipeline/steps/storybook-test.test.js +4 -0
  33. package/dist/src/pipeline/steps/storybook-test.test.js.map +1 -1
  34. package/dist/src/pipeline/steps/test-fix-loop.d.ts.map +1 -1
  35. package/dist/src/pipeline/steps/test-fix-loop.js +1 -0
  36. package/dist/src/pipeline/steps/test-fix-loop.js.map +1 -1
  37. package/dist/src/pipeline/steps/test-fix-loop.test.js +4 -0
  38. package/dist/src/pipeline/steps/test-fix-loop.test.js.map +1 -1
  39. package/dist/src/pipeline/steps/type-fix-loop.d.ts.map +1 -1
  40. package/dist/src/pipeline/steps/type-fix-loop.js +1 -0
  41. package/dist/src/pipeline/steps/type-fix-loop.js.map +1 -1
  42. package/dist/src/pipeline/steps/type-fix-loop.test.js +4 -0
  43. package/dist/src/pipeline/steps/type-fix-loop.test.js.map +1 -1
  44. package/dist/src/prompt.d.ts +3 -3
  45. package/dist/src/prompt.d.ts.map +1 -1
  46. package/dist/src/prompt.js +29 -9
  47. package/dist/src/prompt.js.map +1 -1
  48. package/dist/src/prompt.test.js +143 -0
  49. package/dist/src/prompt.test.js.map +1 -1
  50. package/dist/src/tools/lint-runner.d.ts.map +1 -1
  51. package/dist/src/tools/lint-runner.js +9 -4
  52. package/dist/src/tools/lint-runner.js.map +1 -1
  53. package/dist/src/tools/lint-runner.test.js +3 -7
  54. package/dist/src/tools/lint-runner.test.js.map +1 -1
  55. package/dist/src/tools/test-runner.js +8 -4
  56. package/dist/src/tools/test-runner.js.map +1 -1
  57. package/dist/src/tools/test-runner.test.js +49 -3
  58. package/dist/src/tools/test-runner.test.js.map +1 -1
  59. package/dist/tsconfig.tsbuildinfo +1 -1
  60. package/package.json +3 -3
  61. package/scripts/generate-all.ts +673 -0
  62. package/src/commands/implement-component.test.ts +52 -0
  63. package/src/commands/implement-component.ts +4 -0
  64. package/src/pipeline/run-pipeline.test.ts +39 -3
  65. package/src/pipeline/run-pipeline.ts +16 -0
  66. package/src/pipeline/steps/generate-component.test.ts +4 -0
  67. package/src/pipeline/steps/generate-story.test.ts +4 -0
  68. package/src/pipeline/steps/generate-test.test.ts +4 -0
  69. package/src/pipeline/steps/lint-fix-loop.test.ts +21 -0
  70. package/src/pipeline/steps/lint-fix-loop.ts +5 -3
  71. package/src/pipeline/steps/story-fix-loop.test.ts +4 -0
  72. package/src/pipeline/steps/story-fix-loop.ts +1 -0
  73. package/src/pipeline/steps/storybook-test.test.ts +4 -0
  74. package/src/pipeline/steps/test-fix-loop.test.ts +4 -0
  75. package/src/pipeline/steps/test-fix-loop.ts +1 -0
  76. package/src/pipeline/steps/type-fix-loop.test.ts +4 -0
  77. package/src/pipeline/steps/type-fix-loop.ts +1 -0
  78. package/src/prompt.test.ts +176 -0
  79. package/src/prompt.ts +29 -9
  80. package/src/tools/lint-runner.test.ts +6 -7
  81. package/src/tools/lint-runner.ts +10 -4
  82. package/src/tools/test-runner.test.ts +55 -3
  83. package/src/tools/test-runner.ts +8 -4
@@ -70,6 +70,10 @@ describe('implement-component', () => {
70
70
  success: true,
71
71
  llmCalls: 4,
72
72
  fixIterations: 1,
73
+ typeFixIterations: 0,
74
+ testFixIterations: 0,
75
+ lintFixIterations: 0,
76
+ storyFixIterations: 0,
73
77
  });
74
78
 
75
79
  const result = await handleImplementComponent(makeCommand());
@@ -102,6 +106,10 @@ describe('implement-component', () => {
102
106
  error: 'Step "Type Fix Loop" failed: errors remain',
103
107
  llmCalls: 5,
104
108
  fixIterations: 3,
109
+ typeFixIterations: 0,
110
+ testFixIterations: 0,
111
+ lintFixIterations: 0,
112
+ storyFixIterations: 0,
105
113
  });
106
114
 
107
115
  const result = await handleImplementComponent(makeCommand());
@@ -125,6 +133,10 @@ describe('implement-component', () => {
125
133
  success: true,
126
134
  llmCalls: 3,
127
135
  fixIterations: 0,
136
+ typeFixIterations: 0,
137
+ testFixIterations: 0,
138
+ lintFixIterations: 0,
139
+ storyFixIterations: 0,
128
140
  });
129
141
 
130
142
  const command = makeCommand({
@@ -182,6 +194,10 @@ describe('implement-component', () => {
182
194
  success: true,
183
195
  llmCalls: 4,
184
196
  fixIterations: 1,
197
+ typeFixIterations: 0,
198
+ testFixIterations: 0,
199
+ lintFixIterations: 0,
200
+ storyFixIterations: 0,
185
201
  });
186
202
 
187
203
  const command = {
@@ -220,6 +236,10 @@ describe('implement-component', () => {
220
236
  success: true,
221
237
  llmCalls: 2,
222
238
  fixIterations: 0,
239
+ typeFixIterations: 0,
240
+ testFixIterations: 0,
241
+ lintFixIterations: 0,
242
+ storyFixIterations: 0,
223
243
  });
224
244
 
225
245
  const command = {
@@ -262,6 +282,10 @@ describe('implement-component', () => {
262
282
  success: true,
263
283
  llmCalls: 4,
264
284
  fixIterations: 0,
285
+ typeFixIterations: 0,
286
+ testFixIterations: 0,
287
+ lintFixIterations: 0,
288
+ storyFixIterations: 0,
265
289
  });
266
290
 
267
291
  const command = makeCommand({
@@ -308,6 +332,10 @@ describe('implement-component', () => {
308
332
  success: true,
309
333
  llmCalls: 2,
310
334
  fixIterations: 0,
335
+ typeFixIterations: 0,
336
+ testFixIterations: 0,
337
+ lintFixIterations: 0,
338
+ storyFixIterations: 0,
311
339
  });
312
340
 
313
341
  await handleImplementComponent(makeCommand());
@@ -346,6 +374,10 @@ describe('implement-component', () => {
346
374
  success: true,
347
375
  llmCalls: 0,
348
376
  fixIterations: 0,
377
+ typeFixIterations: 0,
378
+ testFixIterations: 0,
379
+ lintFixIterations: 0,
380
+ storyFixIterations: 0,
349
381
  });
350
382
 
351
383
  const result = await commandHandler.handle(makeCommand());
@@ -361,6 +393,10 @@ describe('implement-component', () => {
361
393
  success: true,
362
394
  llmCalls: 0,
363
395
  fixIterations: 0,
396
+ typeFixIterations: 0,
397
+ testFixIterations: 0,
398
+ lintFixIterations: 0,
399
+ storyFixIterations: 0,
364
400
  });
365
401
 
366
402
  const command = makeCommand({
@@ -400,6 +436,10 @@ describe('implement-component', () => {
400
436
  success: true,
401
437
  llmCalls: 1,
402
438
  fixIterations: 0,
439
+ typeFixIterations: 0,
440
+ testFixIterations: 0,
441
+ lintFixIterations: 0,
442
+ storyFixIterations: 0,
403
443
  });
404
444
 
405
445
  const command = makeCommand({
@@ -438,6 +478,10 @@ describe('implement-component', () => {
438
478
  success: true,
439
479
  llmCalls: 1,
440
480
  fixIterations: 0,
481
+ typeFixIterations: 0,
482
+ testFixIterations: 0,
483
+ lintFixIterations: 0,
484
+ storyFixIterations: 0,
441
485
  });
442
486
 
443
487
  const command = makeCommand({
@@ -475,6 +519,10 @@ describe('implement-component', () => {
475
519
  success: false,
476
520
  llmCalls: 0,
477
521
  fixIterations: 0,
522
+ typeFixIterations: 0,
523
+ testFixIterations: 0,
524
+ lintFixIterations: 0,
525
+ storyFixIterations: 0,
478
526
  });
479
527
 
480
528
  const result = await handleImplementComponent(makeCommand());
@@ -515,6 +563,10 @@ describe('implement-component', () => {
515
563
  success: true,
516
564
  llmCalls: 0,
517
565
  fixIterations: 0,
566
+ typeFixIterations: 0,
567
+ testFixIterations: 0,
568
+ lintFixIterations: 0,
569
+ storyFixIterations: 0,
518
570
  });
519
571
 
520
572
  const command = makeCommand({
@@ -223,6 +223,10 @@ export async function handleImplementComponent(
223
223
  storyVariants: payload.storyVariants,
224
224
  llmCalls: 0,
225
225
  fixIterations: 0,
226
+ typeFixIterations: 0,
227
+ testFixIterations: 0,
228
+ lintFixIterations: 0,
229
+ storyFixIterations: 0,
226
230
  };
227
231
 
228
232
  const config = buildPipelineConfig();
@@ -65,6 +65,10 @@ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
65
65
  isModify: false,
66
66
  llmCalls: 0,
67
67
  fixIterations: 0,
68
+ typeFixIterations: 0,
69
+ testFixIterations: 0,
70
+ lintFixIterations: 0,
71
+ storyFixIterations: 0,
68
72
  ...overrides,
69
73
  };
70
74
  }
@@ -106,7 +110,15 @@ describe('runPipeline', () => {
106
110
 
107
111
  const result = await runPipeline(steps, makeCtx());
108
112
 
109
- expect(result).toEqual({ success: true, llmCalls: 0, fixIterations: 0 });
113
+ expect(result).toEqual({
114
+ success: true,
115
+ llmCalls: 0,
116
+ fixIterations: 0,
117
+ typeFixIterations: 0,
118
+ testFixIterations: 0,
119
+ lintFixIterations: 0,
120
+ storyFixIterations: 0,
121
+ });
110
122
  expect(executionOrder).toEqual(['A', 'B', 'C']);
111
123
  });
112
124
 
@@ -137,6 +149,10 @@ describe('runPipeline', () => {
137
149
  error: 'Step "Step B" failed: something broke',
138
150
  llmCalls: 0,
139
151
  fixIterations: 0,
152
+ typeFixIterations: 0,
153
+ testFixIterations: 0,
154
+ lintFixIterations: 0,
155
+ storyFixIterations: 0,
140
156
  });
141
157
  expect(executionOrder).toEqual(['A']);
142
158
  });
@@ -144,7 +160,15 @@ describe('runPipeline', () => {
144
160
  it('returns success with empty steps', async () => {
145
161
  const result = await runPipeline([], makeCtx());
146
162
 
147
- expect(result).toEqual({ success: true, llmCalls: 0, fixIterations: 0 });
163
+ expect(result).toEqual({
164
+ success: true,
165
+ llmCalls: 0,
166
+ fixIterations: 0,
167
+ typeFixIterations: 0,
168
+ testFixIterations: 0,
169
+ lintFixIterations: 0,
170
+ storyFixIterations: 0,
171
+ });
148
172
  });
149
173
 
150
174
  it('reports accumulated llmCalls and fixIterations from context', async () => {
@@ -153,7 +177,15 @@ describe('runPipeline', () => {
153
177
 
154
178
  const result = await runPipeline(steps, ctx);
155
179
 
156
- expect(result).toEqual({ success: true, llmCalls: 5, fixIterations: 3 });
180
+ expect(result).toEqual({
181
+ success: true,
182
+ llmCalls: 5,
183
+ fixIterations: 3,
184
+ typeFixIterations: 0,
185
+ testFixIterations: 0,
186
+ lintFixIterations: 0,
187
+ storyFixIterations: 0,
188
+ });
157
189
  });
158
190
 
159
191
  it('reports context metrics on failure', async () => {
@@ -167,6 +199,10 @@ describe('runPipeline', () => {
167
199
  error: 'Step "Broken" failed: oops',
168
200
  llmCalls: 2,
169
201
  fixIterations: 1,
202
+ typeFixIterations: 0,
203
+ testFixIterations: 0,
204
+ lintFixIterations: 0,
205
+ storyFixIterations: 0,
170
206
  });
171
207
  });
172
208
  });
@@ -44,6 +44,10 @@ export type PipelineContext = {
44
44
  storyCode?: string;
45
45
  llmCalls: number;
46
46
  fixIterations: number;
47
+ typeFixIterations: number;
48
+ testFixIterations: number;
49
+ lintFixIterations: number;
50
+ storyFixIterations: number;
47
51
  };
48
52
 
49
53
  export type ModelConfig = {
@@ -79,6 +83,10 @@ export type PipelineResult = {
79
83
  error?: string;
80
84
  llmCalls: number;
81
85
  fixIterations: number;
86
+ typeFixIterations: number;
87
+ testFixIterations: number;
88
+ lintFixIterations: number;
89
+ storyFixIterations: number;
82
90
  };
83
91
 
84
92
  export function buildPipelineSteps(
@@ -146,6 +154,10 @@ export async function runPipeline(steps: PipelineStep[], ctx: PipelineContext):
146
154
  error: `Step "${step.name}" failed: ${result.error}`,
147
155
  llmCalls: ctx.llmCalls,
148
156
  fixIterations: ctx.fixIterations,
157
+ typeFixIterations: ctx.typeFixIterations,
158
+ testFixIterations: ctx.testFixIterations,
159
+ lintFixIterations: ctx.lintFixIterations,
160
+ storyFixIterations: ctx.storyFixIterations,
149
161
  };
150
162
  }
151
163
 
@@ -156,5 +168,9 @@ export async function runPipeline(steps: PipelineStep[], ctx: PipelineContext):
156
168
  success: true,
157
169
  llmCalls: ctx.llmCalls,
158
170
  fixIterations: ctx.fixIterations,
171
+ typeFixIterations: ctx.typeFixIterations,
172
+ testFixIterations: ctx.testFixIterations,
173
+ lintFixIterations: ctx.lintFixIterations,
174
+ storyFixIterations: ctx.storyFixIterations,
159
175
  };
160
176
  }
@@ -37,6 +37,10 @@ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
37
37
  isModify: false,
38
38
  llmCalls: 0,
39
39
  fixIterations: 0,
40
+ typeFixIterations: 0,
41
+ testFixIterations: 0,
42
+ lintFixIterations: 0,
43
+ storyFixIterations: 0,
40
44
  testCode: 'import { render } from "@testing-library/react";',
41
45
  ...overrides,
42
46
  };
@@ -22,6 +22,10 @@ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
22
22
  isModify: false,
23
23
  llmCalls: 0,
24
24
  fixIterations: 0,
25
+ typeFixIterations: 0,
26
+ testFixIterations: 0,
27
+ lintFixIterations: 0,
28
+ storyFixIterations: 0,
25
29
  ...overrides,
26
30
  };
27
31
  }
@@ -36,6 +36,10 @@ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
36
36
  isModify: false,
37
37
  llmCalls: 0,
38
38
  fixIterations: 0,
39
+ typeFixIterations: 0,
40
+ testFixIterations: 0,
41
+ lintFixIterations: 0,
42
+ storyFixIterations: 0,
39
43
  ...overrides,
40
44
  };
41
45
  }
@@ -38,6 +38,10 @@ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
38
38
  isModify: false,
39
39
  llmCalls: 0,
40
40
  fixIterations: 0,
41
+ typeFixIterations: 0,
42
+ testFixIterations: 0,
43
+ lintFixIterations: 0,
44
+ storyFixIterations: 0,
41
45
  componentCode: 'original component',
42
46
  testCode: 'original test',
43
47
  ...overrides,
@@ -132,6 +136,23 @@ describe('lintFixLoop', () => {
132
136
  expect(result).toEqual({ success: true });
133
137
  });
134
138
 
139
+ it('re-runs auto-fix after each LLM write to handle formatting issues', async () => {
140
+ vi.mocked(runLintFix).mockReturnValue({ passed: true, errors: [] });
141
+ vi.mocked(readFile).mockResolvedValue('auto-fixed' as never);
142
+ vi.mocked(runLint)
143
+ .mockReturnValueOnce({ passed: false, errors: ['format error'] })
144
+ .mockReturnValueOnce({ passed: true, errors: [] });
145
+
146
+ vi.mocked(generateText).mockResolvedValue({
147
+ text: '```tsx\nfixed component\n```\n```tsx\nfixed test\n```',
148
+ } as Awaited<ReturnType<typeof generateText>>);
149
+ vi.mocked(writeFile).mockResolvedValue(undefined);
150
+
151
+ await lintFixLoop('mock-model' as never, makeCtx(), 2);
152
+
153
+ expect(runLintFix).toHaveBeenCalledTimes(2);
154
+ });
155
+
135
156
  it('uses empty string fallback when componentCode and testCode are undefined after readFile', async () => {
136
157
  vi.mocked(runLintFix).mockReturnValue({ passed: true, errors: [] });
137
158
  vi.mocked(readFile).mockResolvedValue(undefined as never);
@@ -34,17 +34,19 @@ export async function lintFixLoop(
34
34
  const { text } = await generateText({ model, system, prompt });
35
35
  ctx.llmCalls++;
36
36
  ctx.fixIterations++;
37
+ ctx.lintFixIterations++;
37
38
 
38
39
  const blocks = extractCodeBlocks(text);
39
40
  if (blocks.length >= 2) {
40
- ctx.componentCode = blocks[0];
41
- ctx.testCode = blocks[1];
42
41
  await writeFile(ctx.componentPath, blocks[0], 'utf-8');
43
42
  await writeFile(ctx.testPath, blocks[1], 'utf-8');
44
43
  } else if (blocks.length === 1) {
45
- ctx.componentCode = blocks[0];
46
44
  await writeFile(ctx.componentPath, blocks[0], 'utf-8');
47
45
  }
46
+
47
+ runLintFix(filePaths, ctx.targetDir);
48
+ ctx.componentCode = await readFile(ctx.componentPath, 'utf-8');
49
+ ctx.testCode = await readFile(ctx.testPath, 'utf-8');
48
50
  }
49
51
 
50
52
  const finalResult = runLint(filePaths, ctx.targetDir);
@@ -37,6 +37,10 @@ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
37
37
  isModify: false,
38
38
  llmCalls: 0,
39
39
  fixIterations: 0,
40
+ typeFixIterations: 0,
41
+ testFixIterations: 0,
42
+ lintFixIterations: 0,
43
+ storyFixIterations: 0,
40
44
  storyCode: 'original story',
41
45
  componentCode: 'original component',
42
46
  ...overrides,
@@ -27,6 +27,7 @@ export async function storyFixLoop(
27
27
  const { text } = await generateText({ model, system, prompt });
28
28
  ctx.llmCalls++;
29
29
  ctx.fixIterations++;
30
+ ctx.storyFixIterations++;
30
31
 
31
32
  const fixedStory = extractCodeBlock(text);
32
33
  ctx.storyCode = fixedStory;
@@ -27,6 +27,10 @@ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
27
27
  isModify: false,
28
28
  llmCalls: 0,
29
29
  fixIterations: 0,
30
+ typeFixIterations: 0,
31
+ testFixIterations: 0,
32
+ lintFixIterations: 0,
33
+ storyFixIterations: 0,
30
34
  ...overrides,
31
35
  };
32
36
  }
@@ -37,6 +37,10 @@ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
37
37
  isModify: false,
38
38
  llmCalls: 0,
39
39
  fixIterations: 0,
40
+ typeFixIterations: 0,
41
+ testFixIterations: 0,
42
+ lintFixIterations: 0,
43
+ storyFixIterations: 0,
40
44
  componentCode: 'original component',
41
45
  testCode: 'original test',
42
46
  ...overrides,
@@ -28,6 +28,7 @@ export async function testFixLoop(
28
28
  const { text } = await generateText({ model, system, prompt });
29
29
  ctx.llmCalls++;
30
30
  ctx.fixIterations++;
31
+ ctx.testFixIterations++;
31
32
 
32
33
  const blocks = extractCodeBlocks(text);
33
34
  if (blocks.length >= 2) {
@@ -37,6 +37,10 @@ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
37
37
  isModify: false,
38
38
  llmCalls: 0,
39
39
  fixIterations: 0,
40
+ typeFixIterations: 0,
41
+ testFixIterations: 0,
42
+ lintFixIterations: 0,
43
+ storyFixIterations: 0,
40
44
  componentCode: 'original component',
41
45
  testCode: 'original test',
42
46
  ...overrides,
@@ -27,6 +27,7 @@ export async function typeFixLoop(
27
27
  const { text } = await generateText({ model, system, prompt });
28
28
  ctx.llmCalls++;
29
29
  ctx.fixIterations++;
30
+ ctx.typeFixIterations++;
30
31
 
31
32
  const blocks = extractCodeBlocks(text);
32
33
  if (blocks.length >= 2) {
@@ -40,6 +40,34 @@ describe('prompt builders', () => {
40
40
  expect(system).toContain('QUALITY CHECKLIST');
41
41
  });
42
42
 
43
+ it('system prompt contains strict package boundary rule', () => {
44
+ const { system } = buildComponentPrompt({ componentName: 'Card', specDeltas, projectSection });
45
+ expect(system).toContain('Strict package boundary');
46
+ expect(system).toContain('package.json');
47
+ });
48
+
49
+ it('system prompt contains UI barrel import rule', () => {
50
+ const { system } = buildComponentPrompt({ componentName: 'Card', specDeltas, projectSection });
51
+ expect(system).toContain('UI components from barrel only');
52
+ expect(system).toContain('src/components/ui/index.ts');
53
+ });
54
+
55
+ it('system prompt contains page navigation callback rule', () => {
56
+ const { system } = buildComponentPrompt({ componentName: 'Card', specDeltas, projectSection });
57
+ expect(system).toContain('callback props for navigation');
58
+ expect(system).toContain('Never import a router library');
59
+ });
60
+
61
+ it('system prompt contains controlled input initialization rule', () => {
62
+ const { system } = buildComponentPrompt({ componentName: 'Card', specDeltas, projectSection });
63
+ expect(system).toContain("always initialize text/number inputs with an empty string (`useState('')`)");
64
+ });
65
+
66
+ it('system prompt contains array key rule preventing index-based keys', () => {
67
+ const { system } = buildComponentPrompt({ componentName: 'Card', specDeltas, projectSection });
68
+ expect(system).toContain("Biome's `noArrayIndexKey` rule flags both forms");
69
+ });
70
+
43
71
  it('user prompt includes component name and spec deltas', () => {
44
72
  const { prompt } = buildComponentPrompt({ componentName: 'Card', specDeltas, projectSection });
45
73
  expect(prompt).toContain('**Card**');
@@ -111,6 +139,64 @@ describe('prompt builders', () => {
111
139
  expect(system).toContain('Verify semantic elements from component source');
112
140
  expect(system).toContain('getByText');
113
141
  });
142
+
143
+ it('system prompt contains strict package boundary rule', () => {
144
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas, projectSection });
145
+ expect(system).toContain('Strict package boundary');
146
+ expect(system).toContain('@testing-library/react');
147
+ });
148
+
149
+ it('system prompt contains jsdom limitations rule', () => {
150
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas, projectSection });
151
+ expect(system).toContain('jsdom limitations');
152
+ expect(system).toContain('DataTransfer');
153
+ expect(system).toContain('scrollIntoView');
154
+ });
155
+
156
+ it('system prompt contains individual class testing rule', () => {
157
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas, projectSection });
158
+ expect(system).toContain('Test Tailwind classes individually');
159
+ expect(system).toContain('toHaveClass');
160
+ });
161
+
162
+ it('system prompt contains mock composed components rule', () => {
163
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas, projectSection });
164
+ expect(system).toContain('Mock composed components');
165
+ expect(system).toContain('vi.mock');
166
+ });
167
+
168
+ it('system prompt warns against spreading props in mocks', () => {
169
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas, projectSection });
170
+ expect(system).toContain('Do NOT spread props');
171
+ });
172
+
173
+ it('system prompt warns page tests not to query mocked child elements', () => {
174
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas, projectSection });
175
+ expect(system).toContain('expect.anything()');
176
+ });
177
+
178
+ it('system prompt requires mock.calls direct access for mock component prop assertions', () => {
179
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas, projectSection });
180
+ expect(system).toContain(
181
+ 'NEVER use `toHaveBeenCalledWith` — React passes `(props, ref)` where `ref` is `undefined`',
182
+ );
183
+ expect(system).toContain('MockChild.mock.calls[0]');
184
+ });
185
+
186
+ it('system prompt forbids @testing-library/dom import', () => {
187
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas, projectSection });
188
+ expect(system).toContain('@testing-library/dom');
189
+ });
190
+
191
+ it('system prompt contains vi.mock hoisting rule', () => {
192
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas, projectSection });
193
+ expect(system).toContain('hoisted');
194
+ });
195
+
196
+ it('system prompt contains semantic HTML role mapping rule', () => {
197
+ const { system } = buildTestPrompt({ componentName: 'Card', specDeltas, projectSection });
198
+ expect(system).toContain('`<section>` has role="region"');
199
+ });
114
200
  });
115
201
 
116
202
  describe('buildStoryPrompt', () => {
@@ -366,6 +452,76 @@ describe('prompt builders', () => {
366
452
  expect(result.prompt).toContain('## Component Code');
367
453
  expect(result.prompt).toContain('## Test Code');
368
454
  });
455
+
456
+ it('system prompt contains unresolvable import guidance', () => {
457
+ const result = buildTestFixPrompt({
458
+ componentCode: 'export function Card() {}',
459
+ testCode: 'test code',
460
+ failures: [],
461
+ testOutput: '',
462
+ });
463
+
464
+ expect(result.system).toContain('Unresolvable imports');
465
+ expect(result.system).toContain('Failed to resolve import');
466
+ });
467
+
468
+ it('system prompt contains missing UI component guidance', () => {
469
+ const result = buildTestFixPrompt({
470
+ componentCode: 'export function Card() {}',
471
+ testCode: 'test code',
472
+ failures: [],
473
+ testOutput: '',
474
+ });
475
+
476
+ expect(result.system).toContain('Missing UI component imports');
477
+ expect(result.system).toContain('@/components/ui');
478
+ });
479
+
480
+ it('system prompt contains vi.mock hoisting guidance', () => {
481
+ const result = buildTestFixPrompt({
482
+ componentCode: 'export function Card() {}',
483
+ testCode: 'test code',
484
+ failures: [],
485
+ testOutput: '',
486
+ });
487
+
488
+ expect(result.system).toContain('hoisted');
489
+ });
490
+
491
+ it('system prompt contains mock spy direct access fix guidance', () => {
492
+ const result = buildTestFixPrompt({
493
+ componentCode: 'export function Card() {}',
494
+ testCode: 'test code',
495
+ failures: [],
496
+ testOutput: '',
497
+ });
498
+
499
+ expect(result.system).toContain('replace `toHaveBeenCalledWith` with direct mock call access');
500
+ });
501
+
502
+ it('system prompt contains toHaveStyle replacement guidance', () => {
503
+ const result = buildTestFixPrompt({
504
+ componentCode: 'export function Card() {}',
505
+ testCode: 'test code',
506
+ failures: [],
507
+ testOutput: '',
508
+ });
509
+
510
+ expect(result.system).toContain('toHaveStyle()');
511
+ expect(result.system).toContain('toHaveClass');
512
+ });
513
+
514
+ it('system prompt contains ReferenceError mock import guidance', () => {
515
+ const result = buildTestFixPrompt({
516
+ componentCode: 'export function Card() {}',
517
+ testCode: 'test code',
518
+ failures: [],
519
+ testOutput: '',
520
+ });
521
+
522
+ expect(result.system).toContain('ReferenceError');
523
+ expect(result.system).toContain('Add the import after the `vi.mock` call');
524
+ });
369
525
  });
370
526
 
371
527
  describe('buildLintFixPrompt', () => {
@@ -385,6 +541,26 @@ describe('prompt builders', () => {
385
541
  expect(result.prompt).toContain('## Test Code');
386
542
  expect(result.prompt).toContain('var y = 2');
387
543
  });
544
+
545
+ it('system prompt contains noArrayIndexKey guidance', () => {
546
+ const result = buildLintFixPrompt({
547
+ componentCode: 'const x = 1',
548
+ testCode: 'const y = 2',
549
+ errors: [],
550
+ });
551
+
552
+ expect(result.system).toContain('noArrayIndexKey');
553
+ });
554
+
555
+ it('system prompt covers derived keys like template literals in noArrayIndexKey guidance', () => {
556
+ const result = buildLintFixPrompt({
557
+ componentCode: 'const x = 1',
558
+ testCode: 'const y = 2',
559
+ errors: [],
560
+ });
561
+
562
+ expect(result.system).toContain('inside a template literal');
563
+ });
388
564
  });
389
565
 
390
566
  describe('buildStoryFixPrompt', () => {