deepdebug-local-agent 1.0.12 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +211 -146
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepdebug-local-agent",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "DeepDebug Local Agent - AI-powered code debugging assistant",
5
5
  "private": false,
6
6
  "type": "module",
package/src/server.js CHANGED
@@ -583,6 +583,14 @@ if (WORKSPACE_ROOT) {
583
583
  let DETECTED_SERVICES = [];
584
584
  const processManager = new ProcessManager();
585
585
  const wsManager = new WorkspaceManager();
586
+
587
+ function resolveWorkspaceRoot(req) {
588
+ const wsId = req.headers['x-workspace-id']
589
+ || (req.body && req.body.workspaceId)
590
+ || req.query.workspaceId;
591
+ if (wsId && wsManager.isOpen(wsId)) return wsManager.getRoot(wsId);
592
+ return WORKSPACE_ROOT;
593
+ }
586
594
  const MCP_PORT = process.env.MCP_PORT || 5056;
587
595
 
588
596
  // 🧠 Inicializar AI Vibe Coding Engine
@@ -702,7 +710,7 @@ function updateServiceStatus(serviceId, status) {
702
710
  }
703
711
 
704
712
  /** Health */
705
- app.get("/health", (_req, res) => {
713
+ app.get("/health", (req, res) => {
706
714
  res.json({
707
715
  status: "ok",
708
716
  workspace: WORKSPACE_ROOT || null,
@@ -785,7 +793,10 @@ app.post("/workspace/open", async (req, res) => {
785
793
  }
786
794
 
787
795
  // Set as active workspace
788
- WORKSPACE_ROOT = clonePath;
796
+ // TENANT ISOLATION: Only set global for default
797
+ if (!workspaceId || workspaceId === "default") {
798
+ WORKSPACE_ROOT = clonePath;
799
+ }
789
800
  const wsId = workspaceId || "default";
790
801
  try { await wsManager.open(wsId, clonePath); } catch (err) {
791
802
  console.warn(`⚠️ WorkspaceManager.open failed (non-fatal): ${err.message}`);
@@ -821,19 +832,22 @@ app.post("/workspace/open", async (req, res) => {
821
832
  const abs = path.resolve(root);
822
833
  if (!(await exists(abs))) return res.status(404).json({ error: "path not found" });
823
834
 
824
- WORKSPACE_ROOT = abs;
825
-
826
835
  // Registar no WorkspaceManager (multi-workspace support)
827
836
  const wsId = workspaceId || "default";
837
+
838
+ // TENANT ISOLATION: Only set global for default
839
+ if (wsId === "default") {
840
+ WORKSPACE_ROOT = abs;
841
+ }
828
842
  try {
829
843
  await wsManager.open(wsId, abs);
830
844
  } catch (err) {
831
845
  console.warn(`⚠️ WorkspaceManager open failed (non-fatal): ${err.message}`);
832
846
  }
833
847
 
834
- const meta = await detectProject(WORKSPACE_ROOT);
835
- const port = await detectPort(WORKSPACE_ROOT);
836
- res.json({ ok: true, root: WORKSPACE_ROOT, workspaceId: wsId, mode: "local", meta, port });
848
+ const meta = await detectProject(abs);
849
+ const port = await detectPort(abs);
850
+ res.json({ ok: true, root: abs, workspaceId: wsId, mode: "local", meta, port });
837
851
  });
838
852
 
839
853
  /**
@@ -902,19 +916,21 @@ app.post("/workspace/clone", async (req, res) => {
902
916
  });
903
917
 
904
918
  /** Info do workspace */
905
- app.get("/workspace/info", async (_req, res) => {
906
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
907
- const meta = await detectProject(WORKSPACE_ROOT);
908
- const port = await detectPort(WORKSPACE_ROOT);
909
- res.json({ root: WORKSPACE_ROOT, meta, port });
919
+ app.get("/workspace/info", async (req, res) => {
920
+ const wsRoot = resolveWorkspaceRoot(req);
921
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
922
+ const meta = await detectProject(wsRoot);
923
+ const port = await detectPort(wsRoot);
924
+ res.json({ root: wsRoot, meta, port });
910
925
  });
911
926
 
912
927
  /** Scan completo do workspace */
913
- app.get("/workspace/scan", async (_req, res) => {
914
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
928
+ app.get("/workspace/scan", async (req, res) => {
929
+ const wsRoot = resolveWorkspaceRoot(req);
930
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
915
931
 
916
932
  try {
917
- const scanner = new WorkspaceScanner(WORKSPACE_ROOT);
933
+ const scanner = new WorkspaceScanner(wsRoot);
918
934
  const structure = await scanner.scan();
919
935
  res.json(structure);
920
936
  } catch (err) {
@@ -923,17 +939,18 @@ app.get("/workspace/scan", async (_req, res) => {
923
939
  });
924
940
 
925
941
  /** Análise completa: language + framework */
926
- app.get("/workspace/analyze", async (_req, res) => {
927
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
942
+ app.get("/workspace/analyze", async (req, res) => {
943
+ const wsRoot = resolveWorkspaceRoot(req);
944
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
928
945
 
929
946
  try {
930
- const scanner = new WorkspaceScanner(WORKSPACE_ROOT);
947
+ const scanner = new WorkspaceScanner(wsRoot);
931
948
  const structure = await scanner.scan();
932
949
 
933
950
  const languageDetector = new LanguageDetector(structure.files);
934
951
  const languageInfo = languageDetector.detect();
935
952
 
936
- const fileReader = new FileReader(WORKSPACE_ROOT);
953
+ const fileReader = new FileReader(wsRoot);
937
954
  const frameworkDetector = new FrameworkDetector(
938
955
  languageInfo.primary,
939
956
  structure.files,
@@ -942,7 +959,7 @@ app.get("/workspace/analyze", async (_req, res) => {
942
959
  const frameworkInfo = await frameworkDetector.detect();
943
960
 
944
961
  res.json({
945
- workspace: WORKSPACE_ROOT,
962
+ workspace: wsRoot,
946
963
  language: languageInfo,
947
964
  framework: frameworkInfo,
948
965
  stats: structure.metadata,
@@ -955,13 +972,14 @@ app.get("/workspace/analyze", async (_req, res) => {
955
972
 
956
973
  /** Lê conteúdo de arquivo específico */
957
974
  app.get("/workspace/file-content", async (req, res) => {
958
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
975
+ const wsRoot = resolveWorkspaceRoot(req);
976
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
959
977
 
960
978
  const { path: relativePath } = req.query;
961
979
  if (!relativePath) return res.status(400).json({ error: "path query param required" });
962
980
 
963
981
  try {
964
- const reader = new FileReader(WORKSPACE_ROOT);
982
+ const reader = new FileReader(wsRoot);
965
983
  const file = await reader.read(relativePath);
966
984
  res.json(file);
967
985
  } catch (err) {
@@ -971,7 +989,8 @@ app.get("/workspace/file-content", async (req, res) => {
971
989
 
972
990
  /** Lê múltiplos arquivos */
973
991
  app.post("/workspace/batch-read", async (req, res) => {
974
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
992
+ const wsRoot = resolveWorkspaceRoot(req);
993
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
975
994
 
976
995
  const { paths } = req.body || {};
977
996
  if (!paths || !Array.isArray(paths)) {
@@ -979,7 +998,7 @@ app.post("/workspace/batch-read", async (req, res) => {
979
998
  }
980
999
 
981
1000
  try {
982
- const reader = new FileReader(WORKSPACE_ROOT);
1001
+ const reader = new FileReader(wsRoot);
983
1002
  const files = await reader.readMultiple(paths);
984
1003
  res.json(files);
985
1004
  } catch (err) {
@@ -996,7 +1015,8 @@ app.post("/workspace/batch-read", async (req, res) => {
996
1015
  * Checks if a file exists in the workspace
997
1016
  */
998
1017
  app.get("/workspace/file-exists", async (req, res) => {
999
- if (!WORKSPACE_ROOT) {
1018
+ const wsRoot = resolveWorkspaceRoot(req);
1019
+ if (!wsRoot) {
1000
1020
  return res.status(400).json({ error: "workspace not set" });
1001
1021
  }
1002
1022
 
@@ -1006,7 +1026,7 @@ app.get("/workspace/file-exists", async (req, res) => {
1006
1026
  }
1007
1027
 
1008
1028
  try {
1009
- const fullPath = path.join(WORKSPACE_ROOT, relativePath);
1029
+ const fullPath = path.join(wsRoot, relativePath);
1010
1030
  const fileExists = await exists(fullPath);
1011
1031
 
1012
1032
  console.log(`🔍 [file-exists] ${relativePath} -> ${fileExists ? 'EXISTS' : 'NOT FOUND'}`);
@@ -1028,7 +1048,8 @@ app.get("/workspace/file-exists", async (req, res) => {
1028
1048
  * Validates multiple file paths at once
1029
1049
  */
1030
1050
  app.post("/workspace/validate-paths", async (req, res) => {
1031
- if (!WORKSPACE_ROOT) {
1051
+ const wsRoot = resolveWorkspaceRoot(req);
1052
+ if (!wsRoot) {
1032
1053
  return res.status(400).json({ error: "workspace not set" });
1033
1054
  }
1034
1055
 
@@ -1040,7 +1061,7 @@ app.post("/workspace/validate-paths", async (req, res) => {
1040
1061
  try {
1041
1062
  const results = await Promise.all(
1042
1063
  pathList.map(async (relativePath) => {
1043
- const fullPath = path.join(WORKSPACE_ROOT, relativePath);
1064
+ const fullPath = path.join(wsRoot, relativePath);
1044
1065
  const fileExists = await exists(fullPath);
1045
1066
  return { path: relativePath, exists: fileExists };
1046
1067
  })
@@ -1071,7 +1092,8 @@ app.post("/workspace/validate-paths", async (req, res) => {
1071
1092
  * FIXED: Handle both string arrays and object arrays from listRecursive
1072
1093
  */
1073
1094
  app.post("/workspace/search-file", async (req, res) => {
1074
- if (!WORKSPACE_ROOT) {
1095
+ const wsRoot = resolveWorkspaceRoot(req);
1096
+ if (!wsRoot) {
1075
1097
  return res.status(400).json({ error: "workspace not set" });
1076
1098
  }
1077
1099
 
@@ -1083,7 +1105,7 @@ app.post("/workspace/search-file", async (req, res) => {
1083
1105
  try {
1084
1106
  console.log(`🔍 [search-file] Searching for: ${fileName}`);
1085
1107
 
1086
- const rawFiles = await listRecursive(WORKSPACE_ROOT, {
1108
+ const rawFiles = await listRecursive(wsRoot, {
1087
1109
  maxDepth: 15,
1088
1110
  includeHidden: false,
1089
1111
  extensions: null
@@ -1147,7 +1169,8 @@ app.post("/workspace/search-file", async (req, res) => {
1147
1169
  * FIXED: Handle both string arrays and object arrays from listRecursive
1148
1170
  */
1149
1171
  app.post("/workspace/search-by-content", async (req, res) => {
1150
- if (!WORKSPACE_ROOT) {
1172
+ const wsRoot = resolveWorkspaceRoot(req);
1173
+ if (!wsRoot) {
1151
1174
  return res.status(400).json({ error: "workspace not set" });
1152
1175
  }
1153
1176
 
@@ -1160,7 +1183,7 @@ app.post("/workspace/search-by-content", async (req, res) => {
1160
1183
  try {
1161
1184
  console.log(`🔍 [search-by-content] Searching for terms: ${terms.join(', ')}`);
1162
1185
 
1163
- const rawFiles = await listRecursive(WORKSPACE_ROOT, {
1186
+ const rawFiles = await listRecursive(wsRoot, {
1164
1187
  maxDepth: 15,
1165
1188
  includeHidden: false
1166
1189
  });
@@ -1186,7 +1209,7 @@ app.post("/workspace/search-by-content", async (req, res) => {
1186
1209
  if (results.length >= maxResults * 2) break;
1187
1210
 
1188
1211
  try {
1189
- const fullPath = path.join(WORKSPACE_ROOT, filePath);
1212
+ const fullPath = path.join(wsRoot, filePath);
1190
1213
  const content = await readFile(fullPath, 'utf8');
1191
1214
  const lines = content.split('\n');
1192
1215
 
@@ -1244,7 +1267,8 @@ app.post("/workspace/search-by-content", async (req, res) => {
1244
1267
  * FIXED: Handle both string arrays and object arrays from listRecursive
1245
1268
  */
1246
1269
  app.post("/workspace/find-field-definition", async (req, res) => {
1247
- if (!WORKSPACE_ROOT) {
1270
+ const wsRoot = resolveWorkspaceRoot(req);
1271
+ if (!wsRoot) {
1248
1272
  return res.status(400).json({ error: "workspace not set" });
1249
1273
  }
1250
1274
 
@@ -1257,7 +1281,7 @@ app.post("/workspace/find-field-definition", async (req, res) => {
1257
1281
  try {
1258
1282
  console.log(`🔍 [find-field] Searching for field: ${fieldName}`);
1259
1283
 
1260
- const rawFiles = await listRecursive(WORKSPACE_ROOT, {
1284
+ const rawFiles = await listRecursive(wsRoot, {
1261
1285
  maxDepth: 15,
1262
1286
  includeHidden: false
1263
1287
  });
@@ -1281,7 +1305,7 @@ app.post("/workspace/find-field-definition", async (req, res) => {
1281
1305
 
1282
1306
  for (const filePath of targetFiles) {
1283
1307
  try {
1284
- const fullPath = path.join(WORKSPACE_ROOT, filePath);
1308
+ const fullPath = path.join(wsRoot, filePath);
1285
1309
  const content = await readFile(fullPath, 'utf8');
1286
1310
  const lines = content.split('\n');
1287
1311
 
@@ -1354,17 +1378,18 @@ app.post("/workspace/find-field-definition", async (req, res) => {
1354
1378
  // ============================================
1355
1379
 
1356
1380
  /** Detecta serviços no workspace */
1357
- app.get("/workspace/services/detect", async (_req, res) => {
1358
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1381
+ app.get("/workspace/services/detect", async (req, res) => {
1382
+ const wsRoot = resolveWorkspaceRoot(req);
1383
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1359
1384
 
1360
1385
  try {
1361
- const scanner = new WorkspaceScanner(WORKSPACE_ROOT);
1386
+ const scanner = new WorkspaceScanner(wsRoot);
1362
1387
  const structure = await scanner.scan();
1363
1388
 
1364
1389
  const languageDetector = new LanguageDetector(structure.files);
1365
1390
  const languageInfo = languageDetector.detect();
1366
1391
 
1367
- const fileReader = new FileReader(WORKSPACE_ROOT);
1392
+ const fileReader = new FileReader(wsRoot);
1368
1393
  const frameworkDetector = new FrameworkDetector(
1369
1394
  languageInfo.primary,
1370
1395
  structure.files,
@@ -1373,7 +1398,7 @@ app.get("/workspace/services/detect", async (_req, res) => {
1373
1398
  const frameworkInfo = await frameworkDetector.detect();
1374
1399
 
1375
1400
  const serviceDetector = new ServiceDetector(
1376
- WORKSPACE_ROOT,
1401
+ wsRoot,
1377
1402
  languageInfo,
1378
1403
  frameworkInfo
1379
1404
  );
@@ -1390,8 +1415,9 @@ app.get("/workspace/services/detect", async (_req, res) => {
1390
1415
  });
1391
1416
 
1392
1417
  /** Lista todos os serviços */
1393
- app.get("/workspace/services", (_req, res) => {
1394
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1418
+ app.get("/workspace/services", (req, res) => {
1419
+ const wsRoot = resolveWorkspaceRoot(req);
1420
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1395
1421
 
1396
1422
  // Atualizar status dos serviços com info do ProcessManager
1397
1423
  const servicesWithStatus = DETECTED_SERVICES.map(service => {
@@ -1407,7 +1433,8 @@ app.get("/workspace/services", (_req, res) => {
1407
1433
 
1408
1434
  /** Inicia um serviço */
1409
1435
  app.post("/workspace/services/:serviceId/start", async (req, res) => {
1410
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1436
+ const wsRoot = resolveWorkspaceRoot(req);
1437
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1411
1438
 
1412
1439
  const { serviceId } = req.params;
1413
1440
  const service = DETECTED_SERVICES.find(s => s.id === serviceId);
@@ -1433,7 +1460,8 @@ app.post("/workspace/services/:serviceId/start", async (req, res) => {
1433
1460
 
1434
1461
  /** Para um serviço */
1435
1462
  app.post("/workspace/services/:serviceId/stop", async (req, res) => {
1436
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1463
+ const wsRoot = resolveWorkspaceRoot(req);
1464
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1437
1465
 
1438
1466
  const { serviceId } = req.params;
1439
1467
 
@@ -1447,7 +1475,8 @@ app.post("/workspace/services/:serviceId/stop", async (req, res) => {
1447
1475
 
1448
1476
  /** Retorna status de um serviço */
1449
1477
  app.get("/workspace/services/:serviceId/status", (req, res) => {
1450
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1478
+ const wsRoot = resolveWorkspaceRoot(req);
1479
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1451
1480
 
1452
1481
  const { serviceId } = req.params;
1453
1482
  const status = processManager.getStatus(serviceId);
@@ -1457,7 +1486,8 @@ app.get("/workspace/services/:serviceId/status", (req, res) => {
1457
1486
 
1458
1487
  /** Retorna logs de um serviço */
1459
1488
  app.get("/workspace/services/:serviceId/logs", (req, res) => {
1460
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1489
+ const wsRoot = resolveWorkspaceRoot(req);
1490
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1461
1491
 
1462
1492
  const { serviceId } = req.params;
1463
1493
  const { limit = 100 } = req.query;
@@ -1472,7 +1502,8 @@ app.get("/workspace/services/:serviceId/logs", (req, res) => {
1472
1502
 
1473
1503
  /** Streaming de logs em tempo real (SSE) */
1474
1504
  app.get("/workspace/services/:serviceId/logs/stream", (req, res) => {
1475
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1505
+ const wsRoot = resolveWorkspaceRoot(req);
1506
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1476
1507
 
1477
1508
  const { serviceId } = req.params;
1478
1509
 
@@ -1507,18 +1538,20 @@ app.get("/workspace/services/:serviceId/logs/stream", (req, res) => {
1507
1538
  // ============================================
1508
1539
 
1509
1540
  app.get("/workspace/files", async (req, res) => {
1510
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1541
+ const wsRoot = resolveWorkspaceRoot(req);
1542
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1511
1543
  const { max = 5000 } = req.query;
1512
- const tree = await listRecursive(WORKSPACE_ROOT, { maxFiles: Number(max) });
1513
- res.json({ root: WORKSPACE_ROOT, count: tree.length, tree });
1544
+ const tree = await listRecursive(wsRoot, { maxFiles: Number(max) });
1545
+ res.json({ root: wsRoot, count: tree.length, tree });
1514
1546
  });
1515
1547
 
1516
1548
  app.get("/workspace/file", async (req, res) => {
1517
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1549
+ const wsRoot = resolveWorkspaceRoot(req);
1550
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1518
1551
  const rel = req.query.path;
1519
1552
  if (!rel) return res.status(400).json({ error: "path is required" });
1520
1553
  try {
1521
- const content = await readText(WORKSPACE_ROOT, rel);
1554
+ const content = await readText(wsRoot, rel);
1522
1555
  res.json({ path: rel, content });
1523
1556
  } catch (e) {
1524
1557
  res.status(404).json({ error: "file not found", details: String(e) });
@@ -1526,11 +1559,12 @@ app.get("/workspace/file", async (req, res) => {
1526
1559
  });
1527
1560
 
1528
1561
  app.post("/workspace/write", async (req, res) => {
1529
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1562
+ const wsRoot = resolveWorkspaceRoot(req);
1563
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1530
1564
  const { path: rel, content } = req.body || {};
1531
1565
  if (!rel) return res.status(400).json({ error: "path is required" });
1532
1566
  try {
1533
- await writeFile(path.join(WORKSPACE_ROOT, rel), content ?? "", "utf8");
1567
+ await writeFile(path.join(wsRoot, rel), content ?? "", "utf8");
1534
1568
  res.json({ ok: true, path: rel, bytes: Buffer.byteLength(content ?? "", "utf8") });
1535
1569
  } catch (e) {
1536
1570
  res.status(400).json({ error: "write failed", details: String(e) });
@@ -1541,13 +1575,14 @@ app.post("/workspace/write", async (req, res) => {
1541
1575
  // ✅ CORRECTED: /workspace/patch endpoint
1542
1576
  // ============================================
1543
1577
  app.post("/workspace/patch", async (req, res) => {
1544
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1578
+ const wsRoot = resolveWorkspaceRoot(req);
1579
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1545
1580
  const { diff, incidentId } = req.body || {};
1546
1581
  if (!diff) return res.status(400).json({ error: "diff is required" });
1547
1582
 
1548
1583
  try {
1549
1584
  console.log(`📝 Applying patch for incident: ${incidentId || 'unknown'}`);
1550
- const out = await applyUnifiedDiff(WORKSPACE_ROOT, diff);
1585
+ const out = await applyUnifiedDiff(wsRoot, diff);
1551
1586
 
1552
1587
  // ✅ CRITICAL FIX: Format response as expected by Gateway
1553
1588
  const response = {
@@ -1580,18 +1615,20 @@ app.post("/workspace/patch", async (req, res) => {
1580
1615
  }
1581
1616
  });
1582
1617
 
1583
- app.post("/workspace/test", async (_req, res) => {
1584
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1585
- const meta = await detectProject(WORKSPACE_ROOT);
1586
- const result = await compileAndTest({ language: meta.language, buildTool: meta.buildTool, cwd: WORKSPACE_ROOT });
1587
- res.json({ root: WORKSPACE_ROOT, meta, result });
1618
+ app.post("/workspace/test", async (req, res) => {
1619
+ const wsRoot = resolveWorkspaceRoot(req);
1620
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1621
+ const meta = await detectProject(wsRoot);
1622
+ const result = await compileAndTest({ language: meta.language, buildTool: meta.buildTool, cwd: wsRoot });
1623
+ res.json({ root: wsRoot, meta, result });
1588
1624
  });
1589
1625
 
1590
1626
  app.post("/workspace/run", async (req, res) => {
1591
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1627
+ const wsRoot = resolveWorkspaceRoot(req);
1628
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1592
1629
  const { cmd, args = [] } = req.body || {};
1593
1630
  if (!cmd) return res.status(400).json({ error: "cmd is required" });
1594
- const out = await run(cmd, args, WORKSPACE_ROOT, 5 * 60 * 1000);
1631
+ const out = await run(cmd, args, wsRoot, 5 * 60 * 1000);
1595
1632
  res.json(out);
1596
1633
  });
1597
1634
 
@@ -1654,17 +1691,18 @@ app.get("/workspace/test-local/state", async (req, res) => {
1654
1691
  * Compiles the project without starting server
1655
1692
  */
1656
1693
  app.post("/workspace/test-local/compile", async (req, res) => {
1657
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1694
+ const wsRoot = resolveWorkspaceRoot(req);
1695
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1658
1696
 
1659
1697
  try {
1660
1698
  console.log("🔨 [TEST-LOCAL] Starting compilation...");
1661
1699
  TEST_LOCAL_STATE.status = "compiling";
1662
1700
 
1663
- const meta = await detectProject(WORKSPACE_ROOT);
1701
+ const meta = await detectProject(wsRoot);
1664
1702
  const compileResult = await compileAndTest({
1665
1703
  language: meta.language,
1666
1704
  buildTool: meta.buildTool,
1667
- cwd: WORKSPACE_ROOT,
1705
+ cwd: wsRoot,
1668
1706
  skipTests: true
1669
1707
  });
1670
1708
 
@@ -1715,7 +1753,8 @@ app.post("/workspace/test-local/compile", async (req, res) => {
1715
1753
  * 🧠 UPDATED: Now uses AI Vibe Coding Engine for auto-healing
1716
1754
  */
1717
1755
  app.post("/workspace/test-local/start", async (req, res) => {
1718
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1756
+ const wsRoot = resolveWorkspaceRoot(req);
1757
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1719
1758
 
1720
1759
  const { port } = req.body || {};
1721
1760
 
@@ -1723,14 +1762,14 @@ app.post("/workspace/test-local/start", async (req, res) => {
1723
1762
  console.log(`\n🚀 [TEST-LOCAL] Starting server (SIMPLE MODE)...`);
1724
1763
  TEST_LOCAL_STATE.status = "starting";
1725
1764
 
1726
- const meta = await detectProject(WORKSPACE_ROOT);
1765
+ const meta = await detectProject(wsRoot);
1727
1766
 
1728
1767
  // Detect port if not provided
1729
1768
  let serverPort = port || 8080;
1730
1769
 
1731
1770
  // Para Java, encontrar o JAR e correr directamente
1732
1771
  if (meta.language === 'java') {
1733
- const targetDir = path.join(WORKSPACE_ROOT, 'target');
1772
+ const targetDir = path.join(wsRoot, 'target');
1734
1773
 
1735
1774
  if (!fs.existsSync(targetDir)) {
1736
1775
  throw new Error('target/ directory not found. Run mvn clean install first.');
@@ -1774,7 +1813,7 @@ app.post("/workspace/test-local/start", async (req, res) => {
1774
1813
  const startConfig = {
1775
1814
  command,
1776
1815
  args,
1777
- cwd: WORKSPACE_ROOT,
1816
+ cwd: wsRoot,
1778
1817
  port: serverPort,
1779
1818
  env: cleanEnv
1780
1819
  };
@@ -1817,7 +1856,7 @@ app.post("/workspace/test-local/start", async (req, res) => {
1817
1856
  await processManager.start('test-local', {
1818
1857
  command,
1819
1858
  args,
1820
- cwd: WORKSPACE_ROOT,
1859
+ cwd: wsRoot,
1821
1860
  port: serverPort,
1822
1861
  env: { ...process.env, PORT: String(serverPort) }
1823
1862
  });
@@ -1868,7 +1907,8 @@ app.post("/workspace/test-local/stop", async (req, res) => {
1868
1907
  * Returns discovered endpoints
1869
1908
  */
1870
1909
  app.get("/workspace/test-local/endpoints", async (req, res) => {
1871
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
1910
+ const wsRoot = resolveWorkspaceRoot(req);
1911
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
1872
1912
 
1873
1913
  try {
1874
1914
  console.log("📋 [TEST-LOCAL] Getting endpoints...");
@@ -1883,13 +1923,13 @@ app.get("/workspace/test-local/endpoints", async (req, res) => {
1883
1923
  }
1884
1924
 
1885
1925
  // Otherwise discover them
1886
- const scanner = new WorkspaceScanner(WORKSPACE_ROOT);
1926
+ const scanner = new WorkspaceScanner(wsRoot);
1887
1927
  const structure = await scanner.scan();
1888
1928
 
1889
- const controllerAnalyzer = new ControllerAnalyzer(WORKSPACE_ROOT);
1929
+ const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
1890
1930
  const apiDocs = await controllerAnalyzer.generateApiDocs(structure.files);
1891
1931
 
1892
- const dtoAnalyzer = new DTOAnalyzer(WORKSPACE_ROOT);
1932
+ const dtoAnalyzer = new DTOAnalyzer(wsRoot);
1893
1933
  const payloadDocs = await dtoAnalyzer.generatePayloadDocs(structure.files, apiDocs.endpoints);
1894
1934
 
1895
1935
  TEST_LOCAL_STATE.endpoints = payloadDocs.endpoints;
@@ -2042,16 +2082,17 @@ app.get("/workspace/test-local/logs", (req, res) => {
2042
2082
 
2043
2083
 
2044
2084
  /** Discover controllers and endpoints */
2045
- app.get("/workspace/controllers", async (_req, res) => {
2046
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
2085
+ app.get("/workspace/controllers", async (req, res) => {
2086
+ const wsRoot = resolveWorkspaceRoot(req);
2087
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
2047
2088
 
2048
2089
  try {
2049
2090
  console.log("🔍 [CONTROLLERS] Discovering controllers...");
2050
2091
 
2051
- const scanner = new WorkspaceScanner(WORKSPACE_ROOT);
2092
+ const scanner = new WorkspaceScanner(wsRoot);
2052
2093
  const structure = await scanner.scan();
2053
2094
 
2054
- const controllerAnalyzer = new ControllerAnalyzer(WORKSPACE_ROOT);
2095
+ const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
2055
2096
  const apiDocs = await controllerAnalyzer.generateApiDocs(structure.files);
2056
2097
 
2057
2098
  console.log(`✅ [CONTROLLERS] Found ${apiDocs.totalEndpoints} endpoints in ${apiDocs.totalControllers} controllers`);
@@ -2067,19 +2108,20 @@ app.get("/workspace/controllers", async (_req, res) => {
2067
2108
  });
2068
2109
 
2069
2110
  /** Analyze DTOs and payloads */
2070
- app.get("/workspace/dtos", async (_req, res) => {
2071
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
2111
+ app.get("/workspace/dtos", async (req, res) => {
2112
+ const wsRoot = resolveWorkspaceRoot(req);
2113
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
2072
2114
 
2073
2115
  try {
2074
2116
  console.log("📦 [DTOS] Analyzing DTOs...");
2075
2117
 
2076
- const scanner = new WorkspaceScanner(WORKSPACE_ROOT);
2118
+ const scanner = new WorkspaceScanner(wsRoot);
2077
2119
  const structure = await scanner.scan();
2078
2120
 
2079
- const controllerAnalyzer = new ControllerAnalyzer(WORKSPACE_ROOT);
2121
+ const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
2080
2122
  const apiDocs = await controllerAnalyzer.generateApiDocs(structure.files);
2081
2123
 
2082
- const dtoAnalyzer = new DTOAnalyzer(WORKSPACE_ROOT);
2124
+ const dtoAnalyzer = new DTOAnalyzer(wsRoot);
2083
2125
  const payloadDocs = await dtoAnalyzer.generatePayloadDocs(structure.files, apiDocs.endpoints);
2084
2126
 
2085
2127
  console.log(`✅ [DTOS] Found ${payloadDocs.totalDtos} DTOs`);
@@ -2095,13 +2137,14 @@ app.get("/workspace/dtos", async (_req, res) => {
2095
2137
  });
2096
2138
 
2097
2139
  /** Get server configuration */
2098
- app.get("/workspace/config", async (_req, res) => {
2099
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
2140
+ app.get("/workspace/config", async (req, res) => {
2141
+ const wsRoot = resolveWorkspaceRoot(req);
2142
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
2100
2143
 
2101
2144
  try {
2102
2145
  console.log("⚙️ [CONFIG] Analyzing configuration...");
2103
2146
 
2104
- const configAnalyzer = new ConfigAnalyzer(WORKSPACE_ROOT);
2147
+ const configAnalyzer = new ConfigAnalyzer(wsRoot);
2105
2148
  const config = await configAnalyzer.analyze();
2106
2149
 
2107
2150
  console.log(`✅ [CONFIG] Server port: ${config.server.port}`);
@@ -2169,7 +2212,8 @@ function extractTargetFiles(diff) {
2169
2212
  * Applies patch with automatic backup and rollback on failure
2170
2213
  */
2171
2214
  app.post("/workspace/safe-patch", async (req, res) => {
2172
- if (!WORKSPACE_ROOT) {
2215
+ const wsRoot = resolveWorkspaceRoot(req);
2216
+ if (!wsRoot) {
2173
2217
  return res.status(400).json({
2174
2218
  error: "workspace not set",
2175
2219
  hint: "call POST /workspace/open first"
@@ -2201,7 +2245,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
2201
2245
  const backupFiles = [];
2202
2246
 
2203
2247
  for (const relPath of targetFiles) {
2204
- const fullPath = path.join(WORKSPACE_ROOT, relPath);
2248
+ const fullPath = path.join(wsRoot, relPath);
2205
2249
  if (await exists(fullPath)) {
2206
2250
  const content = await readFile(fullPath, 'utf8');
2207
2251
  backupFiles.push({ path: relPath, content });
@@ -2240,7 +2284,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
2240
2284
 
2241
2285
  // 4. Apply patch
2242
2286
  try {
2243
- const result = await applyUnifiedDiff(WORKSPACE_ROOT, diff);
2287
+ const result = await applyUnifiedDiff(wsRoot, diff);
2244
2288
  console.log(`✅ Patch applied successfully: ${result.target}`);
2245
2289
 
2246
2290
  res.json({
@@ -2255,7 +2299,7 @@ app.post("/workspace/safe-patch", async (req, res) => {
2255
2299
  console.error(`❌ Patch failed, rolling back: ${patchError.message}`);
2256
2300
 
2257
2301
  for (const file of backupFiles) {
2258
- const fullPath = path.join(WORKSPACE_ROOT, file.path);
2302
+ const fullPath = path.join(wsRoot, file.path);
2259
2303
  await writeFile(fullPath, file.content, 'utf8');
2260
2304
  }
2261
2305
 
@@ -2279,7 +2323,8 @@ app.post("/workspace/safe-patch", async (req, res) => {
2279
2323
  * Validates and simulates patch application WITHOUT modifying files
2280
2324
  */
2281
2325
  app.post("/workspace/patch/dry-run", async (req, res) => {
2282
- if (!WORKSPACE_ROOT) {
2326
+ const wsRoot = resolveWorkspaceRoot(req);
2327
+ if (!wsRoot) {
2283
2328
  return res.status(400).json({
2284
2329
  error: "workspace not set",
2285
2330
  hint: "call POST /workspace/open first"
@@ -2311,7 +2356,7 @@ app.post("/workspace/patch/dry-run", async (req, res) => {
2311
2356
  // 3. Check if files exist
2312
2357
  const fileChecks = await Promise.all(
2313
2358
  targetFiles.map(async (relPath) => {
2314
- const fullPath = path.join(WORKSPACE_ROOT, relPath);
2359
+ const fullPath = path.join(wsRoot, relPath);
2315
2360
  const fileExists = await exists(fullPath);
2316
2361
  let currentContent = null;
2317
2362
  let lineCount = 0;
@@ -2382,7 +2427,8 @@ app.post("/workspace/validate-diff", async (req, res) => {
2382
2427
  * Creates manual backup of specific files
2383
2428
  */
2384
2429
  app.post("/workspace/backup", async (req, res) => {
2385
- if (!WORKSPACE_ROOT) {
2430
+ const wsRoot = resolveWorkspaceRoot(req);
2431
+ if (!wsRoot) {
2386
2432
  return res.status(400).json({
2387
2433
  error: "workspace not set",
2388
2434
  hint: "call POST /workspace/open first"
@@ -2399,7 +2445,7 @@ app.post("/workspace/backup", async (req, res) => {
2399
2445
  const backupFiles = [];
2400
2446
 
2401
2447
  for (const relPath of files) {
2402
- const fullPath = path.join(WORKSPACE_ROOT, relPath);
2448
+ const fullPath = path.join(wsRoot, relPath);
2403
2449
  if (await exists(fullPath)) {
2404
2450
  const content = await readFile(fullPath, 'utf8');
2405
2451
  backupFiles.push({ path: relPath, content });
@@ -2439,7 +2485,8 @@ app.post("/workspace/backup", async (req, res) => {
2439
2485
  * Restores files from a backup
2440
2486
  */
2441
2487
  app.post("/workspace/rollback", async (req, res) => {
2442
- if (!WORKSPACE_ROOT) {
2488
+ const wsRoot = resolveWorkspaceRoot(req);
2489
+ if (!wsRoot) {
2443
2490
  return res.status(400).json({
2444
2491
  error: "workspace not set",
2445
2492
  hint: "call POST /workspace/open first"
@@ -2463,7 +2510,7 @@ app.post("/workspace/rollback", async (req, res) => {
2463
2510
  console.log(`♻️ Rolling back to backup: ${backupId}`);
2464
2511
 
2465
2512
  for (const file of backup.files) {
2466
- const fullPath = path.join(WORKSPACE_ROOT, file.path);
2513
+ const fullPath = path.join(wsRoot, file.path);
2467
2514
  await writeFile(fullPath, file.content, 'utf8');
2468
2515
  }
2469
2516
 
@@ -2485,7 +2532,7 @@ app.post("/workspace/rollback", async (req, res) => {
2485
2532
  * GET /workspace/backups
2486
2533
  * Lists all available backups
2487
2534
  */
2488
- app.get("/workspace/backups", (_req, res) => {
2535
+ app.get("/workspace/backups", (req, res) => {
2489
2536
  const backupList = Array.from(BACKUPS.entries()).map(([id, data]) => ({
2490
2537
  backupId: id,
2491
2538
  timestamp: data.timestamp,
@@ -2539,7 +2586,8 @@ app.delete("/workspace/backups/:backupId", (req, res) => {
2539
2586
  app.get("/workspace/diff/by-incident/:incidentId", async (req, res) => {
2540
2587
  const { incidentId } = req.params;
2541
2588
 
2542
- if (!WORKSPACE_ROOT) {
2589
+ const wsRoot = resolveWorkspaceRoot(req);
2590
+ if (!wsRoot) {
2543
2591
  return res.status(400).json({ error: "workspace not set" });
2544
2592
  }
2545
2593
 
@@ -2589,7 +2637,7 @@ app.get("/workspace/diff/by-incident/:incidentId", async (req, res) => {
2589
2637
  try {
2590
2638
  const diffs = [];
2591
2639
  for (const file of matchedBackup.files) {
2592
- const fullPath = path.join(WORKSPACE_ROOT, file.path);
2640
+ const fullPath = path.join(wsRoot, file.path);
2593
2641
  let currentContent = '';
2594
2642
  try {
2595
2643
  currentContent = await readFile(fullPath, 'utf8');
@@ -2650,7 +2698,8 @@ app.get("/workspace/diff/by-incident/:incidentId", async (req, res) => {
2650
2698
  app.get("/workspace/diff/:backupId", async (req, res) => {
2651
2699
  const { backupId } = req.params;
2652
2700
 
2653
- if (!WORKSPACE_ROOT) {
2701
+ const wsRoot = resolveWorkspaceRoot(req);
2702
+ if (!wsRoot) {
2654
2703
  return res.status(400).json({ error: "workspace not set" });
2655
2704
  }
2656
2705
 
@@ -2667,7 +2716,7 @@ app.get("/workspace/diff/:backupId", async (req, res) => {
2667
2716
  const diffs = [];
2668
2717
 
2669
2718
  for (const file of backup.files) {
2670
- const fullPath = path.join(WORKSPACE_ROOT, file.path);
2719
+ const fullPath = path.join(wsRoot, file.path);
2671
2720
  let currentContent = '';
2672
2721
 
2673
2722
  try {
@@ -2724,7 +2773,8 @@ app.get("/workspace/diff/:backupId", async (req, res) => {
2724
2773
  * - framework: spring-boot, express, flask, etc (optional)
2725
2774
  */
2726
2775
  app.get("/workspace/detect-port", async (req, res) => {
2727
- if (!WORKSPACE_ROOT) {
2776
+ const wsRoot = resolveWorkspaceRoot(req);
2777
+ if (!wsRoot) {
2728
2778
  return res.status(400).json({
2729
2779
  error: "workspace not set",
2730
2780
  hint: "call POST /workspace/open first"
@@ -2734,7 +2784,7 @@ app.get("/workspace/detect-port", async (req, res) => {
2734
2784
  const { servicePath = '', language, framework } = req.query;
2735
2785
 
2736
2786
  try {
2737
- const fullPath = path.join(WORKSPACE_ROOT, servicePath);
2787
+ const fullPath = path.join(wsRoot, servicePath);
2738
2788
  console.log(`🔍 Detecting port for service at: ${fullPath}`);
2739
2789
 
2740
2790
  let port = null;
@@ -2988,18 +3038,19 @@ async function detectDotNetPort(servicePath) {
2988
3038
 
2989
3039
  /** Prepare for testing: compile + discover endpoints */
2990
3040
  app.post("/workspace/test-local/prepare", async (req, res) => {
2991
- if (!WORKSPACE_ROOT) return res.status(400).json({ error: "workspace not set" });
3041
+ const wsRoot = resolveWorkspaceRoot(req);
3042
+ if (!wsRoot) return res.status(400).json({ error: "workspace not set" });
2992
3043
 
2993
3044
  try {
2994
3045
  console.log("🔧 [TEST-LOCAL] Preparing test environment...");
2995
3046
  TEST_LOCAL_STATE.status = "compiling";
2996
3047
 
2997
3048
  // Step 1: Compile
2998
- const meta = await detectProject(WORKSPACE_ROOT);
3049
+ const meta = await detectProject(wsRoot);
2999
3050
  const compileResult = await compileAndTest({
3000
3051
  language: meta.language,
3001
3052
  buildTool: meta.buildTool,
3002
- cwd: WORKSPACE_ROOT,
3053
+ cwd: wsRoot,
3003
3054
  skipTests: true
3004
3055
  });
3005
3056
 
@@ -3015,19 +3066,19 @@ app.post("/workspace/test-local/prepare", async (req, res) => {
3015
3066
  TEST_LOCAL_STATE.status = "compiled";
3016
3067
 
3017
3068
  // Step 2: Discover endpoints
3018
- const scanner = new WorkspaceScanner(WORKSPACE_ROOT);
3069
+ const scanner = new WorkspaceScanner(wsRoot);
3019
3070
  const structure = await scanner.scan();
3020
3071
 
3021
- const controllerAnalyzer = new ControllerAnalyzer(WORKSPACE_ROOT);
3072
+ const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
3022
3073
  const apiDocs = await controllerAnalyzer.generateApiDocs(structure.files);
3023
3074
 
3024
- const dtoAnalyzer = new DTOAnalyzer(WORKSPACE_ROOT);
3075
+ const dtoAnalyzer = new DTOAnalyzer(wsRoot);
3025
3076
  const payloadDocs = await dtoAnalyzer.generatePayloadDocs(structure.files, apiDocs.endpoints);
3026
3077
 
3027
3078
  TEST_LOCAL_STATE.endpoints = payloadDocs.endpoints;
3028
3079
 
3029
3080
  // Step 3: Get config
3030
- const configAnalyzer = new ConfigAnalyzer(WORKSPACE_ROOT);
3081
+ const configAnalyzer = new ConfigAnalyzer(wsRoot);
3031
3082
  const config = await configAnalyzer.analyze();
3032
3083
  TEST_LOCAL_STATE.config = config;
3033
3084
 
@@ -3065,7 +3116,8 @@ app.post("/workspace/test-local/prepare", async (req, res) => {
3065
3116
  * SSE endpoint for real-time compilation logs
3066
3117
  */
3067
3118
  app.get("/workspace/test-local/compile/stream", async (req, res) => {
3068
- if (!WORKSPACE_ROOT) {
3119
+ const wsRoot = resolveWorkspaceRoot(req);
3120
+ if (!wsRoot) {
3069
3121
  res.status(400).json({ error: "workspace not set" });
3070
3122
  return;
3071
3123
  }
@@ -3079,7 +3131,7 @@ app.get("/workspace/test-local/compile/stream", async (req, res) => {
3079
3131
  res.flushHeaders();
3080
3132
 
3081
3133
  try {
3082
- const meta = await detectProject(WORKSPACE_ROOT);
3134
+ const meta = await detectProject(wsRoot);
3083
3135
 
3084
3136
  // Send initial event
3085
3137
  res.write(`event: log\n`);
@@ -3121,7 +3173,7 @@ app.get("/workspace/test-local/compile/stream", async (req, res) => {
3121
3173
 
3122
3174
  const startTime = Date.now();
3123
3175
  const proc = spawn(command, args, {
3124
- cwd: WORKSPACE_ROOT,
3176
+ cwd: wsRoot,
3125
3177
  shell: true,
3126
3178
  env: { ...process.env, MAVEN_OPTS: "-Dorg.slf4j.simpleLogger.defaultLogLevel=info" }
3127
3179
  });
@@ -3593,14 +3645,15 @@ app.get("/system/info", (req, res) => {
3593
3645
  * Analisa controllers e retorna todos os endpoints da API
3594
3646
  */
3595
3647
  app.get("/workspace/api-docs", async (req, res) => {
3596
- if (!WORKSPACE_ROOT) {
3648
+ const wsRoot = resolveWorkspaceRoot(req);
3649
+ if (!wsRoot) {
3597
3650
  return res.status(400).json({
3598
3651
  error: "workspace not set",
3599
3652
  hint: "call POST /workspace/open first"
3600
3653
  });
3601
3654
  }
3602
3655
 
3603
- console.log(`📚 [API-DOCS] Analyzing controllers in ${WORKSPACE_ROOT}`);
3656
+ console.log(`📚 [API-DOCS] Analyzing controllers in ${wsRoot}`);
3604
3657
 
3605
3658
  try {
3606
3659
  // Primeiro, escanear arquivos do workspace
@@ -3617,7 +3670,7 @@ app.get("/workspace/api-docs", async (req, res) => {
3617
3670
 
3618
3671
  for (const entry of entries) {
3619
3672
  const fullPath = pathModule.join(dir, entry.name);
3620
- const relativePath = pathModule.relative(WORKSPACE_ROOT, fullPath);
3673
+ const relativePath = pathModule.relative(wsRoot, fullPath);
3621
3674
 
3622
3675
  // Skip common ignored directories
3623
3676
  if (entry.name === 'node_modules' || entry.name === 'target' ||
@@ -3637,12 +3690,12 @@ app.get("/workspace/api-docs", async (req, res) => {
3637
3690
  }
3638
3691
  }
3639
3692
 
3640
- await scanDir(WORKSPACE_ROOT);
3693
+ await scanDir(wsRoot);
3641
3694
 
3642
3695
  console.log(`📁 [API-DOCS] Found ${files.length} Java files`);
3643
3696
 
3644
3697
  // Agora analisar os controllers
3645
- const controllerAnalyzer = new ControllerAnalyzer(WORKSPACE_ROOT);
3698
+ const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
3646
3699
  const apiDocs = await controllerAnalyzer.generateApiDocs(files);
3647
3700
 
3648
3701
  console.log(`✅ [API-DOCS] Found ${apiDocs.totalEndpoints} endpoints in ${apiDocs.totalControllers} controllers`);
@@ -3653,7 +3706,7 @@ app.get("/workspace/api-docs", async (req, res) => {
3653
3706
  controllers: apiDocs.controllers.map(c => c.className),
3654
3707
  totalEndpoints: apiDocs.totalEndpoints,
3655
3708
  totalControllers: apiDocs.totalControllers,
3656
- workspace: WORKSPACE_ROOT
3709
+ workspace: wsRoot
3657
3710
  });
3658
3711
 
3659
3712
  } catch (error) {
@@ -3675,7 +3728,8 @@ app.get("/workspace/api-docs", async (req, res) => {
3675
3728
  * Retorna endpoints de uma controller específica
3676
3729
  */
3677
3730
  app.get("/workspace/api-docs/:controller", async (req, res) => {
3678
- if (!WORKSPACE_ROOT) {
3731
+ const wsRoot = resolveWorkspaceRoot(req);
3732
+ if (!wsRoot) {
3679
3733
  return res.status(400).json({
3680
3734
  error: "workspace not set",
3681
3735
  hint: "call POST /workspace/open first"
@@ -3700,7 +3754,7 @@ app.get("/workspace/api-docs/:controller", async (req, res) => {
3700
3754
 
3701
3755
  for (const entry of entries) {
3702
3756
  const fullPath = pathModule.join(dir, entry.name);
3703
- const relativePath = pathModule.relative(WORKSPACE_ROOT, fullPath);
3757
+ const relativePath = pathModule.relative(wsRoot, fullPath);
3704
3758
 
3705
3759
  if (entry.name === 'node_modules' || entry.name === 'target' ||
3706
3760
  entry.name === 'build' || entry.name === '.git' ||
@@ -3719,10 +3773,10 @@ app.get("/workspace/api-docs/:controller", async (req, res) => {
3719
3773
  }
3720
3774
  }
3721
3775
 
3722
- await scanDir(WORKSPACE_ROOT);
3776
+ await scanDir(wsRoot);
3723
3777
 
3724
3778
  // Analisar os controllers
3725
- const controllerAnalyzer = new ControllerAnalyzer(WORKSPACE_ROOT);
3779
+ const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
3726
3780
  const apiDocs = await controllerAnalyzer.generateApiDocs(files);
3727
3781
 
3728
3782
  const found = apiDocs.controllers?.find(c =>
@@ -3843,7 +3897,8 @@ app.get("/ai-engine/fixes", (req, res) => {
3843
3897
  * Recolhe ficheiros de configuração para análise AI
3844
3898
  */
3845
3899
  app.get("/workspace/smart-config", async (req, res) => {
3846
- if (!WORKSPACE_ROOT) {
3900
+ const wsRoot = resolveWorkspaceRoot(req);
3901
+ if (!wsRoot) {
3847
3902
  return res.status(400).json({ error: "workspace not set" });
3848
3903
  }
3849
3904
 
@@ -3866,10 +3921,10 @@ app.get("/workspace/smart-config", async (req, res) => {
3866
3921
  ];
3867
3922
 
3868
3923
  const collectedFiles = [];
3869
- const meta = await detectProject(WORKSPACE_ROOT);
3924
+ const meta = await detectProject(wsRoot);
3870
3925
 
3871
3926
  for (const pattern of configPatterns) {
3872
- const filePath = path.join(WORKSPACE_ROOT, pattern);
3927
+ const filePath = path.join(wsRoot, pattern);
3873
3928
  if (fs.existsSync(filePath)) {
3874
3929
  try {
3875
3930
  const content = fs.readFileSync(filePath, 'utf8');
@@ -3890,7 +3945,7 @@ app.get("/workspace/smart-config", async (req, res) => {
3890
3945
 
3891
3946
  // Detectar profiles disponíveis
3892
3947
  const availableProfiles = [];
3893
- const resourcesDir = path.join(WORKSPACE_ROOT, 'src/main/resources');
3948
+ const resourcesDir = path.join(wsRoot, 'src/main/resources');
3894
3949
  if (fs.existsSync(resourcesDir)) {
3895
3950
  const files = fs.readdirSync(resourcesDir);
3896
3951
  files.forEach(file => {
@@ -3903,7 +3958,7 @@ app.get("/workspace/smart-config", async (req, res) => {
3903
3958
 
3904
3959
  res.json({
3905
3960
  ok: true,
3906
- workspace: WORKSPACE_ROOT,
3961
+ workspace: wsRoot,
3907
3962
  language: meta.language,
3908
3963
  buildTool: meta.buildTool,
3909
3964
  framework: meta.framework || 'unknown',
@@ -3989,7 +4044,8 @@ app.post("/workspace/test-local/restart", async (req, res) => {
3989
4044
  * Response: { "ok": true, "results": "file:line: matching text\n...", "count": 15 }
3990
4045
  */
3991
4046
  app.post("/workspace/search", async (req, res) => {
3992
- if (!WORKSPACE_ROOT) {
4047
+ const wsRoot = resolveWorkspaceRoot(req);
4048
+ if (!wsRoot) {
3993
4049
  return res.status(400).json({
3994
4050
  ok: false,
3995
4051
  error: "workspace not set",
@@ -4024,7 +4080,7 @@ app.post("/workspace/search", async (req, res) => {
4024
4080
 
4025
4081
  // Escape pattern for shell safety and limit results
4026
4082
  const safePattern = pattern.replace(/"/g, '\\"').replace(/`/g, '\\`').replace(/\$/g, '\\$');
4027
- cmd += `"${safePattern}" "${WORKSPACE_ROOT}" | head -100`;
4083
+ cmd += `"${safePattern}" "${wsRoot}" | head -100`;
4028
4084
 
4029
4085
  let stdout = '';
4030
4086
  try {
@@ -4056,7 +4112,7 @@ app.post("/workspace/search", async (req, res) => {
4056
4112
  const results = stdout
4057
4113
  .split('\n')
4058
4114
  .filter(line => line.trim())
4059
- .map(line => line.replace(WORKSPACE_ROOT + '/', ''))
4115
+ .map(line => line.replace(wsRoot + '/', ''))
4060
4116
  .join('\n');
4061
4117
 
4062
4118
  const count = results.split('\n').filter(l => l.trim()).length;
@@ -4086,7 +4142,8 @@ app.post("/workspace/search", async (req, res) => {
4086
4142
  * Dangerous commands are blocked.
4087
4143
  */
4088
4144
  app.post("/workspace/exec", async (req, res) => {
4089
- if (!WORKSPACE_ROOT) {
4145
+ const wsRoot = resolveWorkspaceRoot(req);
4146
+ if (!wsRoot) {
4090
4147
  return res.status(400).json({
4091
4148
  ok: false,
4092
4149
  error: "workspace not set",
@@ -4122,7 +4179,7 @@ app.post("/workspace/exec", async (req, res) => {
4122
4179
  const execAsync = promisify(execCb);
4123
4180
 
4124
4181
  const result = await execAsync(command, {
4125
- cwd: WORKSPACE_ROOT,
4182
+ cwd: wsRoot,
4126
4183
  timeout: 60000,
4127
4184
  maxBuffer: 5 * 1024 * 1024,
4128
4185
  env: { ...process.env, FORCE_COLOR: '0' }
@@ -4253,7 +4310,8 @@ app.post("/workspace/test-endpoint", async (req, res) => {
4253
4310
  * Body: { branchName, commitMessage, files? }
4254
4311
  */
4255
4312
  app.post("/workspace/git/create-fix-branch", async (req, res) => {
4256
- if (!WORKSPACE_ROOT) {
4313
+ const wsRoot = resolveWorkspaceRoot(req);
4314
+ if (!wsRoot) {
4257
4315
  return res.status(400).json({ error: "workspace not set" });
4258
4316
  }
4259
4317
 
@@ -4265,7 +4323,7 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
4265
4323
  try {
4266
4324
  const { execSync } = await import('child_process');
4267
4325
  const opts = {
4268
- cwd: WORKSPACE_ROOT,
4326
+ cwd: wsRoot,
4269
4327
  encoding: 'utf8',
4270
4328
  timeout: 30000,
4271
4329
  env: {
@@ -4707,13 +4765,14 @@ app.post("/workspace/git/create-fix-branch", async (req, res) => {
4707
4765
  * Returns current git status (branch, changes, remote)
4708
4766
  */
4709
4767
  app.get("/workspace/git/status", async (req, res) => {
4710
- if (!WORKSPACE_ROOT) {
4768
+ const wsRoot = resolveWorkspaceRoot(req);
4769
+ if (!wsRoot) {
4711
4770
  return res.status(400).json({ error: "workspace not set" });
4712
4771
  }
4713
4772
 
4714
4773
  try {
4715
4774
  const { execSync } = await import('child_process');
4716
- const opts = { cwd: WORKSPACE_ROOT, encoding: 'utf8', timeout: 10000 };
4775
+ const opts = { cwd: wsRoot, encoding: 'utf8', timeout: 10000 };
4717
4776
 
4718
4777
  let branch = '', remoteUrl = '', status = '';
4719
4778
  let isGitRepo = false;
@@ -4756,7 +4815,8 @@ app.get("/workspace/git/status", async (req, res) => {
4756
4815
  * - filter: 'all' | 'unresolved' | 'review' (default: 'all')
4757
4816
  */
4758
4817
  app.get("/workspace/git/pr/comments", async (req, res) => {
4759
- if (!WORKSPACE_ROOT) {
4818
+ const wsRoot = resolveWorkspaceRoot(req);
4819
+ if (!wsRoot) {
4760
4820
  return res.status(400).json({ error: "workspace not set" });
4761
4821
  }
4762
4822
 
@@ -4821,7 +4881,8 @@ app.get("/workspace/git/pr/comments", async (req, res) => {
4821
4881
  * - prNumber (required): PR/MR number
4822
4882
  */
4823
4883
  app.get("/workspace/git/pr/comments/:commentId", async (req, res) => {
4824
- if (!WORKSPACE_ROOT) {
4884
+ const wsRoot = resolveWorkspaceRoot(req);
4885
+ if (!wsRoot) {
4825
4886
  return res.status(400).json({ error: "workspace not set" });
4826
4887
  }
4827
4888
 
@@ -4864,7 +4925,8 @@ app.get("/workspace/git/pr/comments/:commentId", async (req, res) => {
4864
4925
  * - inReplyToId: Parent comment ID (for thread replies)
4865
4926
  */
4866
4927
  app.post("/workspace/git/pr/comments", async (req, res) => {
4867
- if (!WORKSPACE_ROOT) {
4928
+ const wsRoot = resolveWorkspaceRoot(req);
4929
+ if (!wsRoot) {
4868
4930
  return res.status(400).json({ error: "workspace not set" });
4869
4931
  }
4870
4932
 
@@ -4921,7 +4983,8 @@ app.post("/workspace/git/pr/comments", async (req, res) => {
4921
4983
  * - prNumber (required): PR/MR number
4922
4984
  */
4923
4985
  app.post("/workspace/git/pr/comments/:commentId/resolve", async (req, res) => {
4924
- if (!WORKSPACE_ROOT) {
4986
+ const wsRoot = resolveWorkspaceRoot(req);
4987
+ if (!wsRoot) {
4925
4988
  return res.status(400).json({ error: "workspace not set" });
4926
4989
  }
4927
4990
 
@@ -4958,7 +5021,8 @@ app.post("/workspace/git/pr/comments/:commentId/resolve", async (req, res) => {
4958
5021
  * - prNumber (required): PR/MR number
4959
5022
  */
4960
5023
  app.post("/workspace/git/pr/comments/:commentId/unresolve", async (req, res) => {
4961
- if (!WORKSPACE_ROOT) {
5024
+ const wsRoot = resolveWorkspaceRoot(req);
5025
+ if (!wsRoot) {
4962
5026
  return res.status(400).json({ error: "workspace not set" });
4963
5027
  }
4964
5028
 
@@ -4999,7 +5063,8 @@ app.post("/workspace/git/pr/comments/:commentId/unresolve", async (req, res) =>
4999
5063
  * - branch: Branch to apply fix on (defaults to current branch)
5000
5064
  */
5001
5065
  app.post("/workspace/git/pr/comments/:commentId/fix-and-resolve", async (req, res) => {
5002
- if (!WORKSPACE_ROOT) {
5066
+ const wsRoot = resolveWorkspaceRoot(req);
5067
+ if (!wsRoot) {
5003
5068
  return res.status(400).json({ error: "workspace not set" });
5004
5069
  }
5005
5070
 
@@ -5134,7 +5199,7 @@ async function _getGitProvider() {
5134
5199
  // ============================================
5135
5200
 
5136
5201
  /** Lista todos os workspaces abertos */
5137
- app.get("/workspaces", (_req, res) => {
5202
+ app.get("/workspaces", (req, res) => {
5138
5203
  res.json({
5139
5204
  workspaces: wsManager.list(),
5140
5205
  total: wsManager.count,