@auto-engineer/component-implementor-react 1.98.0 → 1.99.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 (233) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +6 -6
  3. package/.turbo/turbo-type-check.log +1 -1
  4. package/CHANGELOG.md +74 -0
  5. package/dist/src/commands/implement-component.d.ts +19 -0
  6. package/dist/src/commands/implement-component.d.ts.map +1 -1
  7. package/dist/src/commands/implement-component.js +109 -30
  8. package/dist/src/commands/implement-component.js.map +1 -1
  9. package/dist/src/commands/implement-component.test.js +259 -69
  10. package/dist/src/commands/implement-component.test.js.map +1 -1
  11. package/dist/src/extract-exports.d.ts +6 -0
  12. package/dist/src/extract-exports.d.ts.map +1 -0
  13. package/dist/src/extract-exports.js +46 -0
  14. package/dist/src/extract-exports.js.map +1 -0
  15. package/dist/src/generate-story-deterministic.d.ts +30 -0
  16. package/dist/src/generate-story-deterministic.d.ts.map +1 -0
  17. package/dist/src/generate-story-deterministic.js +229 -0
  18. package/dist/src/generate-story-deterministic.js.map +1 -0
  19. package/dist/src/index.d.ts +4 -0
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +3 -0
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/pipeline/run-pipeline.d.ts +69 -0
  24. package/dist/src/pipeline/run-pipeline.d.ts.map +1 -0
  25. package/dist/src/pipeline/run-pipeline.js +78 -0
  26. package/dist/src/pipeline/run-pipeline.js.map +1 -0
  27. package/dist/src/pipeline/run-pipeline.test.d.ts +2 -0
  28. package/dist/src/pipeline/run-pipeline.test.d.ts.map +1 -0
  29. package/dist/src/pipeline/run-pipeline.test.js +247 -0
  30. package/dist/src/pipeline/run-pipeline.test.js.map +1 -0
  31. package/dist/src/pipeline/steps/generate-component.d.ts +4 -0
  32. package/dist/src/pipeline/steps/generate-component.d.ts.map +1 -0
  33. package/dist/src/pipeline/steps/generate-component.js +50 -0
  34. package/dist/src/pipeline/steps/generate-component.js.map +1 -0
  35. package/dist/src/pipeline/steps/generate-component.test.d.ts.map +1 -0
  36. package/dist/src/pipeline/steps/generate-component.test.js +106 -0
  37. package/dist/src/pipeline/steps/generate-component.test.js.map +1 -0
  38. package/dist/src/pipeline/steps/generate-story.d.ts +3 -0
  39. package/dist/src/pipeline/steps/generate-story.d.ts.map +1 -0
  40. package/dist/src/pipeline/steps/generate-story.js +14 -0
  41. package/dist/src/pipeline/steps/generate-story.js.map +1 -0
  42. package/dist/src/pipeline/steps/generate-story.test.d.ts.map +1 -0
  43. package/dist/src/pipeline/steps/generate-story.test.js +41 -0
  44. package/dist/src/pipeline/steps/generate-story.test.js.map +1 -0
  45. package/dist/src/pipeline/steps/generate-test.d.ts +4 -0
  46. package/dist/src/pipeline/steps/generate-test.d.ts.map +1 -0
  47. package/dist/src/pipeline/steps/generate-test.js +19 -0
  48. package/dist/src/pipeline/steps/generate-test.js.map +1 -0
  49. package/dist/src/pipeline/steps/generate-test.test.d.ts.map +1 -0
  50. package/dist/src/pipeline/steps/generate-test.test.js +60 -0
  51. package/dist/src/pipeline/steps/generate-test.test.js.map +1 -0
  52. package/dist/src/pipeline/steps/lint-fix-loop.d.ts +4 -0
  53. package/dist/src/pipeline/steps/lint-fix-loop.d.ts.map +1 -0
  54. package/dist/src/pipeline/steps/lint-fix-loop.js +45 -0
  55. package/dist/src/pipeline/steps/lint-fix-loop.js.map +1 -0
  56. package/dist/src/pipeline/steps/lint-fix-loop.test.d.ts +2 -0
  57. package/dist/src/pipeline/steps/lint-fix-loop.test.d.ts.map +1 -0
  58. package/dist/src/pipeline/steps/lint-fix-loop.test.js +119 -0
  59. package/dist/src/pipeline/steps/lint-fix-loop.test.js.map +1 -0
  60. package/dist/src/pipeline/steps/story-fix-loop.d.ts +4 -0
  61. package/dist/src/pipeline/steps/story-fix-loop.d.ts.map +1 -0
  62. package/dist/src/pipeline/steps/story-fix-loop.js +34 -0
  63. package/dist/src/pipeline/steps/story-fix-loop.js.map +1 -0
  64. package/dist/src/pipeline/steps/story-fix-loop.test.d.ts +2 -0
  65. package/dist/src/pipeline/steps/story-fix-loop.test.d.ts.map +1 -0
  66. package/dist/src/pipeline/steps/story-fix-loop.test.js +94 -0
  67. package/dist/src/pipeline/steps/story-fix-loop.test.js.map +1 -0
  68. package/dist/src/pipeline/steps/storybook-test.d.ts +3 -0
  69. package/dist/src/pipeline/steps/storybook-test.d.ts.map +1 -0
  70. package/dist/src/pipeline/steps/storybook-test.js +22 -0
  71. package/dist/src/pipeline/steps/storybook-test.js.map +1 -0
  72. package/dist/src/pipeline/steps/storybook-test.test.d.ts +2 -0
  73. package/dist/src/pipeline/steps/storybook-test.test.d.ts.map +1 -0
  74. package/dist/src/pipeline/steps/storybook-test.test.js +66 -0
  75. package/dist/src/pipeline/steps/storybook-test.test.js.map +1 -0
  76. package/dist/src/pipeline/steps/test-fix-loop.d.ts +4 -0
  77. package/dist/src/pipeline/steps/test-fix-loop.d.ts.map +1 -0
  78. package/dist/src/pipeline/steps/test-fix-loop.js +44 -0
  79. package/dist/src/pipeline/steps/test-fix-loop.js.map +1 -0
  80. package/dist/src/pipeline/steps/test-fix-loop.test.d.ts +2 -0
  81. package/dist/src/pipeline/steps/test-fix-loop.test.d.ts.map +1 -0
  82. package/dist/src/pipeline/steps/test-fix-loop.test.js +168 -0
  83. package/dist/src/pipeline/steps/test-fix-loop.test.js.map +1 -0
  84. package/dist/src/pipeline/steps/type-fix-loop.d.ts +4 -0
  85. package/dist/src/pipeline/steps/type-fix-loop.d.ts.map +1 -0
  86. package/dist/src/pipeline/steps/type-fix-loop.js +43 -0
  87. package/dist/src/pipeline/steps/type-fix-loop.js.map +1 -0
  88. package/dist/src/pipeline/steps/type-fix-loop.test.d.ts +2 -0
  89. package/dist/src/pipeline/steps/type-fix-loop.test.d.ts.map +1 -0
  90. package/dist/src/pipeline/steps/type-fix-loop.test.js +112 -0
  91. package/dist/src/pipeline/steps/type-fix-loop.test.js.map +1 -0
  92. package/dist/src/pipeline/steps/visual-test.d.ts +3 -0
  93. package/dist/src/pipeline/steps/visual-test.d.ts.map +1 -0
  94. package/dist/src/pipeline/steps/visual-test.js +4 -0
  95. package/dist/src/pipeline/steps/visual-test.js.map +1 -0
  96. package/dist/src/pipeline/steps/visual-test.test.d.ts +2 -0
  97. package/dist/src/pipeline/steps/visual-test.test.d.ts.map +1 -0
  98. package/dist/src/pipeline/steps/visual-test.test.js +9 -0
  99. package/dist/src/pipeline/steps/visual-test.test.js.map +1 -0
  100. package/dist/src/project-context.d.ts +10 -0
  101. package/dist/src/project-context.d.ts.map +1 -0
  102. package/dist/src/project-context.js +178 -0
  103. package/dist/src/project-context.js.map +1 -0
  104. package/dist/src/prompt.d.ts +39 -7
  105. package/dist/src/prompt.d.ts.map +1 -1
  106. package/dist/src/prompt.js +233 -23
  107. package/dist/src/prompt.js.map +1 -1
  108. package/dist/src/prompt.test.js +154 -9
  109. package/dist/src/prompt.test.js.map +1 -1
  110. package/dist/src/scaffold.d.ts +49 -0
  111. package/dist/src/scaffold.d.ts.map +1 -0
  112. package/dist/src/scaffold.js +208 -0
  113. package/dist/src/scaffold.js.map +1 -0
  114. package/dist/src/tools/lint-runner.d.ts +7 -0
  115. package/dist/src/tools/lint-runner.d.ts.map +1 -0
  116. package/dist/src/tools/lint-runner.js +48 -0
  117. package/dist/src/tools/lint-runner.js.map +1 -0
  118. package/dist/src/tools/lint-runner.test.d.ts +2 -0
  119. package/dist/src/tools/lint-runner.test.d.ts.map +1 -0
  120. package/dist/src/tools/lint-runner.test.js +90 -0
  121. package/dist/src/tools/lint-runner.test.js.map +1 -0
  122. package/dist/src/tools/storybook-runner.d.ts +6 -0
  123. package/dist/src/tools/storybook-runner.d.ts.map +1 -0
  124. package/dist/src/tools/storybook-runner.js +25 -0
  125. package/dist/src/tools/storybook-runner.js.map +1 -0
  126. package/dist/src/tools/storybook-runner.test.d.ts +2 -0
  127. package/dist/src/tools/storybook-runner.test.d.ts.map +1 -0
  128. package/dist/src/tools/storybook-runner.test.js +43 -0
  129. package/dist/src/tools/storybook-runner.test.js.map +1 -0
  130. package/dist/src/tools/test-runner.d.ts +9 -0
  131. package/dist/src/tools/test-runner.d.ts.map +1 -0
  132. package/dist/src/tools/test-runner.js +74 -0
  133. package/dist/src/tools/test-runner.js.map +1 -0
  134. package/dist/src/tools/test-runner.test.d.ts +2 -0
  135. package/dist/src/tools/test-runner.test.d.ts.map +1 -0
  136. package/dist/src/tools/test-runner.test.js +177 -0
  137. package/dist/src/tools/test-runner.test.js.map +1 -0
  138. package/dist/src/tools/type-checker.d.ts +6 -0
  139. package/dist/src/tools/type-checker.d.ts.map +1 -0
  140. package/dist/src/tools/type-checker.js +36 -0
  141. package/dist/src/tools/type-checker.js.map +1 -0
  142. package/dist/src/tools/type-checker.test.d.ts +2 -0
  143. package/dist/src/tools/type-checker.test.d.ts.map +1 -0
  144. package/dist/src/tools/type-checker.test.js +96 -0
  145. package/dist/src/tools/type-checker.test.js.map +1 -0
  146. package/dist/tsconfig.tsbuildinfo +1 -1
  147. package/inputs/model-a/spec-deltas.json +1460 -0
  148. package/inputs/model-b/spec-deltas.json +1424 -0
  149. package/inputs/model-c/spec-deltas.json +1432 -0
  150. package/inputs/model-d/spec-deltas.json +967 -0
  151. package/inputs/model-e/spec-deltas.json +2292 -0
  152. package/ketchup-plan.md +43 -8
  153. package/package.json +3 -3
  154. package/scoring-heuristic.md +138 -0
  155. package/scripts/improve.ts +23 -18
  156. package/src/commands/implement-component.test.ts +309 -76
  157. package/src/commands/implement-component.ts +155 -31
  158. package/src/extract-exports.ts +53 -0
  159. package/src/generate-story-deterministic.ts +267 -0
  160. package/src/index.ts +12 -0
  161. package/src/pipeline/run-pipeline.test.ts +292 -0
  162. package/src/pipeline/run-pipeline.ts +160 -0
  163. package/src/pipeline/steps/generate-component.test.ts +130 -0
  164. package/src/pipeline/steps/generate-component.ts +60 -0
  165. package/src/pipeline/steps/generate-story.test.ts +54 -0
  166. package/src/pipeline/steps/generate-story.ts +17 -0
  167. package/src/pipeline/steps/generate-test.test.ts +75 -0
  168. package/src/pipeline/steps/generate-test.ts +25 -0
  169. package/src/pipeline/steps/lint-fix-loop.test.ts +155 -0
  170. package/src/pipeline/steps/lint-fix-loop.ts +59 -0
  171. package/src/pipeline/steps/story-fix-loop.test.ts +123 -0
  172. package/src/pipeline/steps/story-fix-loop.ts +47 -0
  173. package/src/pipeline/steps/storybook-test.test.ts +82 -0
  174. package/src/pipeline/steps/storybook-test.ts +27 -0
  175. package/src/pipeline/steps/test-fix-loop.test.ts +201 -0
  176. package/src/pipeline/steps/test-fix-loop.ts +56 -0
  177. package/src/pipeline/steps/type-fix-loop.test.ts +145 -0
  178. package/src/pipeline/steps/type-fix-loop.ts +55 -0
  179. package/src/pipeline/steps/visual-test.test.ts +10 -0
  180. package/src/pipeline/steps/visual-test.ts +5 -0
  181. package/src/project-context.ts +205 -0
  182. package/src/prompt.test.ts +174 -8
  183. package/src/prompt.ts +301 -23
  184. package/src/scaffold.ts +281 -0
  185. package/src/tools/lint-runner.test.ts +112 -0
  186. package/src/tools/lint-runner.ts +52 -0
  187. package/src/tools/storybook-runner.test.ts +53 -0
  188. package/src/tools/storybook-runner.ts +29 -0
  189. package/src/tools/test-runner.test.ts +213 -0
  190. package/src/tools/test-runner.ts +84 -0
  191. package/src/tools/type-checker.test.ts +120 -0
  192. package/src/tools/type-checker.ts +42 -0
  193. package/vitest.config.ts +9 -1
  194. package/dist/src/generate-component.d.ts +0 -4
  195. package/dist/src/generate-component.d.ts.map +0 -1
  196. package/dist/src/generate-component.js +0 -14
  197. package/dist/src/generate-component.js.map +0 -1
  198. package/dist/src/generate-component.test.d.ts.map +0 -1
  199. package/dist/src/generate-component.test.js +0 -73
  200. package/dist/src/generate-component.test.js.map +0 -1
  201. package/dist/src/generate-story.d.ts +0 -4
  202. package/dist/src/generate-story.d.ts.map +0 -1
  203. package/dist/src/generate-story.js +0 -14
  204. package/dist/src/generate-story.js.map +0 -1
  205. package/dist/src/generate-story.test.d.ts.map +0 -1
  206. package/dist/src/generate-story.test.js +0 -58
  207. package/dist/src/generate-story.test.js.map +0 -1
  208. package/dist/src/generate-test.d.ts +0 -4
  209. package/dist/src/generate-test.d.ts.map +0 -1
  210. package/dist/src/generate-test.js +0 -14
  211. package/dist/src/generate-test.js.map +0 -1
  212. package/dist/src/generate-test.test.d.ts.map +0 -1
  213. package/dist/src/generate-test.test.js +0 -77
  214. package/dist/src/generate-test.test.js.map +0 -1
  215. package/dist/src/reconcile.d.ts +0 -8
  216. package/dist/src/reconcile.d.ts.map +0 -1
  217. package/dist/src/reconcile.js +0 -18
  218. package/dist/src/reconcile.js.map +0 -1
  219. package/dist/src/reconcile.test.d.ts +0 -2
  220. package/dist/src/reconcile.test.d.ts.map +0 -1
  221. package/dist/src/reconcile.test.js +0 -108
  222. package/dist/src/reconcile.test.js.map +0 -1
  223. package/src/generate-component.test.ts +0 -89
  224. package/src/generate-component.ts +0 -16
  225. package/src/generate-story.test.ts +0 -71
  226. package/src/generate-story.ts +0 -16
  227. package/src/generate-test.test.ts +0 -93
  228. package/src/generate-test.ts +0 -16
  229. package/src/reconcile.test.ts +0 -127
  230. package/src/reconcile.ts +0 -27
  231. /package/dist/src/{generate-component.test.d.ts → pipeline/steps/generate-component.test.d.ts} +0 -0
  232. /package/dist/src/{generate-story.test.d.ts → pipeline/steps/generate-story.test.d.ts} +0 -0
  233. /package/dist/src/{generate-test.test.d.ts → pipeline/steps/generate-test.test.d.ts} +0 -0
@@ -0,0 +1,213 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ vi.mock('node:child_process', () => ({
4
+ execSync: vi.fn(),
5
+ }));
6
+
7
+ import { execSync } from 'node:child_process';
8
+ import { runTests } from './test-runner';
9
+
10
+ const successJson = JSON.stringify({
11
+ testResults: [
12
+ {
13
+ assertionResults: [
14
+ { status: 'passed', fullName: 'renders button', title: 'renders button', failureMessages: [] },
15
+ { status: 'passed', fullName: 'handles click', title: 'handles click', failureMessages: [] },
16
+ ],
17
+ },
18
+ ],
19
+ });
20
+
21
+ const failureJson = JSON.stringify({
22
+ testResults: [
23
+ {
24
+ assertionResults: [
25
+ { status: 'passed', fullName: 'renders button', title: 'renders button', failureMessages: [] },
26
+ {
27
+ status: 'failed',
28
+ fullName: 'handles click',
29
+ title: 'handles click',
30
+ failureMessages: ['Expected onClick to have been called'],
31
+ },
32
+ ],
33
+ },
34
+ ],
35
+ });
36
+
37
+ describe('runTests', () => {
38
+ it('returns passed with counts when all tests pass', () => {
39
+ vi.mocked(execSync).mockReturnValue(successJson);
40
+
41
+ const result = runTests('src/Button.test.tsx', '/project');
42
+
43
+ expect(result).toEqual({
44
+ passed: true,
45
+ numPassed: 2,
46
+ numFailed: 0,
47
+ failures: [],
48
+ output: expect.any(String),
49
+ });
50
+ expect(execSync).toHaveBeenCalledWith('npx vitest run --reporter=json src/Button.test.tsx', {
51
+ cwd: '/project',
52
+ stdio: 'pipe',
53
+ encoding: 'utf-8',
54
+ });
55
+ });
56
+
57
+ it('returns failures when tests fail', () => {
58
+ const error = { stdout: failureJson, stderr: '' };
59
+ vi.mocked(execSync).mockImplementation(() => {
60
+ throw error;
61
+ });
62
+
63
+ const result = runTests('src/Button.test.tsx', '/project');
64
+
65
+ expect(result).toEqual({
66
+ passed: false,
67
+ numPassed: 1,
68
+ numFailed: 1,
69
+ failures: ['handles click: Expected onClick to have been called'],
70
+ output: expect.any(String),
71
+ });
72
+ });
73
+
74
+ it('handles non-JSON output gracefully', () => {
75
+ const error = { stdout: 'Something went wrong', stderr: '' };
76
+ vi.mocked(execSync).mockImplementation(() => {
77
+ throw error;
78
+ });
79
+
80
+ const result = runTests('src/Button.test.tsx', '/project');
81
+
82
+ expect(result).toEqual({
83
+ passed: false,
84
+ numPassed: 0,
85
+ numFailed: 0,
86
+ failures: [],
87
+ output: expect.any(String),
88
+ });
89
+ });
90
+
91
+ it('handles empty testResults array', () => {
92
+ vi.mocked(execSync).mockReturnValue(JSON.stringify({ testResults: [] }));
93
+
94
+ const result = runTests('src/Button.test.tsx', '/project');
95
+
96
+ expect(result).toEqual({
97
+ passed: false,
98
+ numPassed: 0,
99
+ numFailed: 0,
100
+ failures: [],
101
+ output: expect.any(String),
102
+ });
103
+ });
104
+
105
+ it('truncates output to 2000 chars', () => {
106
+ const longOutput = JSON.stringify({ testResults: [] }) + 'x'.repeat(3000);
107
+ vi.mocked(execSync).mockReturnValue(longOutput);
108
+
109
+ const result = runTests('src/Button.test.tsx', '/project');
110
+
111
+ expect(result.output.length).toBeLessThanOrEqual(2000);
112
+ });
113
+
114
+ it('handles error object without stdout or stderr properties', () => {
115
+ vi.mocked(execSync).mockImplementation(() => {
116
+ throw new Error('command failed');
117
+ });
118
+
119
+ const result = runTests('src/Button.test.tsx', '/project');
120
+
121
+ expect(result).toEqual({
122
+ passed: false,
123
+ numPassed: 0,
124
+ numFailed: 0,
125
+ failures: [],
126
+ output: expect.any(String),
127
+ });
128
+ });
129
+
130
+ it('handles missing testResults in JSON', () => {
131
+ vi.mocked(execSync).mockReturnValue(JSON.stringify({}));
132
+
133
+ const result = runTests('src/Button.test.tsx', '/project');
134
+
135
+ expect(result).toEqual({
136
+ passed: false,
137
+ numPassed: 0,
138
+ numFailed: 0,
139
+ failures: [],
140
+ output: expect.any(String),
141
+ });
142
+ });
143
+
144
+ it('handles missing assertionResults in suite', () => {
145
+ vi.mocked(execSync).mockReturnValue(JSON.stringify({ testResults: [{}] }));
146
+
147
+ const result = runTests('src/Button.test.tsx', '/project');
148
+
149
+ expect(result).toEqual({
150
+ passed: false,
151
+ numPassed: 0,
152
+ numFailed: 0,
153
+ failures: [],
154
+ output: expect.any(String),
155
+ });
156
+ });
157
+
158
+ it('handles failed test with missing fullName and failureMessages', () => {
159
+ const json = JSON.stringify({
160
+ testResults: [
161
+ {
162
+ assertionResults: [{ status: 'failed', title: 'test title' }],
163
+ },
164
+ ],
165
+ });
166
+ vi.mocked(execSync).mockReturnValue(json);
167
+
168
+ const result = runTests('src/Button.test.tsx', '/project');
169
+
170
+ expect(result).toEqual({
171
+ passed: false,
172
+ numPassed: 0,
173
+ numFailed: 1,
174
+ failures: ['test title: '],
175
+ output: expect.any(String),
176
+ });
177
+ });
178
+
179
+ it('handles failed test with no fullName and no title', () => {
180
+ const json = JSON.stringify({
181
+ testResults: [
182
+ {
183
+ assertionResults: [{ status: 'failed' }],
184
+ },
185
+ ],
186
+ });
187
+ vi.mocked(execSync).mockReturnValue(json);
188
+
189
+ const result = runTests('src/Button.test.tsx', '/project');
190
+
191
+ expect(result).toEqual({
192
+ passed: false,
193
+ numPassed: 0,
194
+ numFailed: 1,
195
+ failures: ['unknown test: '],
196
+ output: expect.any(String),
197
+ });
198
+ });
199
+
200
+ it('handles malformed JSON after opening brace', () => {
201
+ vi.mocked(execSync).mockReturnValue('prefix {invalid json');
202
+
203
+ const result = runTests('src/Button.test.tsx', '/project');
204
+
205
+ expect(result).toEqual({
206
+ passed: false,
207
+ numPassed: 0,
208
+ numFailed: 0,
209
+ failures: [],
210
+ output: expect.any(String),
211
+ });
212
+ });
213
+ });
@@ -0,0 +1,84 @@
1
+ import { execSync } from 'node:child_process';
2
+
3
+ export type TestRunResult = {
4
+ passed: boolean;
5
+ numPassed: number;
6
+ numFailed: number;
7
+ failures: string[];
8
+ output: string;
9
+ };
10
+
11
+ export function runTests(testFilePath: string, targetDir: string): TestRunResult {
12
+ try {
13
+ const stdout = execSync(`npx vitest run --reporter=json ${testFilePath}`, {
14
+ cwd: targetDir,
15
+ stdio: 'pipe',
16
+ encoding: 'utf-8',
17
+ });
18
+
19
+ const parsed = parseVitestJson(stdout);
20
+ return parsed;
21
+ } catch (err: unknown) {
22
+ let stdout = '';
23
+ let stderr = '';
24
+ if (typeof err === 'object' && err !== null) {
25
+ if ('stdout' in err && typeof err.stdout === 'string') stdout = err.stdout;
26
+ if ('stderr' in err && typeof err.stderr === 'string') stderr = err.stderr;
27
+ }
28
+ const combined = stdout + '\n' + stderr;
29
+
30
+ const parsed = parseVitestJson(combined);
31
+ return parsed;
32
+ }
33
+ }
34
+
35
+ function parseVitestJson(output: string): TestRunResult {
36
+ const jsonStart = output.indexOf('{');
37
+ if (jsonStart === -1) {
38
+ return {
39
+ passed: false,
40
+ numPassed: 0,
41
+ numFailed: 0,
42
+ failures: [],
43
+ output: output.slice(0, 2000),
44
+ };
45
+ }
46
+
47
+ try {
48
+ const json = JSON.parse(output.slice(jsonStart));
49
+ const testResults = json.testResults ?? [];
50
+
51
+ let numPassed = 0;
52
+ let numFailed = 0;
53
+ const failures: string[] = [];
54
+
55
+ for (const suite of testResults) {
56
+ for (const test of suite.assertionResults ?? []) {
57
+ if (test.status === 'passed') {
58
+ numPassed++;
59
+ } else if (test.status === 'failed') {
60
+ numFailed++;
61
+ const name = test.fullName ?? test.title ?? 'unknown test';
62
+ const message = (test.failureMessages ?? []).join('\n');
63
+ failures.push(`${name}: ${message}`);
64
+ }
65
+ }
66
+ }
67
+
68
+ return {
69
+ passed: numFailed === 0 && numPassed > 0,
70
+ numPassed,
71
+ numFailed,
72
+ failures,
73
+ output: output.slice(0, 2000),
74
+ };
75
+ } catch {
76
+ return {
77
+ passed: false,
78
+ numPassed: 0,
79
+ numFailed: 0,
80
+ failures: [],
81
+ output: output.slice(0, 2000),
82
+ };
83
+ }
84
+ }
@@ -0,0 +1,120 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ vi.mock('node:child_process', () => ({
4
+ execSync: vi.fn(),
5
+ }));
6
+
7
+ import { execSync } from 'node:child_process';
8
+ import { runTypeCheck } from './type-checker';
9
+
10
+ describe('runTypeCheck', () => {
11
+ it('returns passed when tsc succeeds', () => {
12
+ vi.mocked(execSync).mockReturnValue('');
13
+
14
+ const result = runTypeCheck('/project', ['src/Button.tsx']);
15
+
16
+ expect(result).toEqual({ passed: true, errors: [] });
17
+ expect(execSync).toHaveBeenCalledWith('pnpm type-check', {
18
+ cwd: '/project',
19
+ stdio: 'pipe',
20
+ encoding: 'utf-8',
21
+ });
22
+ });
23
+
24
+ it('returns relevant errors when tsc fails with errors in generated files', () => {
25
+ const error = {
26
+ stderr: '',
27
+ stdout:
28
+ 'src/Button.tsx(5,10): error TS2307: Cannot find module\nsrc/Other.tsx(1,1): error TS2304: Cannot find name',
29
+ };
30
+ vi.mocked(execSync).mockImplementation(() => {
31
+ throw error;
32
+ });
33
+
34
+ const result = runTypeCheck('/project', ['src/Button.tsx']);
35
+
36
+ expect(result).toEqual({
37
+ passed: false,
38
+ errors: ['src/Button.tsx(5,10): error TS2307: Cannot find module'],
39
+ });
40
+ });
41
+
42
+ it('returns passed when errors exist only in non-generated files', () => {
43
+ const error = {
44
+ stderr: '',
45
+ stdout: 'src/Other.tsx(1,1): error TS2304: Cannot find name',
46
+ };
47
+ vi.mocked(execSync).mockImplementation(() => {
48
+ throw error;
49
+ });
50
+
51
+ const result = runTypeCheck('/project', ['src/Button.tsx']);
52
+
53
+ expect(result).toEqual({ passed: true, errors: [] });
54
+ });
55
+
56
+ it('strips leading ./ from generated file paths for matching', () => {
57
+ const error = {
58
+ stderr: '',
59
+ stdout: 'src/Button.tsx(5,10): error TS2307: Cannot find module',
60
+ };
61
+ vi.mocked(execSync).mockImplementation(() => {
62
+ throw error;
63
+ });
64
+
65
+ const result = runTypeCheck('/project', ['./src/Button.tsx']);
66
+
67
+ expect(result).toEqual({
68
+ passed: false,
69
+ errors: ['src/Button.tsx(5,10): error TS2307: Cannot find module'],
70
+ });
71
+ });
72
+
73
+ it('matches errors across multiple generated files', () => {
74
+ const error = {
75
+ stderr: '',
76
+ stdout:
77
+ 'src/Button.tsx(5,10): error TS2307: Module not found\nsrc/Button.test.tsx(3,1): error TS2304: Name not found',
78
+ };
79
+ vi.mocked(execSync).mockImplementation(() => {
80
+ throw error;
81
+ });
82
+
83
+ const result = runTypeCheck('/project', ['src/Button.tsx', 'src/Button.test.tsx']);
84
+
85
+ expect(result).toEqual({
86
+ passed: false,
87
+ errors: [
88
+ 'src/Button.tsx(5,10): error TS2307: Module not found',
89
+ 'src/Button.test.tsx(3,1): error TS2304: Name not found',
90
+ ],
91
+ });
92
+ });
93
+
94
+ it('combines stderr and stdout when parsing errors', () => {
95
+ const error = {
96
+ stderr: 'src/Button.tsx(1,1): error TS1005: Expected semicolon',
97
+ stdout: '',
98
+ };
99
+ vi.mocked(execSync).mockImplementation(() => {
100
+ throw error;
101
+ });
102
+
103
+ const result = runTypeCheck('/project', ['src/Button.tsx']);
104
+
105
+ expect(result).toEqual({
106
+ passed: false,
107
+ errors: ['src/Button.tsx(1,1): error TS1005: Expected semicolon'],
108
+ });
109
+ });
110
+
111
+ it('handles error object without stdout or stderr properties', () => {
112
+ vi.mocked(execSync).mockImplementation(() => {
113
+ throw new Error('command failed');
114
+ });
115
+
116
+ const result = runTypeCheck('/project', ['src/Button.tsx']);
117
+
118
+ expect(result).toEqual({ passed: true, errors: [] });
119
+ });
120
+ });
@@ -0,0 +1,42 @@
1
+ import { execSync } from 'node:child_process';
2
+
3
+ export type TypeCheckResult = {
4
+ passed: boolean;
5
+ errors: string[];
6
+ };
7
+
8
+ export function runTypeCheck(targetDir: string, generatedFiles: string[]): TypeCheckResult {
9
+ try {
10
+ execSync('pnpm type-check', {
11
+ cwd: targetDir,
12
+ stdio: 'pipe',
13
+ encoding: 'utf-8',
14
+ });
15
+ return { passed: true, errors: [] };
16
+ } catch (err: unknown) {
17
+ let stderr = '';
18
+ let stdout = '';
19
+ if (typeof err === 'object' && err !== null) {
20
+ if ('stderr' in err && typeof err.stderr === 'string') stderr = err.stderr;
21
+ if ('stdout' in err && typeof err.stdout === 'string') stdout = err.stdout;
22
+ }
23
+ const combined = stderr + '\n' + stdout;
24
+
25
+ const errorLines = combined.split('\n').filter((line) => /error TS\d+/.test(line));
26
+
27
+ const normalizedPaths = new Set(generatedFiles.map((f) => f.replace(/^\.\//, '')));
28
+
29
+ const relevantErrors = errorLines.filter((line) => {
30
+ for (const filePath of normalizedPaths) {
31
+ if (line.includes(filePath)) return true;
32
+ }
33
+ return false;
34
+ });
35
+
36
+ if (relevantErrors.length === 0) {
37
+ return { passed: true, errors: [] };
38
+ }
39
+
40
+ return { passed: false, errors: relevantErrors };
41
+ }
42
+ }
package/vitest.config.ts CHANGED
@@ -7,7 +7,15 @@ export default defineConfig({
7
7
  coverage: {
8
8
  provider: 'v8',
9
9
  include: ['src/**/*.ts'],
10
- exclude: ['src/index.ts', 'src/**/*.test.ts'],
10
+ exclude: [
11
+ 'src/index.ts',
12
+ 'src/**/*.test.ts',
13
+ 'src/run.ts',
14
+ 'src/project-context.ts',
15
+ 'src/scaffold.ts',
16
+ 'src/extract-exports.ts',
17
+ 'src/generate-story-deterministic.ts',
18
+ ],
11
19
  thresholds: { statements: 100, branches: 100, functions: 100, lines: 100 },
12
20
  },
13
21
  },
@@ -1,4 +0,0 @@
1
- import { type ComponentPromptInput } from './prompt';
2
- export type GenerateComponentInput = ComponentPromptInput;
3
- export declare function generateComponentFile(input: GenerateComponentInput): Promise<string>;
4
- //# sourceMappingURL=generate-component.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-component.d.ts","sourceRoot":"","sources":["../../src/generate-component.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAE3E,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAE1D,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ1F"}
@@ -1,14 +0,0 @@
1
- import { createModelFromEnv } from '@auto-engineer/model-factory';
2
- import { generateText } from 'ai';
3
- import { extractCodeBlock } from './extract-code-block.js';
4
- import { buildComponentPrompt } from './prompt.js';
5
- export async function generateComponentFile(input) {
6
- const { system, prompt } = buildComponentPrompt(input);
7
- const { text } = await generateText({
8
- model: createModelFromEnv(),
9
- system,
10
- prompt,
11
- });
12
- return extractCodeBlock(text);
13
- }
14
- //# sourceMappingURL=generate-component.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-component.js","sourceRoot":"","sources":["../../src/generate-component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAA6B,MAAM,UAAU,CAAC;AAI3E,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,KAA6B;IACvE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IACvD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,YAAY,CAAC;QAClC,KAAK,EAAE,kBAAkB,EAAE;QAC3B,MAAM;QACN,MAAM;KACP,CAAC,CAAC;IACH,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-component.test.d.ts","sourceRoot":"","sources":["../../src/generate-component.test.ts"],"names":[],"mappings":""}
@@ -1,73 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { generateComponentFile } from './generate-component.js';
3
- vi.mock('ai', () => ({
4
- generateText: vi.fn(),
5
- }));
6
- vi.mock('@auto-engineer/model-factory', () => ({
7
- createModelFromEnv: vi.fn(() => 'mock-model'),
8
- }));
9
- import { generateText } from 'ai';
10
- const specDeltas = {
11
- structure: ['renders a Button element'],
12
- rendering: ['shows loading spinner when loading=true'],
13
- interaction: ['calls onClick when clicked'],
14
- styling: ['applies primary variant by default'],
15
- };
16
- describe('generateComponentFile', () => {
17
- afterEach(() => {
18
- vi.clearAllMocks();
19
- });
20
- it('returns generated component code from AI', async () => {
21
- const mockGenerateText = vi.mocked(generateText);
22
- mockGenerateText.mockResolvedValue({
23
- text: '```tsx\nexport function Button() { return <button />; }\n```',
24
- });
25
- const result = await generateComponentFile({
26
- componentName: 'Button',
27
- specDeltas,
28
- });
29
- expect(result).toBe('export function Button() { return <button />; }');
30
- });
31
- it('passes system prompt with methodology, rules, and checklist', async () => {
32
- const mockGenerateText = vi.mocked(generateText);
33
- mockGenerateText.mockResolvedValue({
34
- text: 'component code',
35
- });
36
- await generateComponentFile({ componentName: 'Button', specDeltas });
37
- const system = mockGenerateText.mock.calls[0][0].system;
38
- expect(system).toContain('staff-level frontend engineer');
39
- expect(system).toContain('METHODOLOGY');
40
- expect(system).toContain('RULES');
41
- expect(system).toContain('QUALITY CHECKLIST');
42
- expect(system).toContain('Named exports');
43
- expect(system).toContain('Tailwind CSS');
44
- expect(system).toContain('ARIA roles');
45
- });
46
- it('includes spec deltas in the user prompt', async () => {
47
- const mockGenerateText = vi.mocked(generateText);
48
- mockGenerateText.mockResolvedValue({
49
- text: 'component code',
50
- });
51
- await generateComponentFile({ componentName: 'Button', specDeltas });
52
- const prompt = mockGenerateText.mock.calls[0][0].prompt;
53
- expect(prompt).toContain('## Spec Deltas');
54
- expect(prompt).toContain('## Structure');
55
- expect(prompt).toContain('renders a Button element');
56
- expect(prompt).toContain('**Button**');
57
- });
58
- it('includes existing component code when provided', async () => {
59
- const mockGenerateText = vi.mocked(generateText);
60
- mockGenerateText.mockResolvedValue({
61
- text: 'updated code',
62
- });
63
- await generateComponentFile({
64
- componentName: 'Button',
65
- specDeltas,
66
- existingComponent: 'export function Button() {}',
67
- });
68
- const prompt = mockGenerateText.mock.calls[0][0].prompt;
69
- expect(prompt).toContain('## Existing Component');
70
- expect(prompt).toContain('export function Button() {}');
71
- });
72
- });
73
- //# sourceMappingURL=generate-component.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-component.test.js","sourceRoot":"","sources":["../../src/generate-component.test.ts"],"names":[],"mappings":"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"}
@@ -1,4 +0,0 @@
1
- import { type StoryPromptInput } from './prompt';
2
- export type GenerateStoryInput = StoryPromptInput;
3
- export declare function generateStoryFile(input: GenerateStoryInput): Promise<string>;
4
- //# sourceMappingURL=generate-story.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-story.d.ts","sourceRoot":"","sources":["../../src/generate-story.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEnE,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AAElD,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlF"}
@@ -1,14 +0,0 @@
1
- import { createModelFromEnv } from '@auto-engineer/model-factory';
2
- import { generateText } from 'ai';
3
- import { extractCodeBlock } from './extract-code-block.js';
4
- import { buildStoryPrompt } from './prompt.js';
5
- export async function generateStoryFile(input) {
6
- const { system, prompt } = buildStoryPrompt(input);
7
- const { text } = await generateText({
8
- model: createModelFromEnv(),
9
- system,
10
- prompt,
11
- });
12
- return extractCodeBlock(text);
13
- }
14
- //# sourceMappingURL=generate-story.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-story.js","sourceRoot":"","sources":["../../src/generate-story.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAyB,MAAM,UAAU,CAAC;AAInE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAyB;IAC/D,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACnD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,YAAY,CAAC;QAClC,KAAK,EAAE,kBAAkB,EAAE;QAC3B,MAAM;QACN,MAAM;KACP,CAAC,CAAC;IACH,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-story.test.d.ts","sourceRoot":"","sources":["../../src/generate-story.test.ts"],"names":[],"mappings":""}
@@ -1,58 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { generateStoryFile } from './generate-story.js';
3
- vi.mock('ai', () => ({
4
- generateText: vi.fn(),
5
- }));
6
- vi.mock('@auto-engineer/model-factory', () => ({
7
- createModelFromEnv: vi.fn(() => 'mock-model'),
8
- }));
9
- import { generateText } from 'ai';
10
- const specDeltas = {
11
- structure: ['renders a Button element'],
12
- rendering: ['shows loading spinner when loading=true'],
13
- interaction: ['calls onClick when clicked'],
14
- styling: ['applies primary variant by default'],
15
- };
16
- describe('generateStoryFile', () => {
17
- afterEach(() => {
18
- vi.clearAllMocks();
19
- });
20
- it('returns generated story code from AI', async () => {
21
- const mockGenerateText = vi.mocked(generateText);
22
- mockGenerateText.mockResolvedValue({
23
- text: '```tsx\nimport type { Meta } from "@storybook/react";\n```',
24
- });
25
- const result = await generateStoryFile({
26
- componentName: 'Button',
27
- specDeltas,
28
- });
29
- expect(result).toBe('import type { Meta } from "@storybook/react";');
30
- });
31
- it('passes system prompt with methodology, rules, and checklist', async () => {
32
- const mockGenerateText = vi.mocked(generateText);
33
- mockGenerateText.mockResolvedValue({
34
- text: 'story code',
35
- });
36
- await generateStoryFile({ componentName: 'Button', specDeltas });
37
- const system = mockGenerateText.mock.calls[0][0].system;
38
- expect(system).toContain('design systems engineer');
39
- expect(system).toContain('METHODOLOGY');
40
- expect(system).toContain('RULES');
41
- expect(system).toContain('QUALITY CHECKLIST');
42
- expect(system).toContain('CSF3 format');
43
- expect(system).toContain('Named import');
44
- });
45
- it('includes spec deltas in the user prompt but not component code', async () => {
46
- const mockGenerateText = vi.mocked(generateText);
47
- mockGenerateText.mockResolvedValue({
48
- text: 'story code',
49
- });
50
- await generateStoryFile({ componentName: 'Button', specDeltas });
51
- const prompt = mockGenerateText.mock.calls[0][0].prompt;
52
- expect(prompt).toContain('## Spec Deltas');
53
- expect(prompt).toContain('## Structure');
54
- expect(prompt).toContain('renders a Button element');
55
- expect(prompt).not.toContain('## Component Code');
56
- });
57
- });
58
- //# sourceMappingURL=generate-story.test.js.map