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,466 @@
1
+ import path from 'path';
2
+ import { BaseRuntime } from '../base-runtime.js';
3
+ import { exists, readFile } from '../../utils/fs-utils.js';
4
+ import { run } from '../../utils/exec-utils.js';
5
+
6
+ /**
7
+ * NpmRuntime
8
+ *
9
+ * Runtime para projetos Node.js com npm.
10
+ * Suporta Express, NestJS, Next.js, React, Vue, etc.
11
+ */
12
+ export class NpmRuntime extends BaseRuntime {
13
+
14
+ constructor() {
15
+ super();
16
+ this.framework = null;
17
+ this.packageJson = null;
18
+ }
19
+
20
+ // ==========================================
21
+ // IDENTIFICATION
22
+ // ==========================================
23
+
24
+ getId() {
25
+ return 'node-npm';
26
+ }
27
+
28
+ getName() {
29
+ return 'Node.js (npm)';
30
+ }
31
+
32
+ getLanguage() {
33
+ return 'node';
34
+ }
35
+
36
+ getBuildTool() {
37
+ return 'npm';
38
+ }
39
+
40
+ // ==========================================
41
+ // DETECTION
42
+ // ==========================================
43
+
44
+ getMarkerFiles() {
45
+ return ['package.json'];
46
+ }
47
+
48
+ async isCompatible(workspacePath) {
49
+ const pkgPath = path.join(workspacePath, 'package.json');
50
+ const yarnLock = path.join(workspacePath, 'yarn.lock');
51
+
52
+ // Se tem yarn.lock, prefere YarnRuntime
53
+ if (await exists(yarnLock)) {
54
+ return false;
55
+ }
56
+
57
+ return exists(pkgPath);
58
+ }
59
+
60
+ async detectProject(workspacePath) {
61
+ const pkgPath = path.join(workspacePath, 'package.json');
62
+ const content = await readFile(pkgPath, 'utf8');
63
+ this.packageJson = JSON.parse(content);
64
+
65
+ const name = this.packageJson.name || path.basename(workspacePath);
66
+ const version = this.packageJson.version || '0.0.1';
67
+
68
+ // Detectar framework
69
+ const framework = this.detectFramework(this.packageJson);
70
+ this.framework = framework;
71
+
72
+ // Detectar porta
73
+ const port = await this.detectPort(workspacePath);
74
+
75
+ return {
76
+ name,
77
+ version,
78
+ language: 'node',
79
+ framework,
80
+ buildTool: 'npm',
81
+ port
82
+ };
83
+ }
84
+
85
+ detectFramework(pkg) {
86
+ const deps = {
87
+ ...pkg.dependencies,
88
+ ...pkg.devDependencies
89
+ };
90
+
91
+ // Frontend frameworks
92
+ if (deps['next']) return 'next';
93
+ if (deps['nuxt']) return 'nuxt';
94
+ if (deps['@angular/core']) return 'angular';
95
+ if (deps['vue']) return deps['nuxt'] ? 'nuxt' : 'vue';
96
+ if (deps['react']) return deps['next'] ? 'next' : 'react';
97
+ if (deps['svelte']) return 'svelte';
98
+
99
+ // Backend frameworks
100
+ if (deps['@nestjs/core']) return 'nestjs';
101
+ if (deps['express']) return 'express';
102
+ if (deps['fastify']) return 'fastify';
103
+ if (deps['koa']) return 'koa';
104
+ if (deps['hapi'] || deps['@hapi/hapi']) return 'hapi';
105
+
106
+ // Outras ferramentas
107
+ if (deps['electron']) return 'electron';
108
+
109
+ return 'node-plain';
110
+ }
111
+
112
+ // ==========================================
113
+ // BUILD & RUN
114
+ // ==========================================
115
+
116
+ async install(workspacePath, options = {}) {
117
+ const startTime = Date.now();
118
+ const args = ['install'];
119
+
120
+ if (options.production) {
121
+ args.push('--production');
122
+ }
123
+ if (options.silent) {
124
+ args.push('--silent');
125
+ }
126
+
127
+ const result = await run('npm', args, workspacePath);
128
+
129
+ return {
130
+ ...result,
131
+ duration: Date.now() - startTime
132
+ };
133
+ }
134
+
135
+ async build(workspacePath, options = {}) {
136
+ const startTime = Date.now();
137
+
138
+ // Verificar se existe script de build
139
+ if (!this.packageJson?.scripts?.build) {
140
+ return {
141
+ code: 0,
142
+ stdout: 'No build script defined',
143
+ stderr: '',
144
+ duration: Date.now() - startTime
145
+ };
146
+ }
147
+
148
+ const result = await run('npm', ['run', 'build'], workspacePath);
149
+
150
+ return {
151
+ ...result,
152
+ duration: Date.now() - startTime
153
+ };
154
+ }
155
+
156
+ getStartCommand(options = {}) {
157
+ const framework = options.framework || this.framework || 'node-plain';
158
+ const port = options.port || 3000;
159
+
160
+ // Comandos específicos por framework
161
+ const commands = {
162
+ 'next': {
163
+ cmd: 'npm',
164
+ args: ['run', 'dev'],
165
+ env: { PORT: String(port) }
166
+ },
167
+ 'nuxt': {
168
+ cmd: 'npm',
169
+ args: ['run', 'dev'],
170
+ env: { PORT: String(port) }
171
+ },
172
+ 'nestjs': {
173
+ cmd: 'npm',
174
+ args: ['run', 'start:dev'],
175
+ env: { PORT: String(port) }
176
+ },
177
+ 'express': {
178
+ cmd: 'npm',
179
+ args: ['start'],
180
+ env: { PORT: String(port) }
181
+ },
182
+ 'fastify': {
183
+ cmd: 'npm',
184
+ args: ['start'],
185
+ env: { PORT: String(port) }
186
+ },
187
+ 'react': {
188
+ cmd: 'npm',
189
+ args: ['start'],
190
+ env: { PORT: String(port) }
191
+ },
192
+ 'vue': {
193
+ cmd: 'npm',
194
+ args: ['run', 'serve'],
195
+ env: { PORT: String(port) }
196
+ },
197
+ 'angular': {
198
+ cmd: 'npm',
199
+ args: ['start', '--', '--port', String(port)],
200
+ env: {}
201
+ }
202
+ };
203
+
204
+ return commands[framework] || {
205
+ cmd: 'npm',
206
+ args: ['start'],
207
+ env: { PORT: String(port) }
208
+ };
209
+ }
210
+
211
+ getReadyPattern() {
212
+ const patterns = {
213
+ 'next': /ready.*started server on/i,
214
+ 'nuxt': /Listening on/i,
215
+ 'nestjs': /Nest application successfully started/i,
216
+ 'express': /listening on port|server (is )?running/i,
217
+ 'fastify': /Server listening/i,
218
+ 'react': /compiled successfully|You can now view/i,
219
+ 'vue': /App running at/i,
220
+ 'angular': /Compiled successfully|Angular Live/i
221
+ };
222
+
223
+ return patterns[this.framework] || /listening|started|ready|running/i;
224
+ }
225
+
226
+ getDefaultPort() {
227
+ const defaults = {
228
+ 'next': 3000,
229
+ 'nuxt': 3000,
230
+ 'nestjs': 3000,
231
+ 'express': 3000,
232
+ 'fastify': 3000,
233
+ 'react': 3000,
234
+ 'vue': 8080,
235
+ 'angular': 4200
236
+ };
237
+
238
+ return defaults[this.framework] || 3000;
239
+ }
240
+
241
+ async detectPort(workspacePath) {
242
+ // Tentar .env
243
+ const envPath = path.join(workspacePath, '.env');
244
+ if (await exists(envPath)) {
245
+ const content = await readFile(envPath, 'utf8');
246
+ const match = content.match(/^PORT\s*=\s*(\d+)/m);
247
+ if (match) return parseInt(match[1]);
248
+ }
249
+
250
+ // Tentar .env.local
251
+ const envLocalPath = path.join(workspacePath, '.env.local');
252
+ if (await exists(envLocalPath)) {
253
+ const content = await readFile(envLocalPath, 'utf8');
254
+ const match = content.match(/^PORT\s*=\s*(\d+)/m);
255
+ if (match) return parseInt(match[1]);
256
+ }
257
+
258
+ // Para Next.js, verificar next.config.js
259
+ if (this.framework === 'next') {
260
+ const nextConfigPath = path.join(workspacePath, 'next.config.js');
261
+ if (await exists(nextConfigPath)) {
262
+ const content = await readFile(nextConfigPath, 'utf8');
263
+ const match = content.match(/port:\s*(\d+)/);
264
+ if (match) return parseInt(match[1]);
265
+ }
266
+ }
267
+
268
+ return this.getDefaultPort();
269
+ }
270
+
271
+ // ==========================================
272
+ // TEST
273
+ // ==========================================
274
+
275
+ async test(workspacePath, options = {}) {
276
+ const startTime = Date.now();
277
+
278
+ // Verificar se existe script de test
279
+ if (!this.packageJson?.scripts?.test) {
280
+ return {
281
+ success: true,
282
+ passed: 0,
283
+ failed: 0,
284
+ skipped: 0,
285
+ duration: Date.now() - startTime,
286
+ output: 'No test script defined',
287
+ failures: []
288
+ };
289
+ }
290
+
291
+ const result = await run('npm', ['test', '--', '--passWithNoTests'], workspacePath);
292
+
293
+ const testResult = this.parseTestOutput(result);
294
+ testResult.duration = Date.now() - startTime;
295
+
296
+ return testResult;
297
+ }
298
+
299
+ async testSingle(workspacePath, testFile) {
300
+ const startTime = Date.now();
301
+
302
+ // Detectar test runner
303
+ const deps = {
304
+ ...this.packageJson?.dependencies,
305
+ ...this.packageJson?.devDependencies
306
+ };
307
+
308
+ let args = ['test', '--'];
309
+
310
+ if (deps['jest']) {
311
+ args.push(testFile);
312
+ } else if (deps['mocha']) {
313
+ args.push(testFile);
314
+ } else if (deps['vitest']) {
315
+ args = ['run', 'vitest', testFile];
316
+ }
317
+
318
+ const result = await run('npm', args, workspacePath);
319
+
320
+ const testResult = this.parseTestOutput(result);
321
+ testResult.duration = Date.now() - startTime;
322
+
323
+ return testResult;
324
+ }
325
+
326
+ parseTestOutput(result) {
327
+ const output = result.stdout + result.stderr;
328
+
329
+ // Jest output format
330
+ const jestMatch = output.match(/Tests:\s+(\d+)\s+passed,?\s*(\d+)?\s*failed?,?\s*(\d+)?\s*skipped?/i);
331
+ if (jestMatch) {
332
+ return {
333
+ success: result.code === 0,
334
+ passed: parseInt(jestMatch[1]) || 0,
335
+ failed: parseInt(jestMatch[2]) || 0,
336
+ skipped: parseInt(jestMatch[3]) || 0,
337
+ duration: 0,
338
+ output,
339
+ failures: this.extractJestFailures(output)
340
+ };
341
+ }
342
+
343
+ // Mocha output format
344
+ const mochaMatch = output.match(/(\d+)\s+passing.*?(\d+)?\s*failing/s);
345
+ if (mochaMatch) {
346
+ return {
347
+ success: result.code === 0,
348
+ passed: parseInt(mochaMatch[1]) || 0,
349
+ failed: parseInt(mochaMatch[2]) || 0,
350
+ skipped: 0,
351
+ duration: 0,
352
+ output,
353
+ failures: []
354
+ };
355
+ }
356
+
357
+ // Fallback
358
+ return {
359
+ success: result.code === 0,
360
+ passed: result.code === 0 ? 1 : 0,
361
+ failed: result.code === 0 ? 0 : 1,
362
+ skipped: 0,
363
+ duration: 0,
364
+ output,
365
+ failures: []
366
+ };
367
+ }
368
+
369
+ extractJestFailures(output) {
370
+ const failures = [];
371
+ const failureRegex = /● (.+)/g;
372
+
373
+ let match;
374
+ while ((match = failureRegex.exec(output)) !== null) {
375
+ failures.push({
376
+ testName: match[1],
377
+ message: '',
378
+ stackTrace: ''
379
+ });
380
+ }
381
+
382
+ return failures;
383
+ }
384
+
385
+ // ==========================================
386
+ // CODE ANALYSIS
387
+ // ==========================================
388
+
389
+ getFileExtensions() {
390
+ return ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
391
+ }
392
+
393
+ async findIntegrations(workspacePath) {
394
+ const { NodeIntegrationAnalyzer } = await import('./node-integrations.js');
395
+ const analyzer = new NodeIntegrationAnalyzer(workspacePath);
396
+ return analyzer.findAll();
397
+ }
398
+
399
+ async analyzeImports(filePath, content) {
400
+ const imports = [];
401
+
402
+ // ES6 imports
403
+ const es6Regex = /import\s+(?:{[^}]+}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]/g;
404
+ let match;
405
+ while ((match = es6Regex.exec(content)) !== null) {
406
+ imports.push({
407
+ name: match[1],
408
+ type: 'import',
409
+ isExternal: !match[1].startsWith('.') && !match[1].startsWith('/')
410
+ });
411
+ }
412
+
413
+ // CommonJS requires
414
+ const cjsRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
415
+ while ((match = cjsRegex.exec(content)) !== null) {
416
+ imports.push({
417
+ name: match[1],
418
+ type: 'require',
419
+ isExternal: !match[1].startsWith('.') && !match[1].startsWith('/')
420
+ });
421
+ }
422
+
423
+ return imports;
424
+ }
425
+
426
+ // ==========================================
427
+ // PATCH
428
+ // ==========================================
429
+
430
+ async validatePatch(workspacePath, diff) {
431
+ const errors = [];
432
+
433
+ const fileRegex = /^[\+\-]{3}\s+[ab]\/(.+)$/gm;
434
+ let match;
435
+
436
+ while ((match = fileRegex.exec(diff)) !== null) {
437
+ const filePath = path.join(workspacePath, match[1]);
438
+ if (match[0].startsWith('---') && !(await exists(filePath))) {
439
+ if (!diff.includes('+++ /dev/null')) {
440
+ errors.push(`File not found: ${match[1]}`);
441
+ }
442
+ }
443
+ }
444
+
445
+ return {
446
+ valid: errors.length === 0,
447
+ errors
448
+ };
449
+ }
450
+
451
+ async postPatchActions(workspacePath, modifiedFiles) {
452
+ // Rodar prettier se disponível
453
+ const deps = {
454
+ ...this.packageJson?.dependencies,
455
+ ...this.packageJson?.devDependencies
456
+ };
457
+
458
+ if (deps['prettier']) {
459
+ for (const file of modifiedFiles) {
460
+ await run('npx', ['prettier', '--write', file], workspacePath);
461
+ }
462
+ }
463
+ }
464
+ }
465
+
466
+ export default NpmRuntime;