deepdebug-local-agent 1.0.13 → 1.0.15
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/package.json +1 -1
- package/src/server.js +314 -314
package/src/server.js
CHANGED
|
@@ -28,7 +28,7 @@ import { startMCPHttpServer } from "./mcp-http-server.js";
|
|
|
28
28
|
const execAsync = promisify(exec);
|
|
29
29
|
|
|
30
30
|
// ============================================
|
|
31
|
-
//
|
|
31
|
+
// AI VIBE CODING ENGINE
|
|
32
32
|
// Sistema universal de auto-healing que usa AI
|
|
33
33
|
// para resolver QUALQUER erro automaticamente
|
|
34
34
|
// ============================================
|
|
@@ -48,7 +48,7 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
48
48
|
this.currentSession = null;
|
|
49
49
|
this.lastSuccessfulConfig = null;
|
|
50
50
|
|
|
51
|
-
console.log('
|
|
51
|
+
console.log(' [AI-Engine] Vibe Coding Engine initialized');
|
|
52
52
|
this.setupErrorMonitoring();
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -61,9 +61,9 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
61
61
|
|
|
62
62
|
this.processManager.on('stopped', async ({ serviceId, code, signal }) => {
|
|
63
63
|
if (this.isActive && code !== 0 && code !== null) {
|
|
64
|
-
console.log(
|
|
64
|
+
console.log(` [AI-Engine] Process ${serviceId} crashed (code: ${code})`);
|
|
65
65
|
if (this.pendingFixes.length > 0) {
|
|
66
|
-
console.log(
|
|
66
|
+
console.log(` [AI-Engine] ${this.pendingFixes.length} fixes pending`);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
});
|
|
@@ -89,22 +89,22 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
89
89
|
/cannot.*find.*module/i,
|
|
90
90
|
/application.*run.*failed/i,
|
|
91
91
|
/bean.*creation.*exception/i,
|
|
92
|
-
/hikaripool.*exception/i, //
|
|
92
|
+
/hikaripool.*exception/i, // S excees, no "HikariPool-1 - Starting"
|
|
93
93
|
/jdbc.*exception/i,
|
|
94
94
|
/datasource.*failed/i
|
|
95
95
|
];
|
|
96
96
|
|
|
97
|
-
// Excluir falsos positivos (linhas normais que
|
|
97
|
+
// Excluir falsos positivos (linhas normais que contm palavras de erro)
|
|
98
98
|
const falsePositives = [
|
|
99
|
-
/hikaripool.*start/i, // "HikariPool-1 - Starting..."
|
|
100
|
-
/hikaripool.*completed/i, // "HikariPool-1 - Start completed"
|
|
101
|
-
/no active profile/i, // "No active profile set"
|
|
99
|
+
/hikaripool.*start/i, // "HikariPool-1 - Starting..." normal
|
|
100
|
+
/hikaripool.*completed/i, // "HikariPool-1 - Start completed" normal
|
|
101
|
+
/no active profile/i, // "No active profile set" normal
|
|
102
102
|
/exposing.*endpoint/i, // Linha normal de startup
|
|
103
|
-
/started.*application/i, // "Started PurePilatesCoreApplication"
|
|
104
|
-
/tomcat started/i // "Tomcat started on port"
|
|
103
|
+
/started.*application/i, // "Started PurePilatesCoreApplication" sucesso!
|
|
104
|
+
/tomcat started/i // "Tomcat started on port" sucesso!
|
|
105
105
|
];
|
|
106
106
|
|
|
107
|
-
// Se match com falso positivo,
|
|
107
|
+
// Se match com falso positivo, no erro
|
|
108
108
|
if (falsePositives.some(p => p.test(message))) {
|
|
109
109
|
return false;
|
|
110
110
|
}
|
|
@@ -164,12 +164,12 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
164
164
|
this.errorHistory.push(entry);
|
|
165
165
|
if (this.errorHistory.length > 200) this.errorHistory = this.errorHistory.slice(-200);
|
|
166
166
|
|
|
167
|
-
console.log(
|
|
167
|
+
console.log(` [AI-Engine] Error: ${classification.type} (${classification.severity})`);
|
|
168
168
|
|
|
169
169
|
if (classification.autoFixable) {
|
|
170
170
|
const fix = this.getQuickFix(classification.type, errorMessage);
|
|
171
171
|
if (fix) {
|
|
172
|
-
console.log(
|
|
172
|
+
console.log(` [AI-Engine] Quick fix: ${fix.description}`);
|
|
173
173
|
this.pendingFixes.push({ errorId: entry.id, fix, timestamp: Date.now() });
|
|
174
174
|
}
|
|
175
175
|
}
|
|
@@ -204,7 +204,7 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
async startWithAutoHealing(config) {
|
|
207
|
-
console.log('
|
|
207
|
+
console.log(' [AI-Engine] Starting with auto-healing...');
|
|
208
208
|
|
|
209
209
|
this.currentSession = {
|
|
210
210
|
startTime: Date.now(),
|
|
@@ -218,16 +218,16 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
218
218
|
|
|
219
219
|
while (attempts < this.maxRetries) {
|
|
220
220
|
attempts++;
|
|
221
|
-
console.log(`\n
|
|
221
|
+
console.log(`\n [AI-Engine] Attempt ${attempts}/${this.maxRetries}`);
|
|
222
222
|
|
|
223
223
|
if (attempts > 1 && this.pendingFixes.length > 0) {
|
|
224
224
|
const fix = this.pendingFixes.shift();
|
|
225
|
-
console.log(
|
|
225
|
+
console.log(` Applying: ${fix.fix.description}`);
|
|
226
226
|
currentConfig = this.applyFix(currentConfig, fix.fix);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
if (currentConfig.recompile) {
|
|
230
|
-
console.log('
|
|
230
|
+
console.log(' Recompiling...');
|
|
231
231
|
await this.recompile();
|
|
232
232
|
delete currentConfig.recompile;
|
|
233
233
|
}
|
|
@@ -242,7 +242,7 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
242
242
|
});
|
|
243
243
|
|
|
244
244
|
if (result.success) {
|
|
245
|
-
console.log(`\n
|
|
245
|
+
console.log(`\n [AI-Engine] Success after ${attempts} attempt(s)`);
|
|
246
246
|
this.lastSuccessfulConfig = { ...currentConfig };
|
|
247
247
|
|
|
248
248
|
if (attempts > 1) {
|
|
@@ -257,16 +257,16 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
lastError = result.error;
|
|
260
|
-
console.log(
|
|
260
|
+
console.log(` Attempt ${attempts} failed: ${lastError?.substring(0, 100)}...`);
|
|
261
261
|
|
|
262
262
|
const fix = this.getFix(result.error, currentConfig);
|
|
263
263
|
if (fix) {
|
|
264
|
-
console.log(
|
|
264
|
+
console.log(` Fix: ${fix.description}`);
|
|
265
265
|
currentConfig = this.applyFix(currentConfig, fix);
|
|
266
266
|
} else {
|
|
267
267
|
const ai = await this.analyzeWithAI('startup', lastError, currentConfig);
|
|
268
268
|
if (ai?.newConfig) {
|
|
269
|
-
console.log(
|
|
269
|
+
console.log(` AI: ${ai.suggestion}`);
|
|
270
270
|
currentConfig = ai.newConfig;
|
|
271
271
|
} else {
|
|
272
272
|
break;
|
|
@@ -274,7 +274,7 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
console.log(`\n
|
|
277
|
+
console.log(`\n [AI-Engine] Failed after ${attempts} attempts`);
|
|
278
278
|
return { ok: false, attempts, error: lastError, config: currentConfig };
|
|
279
279
|
}
|
|
280
280
|
|
|
@@ -305,7 +305,7 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
305
305
|
|
|
306
306
|
// Detectar sucesso nos logs do Spring Boot
|
|
307
307
|
if (isStartupSuccess(message) && !resolved) {
|
|
308
|
-
console.log(
|
|
308
|
+
console.log(` [AI-Engine] Detected startup success: ${message.substring(0, 80)}...`);
|
|
309
309
|
resolved = true;
|
|
310
310
|
cleanup();
|
|
311
311
|
resolve({ success: true, logs });
|
|
@@ -372,7 +372,7 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
372
372
|
newConfig.profile = next;
|
|
373
373
|
newConfig.args = this.updateArgs(config.args, 'profile', next);
|
|
374
374
|
newConfig.env = { ...config.env, SPRING_PROFILES_ACTIVE: next };
|
|
375
|
-
console.log(`
|
|
375
|
+
console.log(` Profile: ${config.profile} ${next}`);
|
|
376
376
|
break;
|
|
377
377
|
|
|
378
378
|
case 'change_port':
|
|
@@ -380,7 +380,7 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
380
380
|
newConfig.port = newPort;
|
|
381
381
|
newConfig.args = this.updateArgs(config.args, 'port', newPort);
|
|
382
382
|
newConfig.env = { ...config.env, SERVER_PORT: String(newPort), PORT: String(newPort) };
|
|
383
|
-
console.log(`
|
|
383
|
+
console.log(` Port: ${config.port} ${newPort}`);
|
|
384
384
|
break;
|
|
385
385
|
|
|
386
386
|
case 'recompile':
|
|
@@ -418,7 +418,7 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
418
418
|
|
|
419
419
|
if (response.ok) return await response.json();
|
|
420
420
|
} catch (err) {
|
|
421
|
-
console.log(
|
|
421
|
+
console.log(` [AI-Engine] AI unavailable: ${err.message}`);
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
return this.localFallback(errorType, error, config);
|
|
@@ -527,7 +527,7 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
527
527
|
|
|
528
528
|
setActive(active) {
|
|
529
529
|
this.isActive = active;
|
|
530
|
-
console.log(
|
|
530
|
+
console.log(` [AI-Engine] ${active ? 'ENABLED' : 'DISABLED'}`);
|
|
531
531
|
}
|
|
532
532
|
|
|
533
533
|
clearHistory() {
|
|
@@ -537,15 +537,15 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
537
537
|
}
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
-
//
|
|
540
|
+
// Instncia global do AI Engine
|
|
541
541
|
let aiEngine = null;
|
|
542
542
|
|
|
543
543
|
const app = express();
|
|
544
544
|
|
|
545
|
-
//
|
|
545
|
+
// FIXED: Support Cloud Run PORT environment variable (GCP uses PORT)
|
|
546
546
|
const PORT = process.env.PORT || process.env.LOCAL_AGENT_PORT || 5055;
|
|
547
547
|
|
|
548
|
-
//
|
|
548
|
+
// FIXED: Allow CORS from Cloud Run and local development
|
|
549
549
|
app.use(cors({
|
|
550
550
|
origin: [
|
|
551
551
|
"http://localhost:3010",
|
|
@@ -566,18 +566,18 @@ app.use(cors({
|
|
|
566
566
|
credentials: true
|
|
567
567
|
}));
|
|
568
568
|
|
|
569
|
-
//
|
|
569
|
+
// Handle preflight requests explicitly
|
|
570
570
|
app.options('*', cors());
|
|
571
571
|
|
|
572
572
|
app.use(bodyParser.json({ limit: "50mb" }));
|
|
573
573
|
|
|
574
|
-
//
|
|
575
|
-
// Pode ser sobrescrito via
|
|
574
|
+
// DEFAULT WORKSPACE - Define o workspace padro
|
|
575
|
+
// Pode ser sobrescrito via varivel de ambiente ou POST /workspace/open
|
|
576
576
|
const DEFAULT_WORKSPACE = process.env.DEFAULT_WORKSPACE || '/Users/macintosh/IdeaProjects/pure-core-ms';
|
|
577
577
|
|
|
578
578
|
let WORKSPACE_ROOT = fs.existsSync(DEFAULT_WORKSPACE) ? DEFAULT_WORKSPACE : null;
|
|
579
579
|
if (WORKSPACE_ROOT) {
|
|
580
|
-
console.log(
|
|
580
|
+
console.log(` Default workspace: ${WORKSPACE_ROOT}`);
|
|
581
581
|
}
|
|
582
582
|
|
|
583
583
|
let DETECTED_SERVICES = [];
|
|
@@ -593,11 +593,11 @@ function resolveWorkspaceRoot(req) {
|
|
|
593
593
|
}
|
|
594
594
|
const MCP_PORT = process.env.MCP_PORT || 5056;
|
|
595
595
|
|
|
596
|
-
//
|
|
596
|
+
// Inicializar AI Vibe Coding Engine
|
|
597
597
|
aiEngine = new AIVibeCodingEngine(processManager, () => WORKSPACE_ROOT);
|
|
598
598
|
|
|
599
599
|
// ============================================
|
|
600
|
-
//
|
|
600
|
+
// BACKUP STORAGE (Sprint 1.3)
|
|
601
601
|
// In-memory backup storage with configurable max size
|
|
602
602
|
// ============================================
|
|
603
603
|
const BACKUPS = new Map();
|
|
@@ -606,7 +606,7 @@ const BACKUP_INDEX_PATH = path.join(os.tmpdir(), 'deepdebug-backups-index.json')
|
|
|
606
606
|
|
|
607
607
|
/**
|
|
608
608
|
* Persist backup index to disk so diffs survive server restarts.
|
|
609
|
-
* Only saves the index (backupId
|
|
609
|
+
* Only saves the index (backupId files paths), not the file contents.
|
|
610
610
|
* File contents are read from the backup directory on disk.
|
|
611
611
|
*/
|
|
612
612
|
function saveBackupIndex() {
|
|
@@ -652,7 +652,7 @@ function loadBackupIndex() {
|
|
|
652
652
|
});
|
|
653
653
|
}
|
|
654
654
|
}
|
|
655
|
-
console.log(
|
|
655
|
+
console.log(` Restored ${BACKUPS.size} backups from disk`);
|
|
656
656
|
}
|
|
657
657
|
} catch (e) {
|
|
658
658
|
console.warn('Could not load backup index:', e.message);
|
|
@@ -664,19 +664,19 @@ loadBackupIndex();
|
|
|
664
664
|
|
|
665
665
|
// Event listeners do ProcessManager
|
|
666
666
|
processManager.on("started", ({ serviceId }) => {
|
|
667
|
-
console.log(
|
|
667
|
+
console.log(` Service ${serviceId} started successfully`);
|
|
668
668
|
updateServiceStatus(serviceId, "running");
|
|
669
669
|
addServerLog("info", `Service ${serviceId} started successfully`);
|
|
670
670
|
});
|
|
671
671
|
|
|
672
672
|
processManager.on("stopped", ({ serviceId }) => {
|
|
673
|
-
console.log(
|
|
673
|
+
console.log(` Service ${serviceId} stopped`);
|
|
674
674
|
updateServiceStatus(serviceId, "stopped");
|
|
675
675
|
addServerLog("info", `Service ${serviceId} stopped`);
|
|
676
676
|
});
|
|
677
677
|
|
|
678
678
|
processManager.on("error", ({ serviceId, error }) => {
|
|
679
|
-
console.error(
|
|
679
|
+
console.error(` Service ${serviceId} error: ${error}`);
|
|
680
680
|
updateServiceStatus(serviceId, "failed");
|
|
681
681
|
addServerLog("error", `Service ${serviceId} error: ${error}`);
|
|
682
682
|
});
|
|
@@ -731,10 +731,10 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
731
731
|
// CLOUD MODE: Git clone (when Gateway sends repoUrl)
|
|
732
732
|
// ==========================================
|
|
733
733
|
if (repoUrl) {
|
|
734
|
-
console.log(
|
|
734
|
+
console.log(` [/workspace/open] CLOUD MODE cloning ${repoUrl} (branch: ${branch})`);
|
|
735
735
|
|
|
736
736
|
try {
|
|
737
|
-
// Extract repo name: https://github.com/org/repo
|
|
737
|
+
// Extract repo name: https://github.com/org/repo org_repo
|
|
738
738
|
let repoName = repoUrl.split('/').pop().replace('.git', '');
|
|
739
739
|
const urlParts = repoUrl.replace(/\.git$/, '').split('/');
|
|
740
740
|
if (urlParts.length >= 2) {
|
|
@@ -761,7 +761,7 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
761
761
|
// Already in user:pass format (Bitbucket/GitLab)
|
|
762
762
|
authUrl = cleanUrl.replace('https://', `https://${token}@`);
|
|
763
763
|
} else {
|
|
764
|
-
// Plain token
|
|
764
|
+
// Plain token GitHub style
|
|
765
765
|
authUrl = cleanUrl.replace('https://', `https://x-access-token:${token}@`);
|
|
766
766
|
}
|
|
767
767
|
} else {
|
|
@@ -772,16 +772,16 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
772
772
|
const alreadyCloned = await exists(gitDir);
|
|
773
773
|
|
|
774
774
|
if (alreadyCloned) {
|
|
775
|
-
console.log(
|
|
775
|
+
console.log(` [cloud] Repo exists: ${clonePath}, updating...`);
|
|
776
776
|
// Update remote URL with fresh token
|
|
777
777
|
await execAsync(`git remote set-url origin "${authUrl}"`, { cwd: clonePath }).catch(() => {});
|
|
778
778
|
await execAsync(`git fetch origin`, { cwd: clonePath, timeout: 120000 });
|
|
779
779
|
// Checkout correct branch and reset to remote (discard previous patches)
|
|
780
780
|
await execAsync(`git checkout ${branch} 2>/dev/null || git checkout -b ${branch} origin/${branch}`, { cwd: clonePath }).catch(() => {});
|
|
781
781
|
await execAsync(`git reset --hard origin/${branch}`, { cwd: clonePath });
|
|
782
|
-
console.log(
|
|
782
|
+
console.log(` [cloud] Updated to latest ${branch}`);
|
|
783
783
|
} else {
|
|
784
|
-
console.log(
|
|
784
|
+
console.log(` [cloud] Cloning ${repoUrl} (branch: ${branch})...`);
|
|
785
785
|
await execAsync(
|
|
786
786
|
`git clone --branch ${branch} --single-branch --depth 50 "${authUrl}" "${clonePath}"`,
|
|
787
787
|
{ timeout: 300000 }
|
|
@@ -789,7 +789,7 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
789
789
|
// Configure git user for future commits
|
|
790
790
|
await execAsync(`git config user.email "deepdebug-ai@deepdebug.ai"`, { cwd: clonePath });
|
|
791
791
|
await execAsync(`git config user.name "DeepDebug AI"`, { cwd: clonePath });
|
|
792
|
-
console.log(
|
|
792
|
+
console.log(` [cloud] Cloned successfully: ${clonePath}`);
|
|
793
793
|
}
|
|
794
794
|
|
|
795
795
|
// Set as active workspace
|
|
@@ -799,7 +799,7 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
799
799
|
}
|
|
800
800
|
const wsId = workspaceId || "default";
|
|
801
801
|
try { await wsManager.open(wsId, clonePath); } catch (err) {
|
|
802
|
-
console.warn(
|
|
802
|
+
console.warn(` WorkspaceManager.open failed (non-fatal): ${err.message}`);
|
|
803
803
|
}
|
|
804
804
|
|
|
805
805
|
const meta = await detectProject(clonePath);
|
|
@@ -815,7 +815,7 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
815
815
|
});
|
|
816
816
|
|
|
817
817
|
} catch (gitErr) {
|
|
818
|
-
console.error(
|
|
818
|
+
console.error(` [cloud] Git clone failed:`, gitErr.message);
|
|
819
819
|
const hint = gitErr.message.includes('Authentication') || gitErr.message.includes('could not read')
|
|
820
820
|
? "Authentication failed. Check token and repo URL."
|
|
821
821
|
: gitErr.message.includes('not found') || gitErr.message.includes('does not exist')
|
|
@@ -832,22 +832,22 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
832
832
|
const abs = path.resolve(root);
|
|
833
833
|
if (!(await exists(abs))) return res.status(404).json({ error: "path not found" });
|
|
834
834
|
|
|
835
|
+
// Registar no WorkspaceManager (multi-workspace support)
|
|
836
|
+
const wsId = workspaceId || "default";
|
|
837
|
+
|
|
835
838
|
// TENANT ISOLATION: Only set global for default
|
|
836
839
|
if (wsId === "default") {
|
|
837
840
|
WORKSPACE_ROOT = abs;
|
|
838
841
|
}
|
|
839
|
-
|
|
840
|
-
// Registar no WorkspaceManager (multi-workspace support)
|
|
841
|
-
const wsId = workspaceId || "default";
|
|
842
842
|
try {
|
|
843
843
|
await wsManager.open(wsId, abs);
|
|
844
844
|
} catch (err) {
|
|
845
|
-
console.warn(
|
|
845
|
+
console.warn(` WorkspaceManager open failed (non-fatal): ${err.message}`);
|
|
846
846
|
}
|
|
847
847
|
|
|
848
|
-
const meta = await detectProject(
|
|
849
|
-
const port = await detectPort(
|
|
850
|
-
res.json({ ok: true, root:
|
|
848
|
+
const meta = await detectProject(abs);
|
|
849
|
+
const port = await detectPort(abs);
|
|
850
|
+
res.json({ ok: true, root: abs, workspaceId: wsId, mode: "local", meta, port });
|
|
851
851
|
});
|
|
852
852
|
|
|
853
853
|
/**
|
|
@@ -862,7 +862,7 @@ app.post("/workspace/clone", async (req, res) => {
|
|
|
862
862
|
if (!targetPath) return res.status(400).json({ ok: false, error: "targetPath is required" });
|
|
863
863
|
|
|
864
864
|
const absTarget = path.resolve(targetPath);
|
|
865
|
-
console.log(
|
|
865
|
+
console.log(` Clone request: ${gitUrl} -> ${absTarget}`);
|
|
866
866
|
|
|
867
867
|
try {
|
|
868
868
|
// Ensure parent directory exists
|
|
@@ -874,13 +874,13 @@ app.post("/workspace/clone", async (req, res) => {
|
|
|
874
874
|
const alreadyCloned = await exists(gitDir);
|
|
875
875
|
|
|
876
876
|
if (alreadyCloned) {
|
|
877
|
-
console.log(
|
|
877
|
+
console.log(` Repo already exists at ${absTarget}, running git pull...`);
|
|
878
878
|
const { stdout } = await execAsync('git pull', { cwd: absTarget, timeout: 120000 });
|
|
879
|
-
console.log(
|
|
879
|
+
console.log(` git pull: ${stdout.trim()}`);
|
|
880
880
|
} else {
|
|
881
|
-
console.log(
|
|
881
|
+
console.log(` Running: git clone "${gitUrl}" "${absTarget}"`);
|
|
882
882
|
await execAsync(`git clone "${gitUrl}" "${absTarget}"`, { timeout: 300000 });
|
|
883
|
-
console.log(
|
|
883
|
+
console.log(` Clone complete: ${absTarget}`);
|
|
884
884
|
}
|
|
885
885
|
|
|
886
886
|
// Open the cloned workspace
|
|
@@ -889,7 +889,7 @@ app.post("/workspace/clone", async (req, res) => {
|
|
|
889
889
|
try {
|
|
890
890
|
await wsManager.open(wsId, absTarget);
|
|
891
891
|
} catch (err) {
|
|
892
|
-
console.warn(
|
|
892
|
+
console.warn(` WorkspaceManager.open failed (non-fatal): ${err.message}`);
|
|
893
893
|
}
|
|
894
894
|
|
|
895
895
|
const meta = await detectProject(absTarget);
|
|
@@ -905,7 +905,7 @@ app.post("/workspace/clone", async (req, res) => {
|
|
|
905
905
|
});
|
|
906
906
|
|
|
907
907
|
} catch (err) {
|
|
908
|
-
console.error(
|
|
908
|
+
console.error(` Clone failed: ${err.message}`);
|
|
909
909
|
const hint = err.message.includes('Authentication') || err.message.includes('could not read')
|
|
910
910
|
? "Authentication failed. Ensure the repo is public or GitHub integration is configured."
|
|
911
911
|
: err.message.includes('not found') || err.message.includes('does not exist')
|
|
@@ -938,7 +938,7 @@ app.get("/workspace/scan", async (req, res) => {
|
|
|
938
938
|
}
|
|
939
939
|
});
|
|
940
940
|
|
|
941
|
-
/**
|
|
941
|
+
/** Anlise completa: language + framework */
|
|
942
942
|
app.get("/workspace/analyze", async (req, res) => {
|
|
943
943
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
944
944
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
@@ -970,7 +970,7 @@ app.get("/workspace/analyze", async (req, res) => {
|
|
|
970
970
|
}
|
|
971
971
|
});
|
|
972
972
|
|
|
973
|
-
/**
|
|
973
|
+
/** L contedo de arquivo especfico */
|
|
974
974
|
app.get("/workspace/file-content", async (req, res) => {
|
|
975
975
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
976
976
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
@@ -987,7 +987,7 @@ app.get("/workspace/file-content", async (req, res) => {
|
|
|
987
987
|
}
|
|
988
988
|
});
|
|
989
989
|
|
|
990
|
-
/**
|
|
990
|
+
/** L mltiplos arquivos */
|
|
991
991
|
app.post("/workspace/batch-read", async (req, res) => {
|
|
992
992
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
993
993
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
@@ -1007,7 +1007,7 @@ app.post("/workspace/batch-read", async (req, res) => {
|
|
|
1007
1007
|
});
|
|
1008
1008
|
|
|
1009
1009
|
// ============================================
|
|
1010
|
-
//
|
|
1010
|
+
// FILE VALIDATION ENDPOINTS (Enhanced Analysis)
|
|
1011
1011
|
// ============================================
|
|
1012
1012
|
|
|
1013
1013
|
/**
|
|
@@ -1029,7 +1029,7 @@ app.get("/workspace/file-exists", async (req, res) => {
|
|
|
1029
1029
|
const fullPath = path.join(wsRoot, relativePath);
|
|
1030
1030
|
const fileExists = await exists(fullPath);
|
|
1031
1031
|
|
|
1032
|
-
console.log(
|
|
1032
|
+
console.log(` [file-exists] ${relativePath} -> ${fileExists ? 'EXISTS' : 'NOT FOUND'}`);
|
|
1033
1033
|
|
|
1034
1034
|
res.json({
|
|
1035
1035
|
ok: true,
|
|
@@ -1038,7 +1038,7 @@ app.get("/workspace/file-exists", async (req, res) => {
|
|
|
1038
1038
|
fullPath: fullPath
|
|
1039
1039
|
});
|
|
1040
1040
|
} catch (err) {
|
|
1041
|
-
console.error(
|
|
1041
|
+
console.error(` [file-exists] Error:`, err.message);
|
|
1042
1042
|
res.status(500).json({ ok: false, error: err.message });
|
|
1043
1043
|
}
|
|
1044
1044
|
});
|
|
@@ -1070,7 +1070,7 @@ app.post("/workspace/validate-paths", async (req, res) => {
|
|
|
1070
1070
|
const allExist = results.every(r => r.exists);
|
|
1071
1071
|
const missingPaths = results.filter(r => !r.exists).map(r => r.path);
|
|
1072
1072
|
|
|
1073
|
-
console.log(
|
|
1073
|
+
console.log(` [validate-paths] Checked ${pathList.length} paths, ${missingPaths.length} missing`);
|
|
1074
1074
|
|
|
1075
1075
|
res.json({
|
|
1076
1076
|
ok: true,
|
|
@@ -1080,7 +1080,7 @@ app.post("/workspace/validate-paths", async (req, res) => {
|
|
|
1080
1080
|
totalChecked: pathList.length
|
|
1081
1081
|
});
|
|
1082
1082
|
} catch (err) {
|
|
1083
|
-
console.error(
|
|
1083
|
+
console.error(` [validate-paths] Error:`, err.message);
|
|
1084
1084
|
res.status(500).json({ ok: false, error: err.message });
|
|
1085
1085
|
}
|
|
1086
1086
|
});
|
|
@@ -1103,7 +1103,7 @@ app.post("/workspace/search-file", async (req, res) => {
|
|
|
1103
1103
|
}
|
|
1104
1104
|
|
|
1105
1105
|
try {
|
|
1106
|
-
console.log(
|
|
1106
|
+
console.log(` [search-file] Searching for: ${fileName}`);
|
|
1107
1107
|
|
|
1108
1108
|
const rawFiles = await listRecursive(wsRoot, {
|
|
1109
1109
|
maxDepth: 15,
|
|
@@ -1111,7 +1111,7 @@ app.post("/workspace/search-file", async (req, res) => {
|
|
|
1111
1111
|
extensions: null
|
|
1112
1112
|
});
|
|
1113
1113
|
|
|
1114
|
-
//
|
|
1114
|
+
// FIXED: Normalize files to string paths
|
|
1115
1115
|
// listRecursive may return strings OR objects like {path: 'xxx', name: 'yyy'}
|
|
1116
1116
|
const allFiles = rawFiles.map(f => {
|
|
1117
1117
|
if (typeof f === 'string') return f;
|
|
@@ -1120,7 +1120,7 @@ app.post("/workspace/search-file", async (req, res) => {
|
|
|
1120
1120
|
return String(f);
|
|
1121
1121
|
}).filter(f => f && typeof f === 'string');
|
|
1122
1122
|
|
|
1123
|
-
console.log(
|
|
1123
|
+
console.log(` [search-file] Scanning ${allFiles.length} files`);
|
|
1124
1124
|
|
|
1125
1125
|
// Strategy 1: Exact path match
|
|
1126
1126
|
let foundPath = allFiles.find(f => f.endsWith('/' + fileName) || f === fileName);
|
|
@@ -1150,14 +1150,14 @@ app.post("/workspace/search-file", async (req, res) => {
|
|
|
1150
1150
|
}
|
|
1151
1151
|
|
|
1152
1152
|
if (foundPath) {
|
|
1153
|
-
console.log(
|
|
1153
|
+
console.log(` [search-file] Found: ${foundPath}`);
|
|
1154
1154
|
res.json({ ok: true, found: true, path: foundPath, fileName });
|
|
1155
1155
|
} else {
|
|
1156
|
-
console.log(
|
|
1156
|
+
console.log(` [search-file] Not found: ${fileName}`);
|
|
1157
1157
|
res.json({ ok: true, found: false, fileName, searchedFiles: allFiles.length });
|
|
1158
1158
|
}
|
|
1159
1159
|
} catch (err) {
|
|
1160
|
-
console.error(
|
|
1160
|
+
console.error(` [search-file] Error:`, err.message);
|
|
1161
1161
|
res.status(500).json({ ok: false, error: err.message });
|
|
1162
1162
|
}
|
|
1163
1163
|
});
|
|
@@ -1181,14 +1181,14 @@ app.post("/workspace/search-by-content", async (req, res) => {
|
|
|
1181
1181
|
}
|
|
1182
1182
|
|
|
1183
1183
|
try {
|
|
1184
|
-
console.log(
|
|
1184
|
+
console.log(` [search-by-content] Searching for terms: ${terms.join(', ')}`);
|
|
1185
1185
|
|
|
1186
1186
|
const rawFiles = await listRecursive(wsRoot, {
|
|
1187
1187
|
maxDepth: 15,
|
|
1188
1188
|
includeHidden: false
|
|
1189
1189
|
});
|
|
1190
1190
|
|
|
1191
|
-
//
|
|
1191
|
+
// FIXED: Normalize files to string paths
|
|
1192
1192
|
const allFiles = rawFiles.map(f => {
|
|
1193
1193
|
if (typeof f === 'string') return f;
|
|
1194
1194
|
if (f && typeof f === 'object' && f.path) return f.path;
|
|
@@ -1201,7 +1201,7 @@ app.post("/workspace/search-by-content", async (req, res) => {
|
|
|
1201
1201
|
return extensions.some(ext => filePath.endsWith(ext));
|
|
1202
1202
|
});
|
|
1203
1203
|
|
|
1204
|
-
console.log(
|
|
1204
|
+
console.log(` [search-by-content] Scanning ${filteredFiles.length} files`);
|
|
1205
1205
|
|
|
1206
1206
|
const results = [];
|
|
1207
1207
|
|
|
@@ -1246,7 +1246,7 @@ app.post("/workspace/search-by-content", async (req, res) => {
|
|
|
1246
1246
|
return true;
|
|
1247
1247
|
}).slice(0, maxResults);
|
|
1248
1248
|
|
|
1249
|
-
console.log(
|
|
1249
|
+
console.log(` [search-by-content] Found ${dedupedResults.length} matching files`);
|
|
1250
1250
|
|
|
1251
1251
|
res.json({
|
|
1252
1252
|
ok: true,
|
|
@@ -1255,7 +1255,7 @@ app.post("/workspace/search-by-content", async (req, res) => {
|
|
|
1255
1255
|
termsSearched: terms
|
|
1256
1256
|
});
|
|
1257
1257
|
} catch (err) {
|
|
1258
|
-
console.error(
|
|
1258
|
+
console.error(` [search-by-content] Error:`, err.message);
|
|
1259
1259
|
res.status(500).json({ ok: false, error: err.message });
|
|
1260
1260
|
}
|
|
1261
1261
|
});
|
|
@@ -1279,14 +1279,14 @@ app.post("/workspace/find-field-definition", async (req, res) => {
|
|
|
1279
1279
|
}
|
|
1280
1280
|
|
|
1281
1281
|
try {
|
|
1282
|
-
console.log(
|
|
1282
|
+
console.log(` [find-field] Searching for field: ${fieldName}`);
|
|
1283
1283
|
|
|
1284
1284
|
const rawFiles = await listRecursive(wsRoot, {
|
|
1285
1285
|
maxDepth: 15,
|
|
1286
1286
|
includeHidden: false
|
|
1287
1287
|
});
|
|
1288
1288
|
|
|
1289
|
-
//
|
|
1289
|
+
// FIXED: Normalize files to string paths
|
|
1290
1290
|
const allFiles = rawFiles.map(f => {
|
|
1291
1291
|
if (typeof f === 'string') return f;
|
|
1292
1292
|
if (f && typeof f === 'object' && f.path) return f.path;
|
|
@@ -1358,7 +1358,7 @@ app.post("/workspace/find-field-definition", async (req, res) => {
|
|
|
1358
1358
|
return 0;
|
|
1359
1359
|
});
|
|
1360
1360
|
|
|
1361
|
-
console.log(
|
|
1361
|
+
console.log(` [find-field] Found ${definitions.length} definitions for '${fieldName}'`);
|
|
1362
1362
|
|
|
1363
1363
|
res.json({
|
|
1364
1364
|
ok: true,
|
|
@@ -1367,17 +1367,17 @@ app.post("/workspace/find-field-definition", async (req, res) => {
|
|
|
1367
1367
|
totalSearched: targetFiles.length
|
|
1368
1368
|
});
|
|
1369
1369
|
} catch (err) {
|
|
1370
|
-
console.error(
|
|
1370
|
+
console.error(` [find-field] Error:`, err.message);
|
|
1371
1371
|
res.status(500).json({ ok: false, error: err.message });
|
|
1372
1372
|
}
|
|
1373
1373
|
});
|
|
1374
1374
|
|
|
1375
1375
|
|
|
1376
1376
|
// ============================================
|
|
1377
|
-
//
|
|
1377
|
+
// RUNTIME MANAGEMENT ENDPOINTS
|
|
1378
1378
|
// ============================================
|
|
1379
1379
|
|
|
1380
|
-
/** Detecta
|
|
1380
|
+
/** Detecta servios no workspace */
|
|
1381
1381
|
app.get("/workspace/services/detect", async (req, res) => {
|
|
1382
1382
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
1383
1383
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
@@ -1414,12 +1414,12 @@ app.get("/workspace/services/detect", async (req, res) => {
|
|
|
1414
1414
|
}
|
|
1415
1415
|
});
|
|
1416
1416
|
|
|
1417
|
-
/** Lista todos os
|
|
1417
|
+
/** Lista todos os servios */
|
|
1418
1418
|
app.get("/workspace/services", (req, res) => {
|
|
1419
1419
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
1420
1420
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
1421
1421
|
|
|
1422
|
-
// Atualizar status dos
|
|
1422
|
+
// Atualizar status dos servios com info do ProcessManager
|
|
1423
1423
|
const servicesWithStatus = DETECTED_SERVICES.map(service => {
|
|
1424
1424
|
const status = processManager.getStatus(service.id);
|
|
1425
1425
|
return {
|
|
@@ -1431,7 +1431,7 @@ app.get("/workspace/services", (req, res) => {
|
|
|
1431
1431
|
res.json({ services: servicesWithStatus });
|
|
1432
1432
|
});
|
|
1433
1433
|
|
|
1434
|
-
/** Inicia um
|
|
1434
|
+
/** Inicia um servio */
|
|
1435
1435
|
app.post("/workspace/services/:serviceId/start", async (req, res) => {
|
|
1436
1436
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
1437
1437
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
@@ -1458,7 +1458,7 @@ app.post("/workspace/services/:serviceId/start", async (req, res) => {
|
|
|
1458
1458
|
}
|
|
1459
1459
|
});
|
|
1460
1460
|
|
|
1461
|
-
/** Para um
|
|
1461
|
+
/** Para um servio */
|
|
1462
1462
|
app.post("/workspace/services/:serviceId/stop", async (req, res) => {
|
|
1463
1463
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
1464
1464
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
@@ -1473,7 +1473,7 @@ app.post("/workspace/services/:serviceId/stop", async (req, res) => {
|
|
|
1473
1473
|
}
|
|
1474
1474
|
});
|
|
1475
1475
|
|
|
1476
|
-
/** Retorna status de um
|
|
1476
|
+
/** Retorna status de um servio */
|
|
1477
1477
|
app.get("/workspace/services/:serviceId/status", (req, res) => {
|
|
1478
1478
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
1479
1479
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
@@ -1484,7 +1484,7 @@ app.get("/workspace/services/:serviceId/status", (req, res) => {
|
|
|
1484
1484
|
res.json({ serviceId, ...status });
|
|
1485
1485
|
});
|
|
1486
1486
|
|
|
1487
|
-
/** Retorna logs de um
|
|
1487
|
+
/** Retorna logs de um servio */
|
|
1488
1488
|
app.get("/workspace/services/:serviceId/logs", (req, res) => {
|
|
1489
1489
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
1490
1490
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
@@ -1527,7 +1527,7 @@ app.get("/workspace/services/:serviceId/logs/stream", (req, res) => {
|
|
|
1527
1527
|
|
|
1528
1528
|
processManager.on("log", logHandler);
|
|
1529
1529
|
|
|
1530
|
-
// Cleanup ao fechar
|
|
1530
|
+
// Cleanup ao fechar conexo
|
|
1531
1531
|
req.on("close", () => {
|
|
1532
1532
|
processManager.off("log", logHandler);
|
|
1533
1533
|
});
|
|
@@ -1572,7 +1572,7 @@ app.post("/workspace/write", async (req, res) => {
|
|
|
1572
1572
|
});
|
|
1573
1573
|
|
|
1574
1574
|
// ============================================
|
|
1575
|
-
//
|
|
1575
|
+
// CORRECTED: /workspace/patch endpoint
|
|
1576
1576
|
// ============================================
|
|
1577
1577
|
app.post("/workspace/patch", async (req, res) => {
|
|
1578
1578
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
@@ -1581,10 +1581,10 @@ app.post("/workspace/patch", async (req, res) => {
|
|
|
1581
1581
|
if (!diff) return res.status(400).json({ error: "diff is required" });
|
|
1582
1582
|
|
|
1583
1583
|
try {
|
|
1584
|
-
console.log(
|
|
1584
|
+
console.log(` Applying patch for incident: ${incidentId || 'unknown'}`);
|
|
1585
1585
|
const out = await applyUnifiedDiff(wsRoot, diff);
|
|
1586
1586
|
|
|
1587
|
-
//
|
|
1587
|
+
// CRITICAL FIX: Format response as expected by Gateway
|
|
1588
1588
|
const response = {
|
|
1589
1589
|
ok: true,
|
|
1590
1590
|
filesModified: 1,
|
|
@@ -1596,7 +1596,7 @@ app.post("/workspace/patch", async (req, res) => {
|
|
|
1596
1596
|
incidentId: incidentId
|
|
1597
1597
|
};
|
|
1598
1598
|
|
|
1599
|
-
console.log(
|
|
1599
|
+
console.log(` Patch applied successfully:`, {
|
|
1600
1600
|
target: out.target,
|
|
1601
1601
|
bytes: out.bytes,
|
|
1602
1602
|
incident: incidentId
|
|
@@ -1604,7 +1604,7 @@ app.post("/workspace/patch", async (req, res) => {
|
|
|
1604
1604
|
|
|
1605
1605
|
res.json(response);
|
|
1606
1606
|
} catch (e) {
|
|
1607
|
-
console.error(
|
|
1607
|
+
console.error(` Patch failed:`, e.message);
|
|
1608
1608
|
res.status(400).json({
|
|
1609
1609
|
ok: false,
|
|
1610
1610
|
error: "patch failed",
|
|
@@ -1633,7 +1633,7 @@ app.post("/workspace/run", async (req, res) => {
|
|
|
1633
1633
|
});
|
|
1634
1634
|
|
|
1635
1635
|
// ============================================
|
|
1636
|
-
//
|
|
1636
|
+
// TEST LOCAL ENDPOINTS
|
|
1637
1637
|
// ============================================
|
|
1638
1638
|
|
|
1639
1639
|
/** Store test local state */
|
|
@@ -1644,11 +1644,11 @@ let TEST_LOCAL_STATE = {
|
|
|
1644
1644
|
endpoints: [],
|
|
1645
1645
|
config: null,
|
|
1646
1646
|
testResults: [],
|
|
1647
|
-
serverLogs: [] // Buffer circular de logs do servidor (
|
|
1647
|
+
serverLogs: [] // Buffer circular de logs do servidor (ltimos 1000)
|
|
1648
1648
|
};
|
|
1649
1649
|
|
|
1650
1650
|
// ============================================
|
|
1651
|
-
//
|
|
1651
|
+
// TEST LOCAL STATE ENDPOINTS (ADDED)
|
|
1652
1652
|
// ============================================
|
|
1653
1653
|
|
|
1654
1654
|
/**
|
|
@@ -1656,7 +1656,7 @@ let TEST_LOCAL_STATE = {
|
|
|
1656
1656
|
* Returns current test local state with auto-detected port
|
|
1657
1657
|
*/
|
|
1658
1658
|
app.get("/workspace/test-local/state", async (req, res) => {
|
|
1659
|
-
console.log("
|
|
1659
|
+
console.log(" [TEST-LOCAL] Getting state:", TEST_LOCAL_STATE.status);
|
|
1660
1660
|
|
|
1661
1661
|
// Auto-detect port if not set in config
|
|
1662
1662
|
let port = TEST_LOCAL_STATE.config?.server?.port;
|
|
@@ -1695,7 +1695,7 @@ app.post("/workspace/test-local/compile", async (req, res) => {
|
|
|
1695
1695
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
1696
1696
|
|
|
1697
1697
|
try {
|
|
1698
|
-
console.log("
|
|
1698
|
+
console.log(" [TEST-LOCAL] Starting compilation...");
|
|
1699
1699
|
TEST_LOCAL_STATE.status = "compiling";
|
|
1700
1700
|
|
|
1701
1701
|
const meta = await detectProject(wsRoot);
|
|
@@ -1730,7 +1730,7 @@ app.post("/workspace/test-local/compile", async (req, res) => {
|
|
|
1730
1730
|
duration: compileResult.duration
|
|
1731
1731
|
};
|
|
1732
1732
|
|
|
1733
|
-
console.log("
|
|
1733
|
+
console.log(" [TEST-LOCAL] Compilation successful");
|
|
1734
1734
|
|
|
1735
1735
|
res.json({
|
|
1736
1736
|
ok: true,
|
|
@@ -1740,7 +1740,7 @@ app.post("/workspace/test-local/compile", async (req, res) => {
|
|
|
1740
1740
|
stdout: compileResult.stdout
|
|
1741
1741
|
});
|
|
1742
1742
|
} catch (err) {
|
|
1743
|
-
console.error("
|
|
1743
|
+
console.error(" [TEST-LOCAL] Compilation failed:", err.message);
|
|
1744
1744
|
TEST_LOCAL_STATE.status = "error";
|
|
1745
1745
|
res.status(500).json({ ok: false, error: err.message });
|
|
1746
1746
|
}
|
|
@@ -1750,7 +1750,7 @@ app.post("/workspace/test-local/compile", async (req, res) => {
|
|
|
1750
1750
|
* POST /workspace/test-local/start
|
|
1751
1751
|
* Starts the local server with AUTO-HEALING
|
|
1752
1752
|
*
|
|
1753
|
-
*
|
|
1753
|
+
* UPDATED: Now uses AI Vibe Coding Engine for auto-healing
|
|
1754
1754
|
*/
|
|
1755
1755
|
app.post("/workspace/test-local/start", async (req, res) => {
|
|
1756
1756
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
@@ -1759,7 +1759,7 @@ app.post("/workspace/test-local/start", async (req, res) => {
|
|
|
1759
1759
|
const { port } = req.body || {};
|
|
1760
1760
|
|
|
1761
1761
|
try {
|
|
1762
|
-
console.log(`\n
|
|
1762
|
+
console.log(`\n [TEST-LOCAL] Starting server (SIMPLE MODE)...`);
|
|
1763
1763
|
TEST_LOCAL_STATE.status = "starting";
|
|
1764
1764
|
|
|
1765
1765
|
const meta = await detectProject(wsRoot);
|
|
@@ -1793,15 +1793,15 @@ app.post("/workspace/test-local/start", async (req, res) => {
|
|
|
1793
1793
|
const command = 'java';
|
|
1794
1794
|
const args = ['-jar', jarPath, `--server.port=${serverPort}`];
|
|
1795
1795
|
|
|
1796
|
-
console.log(
|
|
1796
|
+
console.log(` Command: ${command} ${args.join(' ')}`);
|
|
1797
1797
|
|
|
1798
|
-
// Env LIMPO - remover TODAS as
|
|
1798
|
+
// Env LIMPO - remover TODAS as variveis Spring que podem interferir
|
|
1799
1799
|
const cleanEnv = { ...process.env };
|
|
1800
1800
|
delete cleanEnv.SPRING_PROFILES_ACTIVE;
|
|
1801
1801
|
delete cleanEnv.SPRING_DATASOURCE_URL;
|
|
1802
1802
|
delete cleanEnv.SPRING_DATASOURCE_USERNAME;
|
|
1803
1803
|
delete cleanEnv.SPRING_DATASOURCE_PASSWORD;
|
|
1804
|
-
// Remover qualquer
|
|
1804
|
+
// Remover qualquer varivel que comece com SPRING_
|
|
1805
1805
|
Object.keys(cleanEnv).forEach(key => {
|
|
1806
1806
|
if (key.startsWith('SPRING_')) {
|
|
1807
1807
|
delete cleanEnv[key];
|
|
@@ -1823,7 +1823,7 @@ app.post("/workspace/test-local/start", async (req, res) => {
|
|
|
1823
1823
|
TEST_LOCAL_STATE.status = "running";
|
|
1824
1824
|
TEST_LOCAL_STATE.config = { port: serverPort };
|
|
1825
1825
|
|
|
1826
|
-
console.log(
|
|
1826
|
+
console.log(` [TEST-LOCAL] Server started on port ${serverPort}`);
|
|
1827
1827
|
|
|
1828
1828
|
res.json({
|
|
1829
1829
|
ok: true,
|
|
@@ -1873,7 +1873,7 @@ app.post("/workspace/test-local/start", async (req, res) => {
|
|
|
1873
1873
|
});
|
|
1874
1874
|
}
|
|
1875
1875
|
} catch (err) {
|
|
1876
|
-
console.error("
|
|
1876
|
+
console.error(" [TEST-LOCAL] Server start failed:", err.message);
|
|
1877
1877
|
TEST_LOCAL_STATE.status = "error";
|
|
1878
1878
|
res.status(500).json({ ok: false, error: err.message });
|
|
1879
1879
|
}
|
|
@@ -1885,19 +1885,19 @@ app.post("/workspace/test-local/start", async (req, res) => {
|
|
|
1885
1885
|
*/
|
|
1886
1886
|
app.post("/workspace/test-local/stop", async (req, res) => {
|
|
1887
1887
|
try {
|
|
1888
|
-
console.log("
|
|
1888
|
+
console.log(" [TEST-LOCAL] Stopping server...");
|
|
1889
1889
|
|
|
1890
1890
|
await processManager.stop('test-local');
|
|
1891
1891
|
TEST_LOCAL_STATE.status = "stopped";
|
|
1892
1892
|
|
|
1893
|
-
console.log("
|
|
1893
|
+
console.log(" [TEST-LOCAL] Server stopped");
|
|
1894
1894
|
|
|
1895
1895
|
res.json({
|
|
1896
1896
|
ok: true,
|
|
1897
1897
|
status: "stopped"
|
|
1898
1898
|
});
|
|
1899
1899
|
} catch (err) {
|
|
1900
|
-
console.error("
|
|
1900
|
+
console.error(" [TEST-LOCAL] Server stop failed:", err.message);
|
|
1901
1901
|
res.status(500).json({ ok: false, error: err.message });
|
|
1902
1902
|
}
|
|
1903
1903
|
});
|
|
@@ -1911,7 +1911,7 @@ app.get("/workspace/test-local/endpoints", async (req, res) => {
|
|
|
1911
1911
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
1912
1912
|
|
|
1913
1913
|
try {
|
|
1914
|
-
console.log("
|
|
1914
|
+
console.log(" [TEST-LOCAL] Getting endpoints...");
|
|
1915
1915
|
|
|
1916
1916
|
// If endpoints are cached, return them
|
|
1917
1917
|
if (TEST_LOCAL_STATE.endpoints && TEST_LOCAL_STATE.endpoints.length > 0) {
|
|
@@ -1934,7 +1934,7 @@ app.get("/workspace/test-local/endpoints", async (req, res) => {
|
|
|
1934
1934
|
|
|
1935
1935
|
TEST_LOCAL_STATE.endpoints = payloadDocs.endpoints;
|
|
1936
1936
|
|
|
1937
|
-
console.log(
|
|
1937
|
+
console.log(` [TEST-LOCAL] Discovered ${payloadDocs.endpoints.length} endpoints`);
|
|
1938
1938
|
|
|
1939
1939
|
res.json({
|
|
1940
1940
|
ok: true,
|
|
@@ -1942,7 +1942,7 @@ app.get("/workspace/test-local/endpoints", async (req, res) => {
|
|
|
1942
1942
|
cached: false
|
|
1943
1943
|
});
|
|
1944
1944
|
} catch (err) {
|
|
1945
|
-
console.error("
|
|
1945
|
+
console.error(" [TEST-LOCAL] Failed to get endpoints:", err.message);
|
|
1946
1946
|
res.status(500).json({ ok: false, error: err.message });
|
|
1947
1947
|
}
|
|
1948
1948
|
});
|
|
@@ -1962,7 +1962,7 @@ app.post("/workspace/test-local/execute", async (req, res) => {
|
|
|
1962
1962
|
const serverPort = port || TEST_LOCAL_STATE.config?.server?.port || 8080;
|
|
1963
1963
|
const url = `http://localhost:${serverPort}${reqPath}`;
|
|
1964
1964
|
|
|
1965
|
-
console.log(
|
|
1965
|
+
console.log(` [TEST-LOCAL] Executing: ${method} ${url}`);
|
|
1966
1966
|
|
|
1967
1967
|
const startTime = Date.now();
|
|
1968
1968
|
|
|
@@ -2011,14 +2011,14 @@ app.post("/workspace/test-local/execute", async (req, res) => {
|
|
|
2011
2011
|
TEST_LOCAL_STATE.testResults.shift();
|
|
2012
2012
|
}
|
|
2013
2013
|
|
|
2014
|
-
console.log(
|
|
2014
|
+
console.log(` [TEST-LOCAL] Test result: ${response.status} (${duration}ms)`);
|
|
2015
2015
|
|
|
2016
2016
|
res.json({
|
|
2017
2017
|
ok: true,
|
|
2018
2018
|
result
|
|
2019
2019
|
});
|
|
2020
2020
|
} catch (err) {
|
|
2021
|
-
console.error("
|
|
2021
|
+
console.error(" [TEST-LOCAL] Test execution failed:", err.message);
|
|
2022
2022
|
res.json({
|
|
2023
2023
|
ok: false,
|
|
2024
2024
|
error: err.message,
|
|
@@ -2037,7 +2037,7 @@ app.post("/workspace/test-local/execute", async (req, res) => {
|
|
|
2037
2037
|
* Returns stored test results
|
|
2038
2038
|
*/
|
|
2039
2039
|
app.get("/workspace/test-local/results", (req, res) => {
|
|
2040
|
-
console.log("
|
|
2040
|
+
console.log(" [TEST-LOCAL] Getting test results...");
|
|
2041
2041
|
|
|
2042
2042
|
res.json({
|
|
2043
2043
|
ok: true,
|
|
@@ -2051,7 +2051,7 @@ app.get("/workspace/test-local/results", (req, res) => {
|
|
|
2051
2051
|
* Clears stored test results
|
|
2052
2052
|
*/
|
|
2053
2053
|
app.post("/workspace/test-local/clear-results", (req, res) => {
|
|
2054
|
-
console.log("
|
|
2054
|
+
console.log(" [TEST-LOCAL] Clearing test results...");
|
|
2055
2055
|
|
|
2056
2056
|
TEST_LOCAL_STATE.testResults = [];
|
|
2057
2057
|
|
|
@@ -2068,7 +2068,7 @@ app.post("/workspace/test-local/clear-results", (req, res) => {
|
|
|
2068
2068
|
app.get("/workspace/test-local/logs", (req, res) => {
|
|
2069
2069
|
const limit = parseInt(req.query.limit) || 500;
|
|
2070
2070
|
|
|
2071
|
-
console.log(
|
|
2071
|
+
console.log(` [TEST-LOCAL] Getting logs (limit: ${limit})`);
|
|
2072
2072
|
|
|
2073
2073
|
const logs = TEST_LOCAL_STATE.serverLogs.slice(-limit);
|
|
2074
2074
|
|
|
@@ -2087,7 +2087,7 @@ app.get("/workspace/controllers", async (req, res) => {
|
|
|
2087
2087
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
2088
2088
|
|
|
2089
2089
|
try {
|
|
2090
|
-
console.log("
|
|
2090
|
+
console.log(" [CONTROLLERS] Discovering controllers...");
|
|
2091
2091
|
|
|
2092
2092
|
const scanner = new WorkspaceScanner(wsRoot);
|
|
2093
2093
|
const structure = await scanner.scan();
|
|
@@ -2095,14 +2095,14 @@ app.get("/workspace/controllers", async (req, res) => {
|
|
|
2095
2095
|
const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
|
|
2096
2096
|
const apiDocs = await controllerAnalyzer.generateApiDocs(structure.files);
|
|
2097
2097
|
|
|
2098
|
-
console.log(
|
|
2098
|
+
console.log(` [CONTROLLERS] Found ${apiDocs.totalEndpoints} endpoints in ${apiDocs.totalControllers} controllers`);
|
|
2099
2099
|
|
|
2100
2100
|
res.json({
|
|
2101
2101
|
ok: true,
|
|
2102
2102
|
...apiDocs
|
|
2103
2103
|
});
|
|
2104
2104
|
} catch (err) {
|
|
2105
|
-
console.error("
|
|
2105
|
+
console.error(" [CONTROLLERS] Failed:", err.message);
|
|
2106
2106
|
res.status(500).json({ ok: false, error: err.message });
|
|
2107
2107
|
}
|
|
2108
2108
|
});
|
|
@@ -2113,7 +2113,7 @@ app.get("/workspace/dtos", async (req, res) => {
|
|
|
2113
2113
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
2114
2114
|
|
|
2115
2115
|
try {
|
|
2116
|
-
console.log("
|
|
2116
|
+
console.log(" [DTOS] Analyzing DTOs...");
|
|
2117
2117
|
|
|
2118
2118
|
const scanner = new WorkspaceScanner(wsRoot);
|
|
2119
2119
|
const structure = await scanner.scan();
|
|
@@ -2124,14 +2124,14 @@ app.get("/workspace/dtos", async (req, res) => {
|
|
|
2124
2124
|
const dtoAnalyzer = new DTOAnalyzer(wsRoot);
|
|
2125
2125
|
const payloadDocs = await dtoAnalyzer.generatePayloadDocs(structure.files, apiDocs.endpoints);
|
|
2126
2126
|
|
|
2127
|
-
console.log(
|
|
2127
|
+
console.log(` [DTOS] Found ${payloadDocs.totalDtos} DTOs`);
|
|
2128
2128
|
|
|
2129
2129
|
res.json({
|
|
2130
2130
|
ok: true,
|
|
2131
2131
|
...payloadDocs
|
|
2132
2132
|
});
|
|
2133
2133
|
} catch (err) {
|
|
2134
|
-
console.error("
|
|
2134
|
+
console.error(" [DTOS] Failed:", err.message);
|
|
2135
2135
|
res.status(500).json({ ok: false, error: err.message });
|
|
2136
2136
|
}
|
|
2137
2137
|
});
|
|
@@ -2142,25 +2142,25 @@ app.get("/workspace/config", async (req, res) => {
|
|
|
2142
2142
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
2143
2143
|
|
|
2144
2144
|
try {
|
|
2145
|
-
console.log("
|
|
2145
|
+
console.log(" [CONFIG] Analyzing configuration...");
|
|
2146
2146
|
|
|
2147
2147
|
const configAnalyzer = new ConfigAnalyzer(wsRoot);
|
|
2148
2148
|
const config = await configAnalyzer.analyze();
|
|
2149
2149
|
|
|
2150
|
-
console.log(
|
|
2150
|
+
console.log(` [CONFIG] Server port: ${config.server.port}`);
|
|
2151
2151
|
|
|
2152
2152
|
res.json({
|
|
2153
2153
|
ok: true,
|
|
2154
2154
|
...config
|
|
2155
2155
|
});
|
|
2156
2156
|
} catch (err) {
|
|
2157
|
-
console.error("
|
|
2157
|
+
console.error(" [CONFIG] Failed:", err.message);
|
|
2158
2158
|
res.status(500).json({ ok: false, error: err.message });
|
|
2159
2159
|
}
|
|
2160
2160
|
});
|
|
2161
2161
|
|
|
2162
2162
|
// ============================================
|
|
2163
|
-
//
|
|
2163
|
+
// BACKUP & ROLLBACK ENDPOINTS (Sprint 1.3)
|
|
2164
2164
|
// Added without modifying existing endpoints
|
|
2165
2165
|
// ============================================
|
|
2166
2166
|
|
|
@@ -2223,7 +2223,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
|
|
|
2223
2223
|
const { diff, incidentId } = req.body || {};
|
|
2224
2224
|
if (!diff) return res.status(400).json({ error: "diff is required" });
|
|
2225
2225
|
|
|
2226
|
-
console.log(
|
|
2226
|
+
console.log(` Safe patch requested for incident: ${incidentId || 'unknown'}`);
|
|
2227
2227
|
|
|
2228
2228
|
try {
|
|
2229
2229
|
// 1. Validate diff
|
|
@@ -2238,7 +2238,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
|
|
|
2238
2238
|
|
|
2239
2239
|
// 2. Extract target files
|
|
2240
2240
|
const targetFiles = extractTargetFiles(diff);
|
|
2241
|
-
console.log(
|
|
2241
|
+
console.log(` Target files: ${targetFiles.join(', ')}`);
|
|
2242
2242
|
|
|
2243
2243
|
// 3. Create backup
|
|
2244
2244
|
const backupId = `backup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -2269,23 +2269,23 @@ app.post("/workspace/safe-patch", async (req, res) => {
|
|
|
2269
2269
|
}
|
|
2270
2270
|
saveBackupIndex();
|
|
2271
2271
|
} catch (e) {
|
|
2272
|
-
console.warn(
|
|
2272
|
+
console.warn(` Could not persist backup to disk: ${e.message}`);
|
|
2273
2273
|
}
|
|
2274
2274
|
|
|
2275
2275
|
// Cleanup old backups if exceeded max
|
|
2276
2276
|
if (BACKUPS.size > MAX_BACKUPS) {
|
|
2277
2277
|
const oldest = Array.from(BACKUPS.keys())[0];
|
|
2278
2278
|
BACKUPS.delete(oldest);
|
|
2279
|
-
console.log(
|
|
2279
|
+
console.log(` Removed old backup: ${oldest}`);
|
|
2280
2280
|
saveBackupIndex();
|
|
2281
2281
|
}
|
|
2282
2282
|
|
|
2283
|
-
console.log(
|
|
2283
|
+
console.log(` Backup created: ${backupId} (${backupFiles.length} files)`);
|
|
2284
2284
|
|
|
2285
2285
|
// 4. Apply patch
|
|
2286
2286
|
try {
|
|
2287
2287
|
const result = await applyUnifiedDiff(wsRoot, diff);
|
|
2288
|
-
console.log(
|
|
2288
|
+
console.log(` Patch applied successfully: ${result.target}`);
|
|
2289
2289
|
|
|
2290
2290
|
res.json({
|
|
2291
2291
|
ok: true,
|
|
@@ -2296,7 +2296,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
|
|
|
2296
2296
|
});
|
|
2297
2297
|
} catch (patchError) {
|
|
2298
2298
|
// 5. Rollback on failure
|
|
2299
|
-
console.error(
|
|
2299
|
+
console.error(` Patch failed, rolling back: ${patchError.message}`);
|
|
2300
2300
|
|
|
2301
2301
|
for (const file of backupFiles) {
|
|
2302
2302
|
const fullPath = path.join(wsRoot, file.path);
|
|
@@ -2313,7 +2313,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
|
|
|
2313
2313
|
});
|
|
2314
2314
|
}
|
|
2315
2315
|
} catch (err) {
|
|
2316
|
-
console.error(
|
|
2316
|
+
console.error(` Safe patch error: ${err.message}`);
|
|
2317
2317
|
res.status(500).json({ ok: false, error: err.message });
|
|
2318
2318
|
}
|
|
2319
2319
|
});
|
|
@@ -2334,7 +2334,7 @@ app.post("/workspace/patch/dry-run", async (req, res) => {
|
|
|
2334
2334
|
const { diff } = req.body || {};
|
|
2335
2335
|
if (!diff) return res.status(400).json({ error: "diff is required" });
|
|
2336
2336
|
|
|
2337
|
-
console.log(
|
|
2337
|
+
console.log(` Dry-run patch validation requested`);
|
|
2338
2338
|
|
|
2339
2339
|
try {
|
|
2340
2340
|
// 1. Validate diff format
|
|
@@ -2385,7 +2385,7 @@ app.post("/workspace/patch/dry-run", async (req, res) => {
|
|
|
2385
2385
|
const allFilesExist = fileChecks.every(f => f.exists || diff.includes('--- /dev/null'));
|
|
2386
2386
|
const missingFiles = fileChecks.filter(f => !f.exists && !diff.includes('--- /dev/null'));
|
|
2387
2387
|
|
|
2388
|
-
console.log(
|
|
2388
|
+
console.log(` Dry-run complete: ${targetFiles.length} files, ${hunkCount} hunks`);
|
|
2389
2389
|
|
|
2390
2390
|
res.json({
|
|
2391
2391
|
ok: true,
|
|
@@ -2398,7 +2398,7 @@ app.post("/workspace/patch/dry-run", async (req, res) => {
|
|
|
2398
2398
|
warnings: missingFiles.length > 0 ? [`${missingFiles.length} file(s) not found`] : []
|
|
2399
2399
|
});
|
|
2400
2400
|
} catch (err) {
|
|
2401
|
-
console.error(
|
|
2401
|
+
console.error(` Dry-run error: ${err.message}`);
|
|
2402
2402
|
res.status(500).json({ ok: false, error: err.message });
|
|
2403
2403
|
}
|
|
2404
2404
|
});
|
|
@@ -2466,7 +2466,7 @@ app.post("/workspace/backup", async (req, res) => {
|
|
|
2466
2466
|
incidentId: incidentId || null
|
|
2467
2467
|
});
|
|
2468
2468
|
|
|
2469
|
-
console.log(
|
|
2469
|
+
console.log(` Manual backup created: ${backupId}`);
|
|
2470
2470
|
|
|
2471
2471
|
res.json({
|
|
2472
2472
|
ok: true,
|
|
@@ -2475,7 +2475,7 @@ app.post("/workspace/backup", async (req, res) => {
|
|
|
2475
2475
|
timestamp: new Date().toISOString()
|
|
2476
2476
|
});
|
|
2477
2477
|
} catch (err) {
|
|
2478
|
-
console.error(
|
|
2478
|
+
console.error(` Backup error: ${err.message}`);
|
|
2479
2479
|
res.status(500).json({ ok: false, error: err.message });
|
|
2480
2480
|
}
|
|
2481
2481
|
});
|
|
@@ -2507,14 +2507,14 @@ app.post("/workspace/rollback", async (req, res) => {
|
|
|
2507
2507
|
}
|
|
2508
2508
|
|
|
2509
2509
|
try {
|
|
2510
|
-
console.log(
|
|
2510
|
+
console.log(` Rolling back to backup: ${backupId}`);
|
|
2511
2511
|
|
|
2512
2512
|
for (const file of backup.files) {
|
|
2513
2513
|
const fullPath = path.join(wsRoot, file.path);
|
|
2514
2514
|
await writeFile(fullPath, file.content, 'utf8');
|
|
2515
2515
|
}
|
|
2516
2516
|
|
|
2517
|
-
console.log(
|
|
2517
|
+
console.log(` Rollback completed: ${backup.files.length} files restored`);
|
|
2518
2518
|
|
|
2519
2519
|
res.json({
|
|
2520
2520
|
ok: true,
|
|
@@ -2523,7 +2523,7 @@ app.post("/workspace/rollback", async (req, res) => {
|
|
|
2523
2523
|
timestamp: backup.timestamp
|
|
2524
2524
|
});
|
|
2525
2525
|
} catch (err) {
|
|
2526
|
-
console.error(
|
|
2526
|
+
console.error(` Rollback error: ${err.message}`);
|
|
2527
2527
|
res.status(500).json({ ok: false, error: err.message });
|
|
2528
2528
|
}
|
|
2529
2529
|
});
|
|
@@ -2563,7 +2563,7 @@ app.delete("/workspace/backups/:backupId", (req, res) => {
|
|
|
2563
2563
|
}
|
|
2564
2564
|
|
|
2565
2565
|
BACKUPS.delete(backupId);
|
|
2566
|
-
console.log(
|
|
2566
|
+
console.log(` Backup deleted: ${backupId}`);
|
|
2567
2567
|
|
|
2568
2568
|
res.json({
|
|
2569
2569
|
ok: true,
|
|
@@ -2572,7 +2572,7 @@ app.delete("/workspace/backups/:backupId", (req, res) => {
|
|
|
2572
2572
|
});
|
|
2573
2573
|
|
|
2574
2574
|
// ============================================
|
|
2575
|
-
//
|
|
2575
|
+
// DIFF VIEWER ENDPOINT
|
|
2576
2576
|
// Returns before/after content for files modified by a patch
|
|
2577
2577
|
// Used by the frontend diff viewer
|
|
2578
2578
|
// ============================================
|
|
@@ -2622,7 +2622,7 @@ app.get("/workspace/diff/by-incident/:incidentId", async (req, res) => {
|
|
|
2622
2622
|
}
|
|
2623
2623
|
|
|
2624
2624
|
if (!matchedBackup) {
|
|
2625
|
-
console.log(
|
|
2625
|
+
console.log(` No backup found for incident ${incidentId}. Available backups:`, Array.from(BACKUPS.keys()));
|
|
2626
2626
|
return res.status(404).json({
|
|
2627
2627
|
ok: false,
|
|
2628
2628
|
error: "No backup found for this incident",
|
|
@@ -2631,7 +2631,7 @@ app.get("/workspace/diff/by-incident/:incidentId", async (req, res) => {
|
|
|
2631
2631
|
});
|
|
2632
2632
|
}
|
|
2633
2633
|
|
|
2634
|
-
console.log(
|
|
2634
|
+
console.log(` Found backup ${matchedBackupId} for incident ${incidentId}`);
|
|
2635
2635
|
|
|
2636
2636
|
// Reuse the diff logic
|
|
2637
2637
|
try {
|
|
@@ -2668,7 +2668,7 @@ app.get("/workspace/diff/by-incident/:incidentId", async (req, res) => {
|
|
|
2668
2668
|
files: diffs
|
|
2669
2669
|
});
|
|
2670
2670
|
} catch (err) {
|
|
2671
|
-
console.error(
|
|
2671
|
+
console.error(` Diff error: ${err.message}`);
|
|
2672
2672
|
res.status(500).json({ ok: false, error: err.message });
|
|
2673
2673
|
}
|
|
2674
2674
|
});
|
|
@@ -2740,7 +2740,7 @@ app.get("/workspace/diff/:backupId", async (req, res) => {
|
|
|
2740
2740
|
}
|
|
2741
2741
|
}
|
|
2742
2742
|
|
|
2743
|
-
console.log(
|
|
2743
|
+
console.log(` Diff for ${backupId}: ${diffs.length} file(s) changed`);
|
|
2744
2744
|
|
|
2745
2745
|
res.json({
|
|
2746
2746
|
ok: true,
|
|
@@ -2752,13 +2752,13 @@ app.get("/workspace/diff/:backupId", async (req, res) => {
|
|
|
2752
2752
|
files: diffs
|
|
2753
2753
|
});
|
|
2754
2754
|
} catch (err) {
|
|
2755
|
-
console.error(
|
|
2755
|
+
console.error(` Diff error: ${err.message}`);
|
|
2756
2756
|
res.status(500).json({ ok: false, error: err.message });
|
|
2757
2757
|
}
|
|
2758
2758
|
});
|
|
2759
2759
|
|
|
2760
2760
|
// ============================================
|
|
2761
|
-
//
|
|
2761
|
+
// DETECT PORT ENDPOINT (Sprint 1.2)
|
|
2762
2762
|
// Multi-language port detection
|
|
2763
2763
|
// ============================================
|
|
2764
2764
|
|
|
@@ -2785,7 +2785,7 @@ app.get("/workspace/detect-port", async (req, res) => {
|
|
|
2785
2785
|
|
|
2786
2786
|
try {
|
|
2787
2787
|
const fullPath = path.join(wsRoot, servicePath);
|
|
2788
|
-
console.log(
|
|
2788
|
+
console.log(` Detecting port for service at: ${fullPath}`);
|
|
2789
2789
|
|
|
2790
2790
|
let port = null;
|
|
2791
2791
|
let detectionMethod = null;
|
|
@@ -2820,7 +2820,7 @@ app.get("/workspace/detect-port", async (req, res) => {
|
|
|
2820
2820
|
detectionMethod = 'global-detection';
|
|
2821
2821
|
}
|
|
2822
2822
|
|
|
2823
|
-
console.log(
|
|
2823
|
+
console.log(` Detected port: ${port || 'default'} via ${detectionMethod}`);
|
|
2824
2824
|
|
|
2825
2825
|
res.json({
|
|
2826
2826
|
ok: true,
|
|
@@ -2831,7 +2831,7 @@ app.get("/workspace/detect-port", async (req, res) => {
|
|
|
2831
2831
|
servicePath: servicePath || '/'
|
|
2832
2832
|
});
|
|
2833
2833
|
} catch (err) {
|
|
2834
|
-
console.error('
|
|
2834
|
+
console.error(' Error detecting port:', err.message);
|
|
2835
2835
|
res.status(500).json({ ok: false, error: err.message });
|
|
2836
2836
|
}
|
|
2837
2837
|
});
|
|
@@ -3042,7 +3042,7 @@ app.post("/workspace/test-local/prepare", async (req, res) => {
|
|
|
3042
3042
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
3043
3043
|
|
|
3044
3044
|
try {
|
|
3045
|
-
console.log("
|
|
3045
|
+
console.log(" [TEST-LOCAL] Preparing test environment...");
|
|
3046
3046
|
TEST_LOCAL_STATE.status = "compiling";
|
|
3047
3047
|
|
|
3048
3048
|
// Step 1: Compile
|
|
@@ -3082,7 +3082,7 @@ app.post("/workspace/test-local/prepare", async (req, res) => {
|
|
|
3082
3082
|
const config = await configAnalyzer.analyze();
|
|
3083
3083
|
TEST_LOCAL_STATE.config = config;
|
|
3084
3084
|
|
|
3085
|
-
console.log(
|
|
3085
|
+
console.log(` [TEST-LOCAL] Prepared: ${payloadDocs.endpoints.length} endpoints discovered`);
|
|
3086
3086
|
|
|
3087
3087
|
res.json({
|
|
3088
3088
|
ok: true,
|
|
@@ -3105,7 +3105,7 @@ app.post("/workspace/test-local/prepare", async (req, res) => {
|
|
|
3105
3105
|
endpoints: payloadDocs.endpoints
|
|
3106
3106
|
});
|
|
3107
3107
|
} catch (err) {
|
|
3108
|
-
console.error("
|
|
3108
|
+
console.error(" [TEST-LOCAL] Prepare failed:", err.message);
|
|
3109
3109
|
TEST_LOCAL_STATE.status = "error";
|
|
3110
3110
|
res.status(500).json({ ok: false, error: err.message });
|
|
3111
3111
|
}
|
|
@@ -3288,7 +3288,7 @@ app.get("/workspace/test-local/logs/stream", async (req, res) => {
|
|
|
3288
3288
|
});
|
|
3289
3289
|
|
|
3290
3290
|
// ============================================
|
|
3291
|
-
//
|
|
3291
|
+
// AUTO-TRAINING ENDPOINTS
|
|
3292
3292
|
// Escanear e ler arquivos para treinamento AI
|
|
3293
3293
|
// ============================================
|
|
3294
3294
|
|
|
@@ -3307,7 +3307,7 @@ app.post("/workspace/scan-files", async (req, res) => {
|
|
|
3307
3307
|
});
|
|
3308
3308
|
}
|
|
3309
3309
|
|
|
3310
|
-
console.log(
|
|
3310
|
+
console.log(` [SCAN-FILES] Scanning: ${workspaceRoot}`);
|
|
3311
3311
|
console.log(` Extensions: ${includeExtensions.join(", ")}`);
|
|
3312
3312
|
console.log(` Exclude: ${excludePatterns.join(", ")}`);
|
|
3313
3313
|
|
|
@@ -3316,7 +3316,7 @@ app.post("/workspace/scan-files", async (req, res) => {
|
|
|
3316
3316
|
const fs = await import("fs/promises");
|
|
3317
3317
|
const pathModule = await import("path");
|
|
3318
3318
|
|
|
3319
|
-
//
|
|
3319
|
+
// Padres padro para excluir
|
|
3320
3320
|
const defaultExcludes = [
|
|
3321
3321
|
"node_modules", "target", "build", "dist", ".git", ".idea",
|
|
3322
3322
|
".vscode", "__pycache__", ".gradle", "bin", "obj",
|
|
@@ -3324,7 +3324,7 @@ app.post("/workspace/scan-files", async (req, res) => {
|
|
|
3324
3324
|
];
|
|
3325
3325
|
const allExcludes = [...defaultExcludes, ...excludePatterns];
|
|
3326
3326
|
|
|
3327
|
-
//
|
|
3327
|
+
// Funo recursiva para escanear
|
|
3328
3328
|
async function scanDir(dir, depth = 0) {
|
|
3329
3329
|
if (depth > maxDepth || files.length >= maxFiles) return;
|
|
3330
3330
|
|
|
@@ -3351,7 +3351,7 @@ app.post("/workspace/scan-files", async (req, res) => {
|
|
|
3351
3351
|
if (entry.isDirectory()) {
|
|
3352
3352
|
await scanDir(fullPath, depth + 1);
|
|
3353
3353
|
} else if (entry.isFile()) {
|
|
3354
|
-
// Verificar
|
|
3354
|
+
// Verificar extenso
|
|
3355
3355
|
const ext = pathModule.extname(entry.name).toLowerCase();
|
|
3356
3356
|
if (includeExtensions.length === 0 || includeExtensions.includes(ext)) {
|
|
3357
3357
|
files.push(relativePath);
|
|
@@ -3359,13 +3359,13 @@ app.post("/workspace/scan-files", async (req, res) => {
|
|
|
3359
3359
|
}
|
|
3360
3360
|
}
|
|
3361
3361
|
} catch (error) {
|
|
3362
|
-
console.warn(
|
|
3362
|
+
console.warn(` Cannot read directory ${dir}: ${error.message}`);
|
|
3363
3363
|
}
|
|
3364
3364
|
}
|
|
3365
3365
|
|
|
3366
3366
|
await scanDir(workspaceRoot);
|
|
3367
3367
|
|
|
3368
|
-
console.log(
|
|
3368
|
+
console.log(` [SCAN-FILES] Found ${files.length} files`);
|
|
3369
3369
|
|
|
3370
3370
|
res.json({
|
|
3371
3371
|
success: true,
|
|
@@ -3375,7 +3375,7 @@ app.post("/workspace/scan-files", async (req, res) => {
|
|
|
3375
3375
|
});
|
|
3376
3376
|
|
|
3377
3377
|
} catch (error) {
|
|
3378
|
-
console.error(
|
|
3378
|
+
console.error(` [SCAN-FILES] Error: ${error.message}`);
|
|
3379
3379
|
res.status(500).json({
|
|
3380
3380
|
success: false,
|
|
3381
3381
|
error: error.message,
|
|
@@ -3386,7 +3386,7 @@ app.post("/workspace/scan-files", async (req, res) => {
|
|
|
3386
3386
|
|
|
3387
3387
|
/**
|
|
3388
3388
|
* POST /workspace/read-file
|
|
3389
|
-
*
|
|
3389
|
+
* L contedo de um arquivo especfico
|
|
3390
3390
|
*/
|
|
3391
3391
|
app.post("/workspace/read-file", async (req, res) => {
|
|
3392
3392
|
const { root, filePath } = req.body;
|
|
@@ -3404,7 +3404,7 @@ app.post("/workspace/read-file", async (req, res) => {
|
|
|
3404
3404
|
});
|
|
3405
3405
|
}
|
|
3406
3406
|
|
|
3407
|
-
console.log(
|
|
3407
|
+
console.log(` [READ-FILE] Reading: ${filePath}`);
|
|
3408
3408
|
|
|
3409
3409
|
try {
|
|
3410
3410
|
const fs = await import("fs/promises");
|
|
@@ -3435,7 +3435,7 @@ app.post("/workspace/read-file", async (req, res) => {
|
|
|
3435
3435
|
});
|
|
3436
3436
|
|
|
3437
3437
|
} catch (error) {
|
|
3438
|
-
console.error(
|
|
3438
|
+
console.error(` [READ-FILE] Error: ${error.message}`);
|
|
3439
3439
|
res.status(404).json({
|
|
3440
3440
|
success: false,
|
|
3441
3441
|
error: error.message,
|
|
@@ -3446,7 +3446,7 @@ app.post("/workspace/read-file", async (req, res) => {
|
|
|
3446
3446
|
|
|
3447
3447
|
/**
|
|
3448
3448
|
* POST /workspace/read-files
|
|
3449
|
-
*
|
|
3449
|
+
* L mltiplos arquivos de uma vez (batch)
|
|
3450
3450
|
*/
|
|
3451
3451
|
app.post("/workspace/read-files", async (req, res) => {
|
|
3452
3452
|
const { root, filePaths } = req.body;
|
|
@@ -3464,7 +3464,7 @@ app.post("/workspace/read-files", async (req, res) => {
|
|
|
3464
3464
|
});
|
|
3465
3465
|
}
|
|
3466
3466
|
|
|
3467
|
-
console.log(
|
|
3467
|
+
console.log(` [READ-FILES] Reading ${filePaths.length} files`);
|
|
3468
3468
|
|
|
3469
3469
|
try {
|
|
3470
3470
|
const fs = await import("fs/promises");
|
|
@@ -3491,7 +3491,7 @@ app.post("/workspace/read-files", async (req, res) => {
|
|
|
3491
3491
|
}
|
|
3492
3492
|
}
|
|
3493
3493
|
|
|
3494
|
-
console.log(
|
|
3494
|
+
console.log(` [READ-FILES] Read ${Object.keys(files).length} files, ${errors.length} errors`);
|
|
3495
3495
|
|
|
3496
3496
|
res.json({
|
|
3497
3497
|
success: true,
|
|
@@ -3501,7 +3501,7 @@ app.post("/workspace/read-files", async (req, res) => {
|
|
|
3501
3501
|
});
|
|
3502
3502
|
|
|
3503
3503
|
} catch (error) {
|
|
3504
|
-
console.error(
|
|
3504
|
+
console.error(` [READ-FILES] Error: ${error.message}`);
|
|
3505
3505
|
res.status(500).json({
|
|
3506
3506
|
success: false,
|
|
3507
3507
|
error: error.message
|
|
@@ -3510,7 +3510,7 @@ app.post("/workspace/read-files", async (req, res) => {
|
|
|
3510
3510
|
});
|
|
3511
3511
|
|
|
3512
3512
|
// ============================================
|
|
3513
|
-
//
|
|
3513
|
+
// SYSTEM FOLDER PICKER ENDPOINT
|
|
3514
3514
|
// Abre file picker nativo do SO (Windows/Mac/Linux)
|
|
3515
3515
|
// ============================================
|
|
3516
3516
|
|
|
@@ -3519,7 +3519,7 @@ app.post("/workspace/read-files", async (req, res) => {
|
|
|
3519
3519
|
* Abre o file picker nativo do sistema operacional
|
|
3520
3520
|
*/
|
|
3521
3521
|
app.get("/system/folder-picker", async (req, res) => {
|
|
3522
|
-
console.log(
|
|
3522
|
+
console.log(` [FOLDER-PICKER] Opening native folder picker...`);
|
|
3523
3523
|
|
|
3524
3524
|
try {
|
|
3525
3525
|
const platform = process.platform;
|
|
@@ -3532,7 +3532,7 @@ app.get("/system/folder-picker", async (req, res) => {
|
|
|
3532
3532
|
const { stdout } = await execAsync(script);
|
|
3533
3533
|
selectedPath = stdout.trim();
|
|
3534
3534
|
} catch (error) {
|
|
3535
|
-
//
|
|
3535
|
+
// Usurio cancelou
|
|
3536
3536
|
if (error.code === 1) {
|
|
3537
3537
|
return res.json({
|
|
3538
3538
|
success: true,
|
|
@@ -3602,7 +3602,7 @@ app.get("/system/folder-picker", async (req, res) => {
|
|
|
3602
3602
|
selectedPath = selectedPath.slice(0, -1);
|
|
3603
3603
|
}
|
|
3604
3604
|
|
|
3605
|
-
console.log(
|
|
3605
|
+
console.log(` [FOLDER-PICKER] Selected: ${selectedPath}`);
|
|
3606
3606
|
|
|
3607
3607
|
res.json({
|
|
3608
3608
|
success: true,
|
|
@@ -3611,7 +3611,7 @@ app.get("/system/folder-picker", async (req, res) => {
|
|
|
3611
3611
|
});
|
|
3612
3612
|
|
|
3613
3613
|
} catch (error) {
|
|
3614
|
-
console.error(
|
|
3614
|
+
console.error(` [FOLDER-PICKER] Error:`, error.message);
|
|
3615
3615
|
res.status(500).json({
|
|
3616
3616
|
success: false,
|
|
3617
3617
|
error: error.message,
|
|
@@ -3623,7 +3623,7 @@ app.get("/system/folder-picker", async (req, res) => {
|
|
|
3623
3623
|
|
|
3624
3624
|
/**
|
|
3625
3625
|
* GET /system/info
|
|
3626
|
-
* Retorna
|
|
3626
|
+
* Retorna informaes do sistema
|
|
3627
3627
|
*/
|
|
3628
3628
|
app.get("/system/info", (req, res) => {
|
|
3629
3629
|
res.json({
|
|
@@ -3636,7 +3636,7 @@ app.get("/system/info", (req, res) => {
|
|
|
3636
3636
|
});
|
|
3637
3637
|
|
|
3638
3638
|
// ============================================
|
|
3639
|
-
//
|
|
3639
|
+
// API DOCS ENDPOINT
|
|
3640
3640
|
// Retorna endpoints detectados das controllers
|
|
3641
3641
|
// ============================================
|
|
3642
3642
|
|
|
@@ -3653,7 +3653,7 @@ app.get("/workspace/api-docs", async (req, res) => {
|
|
|
3653
3653
|
});
|
|
3654
3654
|
}
|
|
3655
3655
|
|
|
3656
|
-
console.log(
|
|
3656
|
+
console.log(` [API-DOCS] Analyzing controllers in ${wsRoot}`);
|
|
3657
3657
|
|
|
3658
3658
|
try {
|
|
3659
3659
|
// Primeiro, escanear arquivos do workspace
|
|
@@ -3692,13 +3692,13 @@ app.get("/workspace/api-docs", async (req, res) => {
|
|
|
3692
3692
|
|
|
3693
3693
|
await scanDir(wsRoot);
|
|
3694
3694
|
|
|
3695
|
-
console.log(
|
|
3695
|
+
console.log(` [API-DOCS] Found ${files.length} Java files`);
|
|
3696
3696
|
|
|
3697
3697
|
// Agora analisar os controllers
|
|
3698
3698
|
const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
|
|
3699
3699
|
const apiDocs = await controllerAnalyzer.generateApiDocs(files);
|
|
3700
3700
|
|
|
3701
|
-
console.log(
|
|
3701
|
+
console.log(` [API-DOCS] Found ${apiDocs.totalEndpoints} endpoints in ${apiDocs.totalControllers} controllers`);
|
|
3702
3702
|
|
|
3703
3703
|
res.json({
|
|
3704
3704
|
success: true,
|
|
@@ -3710,7 +3710,7 @@ app.get("/workspace/api-docs", async (req, res) => {
|
|
|
3710
3710
|
});
|
|
3711
3711
|
|
|
3712
3712
|
} catch (error) {
|
|
3713
|
-
console.error(
|
|
3713
|
+
console.error(` [API-DOCS] Error:`, error.message);
|
|
3714
3714
|
|
|
3715
3715
|
res.json({
|
|
3716
3716
|
success: false,
|
|
@@ -3725,7 +3725,7 @@ app.get("/workspace/api-docs", async (req, res) => {
|
|
|
3725
3725
|
|
|
3726
3726
|
/**
|
|
3727
3727
|
* GET /workspace/api-docs/:controller
|
|
3728
|
-
* Retorna endpoints de uma controller
|
|
3728
|
+
* Retorna endpoints de uma controller especfica
|
|
3729
3729
|
*/
|
|
3730
3730
|
app.get("/workspace/api-docs/:controller", async (req, res) => {
|
|
3731
3731
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
@@ -3737,7 +3737,7 @@ app.get("/workspace/api-docs/:controller", async (req, res) => {
|
|
|
3737
3737
|
}
|
|
3738
3738
|
|
|
3739
3739
|
const { controller } = req.params;
|
|
3740
|
-
console.log(
|
|
3740
|
+
console.log(` [API-DOCS] Getting endpoints for controller: ${controller}`);
|
|
3741
3741
|
|
|
3742
3742
|
try {
|
|
3743
3743
|
// Primeiro, escanear arquivos do workspace
|
|
@@ -3812,7 +3812,7 @@ app.get("/workspace/api-docs/:controller", async (req, res) => {
|
|
|
3812
3812
|
});
|
|
3813
3813
|
|
|
3814
3814
|
} catch (error) {
|
|
3815
|
-
console.error(
|
|
3815
|
+
console.error(` [API-DOCS] Error:`, error.message);
|
|
3816
3816
|
res.status(500).json({
|
|
3817
3817
|
success: false,
|
|
3818
3818
|
error: error.message
|
|
@@ -3821,7 +3821,7 @@ app.get("/workspace/api-docs/:controller", async (req, res) => {
|
|
|
3821
3821
|
});
|
|
3822
3822
|
|
|
3823
3823
|
// ============================================
|
|
3824
|
-
//
|
|
3824
|
+
// AI ENGINE ENDPOINTS
|
|
3825
3825
|
// ============================================
|
|
3826
3826
|
|
|
3827
3827
|
/**
|
|
@@ -3848,7 +3848,7 @@ app.post("/ai-engine/toggle", (req, res) => {
|
|
|
3848
3848
|
|
|
3849
3849
|
/**
|
|
3850
3850
|
* POST /ai-engine/clear-history
|
|
3851
|
-
* Limpa
|
|
3851
|
+
* Limpa histrico de erros e fixes
|
|
3852
3852
|
*/
|
|
3853
3853
|
app.post("/ai-engine/clear-history", (req, res) => {
|
|
3854
3854
|
if (aiEngine) {
|
|
@@ -3861,7 +3861,7 @@ app.post("/ai-engine/clear-history", (req, res) => {
|
|
|
3861
3861
|
|
|
3862
3862
|
/**
|
|
3863
3863
|
* GET /ai-engine/errors
|
|
3864
|
-
* Retorna
|
|
3864
|
+
* Retorna histrico de erros
|
|
3865
3865
|
*/
|
|
3866
3866
|
app.get("/ai-engine/errors", (req, res) => {
|
|
3867
3867
|
const { limit = 50 } = req.query;
|
|
@@ -3878,7 +3878,7 @@ app.get("/ai-engine/errors", (req, res) => {
|
|
|
3878
3878
|
|
|
3879
3879
|
/**
|
|
3880
3880
|
* GET /ai-engine/fixes
|
|
3881
|
-
* Retorna
|
|
3881
|
+
* Retorna histrico de fixes
|
|
3882
3882
|
*/
|
|
3883
3883
|
app.get("/ai-engine/fixes", (req, res) => {
|
|
3884
3884
|
if (aiEngine) {
|
|
@@ -3894,7 +3894,7 @@ app.get("/ai-engine/fixes", (req, res) => {
|
|
|
3894
3894
|
|
|
3895
3895
|
/**
|
|
3896
3896
|
* GET /workspace/smart-config
|
|
3897
|
-
* Recolhe ficheiros de
|
|
3897
|
+
* Recolhe ficheiros de configurao para anlise AI
|
|
3898
3898
|
*/
|
|
3899
3899
|
app.get("/workspace/smart-config", async (req, res) => {
|
|
3900
3900
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
@@ -3902,7 +3902,7 @@ app.get("/workspace/smart-config", async (req, res) => {
|
|
|
3902
3902
|
return res.status(400).json({ error: "workspace not set" });
|
|
3903
3903
|
}
|
|
3904
3904
|
|
|
3905
|
-
console.log("
|
|
3905
|
+
console.log(" [SMART-CONFIG] Collecting configuration files...");
|
|
3906
3906
|
|
|
3907
3907
|
try {
|
|
3908
3908
|
const configPatterns = [
|
|
@@ -3936,14 +3936,14 @@ app.get("/workspace/smart-config", async (req, res) => {
|
|
|
3936
3936
|
: content,
|
|
3937
3937
|
size: content.length
|
|
3938
3938
|
});
|
|
3939
|
-
console.log(
|
|
3939
|
+
console.log(` Found: ${pattern}`);
|
|
3940
3940
|
} catch (err) {
|
|
3941
|
-
console.error(
|
|
3941
|
+
console.error(` Error reading ${pattern}: ${err.message}`);
|
|
3942
3942
|
}
|
|
3943
3943
|
}
|
|
3944
3944
|
}
|
|
3945
3945
|
|
|
3946
|
-
// Detectar profiles
|
|
3946
|
+
// Detectar profiles disponveis
|
|
3947
3947
|
const availableProfiles = [];
|
|
3948
3948
|
const resourcesDir = path.join(wsRoot, 'src/main/resources');
|
|
3949
3949
|
if (fs.existsSync(resourcesDir)) {
|
|
@@ -3954,7 +3954,7 @@ app.get("/workspace/smart-config", async (req, res) => {
|
|
|
3954
3954
|
});
|
|
3955
3955
|
}
|
|
3956
3956
|
|
|
3957
|
-
console.log(
|
|
3957
|
+
console.log(` Collected ${collectedFiles.length} files, profiles: ${availableProfiles.join(', ')}`);
|
|
3958
3958
|
|
|
3959
3959
|
res.json({
|
|
3960
3960
|
ok: true,
|
|
@@ -3968,7 +3968,7 @@ app.get("/workspace/smart-config", async (req, res) => {
|
|
|
3968
3968
|
});
|
|
3969
3969
|
|
|
3970
3970
|
} catch (err) {
|
|
3971
|
-
console.error("
|
|
3971
|
+
console.error(" [SMART-CONFIG] Error:", err.message);
|
|
3972
3972
|
res.status(500).json({ ok: false, error: err.message });
|
|
3973
3973
|
}
|
|
3974
3974
|
});
|
|
@@ -3979,7 +3979,7 @@ app.get("/workspace/smart-config", async (req, res) => {
|
|
|
3979
3979
|
*/
|
|
3980
3980
|
app.post("/workspace/test-local/restart", async (req, res) => {
|
|
3981
3981
|
try {
|
|
3982
|
-
console.log("
|
|
3982
|
+
console.log(" [TEST-LOCAL] Restarting server...");
|
|
3983
3983
|
|
|
3984
3984
|
// Stop
|
|
3985
3985
|
await processManager.stop('test-local');
|
|
@@ -3987,7 +3987,7 @@ app.post("/workspace/test-local/restart", async (req, res) => {
|
|
|
3987
3987
|
// Esperar um pouco
|
|
3988
3988
|
await new Promise(r => setTimeout(r, 2000));
|
|
3989
3989
|
|
|
3990
|
-
// Usar
|
|
3990
|
+
// Usar ltima config que funcionou
|
|
3991
3991
|
const config = (aiEngine && aiEngine.lastSuccessfulConfig) || TEST_LOCAL_STATE.config;
|
|
3992
3992
|
|
|
3993
3993
|
if (!config) {
|
|
@@ -4024,13 +4024,13 @@ app.post("/workspace/test-local/restart", async (req, res) => {
|
|
|
4024
4024
|
}
|
|
4025
4025
|
|
|
4026
4026
|
} catch (err) {
|
|
4027
|
-
console.error("
|
|
4027
|
+
console.error(" [TEST-LOCAL] Restart failed:", err.message);
|
|
4028
4028
|
res.status(500).json({ ok: false, error: err.message });
|
|
4029
4029
|
}
|
|
4030
4030
|
});
|
|
4031
4031
|
|
|
4032
4032
|
// ============================================
|
|
4033
|
-
//
|
|
4033
|
+
// AGENTIC TOOLS ENDPOINTS
|
|
4034
4034
|
// Used by the agentic Claude loop for autonomous debugging
|
|
4035
4035
|
// ============================================
|
|
4036
4036
|
|
|
@@ -4058,7 +4058,7 @@ app.post("/workspace/search", async (req, res) => {
|
|
|
4058
4058
|
return res.status(400).json({ ok: false, error: "pattern is required" });
|
|
4059
4059
|
}
|
|
4060
4060
|
|
|
4061
|
-
console.log(
|
|
4061
|
+
console.log(` [workspace/search] pattern="${pattern}" filter="${fileFilter || '*'}"`);
|
|
4062
4062
|
|
|
4063
4063
|
try {
|
|
4064
4064
|
const { execSync } = await import('child_process');
|
|
@@ -4116,7 +4116,7 @@ app.post("/workspace/search", async (req, res) => {
|
|
|
4116
4116
|
.join('\n');
|
|
4117
4117
|
|
|
4118
4118
|
const count = results.split('\n').filter(l => l.trim()).length;
|
|
4119
|
-
console.log(`
|
|
4119
|
+
console.log(` Found ${count} matches`);
|
|
4120
4120
|
|
|
4121
4121
|
res.json({
|
|
4122
4122
|
ok: true,
|
|
@@ -4124,7 +4124,7 @@ app.post("/workspace/search", async (req, res) => {
|
|
|
4124
4124
|
count: count
|
|
4125
4125
|
});
|
|
4126
4126
|
} catch (err) {
|
|
4127
|
-
console.error(
|
|
4127
|
+
console.error(` [workspace/search] Error:`, err.message);
|
|
4128
4128
|
res.status(500).json({ ok: false, error: err.message });
|
|
4129
4129
|
}
|
|
4130
4130
|
});
|
|
@@ -4164,14 +4164,14 @@ app.post("/workspace/exec", async (req, res) => {
|
|
|
4164
4164
|
];
|
|
4165
4165
|
const lowerCmd = command.toLowerCase().trim();
|
|
4166
4166
|
if (dangerous.some(d => lowerCmd.includes(d))) {
|
|
4167
|
-
console.warn(
|
|
4167
|
+
console.warn(` [workspace/exec] BLOCKED dangerous command: ${command}`);
|
|
4168
4168
|
return res.status(403).json({
|
|
4169
4169
|
ok: false,
|
|
4170
4170
|
error: "Command blocked for security reasons"
|
|
4171
4171
|
});
|
|
4172
4172
|
}
|
|
4173
4173
|
|
|
4174
|
-
console.log(
|
|
4174
|
+
console.log(` [workspace/exec] Running: ${command.substring(0, 120)}...`);
|
|
4175
4175
|
|
|
4176
4176
|
try {
|
|
4177
4177
|
const { exec: execCb } = await import('child_process');
|
|
@@ -4185,7 +4185,7 @@ app.post("/workspace/exec", async (req, res) => {
|
|
|
4185
4185
|
env: { ...process.env, FORCE_COLOR: '0' }
|
|
4186
4186
|
});
|
|
4187
4187
|
|
|
4188
|
-
console.log(`
|
|
4188
|
+
console.log(` Command completed (stdout: ${result.stdout.length} chars)`);
|
|
4189
4189
|
|
|
4190
4190
|
res.json({
|
|
4191
4191
|
ok: true,
|
|
@@ -4194,7 +4194,7 @@ app.post("/workspace/exec", async (req, res) => {
|
|
|
4194
4194
|
exitCode: 0
|
|
4195
4195
|
});
|
|
4196
4196
|
} catch (err) {
|
|
4197
|
-
console.error(`
|
|
4197
|
+
console.error(` Command failed (exit: ${err.code}):`, err.message?.substring(0, 200));
|
|
4198
4198
|
res.json({
|
|
4199
4199
|
ok: false,
|
|
4200
4200
|
stdout: err.stdout || '',
|
|
@@ -4205,7 +4205,7 @@ app.post("/workspace/exec", async (req, res) => {
|
|
|
4205
4205
|
});
|
|
4206
4206
|
|
|
4207
4207
|
// ============================================
|
|
4208
|
-
//
|
|
4208
|
+
// ENDPOINT TESTING
|
|
4209
4209
|
// Execute curl-like requests and capture full request/response
|
|
4210
4210
|
// ============================================
|
|
4211
4211
|
|
|
@@ -4227,7 +4227,7 @@ app.post("/workspace/test-endpoint", async (req, res) => {
|
|
|
4227
4227
|
return res.status(403).json({ error: "Only localhost URLs allowed for security" });
|
|
4228
4228
|
}
|
|
4229
4229
|
|
|
4230
|
-
console.log(
|
|
4230
|
+
console.log(` Testing: ${method} ${url} (${testName || 'unnamed'})`);
|
|
4231
4231
|
|
|
4232
4232
|
const startTime = Date.now();
|
|
4233
4233
|
|
|
@@ -4277,12 +4277,12 @@ app.post("/workspace/test-endpoint", async (req, res) => {
|
|
|
4277
4277
|
: response.status >= 200 && response.status < 400
|
|
4278
4278
|
};
|
|
4279
4279
|
|
|
4280
|
-
console.log(`
|
|
4280
|
+
console.log(` ${response.status} ${response.statusText} (${durationMs}ms) ${result.passed ? '' : ''}`);
|
|
4281
4281
|
res.json(result);
|
|
4282
4282
|
|
|
4283
4283
|
} catch (err) {
|
|
4284
4284
|
const durationMs = Date.now() - startTime;
|
|
4285
|
-
console.error(`
|
|
4285
|
+
console.error(` Failed: ${err.message} (${durationMs}ms)`);
|
|
4286
4286
|
|
|
4287
4287
|
res.json({
|
|
4288
4288
|
ok: false,
|
|
@@ -4299,7 +4299,7 @@ app.post("/workspace/test-endpoint", async (req, res) => {
|
|
|
4299
4299
|
});
|
|
4300
4300
|
|
|
4301
4301
|
// ============================================
|
|
4302
|
-
//
|
|
4302
|
+
// GIT INTEGRATION
|
|
4303
4303
|
// Create branch, commit, push for auto-fix PRs
|
|
4304
4304
|
// ============================================
|
|
4305
4305
|
|
|
@@ -4353,7 +4353,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4353
4353
|
try {
|
|
4354
4354
|
const stashResult = execSync('git stash --include-untracked 2>&1', opts).trim();
|
|
4355
4355
|
hadStash = !stashResult.includes('No local changes');
|
|
4356
|
-
if (hadStash) console.log('
|
|
4356
|
+
if (hadStash) console.log(' Stashed uncommitted changes');
|
|
4357
4357
|
} catch {}
|
|
4358
4358
|
|
|
4359
4359
|
// 4. Create and checkout new branch (with unique name if exists)
|
|
@@ -4361,7 +4361,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4361
4361
|
try {
|
|
4362
4362
|
execSync(`git checkout -b ${finalBranchName}`, opts);
|
|
4363
4363
|
} catch (e) {
|
|
4364
|
-
// Branch already exists
|
|
4364
|
+
// Branch already exists add timestamp suffix
|
|
4365
4365
|
finalBranchName = `${branchName}-${Date.now() % 100000}`;
|
|
4366
4366
|
try {
|
|
4367
4367
|
execSync(`git checkout -b ${finalBranchName}`, opts);
|
|
@@ -4377,7 +4377,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4377
4377
|
try {
|
|
4378
4378
|
execSync('git stash pop', opts);
|
|
4379
4379
|
} catch (e) {
|
|
4380
|
-
console.warn('
|
|
4380
|
+
console.warn(' Stash pop had conflicts, trying apply:', e.message);
|
|
4381
4381
|
try { execSync('git stash apply', opts); } catch {}
|
|
4382
4382
|
}
|
|
4383
4383
|
}
|
|
@@ -4406,7 +4406,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4406
4406
|
let pushResult = '';
|
|
4407
4407
|
let pushed = false;
|
|
4408
4408
|
|
|
4409
|
-
console.log(
|
|
4409
|
+
console.log(` Git push gitToken provided: ${!!gitToken}, repoUrl provided: ${!!repoUrl}`);
|
|
4410
4410
|
|
|
4411
4411
|
// If gitToken provided, set up authenticated remote URL for push
|
|
4412
4412
|
if (gitToken) {
|
|
@@ -4415,14 +4415,14 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4415
4415
|
try {
|
|
4416
4416
|
remoteUrlRaw = execSync('git remote get-url origin 2>/dev/null', opts).trim();
|
|
4417
4417
|
} catch {
|
|
4418
|
-
// No remote configured
|
|
4418
|
+
// No remote configured use repoUrl from integration if available
|
|
4419
4419
|
if (repoUrl) {
|
|
4420
4420
|
remoteUrlRaw = repoUrl;
|
|
4421
|
-
console.log(
|
|
4421
|
+
console.log(` No git remote, using repoUrl from integration: ${repoUrl}`);
|
|
4422
4422
|
}
|
|
4423
4423
|
}
|
|
4424
4424
|
|
|
4425
|
-
console.log(
|
|
4425
|
+
console.log(` Remote URL: ${remoteUrlRaw.replace(gitToken, '***')}`);
|
|
4426
4426
|
let authenticatedUrl = '';
|
|
4427
4427
|
|
|
4428
4428
|
if (remoteUrlRaw.includes('github.com')) {
|
|
@@ -4431,7 +4431,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4431
4431
|
.replace(/^git@github\.com:/, '')
|
|
4432
4432
|
.replace(/\.git$/, '');
|
|
4433
4433
|
authenticatedUrl = `https://x-access-token:${gitToken}@github.com/${repoPath}.git`;
|
|
4434
|
-
console.log(
|
|
4434
|
+
console.log(` GitHub auth URL: https://x-access-token:***@github.com/${repoPath}.git`);
|
|
4435
4435
|
} else if (remoteUrlRaw.includes('gitlab.com') || remoteUrlRaw.includes('gitlab')) {
|
|
4436
4436
|
const repoPath = remoteUrlRaw
|
|
4437
4437
|
.replace(/^https?:\/\/(.*@)?[^\/]+\//, '')
|
|
@@ -4439,47 +4439,47 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4439
4439
|
.replace(/\.git$/, '');
|
|
4440
4440
|
const host = remoteUrlRaw.match(/https?:\/\/([^\/]+)/)?.[1] || 'gitlab.com';
|
|
4441
4441
|
authenticatedUrl = `https://oauth2:${gitToken}@${host}/${repoPath}.git`;
|
|
4442
|
-
console.log(
|
|
4442
|
+
console.log(` GitLab auth URL: https://oauth2:***@${host}/${repoPath}.git`);
|
|
4443
4443
|
} else if (remoteUrlRaw.includes('bitbucket.org') || remoteUrlRaw.includes('bitbucket')) {
|
|
4444
4444
|
const repoPath = remoteUrlRaw
|
|
4445
4445
|
.replace(/^https?:\/\/(.*@)?bitbucket\.org\//, '')
|
|
4446
4446
|
.replace(/^git@bitbucket\.org:/, '')
|
|
4447
4447
|
.replace(/\.git$/, '');
|
|
4448
|
-
//
|
|
4449
|
-
// x-token-auth
|
|
4448
|
+
// FIXED: Bitbucket App Password auth token j est no formato "username:appPassword"
|
|
4449
|
+
// x-token-auth no funciona; Basic Auth via URL o mtodo correcto
|
|
4450
4450
|
authenticatedUrl = `https://${gitToken}@bitbucket.org/${repoPath}.git`;
|
|
4451
4451
|
} else if (remoteUrlRaw) {
|
|
4452
|
-
// Unknown provider
|
|
4452
|
+
// Unknown provider try generic https with token
|
|
4453
4453
|
const urlObj = new URL(remoteUrlRaw.replace(/^git@([^:]+):/, 'https://$1/'));
|
|
4454
4454
|
authenticatedUrl = `https://oauth2:${gitToken}@${urlObj.host}${urlObj.pathname}`;
|
|
4455
4455
|
if (!authenticatedUrl.endsWith('.git')) authenticatedUrl += '.git';
|
|
4456
|
-
console.log(
|
|
4456
|
+
console.log(` Generic auth URL for ${urlObj.host}`);
|
|
4457
4457
|
}
|
|
4458
4458
|
|
|
4459
4459
|
if (authenticatedUrl) {
|
|
4460
|
-
console.log(
|
|
4460
|
+
console.log(` Pushing ${finalBranchName} with token auth...`);
|
|
4461
4461
|
try {
|
|
4462
|
-
// Don't use 2>&1
|
|
4462
|
+
// Don't use 2>&1 let execSync capture stderr separately
|
|
4463
4463
|
pushResult = execSync(`git push ${authenticatedUrl} ${finalBranchName}`, {
|
|
4464
4464
|
...opts,
|
|
4465
4465
|
timeout: 60000, // 60s for slow connections
|
|
4466
4466
|
stdio: ['pipe', 'pipe', 'pipe'] // capture stdin, stdout, stderr separately
|
|
4467
4467
|
}).toString();
|
|
4468
4468
|
pushed = true;
|
|
4469
|
-
console.log(
|
|
4469
|
+
console.log(` Pushed with token authentication`);
|
|
4470
4470
|
} catch (innerErr) {
|
|
4471
4471
|
// Mask token in error messages before logging
|
|
4472
4472
|
const maskToken = (str) => str ? str.replace(gitToken, '***TOKEN***') : '';
|
|
4473
4473
|
const errMsg = maskToken(innerErr.stderr?.toString() || innerErr.stdout?.toString() || innerErr.message || '');
|
|
4474
|
-
console.warn(
|
|
4474
|
+
console.warn(` Authenticated push failed: ${errMsg}`);
|
|
4475
4475
|
pushResult = errMsg;
|
|
4476
4476
|
}
|
|
4477
4477
|
} else {
|
|
4478
|
-
console.warn(
|
|
4478
|
+
console.warn(` Could not construct authenticated URL from: ${remoteUrlRaw}`);
|
|
4479
4479
|
}
|
|
4480
4480
|
} catch (pushErr) {
|
|
4481
4481
|
const maskToken = (str) => str ? str.replace(gitToken, '***TOKEN***') : '';
|
|
4482
|
-
console.warn(
|
|
4482
|
+
console.warn(` Git auth setup failed: ${maskToken(pushErr.message)}`);
|
|
4483
4483
|
pushResult = maskToken(pushErr.message) || 'Push setup failed';
|
|
4484
4484
|
}
|
|
4485
4485
|
}
|
|
@@ -4494,8 +4494,8 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4494
4494
|
pushResult = execSync(`git push origin ${finalBranchName} 2>&1`, opts).toString();
|
|
4495
4495
|
pushed = true;
|
|
4496
4496
|
} catch (pushErr2) {
|
|
4497
|
-
console.warn(
|
|
4498
|
-
pushResult = pushErr2.message || 'Push failed
|
|
4497
|
+
console.warn(` Git push failed: ${pushErr2.message}`);
|
|
4498
|
+
pushResult = pushErr2.message || 'Push failed no remote configured or auth required';
|
|
4499
4499
|
}
|
|
4500
4500
|
}
|
|
4501
4501
|
}
|
|
@@ -4506,7 +4506,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4506
4506
|
// 9. Get remote URL and generate MR/PR link
|
|
4507
4507
|
let remoteUrl = '';
|
|
4508
4508
|
let mrUrl = '';
|
|
4509
|
-
let targetBranch = 'develop'; // default target
|
|
4509
|
+
let targetBranch = 'develop'; // default target always prefer develop
|
|
4510
4510
|
try {
|
|
4511
4511
|
remoteUrl = execSync('git remote get-url origin 2>/dev/null', opts).trim();
|
|
4512
4512
|
// Always target develop if it exists, regardless of repo default branch
|
|
@@ -4519,13 +4519,13 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4519
4519
|
execSync('git rev-parse --verify develop 2>/dev/null', opts);
|
|
4520
4520
|
targetBranch = 'develop';
|
|
4521
4521
|
} catch {
|
|
4522
|
-
// No develop branch at all
|
|
4522
|
+
// No develop branch at all use main as last resort
|
|
4523
4523
|
targetBranch = 'main';
|
|
4524
4524
|
}
|
|
4525
4525
|
}
|
|
4526
4526
|
|
|
4527
4527
|
// Generate MR/PR URL based on provider
|
|
4528
|
-
//
|
|
4528
|
+
// SECURITY FIX: Remove any embedded tokens from URL before constructing MR link
|
|
4529
4529
|
// This prevents tokens from appearing in logs, UI, or being shared accidentally
|
|
4530
4530
|
const cleanUrl = remoteUrl
|
|
4531
4531
|
.replace(/\.git$/, '')
|
|
@@ -4546,15 +4546,15 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4546
4546
|
} else if (cleanUrl.includes('bitbucket.org') || cleanUrl.includes('bitbucket')) {
|
|
4547
4547
|
mrUrl = `${cleanUrl}/pull-requests/new?source=${finalBranchName}&dest=${targetBranch}`;
|
|
4548
4548
|
} else {
|
|
4549
|
-
// Generic
|
|
4549
|
+
// Generic try GitHub-style URL
|
|
4550
4550
|
mrUrl = `${cleanUrl}/compare/${targetBranch}...${finalBranchName}?expand=1`;
|
|
4551
4551
|
}
|
|
4552
4552
|
} catch (e) {
|
|
4553
4553
|
console.warn('Could not detect remote URL:', e.message);
|
|
4554
4554
|
}
|
|
4555
4555
|
|
|
4556
|
-
console.log(
|
|
4557
|
-
if (mrUrl) console.log(
|
|
4556
|
+
console.log(` Fix branch created: ${finalBranchName} (${commitSha.substring(0, 8)}) pushed=${pushed}`);
|
|
4557
|
+
if (mrUrl) console.log(` MR URL: ${mrUrl}`);
|
|
4558
4558
|
|
|
4559
4559
|
// 10. Switch back to original branch and merge fix to keep changes on disk
|
|
4560
4560
|
try {
|
|
@@ -4563,7 +4563,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4563
4563
|
// This keeps the diff viewer working (disk has patched version)
|
|
4564
4564
|
try {
|
|
4565
4565
|
execSync(`git merge ${finalBranchName} --no-edit`, opts);
|
|
4566
|
-
console.log(
|
|
4566
|
+
console.log(` Merged ${finalBranchName} into ${currentBranch}`);
|
|
4567
4567
|
} catch (mergeErr) {
|
|
4568
4568
|
// If merge fails (conflicts), just cherry-pick the changes without committing
|
|
4569
4569
|
try {
|
|
@@ -4577,27 +4577,27 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4577
4577
|
execSync(`git checkout ${finalBranchName} -- ${f}`, opts);
|
|
4578
4578
|
} catch {}
|
|
4579
4579
|
}
|
|
4580
|
-
console.log(
|
|
4580
|
+
console.log(` Cherry-picked ${changedFiles.length} file(s) from ${finalBranchName}`);
|
|
4581
4581
|
} catch {}
|
|
4582
4582
|
}
|
|
4583
4583
|
} catch {}
|
|
4584
4584
|
|
|
4585
4585
|
// 11. Auto-create Pull Request if pushed and token available
|
|
4586
|
-
//
|
|
4586
|
+
// FIXED: suporta GitHub, GitLab e Bitbucket; branding Insptech AI
|
|
4587
4587
|
let prUrl = '';
|
|
4588
4588
|
const prBodyText =
|
|
4589
|
-
`##
|
|
4589
|
+
`## Auto-fix by Insptech AI\n\n` +
|
|
4590
4590
|
`**Branch:** \`${finalBranchName}\`\n` +
|
|
4591
4591
|
`**Files changed:** ${status.split('\n').length}\n` +
|
|
4592
4592
|
`**Commit:** ${commitSha.substring(0, 8)}\n\n` +
|
|
4593
4593
|
`This pull request was automatically created by Insptech AI after detecting and fixing a production error.\n\n` +
|
|
4594
4594
|
`### Changes\n` +
|
|
4595
4595
|
status.split('\n').map(l => `- \`${l.trim()}\``).join('\n') + '\n\n' +
|
|
4596
|
-
`---\n*Generated by [Insptech AI](https://app.insptech.pt)
|
|
4596
|
+
`---\n*Generated by [Insptech AI](https://app.insptech.pt) Autonomous Debugging Platform*`;
|
|
4597
4597
|
|
|
4598
4598
|
if (pushed && gitToken && remoteUrl) {
|
|
4599
4599
|
|
|
4600
|
-
//
|
|
4600
|
+
// GitHub
|
|
4601
4601
|
if (remoteUrl.includes('github.com')) {
|
|
4602
4602
|
try {
|
|
4603
4603
|
const repoPath = remoteUrl
|
|
@@ -4624,18 +4624,18 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4624
4624
|
if (response.ok) {
|
|
4625
4625
|
const prData = await response.json();
|
|
4626
4626
|
prUrl = prData.html_url;
|
|
4627
|
-
console.log(
|
|
4627
|
+
console.log(` GitHub PR created: ${prUrl}`);
|
|
4628
4628
|
} else {
|
|
4629
4629
|
const errText = await response.text();
|
|
4630
|
-
console.warn(
|
|
4630
|
+
console.warn(` GitHub PR creation failed (${response.status}): ${errText.substring(0, 200)}`);
|
|
4631
4631
|
prUrl = mrUrl;
|
|
4632
4632
|
}
|
|
4633
4633
|
} catch (prErr) {
|
|
4634
|
-
console.warn(
|
|
4634
|
+
console.warn(` GitHub PR error: ${prErr.message}`);
|
|
4635
4635
|
prUrl = mrUrl;
|
|
4636
4636
|
}
|
|
4637
4637
|
|
|
4638
|
-
//
|
|
4638
|
+
// GitLab
|
|
4639
4639
|
} else if (remoteUrl.includes('gitlab.com') || remoteUrl.includes('gitlab')) {
|
|
4640
4640
|
try {
|
|
4641
4641
|
const hostMatch = remoteUrl.match(/https?:\/\/([^\/]+)/);
|
|
@@ -4644,7 +4644,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4644
4644
|
.replace(/^https?:\/\/(.*@)?[^\/]+\//, '')
|
|
4645
4645
|
.replace(/^git@[^:]+:/, '')
|
|
4646
4646
|
.replace(/\.git$/, '');
|
|
4647
|
-
// GitLab API requer o path encoded (ex: "mygroup/myrepo"
|
|
4647
|
+
// GitLab API requer o path encoded (ex: "mygroup/myrepo" "mygroup%2Fmyrepo")
|
|
4648
4648
|
const encodedPath = encodeURIComponent(repoPath);
|
|
4649
4649
|
|
|
4650
4650
|
const response = await fetch(`https://${host}/api/v4/projects/${encodedPath}/merge_requests`, {
|
|
@@ -4665,18 +4665,18 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4665
4665
|
if (response.ok) {
|
|
4666
4666
|
const mrData = await response.json();
|
|
4667
4667
|
prUrl = mrData.web_url;
|
|
4668
|
-
console.log(
|
|
4668
|
+
console.log(` GitLab MR created: ${prUrl}`);
|
|
4669
4669
|
} else {
|
|
4670
4670
|
const errText = await response.text();
|
|
4671
|
-
console.warn(
|
|
4671
|
+
console.warn(` GitLab MR creation failed (${response.status}): ${errText.substring(0, 200)}`);
|
|
4672
4672
|
prUrl = mrUrl;
|
|
4673
4673
|
}
|
|
4674
4674
|
} catch (glErr) {
|
|
4675
|
-
console.warn(
|
|
4675
|
+
console.warn(` GitLab MR error: ${glErr.message}`);
|
|
4676
4676
|
prUrl = mrUrl;
|
|
4677
4677
|
}
|
|
4678
4678
|
|
|
4679
|
-
//
|
|
4679
|
+
// Bitbucket
|
|
4680
4680
|
} else if (remoteUrl.includes('bitbucket.org') || remoteUrl.includes('bitbucket')) {
|
|
4681
4681
|
try {
|
|
4682
4682
|
const repoPath = remoteUrl
|
|
@@ -4691,18 +4691,18 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4691
4691
|
// - plain TOKEN (Repository Access Token)
|
|
4692
4692
|
let authHeader;
|
|
4693
4693
|
if (gitToken.includes(':')) {
|
|
4694
|
-
// Contains colon
|
|
4694
|
+
// Contains colon could be user:pass (App Password) or x-token-auth:token
|
|
4695
4695
|
const [user, pass] = gitToken.split(':', 2);
|
|
4696
4696
|
if (user === 'x-token-auth') {
|
|
4697
|
-
// Repository Access Token
|
|
4697
|
+
// Repository Access Token use Bearer with the token part
|
|
4698
4698
|
authHeader = `Bearer ${pass}`;
|
|
4699
4699
|
} else {
|
|
4700
|
-
// App Password
|
|
4700
|
+
// App Password use Basic auth
|
|
4701
4701
|
const credentials = Buffer.from(gitToken).toString('base64');
|
|
4702
4702
|
authHeader = `Basic ${credentials}`;
|
|
4703
4703
|
}
|
|
4704
4704
|
} else {
|
|
4705
|
-
// Plain token
|
|
4705
|
+
// Plain token use Bearer (Repository Access Token)
|
|
4706
4706
|
authHeader = `Bearer ${gitToken}`;
|
|
4707
4707
|
}
|
|
4708
4708
|
|
|
@@ -4725,14 +4725,14 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4725
4725
|
if (response.ok) {
|
|
4726
4726
|
const prData = await response.json();
|
|
4727
4727
|
prUrl = prData.links && prData.links.html ? prData.links.html.href : mrUrl;
|
|
4728
|
-
console.log(
|
|
4728
|
+
console.log(` Bitbucket PR created: ${prUrl}`);
|
|
4729
4729
|
} else {
|
|
4730
4730
|
const errText = await response.text();
|
|
4731
|
-
console.warn(
|
|
4731
|
+
console.warn(` Bitbucket PR creation failed (${response.status}): ${errText.substring(0, 200)}`);
|
|
4732
4732
|
prUrl = mrUrl;
|
|
4733
4733
|
}
|
|
4734
4734
|
} catch (bbErr) {
|
|
4735
|
-
console.warn(
|
|
4735
|
+
console.warn(` Bitbucket PR error: ${bbErr.message}`);
|
|
4736
4736
|
prUrl = mrUrl;
|
|
4737
4737
|
}
|
|
4738
4738
|
}
|
|
@@ -4755,7 +4755,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4755
4755
|
});
|
|
4756
4756
|
|
|
4757
4757
|
} catch (err) {
|
|
4758
|
-
console.error(
|
|
4758
|
+
console.error(` Git branch creation failed: ${err.message}`);
|
|
4759
4759
|
res.status(500).json({ ok: false, error: err.message });
|
|
4760
4760
|
}
|
|
4761
4761
|
});
|
|
@@ -4799,7 +4799,7 @@ app.get("/workspace/git/status", async (req, res) => {
|
|
|
4799
4799
|
});
|
|
4800
4800
|
|
|
4801
4801
|
// ============================================
|
|
4802
|
-
//
|
|
4802
|
+
// PULL REQUEST COMMENTS ENDPOINTS
|
|
4803
4803
|
// Read, reply, and resolve PR comments via git providers
|
|
4804
4804
|
// Used by the frontend "Code Review" tab in incident detail
|
|
4805
4805
|
// ============================================
|
|
@@ -4867,7 +4867,7 @@ app.get("/workspace/git/pr/comments", async (req, res) => {
|
|
|
4867
4867
|
unresolvedCount: comments.filter(c => c.resolved === false || c.resolved === undefined).length
|
|
4868
4868
|
});
|
|
4869
4869
|
} catch (err) {
|
|
4870
|
-
console.error('
|
|
4870
|
+
console.error(' Failed to fetch PR comments:', err.message);
|
|
4871
4871
|
res.status(500).json({ ok: false, error: err.message });
|
|
4872
4872
|
}
|
|
4873
4873
|
});
|
|
@@ -4904,7 +4904,7 @@ app.get("/workspace/git/pr/comments/:commentId", async (req, res) => {
|
|
|
4904
4904
|
|
|
4905
4905
|
res.json({ ok: true, comment });
|
|
4906
4906
|
} catch (err) {
|
|
4907
|
-
console.error('
|
|
4907
|
+
console.error(' Failed to fetch PR comment:', err.message);
|
|
4908
4908
|
res.status(500).json({ ok: false, error: err.message });
|
|
4909
4909
|
}
|
|
4910
4910
|
});
|
|
@@ -4966,10 +4966,10 @@ app.post("/workspace/git/pr/comments", async (req, res) => {
|
|
|
4966
4966
|
comment = await provider.instance.addPRComment(owner, repo, Number(prNumber), body);
|
|
4967
4967
|
}
|
|
4968
4968
|
|
|
4969
|
-
console.log(
|
|
4969
|
+
console.log(` PR comment added to PR #${prNumber}: ${body.substring(0, 50)}...`);
|
|
4970
4970
|
res.json({ ok: true, comment });
|
|
4971
4971
|
} catch (err) {
|
|
4972
|
-
console.error('
|
|
4972
|
+
console.error(' Failed to add PR comment:', err.message);
|
|
4973
4973
|
res.status(500).json({ ok: false, error: err.message });
|
|
4974
4974
|
}
|
|
4975
4975
|
});
|
|
@@ -5004,10 +5004,10 @@ app.post("/workspace/git/pr/comments/:commentId/resolve", async (req, res) => {
|
|
|
5004
5004
|
const { owner, repo } = provider.info;
|
|
5005
5005
|
const result = await provider.instance.resolvePRComment(owner, repo, Number(prNumber), commentId);
|
|
5006
5006
|
|
|
5007
|
-
console.log(
|
|
5007
|
+
console.log(` PR comment ${commentId} resolved on PR #${prNumber}`);
|
|
5008
5008
|
res.json({ ok: true, commentId, ...result });
|
|
5009
5009
|
} catch (err) {
|
|
5010
|
-
console.error('
|
|
5010
|
+
console.error(' Failed to resolve PR comment:', err.message);
|
|
5011
5011
|
res.status(500).json({ ok: false, error: err.message });
|
|
5012
5012
|
}
|
|
5013
5013
|
});
|
|
@@ -5042,10 +5042,10 @@ app.post("/workspace/git/pr/comments/:commentId/unresolve", async (req, res) =>
|
|
|
5042
5042
|
const { owner, repo } = provider.info;
|
|
5043
5043
|
const result = await provider.instance.unresolvePRComment(owner, repo, Number(prNumber), commentId);
|
|
5044
5044
|
|
|
5045
|
-
console.log(
|
|
5045
|
+
console.log(` PR comment ${commentId} unresolve on PR #${prNumber}`);
|
|
5046
5046
|
res.json({ ok: true, commentId, ...result });
|
|
5047
5047
|
} catch (err) {
|
|
5048
|
-
console.error('
|
|
5048
|
+
console.error(' Failed to unresolve PR comment:', err.message);
|
|
5049
5049
|
res.status(500).json({ ok: false, error: err.message });
|
|
5050
5050
|
}
|
|
5051
5051
|
});
|
|
@@ -5090,7 +5090,7 @@ app.post("/workspace/git/pr/comments/:commentId/fix-and-resolve", async (req, re
|
|
|
5090
5090
|
return res.status(404).json({ error: "Comment not found" });
|
|
5091
5091
|
}
|
|
5092
5092
|
|
|
5093
|
-
console.log(
|
|
5093
|
+
console.log(` AI Fix & Resolve: PR #${prNumber}, comment ${commentId}`);
|
|
5094
5094
|
console.log(` File: ${comment.path || '(general)'}, Line: ${comment.line || 'N/A'}`);
|
|
5095
5095
|
console.log(` Request: ${comment.body.substring(0, 100)}...`);
|
|
5096
5096
|
|
|
@@ -5122,14 +5122,14 @@ app.post("/workspace/git/pr/comments/:commentId/fix-and-resolve", async (req, re
|
|
|
5122
5122
|
message: 'Comment fetched. Gateway should now: 1) Read file, 2) AI analyze, 3) Apply patch, 4) Commit, 5) Reply, 6) Resolve'
|
|
5123
5123
|
});
|
|
5124
5124
|
} catch (err) {
|
|
5125
|
-
console.error('
|
|
5125
|
+
console.error(' Failed to prepare fix-and-resolve:', err.message);
|
|
5126
5126
|
res.status(500).json({ ok: false, error: err.message });
|
|
5127
5127
|
}
|
|
5128
5128
|
});
|
|
5129
5129
|
|
|
5130
5130
|
|
|
5131
5131
|
// ============================================
|
|
5132
|
-
//
|
|
5132
|
+
// GIT PROVIDER HELPER (internal)
|
|
5133
5133
|
// Detects provider from remote URL and configures with token
|
|
5134
5134
|
// ============================================
|
|
5135
5135
|
|
|
@@ -5145,7 +5145,7 @@ async function _getGitProvider() {
|
|
|
5145
5145
|
try {
|
|
5146
5146
|
remoteUrl = execSync('git remote get-url origin', opts).trim();
|
|
5147
5147
|
} catch {
|
|
5148
|
-
console.warn('
|
|
5148
|
+
console.warn(' No git remote found');
|
|
5149
5149
|
return null;
|
|
5150
5150
|
}
|
|
5151
5151
|
|
|
@@ -5155,7 +5155,7 @@ async function _getGitProvider() {
|
|
|
5155
5155
|
const providerId = registry.detectProviderFromUrl(remoteUrl);
|
|
5156
5156
|
|
|
5157
5157
|
if (!providerId) {
|
|
5158
|
-
console.warn(
|
|
5158
|
+
console.warn(` Unknown git provider for URL: ${remoteUrl}`);
|
|
5159
5159
|
return null;
|
|
5160
5160
|
}
|
|
5161
5161
|
|
|
@@ -5170,7 +5170,7 @@ async function _getGitProvider() {
|
|
|
5170
5170
|
}
|
|
5171
5171
|
|
|
5172
5172
|
if (!token) {
|
|
5173
|
-
console.warn(
|
|
5173
|
+
console.warn(` No token found for ${providerId}. Set ${providerId.toUpperCase()}_TOKEN or GIT_TOKEN env var.`);
|
|
5174
5174
|
return null;
|
|
5175
5175
|
}
|
|
5176
5176
|
|
|
@@ -5190,12 +5190,12 @@ async function _getGitProvider() {
|
|
|
5190
5190
|
|
|
5191
5191
|
return { instance, info, providerId, remoteUrl };
|
|
5192
5192
|
} catch (err) {
|
|
5193
|
-
console.error('
|
|
5193
|
+
console.error(' Failed to initialize git provider:', err.message);
|
|
5194
5194
|
return null;
|
|
5195
5195
|
}
|
|
5196
5196
|
}
|
|
5197
5197
|
// ============================================
|
|
5198
|
-
//
|
|
5198
|
+
// MULTI-WORKSPACE ENDPOINTS
|
|
5199
5199
|
// ============================================
|
|
5200
5200
|
|
|
5201
5201
|
/** Lista todos os workspaces abertos */
|
|
@@ -5232,7 +5232,7 @@ app.post("/workspace/:workspaceId/open", async (req, res) => {
|
|
|
5232
5232
|
}
|
|
5233
5233
|
});
|
|
5234
5234
|
|
|
5235
|
-
/** Aplica patch num workspace
|
|
5235
|
+
/** Aplica patch num workspace especfico */
|
|
5236
5236
|
app.post("/workspace/:workspaceId/patch", async (req, res) => {
|
|
5237
5237
|
const { workspaceId } = req.params;
|
|
5238
5238
|
const { diff } = req.body || {};
|
|
@@ -5246,7 +5246,7 @@ app.post("/workspace/:workspaceId/patch", async (req, res) => {
|
|
|
5246
5246
|
}
|
|
5247
5247
|
});
|
|
5248
5248
|
|
|
5249
|
-
/** Compila e testa num workspace
|
|
5249
|
+
/** Compila e testa num workspace especfico */
|
|
5250
5250
|
app.post("/workspace/:workspaceId/test", async (req, res) => {
|
|
5251
5251
|
const { workspaceId } = req.params;
|
|
5252
5252
|
try {
|
|
@@ -5259,7 +5259,7 @@ app.post("/workspace/:workspaceId/test", async (req, res) => {
|
|
|
5259
5259
|
}
|
|
5260
5260
|
});
|
|
5261
5261
|
|
|
5262
|
-
/** Executa comando num workspace
|
|
5262
|
+
/** Executa comando num workspace especfico */
|
|
5263
5263
|
app.post("/workspace/:workspaceId/run", async (req, res) => {
|
|
5264
5264
|
const { workspaceId } = req.params;
|
|
5265
5265
|
const { cmd, args = [] } = req.body || {};
|
|
@@ -5275,12 +5275,12 @@ app.post("/workspace/:workspaceId/run", async (req, res) => {
|
|
|
5275
5275
|
|
|
5276
5276
|
|
|
5277
5277
|
// ============================================
|
|
5278
|
-
//
|
|
5279
|
-
// Connects Local Agent to Gateway
|
|
5278
|
+
// WEBSOCKET REVERSE TUNNEL
|
|
5279
|
+
// Connects Local Agent to Gateway no public URL needed
|
|
5280
5280
|
// ============================================
|
|
5281
5281
|
async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
5282
5282
|
if (!gatewayUrl || !apiKey || !tenantId) {
|
|
5283
|
-
console.log('
|
|
5283
|
+
console.log(' WebSocket tunnel skipped: missing gatewayUrl, apiKey or tenantId in ~/.deepdebug/config.json');
|
|
5284
5284
|
return;
|
|
5285
5285
|
}
|
|
5286
5286
|
|
|
@@ -5298,7 +5298,7 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5298
5298
|
ws = new WebSocket(fullUrl);
|
|
5299
5299
|
|
|
5300
5300
|
ws.on('open', () => {
|
|
5301
|
-
console.log(
|
|
5301
|
+
console.log(` WebSocket tunnel connected to Gateway`);
|
|
5302
5302
|
reconnectDelay = 2000;
|
|
5303
5303
|
ws.send(JSON.stringify({ type: 'register', tenantId, port, workspacePath: WORKSPACE_ROOT || null }));
|
|
5304
5304
|
const hb = setInterval(() => {
|
|
@@ -5314,7 +5314,7 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5314
5314
|
try { request = JSON.parse(text); } catch { return; }
|
|
5315
5315
|
const { requestId, command, params } = request;
|
|
5316
5316
|
if (!requestId || !command) return;
|
|
5317
|
-
console.log(
|
|
5317
|
+
console.log(` WS command: ${command}`);
|
|
5318
5318
|
let result;
|
|
5319
5319
|
try { result = await handleWsCommand(command, params || {}); }
|
|
5320
5320
|
catch (err) { result = { error: err.message }; }
|
|
@@ -5323,19 +5323,19 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5323
5323
|
|
|
5324
5324
|
ws.on('close', () => {
|
|
5325
5325
|
if (!isShuttingDown) {
|
|
5326
|
-
console.log(
|
|
5326
|
+
console.log(` WS disconnected. Reconnecting in ${reconnectDelay/1000}s...`);
|
|
5327
5327
|
setTimeout(connect, reconnectDelay);
|
|
5328
5328
|
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
|
5329
5329
|
}
|
|
5330
5330
|
});
|
|
5331
5331
|
|
|
5332
|
-
ws.on('error', (err) => console.warn(
|
|
5332
|
+
ws.on('error', (err) => console.warn(` WS error: ${err.message}`));
|
|
5333
5333
|
|
|
5334
5334
|
} catch (err) {
|
|
5335
5335
|
if (err.code === 'ERR_MODULE_NOT_FOUND') {
|
|
5336
|
-
console.warn('
|
|
5336
|
+
console.warn(' WebSocket tunnel requires "ws" package. Run: npm install ws');
|
|
5337
5337
|
} else {
|
|
5338
|
-
console.warn(
|
|
5338
|
+
console.warn(` WS tunnel error: ${err.message}`);
|
|
5339
5339
|
setTimeout(connect, reconnectDelay);
|
|
5340
5340
|
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
|
5341
5341
|
}
|
|
@@ -5392,7 +5392,7 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5392
5392
|
}
|
|
5393
5393
|
|
|
5394
5394
|
// ============================================
|
|
5395
|
-
// WORKSPACE DIFF
|
|
5395
|
+
// WORKSPACE DIFF Returns before/after for patches
|
|
5396
5396
|
// ============================================
|
|
5397
5397
|
case 'workspace.diff': {
|
|
5398
5398
|
const { backupId, incidentId } = params;
|
|
@@ -5406,7 +5406,7 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5406
5406
|
return { error: 'backupId or incidentId is required' };
|
|
5407
5407
|
}
|
|
5408
5408
|
|
|
5409
|
-
console.log(
|
|
5409
|
+
console.log(` WS workspace.diff ${endpoint}`);
|
|
5410
5410
|
const res = await fetch(`${localBase}${endpoint}`, {
|
|
5411
5411
|
method: 'GET',
|
|
5412
5412
|
headers: { 'Content-Type': 'application/json' }
|
|
@@ -5415,7 +5415,7 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5415
5415
|
}
|
|
5416
5416
|
|
|
5417
5417
|
// ============================================
|
|
5418
|
-
// MCP TOOLS
|
|
5418
|
+
// MCP TOOLS routed via MCP HTTP Bridge (port 5056)
|
|
5419
5419
|
// ============================================
|
|
5420
5420
|
case 'mcp.read-file': {
|
|
5421
5421
|
const mcpBase = `http://localhost:5056`;
|
|
@@ -5543,18 +5543,18 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5543
5543
|
// START SERVER
|
|
5544
5544
|
// ============================================
|
|
5545
5545
|
app.listen(PORT, '0.0.0.0', async () => {
|
|
5546
|
-
console.log(`\n
|
|
5547
|
-
console.log(
|
|
5548
|
-
console.log(
|
|
5549
|
-
console.log(
|
|
5550
|
-
console.log(
|
|
5546
|
+
console.log(`\n DeepDebug Local Agent listening on port ${PORT}`);
|
|
5547
|
+
console.log(` Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
5548
|
+
console.log(` Process Manager initialized`);
|
|
5549
|
+
console.log(` Backup system ready (max: ${MAX_BACKUPS} backups)`);
|
|
5550
|
+
console.log(` AI Vibe Coding Engine: ${aiEngine?.isActive ? 'ACTIVE' : 'DISABLED'}`);
|
|
5551
5551
|
if (aiEngine) {
|
|
5552
5552
|
console.log(` Gateway URL: ${aiEngine.gatewayUrl}`);
|
|
5553
5553
|
console.log(` Max Retries: ${aiEngine.maxRetries}`);
|
|
5554
5554
|
}
|
|
5555
|
-
console.log(`\n
|
|
5555
|
+
console.log(`\n Ready to receive requests!\n`);
|
|
5556
5556
|
|
|
5557
|
-
//
|
|
5557
|
+
// Start WebSocket reverse tunnel
|
|
5558
5558
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
5559
5559
|
const configPath = path.join(homeDir, '.deepdebug', 'config.json');
|
|
5560
5560
|
let cfg = {};
|
|
@@ -5579,7 +5579,7 @@ app.listen(PORT, '0.0.0.0', async () => {
|
|
|
5579
5579
|
try {
|
|
5580
5580
|
startMCPHttpServer(wsManager, parseInt(MCP_PORT));
|
|
5581
5581
|
} catch (err) {
|
|
5582
|
-
console.warn(
|
|
5582
|
+
console.warn(` MCP HTTP Server failed to start: ${err.message}`);
|
|
5583
5583
|
console.warn(` (MCP features disabled, REST API continues normally)`);
|
|
5584
5584
|
}
|
|
5585
5585
|
|