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.
- package/README.md +203 -1
- package/data/2026-03-04_20-54.json +181 -0
- package/data/2026-03-04_20-56.json +181 -0
- package/data/apex-rulesets/egov.yaml +469 -0
- package/data/apex-rulesets/modernize.yaml +687 -0
- package/data/apex-rulesets/quality.yaml +1677 -0
- package/data/apex-rulesets/rule-schema.yaml +587 -0
- package/data/apex-rulesets/secure.yaml +1688 -0
- package/data/apex-rulesets/spring.yaml +455 -0
- package/data/apex-rulesets/sql-format.yaml +99 -0
- package/data/apex-rulesets/sql-oracle.yaml +281 -0
- package/data/apex-rulesets/sql.yaml +1660 -0
- package/dist/cli/headless.d.ts.map +1 -1
- package/dist/cli/headless.js +32 -10
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/index.js +31 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/core/agent.d.ts +3 -3
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +203 -384
- package/dist/core/agent.js.map +1 -1
- package/dist/core/commands.d.ts +2 -1
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +61 -9
- package/dist/core/commands.js.map +1 -1
- package/dist/core/config.d.ts +14 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +41 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/conversation.d.ts +2 -2
- package/dist/core/conversation.d.ts.map +1 -1
- package/dist/core/conversation.js.map +1 -1
- package/dist/core/intentRouter.d.ts +43 -0
- package/dist/core/intentRouter.d.ts.map +1 -0
- package/dist/core/intentRouter.js +804 -0
- package/dist/core/intentRouter.js.map +1 -0
- package/dist/core/llm/anthropic.d.ts +24 -0
- package/dist/core/llm/anthropic.d.ts.map +1 -0
- package/dist/core/llm/anthropic.js +226 -0
- package/dist/core/llm/anthropic.js.map +1 -0
- package/dist/core/llm/ollama.d.ts +5 -14
- package/dist/core/llm/ollama.d.ts.map +1 -1
- package/dist/core/llm/ollama.js +3 -0
- package/dist/core/llm/ollama.js.map +1 -1
- package/dist/core/llm/types.d.ts +22 -0
- package/dist/core/llm/types.d.ts.map +1 -0
- package/dist/core/llm/types.js +2 -0
- package/dist/core/llm/types.js.map +1 -0
- package/dist/core/mcp/client.d.ts +6 -0
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +16 -0
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/init.d.ts +12 -0
- package/dist/core/mcp/init.d.ts.map +1 -0
- package/dist/core/mcp/init.js +55 -0
- package/dist/core/mcp/init.js.map +1 -0
- package/dist/core/mcp/logger.d.ts +14 -0
- package/dist/core/mcp/logger.d.ts.map +1 -0
- package/dist/core/mcp/logger.js +50 -0
- package/dist/core/mcp/logger.js.map +1 -0
- package/dist/core/tools/analyzePatterns.d.ts +3 -0
- package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
- package/dist/core/tools/analyzePatterns.js +293 -0
- package/dist/core/tools/analyzePatterns.js.map +1 -0
- package/dist/core/tools/apexPaths.d.ts +14 -0
- package/dist/core/tools/apexPaths.d.ts.map +1 -0
- package/dist/core/tools/apexPaths.js +54 -0
- package/dist/core/tools/apexPaths.js.map +1 -0
- package/dist/core/tools/apexUtils.d.ts +36 -0
- package/dist/core/tools/apexUtils.d.ts.map +1 -0
- package/dist/core/tools/apexUtils.js +83 -0
- package/dist/core/tools/apexUtils.js.map +1 -0
- package/dist/core/tools/explainIssue.d.ts +3 -0
- package/dist/core/tools/explainIssue.d.ts.map +1 -0
- package/dist/core/tools/explainIssue.js +181 -0
- package/dist/core/tools/explainIssue.js.map +1 -0
- package/dist/core/tools/fixGen.d.ts +3 -0
- package/dist/core/tools/fixGen.d.ts.map +1 -0
- package/dist/core/tools/fixGen.js +338 -0
- package/dist/core/tools/fixGen.js.map +1 -0
- package/dist/core/tools/generateImprovements.d.ts +21 -0
- package/dist/core/tools/generateImprovements.d.ts.map +1 -0
- package/dist/core/tools/generateImprovements.js +602 -0
- package/dist/core/tools/generateImprovements.js.map +1 -0
- package/dist/core/tools/generateReport.d.ts +3 -0
- package/dist/core/tools/generateReport.d.ts.map +1 -0
- package/dist/core/tools/generateReport.js +315 -0
- package/dist/core/tools/generateReport.js.map +1 -0
- package/dist/core/tools/index.d.ts +7 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +62 -23
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/recommendProfile.d.ts +3 -0
- package/dist/core/tools/recommendProfile.d.ts.map +1 -0
- package/dist/core/tools/recommendProfile.js +334 -0
- package/dist/core/tools/recommendProfile.js.map +1 -0
- package/dist/core/tools/ruleGen.d.ts +3 -0
- package/dist/core/tools/ruleGen.d.ts.map +1 -0
- package/dist/core/tools/ruleGen.js +1103 -0
- package/dist/core/tools/ruleGen.js.map +1 -0
- package/dist/core/tools/standards.d.ts.map +1 -1
- package/dist/core/tools/standards.js +7 -3
- package/dist/core/tools/standards.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +86 -35
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +1 -3
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +146 -5
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/MessageList.d.ts +3 -1
- package/dist/ui/components/MessageList.d.ts.map +1 -1
- package/dist/ui/components/MessageList.js +13 -7
- package/dist/ui/components/MessageList.js.map +1 -1
- package/dist/ui/components/StatusBar.d.ts +1 -1
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +3 -2
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/ToolStatus.d.ts +3 -1
- package/dist/ui/components/ToolStatus.d.ts.map +1 -1
- package/dist/ui/components/ToolStatus.js +19 -4
- package/dist/ui/components/ToolStatus.js.map +1 -1
- package/package.json +7 -1
- package/demo.gif +0 -0
- package/demo.tape +0 -53
- package/screenshot.png +0 -0
- package/src/cli/banner.ts +0 -38
- package/src/cli/headless.ts +0 -63
- package/src/cli/index.ts +0 -57
- package/src/core/agent.ts +0 -711
- package/src/core/commands.ts +0 -118
- package/src/core/config.ts +0 -98
- package/src/core/conversation.ts +0 -235
- package/src/core/llm/ollama.ts +0 -351
- package/src/core/mcp/client.ts +0 -143
- package/src/core/tools/analyzeAll.ts +0 -482
- package/src/core/tools/ast.ts +0 -826
- package/src/core/tools/builtIn.ts +0 -221
- package/src/core/tools/cache.ts +0 -570
- package/src/core/tools/cssAnalysis.ts +0 -324
- package/src/core/tools/dependencyAnalysis.ts +0 -363
- package/src/core/tools/embeddings.ts +0 -746
- package/src/core/tools/frontendAst.ts +0 -802
- package/src/core/tools/htmlAnalysis.ts +0 -466
- package/src/core/tools/index.ts +0 -160
- package/src/core/tools/javaAst.ts +0 -1030
- package/src/core/tools/javaQuality.integration.test.ts +0 -537
- package/src/core/tools/memory.ts +0 -655
- package/src/core/tools/mybatisAnalysis.ts +0 -322
- package/src/core/tools/openapiAnalysis.ts +0 -431
- package/src/core/tools/pythonAnalysis.ts +0 -477
- package/src/core/tools/sqlAnalysis.ts +0 -298
- package/src/core/tools/standards.test.ts +0 -186
- package/src/core/tools/standards.ts +0 -889
- package/src/core/tools/types.ts +0 -38
- package/src/ui/App.tsx +0 -334
- package/src/ui/components/InputBox.tsx +0 -37
- package/src/ui/components/MessageList.tsx +0 -80
- package/src/ui/components/StatusBar.tsx +0 -36
- package/src/ui/components/ToolStatus.tsx +0 -38
- 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
|
-
];
|