activo 0.2.2 → 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 (79) hide show
  1. package/README.md +87 -3
  2. package/dist/core/llm/ollama.d.ts +2 -0
  3. package/dist/core/llm/ollama.d.ts.map +1 -1
  4. package/dist/core/llm/ollama.js +26 -0
  5. package/dist/core/llm/ollama.js.map +1 -1
  6. package/dist/core/tools/ast.d.ts +81 -0
  7. package/dist/core/tools/ast.d.ts.map +1 -0
  8. package/dist/core/tools/ast.js +700 -0
  9. package/dist/core/tools/ast.js.map +1 -0
  10. package/dist/core/tools/cache.d.ts +19 -0
  11. package/dist/core/tools/cache.d.ts.map +1 -0
  12. package/dist/core/tools/cache.js +497 -0
  13. package/dist/core/tools/cache.js.map +1 -0
  14. package/dist/core/tools/cssAnalysis.d.ts +3 -0
  15. package/dist/core/tools/cssAnalysis.d.ts.map +1 -0
  16. package/dist/core/tools/cssAnalysis.js +270 -0
  17. package/dist/core/tools/cssAnalysis.js.map +1 -0
  18. package/dist/core/tools/dependencyAnalysis.d.ts +3 -0
  19. package/dist/core/tools/dependencyAnalysis.d.ts.map +1 -0
  20. package/dist/core/tools/dependencyAnalysis.js +295 -0
  21. package/dist/core/tools/dependencyAnalysis.js.map +1 -0
  22. package/dist/core/tools/embeddings.d.ts +8 -0
  23. package/dist/core/tools/embeddings.d.ts.map +1 -0
  24. package/dist/core/tools/embeddings.js +631 -0
  25. package/dist/core/tools/embeddings.js.map +1 -0
  26. package/dist/core/tools/frontendAst.d.ts +6 -0
  27. package/dist/core/tools/frontendAst.d.ts.map +1 -0
  28. package/dist/core/tools/frontendAst.js +680 -0
  29. package/dist/core/tools/frontendAst.js.map +1 -0
  30. package/dist/core/tools/htmlAnalysis.d.ts +3 -0
  31. package/dist/core/tools/htmlAnalysis.d.ts.map +1 -0
  32. package/dist/core/tools/htmlAnalysis.js +398 -0
  33. package/dist/core/tools/htmlAnalysis.js.map +1 -0
  34. package/dist/core/tools/index.d.ts +13 -0
  35. package/dist/core/tools/index.d.ts.map +1 -1
  36. package/dist/core/tools/index.js +27 -1
  37. package/dist/core/tools/index.js.map +1 -1
  38. package/dist/core/tools/javaAst.d.ts +6 -0
  39. package/dist/core/tools/javaAst.d.ts.map +1 -0
  40. package/dist/core/tools/javaAst.js +678 -0
  41. package/dist/core/tools/javaAst.js.map +1 -0
  42. package/dist/core/tools/memory.d.ts +11 -0
  43. package/dist/core/tools/memory.d.ts.map +1 -0
  44. package/dist/core/tools/memory.js +551 -0
  45. package/dist/core/tools/memory.js.map +1 -0
  46. package/dist/core/tools/mybatisAnalysis.d.ts +3 -0
  47. package/dist/core/tools/mybatisAnalysis.d.ts.map +1 -0
  48. package/dist/core/tools/mybatisAnalysis.js +251 -0
  49. package/dist/core/tools/mybatisAnalysis.js.map +1 -0
  50. package/dist/core/tools/openapiAnalysis.d.ts +3 -0
  51. package/dist/core/tools/openapiAnalysis.d.ts.map +1 -0
  52. package/dist/core/tools/openapiAnalysis.js +356 -0
  53. package/dist/core/tools/openapiAnalysis.js.map +1 -0
  54. package/dist/core/tools/pythonAnalysis.d.ts +3 -0
  55. package/dist/core/tools/pythonAnalysis.d.ts.map +1 -0
  56. package/dist/core/tools/pythonAnalysis.js +387 -0
  57. package/dist/core/tools/pythonAnalysis.js.map +1 -0
  58. package/dist/core/tools/sqlAnalysis.d.ts +3 -0
  59. package/dist/core/tools/sqlAnalysis.d.ts.map +1 -0
  60. package/dist/core/tools/sqlAnalysis.js +250 -0
  61. package/dist/core/tools/sqlAnalysis.js.map +1 -0
  62. package/package.json +2 -1
  63. package/src/core/llm/ollama.ts +30 -0
  64. package/src/core/tools/ast.ts +826 -0
  65. package/src/core/tools/cache.ts +570 -0
  66. package/src/core/tools/cssAnalysis.ts +324 -0
  67. package/src/core/tools/dependencyAnalysis.ts +363 -0
  68. package/src/core/tools/embeddings.ts +746 -0
  69. package/src/core/tools/frontendAst.ts +802 -0
  70. package/src/core/tools/htmlAnalysis.ts +466 -0
  71. package/src/core/tools/index.ts +27 -1
  72. package/src/core/tools/javaAst.ts +812 -0
  73. package/src/core/tools/memory.ts +655 -0
  74. package/src/core/tools/mybatisAnalysis.ts +322 -0
  75. package/src/core/tools/openapiAnalysis.ts +431 -0
  76. package/src/core/tools/pythonAnalysis.ts +477 -0
  77. package/src/core/tools/sqlAnalysis.ts +298 -0
  78. package/FINAL_SIMPLIFIED_SPEC.md +0 -456
  79. package/TODO.md +0 -193
@@ -0,0 +1,812 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { parse, createVisitor } from "java-ast";
4
+ import { Tool, ToolResult } from "./types.js";
5
+
6
+ // Java method info
7
+ interface JavaMethodInfo {
8
+ name: string;
9
+ returnType: string;
10
+ params: Array<{ name: string; type: string }>;
11
+ modifiers: string[];
12
+ line: number;
13
+ annotations: string[];
14
+ throws: string[];
15
+ complexity: number;
16
+ }
17
+
18
+ // Java field info
19
+ interface JavaFieldInfo {
20
+ name: string;
21
+ type: string;
22
+ modifiers: string[];
23
+ line: number;
24
+ annotations: string[];
25
+ }
26
+
27
+ // Java class info
28
+ interface JavaClassInfo {
29
+ name: string;
30
+ type: "class" | "interface" | "enum" | "record";
31
+ modifiers: string[];
32
+ line: number;
33
+ extends?: string;
34
+ implements: string[];
35
+ annotations: string[];
36
+ methods: JavaMethodInfo[];
37
+ fields: JavaFieldInfo[];
38
+ innerClasses: JavaClassInfo[];
39
+ }
40
+
41
+ // Java file analysis result
42
+ interface JavaFileAnalysis {
43
+ filepath: string;
44
+ package: string;
45
+ imports: string[];
46
+ classes: JavaClassInfo[];
47
+ complexity: {
48
+ total: number;
49
+ average: number;
50
+ highest: { name: string; value: number };
51
+ };
52
+ }
53
+
54
+ // Calculate cyclomatic complexity from method body text
55
+ function calculateComplexity(methodText: string): number {
56
+ let complexity = 1;
57
+
58
+ // Count decision points
59
+ const patterns = [
60
+ /\bif\s*\(/g,
61
+ /\belse\s+if\s*\(/g,
62
+ /\bfor\s*\(/g,
63
+ /\bwhile\s*\(/g,
64
+ /\bcase\s+/g,
65
+ /\bcatch\s*\(/g,
66
+ /\b\?\s*[^:]/g, // ternary operator
67
+ /\&\&/g,
68
+ /\|\|/g,
69
+ ];
70
+
71
+ for (const pattern of patterns) {
72
+ const matches = methodText.match(pattern);
73
+ if (matches) {
74
+ complexity += matches.length;
75
+ }
76
+ }
77
+
78
+ return complexity;
79
+ }
80
+
81
+ // Analyze Java file
82
+ function analyzeJavaFile(content: string, filepath: string): JavaFileAnalysis {
83
+ const analysis: JavaFileAnalysis = {
84
+ filepath,
85
+ package: "",
86
+ imports: [],
87
+ classes: [],
88
+ complexity: { total: 0, average: 0, highest: { name: "", value: 0 } },
89
+ };
90
+
91
+ try {
92
+ const tree = parse(content);
93
+ const lines = content.split("\n");
94
+
95
+ // Get line number from context
96
+ const getLine = (ctx: any): number => {
97
+ if (ctx && ctx.start) {
98
+ return ctx.start.line;
99
+ }
100
+ return 0;
101
+ };
102
+
103
+ // Get text from context
104
+ const getText = (ctx: any): string => {
105
+ if (ctx && ctx.start && ctx.stop) {
106
+ const startIdx = ctx.start.startIndex;
107
+ const stopIdx = ctx.stop.stopIndex;
108
+ return content.substring(startIdx, stopIdx + 1);
109
+ }
110
+ return ctx?.text || "";
111
+ };
112
+
113
+ // Get modifiers
114
+ const getModifiers = (modifierCtxs: any[]): string[] => {
115
+ if (!modifierCtxs) return [];
116
+ return modifierCtxs.map((m) => m.text).filter((m) => m);
117
+ };
118
+
119
+ // Get annotations
120
+ const getAnnotations = (modifierCtxs: any[]): string[] => {
121
+ if (!modifierCtxs) return [];
122
+ return modifierCtxs
123
+ .filter((m) => m.annotation)
124
+ .map((m) => m.annotation()?.qualifiedName()?.text || m.text)
125
+ .filter((a) => a);
126
+ };
127
+
128
+ // Parse using visitor pattern
129
+ const visitor = createVisitor({
130
+ defaultResult: () => null,
131
+ aggregateResult: (a, b) => b || a,
132
+
133
+ visitPackageDeclaration: (ctx) => {
134
+ analysis.package = ctx.qualifiedName()?.text || "";
135
+ return null;
136
+ },
137
+
138
+ visitImportDeclaration: (ctx) => {
139
+ const importText = ctx.qualifiedName()?.text || "";
140
+ if (importText) {
141
+ const isStatic = ctx.STATIC() ? "static " : "";
142
+ const isWildcard = ctx.MUL() ? ".*" : "";
143
+ analysis.imports.push(isStatic + importText + isWildcard);
144
+ }
145
+ return null;
146
+ },
147
+
148
+ visitClassDeclaration: (ctx) => {
149
+ const classInfo: JavaClassInfo = {
150
+ name: ctx.identifier()?.text || "Unknown",
151
+ type: "class",
152
+ modifiers: [],
153
+ line: getLine(ctx),
154
+ implements: [],
155
+ annotations: [],
156
+ methods: [],
157
+ fields: [],
158
+ innerClasses: [],
159
+ };
160
+
161
+ // Get parent modifiers
162
+ const parent = ctx.parent;
163
+ if (parent && (parent as any).classOrInterfaceModifier) {
164
+ const mods = (parent as any).classOrInterfaceModifier();
165
+ classInfo.modifiers = getModifiers(mods);
166
+ classInfo.annotations = getAnnotations(mods);
167
+ }
168
+
169
+ // Extends
170
+ const extendsType = ctx.typeType();
171
+ if (extendsType) {
172
+ classInfo.extends = extendsType.text;
173
+ }
174
+
175
+ // Implements
176
+ const typeList = ctx.typeList();
177
+ if (typeList && typeList.length > 0) {
178
+ for (const tl of typeList) {
179
+ const types = tl.typeType();
180
+ if (types) {
181
+ classInfo.implements.push(...types.map((t) => t.text));
182
+ }
183
+ }
184
+ }
185
+
186
+ // Parse class body
187
+ const classBody = ctx.classBody();
188
+ if (classBody) {
189
+ const bodyDecls = classBody.classBodyDeclaration();
190
+ for (const bodyDecl of bodyDecls) {
191
+ const memberDecl = bodyDecl.memberDeclaration?.();
192
+ if (!memberDecl) continue;
193
+
194
+ // Method
195
+ const methodDecl = memberDecl.methodDeclaration?.();
196
+ if (methodDecl) {
197
+ const methodInfo: JavaMethodInfo = {
198
+ name: methodDecl.identifier()?.text || "unknown",
199
+ returnType: methodDecl.typeTypeOrVoid()?.text || "void",
200
+ params: [],
201
+ modifiers: [],
202
+ line: getLine(methodDecl),
203
+ annotations: [],
204
+ throws: [],
205
+ complexity: 1,
206
+ };
207
+
208
+ // Get modifiers from parent
209
+ const modCtxs = bodyDecl.modifier?.();
210
+ if (modCtxs) {
211
+ methodInfo.modifiers = getModifiers(modCtxs);
212
+ methodInfo.annotations = getAnnotations(modCtxs);
213
+ }
214
+
215
+ // Parameters
216
+ const formalParams = methodDecl.formalParameters()?.formalParameterList?.();
217
+ if (formalParams) {
218
+ const params = formalParams.formalParameter?.();
219
+ if (params) {
220
+ for (const param of params) {
221
+ methodInfo.params.push({
222
+ name: param.variableDeclaratorId()?.text || "",
223
+ type: param.typeType()?.text || "",
224
+ });
225
+ }
226
+ }
227
+ }
228
+
229
+ // Throws
230
+ const throwsClause = methodDecl.THROWS?.();
231
+ if (throwsClause) {
232
+ const qualNames = methodDecl.qualifiedNameList()?.qualifiedName?.();
233
+ if (qualNames) {
234
+ methodInfo.throws = qualNames.map((q) => q.text);
235
+ }
236
+ }
237
+
238
+ // Complexity
239
+ const methodBody = methodDecl.methodBody();
240
+ if (methodBody) {
241
+ methodInfo.complexity = calculateComplexity(getText(methodBody));
242
+ }
243
+
244
+ classInfo.methods.push(methodInfo);
245
+ }
246
+
247
+ // Constructor
248
+ const ctorDecl = memberDecl.constructorDeclaration?.();
249
+ if (ctorDecl) {
250
+ const ctorInfo: JavaMethodInfo = {
251
+ name: ctorDecl.identifier()?.text || classInfo.name,
252
+ returnType: classInfo.name,
253
+ params: [],
254
+ modifiers: [],
255
+ line: getLine(ctorDecl),
256
+ annotations: [],
257
+ throws: [],
258
+ complexity: 1,
259
+ };
260
+
261
+ const modCtxs = bodyDecl.modifier?.();
262
+ if (modCtxs) {
263
+ ctorInfo.modifiers = getModifiers(modCtxs);
264
+ ctorInfo.annotations = getAnnotations(modCtxs);
265
+ }
266
+
267
+ const formalParams = ctorDecl.formalParameters()?.formalParameterList?.();
268
+ if (formalParams) {
269
+ const params = formalParams.formalParameter?.();
270
+ if (params) {
271
+ for (const param of params) {
272
+ ctorInfo.params.push({
273
+ name: param.variableDeclaratorId()?.text || "",
274
+ type: param.typeType()?.text || "",
275
+ });
276
+ }
277
+ }
278
+ }
279
+
280
+ const ctorBody = ctorDecl.block();
281
+ if (ctorBody) {
282
+ ctorInfo.complexity = calculateComplexity(getText(ctorBody));
283
+ }
284
+
285
+ classInfo.methods.push(ctorInfo);
286
+ }
287
+
288
+ // Field
289
+ const fieldDecl = memberDecl.fieldDeclaration?.();
290
+ if (fieldDecl) {
291
+ const varDecls = fieldDecl.variableDeclarators()?.variableDeclarator?.();
292
+ if (varDecls) {
293
+ for (const varDecl of varDecls) {
294
+ const fieldInfo: JavaFieldInfo = {
295
+ name: varDecl.variableDeclaratorId()?.text || "",
296
+ type: fieldDecl.typeType()?.text || "",
297
+ modifiers: [],
298
+ line: getLine(fieldDecl),
299
+ annotations: [],
300
+ };
301
+
302
+ const modCtxs = bodyDecl.modifier?.();
303
+ if (modCtxs) {
304
+ fieldInfo.modifiers = getModifiers(modCtxs);
305
+ fieldInfo.annotations = getAnnotations(modCtxs);
306
+ }
307
+
308
+ classInfo.fields.push(fieldInfo);
309
+ }
310
+ }
311
+ }
312
+ }
313
+ }
314
+
315
+ analysis.classes.push(classInfo);
316
+ return null;
317
+ },
318
+
319
+ visitInterfaceDeclaration: (ctx) => {
320
+ const interfaceInfo: JavaClassInfo = {
321
+ name: ctx.identifier()?.text || "Unknown",
322
+ type: "interface",
323
+ modifiers: [],
324
+ line: getLine(ctx),
325
+ implements: [],
326
+ annotations: [],
327
+ methods: [],
328
+ fields: [],
329
+ innerClasses: [],
330
+ };
331
+
332
+ // Extends (interfaces extend other interfaces)
333
+ const typeList = ctx.typeList();
334
+ if (typeList && typeList.length > 0) {
335
+ for (const tl of typeList) {
336
+ const types = tl.typeType?.();
337
+ if (types) {
338
+ interfaceInfo.implements.push(...types.map((t: any) => t.text));
339
+ }
340
+ }
341
+ }
342
+
343
+ // Parse interface body
344
+ const interfaceBody = ctx.interfaceBody();
345
+ if (interfaceBody) {
346
+ const bodyDecls = interfaceBody.interfaceBodyDeclaration();
347
+ for (const bodyDecl of bodyDecls) {
348
+ const memberDecl = bodyDecl.interfaceMemberDeclaration?.();
349
+ if (!memberDecl) continue;
350
+
351
+ const methodDecl = memberDecl.interfaceMethodDeclaration?.();
352
+ if (methodDecl) {
353
+ const commonBody = methodDecl.interfaceCommonBodyDeclaration?.();
354
+ if (commonBody) {
355
+ const methodInfo: JavaMethodInfo = {
356
+ name: commonBody.identifier()?.text || "unknown",
357
+ returnType: methodDecl.interfaceMethodModifier?.()?.map((m) => m.text).join(" ") || "void",
358
+ params: [],
359
+ modifiers: [],
360
+ line: getLine(methodDecl),
361
+ annotations: [],
362
+ throws: [],
363
+ complexity: 1,
364
+ };
365
+
366
+ interfaceInfo.methods.push(methodInfo);
367
+ }
368
+ }
369
+ }
370
+ }
371
+
372
+ analysis.classes.push(interfaceInfo);
373
+ return null;
374
+ },
375
+
376
+ visitEnumDeclaration: (ctx) => {
377
+ const enumInfo: JavaClassInfo = {
378
+ name: ctx.identifier()?.text || "Unknown",
379
+ type: "enum",
380
+ modifiers: [],
381
+ line: getLine(ctx),
382
+ implements: [],
383
+ annotations: [],
384
+ methods: [],
385
+ fields: [],
386
+ innerClasses: [],
387
+ };
388
+
389
+ // Enum constants as fields
390
+ const enumConstants = ctx.enumConstants()?.enumConstant?.();
391
+ if (enumConstants) {
392
+ for (const ec of enumConstants) {
393
+ enumInfo.fields.push({
394
+ name: ec.identifier()?.text || "",
395
+ type: enumInfo.name,
396
+ modifiers: ["public", "static", "final"],
397
+ line: getLine(ec),
398
+ annotations: [],
399
+ });
400
+ }
401
+ }
402
+
403
+ analysis.classes.push(enumInfo);
404
+ return null;
405
+ },
406
+ });
407
+
408
+ visitor.visit(tree);
409
+
410
+ // Calculate complexity stats
411
+ const allMethods = analysis.classes.flatMap((c) => c.methods);
412
+ if (allMethods.length > 0) {
413
+ analysis.complexity.total = allMethods.reduce((sum, m) => sum + m.complexity, 0);
414
+ analysis.complexity.average = Math.round(analysis.complexity.total / allMethods.length * 10) / 10;
415
+ analysis.complexity.highest = allMethods.reduce(
416
+ (max, m) => (m.complexity > max.value ? { name: m.name, value: m.complexity } : max),
417
+ { name: "", value: 0 }
418
+ );
419
+ }
420
+ } catch (error) {
421
+ // If parsing fails, try basic regex extraction
422
+ const classMatch = content.match(/(?:public\s+)?(?:abstract\s+)?(?:class|interface|enum)\s+(\w+)/);
423
+ if (classMatch) {
424
+ analysis.classes.push({
425
+ name: classMatch[1],
426
+ type: content.includes("interface ") ? "interface" : content.includes("enum ") ? "enum" : "class",
427
+ modifiers: [],
428
+ line: 1,
429
+ implements: [],
430
+ annotations: [],
431
+ methods: [],
432
+ fields: [],
433
+ innerClasses: [],
434
+ });
435
+ }
436
+ }
437
+
438
+ return analysis;
439
+ }
440
+
441
+ // Format analysis as text
442
+ function formatJavaAnalysis(analysis: JavaFileAnalysis): string {
443
+ const lines: string[] = [];
444
+
445
+ lines.push(`=== ${path.basename(analysis.filepath)} ===`);
446
+ lines.push("");
447
+
448
+ if (analysis.package) {
449
+ lines.push(`📦 Package: ${analysis.package}`);
450
+ lines.push("");
451
+ }
452
+
453
+ if (analysis.imports.length > 0) {
454
+ lines.push("📥 Imports:");
455
+ // Group by prefix
456
+ const grouped: Record<string, string[]> = {};
457
+ for (const imp of analysis.imports) {
458
+ const prefix = imp.split(".").slice(0, 2).join(".");
459
+ if (!grouped[prefix]) grouped[prefix] = [];
460
+ grouped[prefix].push(imp);
461
+ }
462
+ for (const [prefix, imps] of Object.entries(grouped)) {
463
+ lines.push(` ${prefix}.* (${imps.length})`);
464
+ }
465
+ lines.push("");
466
+ }
467
+
468
+ for (const cls of analysis.classes) {
469
+ const icon = cls.type === "interface" ? "📋" : cls.type === "enum" ? "🔢" : "🏛️";
470
+ const mods = cls.modifiers.length > 0 ? cls.modifiers.join(" ") + " " : "";
471
+ const ext = cls.extends ? ` extends ${cls.extends}` : "";
472
+ const impl = cls.implements.length > 0 ? ` implements ${cls.implements.join(", ")}` : "";
473
+ const annots = cls.annotations.length > 0 ? cls.annotations.map((a) => `@${a}`).join(" ") + " " : "";
474
+
475
+ lines.push(`${icon} L${cls.line}: ${annots}${mods}${cls.type} ${cls.name}${ext}${impl}`);
476
+
477
+ // Fields
478
+ if (cls.fields.length > 0) {
479
+ lines.push(" 필드:");
480
+ for (const field of cls.fields) {
481
+ const fMods = field.modifiers.join(" ");
482
+ const fAnnots = field.annotations.length > 0 ? field.annotations.map((a) => `@${a}`).join(" ") + " " : "";
483
+ lines.push(` ${fAnnots}${fMods} ${field.type} ${field.name}`);
484
+ }
485
+ }
486
+
487
+ // Methods
488
+ if (cls.methods.length > 0) {
489
+ lines.push(" 메서드:");
490
+ for (const method of cls.methods) {
491
+ const mMods = method.modifiers.join(" ");
492
+ const mAnnots = method.annotations.length > 0 ? method.annotations.map((a) => `@${a}`).join(" ") + " " : "";
493
+ const params = method.params.map((p) => `${p.type} ${p.name}`).join(", ");
494
+ const throws = method.throws.length > 0 ? ` throws ${method.throws.join(", ")}` : "";
495
+ lines.push(` L${method.line}: ${mAnnots}${mMods} ${method.returnType} ${method.name}(${params})${throws}`);
496
+ lines.push(` 복잡도: ${method.complexity}`);
497
+ }
498
+ }
499
+ lines.push("");
500
+ }
501
+
502
+ lines.push("📊 복잡도:");
503
+ lines.push(` 총합: ${analysis.complexity.total} | 평균: ${analysis.complexity.average}`);
504
+ if (analysis.complexity.highest.name) {
505
+ lines.push(` 최고: ${analysis.complexity.highest.name} (${analysis.complexity.highest.value})`);
506
+ }
507
+
508
+ return lines.join("\n");
509
+ }
510
+
511
+ // Java Analyze Tool
512
+ export const javaAnalyzeTool: Tool = {
513
+ name: "java_analyze",
514
+ description: "Analyze Java source file using AST parser (Java 분석). Returns classes, methods, fields, annotations, complexity. Use when user asks: 'analyze java', 'java 분석', 'Spring 분석'.",
515
+ parameters: {
516
+ type: "object",
517
+ required: ["filepath"],
518
+ properties: {
519
+ filepath: {
520
+ type: "string",
521
+ description: "Path to Java file (.java)",
522
+ },
523
+ format: {
524
+ type: "string",
525
+ description: "Output format: 'text' or 'json'",
526
+ enum: ["text", "json"],
527
+ },
528
+ },
529
+ },
530
+ handler: async (args): Promise<ToolResult> => {
531
+ try {
532
+ const filepath = path.resolve(args.filepath as string);
533
+ const format = (args.format as string) || "text";
534
+
535
+ if (!fs.existsSync(filepath)) {
536
+ return { success: false, content: "", error: `File not found: ${filepath}` };
537
+ }
538
+
539
+ if (!filepath.endsWith(".java")) {
540
+ return { success: false, content: "", error: "Not a Java file (.java required)" };
541
+ }
542
+
543
+ const content = fs.readFileSync(filepath, "utf-8");
544
+ const analysis = analyzeJavaFile(content, filepath);
545
+
546
+ if (format === "json") {
547
+ return { success: true, content: JSON.stringify(analysis, null, 2) };
548
+ }
549
+
550
+ return { success: true, content: formatJavaAnalysis(analysis) };
551
+ } catch (error) {
552
+ return { success: false, content: "", error: String(error) };
553
+ }
554
+ },
555
+ };
556
+
557
+ // Java Complexity Report Tool
558
+ export const javaComplexityTool: Tool = {
559
+ name: "java_complexity",
560
+ description: "Calculate complexity for Java files (Java 복잡도 리포트). Use when user asks: 'java complexity', 'java 복잡도'.",
561
+ parameters: {
562
+ type: "object",
563
+ required: ["pattern"],
564
+ properties: {
565
+ pattern: {
566
+ type: "string",
567
+ description: "Glob pattern (e.g., src/**/*.java)",
568
+ },
569
+ threshold: {
570
+ type: "number",
571
+ description: "Only show methods with complexity >= threshold (default: 5)",
572
+ },
573
+ },
574
+ },
575
+ handler: async (args): Promise<ToolResult> => {
576
+ try {
577
+ const { glob } = await import("glob");
578
+ const pattern = args.pattern as string;
579
+ const threshold = (args.threshold as number) || 5;
580
+
581
+ const files = await glob(pattern, {
582
+ ignore: ["**/node_modules/**", "**/target/**", "**/build/**"],
583
+ });
584
+
585
+ const allMethods: Array<{
586
+ file: string;
587
+ class: string;
588
+ method: string;
589
+ complexity: number;
590
+ line: number;
591
+ }> = [];
592
+
593
+ for (const file of files) {
594
+ if (!file.endsWith(".java")) continue;
595
+ try {
596
+ const content = fs.readFileSync(file, "utf-8");
597
+ const analysis = analyzeJavaFile(content, file);
598
+
599
+ for (const cls of analysis.classes) {
600
+ for (const method of cls.methods) {
601
+ allMethods.push({
602
+ file: path.relative(process.cwd(), file),
603
+ class: cls.name,
604
+ method: method.name,
605
+ complexity: method.complexity,
606
+ line: method.line,
607
+ });
608
+ }
609
+ }
610
+ } catch {
611
+ // Skip unparseable files
612
+ }
613
+ }
614
+
615
+ // Sort by complexity
616
+ allMethods.sort((a, b) => b.complexity - a.complexity);
617
+
618
+ const high = allMethods.filter((m) => m.complexity >= threshold);
619
+ const total = allMethods.reduce((sum, m) => sum + m.complexity, 0);
620
+ const avg = allMethods.length > 0 ? Math.round(total / allMethods.length * 10) / 10 : 0;
621
+
622
+ const lines: string[] = [];
623
+ lines.push("=== Java 복잡도 리포트 ===");
624
+ lines.push("");
625
+ lines.push("📊 통계:");
626
+ lines.push(` 총 메서드: ${allMethods.length}개`);
627
+ lines.push(` 평균 복잡도: ${avg}`);
628
+ lines.push(` 높은 복잡도 (>= ${threshold}): ${high.length}개`);
629
+ lines.push("");
630
+
631
+ if (high.length > 0) {
632
+ lines.push(`⚠️ 복잡도 높은 메서드 (>= ${threshold}):`);
633
+ for (const m of high.slice(0, 20)) {
634
+ const bar = "█".repeat(Math.min(m.complexity, 20));
635
+ lines.push(` ${m.complexity.toString().padStart(2)} ${bar} ${m.class}.${m.method}()`);
636
+ lines.push(` ${m.file}:${m.line}`);
637
+ }
638
+ if (high.length > 20) {
639
+ lines.push(` ... 외 ${high.length - 20}개`);
640
+ }
641
+ } else {
642
+ lines.push(`✅ 복잡도 ${threshold} 이상인 메서드가 없습니다.`);
643
+ }
644
+
645
+ return { success: true, content: lines.join("\n") };
646
+ } catch (error) {
647
+ return { success: false, content: "", error: String(error) };
648
+ }
649
+ },
650
+ };
651
+
652
+ // Spring Pattern Check Tool
653
+ export const springCheckTool: Tool = {
654
+ name: "spring_check",
655
+ description: "Check Spring framework patterns (Spring 패턴 검사). Finds controllers, services, repositories, configuration. Use when user asks: 'spring check', 'spring 분석', 'Spring 패턴'.",
656
+ parameters: {
657
+ type: "object",
658
+ required: ["pattern"],
659
+ properties: {
660
+ pattern: {
661
+ type: "string",
662
+ description: "Glob pattern (e.g., src/**/*.java)",
663
+ },
664
+ },
665
+ },
666
+ handler: async (args): Promise<ToolResult> => {
667
+ try {
668
+ const { glob } = await import("glob");
669
+ const pattern = args.pattern as string;
670
+
671
+ const files = await glob(pattern, {
672
+ ignore: ["**/node_modules/**", "**/target/**", "**/build/**"],
673
+ });
674
+
675
+ const springComponents = {
676
+ controllers: [] as Array<{ file: string; class: string; mappings: string[] }>,
677
+ services: [] as Array<{ file: string; class: string }>,
678
+ repositories: [] as Array<{ file: string; class: string }>,
679
+ components: [] as Array<{ file: string; class: string }>,
680
+ configurations: [] as Array<{ file: string; class: string }>,
681
+ entities: [] as Array<{ file: string; class: string }>,
682
+ };
683
+
684
+ for (const file of files) {
685
+ if (!file.endsWith(".java")) continue;
686
+ try {
687
+ const content = fs.readFileSync(file, "utf-8");
688
+ const analysis = analyzeJavaFile(content, file);
689
+ const relativePath = path.relative(process.cwd(), file);
690
+
691
+ for (const cls of analysis.classes) {
692
+ const annotations = cls.annotations.map((a) => a.toLowerCase());
693
+
694
+ // Controller
695
+ if (annotations.some((a) => a.includes("controller") || a.includes("restcontroller"))) {
696
+ const mappings: string[] = [];
697
+ // Find request mappings in methods
698
+ for (const method of cls.methods) {
699
+ const methodAnnots = method.annotations.join(" ").toLowerCase();
700
+ if (methodAnnots.includes("mapping")) {
701
+ mappings.push(`${method.name}()`);
702
+ }
703
+ }
704
+ springComponents.controllers.push({ file: relativePath, class: cls.name, mappings });
705
+ }
706
+
707
+ // Service
708
+ if (annotations.some((a) => a.includes("service"))) {
709
+ springComponents.services.push({ file: relativePath, class: cls.name });
710
+ }
711
+
712
+ // Repository
713
+ if (annotations.some((a) => a.includes("repository"))) {
714
+ springComponents.repositories.push({ file: relativePath, class: cls.name });
715
+ }
716
+
717
+ // Component
718
+ if (annotations.some((a) => a === "component")) {
719
+ springComponents.components.push({ file: relativePath, class: cls.name });
720
+ }
721
+
722
+ // Configuration
723
+ if (annotations.some((a) => a.includes("configuration"))) {
724
+ springComponents.configurations.push({ file: relativePath, class: cls.name });
725
+ }
726
+
727
+ // Entity
728
+ if (annotations.some((a) => a.includes("entity") || a.includes("table"))) {
729
+ springComponents.entities.push({ file: relativePath, class: cls.name });
730
+ }
731
+ }
732
+ } catch {
733
+ // Skip unparseable files
734
+ }
735
+ }
736
+
737
+ const lines: string[] = [];
738
+ lines.push("=== Spring 패턴 분석 ===");
739
+ lines.push("");
740
+
741
+ if (springComponents.controllers.length > 0) {
742
+ lines.push(`🎮 Controllers (${springComponents.controllers.length}):`);
743
+ for (const c of springComponents.controllers) {
744
+ lines.push(` ${c.class}`);
745
+ lines.push(` ${c.file}`);
746
+ if (c.mappings.length > 0) {
747
+ lines.push(` 엔드포인트: ${c.mappings.join(", ")}`);
748
+ }
749
+ }
750
+ lines.push("");
751
+ }
752
+
753
+ if (springComponents.services.length > 0) {
754
+ lines.push(`⚙️ Services (${springComponents.services.length}):`);
755
+ for (const s of springComponents.services) {
756
+ lines.push(` ${s.class} - ${s.file}`);
757
+ }
758
+ lines.push("");
759
+ }
760
+
761
+ if (springComponents.repositories.length > 0) {
762
+ lines.push(`🗄️ Repositories (${springComponents.repositories.length}):`);
763
+ for (const r of springComponents.repositories) {
764
+ lines.push(` ${r.class} - ${r.file}`);
765
+ }
766
+ lines.push("");
767
+ }
768
+
769
+ if (springComponents.entities.length > 0) {
770
+ lines.push(`📋 Entities (${springComponents.entities.length}):`);
771
+ for (const e of springComponents.entities) {
772
+ lines.push(` ${e.class} - ${e.file}`);
773
+ }
774
+ lines.push("");
775
+ }
776
+
777
+ if (springComponents.configurations.length > 0) {
778
+ lines.push(`🔧 Configurations (${springComponents.configurations.length}):`);
779
+ for (const c of springComponents.configurations) {
780
+ lines.push(` ${c.class} - ${c.file}`);
781
+ }
782
+ lines.push("");
783
+ }
784
+
785
+ if (springComponents.components.length > 0) {
786
+ lines.push(`📦 Components (${springComponents.components.length}):`);
787
+ for (const c of springComponents.components) {
788
+ lines.push(` ${c.class} - ${c.file}`);
789
+ }
790
+ lines.push("");
791
+ }
792
+
793
+ const total = Object.values(springComponents).reduce((sum, arr) => sum + arr.length, 0);
794
+ if (total === 0) {
795
+ lines.push("Spring 컴포넌트를 찾지 못했습니다.");
796
+ } else {
797
+ lines.push(`📊 총 ${total}개 Spring 컴포넌트 발견`);
798
+ }
799
+
800
+ return { success: true, content: lines.join("\n") };
801
+ } catch (error) {
802
+ return { success: false, content: "", error: String(error) };
803
+ }
804
+ },
805
+ };
806
+
807
+ // Export all Java tools
808
+ export const javaTools: Tool[] = [
809
+ javaAnalyzeTool,
810
+ javaComplexityTool,
811
+ springCheckTool,
812
+ ];