deepdebug-local-agent 0.3.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.
Files changed (50) hide show
  1. package/.dockerignore +24 -0
  2. package/.idea/deepdebug-local-agent.iml +12 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/Dockerfile +46 -0
  6. package/cloudbuild.yaml +42 -0
  7. package/index.js +42 -0
  8. package/mcp-server.js +533 -0
  9. package/package.json +22 -0
  10. package/src/ai-engine.js +861 -0
  11. package/src/analyzers/config-analyzer.js +446 -0
  12. package/src/analyzers/controller-analyzer.js +429 -0
  13. package/src/analyzers/dto-analyzer.js +455 -0
  14. package/src/detectors/build-tool-detector.js +0 -0
  15. package/src/detectors/framework-detector.js +91 -0
  16. package/src/detectors/language-detector.js +89 -0
  17. package/src/detectors/multi-project-detector.js +191 -0
  18. package/src/detectors/service-detector.js +244 -0
  19. package/src/detectors.js +30 -0
  20. package/src/exec-utils.js +215 -0
  21. package/src/fs-utils.js +34 -0
  22. package/src/git/base-git-provider.js +384 -0
  23. package/src/git/git-provider-registry.js +110 -0
  24. package/src/git/github-provider.js +502 -0
  25. package/src/mcp-http-server.js +313 -0
  26. package/src/patch/patch-engine.js +339 -0
  27. package/src/patch-manager.js +816 -0
  28. package/src/patch.js +607 -0
  29. package/src/patch_bkp.js +154 -0
  30. package/src/ports.js +69 -0
  31. package/src/routes/workspace.route.js +528 -0
  32. package/src/runtimes/base-runtime.js +290 -0
  33. package/src/runtimes/java/gradle-runtime.js +378 -0
  34. package/src/runtimes/java/java-integrations.js +339 -0
  35. package/src/runtimes/java/maven-runtime.js +418 -0
  36. package/src/runtimes/node/node-integrations.js +247 -0
  37. package/src/runtimes/node/npm-runtime.js +466 -0
  38. package/src/runtimes/node/yarn-runtime.js +354 -0
  39. package/src/runtimes/runtime-registry.js +256 -0
  40. package/src/server-local.js +576 -0
  41. package/src/server.js +4565 -0
  42. package/src/utils/environment-diagnostics.js +666 -0
  43. package/src/utils/exec-utils.js +264 -0
  44. package/src/utils/fs-utils.js +218 -0
  45. package/src/workspace/detect-port.js +176 -0
  46. package/src/workspace/file-reader.js +54 -0
  47. package/src/workspace/git-client.js +0 -0
  48. package/src/workspace/process-manager.js +619 -0
  49. package/src/workspace/scanner.js +72 -0
  50. package/src/workspace-manager.js +172 -0
@@ -0,0 +1,861 @@
1
+ // ============================================
2
+ // 🧠 AI VIBE CODING ENGINE
3
+ // Ficheiro: ai-engine.js
4
+ // Coloca em: ~/deepdebug-local-agent/src/ai-engine.js
5
+ // ============================================
6
+
7
+ import { EventEmitter } from "events";
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { exec } from "child_process";
11
+ import { promisify } from "util";
12
+
13
+ const execAsync = promisify(exec);
14
+
15
+ /**
16
+ * AIVibeCodingEngine
17
+ *
18
+ * Sistema universal de auto-healing que usa AI para resolver
19
+ * QUALQUER erro automaticamente - não apenas startup.
20
+ *
21
+ * Features:
22
+ * - Monitorização de erros em tempo real
23
+ * - Classificação automática de erros
24
+ * - Quick fixes locais (sem chamar AI)
25
+ * - Escala para AI quando quick fixes falham
26
+ * - Histórico de fixes para reutilização
27
+ */
28
+ export class AIVibeCodingEngine extends EventEmitter {
29
+ constructor(processManager, getWorkspaceRoot) {
30
+ super();
31
+ this.processManager = processManager;
32
+ this.getWorkspaceRoot = getWorkspaceRoot;
33
+ this.gatewayUrl = process.env.GATEWAY_URL || 'http://localhost:8085';
34
+ this.maxRetries = 3;
35
+ this.isActive = true;
36
+
37
+ // Histórico
38
+ this.errorHistory = [];
39
+ this.fixHistory = [];
40
+ this.pendingFixes = [];
41
+
42
+ // Estado
43
+ this.currentSession = null;
44
+ this.lastSuccessfulConfig = null;
45
+
46
+ console.log('🧠 [AI-Engine] Vibe Coding Engine initialized');
47
+ console.log('🧠 [AI-Engine] Gateway URL:', this.gatewayUrl);
48
+
49
+ this.setupErrorMonitoring();
50
+ }
51
+
52
+ // ============================================
53
+ // ERROR MONITORING
54
+ // ============================================
55
+
56
+ setupErrorMonitoring() {
57
+ // Monitorar logs do ProcessManager
58
+ this.processManager.on('log', async ({ serviceId, message, type }) => {
59
+ if (this.isActive && message && this.isError(message)) {
60
+ await this.handleRuntimeError(serviceId, message, type);
61
+ }
62
+ });
63
+
64
+ // Monitorar crashes
65
+ this.processManager.on('stopped', async ({ serviceId, code, signal }) => {
66
+ if (this.isActive && code !== 0 && code !== null) {
67
+ console.log(`🧠 [AI-Engine] Process ${serviceId} crashed (code: ${code})`);
68
+ await this.handleCrash(serviceId, code, signal);
69
+ }
70
+ });
71
+
72
+ // Monitorar erros de processo
73
+ this.processManager.on('error', async ({ serviceId, error }) => {
74
+ if (this.isActive) {
75
+ console.log(`🧠 [AI-Engine] Process error: ${error}`);
76
+ await this.handleProcessError(serviceId, error);
77
+ }
78
+ });
79
+ }
80
+
81
+ // ============================================
82
+ // ERROR DETECTION & CLASSIFICATION
83
+ // ============================================
84
+
85
+ isError(message) {
86
+ if (!message || typeof message !== 'string') return false;
87
+
88
+ const errorPatterns = [
89
+ // Java/Spring
90
+ /exception/i,
91
+ /error.*failed/i,
92
+ /failed.*to.*start/i,
93
+ /application.*run.*failed/i,
94
+ /bean.*creation.*exception/i,
95
+ /no.*qualifying.*bean/i,
96
+ /could.*not.*autowire/i,
97
+ /circular.*dependency/i,
98
+
99
+ // Database
100
+ /connection.*refused/i,
101
+ /unable.*to.*connect/i,
102
+ /access.*denied.*for.*user/i,
103
+ /unknown.*database/i,
104
+ /table.*doesn.*exist/i,
105
+ /sql.*syntax.*error/i,
106
+ /jdbc.*exception/i,
107
+ /hikari.*pool/i,
108
+
109
+ // Network/Port
110
+ /port.*in.*use/i,
111
+ /address.*already.*in.*use/i,
112
+ /bind.*failed/i,
113
+ /connection.*timed.*out/i,
114
+
115
+ // Compilation/Build
116
+ /compilation.*failed/i,
117
+ /syntax.*error/i,
118
+ /cannot.*resolve/i,
119
+ /cannot.*find.*symbol/i,
120
+
121
+ // Runtime
122
+ /null.*pointer/i,
123
+ /class.*not.*found/i,
124
+ /no.*such.*method/i,
125
+ /illegal.*argument/i,
126
+ /stack.*overflow/i,
127
+ /out.*of.*memory/i,
128
+
129
+ // File/Permission
130
+ /no.*such.*file/i,
131
+ /permission.*denied/i,
132
+
133
+ // Node.js
134
+ /module.*not.*found/i,
135
+ /cannot.*find.*module/i,
136
+ /enoent/i,
137
+ /eaddrinuse/i
138
+ ];
139
+
140
+ return errorPatterns.some(p => p.test(message));
141
+ }
142
+
143
+ classifyError(message) {
144
+ if (!message) return { type: 'unknown', severity: 'low', autoFixable: false };
145
+
146
+ const classifications = {
147
+ 'database_connection': {
148
+ patterns: [/connection.*refused.*\d{4}/i, /jdbc/i, /hikari/i, /datasource/i, /sql.*server/i, /mysql/i, /postgres/i],
149
+ severity: 'high',
150
+ autoFixable: true
151
+ },
152
+ 'database_schema': {
153
+ patterns: [/table.*doesn.*exist/i, /unknown.*column/i, /sql.*syntax/i],
154
+ severity: 'medium',
155
+ autoFixable: false
156
+ },
157
+ 'port_conflict': {
158
+ patterns: [/port.*in.*use/i, /address.*already/i, /eaddrinuse/i, /bind.*failed/i],
159
+ severity: 'medium',
160
+ autoFixable: true
161
+ },
162
+ 'dependency_missing': {
163
+ patterns: [/class.*not.*found/i, /no.*such.*method/i, /module.*not.*found/i],
164
+ severity: 'high',
165
+ autoFixable: true
166
+ },
167
+ 'compilation': {
168
+ patterns: [/compilation.*failed/i, /syntax.*error/i, /cannot.*resolve/i, /cannot.*find.*symbol/i],
169
+ severity: 'high',
170
+ autoFixable: true
171
+ },
172
+ 'configuration': {
173
+ patterns: [/no.*qualifying.*bean/i, /could.*not.*autowire/i, /property.*not.*found/i],
174
+ severity: 'medium',
175
+ autoFixable: true
176
+ },
177
+ 'permission': {
178
+ patterns: [/permission.*denied/i, /access.*denied/i, /eacces/i],
179
+ severity: 'high',
180
+ autoFixable: false
181
+ },
182
+ 'memory': {
183
+ patterns: [/out.*of.*memory/i, /heap.*space/i, /gc.*overhead/i],
184
+ severity: 'critical',
185
+ autoFixable: false
186
+ },
187
+ 'null_pointer': {
188
+ patterns: [/null.*pointer/i, /cannot.*invoke.*null/i],
189
+ severity: 'medium',
190
+ autoFixable: true
191
+ },
192
+ 'circular_dependency': {
193
+ patterns: [/circular.*dependency/i, /circular.*reference/i],
194
+ severity: 'high',
195
+ autoFixable: true
196
+ }
197
+ };
198
+
199
+ for (const [type, config] of Object.entries(classifications)) {
200
+ if (config.patterns.some(p => p.test(message))) {
201
+ return { type, ...config };
202
+ }
203
+ }
204
+
205
+ return { type: 'unknown', severity: 'low', autoFixable: false };
206
+ }
207
+
208
+ // ============================================
209
+ // ERROR HANDLERS
210
+ // ============================================
211
+
212
+ async handleRuntimeError(serviceId, errorMessage, type) {
213
+ const classification = this.classifyError(errorMessage);
214
+
215
+ // Debounce: evitar duplicatas em 5 segundos
216
+ const recentSimilar = this.errorHistory.find(e =>
217
+ e.classification.type === classification.type &&
218
+ Date.now() - e.timestamp < 5000
219
+ );
220
+
221
+ if (recentSimilar) return;
222
+
223
+ const errorEntry = {
224
+ id: `err_${Date.now()}`,
225
+ serviceId,
226
+ message: errorMessage,
227
+ classification,
228
+ timestamp: Date.now(),
229
+ handled: false
230
+ };
231
+
232
+ this.errorHistory.push(errorEntry);
233
+ this.trimHistory();
234
+
235
+ console.log(`🧠 [AI-Engine] Error detected: ${classification.type} (${classification.severity})`);
236
+ console.log(` Message: ${errorMessage.substring(0, 150)}...`);
237
+
238
+ // Emitir evento
239
+ this.emit('error_detected', errorEntry);
240
+
241
+ // Se auto-fixável, preparar fix
242
+ if (classification.autoFixable) {
243
+ const quickFix = this.getQuickFix(classification.type, errorMessage);
244
+ if (quickFix) {
245
+ console.log(`💡 [AI-Engine] Quick fix available: ${quickFix.description}`);
246
+ this.pendingFixes.push({
247
+ errorId: errorEntry.id,
248
+ fix: quickFix,
249
+ timestamp: Date.now()
250
+ });
251
+
252
+ this.emit('fix_available', { error: errorEntry, fix: quickFix });
253
+ }
254
+ }
255
+ }
256
+
257
+ async handleCrash(serviceId, exitCode, signal) {
258
+ console.log(`🧠 [AI-Engine] Handling crash for ${serviceId}...`);
259
+
260
+ const recentErrors = this.errorHistory
261
+ .filter(e => e.serviceId === serviceId && Date.now() - e.timestamp < 60000)
262
+ .slice(-10);
263
+
264
+ this.emit('crash_detected', { serviceId, exitCode, signal, recentErrors });
265
+
266
+ if (this.pendingFixes.length > 0) {
267
+ console.log(`💡 [AI-Engine] ${this.pendingFixes.length} fixes pending for next start`);
268
+ }
269
+ }
270
+
271
+ async handleProcessError(serviceId, error) {
272
+ const classification = this.classifyError(error);
273
+
274
+ this.errorHistory.push({
275
+ id: `proc_err_${Date.now()}`,
276
+ serviceId,
277
+ message: error,
278
+ classification,
279
+ timestamp: Date.now(),
280
+ source: 'process'
281
+ });
282
+ }
283
+
284
+ // ============================================
285
+ // QUICK FIXES
286
+ // ============================================
287
+
288
+ getQuickFix(errorType, message) {
289
+ const fixes = {
290
+ 'database_connection': () => ({
291
+ description: 'Database unavailable - switch to test/local profile',
292
+ action: 'change_profile',
293
+ profiles: ['test', 'local', 'h2', 'memory'],
294
+ priority: 1
295
+ }),
296
+ 'port_conflict': () => {
297
+ const portMatch = message.match(/port[:\s]*(\d+)/i);
298
+ const currentPort = portMatch ? parseInt(portMatch[1]) : 8080;
299
+ return {
300
+ description: `Port ${currentPort} in use - try ${currentPort + 1}`,
301
+ action: 'change_port',
302
+ currentPort,
303
+ newPort: currentPort + 1,
304
+ priority: 1
305
+ };
306
+ },
307
+ 'dependency_missing': () => ({
308
+ description: 'Missing dependency - clean and reinstall',
309
+ action: 'reinstall_dependencies',
310
+ priority: 2
311
+ }),
312
+ 'compilation': () => ({
313
+ description: 'Compilation error - analyze with AI',
314
+ action: 'analyze_with_ai',
315
+ priority: 3
316
+ }),
317
+ 'configuration': () => ({
318
+ description: 'Configuration error - use default config',
319
+ action: 'use_default_config',
320
+ priority: 2
321
+ }),
322
+ 'null_pointer': () => ({
323
+ description: 'Null pointer - analyze stacktrace with AI',
324
+ action: 'analyze_with_ai',
325
+ priority: 3
326
+ }),
327
+ 'circular_dependency': () => ({
328
+ description: 'Circular dependency - analyze with AI',
329
+ action: 'analyze_with_ai',
330
+ priority: 3
331
+ })
332
+ };
333
+
334
+ const fixFn = fixes[errorType];
335
+ return fixFn ? fixFn() : null;
336
+ }
337
+
338
+ // ============================================
339
+ // AUTO-HEALING START
340
+ // ============================================
341
+
342
+ async startWithAutoHealing(config) {
343
+ console.log('🧠 [AI-Engine] Starting with auto-healing...');
344
+ console.log(`🧠 [AI-Engine] Config: ${config.command} ${(config.args || []).join(' ')}`);
345
+
346
+ this.currentSession = {
347
+ startTime: Date.now(),
348
+ attempts: [],
349
+ originalConfig: { ...config }
350
+ };
351
+
352
+ let currentConfig = { ...config };
353
+ let attempts = 0;
354
+ let lastError = null;
355
+
356
+ while (attempts < this.maxRetries) {
357
+ attempts++;
358
+ console.log(`\n🔄 [AI-Engine] Attempt ${attempts}/${this.maxRetries}`);
359
+
360
+ // Aplicar fixes pendentes
361
+ if (attempts > 1 && this.pendingFixes.length > 0) {
362
+ const fix = this.pendingFixes.shift();
363
+ console.log(`💡 [AI-Engine] Applying fix: ${fix.fix.description}`);
364
+ currentConfig = await this.applyFix(currentConfig, fix.fix);
365
+ }
366
+
367
+ // Recompilar se necessário
368
+ if (currentConfig.recompile) {
369
+ console.log('🔨 [AI-Engine] Recompiling...');
370
+ const compileResult = await this.recompile();
371
+ if (!compileResult.success) {
372
+ lastError = compileResult.error;
373
+ const aiAnalysis = await this.analyzeWithAI('compilation', compileResult.error, currentConfig);
374
+ if (aiAnalysis?.fix) {
375
+ this.pendingFixes.push({ fix: aiAnalysis.fix });
376
+ }
377
+ continue;
378
+ }
379
+ delete currentConfig.recompile;
380
+ }
381
+
382
+ // Tentar iniciar
383
+ const result = await this.attemptStart(currentConfig);
384
+
385
+ this.currentSession.attempts.push({
386
+ attempt: attempts,
387
+ config: { ...currentConfig },
388
+ result: result.success ? 'success' : 'failed',
389
+ error: result.error,
390
+ timestamp: Date.now()
391
+ });
392
+
393
+ if (result.success) {
394
+ console.log(`\n✅ [AI-Engine] Success after ${attempts} attempt(s)`);
395
+
396
+ this.lastSuccessfulConfig = { ...currentConfig };
397
+
398
+ if (attempts > 1) {
399
+ this.fixHistory.push({
400
+ originalConfig: this.currentSession.originalConfig,
401
+ fixedConfig: currentConfig,
402
+ attempts,
403
+ timestamp: Date.now()
404
+ });
405
+ }
406
+
407
+ this.emit('start_success', { attempts, config: currentConfig, autoHealed: attempts > 1 });
408
+
409
+ return {
410
+ ok: true,
411
+ attempts,
412
+ config: currentConfig,
413
+ autoHealed: attempts > 1,
414
+ session: this.currentSession
415
+ };
416
+ }
417
+
418
+ // Falhou
419
+ lastError = result.error;
420
+ console.log(`❌ [AI-Engine] Attempt ${attempts} failed: ${lastError?.substring(0, 100)}...`);
421
+
422
+ // Obter fix
423
+ const fix = await this.getFix(result.error, result.logs, currentConfig);
424
+
425
+ if (fix) {
426
+ console.log(`💡 [AI-Engine] Fix: ${fix.description}`);
427
+ currentConfig = await this.applyFix(currentConfig, fix);
428
+ } else {
429
+ // Tentar AI
430
+ console.log(`🤖 [AI-Engine] Requesting AI analysis...`);
431
+ const aiAnalysis = await this.analyzeWithAI('startup', lastError, currentConfig);
432
+ if (aiAnalysis?.newConfig) {
433
+ console.log(`🤖 [AI-Engine] AI suggested: ${aiAnalysis.suggestion}`);
434
+ currentConfig = aiAnalysis.newConfig;
435
+ } else {
436
+ console.log(`⚠️ [AI-Engine] No fix available`);
437
+ break;
438
+ }
439
+ }
440
+ }
441
+
442
+ console.log(`\n❌ [AI-Engine] Failed after ${attempts} attempts`);
443
+
444
+ this.emit('start_failed', { attempts, lastError, session: this.currentSession });
445
+
446
+ return {
447
+ ok: false,
448
+ attempts,
449
+ error: lastError,
450
+ config: currentConfig,
451
+ session: this.currentSession
452
+ };
453
+ }
454
+
455
+ async attemptStart(config) {
456
+ return new Promise(async (resolve) => {
457
+ const logs = [];
458
+ let startupError = null;
459
+ let resolved = false;
460
+
461
+ const cleanup = () => {
462
+ this.processManager.off('log', logListener);
463
+ this.processManager.off('started', startedListener);
464
+ this.processManager.off('stopped', stoppedListener);
465
+ clearTimeout(timeout);
466
+ };
467
+
468
+ const logListener = ({ serviceId, message }) => {
469
+ if (serviceId === 'test-local' && message) {
470
+ logs.push(message);
471
+ if (this.isError(message) && !startupError) {
472
+ startupError = message;
473
+ }
474
+ }
475
+ };
476
+
477
+ const startedListener = ({ serviceId }) => {
478
+ if (serviceId === 'test-local' && !resolved) {
479
+ resolved = true;
480
+ cleanup();
481
+ resolve({ success: true, logs });
482
+ }
483
+ };
484
+
485
+ const stoppedListener = ({ serviceId, code }) => {
486
+ if (serviceId === 'test-local' && code !== 0 && !resolved) {
487
+ resolved = true;
488
+ cleanup();
489
+ resolve({
490
+ success: false,
491
+ error: startupError || `Process exited with code ${code}`,
492
+ logs,
493
+ exitCode: code
494
+ });
495
+ }
496
+ };
497
+
498
+ const timeout = setTimeout(() => {
499
+ if (!resolved) {
500
+ resolved = true;
501
+ cleanup();
502
+ if (!startupError) {
503
+ resolve({ success: true, logs, partial: true });
504
+ } else {
505
+ resolve({ success: false, error: startupError, logs, timeout: true });
506
+ }
507
+ }
508
+ }, 90000);
509
+
510
+ this.processManager.on('log', logListener);
511
+ this.processManager.on('started', startedListener);
512
+ this.processManager.on('stopped', stoppedListener);
513
+
514
+ try {
515
+ await this.processManager.start('test-local', config);
516
+ } catch (err) {
517
+ if (!resolved) {
518
+ resolved = true;
519
+ cleanup();
520
+ resolve({ success: false, error: err.message, logs });
521
+ }
522
+ }
523
+ });
524
+ }
525
+
526
+ // ============================================
527
+ // FIX RESOLUTION
528
+ // ============================================
529
+
530
+ async getFix(error, logs, currentConfig) {
531
+ const classification = this.classifyError(error);
532
+
533
+ // Quick fix local
534
+ const quickFix = this.getQuickFix(classification.type, error);
535
+ if (quickFix && quickFix.action !== 'analyze_with_ai') {
536
+ return quickFix;
537
+ }
538
+
539
+ // Verificar histórico
540
+ const similarFix = this.findSimilarFix(classification.type);
541
+ if (similarFix) {
542
+ console.log(`📚 [AI-Engine] Found similar fix in history`);
543
+ return {
544
+ description: 'Applying previously successful fix',
545
+ action: 'apply_historical',
546
+ config: similarFix.fixedConfig
547
+ };
548
+ }
549
+
550
+ return quickFix;
551
+ }
552
+
553
+ findSimilarFix(errorType) {
554
+ return this.fixHistory.find(f => {
555
+ const originalErrors = this.errorHistory.filter(e =>
556
+ e.timestamp >= f.timestamp - 60000 &&
557
+ e.timestamp <= f.timestamp
558
+ );
559
+ return originalErrors.some(e => e.classification?.type === errorType);
560
+ });
561
+ }
562
+
563
+ async applyFix(config, fix) {
564
+ const newConfig = { ...config };
565
+
566
+ switch (fix.action) {
567
+ case 'change_profile':
568
+ const profiles = fix.profiles || ['test', 'local', 'h2'];
569
+ const currentProfile = config.profile;
570
+ const nextProfile = profiles.find(p => p !== currentProfile) || profiles[0];
571
+
572
+ newConfig.profile = nextProfile;
573
+ newConfig.args = this.updateArgs(config.args, 'profile', nextProfile);
574
+ newConfig.env = {
575
+ ...config.env,
576
+ SPRING_PROFILES_ACTIVE: nextProfile
577
+ };
578
+ console.log(` → Profile: ${currentProfile} → ${nextProfile}`);
579
+ break;
580
+
581
+ case 'change_port':
582
+ const newPort = fix.newPort || (config.port || 8080) + 1;
583
+ newConfig.port = newPort;
584
+ newConfig.args = this.updateArgs(config.args, 'port', newPort);
585
+ newConfig.env = {
586
+ ...config.env,
587
+ SERVER_PORT: String(newPort),
588
+ PORT: String(newPort)
589
+ };
590
+ console.log(` → Port: ${config.port} → ${newPort}`);
591
+ break;
592
+
593
+ case 'reinstall_dependencies':
594
+ newConfig.recompile = true;
595
+ newConfig.cleanInstall = true;
596
+ console.log(` → Will reinstall dependencies`);
597
+ break;
598
+
599
+ case 'use_default_config':
600
+ newConfig.profile = null;
601
+ newConfig.args = this.removeArg(config.args, 'profile');
602
+ delete newConfig.env?.SPRING_PROFILES_ACTIVE;
603
+ console.log(` → Using default config`);
604
+ break;
605
+
606
+ case 'apply_historical':
607
+ if (fix.config) {
608
+ Object.assign(newConfig, fix.config);
609
+ console.log(` → Applied historical fix`);
610
+ }
611
+ break;
612
+
613
+ case 'analyze_with_ai':
614
+ newConfig.needsAIAnalysis = true;
615
+ break;
616
+ }
617
+
618
+ return newConfig;
619
+ }
620
+
621
+ // ============================================
622
+ // AI ANALYSIS
623
+ // ============================================
624
+
625
+ async analyzeWithAI(errorType, error, currentConfig) {
626
+ console.log(`🤖 [AI-Engine] Requesting AI analysis for ${errorType}...`);
627
+
628
+ try {
629
+ const workspaceRoot = this.getWorkspaceRoot();
630
+ const configFiles = this.collectConfigFiles(workspaceRoot);
631
+ const recentLogs = this.errorHistory.slice(-20).map(e => e.message).join('\n');
632
+
633
+ const response = await fetch(`${this.gatewayUrl}/api/test-local/analyze-startup-error`, {
634
+ method: 'POST',
635
+ headers: {
636
+ 'Content-Type': 'application/json',
637
+ 'X-Tenant-ID': 'default'
638
+ },
639
+ body: JSON.stringify({
640
+ context: {
641
+ workspace: workspaceRoot,
642
+ error,
643
+ logs: recentLogs,
644
+ configFiles,
645
+ errorType
646
+ },
647
+ currentConfig,
648
+ attempt: this.currentSession?.attempts?.length || 0,
649
+ errorHistory: this.errorHistory.slice(-10)
650
+ }),
651
+ signal: AbortSignal.timeout(30000)
652
+ });
653
+
654
+ if (response.ok) {
655
+ const analysis = await response.json();
656
+ console.log(`🤖 [AI-Engine] AI response received`);
657
+ return analysis;
658
+ } else {
659
+ console.log(`⚠️ [AI-Engine] Gateway returned ${response.status}`);
660
+ }
661
+ } catch (err) {
662
+ console.log(`⚠️ [AI-Engine] AI unavailable: ${err.message}`);
663
+ }
664
+
665
+ // Fallback local
666
+ return this.localAIFallback(errorType, error, currentConfig);
667
+ }
668
+
669
+ localAIFallback(errorType, error, currentConfig) {
670
+ console.log(`🔧 [AI-Engine] Using local fallback`);
671
+
672
+ if (errorType === 'startup' || errorType === 'database_connection') {
673
+ const triedProfiles = this.currentSession?.attempts
674
+ ?.map(a => a.config?.profile)
675
+ ?.filter(Boolean) || [];
676
+
677
+ const allProfiles = ['test', 'local', 'h2', 'dev', 'default'];
678
+ const nextProfile = allProfiles.find(p => !triedProfiles.includes(p));
679
+
680
+ if (nextProfile) {
681
+ return {
682
+ suggestion: `Trying ${nextProfile} profile`,
683
+ newConfig: {
684
+ ...currentConfig,
685
+ profile: nextProfile,
686
+ args: this.updateArgs(currentConfig.args, 'profile', nextProfile),
687
+ env: {
688
+ ...currentConfig.env,
689
+ SPRING_PROFILES_ACTIVE: nextProfile
690
+ }
691
+ }
692
+ };
693
+ }
694
+ }
695
+
696
+ return null;
697
+ }
698
+
699
+ // ============================================
700
+ // COMPILATION
701
+ // ============================================
702
+
703
+ async recompile() {
704
+ const workspaceRoot = this.getWorkspaceRoot();
705
+ if (!workspaceRoot) {
706
+ return { success: false, error: 'No workspace set' };
707
+ }
708
+
709
+ try {
710
+ // Importar detectProject dinamicamente para evitar dependência circular
711
+ const { detectProject } = await import('./detectors.js');
712
+ const meta = await detectProject(workspaceRoot);
713
+
714
+ let cmd;
715
+ if (meta.buildTool === 'maven') {
716
+ cmd = 'mvn clean install -DskipTests';
717
+ } else if (meta.buildTool === 'gradle') {
718
+ cmd = './gradlew clean build -x test';
719
+ } else if (meta.buildTool === 'npm' || meta.buildTool === 'yarn') {
720
+ cmd = `${meta.buildTool} install`;
721
+ } else {
722
+ return { success: true };
723
+ }
724
+
725
+ console.log(`🔨 [AI-Engine] Running: ${cmd}`);
726
+
727
+ const { stdout, stderr } = await execAsync(cmd, {
728
+ cwd: workspaceRoot,
729
+ timeout: 300000
730
+ });
731
+
732
+ return { success: true, stdout, stderr };
733
+ } catch (err) {
734
+ return { success: false, error: err.message, stderr: err.stderr };
735
+ }
736
+ }
737
+
738
+ // ============================================
739
+ // CONFIG FILES COLLECTION
740
+ // ============================================
741
+
742
+ collectConfigFiles(workspaceRoot) {
743
+ if (!workspaceRoot) return {};
744
+
745
+ const configs = {};
746
+ const paths = [
747
+ 'src/main/resources/application.yml',
748
+ 'src/main/resources/application.yaml',
749
+ 'src/main/resources/application.properties',
750
+ 'src/main/resources/application-dev.yml',
751
+ 'src/main/resources/application-local.yml',
752
+ 'src/main/resources/application-test.yml',
753
+ 'src/main/resources/application-h2.yml',
754
+ 'pom.xml',
755
+ 'build.gradle',
756
+ 'package.json',
757
+ '.env'
758
+ ];
759
+
760
+ const fs = require('fs');
761
+ const path = require('path');
762
+
763
+ for (const p of paths) {
764
+ const fullPath = path.join(workspaceRoot, p);
765
+ if (fs.existsSync(fullPath)) {
766
+ try {
767
+ const content = fs.readFileSync(fullPath, 'utf8');
768
+ configs[p] = content.substring(0, 5000);
769
+ } catch (e) {}
770
+ }
771
+ }
772
+
773
+ return configs;
774
+ }
775
+
776
+ // ============================================
777
+ // HELPER METHODS
778
+ // ============================================
779
+
780
+ updateArgs(args, type, value) {
781
+ if (!args) args = [];
782
+ let newArgs = [...args];
783
+
784
+ if (type === 'profile') {
785
+ newArgs = newArgs.filter(a =>
786
+ !a.includes('spring.profiles.active') &&
787
+ !a.includes('spring-boot.run.profiles')
788
+ );
789
+ const jarIndex = newArgs.findIndex(a => a === '-jar');
790
+ if (jarIndex >= 0) {
791
+ newArgs.splice(jarIndex, 0, `-Dspring.profiles.active=${value}`);
792
+ } else {
793
+ newArgs.unshift(`-Dspring.profiles.active=${value}`);
794
+ }
795
+ }
796
+
797
+ if (type === 'port') {
798
+ newArgs = newArgs.filter(a => !a.includes('server.port'));
799
+ newArgs.push(`--server.port=${value}`);
800
+ }
801
+
802
+ return newArgs;
803
+ }
804
+
805
+ removeArg(args, type) {
806
+ if (!args) return [];
807
+
808
+ if (type === 'profile') {
809
+ return args.filter(a =>
810
+ !a.includes('spring.profiles.active') &&
811
+ !a.includes('spring-boot.run.profiles')
812
+ );
813
+ }
814
+
815
+ return args;
816
+ }
817
+
818
+ trimHistory() {
819
+ if (this.errorHistory.length > 200) {
820
+ this.errorHistory = this.errorHistory.slice(-200);
821
+ }
822
+ if (this.fixHistory.length > 50) {
823
+ this.fixHistory = this.fixHistory.slice(-50);
824
+ }
825
+ }
826
+
827
+ // ============================================
828
+ // PUBLIC API
829
+ // ============================================
830
+
831
+ getStatus() {
832
+ return {
833
+ active: this.isActive,
834
+ gatewayUrl: this.gatewayUrl,
835
+ maxRetries: this.maxRetries,
836
+ currentSession: this.currentSession,
837
+ stats: {
838
+ totalErrors: this.errorHistory.length,
839
+ totalFixes: this.fixHistory.length,
840
+ pendingFixes: this.pendingFixes.length
841
+ },
842
+ recentErrors: this.errorHistory.slice(-5),
843
+ recentFixes: this.fixHistory.slice(-3),
844
+ lastSuccessfulConfig: this.lastSuccessfulConfig
845
+ };
846
+ }
847
+
848
+ setActive(active) {
849
+ this.isActive = active;
850
+ console.log(`🧠 [AI-Engine] Auto-healing ${active ? 'enabled' : 'disabled'}`);
851
+ }
852
+
853
+ clearHistory() {
854
+ this.errorHistory = [];
855
+ this.fixHistory = [];
856
+ this.pendingFixes = [];
857
+ console.log('🧠 [AI-Engine] History cleared');
858
+ }
859
+ }
860
+
861
+ export default AIVibeCodingEngine;