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.
- package/package.json +1 -1
- package/src/server.js +211 -146
package/package.json
CHANGED
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", (
|
|
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
|
-
|
|
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(
|
|
835
|
-
const port = await detectPort(
|
|
836
|
-
res.json({ ok: true, root:
|
|
848
|
+
const meta = await detectProject(abs);
|
|
849
|
+
const port = await detectPort(abs);
|
|
850
|
+
res.json({ ok: true, root: abs, workspaceId: wsId, mode: "local", meta, port });
|
|
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 (
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
const
|
|
909
|
-
|
|
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 (
|
|
914
|
-
|
|
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(
|
|
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 (
|
|
927
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 (
|
|
1358
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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", (
|
|
1394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1513
|
-
res.json({ root:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
const
|
|
1587
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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(
|
|
1701
|
+
const meta = await detectProject(wsRoot);
|
|
1664
1702
|
const compileResult = await compileAndTest({
|
|
1665
1703
|
language: meta.language,
|
|
1666
1704
|
buildTool: meta.buildTool,
|
|
1667
|
-
cwd:
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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(
|
|
1926
|
+
const scanner = new WorkspaceScanner(wsRoot);
|
|
1887
1927
|
const structure = await scanner.scan();
|
|
1888
1928
|
|
|
1889
|
-
const controllerAnalyzer = new ControllerAnalyzer(
|
|
1929
|
+
const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
|
|
1890
1930
|
const apiDocs = await controllerAnalyzer.generateApiDocs(structure.files);
|
|
1891
1931
|
|
|
1892
|
-
const dtoAnalyzer = new DTOAnalyzer(
|
|
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 (
|
|
2046
|
-
|
|
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(
|
|
2092
|
+
const scanner = new WorkspaceScanner(wsRoot);
|
|
2052
2093
|
const structure = await scanner.scan();
|
|
2053
2094
|
|
|
2054
|
-
const controllerAnalyzer = new ControllerAnalyzer(
|
|
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 (
|
|
2071
|
-
|
|
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(
|
|
2118
|
+
const scanner = new WorkspaceScanner(wsRoot);
|
|
2077
2119
|
const structure = await scanner.scan();
|
|
2078
2120
|
|
|
2079
|
-
const controllerAnalyzer = new ControllerAnalyzer(
|
|
2121
|
+
const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
|
|
2080
2122
|
const apiDocs = await controllerAnalyzer.generateApiDocs(structure.files);
|
|
2081
2123
|
|
|
2082
|
-
const dtoAnalyzer = new DTOAnalyzer(
|
|
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 (
|
|
2099
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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", (
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
3049
|
+
const meta = await detectProject(wsRoot);
|
|
2999
3050
|
const compileResult = await compileAndTest({
|
|
3000
3051
|
language: meta.language,
|
|
3001
3052
|
buildTool: meta.buildTool,
|
|
3002
|
-
cwd:
|
|
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(
|
|
3069
|
+
const scanner = new WorkspaceScanner(wsRoot);
|
|
3019
3070
|
const structure = await scanner.scan();
|
|
3020
3071
|
|
|
3021
|
-
const controllerAnalyzer = new ControllerAnalyzer(
|
|
3072
|
+
const controllerAnalyzer = new ControllerAnalyzer(wsRoot);
|
|
3022
3073
|
const apiDocs = await controllerAnalyzer.generateApiDocs(structure.files);
|
|
3023
3074
|
|
|
3024
|
-
const dtoAnalyzer = new DTOAnalyzer(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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 ${
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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(
|
|
3776
|
+
await scanDir(wsRoot);
|
|
3723
3777
|
|
|
3724
3778
|
// Analisar os controllers
|
|
3725
|
-
const controllerAnalyzer = new ControllerAnalyzer(
|
|
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
|
-
|
|
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(
|
|
3924
|
+
const meta = await detectProject(wsRoot);
|
|
3870
3925
|
|
|
3871
3926
|
for (const pattern of configPatterns) {
|
|
3872
|
-
const filePath = path.join(
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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}" "${
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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", (
|
|
5202
|
+
app.get("/workspaces", (req, res) => {
|
|
5138
5203
|
res.json({
|
|
5139
5204
|
workspaces: wsManager.list(),
|
|
5140
5205
|
total: wsManager.count,
|