activo 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +87 -3
  2. package/dist/core/llm/ollama.d.ts +2 -0
  3. package/dist/core/llm/ollama.d.ts.map +1 -1
  4. package/dist/core/llm/ollama.js +26 -0
  5. package/dist/core/llm/ollama.js.map +1 -1
  6. package/dist/core/tools/ast.d.ts +81 -0
  7. package/dist/core/tools/ast.d.ts.map +1 -0
  8. package/dist/core/tools/ast.js +700 -0
  9. package/dist/core/tools/ast.js.map +1 -0
  10. package/dist/core/tools/cache.d.ts +19 -0
  11. package/dist/core/tools/cache.d.ts.map +1 -0
  12. package/dist/core/tools/cache.js +497 -0
  13. package/dist/core/tools/cache.js.map +1 -0
  14. package/dist/core/tools/cssAnalysis.d.ts +3 -0
  15. package/dist/core/tools/cssAnalysis.d.ts.map +1 -0
  16. package/dist/core/tools/cssAnalysis.js +270 -0
  17. package/dist/core/tools/cssAnalysis.js.map +1 -0
  18. package/dist/core/tools/dependencyAnalysis.d.ts +3 -0
  19. package/dist/core/tools/dependencyAnalysis.d.ts.map +1 -0
  20. package/dist/core/tools/dependencyAnalysis.js +295 -0
  21. package/dist/core/tools/dependencyAnalysis.js.map +1 -0
  22. package/dist/core/tools/embeddings.d.ts +8 -0
  23. package/dist/core/tools/embeddings.d.ts.map +1 -0
  24. package/dist/core/tools/embeddings.js +631 -0
  25. package/dist/core/tools/embeddings.js.map +1 -0
  26. package/dist/core/tools/frontendAst.d.ts +6 -0
  27. package/dist/core/tools/frontendAst.d.ts.map +1 -0
  28. package/dist/core/tools/frontendAst.js +680 -0
  29. package/dist/core/tools/frontendAst.js.map +1 -0
  30. package/dist/core/tools/htmlAnalysis.d.ts +3 -0
  31. package/dist/core/tools/htmlAnalysis.d.ts.map +1 -0
  32. package/dist/core/tools/htmlAnalysis.js +398 -0
  33. package/dist/core/tools/htmlAnalysis.js.map +1 -0
  34. package/dist/core/tools/index.d.ts +13 -0
  35. package/dist/core/tools/index.d.ts.map +1 -1
  36. package/dist/core/tools/index.js +27 -1
  37. package/dist/core/tools/index.js.map +1 -1
  38. package/dist/core/tools/javaAst.d.ts +6 -0
  39. package/dist/core/tools/javaAst.d.ts.map +1 -0
  40. package/dist/core/tools/javaAst.js +678 -0
  41. package/dist/core/tools/javaAst.js.map +1 -0
  42. package/dist/core/tools/memory.d.ts +11 -0
  43. package/dist/core/tools/memory.d.ts.map +1 -0
  44. package/dist/core/tools/memory.js +551 -0
  45. package/dist/core/tools/memory.js.map +1 -0
  46. package/dist/core/tools/mybatisAnalysis.d.ts +3 -0
  47. package/dist/core/tools/mybatisAnalysis.d.ts.map +1 -0
  48. package/dist/core/tools/mybatisAnalysis.js +251 -0
  49. package/dist/core/tools/mybatisAnalysis.js.map +1 -0
  50. package/dist/core/tools/openapiAnalysis.d.ts +3 -0
  51. package/dist/core/tools/openapiAnalysis.d.ts.map +1 -0
  52. package/dist/core/tools/openapiAnalysis.js +356 -0
  53. package/dist/core/tools/openapiAnalysis.js.map +1 -0
  54. package/dist/core/tools/pythonAnalysis.d.ts +3 -0
  55. package/dist/core/tools/pythonAnalysis.d.ts.map +1 -0
  56. package/dist/core/tools/pythonAnalysis.js +387 -0
  57. package/dist/core/tools/pythonAnalysis.js.map +1 -0
  58. package/dist/core/tools/sqlAnalysis.d.ts +3 -0
  59. package/dist/core/tools/sqlAnalysis.d.ts.map +1 -0
  60. package/dist/core/tools/sqlAnalysis.js +250 -0
  61. package/dist/core/tools/sqlAnalysis.js.map +1 -0
  62. package/package.json +2 -1
  63. package/src/core/llm/ollama.ts +30 -0
  64. package/src/core/tools/ast.ts +826 -0
  65. package/src/core/tools/cache.ts +570 -0
  66. package/src/core/tools/cssAnalysis.ts +324 -0
  67. package/src/core/tools/dependencyAnalysis.ts +363 -0
  68. package/src/core/tools/embeddings.ts +746 -0
  69. package/src/core/tools/frontendAst.ts +802 -0
  70. package/src/core/tools/htmlAnalysis.ts +466 -0
  71. package/src/core/tools/index.ts +27 -1
  72. package/src/core/tools/javaAst.ts +812 -0
  73. package/src/core/tools/memory.ts +655 -0
  74. package/src/core/tools/mybatisAnalysis.ts +322 -0
  75. package/src/core/tools/openapiAnalysis.ts +431 -0
  76. package/src/core/tools/pythonAnalysis.ts +477 -0
  77. package/src/core/tools/sqlAnalysis.ts +298 -0
  78. package/FINAL_SIMPLIFIED_SPEC.md +0 -456
  79. package/TODO.md +0 -193
@@ -0,0 +1,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