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.
- package/.dockerignore +24 -0
- package/.idea/deepdebug-local-agent.iml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/Dockerfile +46 -0
- package/cloudbuild.yaml +42 -0
- package/index.js +42 -0
- package/mcp-server.js +533 -0
- package/package.json +22 -0
- package/src/ai-engine.js +861 -0
- package/src/analyzers/config-analyzer.js +446 -0
- package/src/analyzers/controller-analyzer.js +429 -0
- package/src/analyzers/dto-analyzer.js +455 -0
- package/src/detectors/build-tool-detector.js +0 -0
- package/src/detectors/framework-detector.js +91 -0
- package/src/detectors/language-detector.js +89 -0
- package/src/detectors/multi-project-detector.js +191 -0
- package/src/detectors/service-detector.js +244 -0
- package/src/detectors.js +30 -0
- package/src/exec-utils.js +215 -0
- package/src/fs-utils.js +34 -0
- package/src/git/base-git-provider.js +384 -0
- package/src/git/git-provider-registry.js +110 -0
- package/src/git/github-provider.js +502 -0
- package/src/mcp-http-server.js +313 -0
- package/src/patch/patch-engine.js +339 -0
- package/src/patch-manager.js +816 -0
- package/src/patch.js +607 -0
- package/src/patch_bkp.js +154 -0
- package/src/ports.js +69 -0
- package/src/routes/workspace.route.js +528 -0
- package/src/runtimes/base-runtime.js +290 -0
- package/src/runtimes/java/gradle-runtime.js +378 -0
- package/src/runtimes/java/java-integrations.js +339 -0
- package/src/runtimes/java/maven-runtime.js +418 -0
- package/src/runtimes/node/node-integrations.js +247 -0
- package/src/runtimes/node/npm-runtime.js +466 -0
- package/src/runtimes/node/yarn-runtime.js +354 -0
- package/src/runtimes/runtime-registry.js +256 -0
- package/src/server-local.js +576 -0
- package/src/server.js +4565 -0
- package/src/utils/environment-diagnostics.js +666 -0
- package/src/utils/exec-utils.js +264 -0
- package/src/utils/fs-utils.js +218 -0
- package/src/workspace/detect-port.js +176 -0
- package/src/workspace/file-reader.js +54 -0
- package/src/workspace/git-client.js +0 -0
- package/src/workspace/process-manager.js +619 -0
- package/src/workspace/scanner.js +72 -0
- package/src/workspace-manager.js +172 -0
package/src/ai-engine.js
ADDED
|
@@ -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;
|