deepdebug-local-agent 0.3.8 → 0.3.9

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.
@@ -0,0 +1,191 @@
1
+ import path from "path";
2
+ import { exists, readdir, stat } from "../fs-utils.js";
3
+ import { ServiceDetector } from "./service-detector.js";
4
+ import { LanguageDetector } from "./language-detector.js";
5
+ import { FrameworkDetector } from "./framework-detector.js";
6
+ import { WorkspaceScanner } from "../workspace/scanner.js";
7
+ import { FileReader } from "../workspace/file-reader.js";
8
+
9
+ /**
10
+ * MultiProjectDetector
11
+ *
12
+ * Detecta múltiplos projetos dentro de um workspace
13
+ * Escaneia subpastas em busca de projetos independentes (pom.xml, package.json, etc)
14
+ */
15
+ export class MultiProjectDetector {
16
+ constructor(workspaceRoot) {
17
+ this.workspaceRoot = workspaceRoot;
18
+ this.maxDepth = 2; // Escanear até 2 níveis de profundidade
19
+ }
20
+
21
+ /**
22
+ * Detecta todos os projetos no workspace
23
+ */
24
+ async detectProjects() {
25
+ console.log(`🔍 Scanning workspace for multiple projects: ${this.workspaceRoot}`);
26
+
27
+ const projects = [];
28
+
29
+ // 1️⃣ Verificar se o próprio workspace root é um projeto
30
+ if (await this.isProject(this.workspaceRoot)) {
31
+ console.log(`✅ Root is a project`);
32
+ projects.push({
33
+ name: path.basename(this.workspaceRoot),
34
+ path: this.workspaceRoot,
35
+ isRoot: true
36
+ });
37
+ }
38
+
39
+ // 2️⃣ Escanear subpastas em busca de projetos
40
+ const subProjects = await this.scanSubdirectories(this.workspaceRoot, 0);
41
+ projects.push(...subProjects);
42
+
43
+ console.log(`✅ Found ${projects.length} project(s) in workspace`);
44
+
45
+ // 3️⃣ Detectar serviços para cada projeto
46
+ const allServices = [];
47
+ for (const project of projects) {
48
+ console.log(`🔎 Detecting services in: ${project.name}`);
49
+ const services = await this.detectServicesInProject(project);
50
+ allServices.push(...services);
51
+ }
52
+
53
+ console.log(`✅ Total services detected: ${allServices.length}`);
54
+ return allServices;
55
+ }
56
+
57
+ /**
58
+ * Verifica se um diretório é um projeto
59
+ */
60
+ async isProject(dir) {
61
+ const markers = [
62
+ 'pom.xml', // Maven
63
+ 'build.gradle', // Gradle
64
+ 'build.gradle.kts', // Gradle Kotlin
65
+ 'package.json', // Node.js
66
+ 'requirements.txt', // Python
67
+ 'pyproject.toml', // Python Poetry
68
+ 'go.mod', // Go
69
+ 'Cargo.toml', // Rust
70
+ '.csproj', // .NET (qualquer arquivo .csproj)
71
+ '.sln' // .NET solution
72
+ ];
73
+
74
+ for (const marker of markers) {
75
+ // Para .csproj e .sln, procurar qualquer arquivo com extensão
76
+ if (marker.startsWith('.')) {
77
+ try {
78
+ const files = await readdir(dir);
79
+ if (files.some(f => f.endsWith(marker))) {
80
+ return true;
81
+ }
82
+ } catch {
83
+ continue;
84
+ }
85
+ } else {
86
+ // Para markers específicos, verificar se existe
87
+ if (await exists(path.join(dir, marker))) {
88
+ return true;
89
+ }
90
+ }
91
+ }
92
+
93
+ return false;
94
+ }
95
+
96
+ /**
97
+ * Escaneia subdiretórios recursivamente
98
+ */
99
+ async scanSubdirectories(dir, depth) {
100
+ if (depth >= this.maxDepth) {
101
+ return [];
102
+ }
103
+
104
+ const projects = [];
105
+
106
+ try {
107
+ const entries = await readdir(dir, { withFileTypes: true });
108
+
109
+ for (const entry of entries) {
110
+ if (!entry.isDirectory()) continue;
111
+
112
+ // Ignorar pastas comuns que não são projetos
113
+ const ignoreDirs = [
114
+ 'node_modules', 'target', 'build', 'dist', 'out',
115
+ '.git', '.idea', '.vscode', '__pycache__',
116
+ '.next', 'coverage', '.gradle', 'vendor',
117
+ 'venv', 'env', '.env'
118
+ ];
119
+
120
+ if (ignoreDirs.includes(entry.name)) {
121
+ continue;
122
+ }
123
+
124
+ const fullPath = path.join(dir, entry.name);
125
+
126
+ // Verificar se é um projeto
127
+ if (await this.isProject(fullPath)) {
128
+ console.log(`✅ Found project: ${entry.name}`);
129
+ projects.push({
130
+ name: entry.name,
131
+ path: fullPath,
132
+ isRoot: false
133
+ });
134
+ // Não escanear dentro de projetos encontrados
135
+ continue;
136
+ }
137
+
138
+ // Continuar escaneando recursivamente
139
+ const subProjects = await this.scanSubdirectories(fullPath, depth + 1);
140
+ projects.push(...subProjects);
141
+ }
142
+ } catch (err) {
143
+ console.warn(`⚠️ Could not scan directory ${dir}: ${err.message}`);
144
+ }
145
+
146
+ return projects;
147
+ }
148
+
149
+ /**
150
+ * Detecta serviços em um projeto específico
151
+ */
152
+ async detectServicesInProject(project) {
153
+ try {
154
+ // Usar o scanner para analisar o projeto
155
+ const scanner = new WorkspaceScanner(project.path);
156
+ const structure = await scanner.scan();
157
+
158
+ // Detectar linguagem
159
+ const languageDetector = new LanguageDetector(structure.files);
160
+ const languageInfo = languageDetector.detect();
161
+
162
+ // Detectar framework
163
+ const fileReader = new FileReader(project.path);
164
+ const frameworkDetector = new FrameworkDetector(
165
+ languageInfo.primary,
166
+ structure.files,
167
+ fileReader
168
+ );
169
+ const frameworkInfo = await frameworkDetector.detect();
170
+
171
+ // Detectar serviços
172
+ const serviceDetector = new ServiceDetector(
173
+ project.path,
174
+ languageInfo,
175
+ frameworkInfo
176
+ );
177
+
178
+ const services = await serviceDetector.detect();
179
+
180
+ // Adicionar nome do workspace a cada serviço
181
+ return services.map(service => ({
182
+ ...service,
183
+ workspaceName: project.name,
184
+ workspacePath: this.workspaceRoot
185
+ }));
186
+ } catch (err) {
187
+ console.error(`❌ Error detecting services in ${project.name}: ${err.message}`);
188
+ return [];
189
+ }
190
+ }
191
+ }
@@ -0,0 +1,244 @@
1
+ import path from "path";
2
+ import { exists, readFile } from "../fs-utils.js";
3
+
4
+ /**
5
+ * ServiceDetector
6
+ *
7
+ * Detecta serviços/microservices em um workspace
8
+ * Identifica: nome, porta, tipo, comando de start
9
+ */
10
+ export class ServiceDetector {
11
+ constructor(root, languageInfo, frameworkInfo) {
12
+ this.root = root;
13
+ this.language = languageInfo.primary;
14
+ this.framework = frameworkInfo.framework;
15
+ this.buildTool = frameworkInfo.buildTool;
16
+ }
17
+
18
+ /**
19
+ * Detecta todos os serviços no workspace
20
+ */
21
+ async detect() {
22
+ const services = [];
23
+
24
+ // Detectar por tipo de projeto
25
+ if (this.language === "java" && this.framework === "spring-boot") {
26
+ const service = await this.detectSpringBootService();
27
+ if (service) services.push(service);
28
+ } else if (this.language === "node") {
29
+ const service = await this.detectNodeService();
30
+ if (service) services.push(service);
31
+ } else if (this.language === "python") {
32
+ const service = await this.detectPythonService();
33
+ if (service) services.push(service);
34
+ } else if (this.language === "dotnet") {
35
+ services.push(this.detectDotNetService());
36
+ } else if (this.language === "go") {
37
+ services.push(this.detectGoService());
38
+ }
39
+
40
+ return services;
41
+ }
42
+
43
+ /**
44
+ * Detecta serviço Spring Boot
45
+ */
46
+ async detectSpringBootService() {
47
+ const pomPath = path.join(this.root, "pom.xml");
48
+ const gradlePath = path.join(this.root, "build.gradle");
49
+
50
+ let serviceName = path.basename(this.root); // Fallback: nome da pasta
51
+ let port = 8080;
52
+
53
+ // Tentar extrair nome do pom.xml (ignorando o parent)
54
+ if (await exists(pomPath)) {
55
+ const content = await readFile(pomPath, "utf8");
56
+
57
+ // Remove o bloco <parent>...</parent> para não pegar o artifactId errado
58
+ const contentWithoutParent = content.replace(/<parent>[\s\S]*?<\/parent>/gi, '');
59
+ const artifactIdMatch = contentWithoutParent.match(/<artifactId>([^<]+)<\/artifactId>/);
60
+
61
+ if (artifactIdMatch) {
62
+ serviceName = artifactIdMatch[1].trim();
63
+ }
64
+ }
65
+
66
+ // Tentar extrair do build.gradle
67
+ if (await exists(gradlePath)) {
68
+ const content = await readFile(gradlePath, "utf8");
69
+ const nameMatch = content.match(/rootProject\.name\s*=\s*['"]([^'"]+)['"]/);
70
+ if (nameMatch) {
71
+ serviceName = nameMatch[1];
72
+ }
73
+ }
74
+
75
+ // Tentar detectar porta do application.yml/properties
76
+ port = await this.detectSpringBootPort();
77
+
78
+ return {
79
+ id: serviceName,
80
+ name: serviceName,
81
+ type: "spring-boot",
82
+ language: "java",
83
+ framework: "spring-boot",
84
+ buildTool: this.buildTool,
85
+ port,
86
+ status: "stopped",
87
+ path: this.root
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Detecta porta do Spring Boot
93
+ */
94
+ async detectSpringBootPort() {
95
+ const candidates = [
96
+ "src/main/resources/application.yml",
97
+ "src/main/resources/application.yaml",
98
+ "src/main/resources/application.properties"
99
+ ];
100
+
101
+ for (const rel of candidates) {
102
+ const fullPath = path.join(this.root, rel);
103
+ if (await exists(fullPath)) {
104
+ const content = await readFile(fullPath, "utf8");
105
+
106
+ // YAML - múltiplos padrões
107
+ if (rel.endsWith(".yml") || rel.endsWith(".yaml")) {
108
+ // Padrão 1: server:\n port: 8080
109
+ let match = content.match(/server:\s*\n\s*port:\s*(\d+)/);
110
+ if (match) return parseInt(match[1]);
111
+
112
+ // Padrão 2: server.port: 8080
113
+ match = content.match(/server\.port:\s*(\d+)/);
114
+ if (match) return parseInt(match[1]);
115
+
116
+ // Padrão 3: port: ${PORT:8080}
117
+ match = content.match(/port:\s*\$\{[^}]*:(\d+)\}/);
118
+ if (match) return parseInt(match[1]);
119
+
120
+ // Padrão 4: server:\n port: 8080 (com mais espaços)
121
+ match = content.match(/server:\s*[\r\n]+\s*port:\s*(\d+)/);
122
+ if (match) return parseInt(match[1]);
123
+ }
124
+
125
+ // Properties
126
+ if (rel.endsWith(".properties")) {
127
+ const match = content.match(/server\.port\s*=\s*(\d+)/);
128
+ if (match) return parseInt(match[1]);
129
+ }
130
+ }
131
+ }
132
+
133
+ return 8080; // Default
134
+ }
135
+
136
+ /**
137
+ * Detecta serviço Node.js
138
+ */
139
+ async detectNodeService() {
140
+ const pkgPath = path.join(this.root, "package.json");
141
+ if (!await exists(pkgPath)) return null;
142
+
143
+ const content = await readFile(pkgPath, "utf8");
144
+ const pkg = JSON.parse(content);
145
+
146
+ return {
147
+ id: pkg.name || path.basename(this.root),
148
+ name: pkg.name || path.basename(this.root),
149
+ type: this.framework,
150
+ language: "node",
151
+ framework: this.framework,
152
+ buildTool: "npm",
153
+ port: await this.detectNodePort(),
154
+ status: "stopped",
155
+ path: this.root
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Detecta porta do Node.js
161
+ */
162
+ async detectNodePort() {
163
+ // Tentar .env
164
+ const envPath = path.join(this.root, ".env");
165
+ if (await exists(envPath)) {
166
+ const content = await readFile(envPath, "utf8");
167
+ const match = content.match(/PORT\s*=\s*(\d+)/i);
168
+ if (match) return parseInt(match[1]);
169
+ }
170
+
171
+ // Defaults por framework
172
+ const defaults = {
173
+ "next": 3000,
174
+ "express": 3000,
175
+ "nestjs": 3000
176
+ };
177
+
178
+ return defaults[this.framework] || 3000;
179
+ }
180
+
181
+ /**
182
+ * Detecta serviço Python
183
+ */
184
+ async detectPythonService() {
185
+ return {
186
+ id: path.basename(this.root),
187
+ name: path.basename(this.root),
188
+ type: this.framework,
189
+ language: "python",
190
+ framework: this.framework,
191
+ buildTool: "pip",
192
+ port: await this.detectPythonPort(),
193
+ status: "stopped",
194
+ path: this.root
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Detecta porta do Python
200
+ */
201
+ async detectPythonPort() {
202
+ const defaults = {
203
+ "django": 8000,
204
+ "flask": 5000,
205
+ "fastapi": 8000
206
+ };
207
+
208
+ return defaults[this.framework] || 8000;
209
+ }
210
+
211
+ /**
212
+ * Detecta serviço .NET
213
+ */
214
+ detectDotNetService() {
215
+ return {
216
+ id: path.basename(this.root),
217
+ name: path.basename(this.root),
218
+ type: "dotnet",
219
+ language: "dotnet",
220
+ framework: "dotnet",
221
+ buildTool: "dotnet",
222
+ port: 5000,
223
+ status: "stopped",
224
+ path: this.root
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Detecta serviço Go
230
+ */
231
+ detectGoService() {
232
+ return {
233
+ id: path.basename(this.root),
234
+ name: path.basename(this.root),
235
+ type: "go",
236
+ language: "go",
237
+ framework: "go",
238
+ buildTool: "go",
239
+ port: 8080,
240
+ status: "stopped",
241
+ path: this.root
242
+ };
243
+ }
244
+ }
package/detectors.js ADDED
@@ -0,0 +1,30 @@
1
+ import path from "path";
2
+ import { exists, readFile } from "./fs-utils.js";
3
+
4
+ export async function detectProject(root) {
5
+ const markers = [
6
+ { file: "pom.xml", lang: "java", buildTool: "maven" },
7
+ { file: "build.gradle", lang: "java", buildTool: "gradle" },
8
+ { file: "package.json", lang: "node", buildTool: "npm" },
9
+ { file: "pyproject.toml", lang: "python", buildTool: "poetry" },
10
+ { file: "requirements.txt", lang: "python", buildTool: "pip" },
11
+ { file: "go.mod", lang: "go", buildTool: "go" }
12
+ ];
13
+
14
+ for (const m of markers) {
15
+ if (await exists(path.join(root, m.file))) {
16
+ return { language: m.lang, buildTool: m.buildTool, marker: m.file };
17
+ }
18
+ }
19
+
20
+ // .NET: procura primeiro nível
21
+ const dotnet = (await (await import("fs")).promises.readdir(root))
22
+ .find(f => f.endsWith(".csproj") || f.endsWith(".sln"));
23
+ if (dotnet) return { language: ".net", buildTool: "dotnet", marker: dotnet };
24
+
25
+ return { language: "unknown", buildTool: "unknown", marker: null };
26
+ }
27
+
28
+ export async function readText(root, relPath) {
29
+ return readFile(path.join(root, relPath), "utf8");
30
+ }
package/exec-utils.js ADDED
@@ -0,0 +1,215 @@
1
+ import { spawn } from "child_process";
2
+ import stripAnsi from "strip-ansi";
3
+
4
+ export function run(cmd, args, cwd, timeoutMs = 10 * 60 * 1000) {
5
+ return new Promise((resolve) => {
6
+ const start = Date.now();
7
+ const child = spawn(cmd, args, { cwd, shell: true });
8
+ let stdout = "";
9
+ let stderr = "";
10
+ const timer = setTimeout(() => {
11
+ try { child.kill("SIGKILL"); } catch {}
12
+ }, timeoutMs);
13
+
14
+ child.stdout.on("data", d => stdout += d.toString());
15
+ child.stderr.on("data", d => stderr += d.toString());
16
+
17
+ child.on("close", code => {
18
+ clearTimeout(timer);
19
+ resolve({
20
+ code,
21
+ stdout: stripAnsi(stdout),
22
+ stderr: stripAnsi(stderr),
23
+ duration: Date.now() - start
24
+ });
25
+ });
26
+
27
+ child.on("error", (err) => {
28
+ clearTimeout(timer);
29
+ resolve({
30
+ code: 1,
31
+ stdout: "",
32
+ stderr: err.message,
33
+ duration: Date.now() - start
34
+ });
35
+ });
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Compile and optionally test a project
41
+ *
42
+ * @param {Object} options
43
+ * @param {string} options.language - Programming language (java, node, python, go, .net)
44
+ * @param {string} options.buildTool - Build tool (maven, gradle, npm, yarn)
45
+ * @param {string} options.cwd - Working directory
46
+ * @param {boolean} options.skipTests - Skip running tests (default: false)
47
+ * @returns {Promise<{code: number, stdout: string, stderr: string, duration: number}>}
48
+ */
49
+ export async function compileAndTest({ language, buildTool, cwd, skipTests = false }) {
50
+ console.log(`🔨 [COMPILE] Language: ${language}, BuildTool: ${buildTool}, SkipTests: ${skipTests}`);
51
+
52
+ let result;
53
+ const start = Date.now();
54
+
55
+ try {
56
+ if (language === "java" && buildTool === "maven") {
57
+ // Maven: clean install (skip tests if requested)
58
+ const args = skipTests
59
+ ? ["clean", "install", "-DskipTests", "-q"]
60
+ : ["clean", "install", "-q"];
61
+
62
+ console.log(`🔨 [COMPILE] Running: mvn ${args.join(" ")}`);
63
+ result = await run("mvn", args, cwd);
64
+ }
65
+ else if (language === "java" && buildTool === "gradle") {
66
+ // Gradle: clean build
67
+ const args = skipTests
68
+ ? ["clean", "build", "-x", "test"]
69
+ : ["clean", "build"];
70
+
71
+ console.log(`🔨 [COMPILE] Running: ./gradlew ${args.join(" ")}`);
72
+ result = await run("./gradlew", args, cwd);
73
+ }
74
+ else if (language === "node" || buildTool === "npm") {
75
+ // Node/npm: install and optionally test
76
+ console.log(`🔨 [COMPILE] Running: npm install`);
77
+ const installResult = await run("npm", ["install", "--silent"], cwd);
78
+
79
+ if (installResult.code !== 0) {
80
+ return installResult;
81
+ }
82
+
83
+ if (!skipTests) {
84
+ console.log(`🔨 [COMPILE] Running: npm test`);
85
+ result = await run("npm", ["test", "--silent"], cwd);
86
+ } else {
87
+ result = installResult;
88
+ }
89
+ }
90
+ else if (language === "node" || buildTool === "yarn") {
91
+ // Yarn
92
+ console.log(`🔨 [COMPILE] Running: yarn install`);
93
+ const installResult = await run("yarn", ["install", "--silent"], cwd);
94
+
95
+ if (installResult.code !== 0) {
96
+ return installResult;
97
+ }
98
+
99
+ if (!skipTests) {
100
+ console.log(`🔨 [COMPILE] Running: yarn test`);
101
+ result = await run("yarn", ["test", "--silent"], cwd);
102
+ } else {
103
+ result = installResult;
104
+ }
105
+ }
106
+ else if (language === "python") {
107
+ // Python: pytest
108
+ if (!skipTests) {
109
+ console.log(`🔨 [COMPILE] Running: pytest`);
110
+ result = await run("pytest", [], cwd);
111
+ } else {
112
+ // Python doesn't have a compile step, just return success
113
+ result = { code: 0, stdout: "Python: No compilation needed", stderr: "", duration: 0 };
114
+ }
115
+ }
116
+ else if (language === "go") {
117
+ // Go: build and optionally test
118
+ console.log(`🔨 [COMPILE] Running: go build ./...`);
119
+ const buildResult = await run("go", ["build", "./..."], cwd);
120
+
121
+ if (buildResult.code !== 0) {
122
+ return buildResult;
123
+ }
124
+
125
+ if (!skipTests) {
126
+ console.log(`🔨 [COMPILE] Running: go test ./...`);
127
+ result = await run("go", ["test", "./..."], cwd);
128
+ } else {
129
+ result = buildResult;
130
+ }
131
+ }
132
+ else if (language === ".net" || language === "dotnet") {
133
+ // .NET: build and optionally test
134
+ console.log(`🔨 [COMPILE] Running: dotnet build`);
135
+ const buildResult = await run("dotnet", ["build"], cwd);
136
+
137
+ if (buildResult.code !== 0) {
138
+ return buildResult;
139
+ }
140
+
141
+ if (!skipTests) {
142
+ console.log(`🔨 [COMPILE] Running: dotnet test`);
143
+ result = await run("dotnet", ["test"], cwd);
144
+ } else {
145
+ result = buildResult;
146
+ }
147
+ }
148
+ else {
149
+ console.error(`❌ [COMPILE] Unsupported: ${language}/${buildTool}`);
150
+ return {
151
+ code: 1,
152
+ stdout: "",
153
+ stderr: `Unsupported language/build tool: ${language}/${buildTool}`,
154
+ duration: Date.now() - start
155
+ };
156
+ }
157
+
158
+ // Log result
159
+ if (result.code === 0) {
160
+ console.log(`✅ [COMPILE] Success in ${result.duration}ms`);
161
+ } else {
162
+ console.error(`❌ [COMPILE] Failed with code ${result.code}`);
163
+ if (result.stderr) {
164
+ console.error(`❌ [COMPILE] Error: ${result.stderr.substring(0, 500)}`);
165
+ }
166
+ }
167
+
168
+ return result;
169
+
170
+ } catch (err) {
171
+ console.error(`❌ [COMPILE] Exception: ${err.message}`);
172
+ return {
173
+ code: 1,
174
+ stdout: "",
175
+ stderr: err.message,
176
+ duration: Date.now() - start
177
+ };
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Legacy function for backward compatibility
183
+ * Returns { steps: [...] } format
184
+ */
185
+ export async function compileAndTestLegacy({ language, buildTool, cwd }) {
186
+ if (language === "java" && buildTool === "maven") {
187
+ const c1 = await run("mvn", "clean -q".split(" "), cwd);
188
+ const c2 = await run("mvn", "test -q".split(" "), cwd);
189
+ return { steps: [c1, c2] };
190
+ }
191
+ if (language === "java" && buildTool === "gradle") {
192
+ const c1 = await run("./gradlew", ["clean"], cwd);
193
+ const c2 = await run("./gradlew", ["test"], cwd);
194
+ return { steps: [c1, c2] };
195
+ }
196
+ if (language === "node") {
197
+ const c1 = await run("npm", ["install", "--silent"], cwd);
198
+ const c2 = await run("npm", ["test", "--silent"], cwd);
199
+ return { steps: [c1, c2] };
200
+ }
201
+ if (language === "python") {
202
+ const c1 = await run("pytest", [], cwd);
203
+ return { steps: [c1] };
204
+ }
205
+ if (language === "go") {
206
+ const c1 = await run("go", ["test", "./..."], cwd);
207
+ return { steps: [c1] };
208
+ }
209
+ if (language === ".net") {
210
+ const c1 = await run("dotnet", ["build"], cwd);
211
+ const c2 = await run("dotnet", ["test"], cwd);
212
+ return { steps: [c1, c2] };
213
+ }
214
+ return { steps: [{ code: 1, stdout: "", stderr: "Unsupported language/build tool" }] };
215
+ }