@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/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
+ }