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.
- package/README.md +87 -3
- package/dist/core/llm/ollama.d.ts +2 -0
- package/dist/core/llm/ollama.d.ts.map +1 -1
- package/dist/core/llm/ollama.js +26 -0
- package/dist/core/llm/ollama.js.map +1 -1
- package/dist/core/tools/ast.d.ts +81 -0
- package/dist/core/tools/ast.d.ts.map +1 -0
- package/dist/core/tools/ast.js +700 -0
- package/dist/core/tools/ast.js.map +1 -0
- package/dist/core/tools/cache.d.ts +19 -0
- package/dist/core/tools/cache.d.ts.map +1 -0
- package/dist/core/tools/cache.js +497 -0
- package/dist/core/tools/cache.js.map +1 -0
- package/dist/core/tools/cssAnalysis.d.ts +3 -0
- package/dist/core/tools/cssAnalysis.d.ts.map +1 -0
- package/dist/core/tools/cssAnalysis.js +270 -0
- package/dist/core/tools/cssAnalysis.js.map +1 -0
- package/dist/core/tools/dependencyAnalysis.d.ts +3 -0
- package/dist/core/tools/dependencyAnalysis.d.ts.map +1 -0
- package/dist/core/tools/dependencyAnalysis.js +295 -0
- package/dist/core/tools/dependencyAnalysis.js.map +1 -0
- package/dist/core/tools/embeddings.d.ts +8 -0
- package/dist/core/tools/embeddings.d.ts.map +1 -0
- package/dist/core/tools/embeddings.js +631 -0
- package/dist/core/tools/embeddings.js.map +1 -0
- package/dist/core/tools/frontendAst.d.ts +6 -0
- package/dist/core/tools/frontendAst.d.ts.map +1 -0
- package/dist/core/tools/frontendAst.js +680 -0
- package/dist/core/tools/frontendAst.js.map +1 -0
- package/dist/core/tools/htmlAnalysis.d.ts +3 -0
- package/dist/core/tools/htmlAnalysis.d.ts.map +1 -0
- package/dist/core/tools/htmlAnalysis.js +398 -0
- package/dist/core/tools/htmlAnalysis.js.map +1 -0
- package/dist/core/tools/index.d.ts +13 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +27 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/javaAst.d.ts +6 -0
- package/dist/core/tools/javaAst.d.ts.map +1 -0
- package/dist/core/tools/javaAst.js +678 -0
- package/dist/core/tools/javaAst.js.map +1 -0
- package/dist/core/tools/memory.d.ts +11 -0
- package/dist/core/tools/memory.d.ts.map +1 -0
- package/dist/core/tools/memory.js +551 -0
- package/dist/core/tools/memory.js.map +1 -0
- package/dist/core/tools/mybatisAnalysis.d.ts +3 -0
- package/dist/core/tools/mybatisAnalysis.d.ts.map +1 -0
- package/dist/core/tools/mybatisAnalysis.js +251 -0
- package/dist/core/tools/mybatisAnalysis.js.map +1 -0
- package/dist/core/tools/openapiAnalysis.d.ts +3 -0
- package/dist/core/tools/openapiAnalysis.d.ts.map +1 -0
- package/dist/core/tools/openapiAnalysis.js +356 -0
- package/dist/core/tools/openapiAnalysis.js.map +1 -0
- package/dist/core/tools/pythonAnalysis.d.ts +3 -0
- package/dist/core/tools/pythonAnalysis.d.ts.map +1 -0
- package/dist/core/tools/pythonAnalysis.js +387 -0
- package/dist/core/tools/pythonAnalysis.js.map +1 -0
- package/dist/core/tools/sqlAnalysis.d.ts +3 -0
- package/dist/core/tools/sqlAnalysis.d.ts.map +1 -0
- package/dist/core/tools/sqlAnalysis.js +250 -0
- package/dist/core/tools/sqlAnalysis.js.map +1 -0
- package/package.json +2 -1
- package/src/core/llm/ollama.ts +30 -0
- package/src/core/tools/ast.ts +826 -0
- package/src/core/tools/cache.ts +570 -0
- package/src/core/tools/cssAnalysis.ts +324 -0
- package/src/core/tools/dependencyAnalysis.ts +363 -0
- package/src/core/tools/embeddings.ts +746 -0
- package/src/core/tools/frontendAst.ts +802 -0
- package/src/core/tools/htmlAnalysis.ts +466 -0
- package/src/core/tools/index.ts +27 -1
- package/src/core/tools/javaAst.ts +812 -0
- package/src/core/tools/memory.ts +655 -0
- package/src/core/tools/mybatisAnalysis.ts +322 -0
- package/src/core/tools/openapiAnalysis.ts +431 -0
- package/src/core/tools/pythonAnalysis.ts +477 -0
- package/src/core/tools/sqlAnalysis.ts +298 -0
- package/FINAL_SIMPLIFIED_SPEC.md +0 -456
- package/TODO.md +0 -193
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
// Calculate cyclomatic complexity
|
|
5
|
+
function calculateComplexity(node) {
|
|
6
|
+
let complexity = 1;
|
|
7
|
+
function visit(n) {
|
|
8
|
+
switch (n.kind) {
|
|
9
|
+
case ts.SyntaxKind.IfStatement:
|
|
10
|
+
case ts.SyntaxKind.ConditionalExpression: // ternary
|
|
11
|
+
case ts.SyntaxKind.ForStatement:
|
|
12
|
+
case ts.SyntaxKind.ForInStatement:
|
|
13
|
+
case ts.SyntaxKind.ForOfStatement:
|
|
14
|
+
case ts.SyntaxKind.WhileStatement:
|
|
15
|
+
case ts.SyntaxKind.DoStatement:
|
|
16
|
+
case ts.SyntaxKind.CatchClause:
|
|
17
|
+
case ts.SyntaxKind.CaseClause:
|
|
18
|
+
case ts.SyntaxKind.DefaultClause:
|
|
19
|
+
complexity++;
|
|
20
|
+
break;
|
|
21
|
+
case ts.SyntaxKind.BinaryExpression:
|
|
22
|
+
const binary = n;
|
|
23
|
+
if (binary.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
24
|
+
binary.operatorToken.kind === ts.SyntaxKind.BarBarToken ||
|
|
25
|
+
binary.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
26
|
+
complexity++;
|
|
27
|
+
}
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
ts.forEachChild(n, visit);
|
|
31
|
+
}
|
|
32
|
+
visit(node);
|
|
33
|
+
return complexity;
|
|
34
|
+
}
|
|
35
|
+
// Extract function calls from a node
|
|
36
|
+
function extractCalls(node, sourceFile) {
|
|
37
|
+
const calls = [];
|
|
38
|
+
function visit(n) {
|
|
39
|
+
if (ts.isCallExpression(n)) {
|
|
40
|
+
const expr = n.expression;
|
|
41
|
+
if (ts.isIdentifier(expr)) {
|
|
42
|
+
calls.push(expr.text);
|
|
43
|
+
}
|
|
44
|
+
else if (ts.isPropertyAccessExpression(expr)) {
|
|
45
|
+
calls.push(expr.name.text);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
ts.forEachChild(n, visit);
|
|
49
|
+
}
|
|
50
|
+
visit(node);
|
|
51
|
+
return [...new Set(calls)];
|
|
52
|
+
}
|
|
53
|
+
// Get type string from TypeNode
|
|
54
|
+
function getTypeString(typeNode, sourceFile) {
|
|
55
|
+
if (!typeNode)
|
|
56
|
+
return "any";
|
|
57
|
+
return typeNode.getText(sourceFile);
|
|
58
|
+
}
|
|
59
|
+
// Get modifiers as strings
|
|
60
|
+
function getModifiers(node) {
|
|
61
|
+
const modifiers = [];
|
|
62
|
+
const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
63
|
+
if (mods) {
|
|
64
|
+
for (const mod of mods) {
|
|
65
|
+
modifiers.push(ts.SyntaxKind[mod.kind].replace("Keyword", "").toLowerCase());
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return modifiers;
|
|
69
|
+
}
|
|
70
|
+
// Analyze TypeScript/JavaScript file
|
|
71
|
+
function analyzeTypeScript(content, filepath) {
|
|
72
|
+
const sourceFile = ts.createSourceFile(filepath, content, ts.ScriptTarget.Latest, true, filepath.endsWith(".tsx") || filepath.endsWith(".jsx")
|
|
73
|
+
? ts.ScriptKind.TSX
|
|
74
|
+
: filepath.endsWith(".ts")
|
|
75
|
+
? ts.ScriptKind.TS
|
|
76
|
+
: ts.ScriptKind.JS);
|
|
77
|
+
const analysis = {
|
|
78
|
+
filepath,
|
|
79
|
+
language: filepath.endsWith(".ts") || filepath.endsWith(".tsx") ? "typescript" : "javascript",
|
|
80
|
+
imports: [],
|
|
81
|
+
exports: [],
|
|
82
|
+
functions: [],
|
|
83
|
+
classes: [],
|
|
84
|
+
interfaces: [],
|
|
85
|
+
types: [],
|
|
86
|
+
variables: [],
|
|
87
|
+
complexity: { total: 0, average: 0, highest: { name: "", value: 0 } },
|
|
88
|
+
};
|
|
89
|
+
function visit(node) {
|
|
90
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
91
|
+
const lineNum = line + 1;
|
|
92
|
+
const modifiers = getModifiers(node);
|
|
93
|
+
const isExported = modifiers.includes("export");
|
|
94
|
+
// Import declarations
|
|
95
|
+
if (ts.isImportDeclaration(node)) {
|
|
96
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
97
|
+
if (ts.isStringLiteral(moduleSpecifier)) {
|
|
98
|
+
const names = [];
|
|
99
|
+
const importClause = node.importClause;
|
|
100
|
+
if (importClause) {
|
|
101
|
+
if (importClause.name) {
|
|
102
|
+
names.push(importClause.name.text);
|
|
103
|
+
}
|
|
104
|
+
if (importClause.namedBindings) {
|
|
105
|
+
if (ts.isNamedImports(importClause.namedBindings)) {
|
|
106
|
+
for (const element of importClause.namedBindings.elements) {
|
|
107
|
+
names.push(element.name.text);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (ts.isNamespaceImport(importClause.namedBindings)) {
|
|
111
|
+
names.push(`* as ${importClause.namedBindings.name.text}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
analysis.imports.push({ module: moduleSpecifier.text, names });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Export declarations
|
|
119
|
+
if (ts.isExportDeclaration(node)) {
|
|
120
|
+
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
121
|
+
for (const element of node.exportClause.elements) {
|
|
122
|
+
analysis.exports.push(element.name.text);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Function declarations
|
|
127
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
128
|
+
const funcInfo = {
|
|
129
|
+
name: node.name.text,
|
|
130
|
+
kind: "function",
|
|
131
|
+
line: lineNum,
|
|
132
|
+
params: node.parameters.map((p) => ({
|
|
133
|
+
name: ts.isIdentifier(p.name) ? p.name.text : p.name.getText(sourceFile),
|
|
134
|
+
type: getTypeString(p.type, sourceFile),
|
|
135
|
+
optional: !!p.questionToken,
|
|
136
|
+
})),
|
|
137
|
+
returnType: getTypeString(node.type, sourceFile),
|
|
138
|
+
async: modifiers.includes("async"),
|
|
139
|
+
exported: isExported,
|
|
140
|
+
complexity: calculateComplexity(node),
|
|
141
|
+
calls: extractCalls(node, sourceFile),
|
|
142
|
+
};
|
|
143
|
+
analysis.functions.push(funcInfo);
|
|
144
|
+
if (isExported)
|
|
145
|
+
analysis.exports.push(node.name.text);
|
|
146
|
+
}
|
|
147
|
+
// Arrow functions in variable declarations
|
|
148
|
+
if (ts.isVariableStatement(node)) {
|
|
149
|
+
const isVarExported = getModifiers(node).includes("export");
|
|
150
|
+
for (const decl of node.declarationList.declarations) {
|
|
151
|
+
if (ts.isIdentifier(decl.name)) {
|
|
152
|
+
const varName = decl.name.text;
|
|
153
|
+
if (decl.initializer && (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
|
|
154
|
+
const func = decl.initializer;
|
|
155
|
+
const funcInfo = {
|
|
156
|
+
name: varName,
|
|
157
|
+
kind: "arrow",
|
|
158
|
+
line: lineNum,
|
|
159
|
+
params: func.parameters.map((p) => ({
|
|
160
|
+
name: ts.isIdentifier(p.name) ? p.name.text : p.name.getText(sourceFile),
|
|
161
|
+
type: getTypeString(p.type, sourceFile),
|
|
162
|
+
optional: !!p.questionToken,
|
|
163
|
+
})),
|
|
164
|
+
returnType: getTypeString(func.type, sourceFile),
|
|
165
|
+
async: !!(ts.canHaveModifiers(func) && ts.getModifiers(func)?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)),
|
|
166
|
+
exported: isVarExported,
|
|
167
|
+
complexity: calculateComplexity(func),
|
|
168
|
+
calls: extractCalls(func, sourceFile),
|
|
169
|
+
};
|
|
170
|
+
analysis.functions.push(funcInfo);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Regular variable
|
|
174
|
+
analysis.variables.push({
|
|
175
|
+
name: varName,
|
|
176
|
+
line: lineNum,
|
|
177
|
+
type: decl.type ? getTypeString(decl.type, sourceFile) : "inferred",
|
|
178
|
+
exported: isVarExported,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (isVarExported)
|
|
182
|
+
analysis.exports.push(varName);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Class declarations
|
|
187
|
+
if (ts.isClassDeclaration(node) && node.name) {
|
|
188
|
+
const classInfo = {
|
|
189
|
+
name: node.name.text,
|
|
190
|
+
line: lineNum,
|
|
191
|
+
exported: isExported,
|
|
192
|
+
implements: [],
|
|
193
|
+
methods: [],
|
|
194
|
+
properties: [],
|
|
195
|
+
};
|
|
196
|
+
// Heritage (extends, implements)
|
|
197
|
+
if (node.heritageClauses) {
|
|
198
|
+
for (const clause of node.heritageClauses) {
|
|
199
|
+
if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
|
|
200
|
+
classInfo.extends = clause.types[0]?.expression.getText(sourceFile);
|
|
201
|
+
}
|
|
202
|
+
else if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
|
|
203
|
+
classInfo.implements = clause.types.map((t) => t.expression.getText(sourceFile));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Class members
|
|
208
|
+
for (const member of node.members) {
|
|
209
|
+
const memberMods = getModifiers(member);
|
|
210
|
+
const visibility = memberMods.includes("private")
|
|
211
|
+
? "private"
|
|
212
|
+
: memberMods.includes("protected")
|
|
213
|
+
? "protected"
|
|
214
|
+
: "public";
|
|
215
|
+
if (ts.isMethodDeclaration(member) && member.name) {
|
|
216
|
+
const methodName = member.name.getText(sourceFile);
|
|
217
|
+
const methodInfo = {
|
|
218
|
+
name: methodName,
|
|
219
|
+
kind: "method",
|
|
220
|
+
line: sourceFile.getLineAndCharacterOfPosition(member.getStart()).line + 1,
|
|
221
|
+
params: member.parameters.map((p) => ({
|
|
222
|
+
name: ts.isIdentifier(p.name) ? p.name.text : p.name.getText(sourceFile),
|
|
223
|
+
type: getTypeString(p.type, sourceFile),
|
|
224
|
+
optional: !!p.questionToken,
|
|
225
|
+
})),
|
|
226
|
+
returnType: getTypeString(member.type, sourceFile),
|
|
227
|
+
async: memberMods.includes("async"),
|
|
228
|
+
exported: false,
|
|
229
|
+
className: node.name.text,
|
|
230
|
+
complexity: calculateComplexity(member),
|
|
231
|
+
calls: extractCalls(member, sourceFile),
|
|
232
|
+
};
|
|
233
|
+
classInfo.methods.push(methodInfo);
|
|
234
|
+
}
|
|
235
|
+
else if (ts.isConstructorDeclaration(member)) {
|
|
236
|
+
const ctorInfo = {
|
|
237
|
+
name: "constructor",
|
|
238
|
+
kind: "constructor",
|
|
239
|
+
line: sourceFile.getLineAndCharacterOfPosition(member.getStart()).line + 1,
|
|
240
|
+
params: member.parameters.map((p) => ({
|
|
241
|
+
name: ts.isIdentifier(p.name) ? p.name.text : p.name.getText(sourceFile),
|
|
242
|
+
type: getTypeString(p.type, sourceFile),
|
|
243
|
+
optional: !!p.questionToken,
|
|
244
|
+
})),
|
|
245
|
+
returnType: node.name.text,
|
|
246
|
+
async: false,
|
|
247
|
+
exported: false,
|
|
248
|
+
className: node.name.text,
|
|
249
|
+
complexity: calculateComplexity(member),
|
|
250
|
+
calls: extractCalls(member, sourceFile),
|
|
251
|
+
};
|
|
252
|
+
classInfo.methods.push(ctorInfo);
|
|
253
|
+
}
|
|
254
|
+
else if (ts.isPropertyDeclaration(member) && member.name) {
|
|
255
|
+
classInfo.properties.push({
|
|
256
|
+
name: member.name.getText(sourceFile),
|
|
257
|
+
type: getTypeString(member.type, sourceFile),
|
|
258
|
+
visibility,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
analysis.classes.push(classInfo);
|
|
263
|
+
if (isExported)
|
|
264
|
+
analysis.exports.push(node.name.text);
|
|
265
|
+
}
|
|
266
|
+
// Interface declarations
|
|
267
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
268
|
+
const props = node.members
|
|
269
|
+
.filter((m) => ts.isPropertySignature(m) && m.name)
|
|
270
|
+
.map((m) => m.name.getText(sourceFile));
|
|
271
|
+
analysis.interfaces.push({
|
|
272
|
+
name: node.name.text,
|
|
273
|
+
line: lineNum,
|
|
274
|
+
properties: props,
|
|
275
|
+
});
|
|
276
|
+
if (isExported)
|
|
277
|
+
analysis.exports.push(node.name.text);
|
|
278
|
+
}
|
|
279
|
+
// Type alias declarations
|
|
280
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
281
|
+
analysis.types.push({
|
|
282
|
+
name: node.name.text,
|
|
283
|
+
line: lineNum,
|
|
284
|
+
});
|
|
285
|
+
if (isExported)
|
|
286
|
+
analysis.exports.push(node.name.text);
|
|
287
|
+
}
|
|
288
|
+
ts.forEachChild(node, visit);
|
|
289
|
+
}
|
|
290
|
+
visit(sourceFile);
|
|
291
|
+
// Calculate complexity stats
|
|
292
|
+
const allFuncs = [...analysis.functions, ...analysis.classes.flatMap((c) => c.methods)];
|
|
293
|
+
if (allFuncs.length > 0) {
|
|
294
|
+
const complexities = allFuncs.map((f) => ({ name: f.name, value: f.complexity }));
|
|
295
|
+
analysis.complexity.total = complexities.reduce((sum, c) => sum + c.value, 0);
|
|
296
|
+
analysis.complexity.average = Math.round(analysis.complexity.total / complexities.length * 10) / 10;
|
|
297
|
+
analysis.complexity.highest = complexities.reduce((max, c) => (c.value > max.value ? c : max), { name: "", value: 0 });
|
|
298
|
+
}
|
|
299
|
+
// Deduplicate exports
|
|
300
|
+
analysis.exports = [...new Set(analysis.exports)];
|
|
301
|
+
return analysis;
|
|
302
|
+
}
|
|
303
|
+
// Format analysis as readable text
|
|
304
|
+
function formatAnalysis(analysis) {
|
|
305
|
+
const lines = [];
|
|
306
|
+
lines.push(`=== ${path.basename(analysis.filepath)} (${analysis.language}) ===`);
|
|
307
|
+
lines.push("");
|
|
308
|
+
// Imports
|
|
309
|
+
if (analysis.imports.length > 0) {
|
|
310
|
+
lines.push("📥 Imports:");
|
|
311
|
+
for (const imp of analysis.imports) {
|
|
312
|
+
const names = imp.names.length > 0 ? ` { ${imp.names.join(", ")} }` : "";
|
|
313
|
+
lines.push(` ${imp.module}${names}`);
|
|
314
|
+
}
|
|
315
|
+
lines.push("");
|
|
316
|
+
}
|
|
317
|
+
// Exports
|
|
318
|
+
if (analysis.exports.length > 0) {
|
|
319
|
+
lines.push("📤 Exports:");
|
|
320
|
+
lines.push(` ${analysis.exports.join(", ")}`);
|
|
321
|
+
lines.push("");
|
|
322
|
+
}
|
|
323
|
+
// Classes
|
|
324
|
+
if (analysis.classes.length > 0) {
|
|
325
|
+
lines.push("🏛️ Classes:");
|
|
326
|
+
for (const cls of analysis.classes) {
|
|
327
|
+
const ext = cls.extends ? ` extends ${cls.extends}` : "";
|
|
328
|
+
const impl = cls.implements.length > 0 ? ` implements ${cls.implements.join(", ")}` : "";
|
|
329
|
+
lines.push(` L${cls.line}: ${cls.exported ? "export " : ""}class ${cls.name}${ext}${impl}`);
|
|
330
|
+
for (const prop of cls.properties) {
|
|
331
|
+
lines.push(` • ${prop.visibility} ${prop.name}: ${prop.type}`);
|
|
332
|
+
}
|
|
333
|
+
for (const method of cls.methods) {
|
|
334
|
+
const params = method.params.map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ");
|
|
335
|
+
const async = method.async ? "async " : "";
|
|
336
|
+
lines.push(` → ${async}${method.name}(${params}): ${method.returnType} [복잡도: ${method.complexity}]`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
lines.push("");
|
|
340
|
+
}
|
|
341
|
+
// Functions
|
|
342
|
+
if (analysis.functions.length > 0) {
|
|
343
|
+
lines.push("⚡ Functions:");
|
|
344
|
+
for (const func of analysis.functions) {
|
|
345
|
+
const params = func.params.map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ");
|
|
346
|
+
const async = func.async ? "async " : "";
|
|
347
|
+
const exp = func.exported ? "export " : "";
|
|
348
|
+
const arrow = func.kind === "arrow" ? "=> " : "";
|
|
349
|
+
lines.push(` L${func.line}: ${exp}${async}${func.name}(${params}) ${arrow}: ${func.returnType}`);
|
|
350
|
+
lines.push(` 복잡도: ${func.complexity} | 호출: ${func.calls.slice(0, 5).join(", ") || "(없음)"}`);
|
|
351
|
+
}
|
|
352
|
+
lines.push("");
|
|
353
|
+
}
|
|
354
|
+
// Interfaces
|
|
355
|
+
if (analysis.interfaces.length > 0) {
|
|
356
|
+
lines.push("📋 Interfaces:");
|
|
357
|
+
for (const iface of analysis.interfaces) {
|
|
358
|
+
lines.push(` L${iface.line}: interface ${iface.name} { ${iface.properties.slice(0, 5).join(", ")}${iface.properties.length > 5 ? ", ..." : ""} }`);
|
|
359
|
+
}
|
|
360
|
+
lines.push("");
|
|
361
|
+
}
|
|
362
|
+
// Types
|
|
363
|
+
if (analysis.types.length > 0) {
|
|
364
|
+
lines.push("🏷️ Types:");
|
|
365
|
+
for (const t of analysis.types) {
|
|
366
|
+
lines.push(` L${t.line}: type ${t.name}`);
|
|
367
|
+
}
|
|
368
|
+
lines.push("");
|
|
369
|
+
}
|
|
370
|
+
// Variables
|
|
371
|
+
if (analysis.variables.length > 0) {
|
|
372
|
+
lines.push("📦 Variables:");
|
|
373
|
+
for (const v of analysis.variables) {
|
|
374
|
+
const exp = v.exported ? "export " : "";
|
|
375
|
+
lines.push(` L${v.line}: ${exp}${v.name}: ${v.type}`);
|
|
376
|
+
}
|
|
377
|
+
lines.push("");
|
|
378
|
+
}
|
|
379
|
+
// Complexity summary
|
|
380
|
+
lines.push("📊 Complexity:");
|
|
381
|
+
lines.push(` 총합: ${analysis.complexity.total} | 평균: ${analysis.complexity.average}`);
|
|
382
|
+
if (analysis.complexity.highest.name) {
|
|
383
|
+
lines.push(` 최고: ${analysis.complexity.highest.name} (${analysis.complexity.highest.value})`);
|
|
384
|
+
}
|
|
385
|
+
return lines.join("\n");
|
|
386
|
+
}
|
|
387
|
+
// AST Analyze Tool
|
|
388
|
+
export const astAnalyzeTool = {
|
|
389
|
+
name: "ast_analyze",
|
|
390
|
+
description: "Deep code analysis using AST parser (AST 분석, 심층 분석). Returns functions, classes, imports, exports, complexity. More accurate than outline. Use when user asks: 'analyze code', 'deep analysis', 'complexity', 'AST', '심층 분석', '복잡도'.",
|
|
391
|
+
parameters: {
|
|
392
|
+
type: "object",
|
|
393
|
+
required: ["filepath"],
|
|
394
|
+
properties: {
|
|
395
|
+
filepath: {
|
|
396
|
+
type: "string",
|
|
397
|
+
description: "Path to TypeScript/JavaScript file",
|
|
398
|
+
},
|
|
399
|
+
format: {
|
|
400
|
+
type: "string",
|
|
401
|
+
description: "Output format: 'text' (readable) or 'json' (structured)",
|
|
402
|
+
enum: ["text", "json"],
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
handler: async (args) => {
|
|
407
|
+
try {
|
|
408
|
+
const filepath = path.resolve(args.filepath);
|
|
409
|
+
const format = args.format || "text";
|
|
410
|
+
if (!fs.existsSync(filepath)) {
|
|
411
|
+
return { success: false, content: "", error: `File not found: ${filepath}` };
|
|
412
|
+
}
|
|
413
|
+
const ext = path.extname(filepath).toLowerCase();
|
|
414
|
+
if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
|
|
415
|
+
return {
|
|
416
|
+
success: false,
|
|
417
|
+
content: "",
|
|
418
|
+
error: `Unsupported file type: ${ext}. Supported: .ts, .tsx, .js, .jsx, .mjs, .cjs`,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
422
|
+
const analysis = analyzeTypeScript(content, filepath);
|
|
423
|
+
if (format === "json") {
|
|
424
|
+
return { success: true, content: JSON.stringify(analysis, null, 2) };
|
|
425
|
+
}
|
|
426
|
+
return { success: true, content: formatAnalysis(analysis) };
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
return { success: false, content: "", error: String(error) };
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
// Find function calls (call graph)
|
|
434
|
+
export const getCallGraphTool = {
|
|
435
|
+
name: "get_call_graph",
|
|
436
|
+
description: "Find what functions a file/function calls (호출 그래프, 의존성). Shows function call relationships. Use when user asks: 'what does it call', 'call graph', 'dependencies', '호출 관계', '의존성'.",
|
|
437
|
+
parameters: {
|
|
438
|
+
type: "object",
|
|
439
|
+
required: ["filepath"],
|
|
440
|
+
properties: {
|
|
441
|
+
filepath: {
|
|
442
|
+
type: "string",
|
|
443
|
+
description: "Path to TypeScript/JavaScript file",
|
|
444
|
+
},
|
|
445
|
+
functionName: {
|
|
446
|
+
type: "string",
|
|
447
|
+
description: "Optional: specific function name to analyze",
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
handler: async (args) => {
|
|
452
|
+
try {
|
|
453
|
+
const filepath = path.resolve(args.filepath);
|
|
454
|
+
const targetFunc = args.functionName;
|
|
455
|
+
if (!fs.existsSync(filepath)) {
|
|
456
|
+
return { success: false, content: "", error: `File not found: ${filepath}` };
|
|
457
|
+
}
|
|
458
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
459
|
+
const analysis = analyzeTypeScript(content, filepath);
|
|
460
|
+
const lines = [];
|
|
461
|
+
lines.push(`=== 호출 그래프: ${path.basename(filepath)} ===`);
|
|
462
|
+
lines.push("");
|
|
463
|
+
const allFuncs = [...analysis.functions, ...analysis.classes.flatMap((c) => c.methods)];
|
|
464
|
+
if (targetFunc) {
|
|
465
|
+
const func = allFuncs.find((f) => f.name === targetFunc);
|
|
466
|
+
if (!func) {
|
|
467
|
+
return { success: false, content: "", error: `Function not found: ${targetFunc}` };
|
|
468
|
+
}
|
|
469
|
+
lines.push(`📍 ${func.name}() 호출:`);
|
|
470
|
+
if (func.calls.length > 0) {
|
|
471
|
+
func.calls.forEach((c) => lines.push(` → ${c}()`));
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
lines.push(" (다른 함수 호출 없음)");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
for (const func of allFuncs) {
|
|
479
|
+
const prefix = func.className ? `${func.className}.` : "";
|
|
480
|
+
lines.push(`📍 ${prefix}${func.name}():`);
|
|
481
|
+
if (func.calls.length > 0) {
|
|
482
|
+
func.calls.forEach((c) => lines.push(` → ${c}()`));
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
lines.push(" (다른 함수 호출 없음)");
|
|
486
|
+
}
|
|
487
|
+
lines.push("");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { success: true, content: lines.join("\n") };
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
return { success: false, content: "", error: String(error) };
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
// Find references (where a symbol is used)
|
|
498
|
+
export const findReferencesTool = {
|
|
499
|
+
name: "find_symbol_usage",
|
|
500
|
+
description: "Find where a symbol (function, class, variable) is used in files (심볼 사용처 찾기). Use when user asks: 'where is X used', 'find usages', 'references', '어디서 사용', '참조'.",
|
|
501
|
+
parameters: {
|
|
502
|
+
type: "object",
|
|
503
|
+
required: ["symbol", "pattern"],
|
|
504
|
+
properties: {
|
|
505
|
+
symbol: {
|
|
506
|
+
type: "string",
|
|
507
|
+
description: "Symbol name to find (function, class, variable)",
|
|
508
|
+
},
|
|
509
|
+
pattern: {
|
|
510
|
+
type: "string",
|
|
511
|
+
description: "Glob pattern for files to search (e.g., src/**/*.ts)",
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
handler: async (args) => {
|
|
516
|
+
try {
|
|
517
|
+
const { glob } = await import("glob");
|
|
518
|
+
const symbol = args.symbol;
|
|
519
|
+
const pattern = args.pattern;
|
|
520
|
+
const files = await glob(pattern, {
|
|
521
|
+
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
|
|
522
|
+
});
|
|
523
|
+
const results = [];
|
|
524
|
+
for (const file of files) {
|
|
525
|
+
const ext = path.extname(file).toLowerCase();
|
|
526
|
+
if (![".ts", ".tsx", ".js", ".jsx"].includes(ext))
|
|
527
|
+
continue;
|
|
528
|
+
try {
|
|
529
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
530
|
+
const sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
|
|
531
|
+
function visit(node) {
|
|
532
|
+
// Check identifiers
|
|
533
|
+
if (ts.isIdentifier(node) && node.text === symbol) {
|
|
534
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
535
|
+
const lineText = content.split("\n")[line].trim();
|
|
536
|
+
// Determine usage type
|
|
537
|
+
let type = "reference";
|
|
538
|
+
const parent = node.parent;
|
|
539
|
+
if (ts.isFunctionDeclaration(parent) || ts.isMethodDeclaration(parent)) {
|
|
540
|
+
type = "definition";
|
|
541
|
+
}
|
|
542
|
+
else if (ts.isClassDeclaration(parent)) {
|
|
543
|
+
type = "definition";
|
|
544
|
+
}
|
|
545
|
+
else if (ts.isVariableDeclaration(parent) && parent.name === node) {
|
|
546
|
+
type = "definition";
|
|
547
|
+
}
|
|
548
|
+
else if (ts.isCallExpression(parent) && parent.expression === node) {
|
|
549
|
+
type = "call";
|
|
550
|
+
}
|
|
551
|
+
else if (ts.isImportSpecifier(parent) || ts.isImportClause(parent)) {
|
|
552
|
+
type = "import";
|
|
553
|
+
}
|
|
554
|
+
results.push({
|
|
555
|
+
file: path.relative(process.cwd(), file),
|
|
556
|
+
line: line + 1,
|
|
557
|
+
context: lineText.slice(0, 80),
|
|
558
|
+
type,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
ts.forEachChild(node, visit);
|
|
562
|
+
}
|
|
563
|
+
visit(sourceFile);
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
// Skip files that can't be parsed
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (results.length === 0) {
|
|
570
|
+
return { success: true, content: `"${symbol}" 사용처를 찾을 수 없습니다.` };
|
|
571
|
+
}
|
|
572
|
+
const lines = [];
|
|
573
|
+
lines.push(`=== "${symbol}" 사용처 (${results.length}개) ===`);
|
|
574
|
+
lines.push("");
|
|
575
|
+
// Group by type
|
|
576
|
+
const grouped = {
|
|
577
|
+
definition: results.filter((r) => r.type === "definition"),
|
|
578
|
+
import: results.filter((r) => r.type === "import"),
|
|
579
|
+
call: results.filter((r) => r.type === "call"),
|
|
580
|
+
reference: results.filter((r) => r.type === "reference"),
|
|
581
|
+
};
|
|
582
|
+
if (grouped.definition.length > 0) {
|
|
583
|
+
lines.push("📍 정의:");
|
|
584
|
+
grouped.definition.forEach((r) => lines.push(` ${r.file}:${r.line} - ${r.context}`));
|
|
585
|
+
lines.push("");
|
|
586
|
+
}
|
|
587
|
+
if (grouped.import.length > 0) {
|
|
588
|
+
lines.push("📥 Import:");
|
|
589
|
+
grouped.import.forEach((r) => lines.push(` ${r.file}:${r.line}`));
|
|
590
|
+
lines.push("");
|
|
591
|
+
}
|
|
592
|
+
if (grouped.call.length > 0) {
|
|
593
|
+
lines.push("⚡ 호출:");
|
|
594
|
+
grouped.call.forEach((r) => lines.push(` ${r.file}:${r.line} - ${r.context}`));
|
|
595
|
+
lines.push("");
|
|
596
|
+
}
|
|
597
|
+
if (grouped.reference.length > 0) {
|
|
598
|
+
lines.push("🔗 참조:");
|
|
599
|
+
grouped.reference.slice(0, 20).forEach((r) => lines.push(` ${r.file}:${r.line} - ${r.context}`));
|
|
600
|
+
if (grouped.reference.length > 20) {
|
|
601
|
+
lines.push(` ... 외 ${grouped.reference.length - 20}개`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return { success: true, content: lines.join("\n") };
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
return { success: false, content: "", error: String(error) };
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
};
|
|
611
|
+
// Get complexity report
|
|
612
|
+
export const complexityReportTool = {
|
|
613
|
+
name: "complexity_report",
|
|
614
|
+
description: "Calculate code complexity for files (복잡도 분석 리포트). Shows cyclomatic complexity for all functions. Use when user asks: 'complexity', 'code quality', '복잡도', '코드 품질'.",
|
|
615
|
+
parameters: {
|
|
616
|
+
type: "object",
|
|
617
|
+
required: ["pattern"],
|
|
618
|
+
properties: {
|
|
619
|
+
pattern: {
|
|
620
|
+
type: "string",
|
|
621
|
+
description: "Glob pattern (e.g., src/**/*.ts)",
|
|
622
|
+
},
|
|
623
|
+
threshold: {
|
|
624
|
+
type: "number",
|
|
625
|
+
description: "Only show functions with complexity >= threshold (default: 5)",
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
handler: async (args) => {
|
|
630
|
+
try {
|
|
631
|
+
const { glob } = await import("glob");
|
|
632
|
+
const pattern = args.pattern;
|
|
633
|
+
const threshold = args.threshold || 5;
|
|
634
|
+
const files = await glob(pattern, {
|
|
635
|
+
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
|
|
636
|
+
});
|
|
637
|
+
const allFuncs = [];
|
|
638
|
+
for (const file of files) {
|
|
639
|
+
const ext = path.extname(file).toLowerCase();
|
|
640
|
+
if (![".ts", ".tsx", ".js", ".jsx"].includes(ext))
|
|
641
|
+
continue;
|
|
642
|
+
try {
|
|
643
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
644
|
+
const analysis = analyzeTypeScript(content, file);
|
|
645
|
+
const funcs = [...analysis.functions, ...analysis.classes.flatMap((c) => c.methods)];
|
|
646
|
+
for (const func of funcs) {
|
|
647
|
+
allFuncs.push({
|
|
648
|
+
file: path.relative(process.cwd(), file),
|
|
649
|
+
name: func.className ? `${func.className}.${func.name}` : func.name,
|
|
650
|
+
complexity: func.complexity,
|
|
651
|
+
line: func.line,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
// Skip unparseable files
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
// Sort by complexity descending
|
|
660
|
+
allFuncs.sort((a, b) => b.complexity - a.complexity);
|
|
661
|
+
const high = allFuncs.filter((f) => f.complexity >= threshold);
|
|
662
|
+
const total = allFuncs.reduce((sum, f) => sum + f.complexity, 0);
|
|
663
|
+
const avg = allFuncs.length > 0 ? Math.round(total / allFuncs.length * 10) / 10 : 0;
|
|
664
|
+
const lines = [];
|
|
665
|
+
lines.push(`=== 복잡도 리포트 ===`);
|
|
666
|
+
lines.push("");
|
|
667
|
+
lines.push(`📊 통계:`);
|
|
668
|
+
lines.push(` 총 함수: ${allFuncs.length}개`);
|
|
669
|
+
lines.push(` 평균 복잡도: ${avg}`);
|
|
670
|
+
lines.push(` 높은 복잡도 (>= ${threshold}): ${high.length}개`);
|
|
671
|
+
lines.push("");
|
|
672
|
+
if (high.length > 0) {
|
|
673
|
+
lines.push(`⚠️ 복잡도 높은 함수 (>= ${threshold}):`);
|
|
674
|
+
for (const func of high.slice(0, 20)) {
|
|
675
|
+
const bar = "█".repeat(Math.min(func.complexity, 20));
|
|
676
|
+
lines.push(` ${func.complexity.toString().padStart(2)} ${bar} ${func.name}`);
|
|
677
|
+
lines.push(` ${func.file}:${func.line}`);
|
|
678
|
+
}
|
|
679
|
+
if (high.length > 20) {
|
|
680
|
+
lines.push(` ... 외 ${high.length - 20}개`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
lines.push(`✅ 복잡도 ${threshold} 이상인 함수가 없습니다.`);
|
|
685
|
+
}
|
|
686
|
+
return { success: true, content: lines.join("\n") };
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
return { success: false, content: "", error: String(error) };
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
// Export all AST tools
|
|
694
|
+
export const astTools = [
|
|
695
|
+
astAnalyzeTool,
|
|
696
|
+
getCallGraphTool,
|
|
697
|
+
findReferencesTool,
|
|
698
|
+
complexityReportTool,
|
|
699
|
+
];
|
|
700
|
+
//# sourceMappingURL=ast.js.map
|