@auto-engineer/component-implementor-react 1.110.1 → 1.110.3
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 +42 -0
- package/dist/src/commands/implement-component.d.ts +5 -6
- package/dist/src/commands/implement-component.d.ts.map +1 -1
- package/dist/src/commands/implement-component.js +37 -9
- package/dist/src/commands/implement-component.js.map +1 -1
- package/dist/src/commands/implement-component.test.js +41 -54
- package/dist/src/commands/implement-component.test.js.map +1 -1
- package/dist/src/pipeline/run-pipeline.d.ts +25 -5
- package/dist/src/pipeline/run-pipeline.d.ts.map +1 -1
- package/dist/src/pipeline/run-pipeline.js +47 -17
- package/dist/src/pipeline/run-pipeline.js.map +1 -1
- package/dist/src/pipeline/run-pipeline.test.js +129 -29
- package/dist/src/pipeline/run-pipeline.test.js.map +1 -1
- package/dist/src/pipeline/steps/generate-component.test.js +5 -1
- package/dist/src/pipeline/steps/generate-component.test.js.map +1 -1
- package/dist/src/pipeline/steps/generate-story.test.js +5 -1
- package/dist/src/pipeline/steps/generate-story.test.js.map +1 -1
- package/dist/src/pipeline/steps/generate-test.test.js +5 -1
- package/dist/src/pipeline/steps/generate-test.test.js.map +1 -1
- package/dist/src/pipeline/steps/lint-fix-loop.d.ts +4 -0
- package/dist/src/pipeline/steps/lint-fix-loop.d.ts.map +1 -0
- package/dist/src/pipeline/steps/lint-fix-loop.js +46 -0
- package/dist/src/pipeline/steps/lint-fix-loop.js.map +1 -0
- package/dist/src/pipeline/steps/lint-fix-loop.test.d.ts +2 -0
- package/dist/src/pipeline/steps/lint-fix-loop.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/lint-fix-loop.test.js +123 -0
- package/dist/src/pipeline/steps/lint-fix-loop.test.js.map +1 -0
- package/dist/src/pipeline/steps/story-fix-loop.d.ts +4 -0
- package/dist/src/pipeline/steps/story-fix-loop.d.ts.map +1 -0
- package/dist/src/pipeline/steps/story-fix-loop.js +35 -0
- package/dist/src/pipeline/steps/story-fix-loop.js.map +1 -0
- package/dist/src/pipeline/steps/story-fix-loop.test.d.ts +2 -0
- package/dist/src/pipeline/steps/story-fix-loop.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/story-fix-loop.test.js +98 -0
- package/dist/src/pipeline/steps/story-fix-loop.test.js.map +1 -0
- package/dist/src/pipeline/steps/storybook-test.d.ts +3 -0
- package/dist/src/pipeline/steps/storybook-test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/storybook-test.js +22 -0
- package/dist/src/pipeline/steps/storybook-test.js.map +1 -0
- package/dist/src/pipeline/steps/storybook-test.test.d.ts +2 -0
- package/dist/src/pipeline/steps/storybook-test.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/storybook-test.test.js +70 -0
- package/dist/src/pipeline/steps/storybook-test.test.js.map +1 -0
- package/dist/src/pipeline/steps/test-fix-loop.d.ts +4 -0
- package/dist/src/pipeline/steps/test-fix-loop.d.ts.map +1 -0
- package/dist/src/pipeline/steps/test-fix-loop.js +45 -0
- package/dist/src/pipeline/steps/test-fix-loop.js.map +1 -0
- package/dist/src/pipeline/steps/test-fix-loop.test.d.ts +2 -0
- package/dist/src/pipeline/steps/test-fix-loop.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/test-fix-loop.test.js +172 -0
- package/dist/src/pipeline/steps/test-fix-loop.test.js.map +1 -0
- package/dist/src/pipeline/steps/type-fix-loop.d.ts +4 -0
- package/dist/src/pipeline/steps/type-fix-loop.d.ts.map +1 -0
- package/dist/src/pipeline/steps/type-fix-loop.js +44 -0
- package/dist/src/pipeline/steps/type-fix-loop.js.map +1 -0
- package/dist/src/pipeline/steps/type-fix-loop.test.d.ts +2 -0
- package/dist/src/pipeline/steps/type-fix-loop.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/type-fix-loop.test.js +116 -0
- package/dist/src/pipeline/steps/type-fix-loop.test.js.map +1 -0
- package/dist/src/pipeline/steps/visual-test.d.ts +3 -0
- package/dist/src/pipeline/steps/visual-test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/visual-test.js +4 -0
- package/dist/src/pipeline/steps/visual-test.js.map +1 -0
- package/dist/src/pipeline/steps/visual-test.test.d.ts +2 -0
- package/dist/src/pipeline/steps/visual-test.test.d.ts.map +1 -0
- package/dist/src/pipeline/steps/visual-test.test.js +9 -0
- package/dist/src/pipeline/steps/visual-test.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/commands/implement-component.test.ts +47 -57
- package/src/commands/implement-component.ts +51 -14
- package/src/pipeline/run-pipeline.test.ts +137 -32
- package/src/pipeline/run-pipeline.ts +74 -22
- package/src/pipeline/steps/generate-component.test.ts +5 -1
- package/src/pipeline/steps/generate-story.test.ts +5 -1
- package/src/pipeline/steps/generate-test.test.ts +5 -1
- package/src/pipeline/steps/lint-fix-loop.test.ts +159 -0
- package/src/pipeline/steps/lint-fix-loop.ts +60 -0
- package/src/pipeline/steps/story-fix-loop.test.ts +127 -0
- package/src/pipeline/steps/story-fix-loop.ts +48 -0
- package/src/pipeline/steps/storybook-test.test.ts +86 -0
- package/src/pipeline/steps/storybook-test.ts +27 -0
- package/src/pipeline/steps/test-fix-loop.test.ts +205 -0
- package/src/pipeline/steps/test-fix-loop.ts +57 -0
- package/src/pipeline/steps/type-fix-loop.test.ts +149 -0
- package/src/pipeline/steps/type-fix-loop.ts +56 -0
- package/src/pipeline/steps/visual-test.test.ts +10 -0
- package/src/pipeline/steps/visual-test.ts +5 -0
- package/dist/src/pipeline/steps/fix-from-feedback.d.ts +0 -4
- package/dist/src/pipeline/steps/fix-from-feedback.d.ts.map +0 -1
- package/dist/src/pipeline/steps/fix-from-feedback.js +0 -94
- package/dist/src/pipeline/steps/fix-from-feedback.js.map +0 -1
- package/src/pipeline/steps/fix-from-feedback.ts +0 -105
package/package.json
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"ai": "^6.0.0",
|
|
8
8
|
"debug": "^4.4.1",
|
|
9
|
-
"@auto-engineer/message-bus": "1.110.
|
|
10
|
-
"@auto-engineer/model-factory": "1.110.
|
|
9
|
+
"@auto-engineer/message-bus": "1.110.3",
|
|
10
|
+
"@auto-engineer/model-factory": "1.110.3"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"vitest": "^3.2.1"
|
|
14
14
|
},
|
|
15
|
-
"version": "1.110.
|
|
15
|
+
"version": "1.110.3",
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
@@ -58,6 +58,19 @@ function makeCommand(overrides: Record<string, unknown> = {}) {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function mockPipelineResult(overrides: Record<string, unknown> = {}) {
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
llmCalls: 4,
|
|
65
|
+
fixIterations: 0,
|
|
66
|
+
typeFixIterations: 0,
|
|
67
|
+
testFixIterations: 0,
|
|
68
|
+
lintFixIterations: 0,
|
|
69
|
+
storyFixIterations: 0,
|
|
70
|
+
...overrides,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
describe('implement-component', () => {
|
|
62
75
|
afterEach(() => {
|
|
63
76
|
vi.clearAllMocks();
|
|
@@ -66,10 +79,7 @@ describe('implement-component', () => {
|
|
|
66
79
|
describe('handleImplementComponent', () => {
|
|
67
80
|
it('runs pipeline and returns ComponentImplemented on success', async () => {
|
|
68
81
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
69
|
-
vi.mocked(runPipeline).mockResolvedValue(
|
|
70
|
-
success: true,
|
|
71
|
-
llmCalls: 4,
|
|
72
|
-
});
|
|
82
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult());
|
|
73
83
|
|
|
74
84
|
const result = await handleImplementComponent(makeCommand());
|
|
75
85
|
|
|
@@ -88,8 +98,11 @@ describe('implement-component', () => {
|
|
|
88
98
|
targetDir: '/project/client',
|
|
89
99
|
job: makeCommand().data.job,
|
|
90
100
|
llmCalls: expect.any(Number),
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
fixIterations: 0,
|
|
102
|
+
typeFixIterations: 0,
|
|
103
|
+
testFixIterations: 0,
|
|
104
|
+
lintFixIterations: 0,
|
|
105
|
+
storyFixIterations: 0,
|
|
93
106
|
},
|
|
94
107
|
timestamp: expect.any(Date),
|
|
95
108
|
requestId: 'req-1',
|
|
@@ -101,11 +114,13 @@ describe('implement-component', () => {
|
|
|
101
114
|
|
|
102
115
|
it('returns ComponentImplementationFailed when pipeline fails', async () => {
|
|
103
116
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
104
|
-
vi.mocked(runPipeline).mockResolvedValue(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
vi.mocked(runPipeline).mockResolvedValue(
|
|
118
|
+
mockPipelineResult({
|
|
119
|
+
success: false,
|
|
120
|
+
error: 'Step "Type Fix Loop" failed: errors remain',
|
|
121
|
+
llmCalls: 5,
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
109
124
|
|
|
110
125
|
const result = await handleImplementComponent(makeCommand());
|
|
111
126
|
|
|
@@ -124,10 +139,7 @@ describe('implement-component', () => {
|
|
|
124
139
|
it('reads existing component when modifying', async () => {
|
|
125
140
|
vi.mocked(existsSync).mockReturnValue(true);
|
|
126
141
|
vi.mocked(readFile).mockResolvedValue('existing component code' as never);
|
|
127
|
-
vi.mocked(runPipeline).mockResolvedValue({
|
|
128
|
-
success: true,
|
|
129
|
-
llmCalls: 3,
|
|
130
|
-
});
|
|
142
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult({ llmCalls: 3 }));
|
|
131
143
|
|
|
132
144
|
const command = makeCommand({
|
|
133
145
|
job: {
|
|
@@ -180,10 +192,7 @@ describe('implement-component', () => {
|
|
|
180
192
|
|
|
181
193
|
it('handles payload with undefined spec delta fields without crashing', async () => {
|
|
182
194
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
183
|
-
vi.mocked(runPipeline).mockResolvedValue(
|
|
184
|
-
success: true,
|
|
185
|
-
llmCalls: 4,
|
|
186
|
-
});
|
|
195
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult());
|
|
187
196
|
|
|
188
197
|
const command = {
|
|
189
198
|
type: 'ImplementComponent' as const,
|
|
@@ -217,10 +226,7 @@ describe('implement-component', () => {
|
|
|
217
226
|
|
|
218
227
|
it('defaults targetDir to ./client when not provided', async () => {
|
|
219
228
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
220
|
-
vi.mocked(runPipeline).mockResolvedValue({
|
|
221
|
-
success: true,
|
|
222
|
-
llmCalls: 2,
|
|
223
|
-
});
|
|
229
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult({ llmCalls: 2 }));
|
|
224
230
|
|
|
225
231
|
const command = {
|
|
226
232
|
type: 'ImplementComponent' as const,
|
|
@@ -258,10 +264,7 @@ describe('implement-component', () => {
|
|
|
258
264
|
|
|
259
265
|
it('derives component path from componentId when files.create is empty string', async () => {
|
|
260
266
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
261
|
-
vi.mocked(runPipeline).mockResolvedValue(
|
|
262
|
-
success: true,
|
|
263
|
-
llmCalls: 4,
|
|
264
|
-
});
|
|
267
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult());
|
|
265
268
|
|
|
266
269
|
const command = makeCommand({
|
|
267
270
|
job: {
|
|
@@ -297,8 +300,11 @@ describe('implement-component', () => {
|
|
|
297
300
|
targetDir: '/project/client',
|
|
298
301
|
job: command.data.job,
|
|
299
302
|
llmCalls: expect.any(Number),
|
|
300
|
-
|
|
301
|
-
|
|
303
|
+
fixIterations: 0,
|
|
304
|
+
typeFixIterations: 0,
|
|
305
|
+
testFixIterations: 0,
|
|
306
|
+
lintFixIterations: 0,
|
|
307
|
+
storyFixIterations: 0,
|
|
302
308
|
},
|
|
303
309
|
timestamp: expect.any(Date),
|
|
304
310
|
requestId: 'req-1',
|
|
@@ -308,10 +314,7 @@ describe('implement-component', () => {
|
|
|
308
314
|
|
|
309
315
|
it('passes correct context to pipeline', async () => {
|
|
310
316
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
311
|
-
vi.mocked(runPipeline).mockResolvedValue({
|
|
312
|
-
success: true,
|
|
313
|
-
llmCalls: 2,
|
|
314
|
-
});
|
|
317
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult({ llmCalls: 2 }));
|
|
315
318
|
|
|
316
319
|
await handleImplementComponent(makeCommand());
|
|
317
320
|
|
|
@@ -331,6 +334,11 @@ describe('implement-component', () => {
|
|
|
331
334
|
},
|
|
332
335
|
isModify: false,
|
|
333
336
|
llmCalls: 0,
|
|
337
|
+
fixIterations: 0,
|
|
338
|
+
typeFixIterations: 0,
|
|
339
|
+
testFixIterations: 0,
|
|
340
|
+
lintFixIterations: 0,
|
|
341
|
+
storyFixIterations: 0,
|
|
334
342
|
}),
|
|
335
343
|
);
|
|
336
344
|
});
|
|
@@ -344,10 +352,7 @@ describe('implement-component', () => {
|
|
|
344
352
|
|
|
345
353
|
it('handle delegates to handleImplementComponent', async () => {
|
|
346
354
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
347
|
-
vi.mocked(runPipeline).mockResolvedValue({
|
|
348
|
-
success: true,
|
|
349
|
-
llmCalls: 0,
|
|
350
|
-
});
|
|
355
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult({ llmCalls: 0 }));
|
|
351
356
|
|
|
352
357
|
const result = await commandHandler.handle(makeCommand());
|
|
353
358
|
|
|
@@ -358,10 +363,7 @@ describe('implement-component', () => {
|
|
|
358
363
|
describe('filePathToImportAlias edge cases', () => {
|
|
359
364
|
it('handles paths starting with ./', async () => {
|
|
360
365
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
361
|
-
vi.mocked(runPipeline).mockResolvedValue({
|
|
362
|
-
success: true,
|
|
363
|
-
llmCalls: 0,
|
|
364
|
-
});
|
|
366
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult({ llmCalls: 0 }));
|
|
365
367
|
|
|
366
368
|
const command = makeCommand({
|
|
367
369
|
job: {
|
|
@@ -396,10 +398,7 @@ describe('implement-component', () => {
|
|
|
396
398
|
it('reads existing test when modifying and test file exists', async () => {
|
|
397
399
|
vi.mocked(existsSync).mockReturnValue(true);
|
|
398
400
|
vi.mocked(readFile).mockResolvedValue('existing code' as never);
|
|
399
|
-
vi.mocked(runPipeline).mockResolvedValue({
|
|
400
|
-
success: true,
|
|
401
|
-
llmCalls: 1,
|
|
402
|
-
});
|
|
401
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult({ llmCalls: 1 }));
|
|
403
402
|
|
|
404
403
|
const command = makeCommand({
|
|
405
404
|
job: {
|
|
@@ -433,10 +432,7 @@ describe('implement-component', () => {
|
|
|
433
432
|
it('sets existingTest to undefined when test file does not exist in modify mode', async () => {
|
|
434
433
|
vi.mocked(existsSync).mockReturnValueOnce(true).mockReturnValueOnce(false);
|
|
435
434
|
vi.mocked(readFile).mockResolvedValue('existing component' as never);
|
|
436
|
-
vi.mocked(runPipeline).mockResolvedValue({
|
|
437
|
-
success: true,
|
|
438
|
-
llmCalls: 1,
|
|
439
|
-
});
|
|
435
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult({ llmCalls: 1 }));
|
|
440
436
|
|
|
441
437
|
const command = makeCommand({
|
|
442
438
|
job: {
|
|
@@ -469,10 +465,7 @@ describe('implement-component', () => {
|
|
|
469
465
|
|
|
470
466
|
it('uses fallback error message when pipeline result has no error string', async () => {
|
|
471
467
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
472
|
-
vi.mocked(runPipeline).mockResolvedValue({
|
|
473
|
-
success: false,
|
|
474
|
-
llmCalls: 0,
|
|
475
|
-
});
|
|
468
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult({ success: false, llmCalls: 0 }));
|
|
476
469
|
|
|
477
470
|
const result = await handleImplementComponent(makeCommand());
|
|
478
471
|
|
|
@@ -508,10 +501,7 @@ describe('implement-component', () => {
|
|
|
508
501
|
|
|
509
502
|
it('handles empty files payload gracefully', async () => {
|
|
510
503
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
511
|
-
vi.mocked(runPipeline).mockResolvedValue({
|
|
512
|
-
success: true,
|
|
513
|
-
llmCalls: 0,
|
|
514
|
-
});
|
|
504
|
+
vi.mocked(runPipeline).mockResolvedValue(mockPipelineResult({ llmCalls: 0 }));
|
|
515
505
|
|
|
516
506
|
const command = makeCommand({
|
|
517
507
|
job: {
|
|
@@ -53,7 +53,6 @@ export type ImplementComponentCommand = Command<
|
|
|
53
53
|
{
|
|
54
54
|
targetDir: string;
|
|
55
55
|
job: ComponentJob;
|
|
56
|
-
context?: { previousOutputs?: string; attemptNumber?: number };
|
|
57
56
|
}
|
|
58
57
|
>;
|
|
59
58
|
|
|
@@ -68,8 +67,11 @@ export type ComponentImplementedEvent = Event<
|
|
|
68
67
|
targetDir: string;
|
|
69
68
|
job: ComponentJob;
|
|
70
69
|
llmCalls: number;
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
fixIterations: number;
|
|
71
|
+
typeFixIterations: number;
|
|
72
|
+
testFixIterations: number;
|
|
73
|
+
lintFixIterations: number;
|
|
74
|
+
storyFixIterations: number;
|
|
73
75
|
}
|
|
74
76
|
>;
|
|
75
77
|
|
|
@@ -138,13 +140,37 @@ function buildPipelineConfig(): PipelineConfig {
|
|
|
138
140
|
process.env.IMPL_MODEL ??
|
|
139
141
|
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL ??
|
|
140
142
|
'',
|
|
141
|
-
|
|
142
|
-
process.env.
|
|
143
|
+
typeFixer:
|
|
144
|
+
process.env.STEP_TYPE_FIXER_MODEL ??
|
|
145
|
+
process.env.IMPL_FIXER_MODEL ??
|
|
146
|
+
process.env.IMPL_MODEL ??
|
|
147
|
+
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL ??
|
|
148
|
+
'',
|
|
149
|
+
testFixer:
|
|
150
|
+
process.env.STEP_TEST_FIXER_MODEL ??
|
|
151
|
+
process.env.IMPL_FIXER_MODEL ??
|
|
152
|
+
process.env.IMPL_MODEL ??
|
|
153
|
+
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL ??
|
|
154
|
+
'',
|
|
155
|
+
lintFixer:
|
|
156
|
+
process.env.STEP_LINT_FIXER_MODEL ??
|
|
157
|
+
process.env.IMPL_FIXER_MODEL ??
|
|
158
|
+
process.env.IMPL_MODEL ??
|
|
159
|
+
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL ??
|
|
160
|
+
'',
|
|
161
|
+
storyFixer:
|
|
162
|
+
process.env.STEP_STORY_FIXER_MODEL ??
|
|
143
163
|
process.env.IMPL_FIXER_MODEL ??
|
|
144
164
|
process.env.IMPL_MODEL ??
|
|
145
165
|
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL ??
|
|
146
166
|
'',
|
|
147
167
|
},
|
|
168
|
+
maxTypeFixIterations: Number(process.env.MAX_TYPE_FIX_ITERATIONS ?? 3),
|
|
169
|
+
maxTestFixIterations: Number(process.env.MAX_TEST_FIX_ITERATIONS ?? 3),
|
|
170
|
+
maxLintFixIterations: Number(process.env.MAX_LINT_FIX_ITERATIONS ?? 2),
|
|
171
|
+
maxStoryFixIterations: Number(process.env.MAX_STORY_FIX_ITERATIONS ?? 2),
|
|
172
|
+
enableStorybookTest: process.env.ENABLE_STORYBOOK_TEST === 'true',
|
|
173
|
+
enableVisualTest: process.env.ENABLE_VISUAL_TEST === 'true',
|
|
148
174
|
};
|
|
149
175
|
}
|
|
150
176
|
|
|
@@ -154,7 +180,10 @@ function createPipelineModels(): PipelineModels {
|
|
|
154
180
|
generateTest: model,
|
|
155
181
|
generateComponent: model,
|
|
156
182
|
generateStory: model,
|
|
157
|
-
|
|
183
|
+
typeFixer: model,
|
|
184
|
+
testFixer: model,
|
|
185
|
+
lintFixer: model,
|
|
186
|
+
storyFixer: model,
|
|
158
187
|
};
|
|
159
188
|
}
|
|
160
189
|
|
|
@@ -191,9 +220,6 @@ export async function handleImplementComponent(
|
|
|
191
220
|
|
|
192
221
|
await mkdir(path.dirname(testPath), { recursive: true });
|
|
193
222
|
|
|
194
|
-
const attemptNumber = command.data.context?.attemptNumber ?? 0;
|
|
195
|
-
const errorFeedback = command.data.context?.previousOutputs;
|
|
196
|
-
|
|
197
223
|
const ctx: PipelineContext = {
|
|
198
224
|
componentName,
|
|
199
225
|
componentPath,
|
|
@@ -209,8 +235,11 @@ export async function handleImplementComponent(
|
|
|
209
235
|
existingTest,
|
|
210
236
|
isModify,
|
|
211
237
|
llmCalls: 0,
|
|
212
|
-
|
|
213
|
-
|
|
238
|
+
fixIterations: 0,
|
|
239
|
+
typeFixIterations: 0,
|
|
240
|
+
testFixIterations: 0,
|
|
241
|
+
lintFixIterations: 0,
|
|
242
|
+
storyFixIterations: 0,
|
|
214
243
|
};
|
|
215
244
|
|
|
216
245
|
const config = buildPipelineConfig();
|
|
@@ -231,7 +260,12 @@ export async function handleImplementComponent(
|
|
|
231
260
|
};
|
|
232
261
|
}
|
|
233
262
|
|
|
234
|
-
debug(
|
|
263
|
+
debug(
|
|
264
|
+
'Pipeline succeeded for %s (LLM calls: %d, fix iterations: %d)',
|
|
265
|
+
componentName,
|
|
266
|
+
result.llmCalls,
|
|
267
|
+
result.fixIterations,
|
|
268
|
+
);
|
|
235
269
|
|
|
236
270
|
return {
|
|
237
271
|
type: 'ComponentImplemented',
|
|
@@ -244,8 +278,11 @@ export async function handleImplementComponent(
|
|
|
244
278
|
targetDir,
|
|
245
279
|
job,
|
|
246
280
|
llmCalls: result.llmCalls,
|
|
247
|
-
|
|
248
|
-
|
|
281
|
+
fixIterations: result.fixIterations,
|
|
282
|
+
typeFixIterations: result.typeFixIterations,
|
|
283
|
+
testFixIterations: result.testFixIterations,
|
|
284
|
+
lintFixIterations: result.lintFixIterations,
|
|
285
|
+
storyFixIterations: result.storyFixIterations,
|
|
249
286
|
},
|
|
250
287
|
timestamp: new Date(),
|
|
251
288
|
requestId: command.requestId,
|
|
@@ -7,11 +7,26 @@ vi.mock('./steps/generate-test', () => ({
|
|
|
7
7
|
vi.mock('./steps/generate-component', () => ({
|
|
8
8
|
generateComponentStep: vi.fn(async () => ({ success: true })),
|
|
9
9
|
}));
|
|
10
|
+
vi.mock('./steps/type-fix-loop', () => ({
|
|
11
|
+
typeFixLoop: vi.fn(async () => ({ success: true })),
|
|
12
|
+
}));
|
|
13
|
+
vi.mock('./steps/test-fix-loop', () => ({
|
|
14
|
+
testFixLoop: vi.fn(async () => ({ success: true })),
|
|
15
|
+
}));
|
|
16
|
+
vi.mock('./steps/lint-fix-loop', () => ({
|
|
17
|
+
lintFixLoop: vi.fn(async () => ({ success: true })),
|
|
18
|
+
}));
|
|
10
19
|
vi.mock('./steps/generate-story', () => ({
|
|
11
20
|
generateStoryStep: vi.fn(async () => ({ success: true })),
|
|
12
21
|
}));
|
|
13
|
-
vi.mock('./steps/fix-
|
|
14
|
-
|
|
22
|
+
vi.mock('./steps/story-fix-loop', () => ({
|
|
23
|
+
storyFixLoop: vi.fn(async () => ({ success: true })),
|
|
24
|
+
}));
|
|
25
|
+
vi.mock('./steps/storybook-test', () => ({
|
|
26
|
+
storybookTestStep: vi.fn(async () => ({ success: true })),
|
|
27
|
+
}));
|
|
28
|
+
vi.mock('./steps/visual-test', () => ({
|
|
29
|
+
visualTestStep: vi.fn(async () => ({ success: true })),
|
|
15
30
|
}));
|
|
16
31
|
|
|
17
32
|
import {
|
|
@@ -22,10 +37,15 @@ import {
|
|
|
22
37
|
type PipelineStep,
|
|
23
38
|
runPipeline,
|
|
24
39
|
} from './run-pipeline';
|
|
25
|
-
import { fixFromFeedbackStep } from './steps/fix-from-feedback';
|
|
26
40
|
import { generateComponentStep } from './steps/generate-component';
|
|
27
41
|
import { generateStoryStep } from './steps/generate-story';
|
|
28
42
|
import { generateTestStep } from './steps/generate-test';
|
|
43
|
+
import { lintFixLoop } from './steps/lint-fix-loop';
|
|
44
|
+
import { storyFixLoop } from './steps/story-fix-loop';
|
|
45
|
+
import { storybookTestStep } from './steps/storybook-test';
|
|
46
|
+
import { testFixLoop } from './steps/test-fix-loop';
|
|
47
|
+
import { typeFixLoop } from './steps/type-fix-loop';
|
|
48
|
+
import { visualTestStep } from './steps/visual-test';
|
|
29
49
|
|
|
30
50
|
afterEach(() => {
|
|
31
51
|
vi.clearAllMocks();
|
|
@@ -44,7 +64,11 @@ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
|
|
|
44
64
|
composes: [],
|
|
45
65
|
isModify: false,
|
|
46
66
|
llmCalls: 0,
|
|
47
|
-
|
|
67
|
+
fixIterations: 0,
|
|
68
|
+
typeFixIterations: 0,
|
|
69
|
+
testFixIterations: 0,
|
|
70
|
+
lintFixIterations: 0,
|
|
71
|
+
storyFixIterations: 0,
|
|
48
72
|
...overrides,
|
|
49
73
|
};
|
|
50
74
|
}
|
|
@@ -85,6 +109,11 @@ describe('runPipeline', () => {
|
|
|
85
109
|
expect(result).toEqual({
|
|
86
110
|
success: true,
|
|
87
111
|
llmCalls: 0,
|
|
112
|
+
fixIterations: 0,
|
|
113
|
+
typeFixIterations: 0,
|
|
114
|
+
testFixIterations: 0,
|
|
115
|
+
lintFixIterations: 0,
|
|
116
|
+
storyFixIterations: 0,
|
|
88
117
|
});
|
|
89
118
|
expect(executionOrder).toEqual(['A', 'B', 'C']);
|
|
90
119
|
});
|
|
@@ -115,6 +144,11 @@ describe('runPipeline', () => {
|
|
|
115
144
|
success: false,
|
|
116
145
|
error: 'Step "Step B" failed: something broke',
|
|
117
146
|
llmCalls: 0,
|
|
147
|
+
fixIterations: 0,
|
|
148
|
+
typeFixIterations: 0,
|
|
149
|
+
testFixIterations: 0,
|
|
150
|
+
lintFixIterations: 0,
|
|
151
|
+
storyFixIterations: 0,
|
|
118
152
|
});
|
|
119
153
|
expect(executionOrder).toEqual(['A']);
|
|
120
154
|
});
|
|
@@ -125,24 +159,34 @@ describe('runPipeline', () => {
|
|
|
125
159
|
expect(result).toEqual({
|
|
126
160
|
success: true,
|
|
127
161
|
llmCalls: 0,
|
|
162
|
+
fixIterations: 0,
|
|
163
|
+
typeFixIterations: 0,
|
|
164
|
+
testFixIterations: 0,
|
|
165
|
+
lintFixIterations: 0,
|
|
166
|
+
storyFixIterations: 0,
|
|
128
167
|
});
|
|
129
168
|
});
|
|
130
169
|
|
|
131
|
-
it('reports accumulated llmCalls from context', async () => {
|
|
170
|
+
it('reports accumulated llmCalls and fixIterations from context', async () => {
|
|
132
171
|
const steps: PipelineStep[] = [{ name: 'Step A', run: async () => ({ success: true }) }];
|
|
133
|
-
const ctx = makeCtx({ llmCalls: 5 });
|
|
172
|
+
const ctx = makeCtx({ llmCalls: 5, fixIterations: 3 });
|
|
134
173
|
|
|
135
174
|
const result = await runPipeline(steps, ctx);
|
|
136
175
|
|
|
137
176
|
expect(result).toEqual({
|
|
138
177
|
success: true,
|
|
139
178
|
llmCalls: 5,
|
|
179
|
+
fixIterations: 3,
|
|
180
|
+
typeFixIterations: 0,
|
|
181
|
+
testFixIterations: 0,
|
|
182
|
+
lintFixIterations: 0,
|
|
183
|
+
storyFixIterations: 0,
|
|
140
184
|
});
|
|
141
185
|
});
|
|
142
186
|
|
|
143
187
|
it('reports context metrics on failure', async () => {
|
|
144
188
|
const steps: PipelineStep[] = [failStep('Broken', 'oops')];
|
|
145
|
-
const ctx = makeCtx({ llmCalls: 2 });
|
|
189
|
+
const ctx = makeCtx({ llmCalls: 2, fixIterations: 1 });
|
|
146
190
|
|
|
147
191
|
const result = await runPipeline(steps, ctx);
|
|
148
192
|
|
|
@@ -150,6 +194,11 @@ describe('runPipeline', () => {
|
|
|
150
194
|
success: false,
|
|
151
195
|
error: 'Step "Broken" failed: oops',
|
|
152
196
|
llmCalls: 2,
|
|
197
|
+
fixIterations: 1,
|
|
198
|
+
typeFixIterations: 0,
|
|
199
|
+
testFixIterations: 0,
|
|
200
|
+
lintFixIterations: 0,
|
|
201
|
+
storyFixIterations: 0,
|
|
153
202
|
});
|
|
154
203
|
});
|
|
155
204
|
});
|
|
@@ -161,38 +210,102 @@ function makeModels(): PipelineModels {
|
|
|
161
210
|
generateTest: fakeModel,
|
|
162
211
|
generateComponent: fakeModel,
|
|
163
212
|
generateStory: fakeModel,
|
|
164
|
-
|
|
213
|
+
typeFixer: fakeModel,
|
|
214
|
+
testFixer: fakeModel,
|
|
215
|
+
lintFixer: fakeModel,
|
|
216
|
+
storyFixer: fakeModel,
|
|
165
217
|
};
|
|
166
218
|
}
|
|
167
219
|
|
|
168
|
-
function makeConfig(): PipelineConfig {
|
|
220
|
+
function makeConfig(overrides: Partial<PipelineConfig> = {}): PipelineConfig {
|
|
169
221
|
return {
|
|
170
222
|
models: {
|
|
171
223
|
generateTest: '',
|
|
172
224
|
generateComponent: '',
|
|
173
225
|
generateStory: '',
|
|
174
|
-
|
|
226
|
+
typeFixer: '',
|
|
227
|
+
testFixer: '',
|
|
228
|
+
lintFixer: '',
|
|
229
|
+
storyFixer: '',
|
|
175
230
|
},
|
|
231
|
+
maxTypeFixIterations: 3,
|
|
232
|
+
maxTestFixIterations: 3,
|
|
233
|
+
maxLintFixIterations: 2,
|
|
234
|
+
maxStoryFixIterations: 2,
|
|
235
|
+
enableStorybookTest: false,
|
|
236
|
+
enableVisualTest: false,
|
|
237
|
+
...overrides,
|
|
176
238
|
};
|
|
177
239
|
}
|
|
178
240
|
|
|
179
241
|
describe('buildPipelineSteps', () => {
|
|
180
|
-
it('returns
|
|
242
|
+
it('returns 7 core steps by default', () => {
|
|
181
243
|
const steps = buildPipelineSteps(makeModels(), makeConfig(), makeCtx());
|
|
182
244
|
|
|
183
|
-
expect(steps.map((s) => s.name)).toEqual([
|
|
245
|
+
expect(steps.map((s) => s.name)).toEqual([
|
|
246
|
+
'Generate Test',
|
|
247
|
+
'Generate Component',
|
|
248
|
+
'Type Fix Loop',
|
|
249
|
+
'Test Fix Loop',
|
|
250
|
+
'Lint Fix Loop',
|
|
251
|
+
'Generate Story',
|
|
252
|
+
'Story Fix Loop',
|
|
253
|
+
]);
|
|
184
254
|
});
|
|
185
255
|
|
|
186
|
-
it('
|
|
187
|
-
const
|
|
188
|
-
|
|
256
|
+
it('adds Storybook Test step when enabled', () => {
|
|
257
|
+
const steps = buildPipelineSteps(makeModels(), makeConfig({ enableStorybookTest: true }), makeCtx());
|
|
258
|
+
|
|
259
|
+
expect(steps.map((s) => s.name)).toEqual([
|
|
260
|
+
'Generate Test',
|
|
261
|
+
'Generate Component',
|
|
262
|
+
'Type Fix Loop',
|
|
263
|
+
'Test Fix Loop',
|
|
264
|
+
'Lint Fix Loop',
|
|
265
|
+
'Generate Story',
|
|
266
|
+
'Story Fix Loop',
|
|
267
|
+
'Storybook Test',
|
|
268
|
+
]);
|
|
269
|
+
});
|
|
189
270
|
|
|
190
|
-
|
|
271
|
+
it('adds Visual Test step when enabled', () => {
|
|
272
|
+
const steps = buildPipelineSteps(makeModels(), makeConfig({ enableVisualTest: true }), makeCtx());
|
|
273
|
+
|
|
274
|
+
expect(steps.map((s) => s.name)).toEqual([
|
|
275
|
+
'Generate Test',
|
|
276
|
+
'Generate Component',
|
|
277
|
+
'Type Fix Loop',
|
|
278
|
+
'Test Fix Loop',
|
|
279
|
+
'Lint Fix Loop',
|
|
280
|
+
'Generate Story',
|
|
281
|
+
'Story Fix Loop',
|
|
282
|
+
'Visual Test',
|
|
283
|
+
]);
|
|
191
284
|
});
|
|
192
285
|
|
|
193
|
-
it('
|
|
286
|
+
it('adds both optional steps when both enabled', () => {
|
|
287
|
+
const steps = buildPipelineSteps(
|
|
288
|
+
makeModels(),
|
|
289
|
+
makeConfig({ enableStorybookTest: true, enableVisualTest: true }),
|
|
290
|
+
makeCtx(),
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
expect(steps.map((s) => s.name)).toEqual([
|
|
294
|
+
'Generate Test',
|
|
295
|
+
'Generate Component',
|
|
296
|
+
'Type Fix Loop',
|
|
297
|
+
'Test Fix Loop',
|
|
298
|
+
'Lint Fix Loop',
|
|
299
|
+
'Generate Story',
|
|
300
|
+
'Story Fix Loop',
|
|
301
|
+
'Storybook Test',
|
|
302
|
+
'Visual Test',
|
|
303
|
+
]);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('wires each step to its corresponding function', async () => {
|
|
194
307
|
const models = makeModels();
|
|
195
|
-
const config = makeConfig();
|
|
308
|
+
const config = makeConfig({ enableStorybookTest: true, enableVisualTest: true });
|
|
196
309
|
const ctx = makeCtx();
|
|
197
310
|
const steps = buildPipelineSteps(models, config, ctx);
|
|
198
311
|
|
|
@@ -202,20 +315,12 @@ describe('buildPipelineSteps', () => {
|
|
|
202
315
|
|
|
203
316
|
expect(generateTestStep).toHaveBeenCalledWith(models.generateTest, ctx);
|
|
204
317
|
expect(generateComponentStep).toHaveBeenCalledWith(models.generateComponent, ctx);
|
|
318
|
+
expect(typeFixLoop).toHaveBeenCalledWith(models.typeFixer, ctx, 3);
|
|
319
|
+
expect(testFixLoop).toHaveBeenCalledWith(models.testFixer, ctx, 3);
|
|
320
|
+
expect(lintFixLoop).toHaveBeenCalledWith(models.lintFixer, ctx, 2);
|
|
205
321
|
expect(generateStoryStep).toHaveBeenCalledWith(models.generateStory, ctx);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const models = makeModels();
|
|
210
|
-
const config = makeConfig();
|
|
211
|
-
const ctx = makeCtx({ attemptNumber: 1, errorFeedback: 'Some error' });
|
|
212
|
-
const steps = buildPipelineSteps(models, config, ctx);
|
|
213
|
-
|
|
214
|
-
for (const step of steps) {
|
|
215
|
-
await step.run();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
expect(fixFromFeedbackStep).toHaveBeenCalledWith(models.fixer, ctx);
|
|
219
|
-
expect(generateStoryStep).toHaveBeenCalledWith(models.generateStory, ctx);
|
|
322
|
+
expect(storyFixLoop).toHaveBeenCalledWith(models.storyFixer, ctx, 2);
|
|
323
|
+
expect(storybookTestStep).toHaveBeenCalledWith(ctx, true);
|
|
324
|
+
expect(visualTestStep).toHaveBeenCalled();
|
|
220
325
|
});
|
|
221
326
|
});
|