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/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(' [AI-Engine] Vibe Coding Engine initialized');
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(` [AI-Engine] Process ${serviceId} crashed (code: ${code})`);
64
+ console.log(`[AI-Engine] Process ${serviceId} crashed (code: ${code})`);
65
65
  if (this.pendingFixes.length > 0) {
66
- console.log(` [AI-Engine] ${this.pendingFixes.length} fixes pending`);
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(` [AI-Engine] Error: ${classification.type} (${classification.severity})`);
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(` [AI-Engine] Quick fix: ${fix.description}`);
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(' [AI-Engine] Starting with auto-healing...');
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(` Applying: ${fix.fix.description}`);
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(' Recompiling...');
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(` Attempt ${attempts} failed: ${lastError?.substring(0, 100)}...`);
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(` Fix: ${fix.description}`);
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(` AI: ${ai.suggestion}`);
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(` [AI-Engine] Detected startup success: ${message.substring(0, 80)}...`);
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(` Profile: ${config.profile} ${next}`);
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(` Port: ${config.port} ${newPort}`);
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(` [AI-Engine] AI unavailable: ${err.message}`);
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(` [AI-Engine] ${active ? 'ENABLED' : 'DISABLED'}`);
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(` Default workspace: ${WORKSPACE_ROOT}`);
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
- return WORKSPACE_ROOT;
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(` Restored ${BACKUPS.size} backups from disk`);
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(` Service ${serviceId} started successfully`);
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(` Service ${serviceId} stopped`);
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(` Service ${serviceId} error: ${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(` [/workspace/open] CLOUD MODE cloning ${repoUrl} (branch: ${branch})`);
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(` [cloud] Repo exists: ${clonePath}, updating...`);
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(` [cloud] Updated to latest ${branch}`);
802
+ console.log(`[cloud] Updated to latest ${branch}`);
783
803
  } else {
784
- console.log(` [cloud] Cloning ${repoUrl} (branch: ${branch})...`);
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(` [cloud] Cloned successfully: ${clonePath}`);
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(` WorkspaceManager.open failed (non-fatal): ${err.message}`);
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(` [cloud] Git clone failed:`, gitErr.message);
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(` WorkspaceManager open failed (non-fatal): ${err.message}`);
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
- // Accept both naming conventions:
863
- // - gitUrl + targetPath (local agent format)
864
- // - repoUrl + targetDir (cloud format)
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 = body.gitToken;
869
- const branch = body.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
- // If targetPath not provided, derive from repo name
874
- const repoName = gitUrl.split('/').pop().replace('.git', '');
875
- const resolvedTarget = targetPath
876
- ? path.resolve(targetPath)
877
- : path.join(process.env.HOME || '/home/deepdebug', 'DeepDebug', repoName);
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
- console.log(` Repo already exists at ${absTarget}, running git pull...`);
905
- const { stdout } = await execAsync('git pull', { cwd: absTarget, timeout: 120000 });
906
- console.log(` git pull: ${stdout.trim()}`);
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
- console.log(` Running: git clone "${safeLog}" "${absTarget}"`);
910
- await execAsync(`git clone "${authenticatedUrl}" "${absTarget}"`, { timeout: 300000 });
911
- console.log(` Clone complete: ${absTarget}`);
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(` WorkspaceManager.open failed (non-fatal): ${err.message}`);
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(` Clone failed: ${err.message}`);
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(` [file-exists] ${relativePath} -> ${fileExists ? 'EXISTS' : 'NOT FOUND'}`);
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(` [file-exists] Error:`, err.message);
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(` [validate-paths] Checked ${pathList.length} paths, ${missingPaths.length} missing`);
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(` [validate-paths] Error:`, err.message);
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(` [search-file] Searching for: ${fileName}`);
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(` [search-file] Scanning ${allFiles.length} files`);
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(` [search-file] Found: ${foundPath}`);
1242
+ console.log(`[search-file] Found: ${foundPath}`);
1182
1243
  res.json({ ok: true, found: true, path: foundPath, fileName });
1183
1244
  } else {
1184
- console.log(` [search-file] Not found: ${fileName}`);
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(` [search-file] Error:`, err.message);
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(` [search-by-content] Searching for terms: ${terms.join(', ')}`);
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(` [search-by-content] Scanning ${filteredFiles.length} files`);
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(` [search-by-content] Found ${dedupedResults.length} matching files`);
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(` [search-by-content] Error:`, err.message);
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(` [find-field] Searching for field: ${fieldName}`);
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(` [find-field] Found ${definitions.length} definitions for '${fieldName}'`);
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(` [find-field] Error:`, err.message);
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(` Applying patch for incident: ${incidentId || 'unknown'}`);
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(` Patch applied successfully:`, {
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(` Patch failed:`, e.message);
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(" [TEST-LOCAL] Getting state:", TEST_LOCAL_STATE.status);
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(" [TEST-LOCAL] Starting compilation...");
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.code !== 0) {
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: compileResult.stderr,
1742
- duration: compileResult.duration
1818
+ error: errorOutput || 'Compilation failed',
1819
+ duration: totalDuration
1743
1820
  };
1744
1821
 
1745
1822
  return res.json({
1746
1823
  ok: false,
1747
- error: compileResult.stderr,
1748
- stdout: compileResult.stdout,
1749
- duration: compileResult.duration
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: compileResult.duration
1839
+ duration: totalDuration
1759
1840
  };
1760
1841
 
1761
- console.log(" [TEST-LOCAL] Compilation successful");
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: compileResult.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(" [TEST-LOCAL] Compilation failed:", err.message);
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(` Command: ${command} ${args.join(' ')}`);
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(` [TEST-LOCAL] Server started on port ${serverPort}`);
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(" [TEST-LOCAL] Server start failed:", err.message);
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(" [TEST-LOCAL] Stopping server...");
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(" [TEST-LOCAL] Server stopped");
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(" [TEST-LOCAL] Server stop failed:", err.message);
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(" [TEST-LOCAL] Getting endpoints...");
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(` [TEST-LOCAL] Discovered ${payloadDocs.endpoints.length} endpoints`);
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(" [TEST-LOCAL] Failed to get endpoints:", err.message);
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(` [TEST-LOCAL] Executing: ${method} ${url}`);
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(` [TEST-LOCAL] Test result: ${response.status} (${duration}ms)`);
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(" [TEST-LOCAL] Test execution failed:", err.message);
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(" [TEST-LOCAL] Getting test results...");
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(" [TEST-LOCAL] Clearing test results...");
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(` [TEST-LOCAL] Getting logs (limit: ${limit})`);
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(" [CONTROLLERS] Discovering controllers...");
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(` [CONTROLLERS] Found ${apiDocs.totalEndpoints} endpoints in ${apiDocs.totalControllers} controllers`);
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(" [CONTROLLERS] Failed:", err.message);
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(" [DTOS] Analyzing DTOs...");
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(` [DTOS] Found ${payloadDocs.totalDtos} DTOs`);
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(" [DTOS] Failed:", err.message);
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(" [CONFIG] Analyzing configuration...");
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(` [CONFIG] Server port: ${config.server.port}`);
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(" [CONFIG] Failed:", err.message);
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(` Safe patch requested for incident: ${incidentId || 'unknown'}`);
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(` Target files: ${targetFiles.join(', ')}`);
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(` Could not persist backup to disk: ${e.message}`);
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(` Removed old backup: ${oldest}`);
2388
+ console.log(`Removed old backup: ${oldest}`);
2308
2389
  saveBackupIndex();
2309
2390
  }
2310
2391
 
2311
- console.log(` Backup created: ${backupId} (${backupFiles.length} files)`);
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(` Patch applied successfully: ${result.target}`);
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(` Patch failed, rolling back: ${patchError.message}`);
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(` Safe patch error: ${err.message}`);
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(` Dry-run patch validation requested`);
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(` Dry-run complete: ${targetFiles.length} files, ${hunkCount} hunks`);
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(` Dry-run error: ${err.message}`);
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(` Manual backup created: ${backupId}`);
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(` Backup error: ${err.message}`);
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(` Rolling back to backup: ${backupId}`);
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(` Rollback completed: ${backup.files.length} files restored`);
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(` Rollback error: ${err.message}`);
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(` Backup deleted: ${backupId}`);
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(` No backup found for incident ${incidentId}. Available backups:`, Array.from(BACKUPS.keys()));
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(` Found backup ${matchedBackupId} for incident ${incidentId}`);
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(` Diff error: ${err.message}`);
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(` Diff for ${backupId}: ${diffs.length} file(s) changed`);
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(` Diff error: ${err.message}`);
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(` Detecting port for service at: ${fullPath}`);
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(` Detected port: ${port || 'default'} via ${detectionMethod}`);
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(' Error detecting port:', err.message);
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(" [TEST-LOCAL] Preparing test environment...");
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(` [TEST-LOCAL] Prepared: ${payloadDocs.endpoints.length} endpoints discovered`);
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(" [TEST-LOCAL] Prepare failed:", err.message);
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(` [SCAN-FILES] Scanning: ${workspaceRoot}`);
3339
- console.log(` Extensions: ${includeExtensions.join(", ")}`);
3340
- console.log(` Exclude: ${excludePatterns.join(", ")}`);
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(` Cannot read directory ${dir}: ${error.message}`);
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(` [SCAN-FILES] Found ${files.length} files`);
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(` [SCAN-FILES] Error: ${error.message}`);
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(` [READ-FILE] Reading: ${filePath}`);
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(` [READ-FILE] Error: ${error.message}`);
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(` [READ-FILES] Reading ${filePaths.length} files`);
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(` [READ-FILES] Read ${Object.keys(files).length} files, ${errors.length} errors`);
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(` [READ-FILES] Error: ${error.message}`);
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(` [FOLDER-PICKER] Opening native folder picker...`);
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(` [FOLDER-PICKER] Selected: ${selectedPath}`);
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(` [FOLDER-PICKER] Error:`, error.message);
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(` [API-DOCS] Analyzing controllers in ${wsRoot}`);
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(` [API-DOCS] Found ${files.length} Java files`);
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(` [API-DOCS] Found ${apiDocs.totalEndpoints} endpoints in ${apiDocs.totalControllers} controllers`);
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(` [API-DOCS] Error:`, error.message);
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(` [API-DOCS] Getting endpoints for controller: ${controller}`);
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(` [API-DOCS] Error:`, error.message);
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(" [SMART-CONFIG] Collecting configuration files...");
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(` Found: ${pattern}`);
4048
+ console.log(`Found: ${pattern}`);
3968
4049
  } catch (err) {
3969
- console.error(` Error reading ${pattern}: ${err.message}`);
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(` Collected ${collectedFiles.length} files, profiles: ${availableProfiles.join(', ')}`);
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(" [SMART-CONFIG] Error:", err.message);
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(" [TEST-LOCAL] Restarting server...");
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(" [TEST-LOCAL] Restart failed:", err.message);
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(` [workspace/search] pattern="${pattern}" filter="${fileFilter || '*'}"`);
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(` Found ${count} matches`);
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(` [workspace/search] Error:`, err.message);
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(` [workspace/exec] BLOCKED dangerous command: ${command}`);
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(` [workspace/exec] Running: ${command.substring(0, 120)}...`);
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(` Command completed (stdout: ${result.stdout.length} chars)`);
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(` Command failed (exit: ${err.code}):`, err.message?.substring(0, 200));
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(` Testing: ${method} ${url} (${testName || 'unnamed'})`);
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(` ${response.status} ${response.statusText} (${durationMs}ms) ${result.passed ? '' : ''}`);
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(' Stashed uncommitted changes');
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(' Stash pop had conflicts, trying apply:', e.message);
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(` Git push gitToken provided: ${!!gitToken}, repoUrl provided: ${!!repoUrl}`);
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(` No git remote, using repoUrl from integration: ${repoUrl}`);
4537
+ console.log(`No git remote, using repoUrl from integration: ${repoUrl}`);
4450
4538
  }
4451
4539
  }
4452
4540
 
4453
- console.log(` Remote URL: ${remoteUrlRaw.replace(gitToken, '***')}`);
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(` GitHub auth URL: https://x-access-token:***@github.com/${repoPath}.git`);
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(` GitLab auth URL: https://oauth2:***@${host}/${repoPath}.git`);
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(` Generic auth URL for ${urlObj.host}`);
4572
+ console.log(`Generic auth URL for ${urlObj.host}`);
4485
4573
  }
4486
4574
 
4487
4575
  if (authenticatedUrl) {
4488
- console.log(` Pushing ${finalBranchName} with token auth...`);
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(` Pushed with token authentication`);
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(` Authenticated push failed: ${errMsg}`);
4590
+ console.warn(`Authenticated push failed: ${errMsg}`);
4503
4591
  pushResult = errMsg;
4504
4592
  }
4505
4593
  } else {
4506
- console.warn(` Could not construct authenticated URL from: ${remoteUrlRaw}`);
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(` Git auth setup failed: ${maskToken(pushErr.message)}`);
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(` Git push failed: ${pushErr2.message}`);
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(` Fix branch created: ${finalBranchName} (${commitSha.substring(0, 8)}) pushed=${pushed}`);
4585
- if (mrUrl) console.log(` MR URL: ${mrUrl}`);
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(` Merged ${finalBranchName} into ${currentBranch}`);
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(` Cherry-picked ${changedFiles.length} file(s) from ${finalBranchName}`);
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(` GitHub PR created: ${prUrl}`);
4743
+ console.log(`GitHub PR created: ${prUrl}`);
4656
4744
  } else {
4657
4745
  const errText = await response.text();
4658
- console.warn(` GitHub PR creation failed (${response.status}): ${errText.substring(0, 200)}`);
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(` GitHub PR error: ${prErr.message}`);
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(` GitLab MR created: ${prUrl}`);
4784
+ console.log(`GitLab MR created: ${prUrl}`);
4697
4785
  } else {
4698
4786
  const errText = await response.text();
4699
- console.warn(` GitLab MR creation failed (${response.status}): ${errText.substring(0, 200)}`);
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(` GitLab MR error: ${glErr.message}`);
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(` Bitbucket PR created: ${prUrl}`);
4844
+ console.log(`Bitbucket PR created: ${prUrl}`);
4757
4845
  } else {
4758
4846
  const errText = await response.text();
4759
- console.warn(` Bitbucket PR creation failed (${response.status}): ${errText.substring(0, 200)}`);
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(` Bitbucket PR error: ${bbErr.message}`);
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(` Git branch creation failed: ${err.message}`);
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(' Failed to fetch PR comments:', err.message);
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(' Failed to fetch PR comment:', err.message);
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(` PR comment added to PR #${prNumber}: ${body.substring(0, 50)}...`);
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(' Failed to add PR comment:', err.message);
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(` PR comment ${commentId} resolved on PR #${prNumber}`);
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(' Failed to resolve PR comment:', err.message);
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(` PR comment ${commentId} unresolve on PR #${prNumber}`);
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(' Failed to unresolve PR comment:', err.message);
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(` AI Fix & Resolve: PR #${prNumber}, comment ${commentId}`);
5122
- console.log(` File: ${comment.path || '(general)'}, Line: ${comment.line || 'N/A'}`);
5123
- console.log(` Request: ${comment.body.substring(0, 100)}...`);
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(' Failed to prepare fix-and-resolve:', err.message);
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(' No git remote found');
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(` Unknown git provider for URL: ${remoteUrl}`);
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(` No token found for ${providerId}. Set ${providerId.toUpperCase()}_TOKEN or GIT_TOKEN env var.`);
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(' Failed to initialize git provider:', err.message);
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 || !apiKey || !tenantId) {
5311
- console.log(' WebSocket tunnel skipped: missing gatewayUrl, apiKey or tenantId in ~/.deepdebug/config.json');
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=${encodeURIComponent(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(` WebSocket tunnel connected to Gateway`);
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(` WS command: ${command}`);
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(` WS disconnected. Reconnecting in ${reconnectDelay/1000}s...`);
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(` WS error: ${err.message}`));
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(' WebSocket tunnel requires "ws" package. Run: npm install ws');
5452
+ console.warn('WebSocket tunnel requires "ws" package. Run: npm install ws');
5365
5453
  } else {
5366
- console.warn(` WS tunnel error: ${err.message}`);
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(` WS workspace.diff ${endpoint}`);
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
- // 🤖 SETUP AGENT ROUTES
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(`🤖 [SetupAgent] Running: ${command.substring(0, 100)}`);
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(`🔐 [SetupAgent] Starting auth: ${command.substring(0, 80)}`);
5774
+ console.log(`[SetupAgent] Starting auth: ${command.substring(0, 80)}`);
5687
5775
 
5688
5776
  execCb(bgCmd, { shell: true }, (err) => {
5689
- if (err) console.warn(`⚠️ Auth command warning: ${err.message}`);
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(` Environment: ${process.env.NODE_ENV || 'development'}`);
5734
- console.log(` Process Manager initialized`);
5735
- console.log(` Backup system ready (max: ${MAX_BACKUPS} backups)`);
5736
- console.log(` AI Vibe Coding Engine: ${aiEngine?.isActive ? 'ACTIVE' : 'DISABLED'}`);
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(` Gateway URL: ${aiEngine.gatewayUrl}`);
5739
- console.log(` Max Retries: ${aiEngine.maxRetries}`);
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(` MCP HTTP Server failed to start: ${err.message}`);
5769
- console.warn(` (MCP features disabled, REST API continues normally)`);
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