@vatzzza/botintern 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +33 -0
- package/README.md +15 -0
- package/bun.lock +80 -0
- package/index.js +425 -0
- package/lib/ai.js +531 -0
- package/lib/aiFileParser.js +54 -0
- package/lib/fileTreeParser.js +105 -0
- package/lib/loop.js +174 -0
- package/lib/registerAndFetchKey.js +75 -0
- package/lib/runPlaywright.js +39 -0
- package/lib/runTests.js +418 -0
- package/lib/scan.js +58 -0
- package/package.json +29 -0
- package/tsconfig.json +29 -0
- package/utils/errorExtractor.js +33 -0
- package/utils/setupPlaywright.js +29 -0
- package/utils/yaml-example.txt +21 -0
- package/utils/yamlGuardrails.js +130 -0
package/lib/ai.js
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { GoogleGenAI } from "@google/genai";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import { validateYamlStructure, createBackup, safeYamlUpdate, validateMinimalChanges } from "../utils/yamlGuardrails.js";
|
|
8
|
+
import { getApiKey } from "./registerAndFetchKey.js";
|
|
9
|
+
|
|
10
|
+
const envDir = path.join(process.cwd(), ".env");
|
|
11
|
+
|
|
12
|
+
const model = "gemini-3-pro-preview";
|
|
13
|
+
|
|
14
|
+
dotenv.config({ path: envDir });
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
function cleanCode(text) {
|
|
18
|
+
// Remove markdown blocks (```tsx ... ```)
|
|
19
|
+
let clean = text.replace(/```[a-z]*\n/g, '').replace(/```/g, '');
|
|
20
|
+
// Remove any "Here is the code:" prefixes the AI might add
|
|
21
|
+
clean = clean.replace(/^Here is the .*? code:?/i, '');
|
|
22
|
+
return clean.trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function cleanYaml(text) {
|
|
26
|
+
// Removes ```yaml at the start
|
|
27
|
+
let clean = text.replace(/```yaml\s*/g, '');
|
|
28
|
+
// Removes ``` at the end
|
|
29
|
+
clean = clean.replace(/```\s*$/g, '');
|
|
30
|
+
// Removes any leading/trailing whitespace
|
|
31
|
+
return clean.trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
function getApiKeyOrThrow() {
|
|
37
|
+
const apiKey = getApiKey();
|
|
38
|
+
if (!apiKey) {
|
|
39
|
+
console.error(chalk.red('\nā Error: GEMINI_API_KEY not found.'));
|
|
40
|
+
console.error(chalk.yellow('š Please run: botintern login'));
|
|
41
|
+
console.error(chalk.yellow(' Or create a .env file with: GEMINI_API_KEY=AIzaSy...'));
|
|
42
|
+
console.error(chalk.dim(' (Get one for free at https://aistudio.google.com/)\n'));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
return apiKey;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createAIClient() {
|
|
49
|
+
const apiKey = getApiKeyOrThrow();
|
|
50
|
+
return new GoogleGenAI({ apiKey });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function fixBuild(context) {
|
|
54
|
+
const [err, sourceCode, packageJSON] = context;
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
const prompt = `
|
|
59
|
+
ROLE: You are a Senior React Engineer (BotIntern).
|
|
60
|
+
TASK: Fix the code below to resolve the build error.
|
|
61
|
+
|
|
62
|
+
INPUTS:
|
|
63
|
+
1. SOURCE CODE:
|
|
64
|
+
${sourceCode}
|
|
65
|
+
|
|
66
|
+
2. ERROR LOG:
|
|
67
|
+
${err}
|
|
68
|
+
|
|
69
|
+
3. INSTALLED PACKAGES (package.json):
|
|
70
|
+
${packageJSON}
|
|
71
|
+
|
|
72
|
+
CRITICAL RULES:
|
|
73
|
+
1. **Completeness:** Return the FULL file content. Do not truncate. Do not use placeholders like "// ... rest of code".
|
|
74
|
+
2. **Hallucinations:** Do NOT import packages that are not in package.json (unless they are built-in Node/React modules).
|
|
75
|
+
3. **The Fix:** If an import is missing/broken, remove it or replace it with a valid alternative from package.json.
|
|
76
|
+
4. **Output:** Return ONLY the code. No explanations. No markdown backticks.
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
const spinner = ora(chalk.yellow('Fixing the errors...')).start();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const ai = createAIClient();
|
|
85
|
+
const response = await ai.models.generateContent({
|
|
86
|
+
model: model,
|
|
87
|
+
contents: prompt,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log("Raw txt");
|
|
91
|
+
console.log(response.text);
|
|
92
|
+
const rawText = response.text;
|
|
93
|
+
const finalCode = cleanCode(rawText);
|
|
94
|
+
console.log(finalCode);
|
|
95
|
+
if (!finalCode.includes('import') && !finalCode.includes('export')) {
|
|
96
|
+
spinner.warn(chalk.yellow('Warning: The AI output looks suspicious (no imports/exports found).'));
|
|
97
|
+
}
|
|
98
|
+
spinner.succeed(chalk.green('⨠Code fixed and cleaned.'));
|
|
99
|
+
return finalCode;
|
|
100
|
+
|
|
101
|
+
// spinner.succeed(chalk.green('Build Passed! No errors found.'));
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.error(e);
|
|
104
|
+
spinner.fail(chalk.red('Build Failed! Hallucinations detected.'));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async function generateYaml(context) {
|
|
111
|
+
const [fileTree, sourceCode, yaml, userPrompt] = context;
|
|
112
|
+
|
|
113
|
+
const prompt = `
|
|
114
|
+
ROLE: You are a QA Architect (BotIntern) with PRECISION EDITING capabilities.
|
|
115
|
+
TASK: Make MINIMAL, ACCURATE changes to the YAML test plan. DO NOT RESTRUCTURE UNNECESSARILY.
|
|
116
|
+
|
|
117
|
+
šØ CRITICAL GUARDRAILS šØ
|
|
118
|
+
1. PRESERVE existing working scenarios - ONLY modify what's broken or requested
|
|
119
|
+
2. DO NOT change meta section unless explicitly requested by user
|
|
120
|
+
3. DO NOT restructure the entire file - make targeted edits only
|
|
121
|
+
4. MINIMAL CHANGES PRINCIPLE: If a test case works, DO NOT touch it
|
|
122
|
+
|
|
123
|
+
--- PRECISION EDITING RULES ---
|
|
124
|
+
1. ANALYZE what's already working and preserve it
|
|
125
|
+
2. ONLY modify scenarios that are failing or user-specified
|
|
126
|
+
3. Use NEW syntax for any NEW or MODIFIED content:
|
|
127
|
+
- see: "Text" (instead of assert_visible/text)
|
|
128
|
+
- click: "Text" (instead of type: click)
|
|
129
|
+
- type: "Value" into: "Label" (instead of type: type)
|
|
130
|
+
- url: "/path" (checks navigation)
|
|
131
|
+
- network: "METHOD URL" (checks API)
|
|
132
|
+
- color: 'colorValue on Element' (checks text color)
|
|
133
|
+
- background: 'colorValue on Element' (checks background color)
|
|
134
|
+
- border-color: 'colorValue on Element' (checks border color)
|
|
135
|
+
|
|
136
|
+
šØ COLOR TESTING SYNTAX (CRITICAL YAML RULES):
|
|
137
|
+
|
|
138
|
+
šØ YAML SYNTAX RULE: The word "on" is a YAML boolean keyword!
|
|
139
|
+
You MUST quote the ENTIRE color value as a SINGLE STRING to avoid parsing errors.
|
|
140
|
+
|
|
141
|
+
ā
CORRECT SYNTAX (quote entire value):
|
|
142
|
+
- color: 'white on Submit Button'
|
|
143
|
+
- color: '#1f2937 on Heading'
|
|
144
|
+
- color: 'rgb(107, 114, 128) on Description'
|
|
145
|
+
- background: '#3b82f6 on Primary Button'
|
|
146
|
+
- background: 'red on Error Alert'
|
|
147
|
+
- background: 'rgb(229, 231, 235) on Card'
|
|
148
|
+
- border-color: '#ef4444 on Error Input'
|
|
149
|
+
- border-color: 'blue on Focus Ring'
|
|
150
|
+
|
|
151
|
+
ā WRONG SYNTAX (will cause YAML parsing errors):
|
|
152
|
+
- color: "white" on "Submit Button" # ERROR: 'on' interpreted as boolean
|
|
153
|
+
- color: white on Submit Button # ERROR: unquoted values
|
|
154
|
+
- color: "#fff" on "Button" # ERROR: split quotes
|
|
155
|
+
|
|
156
|
+
š COLOR FORMATS (all must be in single quotes with element):
|
|
157
|
+
- Named colors: 'red on Error', 'blue on Link', 'white on Button'
|
|
158
|
+
- Hex colors: '#ff0000 on Alert', '#3b82f6 on Primary', '#f00 on Icon'
|
|
159
|
+
- RGB colors: 'rgb(255, 0, 0) on Text', 'rgb(59, 130, 246) on Background'
|
|
160
|
+
|
|
161
|
+
When to add color tests:
|
|
162
|
+
- Brand colors on primary elements (buttons, headers, logos)
|
|
163
|
+
- Error/success states (red errors, green success)
|
|
164
|
+
- Accessibility (ensure sufficient contrast)
|
|
165
|
+
- Dark mode validation
|
|
166
|
+
|
|
167
|
+
--- INPUTS ---
|
|
168
|
+
1. SOURCE CODE ANALYSIS:
|
|
169
|
+
${sourceCode}
|
|
170
|
+
|
|
171
|
+
2. EXISTING YAML (PRESERVE WHAT WORKS):
|
|
172
|
+
${yaml || "No existing plan - generate minimal tests only for core functionality"}
|
|
173
|
+
|
|
174
|
+
3. USER REQUEST (REQUIREMENT):
|
|
175
|
+
${userPrompt || "Make minimal changes to ensure tests pass"}
|
|
176
|
+
|
|
177
|
+
--- EDITING STRATEGY ---
|
|
178
|
+
1. If existing YAML works ā ONLY fix syntax issues, preserve structure
|
|
179
|
+
2. If user requests changes ā Apply ONLY to requested sections
|
|
180
|
+
3. If tests are failing ā Update ONLY failing test cases
|
|
181
|
+
4. Output FULL YAML but with MINIMAL changes from original
|
|
182
|
+
|
|
183
|
+
--- EXAMPLE OF PRECISION EDITING ---
|
|
184
|
+
If user says "fix login test", change ONLY the login scenario:
|
|
185
|
+
|
|
186
|
+
scenarios:
|
|
187
|
+
- name: "Login Flow" # KEEP this name
|
|
188
|
+
path: "/login" # KEEP this path
|
|
189
|
+
tests: # UPDATE ONLY this section
|
|
190
|
+
- see: "Welcome Back" # Updated to new syntax
|
|
191
|
+
- color: '#1f2937 on Welcome Back' # NEW - add color validation (SINGLE QUOTES!)
|
|
192
|
+
- click: "Sign In" # Updated to new syntax
|
|
193
|
+
- background: '#3b82f6 on Sign In' # NEW - validate button color (SINGLE QUOTES!)
|
|
194
|
+
- url: "/dashboard" # NEW - add if missing
|
|
195
|
+
|
|
196
|
+
--- EXAMPLE WITH COLOR TESTS ---
|
|
197
|
+
When generating tests for visual elements, include color validation:
|
|
198
|
+
|
|
199
|
+
scenarios:
|
|
200
|
+
- name: "Button Colors"
|
|
201
|
+
path: "/"
|
|
202
|
+
tests:
|
|
203
|
+
- see: "Get Started"
|
|
204
|
+
- color: 'white on Get Started'
|
|
205
|
+
- background: '#3b82f6 on Get Started'
|
|
206
|
+
- click: "Get Started"
|
|
207
|
+
|
|
208
|
+
- name: "Error States"
|
|
209
|
+
path: "/form"
|
|
210
|
+
tests:
|
|
211
|
+
- click: "Submit"
|
|
212
|
+
- see: "Error"
|
|
213
|
+
- color: 'red on Error'
|
|
214
|
+
- border-color: '#ef4444 on Email Input'
|
|
215
|
+
|
|
216
|
+
--- YAML FORMATTING RULES (CRITICAL) ---
|
|
217
|
+
|
|
218
|
+
šØ STRICT YAML SYNTAX REQUIREMENTS:
|
|
219
|
+
|
|
220
|
+
1. **Indentation:** Use 2 spaces (NOT tabs) for each level
|
|
221
|
+
- scenarios: (0 spaces)
|
|
222
|
+
- - name: (2 spaces)
|
|
223
|
+
- path: (4 spaces)
|
|
224
|
+
- tests: (4 spaces)
|
|
225
|
+
- - see: (6 spaces)
|
|
226
|
+
|
|
227
|
+
2. **List Items:** Always use "- " (dash + space)
|
|
228
|
+
ā
CORRECT: - see: "Text"
|
|
229
|
+
ā WRONG: -see: "Text" (no space)
|
|
230
|
+
ā WRONG: - see: "Text" (extra space before dash)
|
|
231
|
+
|
|
232
|
+
3. **Quotes:** Use double quotes for regular values, single quotes for color values
|
|
233
|
+
ā
CORRECT: - see: "Button"
|
|
234
|
+
ā
CORRECT: - color: 'red on Button'
|
|
235
|
+
ā WRONG: - see: Button (unquoted)
|
|
236
|
+
|
|
237
|
+
4. **Colons:** Always add space after colon
|
|
238
|
+
ā
CORRECT: name: "Test"
|
|
239
|
+
ā WRONG: name:"Test"
|
|
240
|
+
|
|
241
|
+
5. **Multi-line values:** Use proper YAML syntax
|
|
242
|
+
For "type X into Y" syntax:
|
|
243
|
+
- type: "Value"
|
|
244
|
+
into: "Label"
|
|
245
|
+
(Note: "into" is indented 2 spaces from "type")
|
|
246
|
+
|
|
247
|
+
6. **No trailing spaces or tabs**
|
|
248
|
+
|
|
249
|
+
7. **Consistent spacing:** Maintain same indentation throughout file
|
|
250
|
+
|
|
251
|
+
VALIDATION CHECKLIST before outputting:
|
|
252
|
+
ā All list items start with "- " at correct indentation
|
|
253
|
+
ā All color values use single quotes: 'value on element'
|
|
254
|
+
ā All other string values use double quotes
|
|
255
|
+
ā Consistent 2-space indentation throughout
|
|
256
|
+
ā No tabs, only spaces
|
|
257
|
+
ā Space after every colon
|
|
258
|
+
ā Multi-line "type/into" syntax properly indented
|
|
259
|
+
|
|
260
|
+
--- FINAL INSTRUCTION ---
|
|
261
|
+
Output COMPLETE YAML but with MINIMAL, TARGETED changes only.
|
|
262
|
+
Preserve all working functionality.
|
|
263
|
+
VERIFY YAML syntax is correct before outputting.
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
const spinner = ora(chalk.yellow('š Analyzing YAML changes...')).start();
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
// Create backup if original exists
|
|
270
|
+
if (yaml && yaml.trim()) {
|
|
271
|
+
const backupPath = createBackup('vibe.yaml', yaml);
|
|
272
|
+
spinner.text = chalk.blue(`š Backup created: ${path.basename(backupPath)}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const ai = createAIClient();
|
|
276
|
+
const response = await ai.models.generateContent({
|
|
277
|
+
model: model,
|
|
278
|
+
contents: prompt,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const rawText = response.text;
|
|
282
|
+
let finalYaml = cleanYaml(rawText);
|
|
283
|
+
|
|
284
|
+
// Validate the generated YAML structure
|
|
285
|
+
const validation = validateYamlStructure(finalYaml);
|
|
286
|
+
|
|
287
|
+
if (!validation.isValid) {
|
|
288
|
+
spinner.warn(chalk.yellow(`ā ļø Invalid syntax detected: ${validation.invalidKeys.join(', ')}`));
|
|
289
|
+
// Apply safe updates to preserve working parts
|
|
290
|
+
if (yaml) {
|
|
291
|
+
finalYaml = safeYamlUpdate(yaml, finalYaml);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Validate minimal changes if original exists
|
|
296
|
+
if (yaml && yaml.trim()) {
|
|
297
|
+
const changeValidation = validateMinimalChanges(yaml, finalYaml, userPrompt);
|
|
298
|
+
|
|
299
|
+
if (!changeValidation.isValid) {
|
|
300
|
+
spinner.warn(chalk.yellow(changeValidation.recommendation));
|
|
301
|
+
console.log(chalk.yellow('š Change Summary:'));
|
|
302
|
+
console.log(chalk.gray(` Total lines: ${changeValidation.diff.stats.total}`));
|
|
303
|
+
console.log(chalk.gray(` Changed: ${changeValidation.diff.stats.added + changeValidation.diff.stats.removed}`));
|
|
304
|
+
console.log(chalk.gray(` Change percentage: ${changeValidation.diff.changePercentage.toFixed(1)}%`));
|
|
305
|
+
} else {
|
|
306
|
+
spinner.succeed(chalk.green(changeValidation.recommendation));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Final validation check
|
|
311
|
+
if (finalYaml.length === 0) {
|
|
312
|
+
throw new Error('Generated YAML is empty');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
spinner.succeed(chalk.green('ā
YAML updated with precision guardrails'));
|
|
316
|
+
return finalYaml;
|
|
317
|
+
|
|
318
|
+
} catch (e) {
|
|
319
|
+
console.error(e);
|
|
320
|
+
spinner.fail(chalk.red('ā YAML generation failed'));
|
|
321
|
+
throw e;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function fix(context) {
|
|
326
|
+
const [err, sourceCode, packageJSON, userPrompt] = context;
|
|
327
|
+
const spinner = ora(chalk.yellow('Fixing the errors...')).start();
|
|
328
|
+
const prompt = `
|
|
329
|
+
ROLE: You are a Senior React Engineer (BotIntern).
|
|
330
|
+
TASK: Fix the code below to resolve the build error.
|
|
331
|
+
|
|
332
|
+
INPUTS:
|
|
333
|
+
1. SOURCE CODE:
|
|
334
|
+
${sourceCode}
|
|
335
|
+
|
|
336
|
+
2. ERROR LOG:
|
|
337
|
+
${err}
|
|
338
|
+
|
|
339
|
+
3. INSTALLED PACKAGES (package.json):
|
|
340
|
+
${packageJSON}
|
|
341
|
+
|
|
342
|
+
CRITICAL OUTPUT FORMAT RULES:
|
|
343
|
+
You MUST return files using this EXACT format:
|
|
344
|
+
|
|
345
|
+
--- FILE: path/to/file.tsx ---
|
|
346
|
+
[complete fixed file content here]
|
|
347
|
+
|
|
348
|
+
--- FILE: path/to/another-file.tsx ---
|
|
349
|
+
[complete fixed file content here]
|
|
350
|
+
|
|
351
|
+
CRITICAL RULES:
|
|
352
|
+
1. **Completeness:** Return the FULL file content. Do not truncate. Do not use placeholders like "// ... rest of code".
|
|
353
|
+
2. **Hallucinations:** Do NOT import packages that are not in package.json (unless they are built-in Node/React modules).
|
|
354
|
+
3. **The Fix:** If an import is missing/broken, remove it or replace it with a valid alternative from package.json.
|
|
355
|
+
4. **NO MARKDOWN:** Do NOT use markdown code blocks (\`\`\`). Do NOT add explanations.
|
|
356
|
+
5. **NO TEST OUTPUT:** Do NOT include test results or any other text. ONLY file content with --- FILE: headers.
|
|
357
|
+
6. **File Markers Required:** Every file MUST start with "--- FILE: path ---" on its own line.
|
|
358
|
+
|
|
359
|
+
EXAMPLE CORRECT OUTPUT:
|
|
360
|
+
--- FILE: app/page.tsx ---
|
|
361
|
+
import React from 'react';
|
|
362
|
+
|
|
363
|
+
export default function Page() {
|
|
364
|
+
return <div>Fixed</div>;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
${userPrompt ? `USER REQUEST: ${userPrompt}` : ''}
|
|
368
|
+
|
|
369
|
+
NOW FIX THE CODE USING THE EXACT FORMAT ABOVE:
|
|
370
|
+
`;
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
const ai = createAIClient();
|
|
375
|
+
const response = await ai.models.generateContent({
|
|
376
|
+
model: model,
|
|
377
|
+
contents: prompt,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const rawText = response.text;
|
|
381
|
+
|
|
382
|
+
// Validate response format
|
|
383
|
+
if (!rawText.includes('--- FILE:')) {
|
|
384
|
+
spinner.warn(chalk.yellow('ā ļø AI response missing file markers. Response may be malformed.'));
|
|
385
|
+
console.log(chalk.dim('Response preview:'), rawText.substring(0, 200) + '...');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
spinner.succeed(chalk.green('⨠Code fixes generated.'));
|
|
389
|
+
return rawText;
|
|
390
|
+
|
|
391
|
+
} catch (e) {
|
|
392
|
+
console.error(e);
|
|
393
|
+
spinner.fail(chalk.red('Code fix generation failed!'));
|
|
394
|
+
throw e; // Re-throw to handle upstream
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function fixTestFailures(context) {
|
|
399
|
+
const [testFailures, yamlContent, sourceCode, fileTree, packageJSON] = context;
|
|
400
|
+
|
|
401
|
+
const spinner = ora(chalk.yellow('š¤ AI analyzing test failures...')).start();
|
|
402
|
+
|
|
403
|
+
// Format test failures for better readability
|
|
404
|
+
const formattedFailures = testFailures.map((failure, idx) => {
|
|
405
|
+
return `
|
|
406
|
+
FAILURE #${idx + 1}:
|
|
407
|
+
- Scenario: ${failure.scenario}
|
|
408
|
+
- Action: ${JSON.stringify(failure.action)}
|
|
409
|
+
- Error: ${failure.error}
|
|
410
|
+
`;
|
|
411
|
+
}).join('\n');
|
|
412
|
+
|
|
413
|
+
const prompt = `
|
|
414
|
+
ROLE: You are a Senior Full-Stack Engineer (BotIntern) specializing in test-driven development.
|
|
415
|
+
TASK: Fix the code to make ALL failing tests pass.
|
|
416
|
+
|
|
417
|
+
šØ CRITICAL CONTEXT šØ
|
|
418
|
+
You have ${testFailures.length} FAILING TEST(S). Your job is to analyze WHY they failed and fix the code accordingly.
|
|
419
|
+
|
|
420
|
+
--- TEST FAILURES ---
|
|
421
|
+
${formattedFailures}
|
|
422
|
+
|
|
423
|
+
--- TEST EXPECTATIONS (vibe.yaml) ---
|
|
424
|
+
${yamlContent}
|
|
425
|
+
|
|
426
|
+
The YAML above defines what the tests EXPECT to see/do. Your fixes must make the actual code match these expectations.
|
|
427
|
+
|
|
428
|
+
--- CURRENT SOURCE CODE ---
|
|
429
|
+
${sourceCode}
|
|
430
|
+
|
|
431
|
+
--- PROJECT STRUCTURE ---
|
|
432
|
+
${fileTree}
|
|
433
|
+
|
|
434
|
+
--- INSTALLED PACKAGES (package.json) ---
|
|
435
|
+
${packageJSON}
|
|
436
|
+
|
|
437
|
+
šÆ ANALYSIS STRATEGY:
|
|
438
|
+
1. For each test failure, identify WHAT the test expected (from YAML)
|
|
439
|
+
2. Understand WHY it failed (from error message)
|
|
440
|
+
3. Determine WHICH file(s) need changes
|
|
441
|
+
4. Make MINIMAL, TARGETED fixes to satisfy the test expectations
|
|
442
|
+
|
|
443
|
+
š COMMON FAILURE PATTERNS & FIXES:
|
|
444
|
+
|
|
445
|
+
**Pattern 1: "Text not found"**
|
|
446
|
+
- Test expects to see specific text (e.g., "Dashboard", "Total Tests")
|
|
447
|
+
- Fix: Add or correct the text in the relevant component
|
|
448
|
+
|
|
449
|
+
**Pattern 2: "Element not found" / "Click failed"**
|
|
450
|
+
- Test expects a clickable element with specific text
|
|
451
|
+
- Fix: Add the button/link with exact text, ensure it's visible
|
|
452
|
+
|
|
453
|
+
**Pattern 3: "Input not found"**
|
|
454
|
+
- Test expects input with specific label or placeholder
|
|
455
|
+
- Fix: Add proper label or placeholder attribute to input
|
|
456
|
+
|
|
457
|
+
**Pattern 4: "Navigation failed" / "URL mismatch"**
|
|
458
|
+
- Test expects navigation to specific path
|
|
459
|
+
- Fix: Add proper routing or onClick navigation
|
|
460
|
+
|
|
461
|
+
**Pattern 5: "Timeout" / "Element not visible"**
|
|
462
|
+
- Element exists but not rendered/visible
|
|
463
|
+
- Fix: Check conditional rendering, CSS display/visibility
|
|
464
|
+
|
|
465
|
+
š§ CRITICAL OUTPUT FORMAT RULES:
|
|
466
|
+
You MUST return files using this EXACT format:
|
|
467
|
+
|
|
468
|
+
--- FILE: path/to/file.tsx ---
|
|
469
|
+
[complete fixed file content here]
|
|
470
|
+
|
|
471
|
+
--- FILE: path/to/another-file.tsx ---
|
|
472
|
+
[complete fixed file content here]
|
|
473
|
+
|
|
474
|
+
š CRITICAL RULES:
|
|
475
|
+
1. **Completeness:** Return the FULL file content. Do not truncate. Do not use placeholders like "// ... rest of code".
|
|
476
|
+
2. **Hallucinations:** Do NOT import packages that are not in package.json (unless they are built-in Node/React modules).
|
|
477
|
+
3. **Multi-File Fixes:** If multiple files need changes, include ALL of them with separate --- FILE: markers.
|
|
478
|
+
4. **NO MARKDOWN:** Do NOT use markdown code blocks (\`\`\`). Do NOT add explanations.
|
|
479
|
+
5. **NO TEST OUTPUT:** Do NOT include test results or any other text. ONLY file content with --- FILE: headers.
|
|
480
|
+
6. **File Markers Required:** Every file MUST start with "--- FILE: path ---" on its own line.
|
|
481
|
+
7. **Exact Text Matching:** If test expects "Total Tests", use EXACTLY "Total Tests" (case-sensitive, spacing matters).
|
|
482
|
+
8. **Preserve Working Code:** Only change what's necessary to fix the failing tests.
|
|
483
|
+
|
|
484
|
+
EXAMPLE CORRECT OUTPUT:
|
|
485
|
+
--- FILE: app/page.tsx ---
|
|
486
|
+
import React from 'react';
|
|
487
|
+
|
|
488
|
+
export default function Page() {
|
|
489
|
+
return (
|
|
490
|
+
<div>
|
|
491
|
+
<h1>Dashboard</h1>
|
|
492
|
+
<button>Run Tests</button>
|
|
493
|
+
</div>
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
--- FILE: components/stats.tsx ---
|
|
498
|
+
export function Stats() {
|
|
499
|
+
return <div>Total Tests: 10</div>;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
NOW FIX THE CODE TO MAKE ALL TESTS PASS:
|
|
503
|
+
`;
|
|
504
|
+
|
|
505
|
+
try {
|
|
506
|
+
const ai = createAIClient();
|
|
507
|
+
const response = await ai.models.generateContent({
|
|
508
|
+
model: model,
|
|
509
|
+
contents: prompt,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const rawText = response.text;
|
|
513
|
+
|
|
514
|
+
// Validate response format
|
|
515
|
+
if (!rawText.includes('--- FILE:')) {
|
|
516
|
+
spinner.warn(chalk.yellow('ā ļø AI response missing file markers. Response may be malformed.'));
|
|
517
|
+
console.log(chalk.dim('Response preview:'), rawText.substring(0, 200) + '...');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
spinner.succeed(chalk.green('⨠Test-driven fixes generated.'));
|
|
521
|
+
return rawText;
|
|
522
|
+
|
|
523
|
+
} catch (e) {
|
|
524
|
+
console.error(e);
|
|
525
|
+
spinner.fail(chalk.red('Test-driven fix generation failed!'));
|
|
526
|
+
throw e;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export { fixBuild, generateYaml, fix, fixTestFailures };
|
|
531
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// lib/utils.js
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
export function parseAiResponse(responseText) {
|
|
7
|
+
// 1. Split by the "--- FILE:" marker
|
|
8
|
+
// This regex looks for lines starting with "--- FILE:" and captures the filename
|
|
9
|
+
const fileRegex = /--- FILE: (.*?) ---\n([\s\S]*?)(?=(--- FILE:|$))/g;
|
|
10
|
+
|
|
11
|
+
const updates = [];
|
|
12
|
+
let match;
|
|
13
|
+
|
|
14
|
+
while ((match = fileRegex.exec(responseText)) !== null) {
|
|
15
|
+
updates.push({
|
|
16
|
+
filepath: match[1].trim(), // The captured filename
|
|
17
|
+
content: match[2].trim() // The captured code content
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Validation: Check if response looks malformed
|
|
22
|
+
if (updates.length === 0 && responseText.trim().length > 0) {
|
|
23
|
+
// Check if response contains test output or other non-code content
|
|
24
|
+
if (responseText.includes('PASSED:') || responseText.includes('FAILED:') ||
|
|
25
|
+
responseText.includes('Running...') || responseText.includes('Test Execution')) {
|
|
26
|
+
console.log(chalk.yellow('\nā ļø AI returned test output instead of code fixes.'));
|
|
27
|
+
console.log(chalk.dim('This usually means the AI misunderstood the task.'));
|
|
28
|
+
} else {
|
|
29
|
+
console.log(chalk.yellow('\nā ļø AI response missing "--- FILE: path ---" markers.'));
|
|
30
|
+
console.log(chalk.dim('Expected format: --- FILE: path/to/file.tsx ---'));
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return updates;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function applyFileUpdates(updates) {
|
|
39
|
+
console.log(chalk.blue(`\nš¦ Applying ${updates.length} file updates...`));
|
|
40
|
+
|
|
41
|
+
for (const update of updates) {
|
|
42
|
+
const fullPath = path.resolve(process.cwd(), update.filepath);
|
|
43
|
+
|
|
44
|
+
// Ensure directory exists (AI might create a new file in a new folder)
|
|
45
|
+
const dir = path.dirname(fullPath);
|
|
46
|
+
if (!fs.existsSync(dir)) {
|
|
47
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Write the file
|
|
51
|
+
fs.writeFileSync(fullPath, update.content);
|
|
52
|
+
console.log(chalk.green(` ā Updated: ${update.filepath}`));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
// Files/Folders to strictly ignore (Performance & Security)
|
|
5
|
+
const IGNORE_LIST = new Set([
|
|
6
|
+
'node_modules',
|
|
7
|
+
'.git',
|
|
8
|
+
'.next', // Next.js build folder (HUGE)
|
|
9
|
+
'.vscode',
|
|
10
|
+
'dist',
|
|
11
|
+
'build',
|
|
12
|
+
'coverage',
|
|
13
|
+
'public', // Images/Assets (Binary data crashes token counts)
|
|
14
|
+
'package-lock.json',
|
|
15
|
+
'yarn.lock',
|
|
16
|
+
'bun.lockb',
|
|
17
|
+
'vibe-snapshot.png',
|
|
18
|
+
'vibe.yaml'
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Recursive function to generate a visual file tree.
|
|
23
|
+
* Returns a string like:
|
|
24
|
+
* āāā app
|
|
25
|
+
* ā āāā page.tsx
|
|
26
|
+
* ā āāā layout.tsx
|
|
27
|
+
* āāā components
|
|
28
|
+
* āāā button.tsx
|
|
29
|
+
*/
|
|
30
|
+
export function getProjectStructure(dir, depth = 0) {
|
|
31
|
+
if (depth > 4) return ''; // Stop recursion if too deep (safety)
|
|
32
|
+
|
|
33
|
+
const files = fs.readdirSync(dir);
|
|
34
|
+
let tree = '';
|
|
35
|
+
|
|
36
|
+
files.forEach((file, index) => {
|
|
37
|
+
if (IGNORE_LIST.has(file)) return;
|
|
38
|
+
|
|
39
|
+
const fullPath = path.join(dir, file);
|
|
40
|
+
const isLast = index === files.length - 1;
|
|
41
|
+
const prefix = ' '.repeat(depth) + (isLast ? 'āāā ' : 'āāā ');
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const stats = fs.statSync(fullPath);
|
|
45
|
+
|
|
46
|
+
if (stats.isDirectory()) {
|
|
47
|
+
tree += `${prefix}${file}/\n`;
|
|
48
|
+
tree += getProjectStructure(fullPath, depth + 1); // Recurse
|
|
49
|
+
} else {
|
|
50
|
+
// Only show relevant code files
|
|
51
|
+
if (file.match(/\.(ts|tsx|js|jsx|css|json)$/)) {
|
|
52
|
+
tree += `${prefix}${file}\n`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
// Ignore permission errors etc.
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return tree;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Finds all source code files to send to Gemini as context.
|
|
65
|
+
* WARNING: Use this carefully to avoid hitting token limits.
|
|
66
|
+
*/
|
|
67
|
+
// lib/files.js
|
|
68
|
+
|
|
69
|
+
export function getCriticalSourceCode(dir) {
|
|
70
|
+
let context = "";
|
|
71
|
+
|
|
72
|
+
function scan(currentDir) {
|
|
73
|
+
const files = fs.readdirSync(currentDir);
|
|
74
|
+
|
|
75
|
+
for (const file of files) {
|
|
76
|
+
// ... ignore list check ...
|
|
77
|
+
|
|
78
|
+
const fullPath = path.join(currentDir, file);
|
|
79
|
+
const stats = fs.statSync(fullPath);
|
|
80
|
+
|
|
81
|
+
if (stats.isDirectory()) {
|
|
82
|
+
scan(fullPath);
|
|
83
|
+
} else {
|
|
84
|
+
// FILTER: Only read code files
|
|
85
|
+
if (file.match(/\.(tsx|ts|jsx|js|css)$/)) {
|
|
86
|
+
// SAFETY: Skip files larger than 100KB to prevent lag
|
|
87
|
+
if (stats.size > 100 * 1024) {
|
|
88
|
+
context += `\n--- FILE: ${file} (Skipped: Too Large) ---\n`;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
93
|
+
context += `\n--- FILE: ${fullPath} ---\n${content}\n`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
scan(dir);
|
|
100
|
+
if (context.length > 400000) {
|
|
101
|
+
console.warn("ā ļø Context too large! Truncating to prevent API Error...");
|
|
102
|
+
return context.substring(0, 400000) + "\n...[TRUNCATED]";
|
|
103
|
+
}
|
|
104
|
+
return context;
|
|
105
|
+
}
|