deepdebug-local-agent 1.0.17 → 1.0.19
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/exec-utils.js +39 -21
- package/src/server.js +448 -241
package/src/server.js
CHANGED
|
@@ -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
|
});
|
|
@@ -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(),
|
|
@@ -222,12 +222,12 @@ class AIVibeCodingEngine extends EventEmitter {
|
|
|
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
|
}
|
|
@@ -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;
|
|
@@ -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() {
|
|
@@ -577,7 +577,7 @@ const DEFAULT_WORKSPACE = process.env.DEFAULT_WORKSPACE || '/Users/macintosh/Ide
|
|
|
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 = [];
|
|
@@ -585,11 +585,31 @@ const processManager = new ProcessManager();
|
|
|
585
585
|
const wsManager = new WorkspaceManager();
|
|
586
586
|
|
|
587
587
|
function resolveWorkspaceRoot(req) {
|
|
588
|
+
// TENANT ISOLATION: stateless, never mutates global state.
|
|
589
|
+
// Returns the workspace path for THIS specific request only.
|
|
590
|
+
//
|
|
591
|
+
// Priority:
|
|
592
|
+
// 1. X-Workspace-Root header -- sent by Gateway with tenant NFS path (post-onboarding)
|
|
593
|
+
// 2. X-Workspace-Id header -- fallback via WorkspaceManager (dev/local mode)
|
|
594
|
+
// 3. null -- no workspace for this request (onboarding flow)
|
|
595
|
+
//
|
|
596
|
+
// IMPORTANT: Does NOT fall back to WORKSPACE_ROOT global.
|
|
597
|
+
// A request without a workspace header gets null, not another tenant's path.
|
|
598
|
+
|
|
599
|
+
// 1. Tenant-isolated NFS path from Gateway
|
|
600
|
+
const secureRoot = req.headers['x-workspace-root'];
|
|
601
|
+
if (secureRoot && secureRoot.trim()) {
|
|
602
|
+
return secureRoot.trim();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// 2. WorkspaceId (dev/local mode only)
|
|
588
606
|
const wsId = req.headers['x-workspace-id']
|
|
589
607
|
|| (req.body && req.body.workspaceId)
|
|
590
608
|
|| req.query.workspaceId;
|
|
591
609
|
if (wsId && wsManager.isOpen(wsId)) return wsManager.getRoot(wsId);
|
|
592
|
-
|
|
610
|
+
|
|
611
|
+
// 3. No workspace -- caller must handle null (e.g. onboarding endpoints)
|
|
612
|
+
return null;
|
|
593
613
|
}
|
|
594
614
|
const MCP_PORT = process.env.MCP_PORT || 5056;
|
|
595
615
|
|
|
@@ -652,7 +672,7 @@ function loadBackupIndex() {
|
|
|
652
672
|
});
|
|
653
673
|
}
|
|
654
674
|
}
|
|
655
|
-
console.log(`
|
|
675
|
+
console.log(`Restored ${BACKUPS.size} backups from disk`);
|
|
656
676
|
}
|
|
657
677
|
} catch (e) {
|
|
658
678
|
console.warn('Could not load backup index:', e.message);
|
|
@@ -664,19 +684,19 @@ loadBackupIndex();
|
|
|
664
684
|
|
|
665
685
|
// Event listeners do ProcessManager
|
|
666
686
|
processManager.on("started", ({ serviceId }) => {
|
|
667
|
-
console.log(`
|
|
687
|
+
console.log(`Service ${serviceId} started successfully`);
|
|
668
688
|
updateServiceStatus(serviceId, "running");
|
|
669
689
|
addServerLog("info", `Service ${serviceId} started successfully`);
|
|
670
690
|
});
|
|
671
691
|
|
|
672
692
|
processManager.on("stopped", ({ serviceId }) => {
|
|
673
|
-
console.log(`
|
|
693
|
+
console.log(`Service ${serviceId} stopped`);
|
|
674
694
|
updateServiceStatus(serviceId, "stopped");
|
|
675
695
|
addServerLog("info", `Service ${serviceId} stopped`);
|
|
676
696
|
});
|
|
677
697
|
|
|
678
698
|
processManager.on("error", ({ serviceId, error }) => {
|
|
679
|
-
console.error(`
|
|
699
|
+
console.error(`Service ${serviceId} error: ${error}`);
|
|
680
700
|
updateServiceStatus(serviceId, "failed");
|
|
681
701
|
addServerLog("error", `Service ${serviceId} error: ${error}`);
|
|
682
702
|
});
|
|
@@ -731,7 +751,7 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
731
751
|
// CLOUD MODE: Git clone (when Gateway sends repoUrl)
|
|
732
752
|
// ==========================================
|
|
733
753
|
if (repoUrl) {
|
|
734
|
-
console.log(`
|
|
754
|
+
console.log(`[/workspace/open] CLOUD MODE cloning ${repoUrl} (branch: ${branch})`);
|
|
735
755
|
|
|
736
756
|
try {
|
|
737
757
|
// Extract repo name: https://github.com/org/repo org_repo
|
|
@@ -772,16 +792,16 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
772
792
|
const alreadyCloned = await exists(gitDir);
|
|
773
793
|
|
|
774
794
|
if (alreadyCloned) {
|
|
775
|
-
console.log(`
|
|
795
|
+
console.log(`[cloud] Repo exists: ${clonePath}, updating...`);
|
|
776
796
|
// Update remote URL with fresh token
|
|
777
797
|
await execAsync(`git remote set-url origin "${authUrl}"`, { cwd: clonePath }).catch(() => {});
|
|
778
798
|
await execAsync(`git fetch origin`, { cwd: clonePath, timeout: 120000 });
|
|
779
799
|
// Checkout correct branch and reset to remote (discard previous patches)
|
|
780
800
|
await execAsync(`git checkout ${branch} 2>/dev/null || git checkout -b ${branch} origin/${branch}`, { cwd: clonePath }).catch(() => {});
|
|
781
801
|
await execAsync(`git reset --hard origin/${branch}`, { cwd: clonePath });
|
|
782
|
-
console.log(`
|
|
802
|
+
console.log(`[cloud] Updated to latest ${branch}`);
|
|
783
803
|
} else {
|
|
784
|
-
console.log(`
|
|
804
|
+
console.log(`[cloud] Cloning ${repoUrl} (branch: ${branch})...`);
|
|
785
805
|
await execAsync(
|
|
786
806
|
`git clone --branch ${branch} --single-branch --depth 50 "${authUrl}" "${clonePath}"`,
|
|
787
807
|
{ timeout: 300000 }
|
|
@@ -789,7 +809,7 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
789
809
|
// Configure git user for future commits
|
|
790
810
|
await execAsync(`git config user.email "deepdebug-ai@deepdebug.ai"`, { cwd: clonePath });
|
|
791
811
|
await execAsync(`git config user.name "DeepDebug AI"`, { cwd: clonePath });
|
|
792
|
-
console.log(`
|
|
812
|
+
console.log(`[cloud] Cloned successfully: ${clonePath}`);
|
|
793
813
|
}
|
|
794
814
|
|
|
795
815
|
// Set as active workspace
|
|
@@ -799,7 +819,7 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
799
819
|
}
|
|
800
820
|
const wsId = workspaceId || "default";
|
|
801
821
|
try { await wsManager.open(wsId, clonePath); } catch (err) {
|
|
802
|
-
console.warn(`
|
|
822
|
+
console.warn(`WorkspaceManager.open failed (non-fatal): ${err.message}`);
|
|
803
823
|
}
|
|
804
824
|
|
|
805
825
|
const meta = await detectProject(clonePath);
|
|
@@ -815,7 +835,7 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
815
835
|
});
|
|
816
836
|
|
|
817
837
|
} catch (gitErr) {
|
|
818
|
-
console.error(`
|
|
838
|
+
console.error(`[cloud] Git clone failed:`, gitErr.message);
|
|
819
839
|
const hint = gitErr.message.includes('Authentication') || gitErr.message.includes('could not read')
|
|
820
840
|
? "Authentication failed. Check token and repo URL."
|
|
821
841
|
: gitErr.message.includes('not found') || gitErr.message.includes('does not exist')
|
|
@@ -842,7 +862,7 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
842
862
|
try {
|
|
843
863
|
await wsManager.open(wsId, abs);
|
|
844
864
|
} catch (err) {
|
|
845
|
-
console.warn(`
|
|
865
|
+
console.warn(`WorkspaceManager open failed (non-fatal): ${err.message}`);
|
|
846
866
|
}
|
|
847
867
|
|
|
848
868
|
const meta = await detectProject(abs);
|
|
@@ -859,24 +879,30 @@ app.post("/workspace/open", async (req, res) => {
|
|
|
859
879
|
*/
|
|
860
880
|
app.post("/workspace/clone", async (req, res) => {
|
|
861
881
|
const body = req.body || {};
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const gitUrl = body.gitUrl || body.repoUrl;
|
|
866
|
-
const targetPath = body.targetPath || body.targetDir;
|
|
882
|
+
const gitUrl = body.gitUrl || body.repoUrl;
|
|
883
|
+
const targetPath = body.targetPath || body.targetDir;
|
|
884
|
+
const tenantId = body.tenantId;
|
|
867
885
|
const workspaceId = body.workspaceId;
|
|
868
|
-
const gitToken
|
|
869
|
-
const branch
|
|
886
|
+
const gitToken = body.gitToken;
|
|
887
|
+
const branch = body.branch;
|
|
870
888
|
|
|
871
889
|
if (!gitUrl) return res.status(400).json({ ok: false, error: "gitUrl is required" });
|
|
872
890
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
891
|
+
const repoName = gitUrl.split('/').pop().replace('.git', '');
|
|
892
|
+
|
|
893
|
+
const NFS_MOUNT = '/mnt/workspaces';
|
|
894
|
+
let absTarget;
|
|
895
|
+
if (tenantId && fs.existsSync(NFS_MOUNT)) {
|
|
896
|
+
absTarget = path.join(NFS_MOUNT, tenantId, repoName);
|
|
897
|
+
console.log(`[clone] Using NFS persistent storage: ${absTarget}`);
|
|
898
|
+
} else if (targetPath) {
|
|
899
|
+
absTarget = path.resolve(targetPath);
|
|
900
|
+
console.log(`[clone] Using explicit targetPath: ${absTarget}`);
|
|
901
|
+
} else {
|
|
902
|
+
absTarget = path.join(process.env.HOME || '/home/deepdebug', 'DeepDebug', repoName);
|
|
903
|
+
console.log(`[clone] Using fallback path: ${absTarget}`);
|
|
904
|
+
}
|
|
878
905
|
|
|
879
|
-
// If gitToken provided and not already embedded in URL, embed it
|
|
880
906
|
let authenticatedUrl = gitUrl;
|
|
881
907
|
if (gitToken && gitUrl.startsWith('https://') && !gitUrl.includes('@')) {
|
|
882
908
|
if (gitUrl.includes('bitbucket.org')) {
|
|
@@ -887,9 +913,7 @@ app.post("/workspace/clone", async (req, res) => {
|
|
|
887
913
|
authenticatedUrl = gitUrl.replace('https://', `https://x-access-token:${gitToken}@`);
|
|
888
914
|
}
|
|
889
915
|
}
|
|
890
|
-
|
|
891
|
-
const absTarget = resolvedTarget;
|
|
892
|
-
console.log(` Clone request: ${gitUrl} -> ${absTarget}`);
|
|
916
|
+
console.log(`Clone request: ${gitUrl} -> ${absTarget}`);
|
|
893
917
|
|
|
894
918
|
try {
|
|
895
919
|
// Ensure parent directory exists
|
|
@@ -901,14 +925,22 @@ app.post("/workspace/clone", async (req, res) => {
|
|
|
901
925
|
const alreadyCloned = await exists(gitDir);
|
|
902
926
|
|
|
903
927
|
if (alreadyCloned) {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
928
|
+
const targetBranch = branch || 'develop';
|
|
929
|
+
console.log(`Repo already exists at ${absTarget}, updating branch: ${targetBranch}...`);
|
|
930
|
+
await execAsync('git fetch --all', { cwd: absTarget, timeout: 60000 });
|
|
931
|
+
try {
|
|
932
|
+
await execAsync(`git checkout ${targetBranch}`, { cwd: absTarget, timeout: 30000 });
|
|
933
|
+
const { stdout } = await execAsync(`git pull origin ${targetBranch}`, { cwd: absTarget, timeout: 120000 });
|
|
934
|
+
console.log(`git pull: ${stdout.trim()}`);
|
|
935
|
+
} catch (checkoutErr) {
|
|
936
|
+
console.warn(`Checkout ${targetBranch} failed, staying on current branch: ${checkoutErr.message}`);
|
|
937
|
+
}
|
|
907
938
|
} else {
|
|
908
939
|
const safeLog = authenticatedUrl.replace(/x-token-auth:[^@]+@/g, 'x-token-auth:***@').replace(/oauth2:[^@]+@/g, 'oauth2:***@').replace(/x-access-token:[^@]+@/g, 'x-access-token:***@');
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
940
|
+
const cloneCmd = branch ? `git clone -b ${branch} "${authenticatedUrl}" "${absTarget}"` : `git clone "${authenticatedUrl}" "${absTarget}"`;
|
|
941
|
+
console.log(`Running: git clone${branch ? ' -b ' + branch : ''} "${safeLog}" "${absTarget}"`);
|
|
942
|
+
await execAsync(cloneCmd, { timeout: 300000 });
|
|
943
|
+
console.log(`Clone complete: ${absTarget}`);
|
|
912
944
|
}
|
|
913
945
|
|
|
914
946
|
// Open the cloned workspace
|
|
@@ -917,7 +949,7 @@ app.post("/workspace/clone", async (req, res) => {
|
|
|
917
949
|
try {
|
|
918
950
|
await wsManager.open(wsId, absTarget);
|
|
919
951
|
} catch (err) {
|
|
920
|
-
console.warn(`
|
|
952
|
+
console.warn(`WorkspaceManager.open failed (non-fatal): ${err.message}`);
|
|
921
953
|
}
|
|
922
954
|
|
|
923
955
|
const meta = await detectProject(absTarget);
|
|
@@ -933,7 +965,7 @@ app.post("/workspace/clone", async (req, res) => {
|
|
|
933
965
|
});
|
|
934
966
|
|
|
935
967
|
} catch (err) {
|
|
936
|
-
console.error(`
|
|
968
|
+
console.error(`Clone failed: ${err.message}`);
|
|
937
969
|
const hint = err.message.includes('Authentication') || err.message.includes('could not read')
|
|
938
970
|
? "Authentication failed. Ensure the repo is public or GitHub integration is configured."
|
|
939
971
|
: err.message.includes('not found') || err.message.includes('does not exist')
|
|
@@ -1015,6 +1047,35 @@ app.get("/workspace/file-content", async (req, res) => {
|
|
|
1015
1047
|
}
|
|
1016
1048
|
});
|
|
1017
1049
|
|
|
1050
|
+
/** Escreve/salva conteudo de arquivo no workspace */
|
|
1051
|
+
app.post("/workspace/write-file", async (req, res) => {
|
|
1052
|
+
const workspaceRoot = getEffectiveRoot(req);
|
|
1053
|
+
if (!workspaceRoot) return res.status(400).json({ error: "workspace not set" });
|
|
1054
|
+
|
|
1055
|
+
const { path: relativePath, content: fileContent } = req.body || {};
|
|
1056
|
+
if (!relativePath) return res.status(400).json({ error: "path is required" });
|
|
1057
|
+
if (fileContent === undefined || fileContent === null) return res.status(400).json({ error: "content is required" });
|
|
1058
|
+
|
|
1059
|
+
const pathMod = await import('path');
|
|
1060
|
+
const fullPath = pathMod.default.resolve(workspaceRoot, relativePath);
|
|
1061
|
+
|
|
1062
|
+
// Security: prevent path traversal outside workspace root
|
|
1063
|
+
if (!fullPath.startsWith(workspaceRoot)) {
|
|
1064
|
+
return res.status(403).json({ error: "Path traversal not allowed" });
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
try {
|
|
1068
|
+
const { mkdir } = await import('fs/promises');
|
|
1069
|
+
await mkdir(pathMod.default.dirname(fullPath), { recursive: true });
|
|
1070
|
+
await fsPromises.writeFile(fullPath, fileContent, 'utf8');
|
|
1071
|
+
res.json({ ok: true, path: relativePath, size: fileContent.length });
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
res.status(500).json({ error: "Could not write file", details: err.message });
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
|
|
1018
1079
|
/** L mltiplos arquivos */
|
|
1019
1080
|
app.post("/workspace/batch-read", async (req, res) => {
|
|
1020
1081
|
const wsRoot = resolveWorkspaceRoot(req);
|
|
@@ -1057,7 +1118,7 @@ app.get("/workspace/file-exists", async (req, res) => {
|
|
|
1057
1118
|
const fullPath = path.join(wsRoot, relativePath);
|
|
1058
1119
|
const fileExists = await exists(fullPath);
|
|
1059
1120
|
|
|
1060
|
-
console.log(`
|
|
1121
|
+
console.log(`[file-exists] ${relativePath} -> ${fileExists ? 'EXISTS' : 'NOT FOUND'}`);
|
|
1061
1122
|
|
|
1062
1123
|
res.json({
|
|
1063
1124
|
ok: true,
|
|
@@ -1066,7 +1127,7 @@ app.get("/workspace/file-exists", async (req, res) => {
|
|
|
1066
1127
|
fullPath: fullPath
|
|
1067
1128
|
});
|
|
1068
1129
|
} catch (err) {
|
|
1069
|
-
console.error(`
|
|
1130
|
+
console.error(`[file-exists] Error:`, err.message);
|
|
1070
1131
|
res.status(500).json({ ok: false, error: err.message });
|
|
1071
1132
|
}
|
|
1072
1133
|
});
|
|
@@ -1098,7 +1159,7 @@ app.post("/workspace/validate-paths", async (req, res) => {
|
|
|
1098
1159
|
const allExist = results.every(r => r.exists);
|
|
1099
1160
|
const missingPaths = results.filter(r => !r.exists).map(r => r.path);
|
|
1100
1161
|
|
|
1101
|
-
console.log(`
|
|
1162
|
+
console.log(`[validate-paths] Checked ${pathList.length} paths, ${missingPaths.length} missing`);
|
|
1102
1163
|
|
|
1103
1164
|
res.json({
|
|
1104
1165
|
ok: true,
|
|
@@ -1108,7 +1169,7 @@ app.post("/workspace/validate-paths", async (req, res) => {
|
|
|
1108
1169
|
totalChecked: pathList.length
|
|
1109
1170
|
});
|
|
1110
1171
|
} catch (err) {
|
|
1111
|
-
console.error(`
|
|
1172
|
+
console.error(`[validate-paths] Error:`, err.message);
|
|
1112
1173
|
res.status(500).json({ ok: false, error: err.message });
|
|
1113
1174
|
}
|
|
1114
1175
|
});
|
|
@@ -1131,7 +1192,7 @@ app.post("/workspace/search-file", async (req, res) => {
|
|
|
1131
1192
|
}
|
|
1132
1193
|
|
|
1133
1194
|
try {
|
|
1134
|
-
console.log(`
|
|
1195
|
+
console.log(`[search-file] Searching for: ${fileName}`);
|
|
1135
1196
|
|
|
1136
1197
|
const rawFiles = await listRecursive(wsRoot, {
|
|
1137
1198
|
maxDepth: 15,
|
|
@@ -1148,7 +1209,7 @@ app.post("/workspace/search-file", async (req, res) => {
|
|
|
1148
1209
|
return String(f);
|
|
1149
1210
|
}).filter(f => f && typeof f === 'string');
|
|
1150
1211
|
|
|
1151
|
-
console.log(`
|
|
1212
|
+
console.log(`[search-file] Scanning ${allFiles.length} files`);
|
|
1152
1213
|
|
|
1153
1214
|
// Strategy 1: Exact path match
|
|
1154
1215
|
let foundPath = allFiles.find(f => f.endsWith('/' + fileName) || f === fileName);
|
|
@@ -1178,14 +1239,14 @@ app.post("/workspace/search-file", async (req, res) => {
|
|
|
1178
1239
|
}
|
|
1179
1240
|
|
|
1180
1241
|
if (foundPath) {
|
|
1181
|
-
console.log(`
|
|
1242
|
+
console.log(`[search-file] Found: ${foundPath}`);
|
|
1182
1243
|
res.json({ ok: true, found: true, path: foundPath, fileName });
|
|
1183
1244
|
} else {
|
|
1184
|
-
console.log(`
|
|
1245
|
+
console.log(`[search-file] Not found: ${fileName}`);
|
|
1185
1246
|
res.json({ ok: true, found: false, fileName, searchedFiles: allFiles.length });
|
|
1186
1247
|
}
|
|
1187
1248
|
} catch (err) {
|
|
1188
|
-
console.error(`
|
|
1249
|
+
console.error(`[search-file] Error:`, err.message);
|
|
1189
1250
|
res.status(500).json({ ok: false, error: err.message });
|
|
1190
1251
|
}
|
|
1191
1252
|
});
|
|
@@ -1209,7 +1270,7 @@ app.post("/workspace/search-by-content", async (req, res) => {
|
|
|
1209
1270
|
}
|
|
1210
1271
|
|
|
1211
1272
|
try {
|
|
1212
|
-
console.log(`
|
|
1273
|
+
console.log(`[search-by-content] Searching for terms: ${terms.join(', ')}`);
|
|
1213
1274
|
|
|
1214
1275
|
const rawFiles = await listRecursive(wsRoot, {
|
|
1215
1276
|
maxDepth: 15,
|
|
@@ -1229,7 +1290,7 @@ app.post("/workspace/search-by-content", async (req, res) => {
|
|
|
1229
1290
|
return extensions.some(ext => filePath.endsWith(ext));
|
|
1230
1291
|
});
|
|
1231
1292
|
|
|
1232
|
-
console.log(`
|
|
1293
|
+
console.log(`[search-by-content] Scanning ${filteredFiles.length} files`);
|
|
1233
1294
|
|
|
1234
1295
|
const results = [];
|
|
1235
1296
|
|
|
@@ -1274,7 +1335,7 @@ app.post("/workspace/search-by-content", async (req, res) => {
|
|
|
1274
1335
|
return true;
|
|
1275
1336
|
}).slice(0, maxResults);
|
|
1276
1337
|
|
|
1277
|
-
console.log(`
|
|
1338
|
+
console.log(`[search-by-content] Found ${dedupedResults.length} matching files`);
|
|
1278
1339
|
|
|
1279
1340
|
res.json({
|
|
1280
1341
|
ok: true,
|
|
@@ -1283,7 +1344,7 @@ app.post("/workspace/search-by-content", async (req, res) => {
|
|
|
1283
1344
|
termsSearched: terms
|
|
1284
1345
|
});
|
|
1285
1346
|
} catch (err) {
|
|
1286
|
-
console.error(`
|
|
1347
|
+
console.error(`[search-by-content] Error:`, err.message);
|
|
1287
1348
|
res.status(500).json({ ok: false, error: err.message });
|
|
1288
1349
|
}
|
|
1289
1350
|
});
|
|
@@ -1307,7 +1368,7 @@ app.post("/workspace/find-field-definition", async (req, res) => {
|
|
|
1307
1368
|
}
|
|
1308
1369
|
|
|
1309
1370
|
try {
|
|
1310
|
-
console.log(`
|
|
1371
|
+
console.log(`[find-field] Searching for field: ${fieldName}`);
|
|
1311
1372
|
|
|
1312
1373
|
const rawFiles = await listRecursive(wsRoot, {
|
|
1313
1374
|
maxDepth: 15,
|
|
@@ -1386,7 +1447,7 @@ app.post("/workspace/find-field-definition", async (req, res) => {
|
|
|
1386
1447
|
return 0;
|
|
1387
1448
|
});
|
|
1388
1449
|
|
|
1389
|
-
console.log(`
|
|
1450
|
+
console.log(`[find-field] Found ${definitions.length} definitions for '${fieldName}'`);
|
|
1390
1451
|
|
|
1391
1452
|
res.json({
|
|
1392
1453
|
ok: true,
|
|
@@ -1395,7 +1456,7 @@ app.post("/workspace/find-field-definition", async (req, res) => {
|
|
|
1395
1456
|
totalSearched: targetFiles.length
|
|
1396
1457
|
});
|
|
1397
1458
|
} catch (err) {
|
|
1398
|
-
console.error(`
|
|
1459
|
+
console.error(`[find-field] Error:`, err.message);
|
|
1399
1460
|
res.status(500).json({ ok: false, error: err.message });
|
|
1400
1461
|
}
|
|
1401
1462
|
});
|
|
@@ -1609,7 +1670,7 @@ app.post("/workspace/patch", async (req, res) => {
|
|
|
1609
1670
|
if (!diff) return res.status(400).json({ error: "diff is required" });
|
|
1610
1671
|
|
|
1611
1672
|
try {
|
|
1612
|
-
console.log(`
|
|
1673
|
+
console.log(`Applying patch for incident: ${incidentId || 'unknown'}`);
|
|
1613
1674
|
const out = await applyUnifiedDiff(wsRoot, diff);
|
|
1614
1675
|
|
|
1615
1676
|
// CRITICAL FIX: Format response as expected by Gateway
|
|
@@ -1624,7 +1685,7 @@ app.post("/workspace/patch", async (req, res) => {
|
|
|
1624
1685
|
incidentId: incidentId
|
|
1625
1686
|
};
|
|
1626
1687
|
|
|
1627
|
-
console.log(`
|
|
1688
|
+
console.log(`Patch applied successfully:`, {
|
|
1628
1689
|
target: out.target,
|
|
1629
1690
|
bytes: out.bytes,
|
|
1630
1691
|
incident: incidentId
|
|
@@ -1632,7 +1693,7 @@ app.post("/workspace/patch", async (req, res) => {
|
|
|
1632
1693
|
|
|
1633
1694
|
res.json(response);
|
|
1634
1695
|
} catch (e) {
|
|
1635
|
-
console.error(`
|
|
1696
|
+
console.error(`Patch failed:`, e.message);
|
|
1636
1697
|
res.status(400).json({
|
|
1637
1698
|
ok: false,
|
|
1638
1699
|
error: "patch failed",
|
|
@@ -1684,7 +1745,7 @@ let TEST_LOCAL_STATE = {
|
|
|
1684
1745
|
* Returns current test local state with auto-detected port
|
|
1685
1746
|
*/
|
|
1686
1747
|
app.get("/workspace/test-local/state", async (req, res) => {
|
|
1687
|
-
console.log("
|
|
1748
|
+
console.log("[TEST-LOCAL] Getting state:", TEST_LOCAL_STATE.status);
|
|
1688
1749
|
|
|
1689
1750
|
// Auto-detect port if not set in config
|
|
1690
1751
|
let port = TEST_LOCAL_STATE.config?.server?.port;
|
|
@@ -1723,7 +1784,7 @@ app.post("/workspace/test-local/compile", async (req, res) => {
|
|
|
1723
1784
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
1724
1785
|
|
|
1725
1786
|
try {
|
|
1726
|
-
console.log("
|
|
1787
|
+
console.log("[TEST-LOCAL] Starting compilation...");
|
|
1727
1788
|
TEST_LOCAL_STATE.status = "compiling";
|
|
1728
1789
|
|
|
1729
1790
|
const meta = await detectProject(wsRoot);
|
|
@@ -1734,41 +1795,61 @@ app.post("/workspace/test-local/compile", async (req, res) => {
|
|
|
1734
1795
|
skipTests: true
|
|
1735
1796
|
});
|
|
1736
1797
|
|
|
1737
|
-
if (compileResult.
|
|
1798
|
+
if (!compileResult.success) {
|
|
1738
1799
|
TEST_LOCAL_STATE.status = "error";
|
|
1800
|
+
|
|
1801
|
+
const errorOutput = (compileResult.steps || [])
|
|
1802
|
+
.map(s => s.stderr || '')
|
|
1803
|
+
.filter(Boolean)
|
|
1804
|
+
.join('\n')
|
|
1805
|
+
.trim();
|
|
1806
|
+
|
|
1807
|
+
const stdoutOutput = (compileResult.steps || [])
|
|
1808
|
+
.map(s => s.stdout || '')
|
|
1809
|
+
.filter(Boolean)
|
|
1810
|
+
.join('\n')
|
|
1811
|
+
.trim();
|
|
1812
|
+
|
|
1813
|
+
const totalDuration = (compileResult.steps || [])
|
|
1814
|
+
.reduce((sum, s) => sum + (s.duration || 0), 0);
|
|
1815
|
+
|
|
1739
1816
|
TEST_LOCAL_STATE.compilationResult = {
|
|
1740
1817
|
success: false,
|
|
1741
|
-
error:
|
|
1742
|
-
duration:
|
|
1818
|
+
error: errorOutput || 'Compilation failed',
|
|
1819
|
+
duration: totalDuration
|
|
1743
1820
|
};
|
|
1744
1821
|
|
|
1745
1822
|
return res.json({
|
|
1746
1823
|
ok: false,
|
|
1747
|
-
error:
|
|
1748
|
-
stdout:
|
|
1749
|
-
|
|
1824
|
+
error: errorOutput || 'Compilation failed',
|
|
1825
|
+
stdout: stdoutOutput,
|
|
1826
|
+
steps: compileResult.steps,
|
|
1827
|
+
duration: totalDuration
|
|
1750
1828
|
});
|
|
1751
1829
|
}
|
|
1752
1830
|
|
|
1831
|
+
const totalDuration = (compileResult.steps || [])
|
|
1832
|
+
.reduce((sum, s) => sum + (s.duration || 0), 0);
|
|
1833
|
+
|
|
1753
1834
|
TEST_LOCAL_STATE.status = "compiled";
|
|
1754
1835
|
TEST_LOCAL_STATE.compilationResult = {
|
|
1755
1836
|
success: true,
|
|
1756
1837
|
language: meta.language,
|
|
1757
1838
|
buildTool: meta.buildTool,
|
|
1758
|
-
duration:
|
|
1839
|
+
duration: totalDuration
|
|
1759
1840
|
};
|
|
1760
1841
|
|
|
1761
|
-
console.log("
|
|
1842
|
+
console.log("[TEST-LOCAL] Compilation successful");
|
|
1762
1843
|
|
|
1763
1844
|
res.json({
|
|
1764
1845
|
ok: true,
|
|
1765
1846
|
language: meta.language,
|
|
1766
1847
|
buildTool: meta.buildTool,
|
|
1767
|
-
duration:
|
|
1768
|
-
stdout: compileResult.stdout
|
|
1848
|
+
duration: totalDuration,
|
|
1849
|
+
stdout: (compileResult.steps || []).map(s => s.stdout || '').filter(Boolean).join('\n').trim()
|
|
1769
1850
|
});
|
|
1770
1851
|
} catch (err) {
|
|
1771
|
-
console.error("
|
|
1852
|
+
console.error("[TEST-LOCAL] Compilation failed:", err.message);
|
|
1772
1853
|
TEST_LOCAL_STATE.status = "error";
|
|
1773
1854
|
res.status(500).json({ ok: false, error: err.message });
|
|
1774
1855
|
}
|
|
@@ -1821,7 +1902,7 @@ app.post("/workspace/test-local/start", async (req, res) => {
|
|
|
1821
1902
|
const command = 'java';
|
|
1822
1903
|
const args = ['-jar', jarPath, `--server.port=${serverPort}`];
|
|
1823
1904
|
|
|
1824
|
-
console.log(`
|
|
1905
|
+
console.log(`Command: ${command} ${args.join(' ')}`);
|
|
1825
1906
|
|
|
1826
1907
|
// Env LIMPO - remover TODAS as variveis Spring que podem interferir
|
|
1827
1908
|
const cleanEnv = { ...process.env };
|
|
@@ -1851,7 +1932,7 @@ app.post("/workspace/test-local/start", async (req, res) => {
|
|
|
1851
1932
|
TEST_LOCAL_STATE.status = "running";
|
|
1852
1933
|
TEST_LOCAL_STATE.config = { port: serverPort };
|
|
1853
1934
|
|
|
1854
|
-
console.log(`
|
|
1935
|
+
console.log(`[TEST-LOCAL] Server started on port ${serverPort}`);
|
|
1855
1936
|
|
|
1856
1937
|
res.json({
|
|
1857
1938
|
ok: true,
|
|
@@ -1901,7 +1982,7 @@ app.post("/workspace/test-local/start", async (req, res) => {
|
|
|
1901
1982
|
});
|
|
1902
1983
|
}
|
|
1903
1984
|
} catch (err) {
|
|
1904
|
-
console.error("
|
|
1985
|
+
console.error("[TEST-LOCAL] Server start failed:", err.message);
|
|
1905
1986
|
TEST_LOCAL_STATE.status = "error";
|
|
1906
1987
|
res.status(500).json({ ok: false, error: err.message });
|
|
1907
1988
|
}
|
|
@@ -1913,19 +1994,19 @@ app.post("/workspace/test-local/start", async (req, res) => {
|
|
|
1913
1994
|
*/
|
|
1914
1995
|
app.post("/workspace/test-local/stop", async (req, res) => {
|
|
1915
1996
|
try {
|
|
1916
|
-
console.log("
|
|
1997
|
+
console.log("[TEST-LOCAL] Stopping server...");
|
|
1917
1998
|
|
|
1918
1999
|
await processManager.stop('test-local');
|
|
1919
2000
|
TEST_LOCAL_STATE.status = "stopped";
|
|
1920
2001
|
|
|
1921
|
-
console.log("
|
|
2002
|
+
console.log("[TEST-LOCAL] Server stopped");
|
|
1922
2003
|
|
|
1923
2004
|
res.json({
|
|
1924
2005
|
ok: true,
|
|
1925
2006
|
status: "stopped"
|
|
1926
2007
|
});
|
|
1927
2008
|
} catch (err) {
|
|
1928
|
-
console.error("
|
|
2009
|
+
console.error("[TEST-LOCAL] Server stop failed:", err.message);
|
|
1929
2010
|
res.status(500).json({ ok: false, error: err.message });
|
|
1930
2011
|
}
|
|
1931
2012
|
});
|
|
@@ -1939,7 +2020,7 @@ app.get("/workspace/test-local/endpoints", async (req, res) => {
|
|
|
1939
2020
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
1940
2021
|
|
|
1941
2022
|
try {
|
|
1942
|
-
console.log("
|
|
2023
|
+
console.log("[TEST-LOCAL] Getting endpoints...");
|
|
1943
2024
|
|
|
1944
2025
|
// If endpoints are cached, return them
|
|
1945
2026
|
if (TEST_LOCAL_STATE.endpoints && TEST_LOCAL_STATE.endpoints.length > 0) {
|
|
@@ -1962,7 +2043,7 @@ app.get("/workspace/test-local/endpoints", async (req, res) => {
|
|
|
1962
2043
|
|
|
1963
2044
|
TEST_LOCAL_STATE.endpoints = payloadDocs.endpoints;
|
|
1964
2045
|
|
|
1965
|
-
console.log(`
|
|
2046
|
+
console.log(`[TEST-LOCAL] Discovered ${payloadDocs.endpoints.length} endpoints`);
|
|
1966
2047
|
|
|
1967
2048
|
res.json({
|
|
1968
2049
|
ok: true,
|
|
@@ -1970,7 +2051,7 @@ app.get("/workspace/test-local/endpoints", async (req, res) => {
|
|
|
1970
2051
|
cached: false
|
|
1971
2052
|
});
|
|
1972
2053
|
} catch (err) {
|
|
1973
|
-
console.error("
|
|
2054
|
+
console.error("[TEST-LOCAL] Failed to get endpoints:", err.message);
|
|
1974
2055
|
res.status(500).json({ ok: false, error: err.message });
|
|
1975
2056
|
}
|
|
1976
2057
|
});
|
|
@@ -1990,7 +2071,7 @@ app.post("/workspace/test-local/execute", async (req, res) => {
|
|
|
1990
2071
|
const serverPort = port || TEST_LOCAL_STATE.config?.server?.port || 8080;
|
|
1991
2072
|
const url = `http://localhost:${serverPort}${reqPath}`;
|
|
1992
2073
|
|
|
1993
|
-
console.log(`
|
|
2074
|
+
console.log(`[TEST-LOCAL] Executing: ${method} ${url}`);
|
|
1994
2075
|
|
|
1995
2076
|
const startTime = Date.now();
|
|
1996
2077
|
|
|
@@ -2039,14 +2120,14 @@ app.post("/workspace/test-local/execute", async (req, res) => {
|
|
|
2039
2120
|
TEST_LOCAL_STATE.testResults.shift();
|
|
2040
2121
|
}
|
|
2041
2122
|
|
|
2042
|
-
console.log(`
|
|
2123
|
+
console.log(`[TEST-LOCAL] Test result: ${response.status} (${duration}ms)`);
|
|
2043
2124
|
|
|
2044
2125
|
res.json({
|
|
2045
2126
|
ok: true,
|
|
2046
2127
|
result
|
|
2047
2128
|
});
|
|
2048
2129
|
} catch (err) {
|
|
2049
|
-
console.error("
|
|
2130
|
+
console.error("[TEST-LOCAL] Test execution failed:", err.message);
|
|
2050
2131
|
res.json({
|
|
2051
2132
|
ok: false,
|
|
2052
2133
|
error: err.message,
|
|
@@ -2065,7 +2146,7 @@ app.post("/workspace/test-local/execute", async (req, res) => {
|
|
|
2065
2146
|
* Returns stored test results
|
|
2066
2147
|
*/
|
|
2067
2148
|
app.get("/workspace/test-local/results", (req, res) => {
|
|
2068
|
-
console.log("
|
|
2149
|
+
console.log("[TEST-LOCAL] Getting test results...");
|
|
2069
2150
|
|
|
2070
2151
|
res.json({
|
|
2071
2152
|
ok: true,
|
|
@@ -2079,7 +2160,7 @@ app.get("/workspace/test-local/results", (req, res) => {
|
|
|
2079
2160
|
* Clears stored test results
|
|
2080
2161
|
*/
|
|
2081
2162
|
app.post("/workspace/test-local/clear-results", (req, res) => {
|
|
2082
|
-
console.log("
|
|
2163
|
+
console.log("[TEST-LOCAL] Clearing test results...");
|
|
2083
2164
|
|
|
2084
2165
|
TEST_LOCAL_STATE.testResults = [];
|
|
2085
2166
|
|
|
@@ -2096,7 +2177,7 @@ app.post("/workspace/test-local/clear-results", (req, res) => {
|
|
|
2096
2177
|
app.get("/workspace/test-local/logs", (req, res) => {
|
|
2097
2178
|
const limit = parseInt(req.query.limit) || 500;
|
|
2098
2179
|
|
|
2099
|
-
console.log(`
|
|
2180
|
+
console.log(`[TEST-LOCAL] Getting logs (limit: ${limit})`);
|
|
2100
2181
|
|
|
2101
2182
|
const logs = TEST_LOCAL_STATE.serverLogs.slice(-limit);
|
|
2102
2183
|
|
|
@@ -2115,7 +2196,7 @@ app.get("/workspace/controllers", async (req, res) => {
|
|
|
2115
2196
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
2116
2197
|
|
|
2117
2198
|
try {
|
|
2118
|
-
console.log("
|
|
2199
|
+
console.log("[CONTROLLERS] Discovering controllers...");
|
|
2119
2200
|
|
|
2120
2201
|
const scanner = new WorkspaceScanner(wsRoot);
|
|
2121
2202
|
const structure = await scanner.scan();
|
|
@@ -2123,14 +2204,14 @@ app.get("/workspace/controllers", async (req, res) => {
|
|
|
2123
2204
|
const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
|
|
2124
2205
|
const apiDocs = await controllerAnalyzer.generateApiDocs(structure.files);
|
|
2125
2206
|
|
|
2126
|
-
console.log(`
|
|
2207
|
+
console.log(`[CONTROLLERS] Found ${apiDocs.totalEndpoints} endpoints in ${apiDocs.totalControllers} controllers`);
|
|
2127
2208
|
|
|
2128
2209
|
res.json({
|
|
2129
2210
|
ok: true,
|
|
2130
2211
|
...apiDocs
|
|
2131
2212
|
});
|
|
2132
2213
|
} catch (err) {
|
|
2133
|
-
console.error("
|
|
2214
|
+
console.error("[CONTROLLERS] Failed:", err.message);
|
|
2134
2215
|
res.status(500).json({ ok: false, error: err.message });
|
|
2135
2216
|
}
|
|
2136
2217
|
});
|
|
@@ -2141,7 +2222,7 @@ app.get("/workspace/dtos", async (req, res) => {
|
|
|
2141
2222
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
2142
2223
|
|
|
2143
2224
|
try {
|
|
2144
|
-
console.log("
|
|
2225
|
+
console.log("[DTOS] Analyzing DTOs...");
|
|
2145
2226
|
|
|
2146
2227
|
const scanner = new WorkspaceScanner(wsRoot);
|
|
2147
2228
|
const structure = await scanner.scan();
|
|
@@ -2152,14 +2233,14 @@ app.get("/workspace/dtos", async (req, res) => {
|
|
|
2152
2233
|
const dtoAnalyzer = new DTOAnalyzer(wsRoot);
|
|
2153
2234
|
const payloadDocs = await dtoAnalyzer.generatePayloadDocs(structure.files, apiDocs.endpoints);
|
|
2154
2235
|
|
|
2155
|
-
console.log(`
|
|
2236
|
+
console.log(`[DTOS] Found ${payloadDocs.totalDtos} DTOs`);
|
|
2156
2237
|
|
|
2157
2238
|
res.json({
|
|
2158
2239
|
ok: true,
|
|
2159
2240
|
...payloadDocs
|
|
2160
2241
|
});
|
|
2161
2242
|
} catch (err) {
|
|
2162
|
-
console.error("
|
|
2243
|
+
console.error("[DTOS] Failed:", err.message);
|
|
2163
2244
|
res.status(500).json({ ok: false, error: err.message });
|
|
2164
2245
|
}
|
|
2165
2246
|
});
|
|
@@ -2170,19 +2251,19 @@ app.get("/workspace/config", async (req, res) => {
|
|
|
2170
2251
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
2171
2252
|
|
|
2172
2253
|
try {
|
|
2173
|
-
console.log("
|
|
2254
|
+
console.log("[CONFIG] Analyzing configuration...");
|
|
2174
2255
|
|
|
2175
2256
|
const configAnalyzer = new ConfigAnalyzer(wsRoot);
|
|
2176
2257
|
const config = await configAnalyzer.analyze();
|
|
2177
2258
|
|
|
2178
|
-
console.log(`
|
|
2259
|
+
console.log(`[CONFIG] Server port: ${config.server.port}`);
|
|
2179
2260
|
|
|
2180
2261
|
res.json({
|
|
2181
2262
|
ok: true,
|
|
2182
2263
|
...config
|
|
2183
2264
|
});
|
|
2184
2265
|
} catch (err) {
|
|
2185
|
-
console.error("
|
|
2266
|
+
console.error("[CONFIG] Failed:", err.message);
|
|
2186
2267
|
res.status(500).json({ ok: false, error: err.message });
|
|
2187
2268
|
}
|
|
2188
2269
|
});
|
|
@@ -2251,7 +2332,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
|
|
|
2251
2332
|
const { diff, incidentId } = req.body || {};
|
|
2252
2333
|
if (!diff) return res.status(400).json({ error: "diff is required" });
|
|
2253
2334
|
|
|
2254
|
-
console.log(`
|
|
2335
|
+
console.log(`Safe patch requested for incident: ${incidentId || 'unknown'}`);
|
|
2255
2336
|
|
|
2256
2337
|
try {
|
|
2257
2338
|
// 1. Validate diff
|
|
@@ -2266,7 +2347,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
|
|
|
2266
2347
|
|
|
2267
2348
|
// 2. Extract target files
|
|
2268
2349
|
const targetFiles = extractTargetFiles(diff);
|
|
2269
|
-
console.log(`
|
|
2350
|
+
console.log(`Target files: ${targetFiles.join(', ')}`);
|
|
2270
2351
|
|
|
2271
2352
|
// 3. Create backup
|
|
2272
2353
|
const backupId = `backup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -2297,23 +2378,23 @@ app.post("/workspace/safe-patch", async (req, res) => {
|
|
|
2297
2378
|
}
|
|
2298
2379
|
saveBackupIndex();
|
|
2299
2380
|
} catch (e) {
|
|
2300
|
-
console.warn(`
|
|
2381
|
+
console.warn(`Could not persist backup to disk: ${e.message}`);
|
|
2301
2382
|
}
|
|
2302
2383
|
|
|
2303
2384
|
// Cleanup old backups if exceeded max
|
|
2304
2385
|
if (BACKUPS.size > MAX_BACKUPS) {
|
|
2305
2386
|
const oldest = Array.from(BACKUPS.keys())[0];
|
|
2306
2387
|
BACKUPS.delete(oldest);
|
|
2307
|
-
console.log(`
|
|
2388
|
+
console.log(`Removed old backup: ${oldest}`);
|
|
2308
2389
|
saveBackupIndex();
|
|
2309
2390
|
}
|
|
2310
2391
|
|
|
2311
|
-
console.log(`
|
|
2392
|
+
console.log(`Backup created: ${backupId} (${backupFiles.length} files)`);
|
|
2312
2393
|
|
|
2313
2394
|
// 4. Apply patch
|
|
2314
2395
|
try {
|
|
2315
2396
|
const result = await applyUnifiedDiff(wsRoot, diff);
|
|
2316
|
-
console.log(`
|
|
2397
|
+
console.log(`Patch applied successfully: ${result.target}`);
|
|
2317
2398
|
|
|
2318
2399
|
res.json({
|
|
2319
2400
|
ok: true,
|
|
@@ -2324,7 +2405,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
|
|
|
2324
2405
|
});
|
|
2325
2406
|
} catch (patchError) {
|
|
2326
2407
|
// 5. Rollback on failure
|
|
2327
|
-
console.error(`
|
|
2408
|
+
console.error(`Patch failed, rolling back: ${patchError.message}`);
|
|
2328
2409
|
|
|
2329
2410
|
for (const file of backupFiles) {
|
|
2330
2411
|
const fullPath = path.join(wsRoot, file.path);
|
|
@@ -2341,7 +2422,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
|
|
|
2341
2422
|
});
|
|
2342
2423
|
}
|
|
2343
2424
|
} catch (err) {
|
|
2344
|
-
console.error(`
|
|
2425
|
+
console.error(`Safe patch error: ${err.message}`);
|
|
2345
2426
|
res.status(500).json({ ok: false, error: err.message });
|
|
2346
2427
|
}
|
|
2347
2428
|
});
|
|
@@ -2362,7 +2443,7 @@ app.post("/workspace/patch/dry-run", async (req, res) => {
|
|
|
2362
2443
|
const { diff } = req.body || {};
|
|
2363
2444
|
if (!diff) return res.status(400).json({ error: "diff is required" });
|
|
2364
2445
|
|
|
2365
|
-
console.log(`
|
|
2446
|
+
console.log(`Dry-run patch validation requested`);
|
|
2366
2447
|
|
|
2367
2448
|
try {
|
|
2368
2449
|
// 1. Validate diff format
|
|
@@ -2413,7 +2494,7 @@ app.post("/workspace/patch/dry-run", async (req, res) => {
|
|
|
2413
2494
|
const allFilesExist = fileChecks.every(f => f.exists || diff.includes('--- /dev/null'));
|
|
2414
2495
|
const missingFiles = fileChecks.filter(f => !f.exists && !diff.includes('--- /dev/null'));
|
|
2415
2496
|
|
|
2416
|
-
console.log(`
|
|
2497
|
+
console.log(`Dry-run complete: ${targetFiles.length} files, ${hunkCount} hunks`);
|
|
2417
2498
|
|
|
2418
2499
|
res.json({
|
|
2419
2500
|
ok: true,
|
|
@@ -2426,7 +2507,7 @@ app.post("/workspace/patch/dry-run", async (req, res) => {
|
|
|
2426
2507
|
warnings: missingFiles.length > 0 ? [`${missingFiles.length} file(s) not found`] : []
|
|
2427
2508
|
});
|
|
2428
2509
|
} catch (err) {
|
|
2429
|
-
console.error(`
|
|
2510
|
+
console.error(`Dry-run error: ${err.message}`);
|
|
2430
2511
|
res.status(500).json({ ok: false, error: err.message });
|
|
2431
2512
|
}
|
|
2432
2513
|
});
|
|
@@ -2494,7 +2575,7 @@ app.post("/workspace/backup", async (req, res) => {
|
|
|
2494
2575
|
incidentId: incidentId || null
|
|
2495
2576
|
});
|
|
2496
2577
|
|
|
2497
|
-
console.log(`
|
|
2578
|
+
console.log(`Manual backup created: ${backupId}`);
|
|
2498
2579
|
|
|
2499
2580
|
res.json({
|
|
2500
2581
|
ok: true,
|
|
@@ -2503,7 +2584,7 @@ app.post("/workspace/backup", async (req, res) => {
|
|
|
2503
2584
|
timestamp: new Date().toISOString()
|
|
2504
2585
|
});
|
|
2505
2586
|
} catch (err) {
|
|
2506
|
-
console.error(`
|
|
2587
|
+
console.error(`Backup error: ${err.message}`);
|
|
2507
2588
|
res.status(500).json({ ok: false, error: err.message });
|
|
2508
2589
|
}
|
|
2509
2590
|
});
|
|
@@ -2535,14 +2616,14 @@ app.post("/workspace/rollback", async (req, res) => {
|
|
|
2535
2616
|
}
|
|
2536
2617
|
|
|
2537
2618
|
try {
|
|
2538
|
-
console.log(`
|
|
2619
|
+
console.log(`Rolling back to backup: ${backupId}`);
|
|
2539
2620
|
|
|
2540
2621
|
for (const file of backup.files) {
|
|
2541
2622
|
const fullPath = path.join(wsRoot, file.path);
|
|
2542
2623
|
await writeFile(fullPath, file.content, 'utf8');
|
|
2543
2624
|
}
|
|
2544
2625
|
|
|
2545
|
-
console.log(`
|
|
2626
|
+
console.log(`Rollback completed: ${backup.files.length} files restored`);
|
|
2546
2627
|
|
|
2547
2628
|
res.json({
|
|
2548
2629
|
ok: true,
|
|
@@ -2551,7 +2632,7 @@ app.post("/workspace/rollback", async (req, res) => {
|
|
|
2551
2632
|
timestamp: backup.timestamp
|
|
2552
2633
|
});
|
|
2553
2634
|
} catch (err) {
|
|
2554
|
-
console.error(`
|
|
2635
|
+
console.error(`Rollback error: ${err.message}`);
|
|
2555
2636
|
res.status(500).json({ ok: false, error: err.message });
|
|
2556
2637
|
}
|
|
2557
2638
|
});
|
|
@@ -2591,7 +2672,7 @@ app.delete("/workspace/backups/:backupId", (req, res) => {
|
|
|
2591
2672
|
}
|
|
2592
2673
|
|
|
2593
2674
|
BACKUPS.delete(backupId);
|
|
2594
|
-
console.log(`
|
|
2675
|
+
console.log(`Backup deleted: ${backupId}`);
|
|
2595
2676
|
|
|
2596
2677
|
res.json({
|
|
2597
2678
|
ok: true,
|
|
@@ -2650,7 +2731,7 @@ app.get("/workspace/diff/by-incident/:incidentId", async (req, res) => {
|
|
|
2650
2731
|
}
|
|
2651
2732
|
|
|
2652
2733
|
if (!matchedBackup) {
|
|
2653
|
-
console.log(`
|
|
2734
|
+
console.log(`No backup found for incident ${incidentId}. Available backups:`, Array.from(BACKUPS.keys()));
|
|
2654
2735
|
return res.status(404).json({
|
|
2655
2736
|
ok: false,
|
|
2656
2737
|
error: "No backup found for this incident",
|
|
@@ -2659,7 +2740,7 @@ app.get("/workspace/diff/by-incident/:incidentId", async (req, res) => {
|
|
|
2659
2740
|
});
|
|
2660
2741
|
}
|
|
2661
2742
|
|
|
2662
|
-
console.log(`
|
|
2743
|
+
console.log(`Found backup ${matchedBackupId} for incident ${incidentId}`);
|
|
2663
2744
|
|
|
2664
2745
|
// Reuse the diff logic
|
|
2665
2746
|
try {
|
|
@@ -2696,7 +2777,7 @@ app.get("/workspace/diff/by-incident/:incidentId", async (req, res) => {
|
|
|
2696
2777
|
files: diffs
|
|
2697
2778
|
});
|
|
2698
2779
|
} catch (err) {
|
|
2699
|
-
console.error(`
|
|
2780
|
+
console.error(`Diff error: ${err.message}`);
|
|
2700
2781
|
res.status(500).json({ ok: false, error: err.message });
|
|
2701
2782
|
}
|
|
2702
2783
|
});
|
|
@@ -2768,7 +2849,7 @@ app.get("/workspace/diff/:backupId", async (req, res) => {
|
|
|
2768
2849
|
}
|
|
2769
2850
|
}
|
|
2770
2851
|
|
|
2771
|
-
console.log(`
|
|
2852
|
+
console.log(`Diff for ${backupId}: ${diffs.length} file(s) changed`);
|
|
2772
2853
|
|
|
2773
2854
|
res.json({
|
|
2774
2855
|
ok: true,
|
|
@@ -2780,7 +2861,7 @@ app.get("/workspace/diff/:backupId", async (req, res) => {
|
|
|
2780
2861
|
files: diffs
|
|
2781
2862
|
});
|
|
2782
2863
|
} catch (err) {
|
|
2783
|
-
console.error(`
|
|
2864
|
+
console.error(`Diff error: ${err.message}`);
|
|
2784
2865
|
res.status(500).json({ ok: false, error: err.message });
|
|
2785
2866
|
}
|
|
2786
2867
|
});
|
|
@@ -2813,7 +2894,7 @@ app.get("/workspace/detect-port", async (req, res) => {
|
|
|
2813
2894
|
|
|
2814
2895
|
try {
|
|
2815
2896
|
const fullPath = path.join(wsRoot, servicePath);
|
|
2816
|
-
console.log(`
|
|
2897
|
+
console.log(`Detecting port for service at: ${fullPath}`);
|
|
2817
2898
|
|
|
2818
2899
|
let port = null;
|
|
2819
2900
|
let detectionMethod = null;
|
|
@@ -2848,7 +2929,7 @@ app.get("/workspace/detect-port", async (req, res) => {
|
|
|
2848
2929
|
detectionMethod = 'global-detection';
|
|
2849
2930
|
}
|
|
2850
2931
|
|
|
2851
|
-
console.log(`
|
|
2932
|
+
console.log(`Detected port: ${port || 'default'} via ${detectionMethod}`);
|
|
2852
2933
|
|
|
2853
2934
|
res.json({
|
|
2854
2935
|
ok: true,
|
|
@@ -2859,7 +2940,7 @@ app.get("/workspace/detect-port", async (req, res) => {
|
|
|
2859
2940
|
servicePath: servicePath || '/'
|
|
2860
2941
|
});
|
|
2861
2942
|
} catch (err) {
|
|
2862
|
-
console.error('
|
|
2943
|
+
console.error('Error detecting port:', err.message);
|
|
2863
2944
|
res.status(500).json({ ok: false, error: err.message });
|
|
2864
2945
|
}
|
|
2865
2946
|
});
|
|
@@ -3070,7 +3151,7 @@ app.post("/workspace/test-local/prepare", async (req, res) => {
|
|
|
3070
3151
|
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
3071
3152
|
|
|
3072
3153
|
try {
|
|
3073
|
-
console.log("
|
|
3154
|
+
console.log("[TEST-LOCAL] Preparing test environment...");
|
|
3074
3155
|
TEST_LOCAL_STATE.status = "compiling";
|
|
3075
3156
|
|
|
3076
3157
|
// Step 1: Compile
|
|
@@ -3110,7 +3191,7 @@ app.post("/workspace/test-local/prepare", async (req, res) => {
|
|
|
3110
3191
|
const config = await configAnalyzer.analyze();
|
|
3111
3192
|
TEST_LOCAL_STATE.config = config;
|
|
3112
3193
|
|
|
3113
|
-
console.log(`
|
|
3194
|
+
console.log(`[TEST-LOCAL] Prepared: ${payloadDocs.endpoints.length} endpoints discovered`);
|
|
3114
3195
|
|
|
3115
3196
|
res.json({
|
|
3116
3197
|
ok: true,
|
|
@@ -3133,7 +3214,7 @@ app.post("/workspace/test-local/prepare", async (req, res) => {
|
|
|
3133
3214
|
endpoints: payloadDocs.endpoints
|
|
3134
3215
|
});
|
|
3135
3216
|
} catch (err) {
|
|
3136
|
-
console.error("
|
|
3217
|
+
console.error("[TEST-LOCAL] Prepare failed:", err.message);
|
|
3137
3218
|
TEST_LOCAL_STATE.status = "error";
|
|
3138
3219
|
res.status(500).json({ ok: false, error: err.message });
|
|
3139
3220
|
}
|
|
@@ -3335,9 +3416,9 @@ app.post("/workspace/scan-files", async (req, res) => {
|
|
|
3335
3416
|
});
|
|
3336
3417
|
}
|
|
3337
3418
|
|
|
3338
|
-
console.log(`
|
|
3339
|
-
console.log(`
|
|
3340
|
-
console.log(`
|
|
3419
|
+
console.log(`[SCAN-FILES] Scanning: ${workspaceRoot}`);
|
|
3420
|
+
console.log(`Extensions: ${includeExtensions.join(", ")}`);
|
|
3421
|
+
console.log(`Exclude: ${excludePatterns.join(", ")}`);
|
|
3341
3422
|
|
|
3342
3423
|
try {
|
|
3343
3424
|
const files = [];
|
|
@@ -3387,13 +3468,13 @@ app.post("/workspace/scan-files", async (req, res) => {
|
|
|
3387
3468
|
}
|
|
3388
3469
|
}
|
|
3389
3470
|
} catch (error) {
|
|
3390
|
-
console.warn(`
|
|
3471
|
+
console.warn(`Cannot read directory ${dir}: ${error.message}`);
|
|
3391
3472
|
}
|
|
3392
3473
|
}
|
|
3393
3474
|
|
|
3394
3475
|
await scanDir(workspaceRoot);
|
|
3395
3476
|
|
|
3396
|
-
console.log(`
|
|
3477
|
+
console.log(`[SCAN-FILES] Found ${files.length} files`);
|
|
3397
3478
|
|
|
3398
3479
|
res.json({
|
|
3399
3480
|
success: true,
|
|
@@ -3403,7 +3484,7 @@ app.post("/workspace/scan-files", async (req, res) => {
|
|
|
3403
3484
|
});
|
|
3404
3485
|
|
|
3405
3486
|
} catch (error) {
|
|
3406
|
-
console.error(`
|
|
3487
|
+
console.error(`[SCAN-FILES] Error: ${error.message}`);
|
|
3407
3488
|
res.status(500).json({
|
|
3408
3489
|
success: false,
|
|
3409
3490
|
error: error.message,
|
|
@@ -3432,7 +3513,7 @@ app.post("/workspace/read-file", async (req, res) => {
|
|
|
3432
3513
|
});
|
|
3433
3514
|
}
|
|
3434
3515
|
|
|
3435
|
-
console.log(`
|
|
3516
|
+
console.log(`[READ-FILE] Reading: ${filePath}`);
|
|
3436
3517
|
|
|
3437
3518
|
try {
|
|
3438
3519
|
const fs = await import("fs/promises");
|
|
@@ -3463,7 +3544,7 @@ app.post("/workspace/read-file", async (req, res) => {
|
|
|
3463
3544
|
});
|
|
3464
3545
|
|
|
3465
3546
|
} catch (error) {
|
|
3466
|
-
console.error(`
|
|
3547
|
+
console.error(`[READ-FILE] Error: ${error.message}`);
|
|
3467
3548
|
res.status(404).json({
|
|
3468
3549
|
success: false,
|
|
3469
3550
|
error: error.message,
|
|
@@ -3492,7 +3573,7 @@ app.post("/workspace/read-files", async (req, res) => {
|
|
|
3492
3573
|
});
|
|
3493
3574
|
}
|
|
3494
3575
|
|
|
3495
|
-
console.log(`
|
|
3576
|
+
console.log(`[READ-FILES] Reading ${filePaths.length} files`);
|
|
3496
3577
|
|
|
3497
3578
|
try {
|
|
3498
3579
|
const fs = await import("fs/promises");
|
|
@@ -3519,7 +3600,7 @@ app.post("/workspace/read-files", async (req, res) => {
|
|
|
3519
3600
|
}
|
|
3520
3601
|
}
|
|
3521
3602
|
|
|
3522
|
-
console.log(`
|
|
3603
|
+
console.log(`[READ-FILES] Read ${Object.keys(files).length} files, ${errors.length} errors`);
|
|
3523
3604
|
|
|
3524
3605
|
res.json({
|
|
3525
3606
|
success: true,
|
|
@@ -3529,7 +3610,7 @@ app.post("/workspace/read-files", async (req, res) => {
|
|
|
3529
3610
|
});
|
|
3530
3611
|
|
|
3531
3612
|
} catch (error) {
|
|
3532
|
-
console.error(`
|
|
3613
|
+
console.error(`[READ-FILES] Error: ${error.message}`);
|
|
3533
3614
|
res.status(500).json({
|
|
3534
3615
|
success: false,
|
|
3535
3616
|
error: error.message
|
|
@@ -3547,7 +3628,7 @@ app.post("/workspace/read-files", async (req, res) => {
|
|
|
3547
3628
|
* Abre o file picker nativo do sistema operacional
|
|
3548
3629
|
*/
|
|
3549
3630
|
app.get("/system/folder-picker", async (req, res) => {
|
|
3550
|
-
console.log(`
|
|
3631
|
+
console.log(`[FOLDER-PICKER] Opening native folder picker...`);
|
|
3551
3632
|
|
|
3552
3633
|
try {
|
|
3553
3634
|
const platform = process.platform;
|
|
@@ -3630,7 +3711,7 @@ app.get("/system/folder-picker", async (req, res) => {
|
|
|
3630
3711
|
selectedPath = selectedPath.slice(0, -1);
|
|
3631
3712
|
}
|
|
3632
3713
|
|
|
3633
|
-
console.log(`
|
|
3714
|
+
console.log(`[FOLDER-PICKER] Selected: ${selectedPath}`);
|
|
3634
3715
|
|
|
3635
3716
|
res.json({
|
|
3636
3717
|
success: true,
|
|
@@ -3639,7 +3720,7 @@ app.get("/system/folder-picker", async (req, res) => {
|
|
|
3639
3720
|
});
|
|
3640
3721
|
|
|
3641
3722
|
} catch (error) {
|
|
3642
|
-
console.error(`
|
|
3723
|
+
console.error(`[FOLDER-PICKER] Error:`, error.message);
|
|
3643
3724
|
res.status(500).json({
|
|
3644
3725
|
success: false,
|
|
3645
3726
|
error: error.message,
|
|
@@ -3681,7 +3762,7 @@ app.get("/workspace/api-docs", async (req, res) => {
|
|
|
3681
3762
|
});
|
|
3682
3763
|
}
|
|
3683
3764
|
|
|
3684
|
-
console.log(`
|
|
3765
|
+
console.log(`[API-DOCS] Analyzing controllers in ${wsRoot}`);
|
|
3685
3766
|
|
|
3686
3767
|
try {
|
|
3687
3768
|
// Primeiro, escanear arquivos do workspace
|
|
@@ -3720,13 +3801,13 @@ app.get("/workspace/api-docs", async (req, res) => {
|
|
|
3720
3801
|
|
|
3721
3802
|
await scanDir(wsRoot);
|
|
3722
3803
|
|
|
3723
|
-
console.log(`
|
|
3804
|
+
console.log(`[API-DOCS] Found ${files.length} Java files`);
|
|
3724
3805
|
|
|
3725
3806
|
// Agora analisar os controllers
|
|
3726
3807
|
const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
|
|
3727
3808
|
const apiDocs = await controllerAnalyzer.generateApiDocs(files);
|
|
3728
3809
|
|
|
3729
|
-
console.log(`
|
|
3810
|
+
console.log(`[API-DOCS] Found ${apiDocs.totalEndpoints} endpoints in ${apiDocs.totalControllers} controllers`);
|
|
3730
3811
|
|
|
3731
3812
|
res.json({
|
|
3732
3813
|
success: true,
|
|
@@ -3738,7 +3819,7 @@ app.get("/workspace/api-docs", async (req, res) => {
|
|
|
3738
3819
|
});
|
|
3739
3820
|
|
|
3740
3821
|
} catch (error) {
|
|
3741
|
-
console.error(`
|
|
3822
|
+
console.error(`[API-DOCS] Error:`, error.message);
|
|
3742
3823
|
|
|
3743
3824
|
res.json({
|
|
3744
3825
|
success: false,
|
|
@@ -3765,7 +3846,7 @@ app.get("/workspace/api-docs/:controller", async (req, res) => {
|
|
|
3765
3846
|
}
|
|
3766
3847
|
|
|
3767
3848
|
const { controller } = req.params;
|
|
3768
|
-
console.log(`
|
|
3849
|
+
console.log(`[API-DOCS] Getting endpoints for controller: ${controller}`);
|
|
3769
3850
|
|
|
3770
3851
|
try {
|
|
3771
3852
|
// Primeiro, escanear arquivos do workspace
|
|
@@ -3840,7 +3921,7 @@ app.get("/workspace/api-docs/:controller", async (req, res) => {
|
|
|
3840
3921
|
});
|
|
3841
3922
|
|
|
3842
3923
|
} catch (error) {
|
|
3843
|
-
console.error(`
|
|
3924
|
+
console.error(`[API-DOCS] Error:`, error.message);
|
|
3844
3925
|
res.status(500).json({
|
|
3845
3926
|
success: false,
|
|
3846
3927
|
error: error.message
|
|
@@ -3930,7 +4011,7 @@ app.get("/workspace/smart-config", async (req, res) => {
|
|
|
3930
4011
|
return res.status(400).json({ error: "workspace not set" });
|
|
3931
4012
|
}
|
|
3932
4013
|
|
|
3933
|
-
console.log("
|
|
4014
|
+
console.log("[SMART-CONFIG] Collecting configuration files...");
|
|
3934
4015
|
|
|
3935
4016
|
try {
|
|
3936
4017
|
const configPatterns = [
|
|
@@ -3964,9 +4045,9 @@ app.get("/workspace/smart-config", async (req, res) => {
|
|
|
3964
4045
|
: content,
|
|
3965
4046
|
size: content.length
|
|
3966
4047
|
});
|
|
3967
|
-
console.log(`
|
|
4048
|
+
console.log(`Found: ${pattern}`);
|
|
3968
4049
|
} catch (err) {
|
|
3969
|
-
console.error(`
|
|
4050
|
+
console.error(`Error reading ${pattern}: ${err.message}`);
|
|
3970
4051
|
}
|
|
3971
4052
|
}
|
|
3972
4053
|
}
|
|
@@ -3982,7 +4063,7 @@ app.get("/workspace/smart-config", async (req, res) => {
|
|
|
3982
4063
|
});
|
|
3983
4064
|
}
|
|
3984
4065
|
|
|
3985
|
-
console.log(`
|
|
4066
|
+
console.log(`Collected ${collectedFiles.length} files, profiles: ${availableProfiles.join(', ')}`);
|
|
3986
4067
|
|
|
3987
4068
|
res.json({
|
|
3988
4069
|
ok: true,
|
|
@@ -3996,7 +4077,7 @@ app.get("/workspace/smart-config", async (req, res) => {
|
|
|
3996
4077
|
});
|
|
3997
4078
|
|
|
3998
4079
|
} catch (err) {
|
|
3999
|
-
console.error("
|
|
4080
|
+
console.error("[SMART-CONFIG] Error:", err.message);
|
|
4000
4081
|
res.status(500).json({ ok: false, error: err.message });
|
|
4001
4082
|
}
|
|
4002
4083
|
});
|
|
@@ -4007,7 +4088,7 @@ app.get("/workspace/smart-config", async (req, res) => {
|
|
|
4007
4088
|
*/
|
|
4008
4089
|
app.post("/workspace/test-local/restart", async (req, res) => {
|
|
4009
4090
|
try {
|
|
4010
|
-
console.log("
|
|
4091
|
+
console.log("[TEST-LOCAL] Restarting server...");
|
|
4011
4092
|
|
|
4012
4093
|
// Stop
|
|
4013
4094
|
await processManager.stop('test-local');
|
|
@@ -4052,7 +4133,7 @@ app.post("/workspace/test-local/restart", async (req, res) => {
|
|
|
4052
4133
|
}
|
|
4053
4134
|
|
|
4054
4135
|
} catch (err) {
|
|
4055
|
-
console.error("
|
|
4136
|
+
console.error("[TEST-LOCAL] Restart failed:", err.message);
|
|
4056
4137
|
res.status(500).json({ ok: false, error: err.message });
|
|
4057
4138
|
}
|
|
4058
4139
|
});
|
|
@@ -4086,7 +4167,7 @@ app.post("/workspace/search", async (req, res) => {
|
|
|
4086
4167
|
return res.status(400).json({ ok: false, error: "pattern is required" });
|
|
4087
4168
|
}
|
|
4088
4169
|
|
|
4089
|
-
console.log(`
|
|
4170
|
+
console.log(`[workspace/search] pattern="${pattern}" filter="${fileFilter || '*'}"`);
|
|
4090
4171
|
|
|
4091
4172
|
try {
|
|
4092
4173
|
const { execSync } = await import('child_process');
|
|
@@ -4144,7 +4225,7 @@ app.post("/workspace/search", async (req, res) => {
|
|
|
4144
4225
|
.join('\n');
|
|
4145
4226
|
|
|
4146
4227
|
const count = results.split('\n').filter(l => l.trim()).length;
|
|
4147
|
-
console.log(`
|
|
4228
|
+
console.log(`Found ${count} matches`);
|
|
4148
4229
|
|
|
4149
4230
|
res.json({
|
|
4150
4231
|
ok: true,
|
|
@@ -4152,7 +4233,7 @@ app.post("/workspace/search", async (req, res) => {
|
|
|
4152
4233
|
count: count
|
|
4153
4234
|
});
|
|
4154
4235
|
} catch (err) {
|
|
4155
|
-
console.error(`
|
|
4236
|
+
console.error(`[workspace/search] Error:`, err.message);
|
|
4156
4237
|
res.status(500).json({ ok: false, error: err.message });
|
|
4157
4238
|
}
|
|
4158
4239
|
});
|
|
@@ -4192,14 +4273,14 @@ app.post("/workspace/exec", async (req, res) => {
|
|
|
4192
4273
|
];
|
|
4193
4274
|
const lowerCmd = command.toLowerCase().trim();
|
|
4194
4275
|
if (dangerous.some(d => lowerCmd.includes(d))) {
|
|
4195
|
-
console.warn(`
|
|
4276
|
+
console.warn(`[workspace/exec] BLOCKED dangerous command: ${command}`);
|
|
4196
4277
|
return res.status(403).json({
|
|
4197
4278
|
ok: false,
|
|
4198
4279
|
error: "Command blocked for security reasons"
|
|
4199
4280
|
});
|
|
4200
4281
|
}
|
|
4201
4282
|
|
|
4202
|
-
console.log(`
|
|
4283
|
+
console.log(`[workspace/exec] Running: ${command.substring(0, 120)}...`);
|
|
4203
4284
|
|
|
4204
4285
|
try {
|
|
4205
4286
|
const { exec: execCb } = await import('child_process');
|
|
@@ -4213,7 +4294,7 @@ app.post("/workspace/exec", async (req, res) => {
|
|
|
4213
4294
|
env: { ...process.env, FORCE_COLOR: '0' }
|
|
4214
4295
|
});
|
|
4215
4296
|
|
|
4216
|
-
console.log(`
|
|
4297
|
+
console.log(`Command completed (stdout: ${result.stdout.length} chars)`);
|
|
4217
4298
|
|
|
4218
4299
|
res.json({
|
|
4219
4300
|
ok: true,
|
|
@@ -4222,7 +4303,7 @@ app.post("/workspace/exec", async (req, res) => {
|
|
|
4222
4303
|
exitCode: 0
|
|
4223
4304
|
});
|
|
4224
4305
|
} catch (err) {
|
|
4225
|
-
console.error(`
|
|
4306
|
+
console.error(`Command failed (exit: ${err.code}):`, err.message?.substring(0, 200));
|
|
4226
4307
|
res.json({
|
|
4227
4308
|
ok: false,
|
|
4228
4309
|
stdout: err.stdout || '',
|
|
@@ -4255,7 +4336,7 @@ app.post("/workspace/test-endpoint", async (req, res) => {
|
|
|
4255
4336
|
return res.status(403).json({ error: "Only localhost URLs allowed for security" });
|
|
4256
4337
|
}
|
|
4257
4338
|
|
|
4258
|
-
console.log(`
|
|
4339
|
+
console.log(`Testing: ${method} ${url} (${testName || 'unnamed'})`);
|
|
4259
4340
|
|
|
4260
4341
|
const startTime = Date.now();
|
|
4261
4342
|
|
|
@@ -4305,7 +4386,7 @@ app.post("/workspace/test-endpoint", async (req, res) => {
|
|
|
4305
4386
|
: response.status >= 200 && response.status < 400
|
|
4306
4387
|
};
|
|
4307
4388
|
|
|
4308
|
-
console.log(
|
|
4389
|
+
console.log(`${response.status} ${response.statusText} (${durationMs}ms) ${result.passed ? '' : ''}`);
|
|
4309
4390
|
res.json(result);
|
|
4310
4391
|
|
|
4311
4392
|
} catch (err) {
|
|
@@ -4381,7 +4462,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4381
4462
|
try {
|
|
4382
4463
|
const stashResult = execSync('git stash --include-untracked 2>&1', opts).trim();
|
|
4383
4464
|
hadStash = !stashResult.includes('No local changes');
|
|
4384
|
-
if (hadStash) console.log('
|
|
4465
|
+
if (hadStash) console.log('Stashed uncommitted changes');
|
|
4385
4466
|
} catch {}
|
|
4386
4467
|
|
|
4387
4468
|
// 4. Create and checkout new branch (with unique name if exists)
|
|
@@ -4405,7 +4486,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4405
4486
|
try {
|
|
4406
4487
|
execSync('git stash pop', opts);
|
|
4407
4488
|
} catch (e) {
|
|
4408
|
-
console.warn('
|
|
4489
|
+
console.warn('Stash pop had conflicts, trying apply:', e.message);
|
|
4409
4490
|
try { execSync('git stash apply', opts); } catch {}
|
|
4410
4491
|
}
|
|
4411
4492
|
}
|
|
@@ -4427,6 +4508,13 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4427
4508
|
}
|
|
4428
4509
|
|
|
4429
4510
|
// 8. Commit
|
|
4511
|
+
// Ensure git identity is configured (required in Cloud Run environment)
|
|
4512
|
+
try {
|
|
4513
|
+
execSync('git config user.email "ai@deepdebug.ai"', opts);
|
|
4514
|
+
execSync('git config user.name "DeepDebug AI"', opts);
|
|
4515
|
+
} catch (e) {
|
|
4516
|
+
console.warn('Could not set git config:', e.message);
|
|
4517
|
+
}
|
|
4430
4518
|
const safeMsg = commitMessage.replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
4431
4519
|
execSync(`git commit -m "${safeMsg}"`, opts);
|
|
4432
4520
|
|
|
@@ -4434,7 +4522,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4434
4522
|
let pushResult = '';
|
|
4435
4523
|
let pushed = false;
|
|
4436
4524
|
|
|
4437
|
-
console.log(`
|
|
4525
|
+
console.log(`Git push gitToken provided: ${!!gitToken}, repoUrl provided: ${!!repoUrl}`);
|
|
4438
4526
|
|
|
4439
4527
|
// If gitToken provided, set up authenticated remote URL for push
|
|
4440
4528
|
if (gitToken) {
|
|
@@ -4446,11 +4534,11 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4446
4534
|
// No remote configured use repoUrl from integration if available
|
|
4447
4535
|
if (repoUrl) {
|
|
4448
4536
|
remoteUrlRaw = repoUrl;
|
|
4449
|
-
console.log(`
|
|
4537
|
+
console.log(`No git remote, using repoUrl from integration: ${repoUrl}`);
|
|
4450
4538
|
}
|
|
4451
4539
|
}
|
|
4452
4540
|
|
|
4453
|
-
console.log(`
|
|
4541
|
+
console.log(`Remote URL: ${remoteUrlRaw.replace(gitToken, '***')}`);
|
|
4454
4542
|
let authenticatedUrl = '';
|
|
4455
4543
|
|
|
4456
4544
|
if (remoteUrlRaw.includes('github.com')) {
|
|
@@ -4459,7 +4547,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4459
4547
|
.replace(/^git@github\.com:/, '')
|
|
4460
4548
|
.replace(/\.git$/, '');
|
|
4461
4549
|
authenticatedUrl = `https://x-access-token:${gitToken}@github.com/${repoPath}.git`;
|
|
4462
|
-
console.log(`
|
|
4550
|
+
console.log(`GitHub auth URL: https://x-access-token:***@github.com/${repoPath}.git`);
|
|
4463
4551
|
} else if (remoteUrlRaw.includes('gitlab.com') || remoteUrlRaw.includes('gitlab')) {
|
|
4464
4552
|
const repoPath = remoteUrlRaw
|
|
4465
4553
|
.replace(/^https?:\/\/(.*@)?[^\/]+\//, '')
|
|
@@ -4467,7 +4555,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4467
4555
|
.replace(/\.git$/, '');
|
|
4468
4556
|
const host = remoteUrlRaw.match(/https?:\/\/([^\/]+)/)?.[1] || 'gitlab.com';
|
|
4469
4557
|
authenticatedUrl = `https://oauth2:${gitToken}@${host}/${repoPath}.git`;
|
|
4470
|
-
console.log(`
|
|
4558
|
+
console.log(`GitLab auth URL: https://oauth2:***@${host}/${repoPath}.git`);
|
|
4471
4559
|
} else if (remoteUrlRaw.includes('bitbucket.org') || remoteUrlRaw.includes('bitbucket')) {
|
|
4472
4560
|
const repoPath = remoteUrlRaw
|
|
4473
4561
|
.replace(/^https?:\/\/(.*@)?bitbucket\.org\//, '')
|
|
@@ -4481,11 +4569,11 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4481
4569
|
const urlObj = new URL(remoteUrlRaw.replace(/^git@([^:]+):/, 'https://$1/'));
|
|
4482
4570
|
authenticatedUrl = `https://oauth2:${gitToken}@${urlObj.host}${urlObj.pathname}`;
|
|
4483
4571
|
if (!authenticatedUrl.endsWith('.git')) authenticatedUrl += '.git';
|
|
4484
|
-
console.log(`
|
|
4572
|
+
console.log(`Generic auth URL for ${urlObj.host}`);
|
|
4485
4573
|
}
|
|
4486
4574
|
|
|
4487
4575
|
if (authenticatedUrl) {
|
|
4488
|
-
console.log(`
|
|
4576
|
+
console.log(`Pushing ${finalBranchName} with token auth...`);
|
|
4489
4577
|
try {
|
|
4490
4578
|
// Don't use 2>&1 let execSync capture stderr separately
|
|
4491
4579
|
pushResult = execSync(`git push ${authenticatedUrl} ${finalBranchName}`, {
|
|
@@ -4494,20 +4582,20 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4494
4582
|
stdio: ['pipe', 'pipe', 'pipe'] // capture stdin, stdout, stderr separately
|
|
4495
4583
|
}).toString();
|
|
4496
4584
|
pushed = true;
|
|
4497
|
-
console.log(`
|
|
4585
|
+
console.log(`Pushed with token authentication`);
|
|
4498
4586
|
} catch (innerErr) {
|
|
4499
4587
|
// Mask token in error messages before logging
|
|
4500
4588
|
const maskToken = (str) => str ? str.replace(gitToken, '***TOKEN***') : '';
|
|
4501
4589
|
const errMsg = maskToken(innerErr.stderr?.toString() || innerErr.stdout?.toString() || innerErr.message || '');
|
|
4502
|
-
console.warn(`
|
|
4590
|
+
console.warn(`Authenticated push failed: ${errMsg}`);
|
|
4503
4591
|
pushResult = errMsg;
|
|
4504
4592
|
}
|
|
4505
4593
|
} else {
|
|
4506
|
-
console.warn(`
|
|
4594
|
+
console.warn(`Could not construct authenticated URL from: ${remoteUrlRaw}`);
|
|
4507
4595
|
}
|
|
4508
4596
|
} catch (pushErr) {
|
|
4509
4597
|
const maskToken = (str) => str ? str.replace(gitToken, '***TOKEN***') : '';
|
|
4510
|
-
console.warn(`
|
|
4598
|
+
console.warn(`Git auth setup failed: ${maskToken(pushErr.message)}`);
|
|
4511
4599
|
pushResult = maskToken(pushErr.message) || 'Push setup failed';
|
|
4512
4600
|
}
|
|
4513
4601
|
}
|
|
@@ -4522,7 +4610,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4522
4610
|
pushResult = execSync(`git push origin ${finalBranchName} 2>&1`, opts).toString();
|
|
4523
4611
|
pushed = true;
|
|
4524
4612
|
} catch (pushErr2) {
|
|
4525
|
-
console.warn(`
|
|
4613
|
+
console.warn(`Git push failed: ${pushErr2.message}`);
|
|
4526
4614
|
pushResult = pushErr2.message || 'Push failed no remote configured or auth required';
|
|
4527
4615
|
}
|
|
4528
4616
|
}
|
|
@@ -4581,8 +4669,8 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4581
4669
|
console.warn('Could not detect remote URL:', e.message);
|
|
4582
4670
|
}
|
|
4583
4671
|
|
|
4584
|
-
console.log(`
|
|
4585
|
-
if (mrUrl) console.log(`
|
|
4672
|
+
console.log(`Fix branch created: ${finalBranchName} (${commitSha.substring(0, 8)}) pushed=${pushed}`);
|
|
4673
|
+
if (mrUrl) console.log(`MR URL: ${mrUrl}`);
|
|
4586
4674
|
|
|
4587
4675
|
// 10. Switch back to original branch and merge fix to keep changes on disk
|
|
4588
4676
|
try {
|
|
@@ -4591,7 +4679,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4591
4679
|
// This keeps the diff viewer working (disk has patched version)
|
|
4592
4680
|
try {
|
|
4593
4681
|
execSync(`git merge ${finalBranchName} --no-edit`, opts);
|
|
4594
|
-
console.log(`
|
|
4682
|
+
console.log(`Merged ${finalBranchName} into ${currentBranch}`);
|
|
4595
4683
|
} catch (mergeErr) {
|
|
4596
4684
|
// If merge fails (conflicts), just cherry-pick the changes without committing
|
|
4597
4685
|
try {
|
|
@@ -4605,7 +4693,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4605
4693
|
execSync(`git checkout ${finalBranchName} -- ${f}`, opts);
|
|
4606
4694
|
} catch {}
|
|
4607
4695
|
}
|
|
4608
|
-
console.log(`
|
|
4696
|
+
console.log(`Cherry-picked ${changedFiles.length} file(s) from ${finalBranchName}`);
|
|
4609
4697
|
} catch {}
|
|
4610
4698
|
}
|
|
4611
4699
|
} catch {}
|
|
@@ -4652,14 +4740,14 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4652
4740
|
if (response.ok) {
|
|
4653
4741
|
const prData = await response.json();
|
|
4654
4742
|
prUrl = prData.html_url;
|
|
4655
|
-
console.log(`
|
|
4743
|
+
console.log(`GitHub PR created: ${prUrl}`);
|
|
4656
4744
|
} else {
|
|
4657
4745
|
const errText = await response.text();
|
|
4658
|
-
console.warn(`
|
|
4746
|
+
console.warn(`GitHub PR creation failed (${response.status}): ${errText.substring(0, 200)}`);
|
|
4659
4747
|
prUrl = mrUrl;
|
|
4660
4748
|
}
|
|
4661
4749
|
} catch (prErr) {
|
|
4662
|
-
console.warn(`
|
|
4750
|
+
console.warn(`GitHub PR error: ${prErr.message}`);
|
|
4663
4751
|
prUrl = mrUrl;
|
|
4664
4752
|
}
|
|
4665
4753
|
|
|
@@ -4693,14 +4781,14 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4693
4781
|
if (response.ok) {
|
|
4694
4782
|
const mrData = await response.json();
|
|
4695
4783
|
prUrl = mrData.web_url;
|
|
4696
|
-
console.log(`
|
|
4784
|
+
console.log(`GitLab MR created: ${prUrl}`);
|
|
4697
4785
|
} else {
|
|
4698
4786
|
const errText = await response.text();
|
|
4699
|
-
console.warn(`
|
|
4787
|
+
console.warn(`GitLab MR creation failed (${response.status}): ${errText.substring(0, 200)}`);
|
|
4700
4788
|
prUrl = mrUrl;
|
|
4701
4789
|
}
|
|
4702
4790
|
} catch (glErr) {
|
|
4703
|
-
console.warn(`
|
|
4791
|
+
console.warn(`GitLab MR error: ${glErr.message}`);
|
|
4704
4792
|
prUrl = mrUrl;
|
|
4705
4793
|
}
|
|
4706
4794
|
|
|
@@ -4753,14 +4841,14 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4753
4841
|
if (response.ok) {
|
|
4754
4842
|
const prData = await response.json();
|
|
4755
4843
|
prUrl = prData.links && prData.links.html ? prData.links.html.href : mrUrl;
|
|
4756
|
-
console.log(`
|
|
4844
|
+
console.log(`Bitbucket PR created: ${prUrl}`);
|
|
4757
4845
|
} else {
|
|
4758
4846
|
const errText = await response.text();
|
|
4759
|
-
console.warn(`
|
|
4847
|
+
console.warn(`Bitbucket PR creation failed (${response.status}): ${errText.substring(0, 200)}`);
|
|
4760
4848
|
prUrl = mrUrl;
|
|
4761
4849
|
}
|
|
4762
4850
|
} catch (bbErr) {
|
|
4763
|
-
console.warn(`
|
|
4851
|
+
console.warn(`Bitbucket PR error: ${bbErr.message}`);
|
|
4764
4852
|
prUrl = mrUrl;
|
|
4765
4853
|
}
|
|
4766
4854
|
}
|
|
@@ -4783,7 +4871,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
|
|
|
4783
4871
|
});
|
|
4784
4872
|
|
|
4785
4873
|
} catch (err) {
|
|
4786
|
-
console.error(`
|
|
4874
|
+
console.error(`Git branch creation failed: ${err.message}`);
|
|
4787
4875
|
res.status(500).json({ ok: false, error: err.message });
|
|
4788
4876
|
}
|
|
4789
4877
|
});
|
|
@@ -4895,7 +4983,7 @@ app.get("/workspace/git/pr/comments", async (req, res) => {
|
|
|
4895
4983
|
unresolvedCount: comments.filter(c => c.resolved === false || c.resolved === undefined).length
|
|
4896
4984
|
});
|
|
4897
4985
|
} catch (err) {
|
|
4898
|
-
console.error('
|
|
4986
|
+
console.error('Failed to fetch PR comments:', err.message);
|
|
4899
4987
|
res.status(500).json({ ok: false, error: err.message });
|
|
4900
4988
|
}
|
|
4901
4989
|
});
|
|
@@ -4932,7 +5020,7 @@ app.get("/workspace/git/pr/comments/:commentId", async (req, res) => {
|
|
|
4932
5020
|
|
|
4933
5021
|
res.json({ ok: true, comment });
|
|
4934
5022
|
} catch (err) {
|
|
4935
|
-
console.error('
|
|
5023
|
+
console.error('Failed to fetch PR comment:', err.message);
|
|
4936
5024
|
res.status(500).json({ ok: false, error: err.message });
|
|
4937
5025
|
}
|
|
4938
5026
|
});
|
|
@@ -4994,10 +5082,10 @@ app.post("/workspace/git/pr/comments", async (req, res) => {
|
|
|
4994
5082
|
comment = await provider.instance.addPRComment(owner, repo, Number(prNumber), body);
|
|
4995
5083
|
}
|
|
4996
5084
|
|
|
4997
|
-
console.log(`
|
|
5085
|
+
console.log(`PR comment added to PR #${prNumber}: ${body.substring(0, 50)}...`);
|
|
4998
5086
|
res.json({ ok: true, comment });
|
|
4999
5087
|
} catch (err) {
|
|
5000
|
-
console.error('
|
|
5088
|
+
console.error('Failed to add PR comment:', err.message);
|
|
5001
5089
|
res.status(500).json({ ok: false, error: err.message });
|
|
5002
5090
|
}
|
|
5003
5091
|
});
|
|
@@ -5032,10 +5120,10 @@ app.post("/workspace/git/pr/comments/:commentId/resolve", async (req, res) => {
|
|
|
5032
5120
|
const { owner, repo } = provider.info;
|
|
5033
5121
|
const result = await provider.instance.resolvePRComment(owner, repo, Number(prNumber), commentId);
|
|
5034
5122
|
|
|
5035
|
-
console.log(`
|
|
5123
|
+
console.log(`PR comment ${commentId} resolved on PR #${prNumber}`);
|
|
5036
5124
|
res.json({ ok: true, commentId, ...result });
|
|
5037
5125
|
} catch (err) {
|
|
5038
|
-
console.error('
|
|
5126
|
+
console.error('Failed to resolve PR comment:', err.message);
|
|
5039
5127
|
res.status(500).json({ ok: false, error: err.message });
|
|
5040
5128
|
}
|
|
5041
5129
|
});
|
|
@@ -5070,10 +5158,10 @@ app.post("/workspace/git/pr/comments/:commentId/unresolve", async (req, res) =>
|
|
|
5070
5158
|
const { owner, repo } = provider.info;
|
|
5071
5159
|
const result = await provider.instance.unresolvePRComment(owner, repo, Number(prNumber), commentId);
|
|
5072
5160
|
|
|
5073
|
-
console.log(`
|
|
5161
|
+
console.log(`PR comment ${commentId} unresolve on PR #${prNumber}`);
|
|
5074
5162
|
res.json({ ok: true, commentId, ...result });
|
|
5075
5163
|
} catch (err) {
|
|
5076
|
-
console.error('
|
|
5164
|
+
console.error('Failed to unresolve PR comment:', err.message);
|
|
5077
5165
|
res.status(500).json({ ok: false, error: err.message });
|
|
5078
5166
|
}
|
|
5079
5167
|
});
|
|
@@ -5118,9 +5206,9 @@ app.post("/workspace/git/pr/comments/:commentId/fix-and-resolve", async (req, re
|
|
|
5118
5206
|
return res.status(404).json({ error: "Comment not found" });
|
|
5119
5207
|
}
|
|
5120
5208
|
|
|
5121
|
-
console.log(`
|
|
5122
|
-
console.log(`
|
|
5123
|
-
console.log(`
|
|
5209
|
+
console.log(`AI Fix & Resolve: PR #${prNumber}, comment ${commentId}`);
|
|
5210
|
+
console.log(`File: ${comment.path || '(general)'}, Line: ${comment.line || 'N/A'}`);
|
|
5211
|
+
console.log(`Request: ${comment.body.substring(0, 100)}...`);
|
|
5124
5212
|
|
|
5125
5213
|
// 2. Return the comment info for the Gateway to orchestrate the AI fix
|
|
5126
5214
|
// The actual AI analysis + patch is handled by the Gateway's AI service,
|
|
@@ -5150,7 +5238,7 @@ app.post("/workspace/git/pr/comments/:commentId/fix-and-resolve", async (req, re
|
|
|
5150
5238
|
message: 'Comment fetched. Gateway should now: 1) Read file, 2) AI analyze, 3) Apply patch, 4) Commit, 5) Reply, 6) Resolve'
|
|
5151
5239
|
});
|
|
5152
5240
|
} catch (err) {
|
|
5153
|
-
console.error('
|
|
5241
|
+
console.error('Failed to prepare fix-and-resolve:', err.message);
|
|
5154
5242
|
res.status(500).json({ ok: false, error: err.message });
|
|
5155
5243
|
}
|
|
5156
5244
|
});
|
|
@@ -5173,7 +5261,7 @@ async function _getGitProvider() {
|
|
|
5173
5261
|
try {
|
|
5174
5262
|
remoteUrl = execSync('git remote get-url origin', opts).trim();
|
|
5175
5263
|
} catch {
|
|
5176
|
-
console.warn('
|
|
5264
|
+
console.warn('No git remote found');
|
|
5177
5265
|
return null;
|
|
5178
5266
|
}
|
|
5179
5267
|
|
|
@@ -5183,7 +5271,7 @@ async function _getGitProvider() {
|
|
|
5183
5271
|
const providerId = registry.detectProviderFromUrl(remoteUrl);
|
|
5184
5272
|
|
|
5185
5273
|
if (!providerId) {
|
|
5186
|
-
console.warn(`
|
|
5274
|
+
console.warn(`Unknown git provider for URL: ${remoteUrl}`);
|
|
5187
5275
|
return null;
|
|
5188
5276
|
}
|
|
5189
5277
|
|
|
@@ -5198,7 +5286,7 @@ async function _getGitProvider() {
|
|
|
5198
5286
|
}
|
|
5199
5287
|
|
|
5200
5288
|
if (!token) {
|
|
5201
|
-
console.warn(`
|
|
5289
|
+
console.warn(`No token found for ${providerId}. Set ${providerId.toUpperCase()}_TOKEN or GIT_TOKEN env var.`);
|
|
5202
5290
|
return null;
|
|
5203
5291
|
}
|
|
5204
5292
|
|
|
@@ -5218,7 +5306,7 @@ async function _getGitProvider() {
|
|
|
5218
5306
|
|
|
5219
5307
|
return { instance, info, providerId, remoteUrl };
|
|
5220
5308
|
} catch (err) {
|
|
5221
|
-
console.error('
|
|
5309
|
+
console.error('Failed to initialize git provider:', err.message);
|
|
5222
5310
|
return null;
|
|
5223
5311
|
}
|
|
5224
5312
|
}
|
|
@@ -5307,13 +5395,13 @@ app.post("/workspace/:workspaceId/run", async (req, res) => {
|
|
|
5307
5395
|
// Connects Local Agent to Gateway no public URL needed
|
|
5308
5396
|
// ============================================
|
|
5309
5397
|
async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
5310
|
-
if (!gatewayUrl || !
|
|
5311
|
-
console.log('
|
|
5398
|
+
if (!gatewayUrl || !tenantId) {
|
|
5399
|
+
console.log('WebSocket tunnel skipped: missing gatewayUrl or tenantId');
|
|
5312
5400
|
return;
|
|
5313
5401
|
}
|
|
5314
5402
|
|
|
5315
5403
|
const wsUrl = gatewayUrl.replace(/^https:\/\//, 'wss://').replace(/^http:\/\//, 'ws://');
|
|
5316
|
-
const fullUrl = `${wsUrl}/api/v1/agent/ws?tenantId=${encodeURIComponent(tenantId)}&apiKey
|
|
5404
|
+
const fullUrl = `${wsUrl}/api/v1/agent/ws?tenantId=${encodeURIComponent(tenantId)}${apiKey ? '&apiKey=' + encodeURIComponent(apiKey) : ''}`;
|
|
5317
5405
|
|
|
5318
5406
|
let reconnectDelay = 2000;
|
|
5319
5407
|
let isShuttingDown = false;
|
|
@@ -5326,7 +5414,7 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5326
5414
|
ws = new WebSocket(fullUrl);
|
|
5327
5415
|
|
|
5328
5416
|
ws.on('open', () => {
|
|
5329
|
-
console.log(`
|
|
5417
|
+
console.log(`WebSocket tunnel connected to Gateway`);
|
|
5330
5418
|
reconnectDelay = 2000;
|
|
5331
5419
|
ws.send(JSON.stringify({ type: 'register', tenantId, port, workspacePath: WORKSPACE_ROOT || null }));
|
|
5332
5420
|
const hb = setInterval(() => {
|
|
@@ -5342,7 +5430,7 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5342
5430
|
try { request = JSON.parse(text); } catch { return; }
|
|
5343
5431
|
const { requestId, command, params } = request;
|
|
5344
5432
|
if (!requestId || !command) return;
|
|
5345
|
-
console.log(`
|
|
5433
|
+
console.log(`WS command: ${command}`);
|
|
5346
5434
|
let result;
|
|
5347
5435
|
try { result = await handleWsCommand(command, params || {}); }
|
|
5348
5436
|
catch (err) { result = { error: err.message }; }
|
|
@@ -5351,19 +5439,19 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5351
5439
|
|
|
5352
5440
|
ws.on('close', () => {
|
|
5353
5441
|
if (!isShuttingDown) {
|
|
5354
|
-
console.log(`
|
|
5442
|
+
console.log(`WS disconnected. Reconnecting in ${reconnectDelay/1000}s...`);
|
|
5355
5443
|
setTimeout(connect, reconnectDelay);
|
|
5356
5444
|
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
|
5357
5445
|
}
|
|
5358
5446
|
});
|
|
5359
5447
|
|
|
5360
|
-
ws.on('error', (err) => console.warn(`
|
|
5448
|
+
ws.on('error', (err) => console.warn(`WS error: ${err.message}`));
|
|
5361
5449
|
|
|
5362
5450
|
} catch (err) {
|
|
5363
5451
|
if (err.code === 'ERR_MODULE_NOT_FOUND') {
|
|
5364
|
-
console.warn('
|
|
5452
|
+
console.warn('WebSocket tunnel requires "ws" package. Run: npm install ws');
|
|
5365
5453
|
} else {
|
|
5366
|
-
console.warn(`
|
|
5454
|
+
console.warn(`WS tunnel error: ${err.message}`);
|
|
5367
5455
|
setTimeout(connect, reconnectDelay);
|
|
5368
5456
|
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
|
5369
5457
|
}
|
|
@@ -5434,7 +5522,7 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5434
5522
|
return { error: 'backupId or incidentId is required' };
|
|
5435
5523
|
}
|
|
5436
5524
|
|
|
5437
|
-
console.log(`
|
|
5525
|
+
console.log(`WS workspace.diff ${endpoint}`);
|
|
5438
5526
|
const res = await fetch(`${localBase}${endpoint}`, {
|
|
5439
5527
|
method: 'GET',
|
|
5440
5528
|
headers: { 'Content-Type': 'application/json' }
|
|
@@ -5568,7 +5656,7 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5568
5656
|
}
|
|
5569
5657
|
|
|
5570
5658
|
// ============================================
|
|
5571
|
-
//
|
|
5659
|
+
// SETUP AGENT ROUTES
|
|
5572
5660
|
// Supports the AI Assistant chat commands
|
|
5573
5661
|
// ============================================
|
|
5574
5662
|
|
|
@@ -5585,7 +5673,7 @@ app.post("/setup/run-command", async (req, res) => {
|
|
|
5585
5673
|
return res.status(403).json({ ok: false, error: "Command blocked for safety" });
|
|
5586
5674
|
}
|
|
5587
5675
|
|
|
5588
|
-
console.log(
|
|
5676
|
+
console.log(`[SetupAgent] Running: ${command.substring(0, 100)}`);
|
|
5589
5677
|
const { exec: execCb } = await import('child_process');
|
|
5590
5678
|
const { promisify } = await import('util');
|
|
5591
5679
|
const execAsync = promisify(execCb);
|
|
@@ -5683,10 +5771,10 @@ app.post("/setup/run-auth-command", async (req, res) => {
|
|
|
5683
5771
|
|
|
5684
5772
|
const { exec: execCb } = await import('child_process');
|
|
5685
5773
|
const bgCmd = process.platform === 'win32' ? `start cmd /c "${command}"` : `${command} &`;
|
|
5686
|
-
console.log(
|
|
5774
|
+
console.log(`[SetupAgent] Starting auth: ${command.substring(0, 80)}`);
|
|
5687
5775
|
|
|
5688
5776
|
execCb(bgCmd, { shell: true }, (err) => {
|
|
5689
|
-
if (err) console.warn(
|
|
5777
|
+
if (err) console.warn(`Auth command warning: ${err.message}`);
|
|
5690
5778
|
});
|
|
5691
5779
|
|
|
5692
5780
|
res.json({ ok: true, status: "started", session_id, message: "Auth command started, browser should open" });
|
|
@@ -5724,19 +5812,138 @@ app.get("/setup/auth-status", async (req, res) => {
|
|
|
5724
5812
|
}
|
|
5725
5813
|
});
|
|
5726
5814
|
|
|
5815
|
+
// ============================================
|
|
5816
|
+
// SPRINT 1 S1-T2: POST /workspace/test-local/run-single
|
|
5817
|
+
// Run a single test class (faster than full suite)
|
|
5818
|
+
// ============================================
|
|
5819
|
+
|
|
5820
|
+
/**
|
|
5821
|
+
* POST /workspace/test-local/run-single
|
|
5822
|
+
*
|
|
5823
|
+
* Runs a single test class by name much faster than running the full suite.
|
|
5824
|
+
* Used by the agentic loop (tool: run_single_test) to validate a specific fix
|
|
5825
|
+
* without waiting for all tests to complete.
|
|
5826
|
+
*
|
|
5827
|
+
* Body:
|
|
5828
|
+
* { className: "BookingServiceTest" }
|
|
5829
|
+
* OR { testFile: "src/test/java/com/example/BookingServiceTest.java" }
|
|
5830
|
+
*
|
|
5831
|
+
* Response:
|
|
5832
|
+
* { ok, className, passed, failed, skipped, total, duration, output, failures[], exitCode }
|
|
5833
|
+
*/
|
|
5834
|
+
app.post("/workspace/test-local/run-single", async (req, res) => {
|
|
5835
|
+
const wsRoot = resolveWorkspaceRoot(req);
|
|
5836
|
+
if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
|
|
5837
|
+
|
|
5838
|
+
const { className, testFile } = req.body || {};
|
|
5839
|
+
|
|
5840
|
+
// Resolve class name from either param
|
|
5841
|
+
let resolvedClass = className;
|
|
5842
|
+
if (!resolvedClass && testFile) {
|
|
5843
|
+
resolvedClass = path.basename(testFile, '.java');
|
|
5844
|
+
}
|
|
5845
|
+
|
|
5846
|
+
if (!resolvedClass || !resolvedClass.trim()) {
|
|
5847
|
+
return res.status(400).json({
|
|
5848
|
+
ok: false,
|
|
5849
|
+
error: "className or testFile is required. Example: { className: 'BookingServiceTest' }"
|
|
5850
|
+
});
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5853
|
+
resolvedClass = resolvedClass.trim();
|
|
5854
|
+
console.log(`[RUN-SINGLE] Running test class: ${resolvedClass} in ${wsRoot}`);
|
|
5855
|
+
|
|
5856
|
+
const startTime = Date.now();
|
|
5857
|
+
|
|
5858
|
+
try {
|
|
5859
|
+
const meta = await detectProject(wsRoot);
|
|
5860
|
+
|
|
5861
|
+
let result;
|
|
5862
|
+
|
|
5863
|
+
if (meta.buildTool === 'maven') {
|
|
5864
|
+
// mvn test -Dtest=ClassName -q (sem clean para ser mais rapido)
|
|
5865
|
+
result = await run('mvn', ['test', `-Dtest=${resolvedClass}`, '-q'], wsRoot);
|
|
5866
|
+
} else if (meta.buildTool === 'gradle') {
|
|
5867
|
+
const gradleCmd = fs.existsSync(path.join(wsRoot, 'gradlew')) ? './gradlew' : 'gradle';
|
|
5868
|
+
result = await run(gradleCmd, ['test', '--tests', `*.${resolvedClass}`], wsRoot);
|
|
5869
|
+
} else if (meta.language === 'node') {
|
|
5870
|
+
result = await run('npx', ['jest', resolvedClass, '--no-coverage'], wsRoot);
|
|
5871
|
+
} else {
|
|
5872
|
+
return res.status(400).json({
|
|
5873
|
+
ok: false,
|
|
5874
|
+
error: `Single test not supported for buildTool: ${meta.buildTool}. Use /workspace/test-local/compile instead.`
|
|
5875
|
+
});
|
|
5876
|
+
}
|
|
5877
|
+
|
|
5878
|
+
const duration = Date.now() - startTime;
|
|
5879
|
+
const output = (result.stdout || '') + (result.stderr || '');
|
|
5880
|
+
|
|
5881
|
+
// Parse Maven Surefire / JUnit output
|
|
5882
|
+
const testsRunMatch = output.match(/Tests run: (\d+)/);
|
|
5883
|
+
const failuresMatch = output.match(/Failures: (\d+)/);
|
|
5884
|
+
const errorsMatch = output.match(/Errors: (\d+)/);
|
|
5885
|
+
const skippedMatch = output.match(/Skipped: (\d+)/);
|
|
5886
|
+
|
|
5887
|
+
const total = testsRunMatch ? parseInt(testsRunMatch[1]) : 0;
|
|
5888
|
+
const failures = failuresMatch ? parseInt(failuresMatch[1]) : 0;
|
|
5889
|
+
const errors = errorsMatch ? parseInt(errorsMatch[1]) : 0;
|
|
5890
|
+
const skipped = skippedMatch ? parseInt(skippedMatch[1]) : 0;
|
|
5891
|
+
const passed = total - failures - errors - skipped;
|
|
5892
|
+
const success = result.code === 0 && failures === 0 && errors === 0;
|
|
5893
|
+
|
|
5894
|
+
// Extract failure names from output
|
|
5895
|
+
const failureDetails = [];
|
|
5896
|
+
const failureRegex = /FAILED\s+([^\n]+)/g;
|
|
5897
|
+
let match;
|
|
5898
|
+
while ((match = failureRegex.exec(output)) !== null) {
|
|
5899
|
+
failureDetails.push(match[1].trim());
|
|
5900
|
+
}
|
|
5901
|
+
|
|
5902
|
+
console.log(`[RUN-SINGLE] ${resolvedClass}: ${success ? 'PASSED' : 'FAILED'} ` +
|
|
5903
|
+
`(${duration}ms, ${passed}/${total})`);
|
|
5904
|
+
|
|
5905
|
+
res.json({
|
|
5906
|
+
ok: success,
|
|
5907
|
+
className: resolvedClass,
|
|
5908
|
+
passed,
|
|
5909
|
+
failed: failures + errors,
|
|
5910
|
+
skipped,
|
|
5911
|
+
total,
|
|
5912
|
+
duration,
|
|
5913
|
+
output: output.length > 8000 ? output.substring(0, 8000) + '\n...[truncated]' : output,
|
|
5914
|
+
failures: failureDetails,
|
|
5915
|
+
exitCode: result.code
|
|
5916
|
+
});
|
|
5917
|
+
|
|
5918
|
+
} catch (err) {
|
|
5919
|
+
const duration = Date.now() - startTime;
|
|
5920
|
+
console.error(`[RUN-SINGLE] Failed for ${resolvedClass}:`, err.message);
|
|
5921
|
+
res.status(500).json({
|
|
5922
|
+
ok: false,
|
|
5923
|
+
className: resolvedClass,
|
|
5924
|
+
error: err.message,
|
|
5925
|
+
duration,
|
|
5926
|
+
passed: 0,
|
|
5927
|
+
failed: 1,
|
|
5928
|
+
skipped: 0,
|
|
5929
|
+
total: 0
|
|
5930
|
+
});
|
|
5931
|
+
}
|
|
5932
|
+
});
|
|
5933
|
+
|
|
5727
5934
|
// ============================================
|
|
5728
5935
|
// START SERVER
|
|
5729
5936
|
|
|
5730
5937
|
// ============================================
|
|
5731
5938
|
app.listen(PORT, '0.0.0.0', async () => {
|
|
5732
5939
|
console.log(`\n DeepDebug Local Agent listening on port ${PORT}`);
|
|
5733
|
-
console.log(`
|
|
5734
|
-
console.log(`
|
|
5735
|
-
console.log(`
|
|
5736
|
-
console.log(`
|
|
5940
|
+
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
5941
|
+
console.log(`Process Manager initialized`);
|
|
5942
|
+
console.log(`Backup system ready (max: ${MAX_BACKUPS} backups)`);
|
|
5943
|
+
console.log(`AI Vibe Coding Engine: ${aiEngine?.isActive ? 'ACTIVE' : 'DISABLED'}`);
|
|
5737
5944
|
if (aiEngine) {
|
|
5738
|
-
console.log(`
|
|
5739
|
-
console.log(`
|
|
5945
|
+
console.log(`Gateway URL: ${aiEngine.gatewayUrl}`);
|
|
5946
|
+
console.log(`Max Retries: ${aiEngine.maxRetries}`);
|
|
5740
5947
|
}
|
|
5741
5948
|
console.log(`\n Ready to receive requests!\n`);
|
|
5742
5949
|
|
|
@@ -5748,7 +5955,7 @@ app.listen(PORT, '0.0.0.0', async () => {
|
|
|
5748
5955
|
|
|
5749
5956
|
const gwUrl = cfg.gatewayUrl || process.env.GATEWAY_URL;
|
|
5750
5957
|
const apiKey = cfg.apiKey || process.env.DEEPDEBUG_API_KEY;
|
|
5751
|
-
let tenantId = cfg.tenantId;
|
|
5958
|
+
let tenantId = cfg.tenantId || process.env.TENANT_ID;
|
|
5752
5959
|
if (!tenantId && apiKey) {
|
|
5753
5960
|
try {
|
|
5754
5961
|
const payload = JSON.parse(Buffer.from(apiKey.split('.')[1], 'base64').toString());
|
|
@@ -5765,8 +5972,8 @@ app.listen(PORT, '0.0.0.0', async () => {
|
|
|
5765
5972
|
try {
|
|
5766
5973
|
startMCPHttpServer(wsManager, parseInt(MCP_PORT));
|
|
5767
5974
|
} catch (err) {
|
|
5768
|
-
console.warn(`
|
|
5769
|
-
console.warn(`
|
|
5975
|
+
console.warn(`MCP HTTP Server failed to start: ${err.message}`);
|
|
5976
|
+
console.warn(`(MCP features disabled, REST API continues normally)`);
|
|
5770
5977
|
}
|
|
5771
5978
|
|
|
5772
5979
|
// Auto-register default workspace in WorkspaceManager
|