@auto-engineer/frontend-implementer 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-format.log +15 -0
- package/.turbo/turbo-lint.log +5 -0
- package/.turbo/turbo-test.log +12 -0
- package/.turbo/turbo-type-check.log +4 -0
- package/CHANGELOG.md +157 -0
- package/DEBUG.md +184 -0
- package/LICENSE +10 -0
- package/README.md +358 -0
- package/dist/agent-cli.d.ts +3 -0
- package/dist/agent-cli.d.ts.map +1 -0
- package/dist/agent-cli.js +12 -0
- package/dist/agent-cli.js.map +1 -0
- package/dist/agent.d.ts +2 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +737 -0
- package/dist/agent.js.map +1 -0
- package/dist/cli-manifest.d.ts +3 -0
- package/dist/cli-manifest.d.ts.map +1 -0
- package/dist/cli-manifest.js +8 -0
- package/dist/cli-manifest.js.map +1 -0
- package/dist/commands/implement-client.d.ts +56 -0
- package/dist/commands/implement-client.d.ts.map +1 -0
- package/dist/commands/implement-client.js +62 -0
- package/dist/commands/implement-client.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +265 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
- package/src/agent-cli.ts +14 -0
- package/src/agent.ts +863 -0
- package/src/cli-manifest.ts +9 -0
- package/src/commands/implement-client.ts +100 -0
- package/src/index.ts +321 -0
- package/src/website-mock.html +40 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
package/dist/agent.js
ADDED
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
import { AIProvider, generateTextWithAI } from '@auto-engineer/ai-gateway';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as ts from 'typescript';
|
|
5
|
+
import createDebug from 'debug';
|
|
6
|
+
const debug = createDebug('frontend-impl:agent');
|
|
7
|
+
const debugPlan = createDebug('frontend-impl:agent:plan');
|
|
8
|
+
// const debugErrors = createDebug('frontend-impl:agent:errors');
|
|
9
|
+
// const debugScreenshots = createDebug('frontend-impl:agent:screenshots');
|
|
10
|
+
// const debugFixes = createDebug('frontend-impl:agent:fixes');
|
|
11
|
+
const debugContext = createDebug('frontend-impl:agent:context');
|
|
12
|
+
const debugAI = createDebug('frontend-impl:agent:ai');
|
|
13
|
+
const debugFiles = createDebug('frontend-impl:agent:files');
|
|
14
|
+
const debugComponents = createDebug('frontend-impl:agent:components');
|
|
15
|
+
// Utility to extract props from interface
|
|
16
|
+
function extractPropsFromInterface(node, sourceFile) {
|
|
17
|
+
debugComponents('Extracting props from interface: %s', node.name.text);
|
|
18
|
+
const props = node.members.filter(ts.isPropertySignature).map((member) => {
|
|
19
|
+
const name = member.name.getText(sourceFile);
|
|
20
|
+
const type = member.type ? member.type.getText(sourceFile) : 'any';
|
|
21
|
+
debugComponents(' Property: %s: %s', name, type);
|
|
22
|
+
return { name, type };
|
|
23
|
+
});
|
|
24
|
+
debugComponents('Extracted %d props from interface', props.length);
|
|
25
|
+
return props;
|
|
26
|
+
}
|
|
27
|
+
// Utility to extract props from type alias
|
|
28
|
+
function extractPropsFromTypeAlias(node, sourceFile) {
|
|
29
|
+
debugComponents('Extracting props from type alias: %s', node.name.text);
|
|
30
|
+
if (!ts.isTypeLiteralNode(node.type)) {
|
|
31
|
+
debugComponents(' Type alias is not a type literal, skipping');
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const props = node.type.members.filter(ts.isPropertySignature).map((member) => {
|
|
35
|
+
const name = member.name.getText(sourceFile);
|
|
36
|
+
const type = member.type ? member.type.getText(sourceFile) : 'any';
|
|
37
|
+
debugComponents(' Property: %s: %s', name, type);
|
|
38
|
+
return { name, type };
|
|
39
|
+
});
|
|
40
|
+
debugComponents('Extracted %d props from type alias', props.length);
|
|
41
|
+
return props;
|
|
42
|
+
}
|
|
43
|
+
// Extract atoms and their props from src/components/atoms
|
|
44
|
+
async function getAtomsWithProps(projectDir) {
|
|
45
|
+
const atomsDir = path.join(projectDir, 'src/components/atoms');
|
|
46
|
+
debugComponents('Getting atoms from: %s', atomsDir);
|
|
47
|
+
let files = [];
|
|
48
|
+
try {
|
|
49
|
+
files = (await fs.readdir(atomsDir)).filter((f) => f.endsWith('.tsx'));
|
|
50
|
+
debugComponents('Found %d atom files', files.length);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
debugComponents('Error reading atoms directory: %O', error);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const atoms = [];
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const filePath = path.join(atomsDir, file);
|
|
59
|
+
debugComponents('Processing atom file: %s', file);
|
|
60
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
61
|
+
const sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
|
|
62
|
+
let componentName = file.replace(/\.tsx$/, '');
|
|
63
|
+
componentName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
|
|
64
|
+
debugComponents('Component name: %s', componentName);
|
|
65
|
+
let props = [];
|
|
66
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
67
|
+
if (ts.isInterfaceDeclaration(node) &&
|
|
68
|
+
node.name.text.toLowerCase().includes(componentName.toLowerCase()) &&
|
|
69
|
+
node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) === true) {
|
|
70
|
+
props = extractPropsFromInterface(node, sourceFile);
|
|
71
|
+
}
|
|
72
|
+
if (ts.isTypeAliasDeclaration(node) &&
|
|
73
|
+
node.name.text.toLowerCase().includes(componentName.toLowerCase()) &&
|
|
74
|
+
node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) === true) {
|
|
75
|
+
props = extractPropsFromTypeAlias(node, sourceFile);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
atoms.push({ name: componentName, props });
|
|
79
|
+
debugComponents('Added atom %s with %d props', componentName, props.length);
|
|
80
|
+
}
|
|
81
|
+
debugComponents('Total atoms extracted: %d', atoms.length);
|
|
82
|
+
return atoms;
|
|
83
|
+
}
|
|
84
|
+
const provider = AIProvider.Anthropic;
|
|
85
|
+
function extractJsonArray(text) {
|
|
86
|
+
debugAI('Extracting JSON array from text of length: %d', text.length);
|
|
87
|
+
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
|
88
|
+
if (codeBlockMatch && codeBlockMatch[1]) {
|
|
89
|
+
debugAI('Found JSON in code block');
|
|
90
|
+
return codeBlockMatch[1].trim();
|
|
91
|
+
}
|
|
92
|
+
const arrayMatch = text.match(/(\[[\s\S]*\])/);
|
|
93
|
+
if (arrayMatch) {
|
|
94
|
+
debugAI('Found JSON array in text');
|
|
95
|
+
return arrayMatch[0];
|
|
96
|
+
}
|
|
97
|
+
debugAI('No JSON array found, returning original text');
|
|
98
|
+
return text;
|
|
99
|
+
}
|
|
100
|
+
async function callAI(prompt, options) {
|
|
101
|
+
const temperature = options?.temperature ?? 0.2;
|
|
102
|
+
const maxTokens = options?.maxTokens ?? 4000;
|
|
103
|
+
debugAI('Calling AI with prompt length: %d, temperature: %f, maxTokens: %d', prompt.length, temperature, maxTokens);
|
|
104
|
+
const result = await generateTextWithAI(prompt, provider, { temperature, maxTokens });
|
|
105
|
+
debugAI('AI response received, length: %d', result.length);
|
|
106
|
+
return result.trim();
|
|
107
|
+
}
|
|
108
|
+
// eslint-disable-next-line complexity
|
|
109
|
+
async function loadScheme(iaSchemeDir) {
|
|
110
|
+
const schemePath = path.join(iaSchemeDir, 'auto-ia-scheme.json');
|
|
111
|
+
debugContext('Loading IA scheme from: %s', schemePath);
|
|
112
|
+
try {
|
|
113
|
+
const content = await fs.readFile(schemePath, 'utf-8');
|
|
114
|
+
const scheme = JSON.parse(content);
|
|
115
|
+
debugContext('IA scheme loaded successfully');
|
|
116
|
+
debugContext('Scheme has %d pages, %d organisms, %d molecules, %d atoms', Object.keys(scheme.pages?.items ?? {}).length, Object.keys(scheme.organisms?.items ?? {}).length, Object.keys(scheme.molecules?.items ?? {}).length, Object.keys(scheme.atoms?.items ?? {}).length);
|
|
117
|
+
return scheme;
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (typeof err === 'object' && err !== null && 'code' in err && err.code !== 'ENOENT') {
|
|
121
|
+
debugContext('Error loading scheme: %O', err);
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
debugContext('Scheme file not found');
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function getGraphqlOperations(projectDir, files) {
|
|
129
|
+
const graphqlFiles = files.filter((f) => f.startsWith('src/graphql/') && f.endsWith('.ts'));
|
|
130
|
+
debugContext('Found %d GraphQL files', graphqlFiles.length);
|
|
131
|
+
const operations = {};
|
|
132
|
+
for (const filePath of graphqlFiles) {
|
|
133
|
+
try {
|
|
134
|
+
debugContext('Reading GraphQL file: %s', filePath);
|
|
135
|
+
const content = await fs.readFile(path.join(projectDir, filePath), 'utf-8');
|
|
136
|
+
const operationName = path.basename(filePath, '.ts');
|
|
137
|
+
operations[operationName] = content;
|
|
138
|
+
debugContext('Loaded GraphQL operations from %s', operationName);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
debugContext('Error reading GraphQL operations file %s: %O', filePath, error);
|
|
142
|
+
console.error(`Error reading GraphQL operations file ${filePath}:`, error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
debugContext('Total GraphQL operations loaded: %d', Object.keys(operations).length);
|
|
146
|
+
return operations;
|
|
147
|
+
}
|
|
148
|
+
async function getKeyFileContents(projectDir, files) {
|
|
149
|
+
const keyFiles = files.filter((f) => ['src/index.css', 'src/globals.css'].includes(f));
|
|
150
|
+
debugContext('Getting key file contents for %d files', keyFiles.length);
|
|
151
|
+
const contents = {};
|
|
152
|
+
for (const file of keyFiles) {
|
|
153
|
+
try {
|
|
154
|
+
debugContext('Reading key file: %s', file);
|
|
155
|
+
contents[file] = await fs.readFile(path.join(projectDir, file), 'utf-8');
|
|
156
|
+
debugContext('Key file %s loaded, size: %d bytes', file, contents[file].length);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
debugContext('Could not read key file %s: %O', file, error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
debugContext('Loaded %d key files', Object.keys(contents).length);
|
|
163
|
+
return contents;
|
|
164
|
+
}
|
|
165
|
+
function getFileTreeSummary(files, atoms) {
|
|
166
|
+
return [
|
|
167
|
+
...files.filter((f) => f.startsWith('src/pages/') ||
|
|
168
|
+
f.startsWith('src/hooks/') ||
|
|
169
|
+
f.startsWith('src/lib/') ||
|
|
170
|
+
['src/App.tsx', 'src/routes.tsx', 'src/main.tsx'].includes(f)),
|
|
171
|
+
`src/components/atoms/ (atoms: ${atoms.map((a) => a.name).join(', ')})`,
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
async function getTheme(designSystem) {
|
|
175
|
+
debugContext('Extracting theme from design system, content length: %d', designSystem.length);
|
|
176
|
+
try {
|
|
177
|
+
const themeMatch = designSystem.match(/## Theme\s*\n([\s\S]*?)(?=\n## |\n# |\n*$)/);
|
|
178
|
+
if (themeMatch && themeMatch[1]) {
|
|
179
|
+
const theme = themeMatch[1].trim();
|
|
180
|
+
debugContext('Theme extracted, length: %d', theme.length);
|
|
181
|
+
return theme;
|
|
182
|
+
}
|
|
183
|
+
debugContext('No theme section found in design system');
|
|
184
|
+
return '';
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
debugContext('Error extracting theme: %O', error);
|
|
188
|
+
console.error(`Error reading design-system.md:`, error);
|
|
189
|
+
return '';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function getProjectContext(projectDir, iaSchemeDir, userPreferences, designSystem) {
|
|
193
|
+
debugContext('Building project context for: %s', projectDir);
|
|
194
|
+
debugContext('IA scheme directory: %s', iaSchemeDir);
|
|
195
|
+
debugContext('User preferences length: %d', userPreferences.length);
|
|
196
|
+
debugContext('Design system length: %d', designSystem.length);
|
|
197
|
+
const files = await listFiles(projectDir);
|
|
198
|
+
debugContext('Found %d files in project', files.length);
|
|
199
|
+
const [scheme, atoms, graphqlOperations, keyFileContents, theme] = await Promise.all([
|
|
200
|
+
loadScheme(iaSchemeDir),
|
|
201
|
+
getAtomsWithProps(projectDir),
|
|
202
|
+
getGraphqlOperations(projectDir, files),
|
|
203
|
+
getKeyFileContents(projectDir, files),
|
|
204
|
+
getTheme(designSystem),
|
|
205
|
+
]);
|
|
206
|
+
const fileTreeSummary = getFileTreeSummary(files, atoms);
|
|
207
|
+
debugContext('File tree summary created with %d entries', fileTreeSummary.length);
|
|
208
|
+
debugContext('Project context built successfully');
|
|
209
|
+
return {
|
|
210
|
+
scheme,
|
|
211
|
+
files,
|
|
212
|
+
atoms,
|
|
213
|
+
keyFileContents,
|
|
214
|
+
fileTreeSummary,
|
|
215
|
+
graphqlOperations,
|
|
216
|
+
userPreferences,
|
|
217
|
+
theme,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function listFiles(dir, base = dir) {
|
|
221
|
+
debugFiles('Listing files in: %s', dir);
|
|
222
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
223
|
+
debugFiles('Found %d entries in directory', entries.length);
|
|
224
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
225
|
+
if (entry.name === 'node_modules') {
|
|
226
|
+
debugFiles('Skipping node_modules');
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
const res = path.resolve(dir, entry.name);
|
|
230
|
+
if (entry.isDirectory()) {
|
|
231
|
+
debugFiles('Entering directory: %s', entry.name);
|
|
232
|
+
return listFiles(res, base);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
return [path.relative(base, res)];
|
|
236
|
+
}
|
|
237
|
+
}));
|
|
238
|
+
const flatFiles = files.flat();
|
|
239
|
+
debugFiles('Total files found: %d', flatFiles.length);
|
|
240
|
+
return flatFiles;
|
|
241
|
+
}
|
|
242
|
+
function makeBasePrompt(ctx) {
|
|
243
|
+
const keyFileContents = Object.entries(ctx.keyFileContents)
|
|
244
|
+
.map(([f, c]) => `--- ${f} ---\n${c}\n`)
|
|
245
|
+
.join('\n');
|
|
246
|
+
const graphqlDescriptions = Object.entries(ctx.graphqlOperations)
|
|
247
|
+
.map(([f, c]) => `--- ${f} ---\n${c}\n`)
|
|
248
|
+
.join('\n');
|
|
249
|
+
return `
|
|
250
|
+
You are Auto, an expert AI frontend engineer specializing in scalable, clean, production-grade React applications using modern TypeScript, and GraphQL via Apollo Client.
|
|
251
|
+
|
|
252
|
+
Your task: Analyze the current project and generate a complete plan to implement a well-structured, schema-compliant React app using atomic design and integrated GraphQL operations. You must ensure code clarity, maintainability, and adherence to project styling conventions.
|
|
253
|
+
|
|
254
|
+
User Preferences: ${ctx.userPreferences}
|
|
255
|
+
|
|
256
|
+
IMPLEMENTATION MUST:
|
|
257
|
+
- DONT EVER CHANGE THE THEME TOKENS BY YOURSELF
|
|
258
|
+
- If there are any page templates in the user preferences make sure to use that layout for pages.
|
|
259
|
+
|
|
260
|
+
Component Design & Structure:
|
|
261
|
+
- Follow atomic design:
|
|
262
|
+
- Build molecules → organisms → pages
|
|
263
|
+
- Then update routing in \`App.tsx\` accordingly. **DO NOT FORGET THIS.**
|
|
264
|
+
- Only create pages that are explicitly listed in \`auto-ia-scheme.json\`. No extra routes.
|
|
265
|
+
- If a root page is not explicitly listed in \`auto-ia-scheme.json\`, then make the root page an index to all the other routes
|
|
266
|
+
- Reuse atoms/molecules/organisms when possible. Only create new components when absolutely required.
|
|
267
|
+
- Use responsive layout by default.
|
|
268
|
+
- Use a consistent spacing scale (4/8/12/16px).
|
|
269
|
+
- Component files must stay under 50 lines when possible.
|
|
270
|
+
- All components must be typed. Use the format \`<ComponentName>Props\`.
|
|
271
|
+
|
|
272
|
+
Component Responsibilities:
|
|
273
|
+
- Components must not include generic or redundant headings to represent structure.
|
|
274
|
+
- Page-level wrappers must **not** introduce headings unless absolutely necessary.
|
|
275
|
+
- Use semantic structure, branded color tokens, spacing, and layout to indicate purpose.
|
|
276
|
+
|
|
277
|
+
Code Standards:
|
|
278
|
+
- Use **TypeScript** throughout.
|
|
279
|
+
- Use **named exports and imports only**. Never use \`export default\`.
|
|
280
|
+
- Use relative imports across the app.
|
|
281
|
+
- Avoid prop drilling — prefer context or colocated state.
|
|
282
|
+
- Ensure accessibility (ARIA, keyboard nav, focus rings).
|
|
283
|
+
- Use toast notifications for critical feedback.
|
|
284
|
+
- Add console logs for key state transitions.
|
|
285
|
+
- Avoid layout jitter — use placeholder/stable containers during async rendering.
|
|
286
|
+
- Maintain modular folder structure aligned with atomic principles.
|
|
287
|
+
|
|
288
|
+
GraphQL Integration Rules:
|
|
289
|
+
- Use **Apollo Client**: \`useQuery\`, \`useMutation\`, \`useLazyQuery\`.
|
|
290
|
+
- GraphQL operations must be used inside molecules or organisms — **never inside atoms**.
|
|
291
|
+
- Use operations defined only in:
|
|
292
|
+
- \`src/graphql/queries.ts\`
|
|
293
|
+
- \`src/graphql/mutations.ts\`
|
|
294
|
+
- These files are **read-only**. You may not add, modify, or delete any GraphQL documents.
|
|
295
|
+
- If a GraphQL query doesn’t exactly match the UI, **adapt the UI** — never change the query.
|
|
296
|
+
|
|
297
|
+
Key File Rule:
|
|
298
|
+
When working with a key file, always assume it contains all needed imports/specs.
|
|
299
|
+
- Do **not** add or modify imports/specs in the key file. Implement based only on what is provided.
|
|
300
|
+
|
|
301
|
+
Output Format:
|
|
302
|
+
You must return a JSON array where each item contains:
|
|
303
|
+
- \`action\`: "create" | "update"
|
|
304
|
+
- \`file\`: Relative path from project root
|
|
305
|
+
- \`description\`: Short and clear explanation of the change
|
|
306
|
+
|
|
307
|
+
Respond with **only** a JSON array. No explanations. No markdown. No code blocks.
|
|
308
|
+
|
|
309
|
+
Here is a summary of the file tree:
|
|
310
|
+
${JSON.stringify(ctx.fileTreeSummary, null, 2)}
|
|
311
|
+
|
|
312
|
+
Here are the available atoms and their props:
|
|
313
|
+
${JSON.stringify(ctx.atoms, null, 2)}
|
|
314
|
+
And if there are no atoms found, make sure to use what the user preferences suggest. Like using a library atom component for example.
|
|
315
|
+
|
|
316
|
+
Here are the contents of key files:
|
|
317
|
+
${keyFileContents}
|
|
318
|
+
|
|
319
|
+
Here is the content of auto-ia-scheme.json:
|
|
320
|
+
${JSON.stringify(ctx.scheme, null, 2)}
|
|
321
|
+
|
|
322
|
+
Here are the descriptions of available GraphQL operations:
|
|
323
|
+
${graphqlDescriptions}
|
|
324
|
+
`;
|
|
325
|
+
}
|
|
326
|
+
async function planProject(ctx) {
|
|
327
|
+
debugPlan('Starting project planning');
|
|
328
|
+
const prompt = makeBasePrompt(ctx);
|
|
329
|
+
debugPlan('Generated prompt with length: %d', prompt.length);
|
|
330
|
+
const planText = await callAI(prompt);
|
|
331
|
+
debugPlan('Received plan response, length: %d', planText.length);
|
|
332
|
+
try {
|
|
333
|
+
const changes = JSON.parse(extractJsonArray(planText));
|
|
334
|
+
debugPlan('Successfully parsed plan with %d changes', changes.length);
|
|
335
|
+
changes.forEach((change, idx) => {
|
|
336
|
+
debugPlan('Change %d: %s %s - %s', idx + 1, change.action, change.file, change.description);
|
|
337
|
+
});
|
|
338
|
+
return changes;
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
debugPlan('Failed to parse plan: %O', error);
|
|
342
|
+
console.error('Could not parse plan from LLM:', planText);
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async function applyPlan(plan, ctx, projectDir) {
|
|
347
|
+
debugPlan('Applying plan with %d changes', plan.length);
|
|
348
|
+
for (const [index, change] of plan.entries()) {
|
|
349
|
+
debugPlan('Applying change %d/%d: %s %s', index + 1, plan.length, change.action, change.file);
|
|
350
|
+
let fileContent = '';
|
|
351
|
+
if (change.action === 'update') {
|
|
352
|
+
try {
|
|
353
|
+
fileContent = await fs.readFile(path.join(projectDir, change.file), 'utf-8');
|
|
354
|
+
debugPlan('Read existing file %s, size: %d bytes', change.file, fileContent.length);
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
debugPlan('File %s does not exist, will create', change.file);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const codePrompt = `${makeBasePrompt(ctx)}\nHere is the planned change:\n${JSON.stringify(change, null, 2)}\n${change.action === 'update' ? `Here is the current content of ${change.file}:\n${fileContent}\n` : ''}Please output ONLY the full new code for the file (no markdown, no triple backticks, just code, ready to write to disk).`;
|
|
361
|
+
const code = await callAI(codePrompt);
|
|
362
|
+
debugPlan('Generated code for %s, size: %d bytes', change.file, code.length);
|
|
363
|
+
const outPath = path.join(projectDir, change.file);
|
|
364
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
365
|
+
await fs.writeFile(outPath, code, 'utf-8');
|
|
366
|
+
debugPlan('Successfully wrote file: %s', outPath);
|
|
367
|
+
console.log(`${change.action === 'update' ? 'Updated' : 'Created'} ${change.file}`);
|
|
368
|
+
}
|
|
369
|
+
debugPlan('Plan application complete');
|
|
370
|
+
}
|
|
371
|
+
// interface Fix {
|
|
372
|
+
// action: 'update';
|
|
373
|
+
// file: string;
|
|
374
|
+
// description: string;
|
|
375
|
+
// content: string;
|
|
376
|
+
// }
|
|
377
|
+
// Removed fixTsErrors function - checks now handled by check:client command
|
|
378
|
+
/*
|
|
379
|
+
async function fixTsErrors(ctx: ProjectContext, projectDir: string): Promise<boolean> {
|
|
380
|
+
debugErrors('Checking for TypeScript errors in: %s', projectDir);
|
|
381
|
+
const tsErrors = await getTsErrors(projectDir);
|
|
382
|
+
debugErrors('Found %d TypeScript errors', tsErrors.length);
|
|
383
|
+
console.log('Found', tsErrors.length, 'TypeScript errors');
|
|
384
|
+
|
|
385
|
+
if (tsErrors.length === 0) {
|
|
386
|
+
debugErrors('No TypeScript errors to fix');
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
debugErrors('TypeScript errors found:');
|
|
391
|
+
tsErrors.forEach((error, idx) => {
|
|
392
|
+
debugErrors(' Error %d: %s', idx + 1, error);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const errorFeedback = tsErrors.join('\n');
|
|
396
|
+
const fixupPrompt = `${makeBasePrompt(ctx)}\n
|
|
397
|
+
After your previous changes, the application produced the following TypeScript errors:\n\n${errorFeedback}\n
|
|
398
|
+
You must now fix **every** error listed above. This is a critical pass: if any error remains after your fix, your output is rejected.
|
|
399
|
+
|
|
400
|
+
Before generating code, analyze and validate your solution against every error. Use existing type definitions, component props, GraphQL typings, and shared interfaces from the project. Do not invent new types or structures unless absolutely necessary.
|
|
401
|
+
|
|
402
|
+
Strict rules:
|
|
403
|
+
- Never use \`any\`, \`as any\`, or unsafe type assertions
|
|
404
|
+
- Do not silence errors — resolve them fully and correctly
|
|
405
|
+
- Fix all errors in each file in one go
|
|
406
|
+
- Reuse existing logic or types instead of re-creating similar ones
|
|
407
|
+
- Do not modify the GraphQL files
|
|
408
|
+
- Do not submit partial updates; provide the full updated content of the file
|
|
409
|
+
|
|
410
|
+
Output must be a **JSON array** only. Each item must include:
|
|
411
|
+
- \`action\`: "update"
|
|
412
|
+
- \`file\`: relative path to the updated file
|
|
413
|
+
- \`description\`: "Fix TypeScript errors"
|
|
414
|
+
- \`content\`: full new content of the file, as a string
|
|
415
|
+
|
|
416
|
+
Do not include explanations, markdown, or code blocks.
|
|
417
|
+
`;
|
|
418
|
+
const fixupPlanText = await callAI(fixupPrompt);
|
|
419
|
+
let fixupPlan: Fix[] = [];
|
|
420
|
+
try {
|
|
421
|
+
fixupPlan = JSON.parse(extractJsonArray(fixupPlanText)) as Fix[];
|
|
422
|
+
debugFixes('Parsed TypeScript fixup plan with %d fixes', fixupPlan.length);
|
|
423
|
+
} catch (e) {
|
|
424
|
+
debugFixes('Failed to parse TypeScript fixup plan: %O', e);
|
|
425
|
+
console.error('Could not parse TS fixup plan from LLM:', e instanceof Error ? e.message : String(e));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
console.log('Fixup plan has', fixupPlan.length, 'items');
|
|
429
|
+
|
|
430
|
+
for (const [index, fix] of fixupPlan.entries()) {
|
|
431
|
+
debugFixes('Applying fix %d/%d: %s', index + 1, fixupPlan.length, fix.file);
|
|
432
|
+
if (fix.action === 'update' && fix.file && fix.content) {
|
|
433
|
+
const outPath = path.join(projectDir, fix.file);
|
|
434
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
435
|
+
await fs.writeFile(outPath, fix.content, 'utf-8');
|
|
436
|
+
debugFixes('Successfully fixed TypeScript errors in %s', fix.file);
|
|
437
|
+
console.log(`Fixed TS errors in ${fix.file}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
debugFixes('TypeScript error fixing complete');
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
*/
|
|
445
|
+
// Removed fixBuildErrors function - checks now handled by check:client command
|
|
446
|
+
/*
|
|
447
|
+
async function fixBuildErrors(ctx: ProjectContext, projectDir: string): Promise<boolean> {
|
|
448
|
+
debugErrors('Checking for build errors in: %s', projectDir);
|
|
449
|
+
const buildErrors = await getBuildErrors(projectDir);
|
|
450
|
+
debugErrors('Found %d build errors', buildErrors.length);
|
|
451
|
+
console.log('Found', buildErrors.length, 'build errors');
|
|
452
|
+
|
|
453
|
+
if (buildErrors.length === 0) {
|
|
454
|
+
debugErrors('No build errors to fix');
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
debugErrors('Build errors found:');
|
|
459
|
+
buildErrors.forEach((error, idx) => {
|
|
460
|
+
debugErrors(' Error %d: %s', idx + 1, error);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const errorFeedback = buildErrors.join('\n');
|
|
464
|
+
const fixupPrompt = `${makeBasePrompt(ctx)}\n
|
|
465
|
+
After your previous changes, the application produced the following build errors:\n\n${errorFeedback}\n
|
|
466
|
+
You must now fix **every** error listed above. This is a critical pass: if any error remains after your fix, your output is rejected.
|
|
467
|
+
|
|
468
|
+
Before generating code, analyze and validate your solution against every error. Use existing component props, imports, and shared interfaces from the project. Do not invent new structures unless absolutely necessary.
|
|
469
|
+
|
|
470
|
+
Strict rules:
|
|
471
|
+
- Never use unsafe imports or invalid module references
|
|
472
|
+
- Do not silence errors — resolve them fully and correctly
|
|
473
|
+
- Fix all errors in each file in one go
|
|
474
|
+
- Reuse existing logic instead of re-creating similar ones
|
|
475
|
+
- Do not modify the GraphQL files
|
|
476
|
+
- Do not submit partial updates; provide the full updated content of the file
|
|
477
|
+
|
|
478
|
+
Output must be a **JSON array** only. Each item must include:
|
|
479
|
+
- \`action\`: "update"
|
|
480
|
+
- \`file\`: relative path to the updated file
|
|
481
|
+
- \`description\`: "Fix build errors"
|
|
482
|
+
- \`content\`: full new content of the file, as a string
|
|
483
|
+
|
|
484
|
+
Do not include explanations, markdown, or code blocks.
|
|
485
|
+
`;
|
|
486
|
+
const fixupPlanText = await callAI(fixupPrompt);
|
|
487
|
+
let fixupPlan: Fix[] = [];
|
|
488
|
+
try {
|
|
489
|
+
fixupPlan = JSON.parse(extractJsonArray(fixupPlanText)) as Fix[];
|
|
490
|
+
debugFixes('Parsed build fixup plan with %d fixes', fixupPlan.length);
|
|
491
|
+
} catch (e) {
|
|
492
|
+
debugFixes('Failed to parse build fixup plan: %O', e);
|
|
493
|
+
console.error('Could not parse build fixup plan from LLM:', e instanceof Error ? e.message : String(e));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
console.log('Fixup plan has', fixupPlan.length, 'items');
|
|
497
|
+
|
|
498
|
+
for (const [index, fix] of fixupPlan.entries()) {
|
|
499
|
+
debugFixes('Applying fix %d/%d: %s', index + 1, fixupPlan.length, fix.file);
|
|
500
|
+
if (fix.action === 'update' && fix.file && fix.content) {
|
|
501
|
+
const outPath = path.join(projectDir, fix.file);
|
|
502
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
503
|
+
await fs.writeFile(outPath, fix.content, 'utf-8');
|
|
504
|
+
debugFixes('Successfully fixed build errors in %s', fix.file);
|
|
505
|
+
console.log(`Fixed build errors in ${fix.file}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
debugFixes('Build error fixing complete');
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
*/
|
|
513
|
+
// Helper to extract all page routes from the IA scheme - kept for potential future use
|
|
514
|
+
// Currently not used as error checking has been removed
|
|
515
|
+
/*
|
|
516
|
+
function extractPageRoutesFromScheme(scheme: Scheme | undefined): string[] {
|
|
517
|
+
debugContext('Extracting page routes from scheme');
|
|
518
|
+
if (scheme?.pages?.items && typeof scheme.pages.items === 'object') {
|
|
519
|
+
const routes = Object.values(scheme.pages.items)
|
|
520
|
+
.map((page) =>
|
|
521
|
+
typeof page === 'object' && 'route' in page && typeof page.route === 'string' ? page.route : undefined,
|
|
522
|
+
)
|
|
523
|
+
.filter((route): route is string => typeof route === 'string');
|
|
524
|
+
debugContext('Extracted %d routes: %o', routes.length, routes);
|
|
525
|
+
return routes;
|
|
526
|
+
}
|
|
527
|
+
debugContext('No page routes found in scheme');
|
|
528
|
+
return [];
|
|
529
|
+
}
|
|
530
|
+
*/
|
|
531
|
+
// Removed checkRouteErrors function - checks now handled by check:client command
|
|
532
|
+
/*
|
|
533
|
+
async function checkRouteErrors(baseUrl: string, routes: string[]): Promise<string[]> {
|
|
534
|
+
const allConsoleErrors: string[] = [];
|
|
535
|
+
// Function removed - checks handled by check:client command
|
|
536
|
+
return allConsoleErrors;
|
|
537
|
+
}
|
|
538
|
+
*/
|
|
539
|
+
// Removed applyFixes function - checks now handled by check:client command
|
|
540
|
+
/*
|
|
541
|
+
async function applyFixes(fixupPlan: any[], projectDir: string): Promise<void> {
|
|
542
|
+
// Function removed - checks handled by check:client command
|
|
543
|
+
}
|
|
544
|
+
*/
|
|
545
|
+
// Removed fixConsoleErrors function - checks now handled by check:client command
|
|
546
|
+
/*
|
|
547
|
+
async function fixConsoleErrors(ctx: ProjectContext, projectDir: string): Promise<boolean> {
|
|
548
|
+
// Function removed - checks handled by check:client command
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
*/
|
|
552
|
+
// Visual error reporting removed - can be handled separately if needed
|
|
553
|
+
/*
|
|
554
|
+
async function checkVisualErrors(baseUrl: string, routes: string[], theme: string): Promise<string> {
|
|
555
|
+
debugScreenshots('Checking visual errors for %d routes', routes.length);
|
|
556
|
+
const screenshots = await getPageScreenshots(baseUrl, routes);
|
|
557
|
+
debugScreenshots('Got %d screenshots', screenshots.length);
|
|
558
|
+
|
|
559
|
+
let allVisualErrors: string = '';
|
|
560
|
+
for (const [index, screenshot] of screenshots.entries()) {
|
|
561
|
+
debugScreenshots('Analyzing screenshot %d/%d for route: %s', index + 1, screenshots.length, screenshot.route);
|
|
562
|
+
console.log(`Checking visual errors for ${screenshot.route}`);
|
|
563
|
+
const error = await generateTextWithImageAI(
|
|
564
|
+
`
|
|
565
|
+
This is the theme used: ${theme}.
|
|
566
|
+
When analyzing UI screenshots, only flag high-impact visual issues that significantly affect usability, accessibility, or user comprehension. Ignore minor spacing inconsistencies, slight misalignments, and non-critical aesthetic variations unless they create a clear functional or accessibility problem. Focus feedback on elements that:
|
|
567
|
+
- Do not flag color or style choices that match the theme.
|
|
568
|
+
- Do not critique placeholder contrast, alignment, or heading hierarchy unless the text is truly unreadable or confusing.
|
|
569
|
+
- Ignore small alignment shifts, whitespace distribution, and center-aligned titles.
|
|
570
|
+
- Only highlight contrast issues if they fail WCAG standards or make text functionally unreadable.
|
|
571
|
+
- Do not mention the lack of loading indicators unless it causes a clear usability failure (e.g., users stuck or misled).
|
|
572
|
+
- Focus only on issues that break flow, block interaction, or seriously reduce clarity.
|
|
573
|
+
- Allow intentionally unique design elements like center-aligned titles.
|
|
574
|
+
- Do not report white space as an issue when the layout is intentionally minimal.
|
|
575
|
+
- Skip pixel-perfect feedback unless there’s a clear visual or structural flaw.
|
|
576
|
+
- Focus on readability, navigability, accessibility, and broken UI flows.
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
IMPORTANT: return in a nicely formatted markdown, easy to read for the user, not as array of markdown, pure markdown content! Include the route: ${screenshot.route} name, because I have multiple errors showing, and add an empty line at the end.
|
|
580
|
+
IMPORTANT: don't overly nest the markdown sections, just one # Visual Report, below it the name of the route: ## Route: _route_name_, and ### _types_of_issues_ per route (can have multiple under same route) and bullet list after that
|
|
581
|
+
IMPORTANT: return something only if you found valid errors, I don't want to show only the route name from the above request.
|
|
582
|
+
`,
|
|
583
|
+
screenshot.screenshot,
|
|
584
|
+
AIProvider.OpenAI,
|
|
585
|
+
);
|
|
586
|
+
if (error) {
|
|
587
|
+
debugScreenshots('Visual errors found on route %s', screenshot.route);
|
|
588
|
+
allVisualErrors += error;
|
|
589
|
+
} else {
|
|
590
|
+
debugScreenshots('No visual errors on route %s', screenshot.route);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
debugScreenshots('Visual error check complete, total errors length: %d', allVisualErrors.length);
|
|
595
|
+
return allVisualErrors;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async function getPageScreenshots(baseUrl: string, routes: string[]): Promise<{ route: string; screenshot: string }[]> {
|
|
599
|
+
debugScreenshots('Taking screenshots for %d routes', routes.length);
|
|
600
|
+
const pageScreenshots: { route: string; screenshot: string }[] = [];
|
|
601
|
+
|
|
602
|
+
for (const [index, route] of routes.entries()) {
|
|
603
|
+
const url = baseUrl + (route.startsWith('/') ? route : '/' + route);
|
|
604
|
+
debugScreenshots('Taking screenshot %d/%d for: %s', index + 1, routes.length, url);
|
|
605
|
+
console.log(`Taking screenshot for ${url}`);
|
|
606
|
+
|
|
607
|
+
const screenshot = await getPageScreenshot(url);
|
|
608
|
+
if (screenshot) {
|
|
609
|
+
debugScreenshots('Screenshot captured for %s, size: %d bytes', route, screenshot.length);
|
|
610
|
+
pageScreenshots.push({
|
|
611
|
+
route: route,
|
|
612
|
+
screenshot: screenshot,
|
|
613
|
+
});
|
|
614
|
+
} else {
|
|
615
|
+
debugScreenshots('Failed to capture screenshot for %s', route);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
debugScreenshots('Closing browser after screenshots');
|
|
620
|
+
await closeBrowser();
|
|
621
|
+
debugScreenshots('Captured %d screenshots', pageScreenshots.length);
|
|
622
|
+
return pageScreenshots;
|
|
623
|
+
}
|
|
624
|
+
*/
|
|
625
|
+
// Visual error reporting removed - can be handled separately if needed
|
|
626
|
+
/*
|
|
627
|
+
async function reportVisualErrors(ctx: ProjectContext): Promise<void> {
|
|
628
|
+
debugScreenshots('Starting visual error report');
|
|
629
|
+
const baseUrl = 'http://localhost:8080';
|
|
630
|
+
|
|
631
|
+
let routes = extractPageRoutesFromScheme(ctx.scheme);
|
|
632
|
+
if (routes.length === 0) {
|
|
633
|
+
debugScreenshots('No routes found, defaulting to root');
|
|
634
|
+
routes = ['/'];
|
|
635
|
+
}
|
|
636
|
+
debugScreenshots('Reporting visual errors for %d routes', routes.length);
|
|
637
|
+
|
|
638
|
+
const allVisualErrors = await checkVisualErrors(baseUrl, routes, ctx.theme);
|
|
639
|
+
|
|
640
|
+
if (allVisualErrors) {
|
|
641
|
+
debugScreenshots('Visual errors report generated, length: %d', allVisualErrors.length);
|
|
642
|
+
} else {
|
|
643
|
+
debugScreenshots('No visual errors to report');
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
console.log(allVisualErrors);
|
|
647
|
+
}
|
|
648
|
+
*/
|
|
649
|
+
// async function fixVisualErrors(ctx: ProjectContext, projectDir: string): Promise<boolean> {
|
|
650
|
+
// const baseUrl = 'http://localhost:8080';
|
|
651
|
+
// let routes = extractPageRoutesFromScheme(ctx.scheme);
|
|
652
|
+
// if (routes.length === 0) {
|
|
653
|
+
// routes = ['/'];
|
|
654
|
+
// }
|
|
655
|
+
//
|
|
656
|
+
// const allVisualErrors = await checkVisualErrors(baseUrl, routes, ctx.theme);
|
|
657
|
+
// console.log('Found', allVisualErrors, 'visual errors');
|
|
658
|
+
// if (allVisualErrors.length === 0) {
|
|
659
|
+
// await closeBrowser();
|
|
660
|
+
// return false;
|
|
661
|
+
// }
|
|
662
|
+
//
|
|
663
|
+
// const fixupPrompt = `${makeBasePrompt(ctx)}\n
|
|
664
|
+
// After your previous changes, the application has the following visual errors:\n\n${allVisualErrors}\n
|
|
665
|
+
// You must now fix **every** error listed above. This is a critical pass: if any error remains after your fix, your output is rejected.
|
|
666
|
+
//
|
|
667
|
+
// Before generating code, analyze and validate your solution against every error. Use existing types, props, and logic from the project. Do not invent new structures unless absolutely necessary.
|
|
668
|
+
//
|
|
669
|
+
// Strict rules:
|
|
670
|
+
// - Ignore connection or network errors
|
|
671
|
+
// - Never use \`any\`, unsafe type assertions, or silence errors
|
|
672
|
+
// - Do not silence errors — resolve them fully and correctly
|
|
673
|
+
// - Fix all errors in each file in one go
|
|
674
|
+
// - Reuse existing logic or types instead of re-creating similar ones
|
|
675
|
+
// - Do not submit partial updates; provide the full updated content of the file
|
|
676
|
+
//
|
|
677
|
+
// Output must be a **JSON array** only. Each item must include:
|
|
678
|
+
// - \`action\`: "update"
|
|
679
|
+
// - \`file\`: relative path to the updated file
|
|
680
|
+
// - \`description\`: "Fix console errors"
|
|
681
|
+
// - \`content\`: full new content of the file, as a string
|
|
682
|
+
//
|
|
683
|
+
// Do not include explanations, markdown, or code blocks.
|
|
684
|
+
// `;
|
|
685
|
+
// let fixupPlan: Fix[] = [];
|
|
686
|
+
// try {
|
|
687
|
+
// fixupPlan = JSON.parse(extractJsonArray(await callAI(fixupPrompt))) as Fix[];
|
|
688
|
+
// } catch (e) {
|
|
689
|
+
// console.error('Could not parse visual fixup plan from LLM:', e instanceof Error ? e.message : String(e));
|
|
690
|
+
// }
|
|
691
|
+
//
|
|
692
|
+
// console.log('Fixup plan has', fixupPlan.length, 'items');
|
|
693
|
+
// await applyFixes(fixupPlan, projectDir);
|
|
694
|
+
// await closeBrowser();
|
|
695
|
+
// return true;
|
|
696
|
+
// }
|
|
697
|
+
// Removed fixErrorsLoop function - checks now handled by check:client command
|
|
698
|
+
/*
|
|
699
|
+
async function fixErrorsLoop(ctx: ProjectContext, projectDir: string) {
|
|
700
|
+
// Function removed - checks handled by check:client command
|
|
701
|
+
}
|
|
702
|
+
*/
|
|
703
|
+
export async function runAIAgent(projectDir, iaSchemeDir, designSystemPath) {
|
|
704
|
+
debug('='.repeat(80));
|
|
705
|
+
debug('Starting AI agent');
|
|
706
|
+
debug('Project directory: %s', projectDir);
|
|
707
|
+
debug('IA scheme directory: %s', iaSchemeDir);
|
|
708
|
+
debug('Design system path: %s', designSystemPath);
|
|
709
|
+
debug('='.repeat(80));
|
|
710
|
+
const userPreferencesFile = path.join(projectDir, 'design-system-principles.md');
|
|
711
|
+
debug('Loading user preferences from: %s', userPreferencesFile);
|
|
712
|
+
const userPreferences = await fs.readFile(userPreferencesFile, 'utf-8');
|
|
713
|
+
debug('User preferences loaded, size: %d bytes', userPreferences.length);
|
|
714
|
+
debug('Loading design system from: %s', designSystemPath);
|
|
715
|
+
const designSystem = await fs.readFile(designSystemPath, 'utf-8');
|
|
716
|
+
debug('Design system loaded, size: %d bytes', designSystem.length);
|
|
717
|
+
debug('Building project context...');
|
|
718
|
+
const ctx = await getProjectContext(projectDir, iaSchemeDir, userPreferences, designSystem);
|
|
719
|
+
debug('Project context created successfully');
|
|
720
|
+
debug('Planning project implementation...');
|
|
721
|
+
const plan = await planProject(ctx);
|
|
722
|
+
debugPlan('Generated plan with %d items', plan.length);
|
|
723
|
+
debug('Applying implementation plan...');
|
|
724
|
+
await applyPlan(plan, ctx, projectDir);
|
|
725
|
+
debug('Plan applied successfully');
|
|
726
|
+
// Error checking removed - now handled by separate check:client command
|
|
727
|
+
debug('Skipping error correction phase - use check:client command for validation');
|
|
728
|
+
// Visual error reporting removed - can be handled separately if needed
|
|
729
|
+
debug('Skipping visual error report - use separate visual testing tools if needed');
|
|
730
|
+
// Browser cleanup no longer needed as we don't run checks
|
|
731
|
+
debug('Implementation complete - no browser cleanup needed');
|
|
732
|
+
debug('='.repeat(80));
|
|
733
|
+
console.log('AI project implementation complete!');
|
|
734
|
+
debug('AI agent completed successfully');
|
|
735
|
+
debug('='.repeat(80));
|
|
736
|
+
}
|
|
737
|
+
//# sourceMappingURL=agent.js.map
|