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,455 @@
1
+ import path from "path";
2
+ import { readFile } from "../fs-utils.js";
3
+
4
+ /**
5
+ * DTOAnalyzer
6
+ *
7
+ * Analyzes Java DTO/Model classes to extract:
8
+ * - Fields and types
9
+ * - Validation annotations
10
+ * - Example payloads
11
+ */
12
+ export class DTOAnalyzer {
13
+ constructor(workspaceRoot) {
14
+ this.workspaceRoot = workspaceRoot;
15
+ this.analyzedDtos = new Map();
16
+ }
17
+
18
+ /**
19
+ * Find all DTO/Model files in the workspace
20
+ */
21
+ async findDtoFiles(files) {
22
+ const dtoPatterns = [
23
+ /Dto\.java$/,
24
+ /DTO\.java$/,
25
+ /Request\.java$/,
26
+ /Response\.java$/,
27
+ /Model\.java$/,
28
+ /Entity\.java$/
29
+ ];
30
+
31
+ // Also look in model/dto/request/response directories
32
+ const dtoDirectories = ["/model/", "/dto/", "/request/", "/response/", "/entity/"];
33
+
34
+ return files.filter(file => {
35
+ const filePath = file.path || file;
36
+ const fileName = path.basename(filePath);
37
+
38
+ // Check by name pattern
39
+ for (const pattern of dtoPatterns) {
40
+ if (pattern.test(fileName)) return true;
41
+ }
42
+
43
+ // Check by directory
44
+ for (const dir of dtoDirectories) {
45
+ if (filePath.includes(dir)) return true;
46
+ }
47
+
48
+ return false;
49
+ }).map(f => f.path || f);
50
+ }
51
+
52
+ /**
53
+ * Analyze a single DTO file
54
+ */
55
+ async analyzeDto(filePath) {
56
+ // Check cache
57
+ if (this.analyzedDtos.has(filePath)) {
58
+ return this.analyzedDtos.get(filePath);
59
+ }
60
+
61
+ try {
62
+ const fullPath = path.join(this.workspaceRoot, filePath);
63
+ const content = await readFile(fullPath, "utf8");
64
+
65
+ const dto = {
66
+ file: filePath,
67
+ className: this.extractClassName(content),
68
+ packageName: this.extractPackageName(content),
69
+ fields: this.extractFields(content),
70
+ annotations: this.extractClassAnnotations(content),
71
+ isRecord: content.includes("public record"),
72
+ extends: this.extractExtends(content),
73
+ implements: this.extractImplements(content)
74
+ };
75
+
76
+ // Generate example payload
77
+ dto.examplePayload = this.generateExamplePayload(dto);
78
+
79
+ this.analyzedDtos.set(filePath, dto);
80
+ return dto;
81
+ } catch (err) {
82
+ console.error(`Failed to analyze DTO ${filePath}:`, err.message);
83
+ return null;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Extract package name
89
+ */
90
+ extractPackageName(content) {
91
+ const match = content.match(/package\s+([\w.]+);/);
92
+ return match ? match[1] : "";
93
+ }
94
+
95
+ /**
96
+ * Extract class name
97
+ */
98
+ extractClassName(content) {
99
+ // Handle records
100
+ const recordMatch = content.match(/public\s+record\s+(\w+)/);
101
+ if (recordMatch) return recordMatch[1];
102
+
103
+ const classMatch = content.match(/public\s+class\s+(\w+)/);
104
+ return classMatch ? classMatch[1] : "Unknown";
105
+ }
106
+
107
+ /**
108
+ * Extract class-level annotations
109
+ */
110
+ extractClassAnnotations(content) {
111
+ const annotations = [];
112
+ const beforeClass = content.split(/public\s+(?:class|record)/)[0];
113
+ const regex = /@(\w+)(?:\([^)]*\))?/g;
114
+ let match;
115
+ while ((match = regex.exec(beforeClass)) !== null) {
116
+ annotations.push(match[1]);
117
+ }
118
+ return annotations;
119
+ }
120
+
121
+ /**
122
+ * Extract all fields from class
123
+ */
124
+ extractFields(content) {
125
+ const fields = [];
126
+
127
+ // Check if it's a record
128
+ const recordMatch = content.match(/public\s+record\s+\w+\s*\(([^)]+)\)/);
129
+ if (recordMatch) {
130
+ return this.extractRecordFields(recordMatch[1]);
131
+ }
132
+
133
+ // Regular class fields
134
+ const fieldRegex = /(?:@[\w.]+(?:\([^)]*\))?\s+)*(?:private|protected|public)\s+([\w<>,\s?]+)\s+(\w+)\s*(?:=|;)/g;
135
+ let match;
136
+ while ((match = fieldRegex.exec(content)) !== null) {
137
+ const fieldBlock = content.substring(match.index - 200, match.index + match[0].length);
138
+ const annotations = this.extractFieldAnnotations(fieldBlock);
139
+
140
+ fields.push({
141
+ type: match[1].trim(),
142
+ name: match[2],
143
+ annotations: annotations,
144
+ required: this.isFieldRequired(annotations),
145
+ validation: this.extractValidationRules(annotations)
146
+ });
147
+ }
148
+
149
+ return fields;
150
+ }
151
+
152
+ /**
153
+ * Extract fields from record definition
154
+ */
155
+ extractRecordFields(recordParams) {
156
+ const fields = [];
157
+ const params = this.splitParameters(recordParams);
158
+
159
+ for (const param of params) {
160
+ const parts = param.trim().split(/\s+/);
161
+ if (parts.length >= 2) {
162
+ const type = parts[0];
163
+ const name = parts[parts.length - 1];
164
+ fields.push({
165
+ type: type,
166
+ name: name,
167
+ annotations: [],
168
+ required: true,
169
+ validation: {}
170
+ });
171
+ }
172
+ }
173
+
174
+ return fields;
175
+ }
176
+
177
+ /**
178
+ * Split parameters handling generics
179
+ */
180
+ splitParameters(paramsString) {
181
+ const params = [];
182
+ let current = "";
183
+ let depth = 0;
184
+
185
+ for (const char of paramsString) {
186
+ if (char === "<") depth++;
187
+ else if (char === ">") depth--;
188
+ else if (char === "," && depth === 0) {
189
+ params.push(current);
190
+ current = "";
191
+ continue;
192
+ }
193
+ current += char;
194
+ }
195
+ if (current.trim()) params.push(current);
196
+ return params;
197
+ }
198
+
199
+ /**
200
+ * Extract annotations for a field
201
+ */
202
+ extractFieldAnnotations(fieldBlock) {
203
+ const annotations = [];
204
+ const regex = /@(\w+)(?:\(([^)]*)\))?/g;
205
+ let match;
206
+ while ((match = regex.exec(fieldBlock)) !== null) {
207
+ annotations.push({
208
+ name: match[1],
209
+ value: match[2] || null
210
+ });
211
+ }
212
+ return annotations;
213
+ }
214
+
215
+ /**
216
+ * Check if field is required based on annotations
217
+ */
218
+ isFieldRequired(annotations) {
219
+ const requiredAnnotations = ["NotNull", "NotBlank", "NotEmpty", "NonNull"];
220
+ return annotations.some(a => requiredAnnotations.includes(a.name));
221
+ }
222
+
223
+ /**
224
+ * Extract validation rules from annotations
225
+ */
226
+ extractValidationRules(annotations) {
227
+ const rules = {};
228
+
229
+ for (const annotation of annotations) {
230
+ switch (annotation.name) {
231
+ case "Size":
232
+ const sizeMatch = annotation.value?.match(/min\s*=\s*(\d+).*max\s*=\s*(\d+)/);
233
+ if (sizeMatch) {
234
+ rules.minLength = parseInt(sizeMatch[1]);
235
+ rules.maxLength = parseInt(sizeMatch[2]);
236
+ }
237
+ break;
238
+ case "Min":
239
+ rules.min = parseInt(annotation.value);
240
+ break;
241
+ case "Max":
242
+ rules.max = parseInt(annotation.value);
243
+ break;
244
+ case "Email":
245
+ rules.format = "email";
246
+ break;
247
+ case "Pattern":
248
+ const patternMatch = annotation.value?.match(/regexp\s*=\s*"([^"]+)"/);
249
+ if (patternMatch) rules.pattern = patternMatch[1];
250
+ break;
251
+ }
252
+ }
253
+
254
+ return rules;
255
+ }
256
+
257
+ /**
258
+ * Extract extends clause
259
+ */
260
+ extractExtends(content) {
261
+ const match = content.match(/class\s+\w+\s+extends\s+([\w<>]+)/);
262
+ return match ? match[1] : null;
263
+ }
264
+
265
+ /**
266
+ * Extract implements clause
267
+ */
268
+ extractImplements(content) {
269
+ const match = content.match(/class\s+\w+(?:\s+extends\s+[\w<>]+)?\s+implements\s+([\w<>,\s]+)/);
270
+ if (match) {
271
+ return match[1].split(",").map(s => s.trim());
272
+ }
273
+ return [];
274
+ }
275
+
276
+ /**
277
+ * Generate example payload JSON
278
+ */
279
+ generateExamplePayload(dto) {
280
+ const payload = {};
281
+
282
+ for (const field of dto.fields) {
283
+ payload[field.name] = this.generateExampleValue(field);
284
+ }
285
+
286
+ return payload;
287
+ }
288
+
289
+ /**
290
+ * Generate example value for a field based on type
291
+ */
292
+ generateExampleValue(field) {
293
+ const type = field.type.replace(/<.*>/, ""); // Remove generics
294
+
295
+ // Check validation rules first
296
+ if (field.validation?.format === "email") {
297
+ return "user@example.com";
298
+ }
299
+
300
+ switch (type) {
301
+ case "String":
302
+ if (field.name.toLowerCase().includes("email")) return "user@example.com";
303
+ if (field.name.toLowerCase().includes("name")) return "John Doe";
304
+ if (field.name.toLowerCase().includes("phone")) return "+1234567890";
305
+ if (field.name.toLowerCase().includes("password")) return "********";
306
+ if (field.name.toLowerCase().includes("id")) return "abc123";
307
+ if (field.name.toLowerCase().includes("url")) return "https://example.com";
308
+ return "example_" + field.name;
309
+
310
+ case "Integer":
311
+ case "int":
312
+ case "Long":
313
+ case "long":
314
+ if (field.name.toLowerCase().includes("id")) return 1;
315
+ if (field.name.toLowerCase().includes("age")) return 25;
316
+ if (field.name.toLowerCase().includes("count")) return 10;
317
+ return field.validation?.min || 1;
318
+
319
+ case "Double":
320
+ case "double":
321
+ case "Float":
322
+ case "float":
323
+ case "BigDecimal":
324
+ if (field.name.toLowerCase().includes("price")) return 99.99;
325
+ if (field.name.toLowerCase().includes("amount")) return 100.00;
326
+ return 0.0;
327
+
328
+ case "Boolean":
329
+ case "boolean":
330
+ return true;
331
+
332
+ case "List":
333
+ case "Set":
334
+ case "Collection":
335
+ return [];
336
+
337
+ case "Map":
338
+ return {};
339
+
340
+ case "Date":
341
+ case "LocalDate":
342
+ return "2024-01-15";
343
+
344
+ case "LocalDateTime":
345
+ case "Instant":
346
+ case "ZonedDateTime":
347
+ return "2024-01-15T10:30:00Z";
348
+
349
+ case "UUID":
350
+ return "550e8400-e29b-41d4-a716-446655440000";
351
+
352
+ default:
353
+ // Complex type - return empty object
354
+ return {};
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Find DTO by class name
360
+ */
361
+ async findDtoByClassName(className, files) {
362
+ // Clean class name (remove generics)
363
+ const cleanName = className.replace(/<.*>/, "").replace("ResponseEntity", "").trim();
364
+
365
+ if (!cleanName || cleanName === "void" || cleanName === "Object") {
366
+ return null;
367
+ }
368
+
369
+ // Look for file with this class name
370
+ for (const file of files) {
371
+ const filePath = file.path || file;
372
+ const fileName = path.basename(filePath, ".java");
373
+
374
+ if (fileName === cleanName) {
375
+ return this.analyzeDto(filePath);
376
+ }
377
+ }
378
+
379
+ return null;
380
+ }
381
+
382
+ /**
383
+ * Analyze all DTOs in workspace
384
+ */
385
+ async analyzeAll(files) {
386
+ const dtoPaths = await this.findDtoFiles(files);
387
+ const dtos = [];
388
+
389
+ for (const dtoPath of dtoPaths) {
390
+ const dto = await this.analyzeDto(dtoPath);
391
+ if (dto) {
392
+ dtos.push(dto);
393
+ }
394
+ }
395
+
396
+ return dtos;
397
+ }
398
+
399
+ /**
400
+ * Generate complete API payload documentation
401
+ */
402
+ async generatePayloadDocs(files, endpoints) {
403
+ const dtos = await this.analyzeAll(files);
404
+ const dtoMap = new Map(dtos.map(d => [d.className, d]));
405
+
406
+ // Enrich endpoints with payload info
407
+ const enrichedEndpoints = [];
408
+
409
+ for (const endpoint of endpoints) {
410
+ const enriched = { ...endpoint };
411
+
412
+ // Find request body DTO
413
+ if (endpoint.requestBody) {
414
+ const requestDto = dtoMap.get(endpoint.requestBody.type);
415
+ if (requestDto) {
416
+ enriched.requestPayload = {
417
+ dto: requestDto.className,
418
+ fields: requestDto.fields,
419
+ example: requestDto.examplePayload
420
+ };
421
+ }
422
+ }
423
+
424
+ // Find response DTO
425
+ if (endpoint.returnType) {
426
+ const returnTypeName = endpoint.returnType
427
+ .replace(/ResponseEntity</, "")
428
+ .replace(/Mono</, "")
429
+ .replace(/Flux</, "")
430
+ .replace(/<.*>/, "")
431
+ .replace(/>/, "")
432
+ .trim();
433
+
434
+ const responseDto = dtoMap.get(returnTypeName);
435
+ if (responseDto) {
436
+ enriched.responsePayload = {
437
+ dto: responseDto.className,
438
+ fields: responseDto.fields,
439
+ example: responseDto.examplePayload
440
+ };
441
+ }
442
+ }
443
+
444
+ enrichedEndpoints.push(enriched);
445
+ }
446
+
447
+ return {
448
+ totalDtos: dtos.length,
449
+ dtos: dtos,
450
+ endpoints: enrichedEndpoints
451
+ };
452
+ }
453
+ }
454
+
455
+ export default DTOAnalyzer;
File without changes
@@ -0,0 +1,91 @@
1
+ export class FrameworkDetector {
2
+ constructor(language, files, fileReader) {
3
+ this.language = language;
4
+ this.files = files;
5
+ this.fileReader = fileReader;
6
+ }
7
+
8
+ async detect() {
9
+ switch (this.language) {
10
+ case 'java':
11
+ return this.detectJavaFramework();
12
+ case 'node':
13
+ return this.detectNodeFramework();
14
+ case 'python':
15
+ return this.detectPythonFramework();
16
+ case 'dotnet':
17
+ return { framework: 'dotnet', version: null, buildTool: 'dotnet' };
18
+ case 'go':
19
+ return { framework: 'go', version: null, buildTool: 'go' };
20
+ default:
21
+ return { framework: 'unknown', version: null, buildTool: null };
22
+ }
23
+ }
24
+
25
+ async detectJavaFramework() {
26
+ const pomFile = this.files.find(f => f.name === 'pom.xml');
27
+ if (pomFile) {
28
+ const content = await this.fileReader.read(pomFile.path);
29
+ if (content.content.includes('spring-boot')) {
30
+ const version = this.extractVersion(content.content, 'spring-boot-starter-parent');
31
+ return { framework: 'spring-boot', version, buildTool: 'maven' };
32
+ }
33
+ if (content.content.includes('quarkus')) {
34
+ return { framework: 'quarkus', version: null, buildTool: 'maven' };
35
+ }
36
+ return { framework: 'java-maven', version: null, buildTool: 'maven' };
37
+ }
38
+
39
+ const gradleFile = this.files.find(f => f.name === 'build.gradle' || f.name === 'build.gradle.kts');
40
+ if (gradleFile) {
41
+ const content = await this.fileReader.read(gradleFile.path);
42
+ if (content.content.includes('spring-boot') || content.content.includes('org.springframework.boot')) {
43
+ return { framework: 'spring-boot', version: null, buildTool: 'gradle' };
44
+ }
45
+ return { framework: 'java-gradle', version: null, buildTool: 'gradle' };
46
+ }
47
+
48
+ return { framework: 'java-plain', version: null, buildTool: null };
49
+ }
50
+
51
+ async detectNodeFramework() {
52
+ const pkgFile = this.files.find(f => f.name === 'package.json');
53
+ if (!pkgFile) return { framework: 'unknown', version: null, buildTool: 'npm' };
54
+
55
+ const content = await this.fileReader.read(pkgFile.path);
56
+ const pkg = JSON.parse(content.content);
57
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
58
+
59
+ if (deps['next']) return { framework: 'next', version: deps['next'], buildTool: 'npm' };
60
+ if (deps['express']) return { framework: 'express', version: deps['express'], buildTool: 'npm' };
61
+ if (deps['react']) return { framework: 'react', version: deps['react'], buildTool: 'npm' };
62
+ if (deps['vue']) return { framework: 'vue', version: deps['vue'], buildTool: 'npm' };
63
+ if (deps['@nestjs/core']) return { framework: 'nestjs', version: deps['@nestjs/core'], buildTool: 'npm' };
64
+ if (deps['@angular/core']) return { framework: 'angular', version: deps['@angular/core'], buildTool: 'npm' };
65
+
66
+ return { framework: 'node-plain', version: null, buildTool: 'npm' };
67
+ }
68
+
69
+ async detectPythonFramework() {
70
+ const reqFile = this.files.find(f => f.name === 'requirements.txt');
71
+ if (reqFile) {
72
+ const content = await this.fileReader.read(reqFile.path);
73
+ if (content.content.includes('django')) return { framework: 'django', version: null, buildTool: 'pip' };
74
+ if (content.content.includes('flask')) return { framework: 'flask', version: null, buildTool: 'pip' };
75
+ if (content.content.includes('fastapi')) return { framework: 'fastapi', version: null, buildTool: 'pip' };
76
+ }
77
+
78
+ const pyprojectFile = this.files.find(f => f.name === 'pyproject.toml');
79
+ if (pyprojectFile) {
80
+ return { framework: 'python-poetry', version: null, buildTool: 'poetry' };
81
+ }
82
+
83
+ return { framework: 'python-plain', version: null, buildTool: 'pip' };
84
+ }
85
+
86
+ extractVersion(xml, dependency) {
87
+ const regex = new RegExp(`<${dependency}>.*?<version>(.*?)</version>`, 's');
88
+ const match = xml.match(regex);
89
+ return match ? match[1] : null;
90
+ }
91
+ }
@@ -0,0 +1,89 @@
1
+ export class LanguageDetector {
2
+ constructor(files) {
3
+ this.files = files;
4
+ }
5
+
6
+ detect() {
7
+ const markers = this.findMarkers();
8
+ const extensions = this.analyzeExtensions();
9
+
10
+ return {
11
+ primary: this.determinePrimaryLanguage(markers, extensions),
12
+ secondary: this.findSecondaryLanguages(extensions),
13
+ confidence: this.calculateConfidence(markers)
14
+ };
15
+ }
16
+
17
+ findMarkers() {
18
+ const markers = {
19
+ java: this.files.some(f => f.name === 'pom.xml' || f.name === 'build.gradle'),
20
+ node: this.files.some(f => f.name === 'package.json'),
21
+ python: this.files.some(f => f.name === 'requirements.txt' || f.name === 'pyproject.toml'),
22
+ dotnet: this.files.some(f => f.name.endsWith('.csproj') || f.name.endsWith('.sln')),
23
+ go: this.files.some(f => f.name === 'go.mod'),
24
+ php: this.files.some(f => f.name === 'composer.json'),
25
+ ruby: this.files.some(f => f.name === 'Gemfile')
26
+ };
27
+ return markers;
28
+ }
29
+
30
+ analyzeExtensions() {
31
+ const extCount = {};
32
+ this.files.forEach(f => {
33
+ const ext = f.extension?.toLowerCase();
34
+ if (ext) extCount[ext] = (extCount[ext] || 0) + 1;
35
+ });
36
+ return extCount;
37
+ }
38
+
39
+ determinePrimaryLanguage(markers, extensions) {
40
+ // Prioridade: markers > extensões
41
+ const detected = Object.entries(markers)
42
+ .filter(([_, exists]) => exists)
43
+ .map(([lang]) => lang);
44
+
45
+ if (detected.length === 1) return detected[0];
46
+ if (detected.length > 1) {
47
+ // Múltiplas linguagens, escolhe por extensões
48
+ const sortedExts = Object.entries(extensions)
49
+ .sort((a, b) => b[1] - a[1]);
50
+ return this.mapExtToLang(sortedExts[0]?.[0]);
51
+ }
52
+
53
+ // Fallback: extensão mais comum
54
+ const sortedExts = Object.entries(extensions)
55
+ .sort((a, b) => b[1] - a[1]);
56
+ return this.mapExtToLang(sortedExts[0]?.[0]) || 'unknown';
57
+ }
58
+
59
+ mapExtToLang(ext) {
60
+ const map = {
61
+ '.java': 'java',
62
+ '.js': 'node',
63
+ '.ts': 'node',
64
+ '.jsx': 'node',
65
+ '.tsx': 'node',
66
+ '.py': 'python',
67
+ '.cs': 'dotnet',
68
+ '.go': 'go',
69
+ '.php': 'php',
70
+ '.rb': 'ruby'
71
+ };
72
+ return map[ext] || null;
73
+ }
74
+
75
+ findSecondaryLanguages(extensions) {
76
+ const primary = this.determinePrimaryLanguage(this.findMarkers(), extensions);
77
+ return Object.keys(extensions)
78
+ .map(ext => this.mapExtToLang(ext))
79
+ .filter(lang => lang && lang !== primary)
80
+ .filter((v, i, a) => a.indexOf(v) === i); // unique
81
+ }
82
+
83
+ calculateConfidence(markers) {
84
+ const markerCount = Object.values(markers).filter(Boolean).length;
85
+ if (markerCount === 0) return 0.3;
86
+ if (markerCount === 1) return 0.9;
87
+ return 0.7; // Múltiplos markers = projeto polyglot
88
+ }
89
+ }