activo 0.4.4 → 0.5.0

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 (161) hide show
  1. package/README.md +203 -1
  2. package/data/2026-03-04_20-54.json +181 -0
  3. package/data/2026-03-04_20-56.json +181 -0
  4. package/data/apex-rulesets/egov.yaml +469 -0
  5. package/data/apex-rulesets/modernize.yaml +687 -0
  6. package/data/apex-rulesets/quality.yaml +1677 -0
  7. package/data/apex-rulesets/rule-schema.yaml +587 -0
  8. package/data/apex-rulesets/secure.yaml +1688 -0
  9. package/data/apex-rulesets/spring.yaml +455 -0
  10. package/data/apex-rulesets/sql-format.yaml +99 -0
  11. package/data/apex-rulesets/sql-oracle.yaml +281 -0
  12. package/data/apex-rulesets/sql.yaml +1660 -0
  13. package/dist/cli/headless.d.ts.map +1 -1
  14. package/dist/cli/headless.js +32 -10
  15. package/dist/cli/headless.js.map +1 -1
  16. package/dist/cli/index.js +31 -3
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/core/agent.d.ts +3 -3
  19. package/dist/core/agent.d.ts.map +1 -1
  20. package/dist/core/agent.js +203 -384
  21. package/dist/core/agent.js.map +1 -1
  22. package/dist/core/commands.d.ts +2 -1
  23. package/dist/core/commands.d.ts.map +1 -1
  24. package/dist/core/commands.js +61 -9
  25. package/dist/core/commands.js.map +1 -1
  26. package/dist/core/config.d.ts +14 -0
  27. package/dist/core/config.d.ts.map +1 -1
  28. package/dist/core/config.js +41 -4
  29. package/dist/core/config.js.map +1 -1
  30. package/dist/core/conversation.d.ts +2 -2
  31. package/dist/core/conversation.d.ts.map +1 -1
  32. package/dist/core/conversation.js.map +1 -1
  33. package/dist/core/intentRouter.d.ts +43 -0
  34. package/dist/core/intentRouter.d.ts.map +1 -0
  35. package/dist/core/intentRouter.js +804 -0
  36. package/dist/core/intentRouter.js.map +1 -0
  37. package/dist/core/llm/anthropic.d.ts +24 -0
  38. package/dist/core/llm/anthropic.d.ts.map +1 -0
  39. package/dist/core/llm/anthropic.js +226 -0
  40. package/dist/core/llm/anthropic.js.map +1 -0
  41. package/dist/core/llm/ollama.d.ts +5 -14
  42. package/dist/core/llm/ollama.d.ts.map +1 -1
  43. package/dist/core/llm/ollama.js +3 -0
  44. package/dist/core/llm/ollama.js.map +1 -1
  45. package/dist/core/llm/types.d.ts +22 -0
  46. package/dist/core/llm/types.d.ts.map +1 -0
  47. package/dist/core/llm/types.js +2 -0
  48. package/dist/core/llm/types.js.map +1 -0
  49. package/dist/core/mcp/client.d.ts +6 -0
  50. package/dist/core/mcp/client.d.ts.map +1 -1
  51. package/dist/core/mcp/client.js +16 -0
  52. package/dist/core/mcp/client.js.map +1 -1
  53. package/dist/core/mcp/init.d.ts +12 -0
  54. package/dist/core/mcp/init.d.ts.map +1 -0
  55. package/dist/core/mcp/init.js +55 -0
  56. package/dist/core/mcp/init.js.map +1 -0
  57. package/dist/core/mcp/logger.d.ts +14 -0
  58. package/dist/core/mcp/logger.d.ts.map +1 -0
  59. package/dist/core/mcp/logger.js +50 -0
  60. package/dist/core/mcp/logger.js.map +1 -0
  61. package/dist/core/tools/analyzePatterns.d.ts +3 -0
  62. package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
  63. package/dist/core/tools/analyzePatterns.js +293 -0
  64. package/dist/core/tools/analyzePatterns.js.map +1 -0
  65. package/dist/core/tools/apexPaths.d.ts +14 -0
  66. package/dist/core/tools/apexPaths.d.ts.map +1 -0
  67. package/dist/core/tools/apexPaths.js +54 -0
  68. package/dist/core/tools/apexPaths.js.map +1 -0
  69. package/dist/core/tools/apexUtils.d.ts +36 -0
  70. package/dist/core/tools/apexUtils.d.ts.map +1 -0
  71. package/dist/core/tools/apexUtils.js +83 -0
  72. package/dist/core/tools/apexUtils.js.map +1 -0
  73. package/dist/core/tools/explainIssue.d.ts +3 -0
  74. package/dist/core/tools/explainIssue.d.ts.map +1 -0
  75. package/dist/core/tools/explainIssue.js +181 -0
  76. package/dist/core/tools/explainIssue.js.map +1 -0
  77. package/dist/core/tools/fixGen.d.ts +3 -0
  78. package/dist/core/tools/fixGen.d.ts.map +1 -0
  79. package/dist/core/tools/fixGen.js +338 -0
  80. package/dist/core/tools/fixGen.js.map +1 -0
  81. package/dist/core/tools/generateImprovements.d.ts +21 -0
  82. package/dist/core/tools/generateImprovements.d.ts.map +1 -0
  83. package/dist/core/tools/generateImprovements.js +602 -0
  84. package/dist/core/tools/generateImprovements.js.map +1 -0
  85. package/dist/core/tools/generateReport.d.ts +3 -0
  86. package/dist/core/tools/generateReport.d.ts.map +1 -0
  87. package/dist/core/tools/generateReport.js +315 -0
  88. package/dist/core/tools/generateReport.js.map +1 -0
  89. package/dist/core/tools/index.d.ts +7 -0
  90. package/dist/core/tools/index.d.ts.map +1 -1
  91. package/dist/core/tools/index.js +62 -23
  92. package/dist/core/tools/index.js.map +1 -1
  93. package/dist/core/tools/recommendProfile.d.ts +3 -0
  94. package/dist/core/tools/recommendProfile.d.ts.map +1 -0
  95. package/dist/core/tools/recommendProfile.js +334 -0
  96. package/dist/core/tools/recommendProfile.js.map +1 -0
  97. package/dist/core/tools/ruleGen.d.ts +3 -0
  98. package/dist/core/tools/ruleGen.d.ts.map +1 -0
  99. package/dist/core/tools/ruleGen.js +1103 -0
  100. package/dist/core/tools/ruleGen.js.map +1 -0
  101. package/dist/core/tools/standards.d.ts.map +1 -1
  102. package/dist/core/tools/standards.js +7 -3
  103. package/dist/core/tools/standards.js.map +1 -1
  104. package/dist/ui/App.d.ts.map +1 -1
  105. package/dist/ui/App.js +86 -35
  106. package/dist/ui/App.js.map +1 -1
  107. package/dist/ui/components/InputBox.d.ts +1 -3
  108. package/dist/ui/components/InputBox.d.ts.map +1 -1
  109. package/dist/ui/components/InputBox.js +146 -5
  110. package/dist/ui/components/InputBox.js.map +1 -1
  111. package/dist/ui/components/MessageList.d.ts +3 -1
  112. package/dist/ui/components/MessageList.d.ts.map +1 -1
  113. package/dist/ui/components/MessageList.js +13 -7
  114. package/dist/ui/components/MessageList.js.map +1 -1
  115. package/dist/ui/components/StatusBar.d.ts +1 -1
  116. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  117. package/dist/ui/components/StatusBar.js +3 -2
  118. package/dist/ui/components/StatusBar.js.map +1 -1
  119. package/dist/ui/components/ToolStatus.d.ts +3 -1
  120. package/dist/ui/components/ToolStatus.d.ts.map +1 -1
  121. package/dist/ui/components/ToolStatus.js +19 -4
  122. package/dist/ui/components/ToolStatus.js.map +1 -1
  123. package/package.json +7 -1
  124. package/demo.gif +0 -0
  125. package/demo.tape +0 -53
  126. package/screenshot.png +0 -0
  127. package/src/cli/banner.ts +0 -38
  128. package/src/cli/headless.ts +0 -63
  129. package/src/cli/index.ts +0 -57
  130. package/src/core/agent.ts +0 -711
  131. package/src/core/commands.ts +0 -118
  132. package/src/core/config.ts +0 -98
  133. package/src/core/conversation.ts +0 -235
  134. package/src/core/llm/ollama.ts +0 -351
  135. package/src/core/mcp/client.ts +0 -143
  136. package/src/core/tools/analyzeAll.ts +0 -482
  137. package/src/core/tools/ast.ts +0 -826
  138. package/src/core/tools/builtIn.ts +0 -221
  139. package/src/core/tools/cache.ts +0 -570
  140. package/src/core/tools/cssAnalysis.ts +0 -324
  141. package/src/core/tools/dependencyAnalysis.ts +0 -363
  142. package/src/core/tools/embeddings.ts +0 -746
  143. package/src/core/tools/frontendAst.ts +0 -802
  144. package/src/core/tools/htmlAnalysis.ts +0 -466
  145. package/src/core/tools/index.ts +0 -160
  146. package/src/core/tools/javaAst.ts +0 -1030
  147. package/src/core/tools/javaQuality.integration.test.ts +0 -537
  148. package/src/core/tools/memory.ts +0 -655
  149. package/src/core/tools/mybatisAnalysis.ts +0 -322
  150. package/src/core/tools/openapiAnalysis.ts +0 -431
  151. package/src/core/tools/pythonAnalysis.ts +0 -477
  152. package/src/core/tools/sqlAnalysis.ts +0 -298
  153. package/src/core/tools/standards.test.ts +0 -186
  154. package/src/core/tools/standards.ts +0 -889
  155. package/src/core/tools/types.ts +0 -38
  156. package/src/ui/App.tsx +0 -334
  157. package/src/ui/components/InputBox.tsx +0 -37
  158. package/src/ui/components/MessageList.tsx +0 -80
  159. package/src/ui/components/StatusBar.tsx +0 -36
  160. package/src/ui/components/ToolStatus.tsx +0 -38
  161. package/tsconfig.json +0 -21
@@ -1,1030 +0,0 @@
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
- // Quality issue
42
- interface QualityIssue {
43
- type: "npe-risk" | "exception-antipattern" | "dead-code";
44
- severity: "error" | "warning" | "info";
45
- line: number;
46
- message: string;
47
- suggestion: string;
48
- }
49
-
50
- // Java file analysis result
51
- interface JavaFileAnalysis {
52
- filepath: string;
53
- package: string;
54
- imports: string[];
55
- classes: JavaClassInfo[];
56
- issues: QualityIssue[];
57
- complexity: {
58
- total: number;
59
- average: number;
60
- highest: { name: string; value: number };
61
- };
62
- }
63
-
64
- // Calculate cyclomatic complexity from method body text
65
- function calculateComplexity(methodText: string): number {
66
- let complexity = 1;
67
-
68
- // Count decision points
69
- const patterns = [
70
- /\bif\s*\(/g,
71
- /\belse\s+if\s*\(/g,
72
- /\bfor\s*\(/g,
73
- /\bwhile\s*\(/g,
74
- /\bcase\s+/g,
75
- /\bcatch\s*\(/g,
76
- /\b\?\s*[^:]/g, // ternary operator
77
- /\&\&/g,
78
- /\|\|/g,
79
- ];
80
-
81
- for (const pattern of patterns) {
82
- const matches = methodText.match(pattern);
83
- if (matches) {
84
- complexity += matches.length;
85
- }
86
- }
87
-
88
- return complexity;
89
- }
90
-
91
- // Detect NullPointerException risk patterns
92
- function detectNPERisks(content: string): QualityIssue[] {
93
- const issues: QualityIssue[] = [];
94
- const lines = content.split("\n");
95
-
96
- const patterns: Array<{ regex: RegExp; message: string; suggestion: string }> = [
97
- {
98
- regex: /\.get\([^)]*\)\s*\.toString\s*\(/,
99
- message: ".get().toString() — null일 때 NPE 발생",
100
- suggestion: "Objects.toString() 또는 null 체크 후 호출",
101
- },
102
- {
103
- regex: /\.get\([^)]*\)\s*\.equals\s*\(/,
104
- message: ".get().equals() — null일 때 NPE 발생",
105
- suggestion: "리터럴.equals(obj) 또는 Objects.equals() 사용",
106
- },
107
- {
108
- regex: /\(\s*String\s*\)\s*\w+\.get\s*\(/,
109
- message: "(String) map.get() — null 캐스팅 시 NPE 위험",
110
- suggestion: "String.valueOf() 또는 Optional 사용",
111
- },
112
- {
113
- regex: /\.get\([^)]*\)\s*\.(length|size)\s*\(/,
114
- message: ".get().length()/size() — null 체이닝 NPE 위험",
115
- suggestion: "null 체크 후 호출 또는 Optional 사용",
116
- },
117
- ];
118
-
119
- for (let i = 0; i < lines.length; i++) {
120
- const line = lines[i];
121
- // Skip comments
122
- const trimmed = line.trim();
123
- if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) continue;
124
-
125
- for (const p of patterns) {
126
- if (p.regex.test(line)) {
127
- issues.push({
128
- type: "npe-risk",
129
- severity: "error",
130
- line: i + 1,
131
- message: p.message,
132
- suggestion: p.suggestion,
133
- });
134
- }
135
- }
136
- }
137
-
138
- return issues;
139
- }
140
-
141
- // Detect exception handling anti-patterns
142
- function detectExceptionAntiPatterns(content: string): QualityIssue[] {
143
- const issues: QualityIssue[] = [];
144
- const lines = content.split("\n");
145
-
146
- // 1. Empty catch blocks
147
- const emptyCatchRegex = /catch\s*\([^)]+\)\s*\{\s*\}/g;
148
- let match: RegExpExecArray | null;
149
- while ((match = emptyCatchRegex.exec(content)) !== null) {
150
- const lineNum = content.substring(0, match.index).split("\n").length;
151
- issues.push({
152
- type: "exception-antipattern",
153
- severity: "warning",
154
- line: lineNum,
155
- message: "빈 catch 블록 — 예외가 무시됨",
156
- suggestion: "최소한 로그를 남기거나 rethrow 하세요",
157
- });
158
- }
159
-
160
- // 2. Multi-line empty catch (catch with only whitespace)
161
- for (let i = 0; i < lines.length; i++) {
162
- const line = lines[i].trim();
163
-
164
- // e.printStackTrace()
165
- if (line.includes(".printStackTrace()")) {
166
- issues.push({
167
- type: "exception-antipattern",
168
- severity: "warning",
169
- line: i + 1,
170
- message: "e.printStackTrace() 사용 — 프로덕션 부적합",
171
- suggestion: "Logger를 사용하세요 (LOGGER.error(\"msg\", e))",
172
- });
173
- }
174
-
175
- // LOGGER.info in catch block - check context
176
- if (/LOGGER\s*\.\s*info\s*\(/.test(line) || /log\s*\.\s*info\s*\(/i.test(line)) {
177
- // Look backwards to see if we're inside a catch block
178
- for (let j = i - 1; j >= Math.max(0, i - 10); j--) {
179
- if (/catch\s*\(/.test(lines[j])) {
180
- issues.push({
181
- type: "exception-antipattern",
182
- severity: "warning",
183
- line: i + 1,
184
- message: "catch 블록에서 info 레벨 로그 — error/warn 사용 권장",
185
- suggestion: "LOGGER.error() 또는 LOGGER.warn() 사용",
186
- });
187
- break;
188
- }
189
- if (lines[j].trim() === "}") break; // exited the block
190
- }
191
- }
192
- }
193
-
194
- // 3. Unused exception variable in catch
195
- const catchVarRegex = /catch\s*\(\s*\w+\s+(\w+)\s*\)/g;
196
- while ((match = catchVarRegex.exec(content)) !== null) {
197
- const varName = match[1];
198
- const catchStart = match.index + match[0].length;
199
-
200
- // Find the matching closing brace
201
- let braceCount = 0;
202
- let blockEnd = catchStart;
203
- for (let i = catchStart; i < content.length; i++) {
204
- if (content[i] === "{") braceCount++;
205
- if (content[i] === "}") {
206
- braceCount--;
207
- if (braceCount === 0) {
208
- blockEnd = i;
209
- break;
210
- }
211
- }
212
- }
213
-
214
- const catchBody = content.substring(catchStart, blockEnd);
215
- // Check if variable is referenced in catch body (exclude the declaration itself)
216
- const varUsageRegex = new RegExp(`\\b${varName}\\b`);
217
- if (!varUsageRegex.test(catchBody)) {
218
- const lineNum = content.substring(0, match.index).split("\n").length;
219
- issues.push({
220
- type: "exception-antipattern",
221
- severity: "warning",
222
- line: lineNum,
223
- message: `catch 블록에서 예외 변수 '${varName}' 미사용`,
224
- suggestion: "예외 정보를 로깅하거나, 필요 없으면 변수명을 'ignored'로 변경",
225
- });
226
- }
227
- }
228
-
229
- return issues;
230
- }
231
-
232
- // Detect commented-out code blocks
233
- function detectCommentedOutCode(content: string): QualityIssue[] {
234
- const issues: QualityIssue[] = [];
235
- const lines = content.split("\n");
236
- const codeKeywords = /\b(if|else|return|public|private|protected|import|class|interface|void|int|String|new|throw|try|catch|for|while)\b/;
237
-
238
- let consecutiveComments: number[] = [];
239
-
240
- for (let i = 0; i < lines.length; i++) {
241
- const trimmed = lines[i].trim();
242
- if (trimmed.startsWith("//") && !trimmed.startsWith("///") && !trimmed.startsWith("// TODO") && !trimmed.startsWith("// FIXME") && !trimmed.startsWith("// NOTE")) {
243
- consecutiveComments.push(i);
244
- } else {
245
- // Check if we had a block of 5+ consecutive comment lines with code keywords
246
- if (consecutiveComments.length >= 5) {
247
- const commentBlock = consecutiveComments
248
- .map((idx) => lines[idx].trim().substring(2).trim())
249
- .join("\n");
250
- if (codeKeywords.test(commentBlock)) {
251
- issues.push({
252
- type: "dead-code",
253
- severity: "info",
254
- line: consecutiveComments[0] + 1,
255
- message: `주석 처리된 코드 블록 (${consecutiveComments.length}줄, L${consecutiveComments[0] + 1}-L${consecutiveComments[consecutiveComments.length - 1] + 1})`,
256
- suggestion: "사용하지 않는 코드는 삭제하세요 (VCS에서 복원 가능)",
257
- });
258
- }
259
- }
260
- consecutiveComments = [];
261
- }
262
- }
263
-
264
- // Check trailing block
265
- if (consecutiveComments.length >= 5) {
266
- const commentBlock = consecutiveComments
267
- .map((idx) => lines[idx].trim().substring(2).trim())
268
- .join("\n");
269
- if (codeKeywords.test(commentBlock)) {
270
- issues.push({
271
- type: "dead-code",
272
- severity: "info",
273
- line: consecutiveComments[0] + 1,
274
- message: `주석 처리된 코드 블록 (${consecutiveComments.length}줄, L${consecutiveComments[0] + 1}-L${consecutiveComments[consecutiveComments.length - 1] + 1})`,
275
- suggestion: "사용하지 않는 코드는 삭제하세요 (VCS에서 복원 가능)",
276
- });
277
- }
278
- }
279
-
280
- return issues;
281
- }
282
-
283
- // Analyze Java file
284
- function analyzeJavaFile(content: string, filepath: string): JavaFileAnalysis {
285
- const analysis: JavaFileAnalysis = {
286
- filepath,
287
- package: "",
288
- imports: [],
289
- classes: [],
290
- issues: [],
291
- complexity: { total: 0, average: 0, highest: { name: "", value: 0 } },
292
- };
293
-
294
- try {
295
- const tree = parse(content);
296
- const lines = content.split("\n");
297
-
298
- // Get line number from context
299
- const getLine = (ctx: any): number => {
300
- if (ctx && ctx.start) {
301
- return ctx.start.line;
302
- }
303
- return 0;
304
- };
305
-
306
- // Get text from context
307
- const getText = (ctx: any): string => {
308
- if (ctx && ctx.start && ctx.stop) {
309
- const startIdx = ctx.start.startIndex;
310
- const stopIdx = ctx.stop.stopIndex;
311
- return content.substring(startIdx, stopIdx + 1);
312
- }
313
- return ctx?.text || "";
314
- };
315
-
316
- // Get modifiers
317
- const getModifiers = (modifierCtxs: any[]): string[] => {
318
- if (!modifierCtxs) return [];
319
- return modifierCtxs.map((m) => m.text).filter((m) => m);
320
- };
321
-
322
- // Get annotations
323
- const getAnnotations = (modifierCtxs: any[]): string[] => {
324
- if (!modifierCtxs) return [];
325
- return modifierCtxs
326
- .filter((m) => m.annotation)
327
- .map((m) => m.annotation()?.qualifiedName()?.text || m.text)
328
- .filter((a) => a);
329
- };
330
-
331
- // Parse using visitor pattern
332
- const visitor = createVisitor({
333
- defaultResult: () => null,
334
- aggregateResult: (a, b) => b || a,
335
-
336
- visitPackageDeclaration: (ctx) => {
337
- analysis.package = ctx.qualifiedName()?.text || "";
338
- return null;
339
- },
340
-
341
- visitImportDeclaration: (ctx) => {
342
- const importText = ctx.qualifiedName()?.text || "";
343
- if (importText) {
344
- const isStatic = ctx.STATIC() ? "static " : "";
345
- const isWildcard = ctx.MUL() ? ".*" : "";
346
- analysis.imports.push(isStatic + importText + isWildcard);
347
- }
348
- return null;
349
- },
350
-
351
- visitClassDeclaration: (ctx) => {
352
- const classInfo: JavaClassInfo = {
353
- name: ctx.identifier()?.text || "Unknown",
354
- type: "class",
355
- modifiers: [],
356
- line: getLine(ctx),
357
- implements: [],
358
- annotations: [],
359
- methods: [],
360
- fields: [],
361
- innerClasses: [],
362
- };
363
-
364
- // Get parent modifiers
365
- const parent = ctx.parent;
366
- if (parent && (parent as any).classOrInterfaceModifier) {
367
- const mods = (parent as any).classOrInterfaceModifier();
368
- classInfo.modifiers = getModifiers(mods);
369
- classInfo.annotations = getAnnotations(mods);
370
- }
371
-
372
- // Extends
373
- const extendsType = ctx.typeType();
374
- if (extendsType) {
375
- classInfo.extends = extendsType.text;
376
- }
377
-
378
- // Implements
379
- const typeList = ctx.typeList();
380
- if (typeList && typeList.length > 0) {
381
- for (const tl of typeList) {
382
- const types = tl.typeType();
383
- if (types) {
384
- classInfo.implements.push(...types.map((t) => t.text));
385
- }
386
- }
387
- }
388
-
389
- // Parse class body
390
- const classBody = ctx.classBody();
391
- if (classBody) {
392
- const bodyDecls = classBody.classBodyDeclaration();
393
- for (const bodyDecl of bodyDecls) {
394
- const memberDecl = bodyDecl.memberDeclaration?.();
395
- if (!memberDecl) continue;
396
-
397
- // Method
398
- const methodDecl = memberDecl.methodDeclaration?.();
399
- if (methodDecl) {
400
- const methodInfo: JavaMethodInfo = {
401
- name: methodDecl.identifier()?.text || "unknown",
402
- returnType: methodDecl.typeTypeOrVoid()?.text || "void",
403
- params: [],
404
- modifiers: [],
405
- line: getLine(methodDecl),
406
- annotations: [],
407
- throws: [],
408
- complexity: 1,
409
- };
410
-
411
- // Get modifiers from parent
412
- const modCtxs = bodyDecl.modifier?.();
413
- if (modCtxs) {
414
- methodInfo.modifiers = getModifiers(modCtxs);
415
- methodInfo.annotations = getAnnotations(modCtxs);
416
- }
417
-
418
- // Parameters
419
- const formalParams = methodDecl.formalParameters()?.formalParameterList?.();
420
- if (formalParams) {
421
- const params = formalParams.formalParameter?.();
422
- if (params) {
423
- for (const param of params) {
424
- methodInfo.params.push({
425
- name: param.variableDeclaratorId()?.text || "",
426
- type: param.typeType()?.text || "",
427
- });
428
- }
429
- }
430
- }
431
-
432
- // Throws
433
- const throwsClause = methodDecl.THROWS?.();
434
- if (throwsClause) {
435
- const qualNames = methodDecl.qualifiedNameList()?.qualifiedName?.();
436
- if (qualNames) {
437
- methodInfo.throws = qualNames.map((q) => q.text);
438
- }
439
- }
440
-
441
- // Complexity
442
- const methodBody = methodDecl.methodBody();
443
- if (methodBody) {
444
- methodInfo.complexity = calculateComplexity(getText(methodBody));
445
- }
446
-
447
- classInfo.methods.push(methodInfo);
448
- }
449
-
450
- // Constructor
451
- const ctorDecl = memberDecl.constructorDeclaration?.();
452
- if (ctorDecl) {
453
- const ctorInfo: JavaMethodInfo = {
454
- name: ctorDecl.identifier()?.text || classInfo.name,
455
- returnType: classInfo.name,
456
- params: [],
457
- modifiers: [],
458
- line: getLine(ctorDecl),
459
- annotations: [],
460
- throws: [],
461
- complexity: 1,
462
- };
463
-
464
- const modCtxs = bodyDecl.modifier?.();
465
- if (modCtxs) {
466
- ctorInfo.modifiers = getModifiers(modCtxs);
467
- ctorInfo.annotations = getAnnotations(modCtxs);
468
- }
469
-
470
- const formalParams = ctorDecl.formalParameters()?.formalParameterList?.();
471
- if (formalParams) {
472
- const params = formalParams.formalParameter?.();
473
- if (params) {
474
- for (const param of params) {
475
- ctorInfo.params.push({
476
- name: param.variableDeclaratorId()?.text || "",
477
- type: param.typeType()?.text || "",
478
- });
479
- }
480
- }
481
- }
482
-
483
- const ctorBody = ctorDecl.block();
484
- if (ctorBody) {
485
- ctorInfo.complexity = calculateComplexity(getText(ctorBody));
486
- }
487
-
488
- classInfo.methods.push(ctorInfo);
489
- }
490
-
491
- // Field
492
- const fieldDecl = memberDecl.fieldDeclaration?.();
493
- if (fieldDecl) {
494
- const varDecls = fieldDecl.variableDeclarators()?.variableDeclarator?.();
495
- if (varDecls) {
496
- for (const varDecl of varDecls) {
497
- const fieldInfo: JavaFieldInfo = {
498
- name: varDecl.variableDeclaratorId()?.text || "",
499
- type: fieldDecl.typeType()?.text || "",
500
- modifiers: [],
501
- line: getLine(fieldDecl),
502
- annotations: [],
503
- };
504
-
505
- const modCtxs = bodyDecl.modifier?.();
506
- if (modCtxs) {
507
- fieldInfo.modifiers = getModifiers(modCtxs);
508
- fieldInfo.annotations = getAnnotations(modCtxs);
509
- }
510
-
511
- classInfo.fields.push(fieldInfo);
512
- }
513
- }
514
- }
515
- }
516
- }
517
-
518
- analysis.classes.push(classInfo);
519
- return null;
520
- },
521
-
522
- visitInterfaceDeclaration: (ctx) => {
523
- const interfaceInfo: JavaClassInfo = {
524
- name: ctx.identifier()?.text || "Unknown",
525
- type: "interface",
526
- modifiers: [],
527
- line: getLine(ctx),
528
- implements: [],
529
- annotations: [],
530
- methods: [],
531
- fields: [],
532
- innerClasses: [],
533
- };
534
-
535
- // Extends (interfaces extend other interfaces)
536
- const typeList = ctx.typeList();
537
- if (typeList && typeList.length > 0) {
538
- for (const tl of typeList) {
539
- const types = tl.typeType?.();
540
- if (types) {
541
- interfaceInfo.implements.push(...types.map((t: any) => t.text));
542
- }
543
- }
544
- }
545
-
546
- // Parse interface body
547
- const interfaceBody = ctx.interfaceBody();
548
- if (interfaceBody) {
549
- const bodyDecls = interfaceBody.interfaceBodyDeclaration();
550
- for (const bodyDecl of bodyDecls) {
551
- const memberDecl = bodyDecl.interfaceMemberDeclaration?.();
552
- if (!memberDecl) continue;
553
-
554
- const methodDecl = memberDecl.interfaceMethodDeclaration?.();
555
- if (methodDecl) {
556
- const commonBody = methodDecl.interfaceCommonBodyDeclaration?.();
557
- if (commonBody) {
558
- const methodInfo: JavaMethodInfo = {
559
- name: commonBody.identifier()?.text || "unknown",
560
- returnType: methodDecl.interfaceMethodModifier?.()?.map((m) => m.text).join(" ") || "void",
561
- params: [],
562
- modifiers: [],
563
- line: getLine(methodDecl),
564
- annotations: [],
565
- throws: [],
566
- complexity: 1,
567
- };
568
-
569
- interfaceInfo.methods.push(methodInfo);
570
- }
571
- }
572
- }
573
- }
574
-
575
- analysis.classes.push(interfaceInfo);
576
- return null;
577
- },
578
-
579
- visitEnumDeclaration: (ctx) => {
580
- const enumInfo: JavaClassInfo = {
581
- name: ctx.identifier()?.text || "Unknown",
582
- type: "enum",
583
- modifiers: [],
584
- line: getLine(ctx),
585
- implements: [],
586
- annotations: [],
587
- methods: [],
588
- fields: [],
589
- innerClasses: [],
590
- };
591
-
592
- // Enum constants as fields
593
- const enumConstants = ctx.enumConstants()?.enumConstant?.();
594
- if (enumConstants) {
595
- for (const ec of enumConstants) {
596
- enumInfo.fields.push({
597
- name: ec.identifier()?.text || "",
598
- type: enumInfo.name,
599
- modifiers: ["public", "static", "final"],
600
- line: getLine(ec),
601
- annotations: [],
602
- });
603
- }
604
- }
605
-
606
- analysis.classes.push(enumInfo);
607
- return null;
608
- },
609
- });
610
-
611
- visitor.visit(tree);
612
-
613
- // Calculate complexity stats
614
- const allMethods = analysis.classes.flatMap((c) => c.methods);
615
- if (allMethods.length > 0) {
616
- analysis.complexity.total = allMethods.reduce((sum, m) => sum + m.complexity, 0);
617
- analysis.complexity.average = Math.round(analysis.complexity.total / allMethods.length * 10) / 10;
618
- analysis.complexity.highest = allMethods.reduce(
619
- (max, m) => (m.complexity > max.value ? { name: m.name, value: m.complexity } : max),
620
- { name: "", value: 0 }
621
- );
622
- }
623
-
624
- // Quality issue detection
625
- analysis.issues.push(...detectNPERisks(content));
626
- analysis.issues.push(...detectExceptionAntiPatterns(content));
627
- analysis.issues.push(...detectCommentedOutCode(content));
628
- } catch (error) {
629
- // If parsing fails, try basic regex extraction
630
- const classMatch = content.match(/(?:public\s+)?(?:abstract\s+)?(?:class|interface|enum)\s+(\w+)/);
631
- if (classMatch) {
632
- analysis.classes.push({
633
- name: classMatch[1],
634
- type: content.includes("interface ") ? "interface" : content.includes("enum ") ? "enum" : "class",
635
- modifiers: [],
636
- line: 1,
637
- implements: [],
638
- annotations: [],
639
- methods: [],
640
- fields: [],
641
- innerClasses: [],
642
- });
643
- }
644
- }
645
-
646
- return analysis;
647
- }
648
-
649
- // Format analysis as text
650
- function formatJavaAnalysis(analysis: JavaFileAnalysis): string {
651
- const lines: string[] = [];
652
-
653
- lines.push(`=== ${path.basename(analysis.filepath)} ===`);
654
- lines.push("");
655
-
656
- if (analysis.package) {
657
- lines.push(`📦 Package: ${analysis.package}`);
658
- lines.push("");
659
- }
660
-
661
- if (analysis.imports.length > 0) {
662
- lines.push("📥 Imports:");
663
- // Group by prefix
664
- const grouped: Record<string, string[]> = {};
665
- for (const imp of analysis.imports) {
666
- const prefix = imp.split(".").slice(0, 2).join(".");
667
- if (!grouped[prefix]) grouped[prefix] = [];
668
- grouped[prefix].push(imp);
669
- }
670
- for (const [prefix, imps] of Object.entries(grouped)) {
671
- lines.push(` ${prefix}.* (${imps.length})`);
672
- }
673
- lines.push("");
674
- }
675
-
676
- for (const cls of analysis.classes) {
677
- const icon = cls.type === "interface" ? "📋" : cls.type === "enum" ? "🔢" : "🏛️";
678
- const mods = cls.modifiers.length > 0 ? cls.modifiers.join(" ") + " " : "";
679
- const ext = cls.extends ? ` extends ${cls.extends}` : "";
680
- const impl = cls.implements.length > 0 ? ` implements ${cls.implements.join(", ")}` : "";
681
- const annots = cls.annotations.length > 0 ? cls.annotations.map((a) => `@${a}`).join(" ") + " " : "";
682
-
683
- lines.push(`${icon} L${cls.line}: ${annots}${mods}${cls.type} ${cls.name}${ext}${impl}`);
684
-
685
- // Fields
686
- if (cls.fields.length > 0) {
687
- lines.push(" 필드:");
688
- for (const field of cls.fields) {
689
- const fMods = field.modifiers.join(" ");
690
- const fAnnots = field.annotations.length > 0 ? field.annotations.map((a) => `@${a}`).join(" ") + " " : "";
691
- lines.push(` ${fAnnots}${fMods} ${field.type} ${field.name}`);
692
- }
693
- }
694
-
695
- // Methods
696
- if (cls.methods.length > 0) {
697
- lines.push(" 메서드:");
698
- for (const method of cls.methods) {
699
- const mMods = method.modifiers.join(" ");
700
- const mAnnots = method.annotations.length > 0 ? method.annotations.map((a) => `@${a}`).join(" ") + " " : "";
701
- const params = method.params.map((p) => `${p.type} ${p.name}`).join(", ");
702
- const throws = method.throws.length > 0 ? ` throws ${method.throws.join(", ")}` : "";
703
- lines.push(` L${method.line}: ${mAnnots}${mMods} ${method.returnType} ${method.name}(${params})${throws}`);
704
- lines.push(` 복잡도: ${method.complexity}`);
705
- }
706
- }
707
- lines.push("");
708
- }
709
-
710
- lines.push("📊 복잡도:");
711
- lines.push(` 총합: ${analysis.complexity.total} | 평균: ${analysis.complexity.average}`);
712
- if (analysis.complexity.highest.name) {
713
- lines.push(` 최고: ${analysis.complexity.highest.name} (${analysis.complexity.highest.value})`);
714
- }
715
-
716
- if (analysis.issues.length > 0) {
717
- lines.push("");
718
- const severityIcon = { error: "🔴", warning: "🟡", info: "🔵" };
719
- lines.push(`⚠️ 이슈 (${analysis.issues.length}건):`);
720
- for (const issue of analysis.issues) {
721
- lines.push(` ${severityIcon[issue.severity]} L${issue.line}: ${issue.message}`);
722
- lines.push(` → ${issue.suggestion}`);
723
- }
724
- }
725
-
726
- return lines.join("\n");
727
- }
728
-
729
- // Java Analyze Tool
730
- export const javaAnalyzeTool: Tool = {
731
- name: "java_analyze",
732
- description: "Analyze Java source file using AST parser (Java 분석). Returns classes, methods, fields, annotations, complexity. Use when user asks: 'analyze java', 'java 분석', 'Spring 분석'.",
733
- parameters: {
734
- type: "object",
735
- required: ["filepath"],
736
- properties: {
737
- filepath: {
738
- type: "string",
739
- description: "Path to Java file (.java)",
740
- },
741
- format: {
742
- type: "string",
743
- description: "Output format: 'text' or 'json'",
744
- enum: ["text", "json"],
745
- },
746
- },
747
- },
748
- handler: async (args): Promise<ToolResult> => {
749
- try {
750
- const filepath = path.resolve(args.filepath as string);
751
- const format = (args.format as string) || "text";
752
-
753
- if (!fs.existsSync(filepath)) {
754
- return { success: false, content: "", error: `File not found: ${filepath}` };
755
- }
756
-
757
- if (!filepath.endsWith(".java")) {
758
- return { success: false, content: "", error: "Not a Java file (.java required)" };
759
- }
760
-
761
- const content = fs.readFileSync(filepath, "utf-8");
762
- const analysis = analyzeJavaFile(content, filepath);
763
-
764
- if (format === "json") {
765
- return { success: true, content: JSON.stringify(analysis, null, 2) };
766
- }
767
-
768
- return { success: true, content: formatJavaAnalysis(analysis) };
769
- } catch (error) {
770
- return { success: false, content: "", error: String(error) };
771
- }
772
- },
773
- };
774
-
775
- // Java Complexity Report Tool
776
- export const javaComplexityTool: Tool = {
777
- name: "java_complexity",
778
- description: "Calculate complexity for Java files (Java 복잡도 리포트). Use when user asks: 'java complexity', 'java 복잡도'.",
779
- parameters: {
780
- type: "object",
781
- required: ["pattern"],
782
- properties: {
783
- pattern: {
784
- type: "string",
785
- description: "Glob pattern (e.g., src/**/*.java)",
786
- },
787
- threshold: {
788
- type: "number",
789
- description: "Only show methods with complexity >= threshold (default: 5)",
790
- },
791
- },
792
- },
793
- handler: async (args): Promise<ToolResult> => {
794
- try {
795
- const { glob } = await import("glob");
796
- const pattern = args.pattern as string;
797
- const threshold = (args.threshold as number) || 5;
798
-
799
- const files = await glob(pattern, {
800
- ignore: ["**/node_modules/**", "**/target/**", "**/build/**"],
801
- });
802
-
803
- const allMethods: Array<{
804
- file: string;
805
- class: string;
806
- method: string;
807
- complexity: number;
808
- line: number;
809
- }> = [];
810
-
811
- for (const file of files) {
812
- if (!file.endsWith(".java")) continue;
813
- try {
814
- const content = fs.readFileSync(file, "utf-8");
815
- const analysis = analyzeJavaFile(content, file);
816
-
817
- for (const cls of analysis.classes) {
818
- for (const method of cls.methods) {
819
- allMethods.push({
820
- file: path.relative(process.cwd(), file),
821
- class: cls.name,
822
- method: method.name,
823
- complexity: method.complexity,
824
- line: method.line,
825
- });
826
- }
827
- }
828
- } catch {
829
- // Skip unparseable files
830
- }
831
- }
832
-
833
- // Sort by complexity
834
- allMethods.sort((a, b) => b.complexity - a.complexity);
835
-
836
- const high = allMethods.filter((m) => m.complexity >= threshold);
837
- const total = allMethods.reduce((sum, m) => sum + m.complexity, 0);
838
- const avg = allMethods.length > 0 ? Math.round(total / allMethods.length * 10) / 10 : 0;
839
-
840
- const lines: string[] = [];
841
- lines.push("=== Java 복잡도 리포트 ===");
842
- lines.push("");
843
- lines.push("📊 통계:");
844
- lines.push(` 총 메서드: ${allMethods.length}개`);
845
- lines.push(` 평균 복잡도: ${avg}`);
846
- lines.push(` 높은 복잡도 (>= ${threshold}): ${high.length}개`);
847
- lines.push("");
848
-
849
- if (high.length > 0) {
850
- lines.push(`⚠️ 복잡도 높은 메서드 (>= ${threshold}):`);
851
- for (const m of high.slice(0, 20)) {
852
- const bar = "█".repeat(Math.min(m.complexity, 20));
853
- lines.push(` ${m.complexity.toString().padStart(2)} ${bar} ${m.class}.${m.method}()`);
854
- lines.push(` ${m.file}:${m.line}`);
855
- }
856
- if (high.length > 20) {
857
- lines.push(` ... 외 ${high.length - 20}개`);
858
- }
859
- } else {
860
- lines.push(`✅ 복잡도 ${threshold} 이상인 메서드가 없습니다.`);
861
- }
862
-
863
- return { success: true, content: lines.join("\n") };
864
- } catch (error) {
865
- return { success: false, content: "", error: String(error) };
866
- }
867
- },
868
- };
869
-
870
- // Spring Pattern Check Tool
871
- export const springCheckTool: Tool = {
872
- name: "spring_check",
873
- description: "Check Spring framework patterns (Spring 패턴 검사). Finds controllers, services, repositories, configuration. Use when user asks: 'spring check', 'spring 분석', 'Spring 패턴'.",
874
- parameters: {
875
- type: "object",
876
- required: ["pattern"],
877
- properties: {
878
- pattern: {
879
- type: "string",
880
- description: "Glob pattern (e.g., src/**/*.java)",
881
- },
882
- },
883
- },
884
- handler: async (args): Promise<ToolResult> => {
885
- try {
886
- const { glob } = await import("glob");
887
- const pattern = args.pattern as string;
888
-
889
- const files = await glob(pattern, {
890
- ignore: ["**/node_modules/**", "**/target/**", "**/build/**"],
891
- });
892
-
893
- const springComponents = {
894
- controllers: [] as Array<{ file: string; class: string; mappings: string[] }>,
895
- services: [] as Array<{ file: string; class: string }>,
896
- repositories: [] as Array<{ file: string; class: string }>,
897
- components: [] as Array<{ file: string; class: string }>,
898
- configurations: [] as Array<{ file: string; class: string }>,
899
- entities: [] as Array<{ file: string; class: string }>,
900
- };
901
-
902
- for (const file of files) {
903
- if (!file.endsWith(".java")) continue;
904
- try {
905
- const content = fs.readFileSync(file, "utf-8");
906
- const analysis = analyzeJavaFile(content, file);
907
- const relativePath = path.relative(process.cwd(), file);
908
-
909
- for (const cls of analysis.classes) {
910
- const annotations = cls.annotations.map((a) => a.toLowerCase());
911
-
912
- // Controller
913
- if (annotations.some((a) => a.includes("controller") || a.includes("restcontroller"))) {
914
- const mappings: string[] = [];
915
- // Find request mappings in methods
916
- for (const method of cls.methods) {
917
- const methodAnnots = method.annotations.join(" ").toLowerCase();
918
- if (methodAnnots.includes("mapping")) {
919
- mappings.push(`${method.name}()`);
920
- }
921
- }
922
- springComponents.controllers.push({ file: relativePath, class: cls.name, mappings });
923
- }
924
-
925
- // Service
926
- if (annotations.some((a) => a.includes("service"))) {
927
- springComponents.services.push({ file: relativePath, class: cls.name });
928
- }
929
-
930
- // Repository
931
- if (annotations.some((a) => a.includes("repository"))) {
932
- springComponents.repositories.push({ file: relativePath, class: cls.name });
933
- }
934
-
935
- // Component
936
- if (annotations.some((a) => a === "component")) {
937
- springComponents.components.push({ file: relativePath, class: cls.name });
938
- }
939
-
940
- // Configuration
941
- if (annotations.some((a) => a.includes("configuration"))) {
942
- springComponents.configurations.push({ file: relativePath, class: cls.name });
943
- }
944
-
945
- // Entity
946
- if (annotations.some((a) => a.includes("entity") || a.includes("table"))) {
947
- springComponents.entities.push({ file: relativePath, class: cls.name });
948
- }
949
- }
950
- } catch {
951
- // Skip unparseable files
952
- }
953
- }
954
-
955
- const lines: string[] = [];
956
- lines.push("=== Spring 패턴 분석 ===");
957
- lines.push("");
958
-
959
- if (springComponents.controllers.length > 0) {
960
- lines.push(`🎮 Controllers (${springComponents.controllers.length}):`);
961
- for (const c of springComponents.controllers) {
962
- lines.push(` ${c.class}`);
963
- lines.push(` ${c.file}`);
964
- if (c.mappings.length > 0) {
965
- lines.push(` 엔드포인트: ${c.mappings.join(", ")}`);
966
- }
967
- }
968
- lines.push("");
969
- }
970
-
971
- if (springComponents.services.length > 0) {
972
- lines.push(`⚙️ Services (${springComponents.services.length}):`);
973
- for (const s of springComponents.services) {
974
- lines.push(` ${s.class} - ${s.file}`);
975
- }
976
- lines.push("");
977
- }
978
-
979
- if (springComponents.repositories.length > 0) {
980
- lines.push(`🗄️ Repositories (${springComponents.repositories.length}):`);
981
- for (const r of springComponents.repositories) {
982
- lines.push(` ${r.class} - ${r.file}`);
983
- }
984
- lines.push("");
985
- }
986
-
987
- if (springComponents.entities.length > 0) {
988
- lines.push(`📋 Entities (${springComponents.entities.length}):`);
989
- for (const e of springComponents.entities) {
990
- lines.push(` ${e.class} - ${e.file}`);
991
- }
992
- lines.push("");
993
- }
994
-
995
- if (springComponents.configurations.length > 0) {
996
- lines.push(`🔧 Configurations (${springComponents.configurations.length}):`);
997
- for (const c of springComponents.configurations) {
998
- lines.push(` ${c.class} - ${c.file}`);
999
- }
1000
- lines.push("");
1001
- }
1002
-
1003
- if (springComponents.components.length > 0) {
1004
- lines.push(`📦 Components (${springComponents.components.length}):`);
1005
- for (const c of springComponents.components) {
1006
- lines.push(` ${c.class} - ${c.file}`);
1007
- }
1008
- lines.push("");
1009
- }
1010
-
1011
- const total = Object.values(springComponents).reduce((sum, arr) => sum + arr.length, 0);
1012
- if (total === 0) {
1013
- lines.push("Spring 컴포넌트를 찾지 못했습니다.");
1014
- } else {
1015
- lines.push(`📊 총 ${total}개 Spring 컴포넌트 발견`);
1016
- }
1017
-
1018
- return { success: true, content: lines.join("\n") };
1019
- } catch (error) {
1020
- return { success: false, content: "", error: String(error) };
1021
- }
1022
- },
1023
- };
1024
-
1025
- // Export all Java tools
1026
- export const javaTools: Tool[] = [
1027
- javaAnalyzeTool,
1028
- javaComplexityTool,
1029
- springCheckTool,
1030
- ];