deepdebug-local-agent 0.3.1

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 (50) hide show
  1. package/.dockerignore +24 -0
  2. package/.idea/deepdebug-local-agent.iml +12 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/Dockerfile +46 -0
  6. package/cloudbuild.yaml +42 -0
  7. package/index.js +42 -0
  8. package/mcp-server.js +533 -0
  9. package/package.json +22 -0
  10. package/src/ai-engine.js +861 -0
  11. package/src/analyzers/config-analyzer.js +446 -0
  12. package/src/analyzers/controller-analyzer.js +429 -0
  13. package/src/analyzers/dto-analyzer.js +455 -0
  14. package/src/detectors/build-tool-detector.js +0 -0
  15. package/src/detectors/framework-detector.js +91 -0
  16. package/src/detectors/language-detector.js +89 -0
  17. package/src/detectors/multi-project-detector.js +191 -0
  18. package/src/detectors/service-detector.js +244 -0
  19. package/src/detectors.js +30 -0
  20. package/src/exec-utils.js +215 -0
  21. package/src/fs-utils.js +34 -0
  22. package/src/git/base-git-provider.js +384 -0
  23. package/src/git/git-provider-registry.js +110 -0
  24. package/src/git/github-provider.js +502 -0
  25. package/src/mcp-http-server.js +313 -0
  26. package/src/patch/patch-engine.js +339 -0
  27. package/src/patch-manager.js +816 -0
  28. package/src/patch.js +607 -0
  29. package/src/patch_bkp.js +154 -0
  30. package/src/ports.js +69 -0
  31. package/src/routes/workspace.route.js +528 -0
  32. package/src/runtimes/base-runtime.js +290 -0
  33. package/src/runtimes/java/gradle-runtime.js +378 -0
  34. package/src/runtimes/java/java-integrations.js +339 -0
  35. package/src/runtimes/java/maven-runtime.js +418 -0
  36. package/src/runtimes/node/node-integrations.js +247 -0
  37. package/src/runtimes/node/npm-runtime.js +466 -0
  38. package/src/runtimes/node/yarn-runtime.js +354 -0
  39. package/src/runtimes/runtime-registry.js +256 -0
  40. package/src/server-local.js +576 -0
  41. package/src/server.js +4565 -0
  42. package/src/utils/environment-diagnostics.js +666 -0
  43. package/src/utils/exec-utils.js +264 -0
  44. package/src/utils/fs-utils.js +218 -0
  45. package/src/workspace/detect-port.js +176 -0
  46. package/src/workspace/file-reader.js +54 -0
  47. package/src/workspace/git-client.js +0 -0
  48. package/src/workspace/process-manager.js +619 -0
  49. package/src/workspace/scanner.js +72 -0
  50. package/src/workspace-manager.js +172 -0
@@ -0,0 +1,528 @@
1
+ import express from "express";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import os from "os";
5
+
6
+ const router = express.Router();
7
+
8
+ // 🔹 Caminho global do workspace (inicializado com fallback padrão)
9
+ let workspacePath =
10
+ process.env.WORKSPACE_BASE ||
11
+ path.join(os.homedir(), "Documents", "Projects");
12
+
13
+ // --------------------------------------------------
14
+ // 🧩 1️⃣ GET /workspace/info
15
+ // Retorna informações sobre o workspace atual
16
+ // --------------------------------------------------
17
+ router.get("/info", async (req, res) => {
18
+ try {
19
+ return res.json({
20
+ workspacePath,
21
+ exists: fs.existsSync(workspacePath),
22
+ isDirectory: fs.existsSync(workspacePath)
23
+ ? fs.lstatSync(workspacePath).isDirectory()
24
+ : false,
25
+ });
26
+ } catch (err) {
27
+ console.error("❌ Error reading workspace info:", err);
28
+ return res.status(500).json({ error: err.message });
29
+ }
30
+ });
31
+
32
+ // --------------------------------------------------
33
+ // 🧩 2️⃣ POST /workspace
34
+ // Atualiza o diretório de workspace selecionado pelo usuário
35
+ // --------------------------------------------------
36
+ router.post("/", async (req, res) => {
37
+ try {
38
+ console.log(workspacePath)
39
+ const { workspacePath: newPath } = req.body;
40
+ if (!newPath) {
41
+ return res.status(400).json({ error: "Missing workspacePath" });
42
+ }
43
+
44
+ // 🔹 Resolve o caminho completo (garante formato absoluto)
45
+ const resolvedPath = path.resolve(newPath);
46
+
47
+ if (!fs.existsSync(resolvedPath) || !fs.lstatSync(resolvedPath).isDirectory()) {
48
+ return res
49
+ .status(400)
50
+ .json({ error: `Invalid directory path: ${resolvedPath}` });
51
+ }
52
+
53
+ workspacePath = resolvedPath;
54
+ console.log(`📂 Workspace updated to: ${workspacePath}`);
55
+
56
+ return res.json({ success: true, workspacePath });
57
+ } catch (err) {
58
+ console.error("❌ Error updating workspace:", err);
59
+ return res.status(500).json({ error: err.message });
60
+ }
61
+ });
62
+
63
+ // --------------------------------------------------
64
+ // 🧩 3️⃣ GET /workspace/files
65
+ // Lista arquivos .java (até 3 níveis de profundidade)
66
+ // --------------------------------------------------
67
+ router.get("/files", async (req, res) => {
68
+ try {
69
+ if (!workspacePath || !fs.existsSync(workspacePath)) {
70
+ return res.status(400).json({ error: "Workspace not configured or invalid" });
71
+ }
72
+
73
+ const files = [];
74
+
75
+ function walk(dir, depth = 0) {
76
+ if (depth > 3) return;
77
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
78
+ for (const entry of entries) {
79
+ const fullPath = path.join(dir, entry.name);
80
+ if (entry.isDirectory()) {
81
+ walk(fullPath, depth + 1);
82
+ } else if (entry.isFile() && entry.name.endsWith(".java")) {
83
+ files.push(fullPath);
84
+ }
85
+ }
86
+ }
87
+
88
+ walk(workspacePath);
89
+ return res.json({ workspacePath, files });
90
+ } catch (err) {
91
+ console.error("❌ Error listing files:", err);
92
+ return res.status(500).json({ error: err.message });
93
+ }
94
+ });
95
+
96
+ // --------------------------------------------------
97
+ // 🧩 4️⃣ GET /workspace/file
98
+ // Lê o conteúdo de um arquivo específico dentro do workspace
99
+ // --------------------------------------------------
100
+ router.get("/file", async (req, res) => {
101
+ try {
102
+ const { path: requestedPath } = req.query;
103
+
104
+ if (!requestedPath) {
105
+ return res.status(400).json({ error: "Missing 'path' query parameter" });
106
+ }
107
+
108
+ // 🔹 Corrige o caminho caso venha relativo (ex: "src/...") ou com prefixo incorreto
109
+ let fullPath = requestedPath;
110
+
111
+ if (!path.isAbsolute(requestedPath) || !requestedPath.startsWith(workspacePath)) {
112
+ fullPath = path.join(workspacePath, requestedPath.replace(/^\//, ""));
113
+ }
114
+
115
+ fullPath = path.resolve(fullPath);
116
+
117
+ console.log(`📄 Attempting to read: ${fullPath}`);
118
+
119
+ if (!fs.existsSync(fullPath)) {
120
+ return res.status(404).json({
121
+ error: "File not found",
122
+ receivedPath: requestedPath,
123
+ resolvedPath: fullPath,
124
+ workspacePath,
125
+ });
126
+ }
127
+
128
+ const content = fs.readFileSync(fullPath, "utf8");
129
+ const stats = fs.statSync(fullPath);
130
+
131
+ return res.json({
132
+ workspacePath,
133
+ fullPath,
134
+ relativePath: path.relative(workspacePath, fullPath),
135
+ size: stats.size,
136
+ modified: stats.mtime,
137
+ content,
138
+ });
139
+ } catch (err) {
140
+ console.error("❌ Error reading file:", err);
141
+ return res.status(500).json({ error: err.message });
142
+ }
143
+ });
144
+
145
+ // --------------------------------------------------
146
+ // 🧩 5️⃣ POST /workspace/resolve
147
+ // Resolve caminho absoluto do projeto pelo nome da pasta
148
+ // (usado quando o dev escolhe o workspace no Setup Wizard)
149
+ // --------------------------------------------------
150
+ router.post("/resolve", async (req, res) => {
151
+ try {
152
+ const { folderName } = req.body;
153
+ if (!folderName) {
154
+ return res.status(400).json({ error: "Missing folderName" });
155
+ }
156
+
157
+ const base = path.join(os.homedir(), "Documents", "Projects");
158
+ const resolved = path.resolve(base, folderName);
159
+
160
+ if (!fs.existsSync(resolved)) {
161
+ return res.status(400).json({ error: `Folder not found: ${resolved}` });
162
+ }
163
+
164
+ workspacePath = resolved;
165
+ console.log(`🧩 Resolved workspace path: ${workspacePath}`);
166
+
167
+ return res.json({ absolutePath: workspacePath });
168
+ } catch (err) {
169
+ console.error("❌ Error resolving absolute path:", err);
170
+ return res.status(500).json({ error: err.message });
171
+ }
172
+ });
173
+
174
+ // --------------------------------------------------
175
+ // 🧩 6️⃣ GET /workspace/detect
176
+ // Detecta tipo de projeto (Maven / Gradle / Outro)
177
+ // --------------------------------------------------
178
+ router.get("/detect", async (req, res) => {
179
+ try {
180
+ if (!workspacePath || !fs.existsSync(workspacePath)) {
181
+ return res.status(400).json({ error: "Workspace not configured" });
182
+ }
183
+
184
+ const files = fs.readdirSync(workspacePath);
185
+ const type = files.includes("pom.xml")
186
+ ? "maven"
187
+ : files.includes("build.gradle")
188
+ ? "gradle"
189
+ : "unknown";
190
+
191
+ return res.json({ type, workspacePath });
192
+ } catch (err) {
193
+ console.error("❌ Error detecting project type:", err);
194
+ return res.status(500).json({ error: err.message });
195
+ }
196
+ });
197
+
198
+ // ==========================================
199
+ // ✅ NOVOS ENDPOINTS - Investigation Mode (Sprint 1.1.5)
200
+ // ==========================================
201
+
202
+ // --------------------------------------------------
203
+ // 🧩 7️⃣ POST /workspace/search-file
204
+ // Busca recursiva por nome de arquivo no workspace
205
+ // Usado pelo InvestigationModeService para encontrar arquivos por hint
206
+ // --------------------------------------------------
207
+ router.post("/search-file", async (req, res) => {
208
+ try {
209
+ const { fileName } = req.body;
210
+
211
+ if (!fileName) {
212
+ return res.status(400).json({ error: "Missing fileName" });
213
+ }
214
+
215
+ if (!workspacePath || !fs.existsSync(workspacePath)) {
216
+ return res.status(400).json({ error: "Workspace not configured or invalid" });
217
+ }
218
+
219
+ console.log(`🔍 [search-file] Searching for: ${fileName} in ${workspacePath}`);
220
+
221
+ let foundPath = null;
222
+ const matches = [];
223
+
224
+ // Função de busca recursiva
225
+ function searchRecursive(dir, depth = 0) {
226
+ if (depth > 10 || foundPath) return; // Limitar profundidade
227
+
228
+ try {
229
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
230
+
231
+ for (const entry of entries) {
232
+ // Ignorar diretórios comuns
233
+ if (entry.name.startsWith('.') ||
234
+ entry.name === 'node_modules' ||
235
+ entry.name === 'target' ||
236
+ entry.name === 'build' ||
237
+ entry.name === '.git') {
238
+ continue;
239
+ }
240
+
241
+ const fullPath = path.join(dir, entry.name);
242
+
243
+ if (entry.isDirectory()) {
244
+ searchRecursive(fullPath, depth + 1);
245
+ } else if (entry.isFile()) {
246
+ // Match exato ou parcial
247
+ if (entry.name === fileName ||
248
+ entry.name.toLowerCase() === fileName.toLowerCase()) {
249
+ foundPath = path.relative(workspacePath, fullPath);
250
+ matches.push({
251
+ path: foundPath,
252
+ matchType: 'exact'
253
+ });
254
+ } else if (entry.name.includes(fileName.replace('.java', ''))) {
255
+ matches.push({
256
+ path: path.relative(workspacePath, fullPath),
257
+ matchType: 'partial'
258
+ });
259
+ }
260
+ }
261
+ }
262
+ } catch (err) {
263
+ // Ignorar erros de permissão
264
+ console.warn(`⚠️ Cannot read directory: ${dir}`);
265
+ }
266
+ }
267
+
268
+ searchRecursive(workspacePath);
269
+
270
+ if (foundPath) {
271
+ console.log(`✅ [search-file] Found: ${foundPath}`);
272
+ return res.json({
273
+ found: true,
274
+ path: foundPath,
275
+ matches: matches.slice(0, 10) // Limitar matches
276
+ });
277
+ }
278
+
279
+ // Se não encontrou match exato, retornar matches parciais
280
+ if (matches.length > 0) {
281
+ console.log(`🔶 [search-file] Partial matches found: ${matches.length}`);
282
+ return res.json({
283
+ found: true,
284
+ path: matches[0].path,
285
+ matches: matches.slice(0, 10)
286
+ });
287
+ }
288
+
289
+ console.log(`❌ [search-file] Not found: ${fileName}`);
290
+ return res.json({ found: false, matches: [] });
291
+
292
+ } catch (err) {
293
+ console.error("❌ Error searching file:", err);
294
+ return res.status(500).json({ error: err.message });
295
+ }
296
+ });
297
+
298
+ // --------------------------------------------------
299
+ // 🧩 8️⃣ GET /workspace/recent-files
300
+ // Lista arquivos recentemente modificados
301
+ // Usado pelo InvestigationModeService como última estratégia
302
+ // --------------------------------------------------
303
+ router.get("/recent-files", async (req, res) => {
304
+ try {
305
+ const limit = parseInt(req.query.limit) || 10;
306
+
307
+ if (!workspacePath || !fs.existsSync(workspacePath)) {
308
+ return res.status(400).json({ error: "Workspace not configured or invalid" });
309
+ }
310
+
311
+ console.log(`🕐 [recent-files] Getting ${limit} recent files from ${workspacePath}`);
312
+
313
+ const allFiles = [];
314
+
315
+ // Coletar todos os arquivos com data de modificação
316
+ function collectFiles(dir, depth = 0) {
317
+ if (depth > 5) return; // Limitar profundidade
318
+
319
+ try {
320
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
321
+
322
+ for (const entry of entries) {
323
+ // Ignorar diretórios comuns
324
+ if (entry.name.startsWith('.') ||
325
+ entry.name === 'node_modules' ||
326
+ entry.name === 'target' ||
327
+ entry.name === 'build' ||
328
+ entry.name === '.git') {
329
+ continue;
330
+ }
331
+
332
+ const fullPath = path.join(dir, entry.name);
333
+
334
+ if (entry.isDirectory()) {
335
+ collectFiles(fullPath, depth + 1);
336
+ } else if (entry.isFile() &&
337
+ (entry.name.endsWith('.java') ||
338
+ entry.name.endsWith('.js') ||
339
+ entry.name.endsWith('.ts'))) {
340
+ try {
341
+ const stats = fs.statSync(fullPath);
342
+ allFiles.push({
343
+ path: path.relative(workspacePath, fullPath),
344
+ name: entry.name,
345
+ modified: stats.mtime,
346
+ size: stats.size
347
+ });
348
+ } catch (e) {
349
+ // Ignorar arquivos que não podem ser lidos
350
+ }
351
+ }
352
+ }
353
+ } catch (err) {
354
+ // Ignorar erros de permissão
355
+ }
356
+ }
357
+
358
+ collectFiles(workspacePath);
359
+
360
+ // Ordenar por data de modificação (mais recente primeiro)
361
+ allFiles.sort((a, b) => b.modified - a.modified);
362
+
363
+ // Retornar apenas os N mais recentes
364
+ const recentFiles = allFiles.slice(0, limit);
365
+
366
+ console.log(`✅ [recent-files] Returning ${recentFiles.length} files`);
367
+
368
+ return res.json({
369
+ workspacePath,
370
+ total: allFiles.length,
371
+ files: recentFiles
372
+ });
373
+
374
+ } catch (err) {
375
+ console.error("❌ Error getting recent files:", err);
376
+ return res.status(500).json({ error: err.message });
377
+ }
378
+ });
379
+
380
+ // --------------------------------------------------
381
+ // 🧩 9️⃣ GET /workspace/scan
382
+ // Escaneia workspace completo com metadados
383
+ // Usado pelo SmartFileDetectionService para análise com AI
384
+ // --------------------------------------------------
385
+ router.get("/scan", async (req, res) => {
386
+ try {
387
+ if (!workspacePath || !fs.existsSync(workspacePath)) {
388
+ return res.status(400).json({ error: "Workspace not configured or invalid" });
389
+ }
390
+
391
+ console.log(`📂 [scan] Scanning workspace: ${workspacePath}`);
392
+
393
+ const files = [];
394
+ const languages = new Set();
395
+ let totalFiles = 0;
396
+
397
+ // Mapeamento de extensão para linguagem
398
+ const extensionMap = {
399
+ '.java': 'Java',
400
+ '.js': 'JavaScript',
401
+ '.ts': 'TypeScript',
402
+ '.py': 'Python',
403
+ '.go': 'Go',
404
+ '.rs': 'Rust',
405
+ '.kt': 'Kotlin',
406
+ '.scala': 'Scala',
407
+ '.rb': 'Ruby',
408
+ '.php': 'PHP',
409
+ '.cs': 'C#',
410
+ '.cpp': 'C++',
411
+ '.c': 'C'
412
+ };
413
+
414
+ function scanRecursive(dir, depth = 0) {
415
+ if (depth > 6) return; // Limitar profundidade
416
+
417
+ try {
418
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
419
+
420
+ for (const entry of entries) {
421
+ // Ignorar diretórios comuns
422
+ if (entry.name.startsWith('.') ||
423
+ entry.name === 'node_modules' ||
424
+ entry.name === 'target' ||
425
+ entry.name === 'build' ||
426
+ entry.name === '.git' ||
427
+ entry.name === '__pycache__') {
428
+ continue;
429
+ }
430
+
431
+ const fullPath = path.join(dir, entry.name);
432
+
433
+ if (entry.isDirectory()) {
434
+ scanRecursive(fullPath, depth + 1);
435
+ } else if (entry.isFile()) {
436
+ const ext = path.extname(entry.name).toLowerCase();
437
+
438
+ // Verificar se é arquivo de código
439
+ if (extensionMap[ext]) {
440
+ languages.add(extensionMap[ext]);
441
+ totalFiles++;
442
+
443
+ // Limitar arquivos retornados para não estourar memória
444
+ if (files.length < 200) {
445
+ try {
446
+ const stats = fs.statSync(fullPath);
447
+ files.push({
448
+ path: path.relative(workspacePath, fullPath),
449
+ name: entry.name,
450
+ language: extensionMap[ext],
451
+ size: stats.size,
452
+ modified: stats.mtime
453
+ });
454
+ } catch (e) {
455
+ // Ignorar
456
+ }
457
+ }
458
+ }
459
+ }
460
+ }
461
+ } catch (err) {
462
+ // Ignorar erros de permissão
463
+ }
464
+ }
465
+
466
+ scanRecursive(workspacePath);
467
+
468
+ console.log(`✅ [scan] Found ${totalFiles} files in ${languages.size} languages`);
469
+
470
+ return res.json({
471
+ workspacePath,
472
+ files,
473
+ metadata: {
474
+ totalFiles,
475
+ languages: Array.from(languages),
476
+ scannedAt: new Date().toISOString()
477
+ }
478
+ });
479
+
480
+ } catch (err) {
481
+ console.error("❌ Error scanning workspace:", err);
482
+ return res.status(500).json({ error: err.message });
483
+ }
484
+ });
485
+
486
+ // --------------------------------------------------
487
+ // 🧩 🔟 GET /workspace/file-content
488
+ // Alias para /workspace/file (compatibilidade com código Java)
489
+ // --------------------------------------------------
490
+ router.get("/file-content", async (req, res) => {
491
+ try {
492
+ const { path: requestedPath } = req.query;
493
+
494
+ if (!requestedPath) {
495
+ return res.status(400).json({ error: "Missing 'path' query parameter" });
496
+ }
497
+
498
+ let fullPath = requestedPath;
499
+
500
+ if (!path.isAbsolute(requestedPath) || !requestedPath.startsWith(workspacePath)) {
501
+ fullPath = path.join(workspacePath, requestedPath.replace(/^\//, ""));
502
+ }
503
+
504
+ fullPath = path.resolve(fullPath);
505
+
506
+ console.log(`📄 [file-content] Reading: ${fullPath}`);
507
+
508
+ if (!fs.existsSync(fullPath)) {
509
+ return res.status(404).json({
510
+ error: "File not found",
511
+ path: requestedPath
512
+ });
513
+ }
514
+
515
+ const content = fs.readFileSync(fullPath, "utf8");
516
+
517
+ return res.json({
518
+ content,
519
+ path: path.relative(workspacePath, fullPath)
520
+ });
521
+
522
+ } catch (err) {
523
+ console.error("❌ Error reading file content:", err);
524
+ return res.status(500).json({ error: err.message });
525
+ }
526
+ });
527
+
528
+ export default router;