@auto-engineer/component-implementer 0.10.5 → 0.11.11

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/package.json CHANGED
@@ -19,14 +19,14 @@
19
19
  "openai": "^5.7.0",
20
20
  "vite": "^5.4.1",
21
21
  "zod": "^3.25.67",
22
- "@auto-engineer/ai-gateway": "0.11.9",
23
- "@auto-engineer/message-bus": "0.11.9"
22
+ "@auto-engineer/message-bus": "0.11.11",
23
+ "@auto-engineer/ai-gateway": "0.11.11"
24
24
  },
25
25
  "devDependencies": {
26
26
  "playwright": "^1.54.1",
27
- "@auto-engineer/cli": "0.11.9"
27
+ "@auto-engineer/cli": "0.11.11"
28
28
  },
29
- "version": "0.10.5",
29
+ "version": "0.11.11",
30
30
  "scripts": {
31
31
  "start": "tsx src/index.ts",
32
32
  "build": "tsc && tsx ../../scripts/fix-esm-imports.ts && cp src/*.html dist/src/ 2>/dev/null || true",
@@ -1,10 +1,17 @@
1
+ // noinspection ExceptionCaughtLocallyJS
2
+
1
3
  import { type Command, defineCommandHandler, type Event } from '@auto-engineer/message-bus';
2
4
  import * as fs from 'fs/promises';
3
5
  import * as path from 'path';
4
6
  import createDebug from 'debug';
5
- import { callAI, getProjectContext, loadScheme } from '../agent';
7
+ import { callAI, loadScheme } from '../agent';
8
+ import { execa } from 'execa';
9
+ import { performance } from 'perf_hooks';
6
10
 
7
- const debug = createDebug('frontend-implementer:implement-component');
11
+ const debug = createDebug('auto:client-implementer:component');
12
+ const debugTypeCheck = createDebug('auto:client-implementer:component:typecheck');
13
+ const debugProcess = createDebug('auto:client-implementer:component:process');
14
+ const debugResult = createDebug('auto:client-implementer:component:result');
8
15
 
9
16
  export type ImplementComponentCommand = Command<
10
17
  'ImplementComponent',
@@ -13,6 +20,7 @@ export type ImplementComponentCommand = Command<
13
20
  iaSchemeDir: string;
14
21
  designSystemPath: string;
15
22
  componentType: 'atom' | 'molecule' | 'organism' | 'page';
23
+ filePath: string;
16
24
  componentName: string;
17
25
  failures?: string[];
18
26
  }
@@ -35,6 +43,7 @@ export type ComponentImplementationFailedEvent = Event<
35
43
  error: string;
36
44
  componentType: string;
37
45
  componentName: string;
46
+ filePath: string;
38
47
  }
39
48
  >;
40
49
 
@@ -55,11 +64,9 @@ export const commandHandler = defineCommandHandler<
55
64
  description: 'Type of component: atom|molecule|organism|page',
56
65
  required: true,
57
66
  },
67
+ filePath: { description: 'Component file path', required: true },
58
68
  componentName: { description: 'Name of component to implement', required: true },
59
- failures: {
60
- description: 'Any failures from previous implementations',
61
- required: false,
62
- },
69
+ failures: { description: 'Any failures from previous implementations', required: false },
63
70
  },
64
71
  examples: [
65
72
  '$ auto implement:component --project-dir=./client --ia-scheme-dir=./.context --design-system-path=./design-system.md --component-type=molecule --component-name=SurveyCard',
@@ -70,78 +77,139 @@ export const commandHandler = defineCommandHandler<
70
77
  ): Promise<ComponentImplementedEvent | ComponentImplementationFailedEvent> => {
71
78
  const result = await handleImplementComponentCommandInternal(command);
72
79
  if (result.type === 'ComponentImplemented') {
73
- debug(
74
- 'Component implemented: %s/%s at %s',
75
- result.data.componentType,
76
- result.data.componentName,
77
- result.data.filePath,
78
- );
80
+ debug('Component implemented successfully: %s/%s', result.data.componentType, result.data.componentName);
79
81
  } else {
80
- debug('Failed: %s', result.data.error);
82
+ debug('Component implementation failed: %s', result.data.error);
81
83
  }
82
84
  return result;
83
85
  },
84
86
  });
85
87
 
88
+ // eslint-disable-next-line complexity
86
89
  async function handleImplementComponentCommandInternal(
87
90
  command: ImplementComponentCommand,
88
91
  ): Promise<ComponentImplementedEvent | ComponentImplementationFailedEvent> {
89
- const { projectDir, iaSchemeDir, designSystemPath, componentType, componentName, failures = [] } = command.data;
92
+ const { projectDir, iaSchemeDir, designSystemPath, componentType, componentName, filePath } = command.data;
90
93
 
91
94
  try {
92
- const userPreferencesFile = path.resolve(projectDir, 'design-system-principles.md');
93
- const [userPreferences, designSystem] = await Promise.all([
94
- fs.readFile(userPreferencesFile, 'utf-8'),
95
- fs.readFile(designSystemPath, 'utf-8'),
96
- ]);
95
+ const start = performance.now();
96
+ debugProcess(`Starting ${componentType}:${componentName}`);
97
97
 
98
+ const t1 = performance.now();
98
99
  const scheme = await loadScheme(iaSchemeDir);
99
- if (!scheme) {
100
- throw new Error('IA scheme not found');
101
- }
100
+ debugProcess(`[1] Loaded IA scheme in ${(performance.now() - t1).toFixed(2)} ms`);
101
+ if (!scheme) throw new Error('IA scheme not found');
102
102
 
103
103
  const pluralKey = `${componentType}s`;
104
104
  const collection = (scheme as Record<string, unknown>)[pluralKey];
105
- if (!isValidCollection(collection)) {
106
- throw new Error(`Invalid IA schema structure for ${pluralKey}`);
107
- }
105
+ if (!isValidCollection(collection)) throw new Error(`Invalid IA schema structure for ${pluralKey}`);
108
106
 
109
107
  const items = (collection as { items: Record<string, unknown> }).items;
110
108
  const componentDef = items[componentName] as Record<string, unknown> | undefined;
111
- if (!componentDef) {
112
- throw new Error(`Component ${componentType}:${componentName} not found in IA schema`);
109
+ if (!componentDef) throw new Error(`Component ${componentType}:${componentName} not found in IA schema`);
110
+
111
+ const outPath = path.join(projectDir, '..', filePath);
112
+
113
+ const t2 = performance.now();
114
+ let existingScaffold = '';
115
+ try {
116
+ existingScaffold = await fs.readFile(outPath, 'utf-8');
117
+ debugProcess(`[2] Found existing scaffold in ${(performance.now() - t2).toFixed(2)} ms`);
118
+ } catch {
119
+ debugProcess(`[2] No existing scaffold found (${(performance.now() - t2).toFixed(2)} ms)`);
113
120
  }
114
121
 
115
- const ctx = await getProjectContext(projectDir, iaSchemeDir, userPreferences, designSystem, failures);
122
+ const t3 = performance.now();
123
+ const projectConfig = await readAllTopLevelFiles(projectDir);
124
+ debugProcess(`[3] Loaded project + gql/graphql files in ${(performance.now() - t3).toFixed(2)} ms`);
116
125
 
117
- const prompt = makeComponentPrompt(ctx, componentType, componentName, componentDef);
126
+ const t4 = performance.now();
127
+ const designSystemReference = await readDesignSystem(designSystemPath, { projectDir, iaSchemeDir });
128
+ debugProcess(`[4] Loaded design system reference in ${(performance.now() - t4).toFixed(2)} ms`);
118
129
 
119
- const code = await callAI(prompt);
130
+ const dependencyList = await resolveDependenciesRecursively(
131
+ scheme as Record<string, unknown>,
132
+ componentType,
133
+ componentName,
134
+ );
135
+
136
+ debugProcess(`[5] Resolved ${dependencyList.length} dependencies for ${componentName}`);
137
+
138
+ const dependencySources: Record<string, string> = {};
139
+ for (const dep of dependencyList) {
140
+ const depSource = await readComponentSource(projectDir, dep.type, dep.name);
141
+ if (depSource != null) dependencySources[`${dep.type}/${dep.name}`] = depSource;
142
+ }
143
+
144
+ const basePrompt = makeBasePrompt(
145
+ componentType,
146
+ componentName,
147
+ componentDef,
148
+ existingScaffold,
149
+ projectConfig,
150
+ designSystemReference,
151
+ dependencySources,
152
+ );
120
153
 
121
- const outPath = path.join(projectDir, 'src/components', `${componentType}s`, `${componentName}.tsx`);
122
154
  await fs.mkdir(path.dirname(outPath), { recursive: true });
123
- await fs.writeFile(outPath, code, 'utf-8');
124
155
 
125
- return {
126
- type: 'ComponentImplemented',
127
- data: {
128
- filePath: outPath,
129
- componentType,
130
- componentName,
131
- composition: extractComposition(componentDef),
132
- specs: extractSpecs(componentDef),
133
- },
134
- timestamp: new Date(),
135
- requestId: command.requestId,
136
- correlationId: command.correlationId,
137
- };
156
+ let attempt = 1;
157
+ let code = '';
158
+ let lastErrors = '';
159
+ const maxAttempts = 3;
160
+
161
+ while (attempt <= maxAttempts) {
162
+ const genStart = performance.now();
163
+ const prompt =
164
+ attempt === 1
165
+ ? makeImplementPrompt(basePrompt)
166
+ : makeRetryPrompt(basePrompt, componentType, componentName, code, lastErrors);
167
+
168
+ const aiRaw = await callAI(prompt);
169
+ code = extractCodeBlock(aiRaw);
170
+ await fs.writeFile(outPath, code, 'utf-8');
171
+ debugProcess(
172
+ `[6.${attempt}] AI output written (${code.length} chars) in ${(performance.now() - genStart).toFixed(2)} ms`,
173
+ );
174
+
175
+ const checkStart = performance.now();
176
+ const { success, errors } = await runTypeCheckForFile(projectDir, outPath);
177
+ debugTypeCheck(
178
+ `[7.${attempt}] Type check in ${(performance.now() - checkStart).toFixed(2)} ms (success: ${success})`,
179
+ );
180
+
181
+ if (success) {
182
+ debugResult(`[✓] Implementation succeeded in ${(performance.now() - start).toFixed(2)} ms total`);
183
+ return {
184
+ type: 'ComponentImplemented',
185
+ data: {
186
+ filePath: outPath,
187
+ componentType,
188
+ componentName,
189
+ composition: extractComposition(componentDef),
190
+ specs: extractSpecs(componentDef),
191
+ },
192
+ timestamp: new Date(),
193
+ requestId: command.requestId,
194
+ correlationId: command.correlationId,
195
+ };
196
+ }
197
+
198
+ lastErrors = errors;
199
+ if (attempt === maxAttempts) throw new Error(`Type errors persist after ${attempt} attempts:\n${errors}`);
200
+ attempt += 1;
201
+ }
202
+
203
+ throw new Error('Unreachable state');
138
204
  } catch (error: unknown) {
205
+ debug('[Error] Component implementation failed: %O', error);
139
206
  return {
140
207
  type: 'ComponentImplementationFailed',
141
208
  data: {
142
209
  error: error instanceof Error ? error.message : String(error),
143
210
  componentType,
144
211
  componentName,
212
+ filePath,
145
213
  },
146
214
  timestamp: new Date(),
147
215
  requestId: command.requestId,
@@ -150,67 +218,393 @@ async function handleImplementComponentCommandInternal(
150
218
  }
151
219
  }
152
220
 
153
- function extractComposition(componentDef: Record<string, unknown>): string[] {
154
- if ('composition' in componentDef && Boolean(componentDef.composition)) {
155
- const comp = componentDef.composition as Record<string, unknown>;
156
- if ('atoms' in comp && Array.isArray(comp.atoms)) {
157
- return comp.atoms as string[];
221
+ // eslint-disable-next-line complexity
222
+ async function resolveDependenciesRecursively(
223
+ scheme: Record<string, unknown>,
224
+ type: string,
225
+ name: string,
226
+ visited: Set<string> = new Set(),
227
+ ): Promise<{ type: string; name: string }[]> {
228
+ const key = `${type}:${name}`;
229
+ if (visited.has(key)) return [];
230
+ visited.add(key);
231
+
232
+ const collection = scheme[`${type}s`];
233
+ //
234
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
235
+ if (!collection || !isValidCollection(collection)) return [];
236
+
237
+ const def = collection.items[name];
238
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
239
+ if (!def || typeof def !== 'object' || !('composition' in def)) return [];
240
+
241
+ const result: { type: string; name: string }[] = [];
242
+
243
+ const composition = def.composition as Record<string, string[]>;
244
+ for (const [subType, subNames] of Object.entries(composition)) {
245
+ if (!Array.isArray(subNames)) continue;
246
+ for (const subName of subNames) {
247
+ result.push({ type: subType, name: subName });
248
+ const nested = await resolveDependenciesRecursively(scheme, subType, subName, visited);
249
+ result.push(...nested);
158
250
  }
159
- if ('molecules' in comp && Array.isArray(comp.molecules)) {
160
- return comp.molecules as string[];
251
+ }
252
+ return result;
253
+ }
254
+
255
+ async function readComponentSource(projectDir: string, type: string, name: string): Promise<string | null> {
256
+ const file = path.join(projectDir, 'src', 'components', type, `${name}.tsx`);
257
+ try {
258
+ return await fs.readFile(file, 'utf-8');
259
+ } catch {
260
+ return null;
261
+ }
262
+ }
263
+
264
+ function extractCodeBlock(text: string): string {
265
+ return text
266
+ .replace(/```(?:tsx|ts|typescript)?/g, '')
267
+ .replace(/```/g, '')
268
+ .trim();
269
+ }
270
+
271
+ async function readAllTopLevelFiles(projectDir: string): Promise<Record<string, string>> {
272
+ debugProcess('[readAllTopLevelFiles] Reading project files from %s', projectDir);
273
+ const start = performance.now();
274
+ const config: Record<string, string> = {};
275
+
276
+ async function readRecursive(currentDir: string) {
277
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
278
+ for (const entry of entries) {
279
+ const fullPath = path.join(currentDir, entry.name);
280
+ const relativePath = path.relative(projectDir, fullPath);
281
+ if (entry.isDirectory()) {
282
+ if (['node_modules', 'dist', 'build', '.next', '.turbo'].includes(entry.name)) continue;
283
+ await readRecursive(fullPath);
284
+ } else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
285
+ try {
286
+ config[relativePath] = await fs.readFile(fullPath, 'utf-8');
287
+ } catch (err) {
288
+ debugProcess(`Failed to read ${relativePath}: ${(err as Error).message}`);
289
+ }
290
+ }
161
291
  }
162
292
  }
163
- return [];
293
+
294
+ await readRecursive(projectDir);
295
+ debugProcess(`[readAllTopLevelFiles] Completed in ${(performance.now() - start).toFixed(2)} ms`);
296
+ return config;
164
297
  }
165
298
 
166
- function extractSpecs(componentDef: Record<string, unknown>): string[] {
167
- if ('specs' in componentDef && Array.isArray(componentDef.specs)) {
168
- return componentDef.specs as string[];
299
+ async function runTypeCheckForFile(
300
+ projectDir: string,
301
+ filePath: string,
302
+ ): Promise<{ success: boolean; errors: string }> {
303
+ const start = performance.now();
304
+ try {
305
+ const tsconfigRoot = await findProjectRoot(projectDir);
306
+ const relativeFilePath = path.relative(tsconfigRoot, filePath).replace(/\\/g, '/');
307
+ const normalizedRelative = relativeFilePath.replace(/^client\//, '');
308
+ const result = await execa('npx', ['tsc', '--noEmit', '--skipLibCheck', '--pretty', 'false'], {
309
+ cwd: tsconfigRoot,
310
+ stdio: 'pipe',
311
+ reject: false,
312
+ });
313
+
314
+ const output = (result.stdout ?? '') + (result.stderr ?? '');
315
+ debugTypeCheck(`[runTypeCheckForFile] Finished tsc in ${(performance.now() - start).toFixed(2)} ms`);
316
+
317
+ if (result.exitCode === 0 && !output.includes('error TS')) return { success: true, errors: '' };
318
+
319
+ const filteredErrors = output
320
+ .split('\n')
321
+ .filter((line) => {
322
+ const hasError = line.includes('error TS');
323
+ const notNodeModules = !line.includes('node_modules');
324
+ const matchesTarget =
325
+ line.includes(relativeFilePath) ||
326
+ line.includes(normalizedRelative) ||
327
+ line.includes(path.basename(filePath));
328
+ return hasError && notNodeModules && matchesTarget;
329
+ })
330
+ .join('\n');
331
+
332
+ if (filteredErrors.trim().length === 0) return { success: true, errors: '' };
333
+ return { success: false, errors: filteredErrors };
334
+ } catch (err) {
335
+ const message = err instanceof Error ? err.message : String(err);
336
+ return { success: false, errors: message };
169
337
  }
170
- return [];
171
338
  }
172
339
 
173
- function makeComponentPrompt(
174
- ctx: {
175
- fileTreeSummary: string[];
176
- atoms: unknown;
177
- graphqlOperations: Record<string, string>;
178
- theme: string;
179
- },
340
+ async function findProjectRoot(startDir: string): Promise<string> {
341
+ let dir = startDir;
342
+ while (dir !== path.dirname(dir)) {
343
+ try {
344
+ await fs.access(path.join(dir, 'package.json'));
345
+ await fs.access(path.join(dir, 'tsconfig.json'));
346
+ return dir;
347
+ } catch {
348
+ dir = path.dirname(dir);
349
+ }
350
+ }
351
+ throw new Error('Could not find project root (no package.json or tsconfig.json found)');
352
+ }
353
+
354
+ // eslint-disable-next-line complexity
355
+ function makeBasePrompt(
180
356
  componentType: string,
181
357
  componentName: string,
182
358
  componentDef: Record<string, unknown>,
359
+ existingScaffold: string,
360
+ projectConfig: Record<string, string>,
361
+ designSystemReference: string,
362
+ dependencySources: Record<string, string>,
183
363
  ): string {
364
+ const hasScaffold = Boolean(existingScaffold?.trim());
365
+
366
+ const gqlFiles: Record<string, string> = {};
367
+ const graphqlFiles: Record<string, string> = {};
368
+ const otherFiles: Record<string, string> = {};
369
+
370
+ for (const [filePath, content] of Object.entries(projectConfig)) {
371
+ const lower = filePath.toLowerCase();
372
+ if (lower.includes('src/gql/')) gqlFiles[filePath] = content;
373
+ else if (lower.includes('src/graphql/')) graphqlFiles[filePath] = content;
374
+ else otherFiles[filePath] = content;
375
+ }
376
+
377
+ const queriesFile = Object.entries(graphqlFiles).find(([n]) => n.endsWith('queries.ts'))?.[1] ?? '';
378
+ const mutationsFile = Object.entries(graphqlFiles).find(([n]) => n.endsWith('mutations.ts'))?.[1] ?? '';
379
+
380
+ const gqlSection =
381
+ Object.entries(gqlFiles)
382
+ .map(([p, c]) => `### ${p}\n${c}`)
383
+ .join('\n\n') || '(No gql folder found)';
384
+
385
+ const graphqlSection =
386
+ Object.entries(graphqlFiles)
387
+ .map(([p, c]) => `### ${p}\n${c}`)
388
+ .join('\n\n') || '(No graphql folder found)';
389
+
390
+ const configSection =
391
+ Object.entries(otherFiles)
392
+ .map(([p, c]) => `### ${p}\n${c}`)
393
+ .join('\n\n') || '(No additional config files)';
394
+
395
+ const designSystemBlock = designSystemReference.trim()
396
+ ? designSystemReference
397
+ : '(No design system content provided)';
398
+
399
+ const dependencySection =
400
+ Object.entries(dependencySources)
401
+ .map(([name, src]) => `### ${name}\n${src}`)
402
+ .join('\n\n') || '(No dependencies found)';
403
+
184
404
  return `
185
- You are Auto, a masterful frontend engineer.
186
- Implement the following ${componentType}: **${componentName}** from the IA schema.
405
+ # Implementation Brief: ${componentName} (${componentType})
406
+
407
+ You are a senior frontend engineer specializing in **React + TypeScript + Apollo Client**.
408
+ Your task is to build a visually excellent, type-safe, and production-ready ${componentType} component.
409
+ The goal is to deliver elegant, minimal, and robust code that integrates perfectly with the existing system.
410
+
411
+ ---
412
+
413
+ ## Objective
414
+ Implement **${componentName}** as defined in the IA schema and design system.
415
+ Your component must:
416
+ - Compile cleanly with no TypeScript errors.
417
+ - Follow established design tokens, colors, and spacing.
418
+ - Be visually polished, responsive, and accessible.
419
+ - Reuse existing atoms/molecules/organisms wherever possible.
420
+ - Use valid imports only — no new dependencies or mock data.
187
421
 
188
- Component Definition:
422
+ ---
423
+
424
+ ## Project Context
425
+
426
+ **File Path:** src/components/${componentType}/${componentName}.tsx
427
+ **Purpose:** A reusable UI element connected to the GraphQL layer and design system.
428
+
429
+ ### IA Schema
189
430
  ${JSON.stringify(componentDef, null, 2)}
190
431
 
191
- Project Snapshot:
192
- ${JSON.stringify(ctx.fileTreeSummary, null, 2)}
432
+ ### Existing Scaffold
433
+ ${hasScaffold ? existingScaffold : '(No existing scaffold found)'}
434
+
435
+ ### Design System Reference
436
+ ${designSystemBlock}
437
+
438
+ ### Related Components (Dependencies)
439
+ ${dependencySection}
440
+
441
+ ### GraphQL Context (src/graphql)
442
+ ${graphqlSection}
443
+
444
+ #### queries.ts
445
+ ${queriesFile || '(queries.ts not found)'}
446
+
447
+ #### mutations.ts
448
+ ${mutationsFile || '(mutations.ts not found)'}
193
449
 
194
- Available Atoms:
195
- ${JSON.stringify(ctx.atoms, null, 2)}
450
+ ### GraphQL Codegen (src/gql)
451
+ ${gqlSection}
196
452
 
197
- GraphQL Operations:
198
- ${Object.keys(ctx.graphqlOperations).join(', ')}
453
+ ### Other Relevant Files
454
+ ${configSection}
199
455
 
200
- Theme:
201
- ${ctx.theme}
456
+ ---
202
457
 
203
- Output: ONLY the full TypeScript React component code (no markdown, no explanations).
204
- `;
458
+ ## Engineering Guidelines
459
+
460
+ **Type Safety**
461
+ - Explicitly type all props, state, and GraphQL responses.
462
+ - Avoid \`any\` — prefer discriminated unions, interfaces, and generics.
463
+
464
+ **React Practices**
465
+ - Never call setState during render.
466
+ - Always use dependency arrays in effects.
467
+ - Memoize computed values and callbacks.
468
+ - Keep rendering pure and predictable.
469
+
470
+ **Error Handling**
471
+ - Wrap async operations in try/catch with graceful fallback UI.
472
+ - Check for null/undefined using optional chaining (?.) and defaults (??).
473
+
474
+ **Visual & UX Quality**
475
+ - Perfect spacing and alignment using Tailwind or the design system tokens.
476
+ - Add subtle hover, focus, and loading states.
477
+ - Use accessible HTML semantics and ARIA attributes.
478
+ - Animate with Framer Motion when appropriate.
479
+
480
+ **Performance**
481
+ - Prevent unnecessary re-renders with React.memo and stable references.
482
+ - Avoid redundant state and computations.
483
+
484
+ **Consistency**
485
+ - Follow established color, typography, and spacing scales.
486
+ - Match button, card, and badge styles with existing components.
487
+
488
+ **Prohibited**
489
+ - No placeholder data, TODOs, or pseudo-logic.
490
+ - No new external packages.
491
+ - No commented-out or partial implementations.
492
+
493
+ ---
494
+
495
+ ## Visual Quality Checklist
496
+ - Consistent vertical rhythm and alignment.
497
+ - Smooth hover and transition states.
498
+ - Responsive design that looks intentional at all breakpoints.
499
+ - Uses design tokens and existing components wherever possible.
500
+ - Clear visual hierarchy and accessible structure.
501
+
502
+ ---
503
+
504
+ ## Validation Checklist
505
+ - Compiles cleanly with \`tsc --noEmit\`.
506
+ - Imports exist and resolve correctly.
507
+ - Component matches the design system and IA schema.
508
+ - No unused props, variables, or imports.
509
+ - Visually and functionally complete.
510
+
511
+ ---
512
+
513
+ **Final Output Requirement:**
514
+ Return only the complete \`.tsx\` source code for this component — no markdown fences, commentary, or extra text.
515
+ `.trim();
516
+ }
517
+
518
+ function makeImplementPrompt(basePrompt: string): string {
519
+ return `${basePrompt}
520
+
521
+ ---
522
+
523
+ Generate the **complete final implementation** for \`${basePrompt}\`.
524
+ Begin directly with import statements and end with the export statement.
525
+ Do not include markdown fences, comments, or explanations — only the valid .tsx file content.
526
+ `.trim();
527
+ }
528
+
529
+ function makeRetryPrompt(
530
+ basePrompt: string,
531
+ componentType: string,
532
+ componentName: string,
533
+ previousCode: string,
534
+ previousErrors: string,
535
+ ): string {
536
+ return `
537
+ ${basePrompt}
538
+
539
+ ---
540
+
541
+ ### Correction Task
542
+ The previously generated ${componentType} component **${componentName}** failed TypeScript validation.
543
+ Fix only the issues listed below without altering logic or layout.
544
+
545
+ **Errors**
546
+ ${previousErrors}
547
+
548
+ **Previous Code**
549
+ ${previousCode}
550
+
551
+ ---
552
+
553
+ ### Correction Rules
554
+ - Fix only TypeScript or import errors.
555
+ - Do not change working logic or structure.
556
+ - Keep eslint directives and formatting intact.
557
+ - Return the corrected \`.tsx\` file only, with no markdown fences or commentary.
558
+ `.trim();
559
+ }
560
+
561
+ /* -------------------------------------------------------------------------- */
562
+
563
+ function extractComposition(componentDef: Record<string, unknown>): string[] {
564
+ if ('composition' in componentDef && Boolean(componentDef.composition)) {
565
+ const comp = componentDef.composition as Record<string, unknown>;
566
+ if ('atoms' in comp && Array.isArray(comp.atoms)) return comp.atoms as string[];
567
+ if ('molecules' in comp && Array.isArray(comp.molecules)) return comp.molecules as string[];
568
+ }
569
+ return [];
570
+ }
571
+
572
+ function extractSpecs(componentDef: Record<string, unknown>): string[] {
573
+ return Array.isArray(componentDef.specs) ? (componentDef.specs as string[]) : [];
205
574
  }
206
575
 
207
576
  function isValidCollection(collection: unknown): collection is { items: Record<string, unknown> } {
208
577
  if (collection === null || collection === undefined) return false;
209
578
  if (typeof collection !== 'object') return false;
210
579
  if (!('items' in collection)) return false;
211
-
212
580
  const items = (collection as { items: unknown }).items;
213
581
  return typeof items === 'object' && items !== null;
214
582
  }
215
583
 
216
- export default commandHandler;
584
+ async function readDesignSystem(
585
+ providedPath: string,
586
+ refs: { projectDir: string; iaSchemeDir: string },
587
+ ): Promise<string> {
588
+ const start = performance.now();
589
+ const candidates: string[] = [];
590
+ if (providedPath) {
591
+ candidates.push(providedPath);
592
+ if (!path.isAbsolute(providedPath)) {
593
+ candidates.push(path.resolve(refs.projectDir, providedPath));
594
+ candidates.push(path.resolve(refs.iaSchemeDir, providedPath));
595
+ }
596
+ }
597
+
598
+ for (const candidate of candidates) {
599
+ try {
600
+ const content = await fs.readFile(candidate, 'utf-8');
601
+ debugProcess(`[readDesignSystem] Loaded from ${candidate} in ${(performance.now() - start).toFixed(2)} ms`);
602
+ return content;
603
+ } catch {
604
+ debugProcess(`[readDesignSystem] Could not read design system from %s`, candidate);
605
+ }
606
+ }
607
+
608
+ debugProcess(`[readDesignSystem] Design system not found, elapsed ${(performance.now() - start).toFixed(2)} ms`);
609
+ return '';
610
+ }