@auto-engineer/component-implementer 0.10.5 → 0.11.10
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/CHANGELOG.md +8 -0
- package/dist/src/agent-cli.js +1 -1
- package/dist/src/agent.d.ts +1 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/commands/implement-component.d.ts +17 -2
- package/dist/src/commands/implement-component.d.ts.map +1 -1
- package/dist/src/commands/implement-component.js +407 -64
- package/dist/src/commands/implement-component.js.map +1 -1
- package/dist/src/index.d.ts +15 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/commands/implement-component.ts +473 -79
package/CHANGELOG.md
CHANGED
package/dist/src/agent-cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
|
-
import { runAIAgent } from './agent';
|
|
2
|
+
import { runAIAgent } from './agent.js';
|
|
3
3
|
const [, , projectDir, iaSchemeDir, designSystemPath] = process.argv;
|
|
4
4
|
if (!projectDir) {
|
|
5
5
|
console.error('Usage: agent-cli <project-directory> <ia-scheme-directory>');
|
package/dist/src/agent.d.ts
CHANGED
package/dist/src/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/agent.ts"],"names":[],"mappings":"AAqHA,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/agent.ts"],"names":[],"mappings":"AAqHA,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,mBAOlG;AAGD,UAAU,MAAM;IACd,mBAAmB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,CAAC;IAC1E,KAAK,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACjC,CAAC;IACF,SAAS,CAAC,EAAE;QACV,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACjC,CAAC;IACF,SAAS,CAAC,EAAE;QACV,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACjC,CAAC;IACF,KAAK,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CACZ,MAAM,EACN;YACE,KAAK,EAAE,MAAM,CAAC;YACd,WAAW,EAAE,MAAM,CAAC;YACpB,MAAM,CAAC,EAAE,OAAO,CAAC;YACjB,UAAU,CAAC,EAAE,OAAO,CAAC;YACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CACF,CAAC;KACH,CAAC;CACH;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,EAAE,CAAC;IACnE,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAGD,wBAAsB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuBjF;AA0ED,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,cAAc,CAAC,CAiCzB;AA+gBD,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,MAAM,EAAE,iBA2CnB"}
|
|
@@ -4,6 +4,7 @@ export type ImplementComponentCommand = Command<'ImplementComponent', {
|
|
|
4
4
|
iaSchemeDir: string;
|
|
5
5
|
designSystemPath: string;
|
|
6
6
|
componentType: 'atom' | 'molecule' | 'organism' | 'page';
|
|
7
|
+
filePath: string;
|
|
7
8
|
componentName: string;
|
|
8
9
|
failures?: string[];
|
|
9
10
|
}>;
|
|
@@ -18,7 +19,21 @@ export type ComponentImplementationFailedEvent = Event<'ComponentImplementationF
|
|
|
18
19
|
error: string;
|
|
19
20
|
componentType: string;
|
|
20
21
|
componentName: string;
|
|
22
|
+
filePath: string;
|
|
21
23
|
}>;
|
|
22
|
-
export declare const commandHandler:
|
|
23
|
-
|
|
24
|
+
export declare const commandHandler: import("@auto-engineer/message-bus").UnifiedCommandHandler<Readonly<{
|
|
25
|
+
type: "ImplementComponent";
|
|
26
|
+
data: Readonly<{
|
|
27
|
+
projectDir: string;
|
|
28
|
+
iaSchemeDir: string;
|
|
29
|
+
designSystemPath: string;
|
|
30
|
+
componentType: "atom" | "molecule" | "organism" | "page";
|
|
31
|
+
filePath: string;
|
|
32
|
+
componentName: string;
|
|
33
|
+
failures?: string[];
|
|
34
|
+
}>;
|
|
35
|
+
timestamp?: Date;
|
|
36
|
+
requestId?: string;
|
|
37
|
+
correlationId?: string;
|
|
38
|
+
}>>;
|
|
24
39
|
//# sourceMappingURL=implement-component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"implement-component.d.ts","sourceRoot":"","sources":["../../../src/commands/implement-component.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"implement-component.d.ts","sourceRoot":"","sources":["../../../src/commands/implement-component.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAwB,KAAK,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAa5F,MAAM,MAAM,yBAAyB,GAAG,OAAO,CAC7C,oBAAoB,EACpB;IACE,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CACF,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,KAAK,CAC3C,sBAAsB,EACtB;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CACF,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG,KAAK,CACpD,+BAA+B,EAC/B;IACE,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,CACF,CAAC;AAEF,eAAO,MAAM,cAAc;;;oBA/BX,MAAM;qBACL,MAAM;0BACD,MAAM;uBACT,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM;kBAC9C,MAAM;uBACD,MAAM;mBACV,MAAM,EAAE;;;;;GA6DrB,CAAC"}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
// noinspection ExceptionCaughtLocallyJS
|
|
1
2
|
import { defineCommandHandler } from '@auto-engineer/message-bus';
|
|
2
3
|
import * as fs from 'fs/promises';
|
|
3
4
|
import * as path from 'path';
|
|
4
5
|
import createDebug from 'debug';
|
|
5
|
-
import { callAI,
|
|
6
|
-
|
|
6
|
+
import { callAI, loadScheme } from '../agent.js';
|
|
7
|
+
import { execa } from 'execa';
|
|
8
|
+
import { performance } from 'perf_hooks';
|
|
9
|
+
const debug = createDebug('auto:client-implementer:component');
|
|
10
|
+
const debugTypeCheck = createDebug('auto:client-implementer:component:typecheck');
|
|
11
|
+
const debugProcess = createDebug('auto:client-implementer:component:process');
|
|
12
|
+
const debugResult = createDebug('auto:client-implementer:component:result');
|
|
7
13
|
export const commandHandler = defineCommandHandler({
|
|
8
14
|
name: 'ImplementComponent',
|
|
9
15
|
alias: 'implement:component',
|
|
@@ -18,11 +24,9 @@ export const commandHandler = defineCommandHandler({
|
|
|
18
24
|
description: 'Type of component: atom|molecule|organism|page',
|
|
19
25
|
required: true,
|
|
20
26
|
},
|
|
27
|
+
filePath: { description: 'Component file path', required: true },
|
|
21
28
|
componentName: { description: 'Name of component to implement', required: true },
|
|
22
|
-
failures: {
|
|
23
|
-
description: 'Any failures from previous implementations',
|
|
24
|
-
required: false,
|
|
25
|
-
},
|
|
29
|
+
failures: { description: 'Any failures from previous implementations', required: false },
|
|
26
30
|
},
|
|
27
31
|
examples: [
|
|
28
32
|
'$ auto implement:component --project-dir=./client --ia-scheme-dir=./.context --design-system-path=./design-system.md --component-type=molecule --component-name=SurveyCard',
|
|
@@ -31,63 +35,107 @@ export const commandHandler = defineCommandHandler({
|
|
|
31
35
|
handle: async (command) => {
|
|
32
36
|
const result = await handleImplementComponentCommandInternal(command);
|
|
33
37
|
if (result.type === 'ComponentImplemented') {
|
|
34
|
-
debug('Component implemented: %s/%s
|
|
38
|
+
debug('Component implemented successfully: %s/%s', result.data.componentType, result.data.componentName);
|
|
35
39
|
}
|
|
36
40
|
else {
|
|
37
|
-
debug('
|
|
41
|
+
debug('Component implementation failed: %s', result.data.error);
|
|
38
42
|
}
|
|
39
43
|
return result;
|
|
40
44
|
},
|
|
41
45
|
});
|
|
46
|
+
// eslint-disable-next-line complexity
|
|
42
47
|
async function handleImplementComponentCommandInternal(command) {
|
|
43
|
-
const { projectDir, iaSchemeDir, designSystemPath, componentType, componentName,
|
|
48
|
+
const { projectDir, iaSchemeDir, designSystemPath, componentType, componentName, filePath } = command.data;
|
|
44
49
|
try {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
fs.readFile(designSystemPath, 'utf-8'),
|
|
49
|
-
]);
|
|
50
|
+
const start = performance.now();
|
|
51
|
+
debugProcess(`Starting ${componentType}:${componentName}`);
|
|
52
|
+
const t1 = performance.now();
|
|
50
53
|
const scheme = await loadScheme(iaSchemeDir);
|
|
51
|
-
|
|
54
|
+
debugProcess(`[1] Loaded IA scheme in ${(performance.now() - t1).toFixed(2)} ms`);
|
|
55
|
+
if (!scheme)
|
|
52
56
|
throw new Error('IA scheme not found');
|
|
53
|
-
}
|
|
54
57
|
const pluralKey = `${componentType}s`;
|
|
55
58
|
const collection = scheme[pluralKey];
|
|
56
|
-
if (!isValidCollection(collection))
|
|
59
|
+
if (!isValidCollection(collection))
|
|
57
60
|
throw new Error(`Invalid IA schema structure for ${pluralKey}`);
|
|
58
|
-
}
|
|
59
61
|
const items = collection.items;
|
|
60
62
|
const componentDef = items[componentName];
|
|
61
|
-
if (!componentDef)
|
|
63
|
+
if (!componentDef)
|
|
62
64
|
throw new Error(`Component ${componentType}:${componentName} not found in IA schema`);
|
|
65
|
+
const outPath = path.join(projectDir, '..', filePath);
|
|
66
|
+
const t2 = performance.now();
|
|
67
|
+
let existingScaffold = '';
|
|
68
|
+
try {
|
|
69
|
+
existingScaffold = await fs.readFile(outPath, 'utf-8');
|
|
70
|
+
debugProcess(`[2] Found existing scaffold in ${(performance.now() - t2).toFixed(2)} ms`);
|
|
63
71
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
72
|
+
catch {
|
|
73
|
+
debugProcess(`[2] No existing scaffold found (${(performance.now() - t2).toFixed(2)} ms)`);
|
|
74
|
+
}
|
|
75
|
+
const t3 = performance.now();
|
|
76
|
+
const projectConfig = await readAllTopLevelFiles(projectDir);
|
|
77
|
+
debugProcess(`[3] Loaded project + gql/graphql files in ${(performance.now() - t3).toFixed(2)} ms`);
|
|
78
|
+
const t4 = performance.now();
|
|
79
|
+
const designSystemReference = await readDesignSystem(designSystemPath, { projectDir, iaSchemeDir });
|
|
80
|
+
debugProcess(`[4] Loaded design system reference in ${(performance.now() - t4).toFixed(2)} ms`);
|
|
81
|
+
const dependencyList = await resolveDependenciesRecursively(scheme, componentType, componentName);
|
|
82
|
+
debugProcess(`[5] Resolved ${dependencyList.length} dependencies for ${componentName}`);
|
|
83
|
+
const dependencySources = {};
|
|
84
|
+
for (const dep of dependencyList) {
|
|
85
|
+
const depSource = await readComponentSource(projectDir, dep.type, dep.name);
|
|
86
|
+
if (depSource != null)
|
|
87
|
+
dependencySources[`${dep.type}/${dep.name}`] = depSource;
|
|
88
|
+
}
|
|
89
|
+
const basePrompt = makeBasePrompt(componentType, componentName, componentDef, existingScaffold, projectConfig, designSystemReference, dependencySources);
|
|
68
90
|
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
let attempt = 1;
|
|
92
|
+
let code = '';
|
|
93
|
+
let lastErrors = '';
|
|
94
|
+
const maxAttempts = 3;
|
|
95
|
+
while (attempt <= maxAttempts) {
|
|
96
|
+
const genStart = performance.now();
|
|
97
|
+
const prompt = attempt === 1
|
|
98
|
+
? makeImplementPrompt(basePrompt)
|
|
99
|
+
: makeRetryPrompt(basePrompt, componentType, componentName, code, lastErrors);
|
|
100
|
+
const aiRaw = await callAI(prompt);
|
|
101
|
+
code = extractCodeBlock(aiRaw);
|
|
102
|
+
await fs.writeFile(outPath, code, 'utf-8');
|
|
103
|
+
debugProcess(`[6.${attempt}] AI output written (${code.length} chars) in ${(performance.now() - genStart).toFixed(2)} ms`);
|
|
104
|
+
const checkStart = performance.now();
|
|
105
|
+
const { success, errors } = await runTypeCheckForFile(projectDir, outPath);
|
|
106
|
+
debugTypeCheck(`[7.${attempt}] Type check in ${(performance.now() - checkStart).toFixed(2)} ms (success: ${success})`);
|
|
107
|
+
if (success) {
|
|
108
|
+
debugResult(`[✓] Implementation succeeded in ${(performance.now() - start).toFixed(2)} ms total`);
|
|
109
|
+
return {
|
|
110
|
+
type: 'ComponentImplemented',
|
|
111
|
+
data: {
|
|
112
|
+
filePath: outPath,
|
|
113
|
+
componentType,
|
|
114
|
+
componentName,
|
|
115
|
+
composition: extractComposition(componentDef),
|
|
116
|
+
specs: extractSpecs(componentDef),
|
|
117
|
+
},
|
|
118
|
+
timestamp: new Date(),
|
|
119
|
+
requestId: command.requestId,
|
|
120
|
+
correlationId: command.correlationId,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
lastErrors = errors;
|
|
124
|
+
if (attempt === maxAttempts)
|
|
125
|
+
throw new Error(`Type errors persist after ${attempt} attempts:\n${errors}`);
|
|
126
|
+
attempt += 1;
|
|
127
|
+
}
|
|
128
|
+
throw new Error('Unreachable state');
|
|
83
129
|
}
|
|
84
130
|
catch (error) {
|
|
131
|
+
debug('[Error] Component implementation failed: %O', error);
|
|
85
132
|
return {
|
|
86
133
|
type: 'ComponentImplementationFailed',
|
|
87
134
|
data: {
|
|
88
135
|
error: error instanceof Error ? error.message : String(error),
|
|
89
136
|
componentType,
|
|
90
137
|
componentName,
|
|
138
|
+
filePath,
|
|
91
139
|
},
|
|
92
140
|
timestamp: new Date(),
|
|
93
141
|
requestId: command.requestId,
|
|
@@ -95,46 +143,319 @@ async function handleImplementComponentCommandInternal(command) {
|
|
|
95
143
|
};
|
|
96
144
|
}
|
|
97
145
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
146
|
+
// eslint-disable-next-line complexity
|
|
147
|
+
async function resolveDependenciesRecursively(scheme, type, name, visited = new Set()) {
|
|
148
|
+
const key = `${type}:${name}`;
|
|
149
|
+
if (visited.has(key))
|
|
150
|
+
return [];
|
|
151
|
+
visited.add(key);
|
|
152
|
+
const collection = scheme[`${type}s`];
|
|
153
|
+
//
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
155
|
+
if (!collection || !isValidCollection(collection))
|
|
156
|
+
return [];
|
|
157
|
+
const def = collection.items[name];
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
159
|
+
if (!def || typeof def !== 'object' || !('composition' in def))
|
|
160
|
+
return [];
|
|
161
|
+
const result = [];
|
|
162
|
+
const composition = def.composition;
|
|
163
|
+
for (const [subType, subNames] of Object.entries(composition)) {
|
|
164
|
+
if (!Array.isArray(subNames))
|
|
165
|
+
continue;
|
|
166
|
+
for (const subName of subNames) {
|
|
167
|
+
result.push({ type: subType, name: subName });
|
|
168
|
+
const nested = await resolveDependenciesRecursively(scheme, subType, subName, visited);
|
|
169
|
+
result.push(...nested);
|
|
103
170
|
}
|
|
104
|
-
|
|
105
|
-
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
async function readComponentSource(projectDir, type, name) {
|
|
175
|
+
const file = path.join(projectDir, 'src', 'components', type, `${name}.tsx`);
|
|
176
|
+
try {
|
|
177
|
+
return await fs.readFile(file, 'utf-8');
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function extractCodeBlock(text) {
|
|
184
|
+
return text
|
|
185
|
+
.replace(/```(?:tsx|ts|typescript)?/g, '')
|
|
186
|
+
.replace(/```/g, '')
|
|
187
|
+
.trim();
|
|
188
|
+
}
|
|
189
|
+
async function readAllTopLevelFiles(projectDir) {
|
|
190
|
+
debugProcess('[readAllTopLevelFiles] Reading project files from %s', projectDir);
|
|
191
|
+
const start = performance.now();
|
|
192
|
+
const config = {};
|
|
193
|
+
async function readRecursive(currentDir) {
|
|
194
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
195
|
+
for (const entry of entries) {
|
|
196
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
197
|
+
const relativePath = path.relative(projectDir, fullPath);
|
|
198
|
+
if (entry.isDirectory()) {
|
|
199
|
+
if (['node_modules', 'dist', 'build', '.next', '.turbo'].includes(entry.name))
|
|
200
|
+
continue;
|
|
201
|
+
await readRecursive(fullPath);
|
|
202
|
+
}
|
|
203
|
+
else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
|
|
204
|
+
try {
|
|
205
|
+
config[relativePath] = await fs.readFile(fullPath, 'utf-8');
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
debugProcess(`Failed to read ${relativePath}: ${err.message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
106
211
|
}
|
|
107
212
|
}
|
|
108
|
-
|
|
213
|
+
await readRecursive(projectDir);
|
|
214
|
+
debugProcess(`[readAllTopLevelFiles] Completed in ${(performance.now() - start).toFixed(2)} ms`);
|
|
215
|
+
return config;
|
|
109
216
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
217
|
+
async function runTypeCheckForFile(projectDir, filePath) {
|
|
218
|
+
const start = performance.now();
|
|
219
|
+
try {
|
|
220
|
+
const tsconfigRoot = await findProjectRoot(projectDir);
|
|
221
|
+
const relativeFilePath = path.relative(tsconfigRoot, filePath).replace(/\\/g, '/');
|
|
222
|
+
const normalizedRelative = relativeFilePath.replace(/^client\//, '');
|
|
223
|
+
const result = await execa('npx', ['tsc', '--noEmit', '--skipLibCheck', '--pretty', 'false'], {
|
|
224
|
+
cwd: tsconfigRoot,
|
|
225
|
+
stdio: 'pipe',
|
|
226
|
+
reject: false,
|
|
227
|
+
});
|
|
228
|
+
const output = (result.stdout ?? '') + (result.stderr ?? '');
|
|
229
|
+
debugTypeCheck(`[runTypeCheckForFile] Finished tsc in ${(performance.now() - start).toFixed(2)} ms`);
|
|
230
|
+
if (result.exitCode === 0 && !output.includes('error TS'))
|
|
231
|
+
return { success: true, errors: '' };
|
|
232
|
+
const filteredErrors = output
|
|
233
|
+
.split('\n')
|
|
234
|
+
.filter((line) => {
|
|
235
|
+
const hasError = line.includes('error TS');
|
|
236
|
+
const notNodeModules = !line.includes('node_modules');
|
|
237
|
+
const matchesTarget = line.includes(relativeFilePath) ||
|
|
238
|
+
line.includes(normalizedRelative) ||
|
|
239
|
+
line.includes(path.basename(filePath));
|
|
240
|
+
return hasError && notNodeModules && matchesTarget;
|
|
241
|
+
})
|
|
242
|
+
.join('\n');
|
|
243
|
+
if (filteredErrors.trim().length === 0)
|
|
244
|
+
return { success: true, errors: '' };
|
|
245
|
+
return { success: false, errors: filteredErrors };
|
|
113
246
|
}
|
|
114
|
-
|
|
247
|
+
catch (err) {
|
|
248
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
249
|
+
return { success: false, errors: message };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function findProjectRoot(startDir) {
|
|
253
|
+
let dir = startDir;
|
|
254
|
+
while (dir !== path.dirname(dir)) {
|
|
255
|
+
try {
|
|
256
|
+
await fs.access(path.join(dir, 'package.json'));
|
|
257
|
+
await fs.access(path.join(dir, 'tsconfig.json'));
|
|
258
|
+
return dir;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
dir = path.dirname(dir);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
throw new Error('Could not find project root (no package.json or tsconfig.json found)');
|
|
115
265
|
}
|
|
116
|
-
|
|
266
|
+
// eslint-disable-next-line complexity
|
|
267
|
+
function makeBasePrompt(componentType, componentName, componentDef, existingScaffold, projectConfig, designSystemReference, dependencySources) {
|
|
268
|
+
const hasScaffold = Boolean(existingScaffold?.trim());
|
|
269
|
+
const gqlFiles = {};
|
|
270
|
+
const graphqlFiles = {};
|
|
271
|
+
const otherFiles = {};
|
|
272
|
+
for (const [filePath, content] of Object.entries(projectConfig)) {
|
|
273
|
+
const lower = filePath.toLowerCase();
|
|
274
|
+
if (lower.includes('src/gql/'))
|
|
275
|
+
gqlFiles[filePath] = content;
|
|
276
|
+
else if (lower.includes('src/graphql/'))
|
|
277
|
+
graphqlFiles[filePath] = content;
|
|
278
|
+
else
|
|
279
|
+
otherFiles[filePath] = content;
|
|
280
|
+
}
|
|
281
|
+
const queriesFile = Object.entries(graphqlFiles).find(([n]) => n.endsWith('queries.ts'))?.[1] ?? '';
|
|
282
|
+
const mutationsFile = Object.entries(graphqlFiles).find(([n]) => n.endsWith('mutations.ts'))?.[1] ?? '';
|
|
283
|
+
const gqlSection = Object.entries(gqlFiles)
|
|
284
|
+
.map(([p, c]) => `### ${p}\n${c}`)
|
|
285
|
+
.join('\n\n') || '(No gql folder found)';
|
|
286
|
+
const graphqlSection = Object.entries(graphqlFiles)
|
|
287
|
+
.map(([p, c]) => `### ${p}\n${c}`)
|
|
288
|
+
.join('\n\n') || '(No graphql folder found)';
|
|
289
|
+
const configSection = Object.entries(otherFiles)
|
|
290
|
+
.map(([p, c]) => `### ${p}\n${c}`)
|
|
291
|
+
.join('\n\n') || '(No additional config files)';
|
|
292
|
+
const designSystemBlock = designSystemReference.trim()
|
|
293
|
+
? designSystemReference
|
|
294
|
+
: '(No design system content provided)';
|
|
295
|
+
const dependencySection = Object.entries(dependencySources)
|
|
296
|
+
.map(([name, src]) => `### ${name}\n${src}`)
|
|
297
|
+
.join('\n\n') || '(No dependencies found)';
|
|
117
298
|
return `
|
|
118
|
-
|
|
119
|
-
|
|
299
|
+
# Implementation Brief: ${componentName} (${componentType})
|
|
300
|
+
|
|
301
|
+
You are a senior frontend engineer specializing in **React + TypeScript + Apollo Client**.
|
|
302
|
+
Your task is to build a visually excellent, type-safe, and production-ready ${componentType} component.
|
|
303
|
+
The goal is to deliver elegant, minimal, and robust code that integrates perfectly with the existing system.
|
|
304
|
+
|
|
305
|
+
---
|
|
120
306
|
|
|
121
|
-
|
|
307
|
+
## Objective
|
|
308
|
+
Implement **${componentName}** as defined in the IA schema and design system.
|
|
309
|
+
Your component must:
|
|
310
|
+
- Compile cleanly with no TypeScript errors.
|
|
311
|
+
- Follow established design tokens, colors, and spacing.
|
|
312
|
+
- Be visually polished, responsive, and accessible.
|
|
313
|
+
- Reuse existing atoms/molecules/organisms wherever possible.
|
|
314
|
+
- Use valid imports only — no new dependencies or mock data.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Project Context
|
|
319
|
+
|
|
320
|
+
**File Path:** src/components/${componentType}/${componentName}.tsx
|
|
321
|
+
**Purpose:** A reusable UI element connected to the GraphQL layer and design system.
|
|
322
|
+
|
|
323
|
+
### IA Schema
|
|
122
324
|
${JSON.stringify(componentDef, null, 2)}
|
|
123
325
|
|
|
124
|
-
|
|
125
|
-
${
|
|
326
|
+
### Existing Scaffold
|
|
327
|
+
${hasScaffold ? existingScaffold : '(No existing scaffold found)'}
|
|
328
|
+
|
|
329
|
+
### Design System Reference
|
|
330
|
+
${designSystemBlock}
|
|
126
331
|
|
|
127
|
-
|
|
128
|
-
${
|
|
332
|
+
### Related Components (Dependencies)
|
|
333
|
+
${dependencySection}
|
|
129
334
|
|
|
130
|
-
GraphQL
|
|
131
|
-
${
|
|
335
|
+
### GraphQL Context (src/graphql)
|
|
336
|
+
${graphqlSection}
|
|
132
337
|
|
|
133
|
-
|
|
134
|
-
${
|
|
338
|
+
#### queries.ts
|
|
339
|
+
${queriesFile || '(queries.ts not found)'}
|
|
135
340
|
|
|
136
|
-
|
|
137
|
-
|
|
341
|
+
#### mutations.ts
|
|
342
|
+
${mutationsFile || '(mutations.ts not found)'}
|
|
343
|
+
|
|
344
|
+
### GraphQL Codegen (src/gql)
|
|
345
|
+
${gqlSection}
|
|
346
|
+
|
|
347
|
+
### Other Relevant Files
|
|
348
|
+
${configSection}
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Engineering Guidelines
|
|
353
|
+
|
|
354
|
+
**Type Safety**
|
|
355
|
+
- Explicitly type all props, state, and GraphQL responses.
|
|
356
|
+
- Avoid \`any\` — prefer discriminated unions, interfaces, and generics.
|
|
357
|
+
|
|
358
|
+
**React Practices**
|
|
359
|
+
- Never call setState during render.
|
|
360
|
+
- Always use dependency arrays in effects.
|
|
361
|
+
- Memoize computed values and callbacks.
|
|
362
|
+
- Keep rendering pure and predictable.
|
|
363
|
+
|
|
364
|
+
**Error Handling**
|
|
365
|
+
- Wrap async operations in try/catch with graceful fallback UI.
|
|
366
|
+
- Check for null/undefined using optional chaining (?.) and defaults (??).
|
|
367
|
+
|
|
368
|
+
**Visual & UX Quality**
|
|
369
|
+
- Perfect spacing and alignment using Tailwind or the design system tokens.
|
|
370
|
+
- Add subtle hover, focus, and loading states.
|
|
371
|
+
- Use accessible HTML semantics and ARIA attributes.
|
|
372
|
+
- Animate with Framer Motion when appropriate.
|
|
373
|
+
|
|
374
|
+
**Performance**
|
|
375
|
+
- Prevent unnecessary re-renders with React.memo and stable references.
|
|
376
|
+
- Avoid redundant state and computations.
|
|
377
|
+
|
|
378
|
+
**Consistency**
|
|
379
|
+
- Follow established color, typography, and spacing scales.
|
|
380
|
+
- Match button, card, and badge styles with existing components.
|
|
381
|
+
|
|
382
|
+
**Prohibited**
|
|
383
|
+
- No placeholder data, TODOs, or pseudo-logic.
|
|
384
|
+
- No new external packages.
|
|
385
|
+
- No commented-out or partial implementations.
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Visual Quality Checklist
|
|
390
|
+
- Consistent vertical rhythm and alignment.
|
|
391
|
+
- Smooth hover and transition states.
|
|
392
|
+
- Responsive design that looks intentional at all breakpoints.
|
|
393
|
+
- Uses design tokens and existing components wherever possible.
|
|
394
|
+
- Clear visual hierarchy and accessible structure.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Validation Checklist
|
|
399
|
+
- Compiles cleanly with \`tsc --noEmit\`.
|
|
400
|
+
- Imports exist and resolve correctly.
|
|
401
|
+
- Component matches the design system and IA schema.
|
|
402
|
+
- No unused props, variables, or imports.
|
|
403
|
+
- Visually and functionally complete.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
**Final Output Requirement:**
|
|
408
|
+
Return only the complete \`.tsx\` source code for this component — no markdown fences, commentary, or extra text.
|
|
409
|
+
`.trim();
|
|
410
|
+
}
|
|
411
|
+
function makeImplementPrompt(basePrompt) {
|
|
412
|
+
return `${basePrompt}
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
Generate the **complete final implementation** for \`${basePrompt}\`.
|
|
417
|
+
Begin directly with import statements and end with the export statement.
|
|
418
|
+
Do not include markdown fences, comments, or explanations — only the valid .tsx file content.
|
|
419
|
+
`.trim();
|
|
420
|
+
}
|
|
421
|
+
function makeRetryPrompt(basePrompt, componentType, componentName, previousCode, previousErrors) {
|
|
422
|
+
return `
|
|
423
|
+
${basePrompt}
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
### Correction Task
|
|
428
|
+
The previously generated ${componentType} component **${componentName}** failed TypeScript validation.
|
|
429
|
+
Fix only the issues listed below without altering logic or layout.
|
|
430
|
+
|
|
431
|
+
**Errors**
|
|
432
|
+
${previousErrors}
|
|
433
|
+
|
|
434
|
+
**Previous Code**
|
|
435
|
+
${previousCode}
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
### Correction Rules
|
|
440
|
+
- Fix only TypeScript or import errors.
|
|
441
|
+
- Do not change working logic or structure.
|
|
442
|
+
- Keep eslint directives and formatting intact.
|
|
443
|
+
- Return the corrected \`.tsx\` file only, with no markdown fences or commentary.
|
|
444
|
+
`.trim();
|
|
445
|
+
}
|
|
446
|
+
/* -------------------------------------------------------------------------- */
|
|
447
|
+
function extractComposition(componentDef) {
|
|
448
|
+
if ('composition' in componentDef && Boolean(componentDef.composition)) {
|
|
449
|
+
const comp = componentDef.composition;
|
|
450
|
+
if ('atoms' in comp && Array.isArray(comp.atoms))
|
|
451
|
+
return comp.atoms;
|
|
452
|
+
if ('molecules' in comp && Array.isArray(comp.molecules))
|
|
453
|
+
return comp.molecules;
|
|
454
|
+
}
|
|
455
|
+
return [];
|
|
456
|
+
}
|
|
457
|
+
function extractSpecs(componentDef) {
|
|
458
|
+
return Array.isArray(componentDef.specs) ? componentDef.specs : [];
|
|
138
459
|
}
|
|
139
460
|
function isValidCollection(collection) {
|
|
140
461
|
if (collection === null || collection === undefined)
|
|
@@ -146,5 +467,27 @@ function isValidCollection(collection) {
|
|
|
146
467
|
const items = collection.items;
|
|
147
468
|
return typeof items === 'object' && items !== null;
|
|
148
469
|
}
|
|
149
|
-
|
|
470
|
+
async function readDesignSystem(providedPath, refs) {
|
|
471
|
+
const start = performance.now();
|
|
472
|
+
const candidates = [];
|
|
473
|
+
if (providedPath) {
|
|
474
|
+
candidates.push(providedPath);
|
|
475
|
+
if (!path.isAbsolute(providedPath)) {
|
|
476
|
+
candidates.push(path.resolve(refs.projectDir, providedPath));
|
|
477
|
+
candidates.push(path.resolve(refs.iaSchemeDir, providedPath));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
for (const candidate of candidates) {
|
|
481
|
+
try {
|
|
482
|
+
const content = await fs.readFile(candidate, 'utf-8');
|
|
483
|
+
debugProcess(`[readDesignSystem] Loaded from ${candidate} in ${(performance.now() - start).toFixed(2)} ms`);
|
|
484
|
+
return content;
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
debugProcess(`[readDesignSystem] Could not read design system from %s`, candidate);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
debugProcess(`[readDesignSystem] Design system not found, elapsed ${(performance.now() - start).toFixed(2)} ms`);
|
|
491
|
+
return '';
|
|
492
|
+
}
|
|
150
493
|
//# sourceMappingURL=implement-component.js.map
|