activo 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/README.md +203 -1
  2. package/data/2026-03-04_20-54.json +181 -0
  3. package/data/2026-03-04_20-56.json +181 -0
  4. package/data/apex-rulesets/egov.yaml +469 -0
  5. package/data/apex-rulesets/modernize.yaml +687 -0
  6. package/data/apex-rulesets/quality.yaml +1677 -0
  7. package/data/apex-rulesets/rule-schema.yaml +587 -0
  8. package/data/apex-rulesets/secure.yaml +1688 -0
  9. package/data/apex-rulesets/spring.yaml +455 -0
  10. package/data/apex-rulesets/sql-format.yaml +99 -0
  11. package/data/apex-rulesets/sql-oracle.yaml +281 -0
  12. package/data/apex-rulesets/sql.yaml +1660 -0
  13. package/dist/cli/headless.d.ts.map +1 -1
  14. package/dist/cli/headless.js +32 -10
  15. package/dist/cli/headless.js.map +1 -1
  16. package/dist/cli/index.js +31 -3
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/core/agent.d.ts +3 -3
  19. package/dist/core/agent.d.ts.map +1 -1
  20. package/dist/core/agent.js +203 -384
  21. package/dist/core/agent.js.map +1 -1
  22. package/dist/core/commands.d.ts +2 -1
  23. package/dist/core/commands.d.ts.map +1 -1
  24. package/dist/core/commands.js +61 -9
  25. package/dist/core/commands.js.map +1 -1
  26. package/dist/core/config.d.ts +14 -0
  27. package/dist/core/config.d.ts.map +1 -1
  28. package/dist/core/config.js +41 -4
  29. package/dist/core/config.js.map +1 -1
  30. package/dist/core/conversation.d.ts +2 -2
  31. package/dist/core/conversation.d.ts.map +1 -1
  32. package/dist/core/conversation.js.map +1 -1
  33. package/dist/core/intentRouter.d.ts +43 -0
  34. package/dist/core/intentRouter.d.ts.map +1 -0
  35. package/dist/core/intentRouter.js +804 -0
  36. package/dist/core/intentRouter.js.map +1 -0
  37. package/dist/core/llm/anthropic.d.ts +24 -0
  38. package/dist/core/llm/anthropic.d.ts.map +1 -0
  39. package/dist/core/llm/anthropic.js +226 -0
  40. package/dist/core/llm/anthropic.js.map +1 -0
  41. package/dist/core/llm/ollama.d.ts +5 -14
  42. package/dist/core/llm/ollama.d.ts.map +1 -1
  43. package/dist/core/llm/ollama.js +3 -0
  44. package/dist/core/llm/ollama.js.map +1 -1
  45. package/dist/core/llm/types.d.ts +22 -0
  46. package/dist/core/llm/types.d.ts.map +1 -0
  47. package/dist/core/llm/types.js +2 -0
  48. package/dist/core/llm/types.js.map +1 -0
  49. package/dist/core/mcp/client.d.ts +6 -0
  50. package/dist/core/mcp/client.d.ts.map +1 -1
  51. package/dist/core/mcp/client.js +16 -0
  52. package/dist/core/mcp/client.js.map +1 -1
  53. package/dist/core/mcp/init.d.ts +12 -0
  54. package/dist/core/mcp/init.d.ts.map +1 -0
  55. package/dist/core/mcp/init.js +55 -0
  56. package/dist/core/mcp/init.js.map +1 -0
  57. package/dist/core/mcp/logger.d.ts +14 -0
  58. package/dist/core/mcp/logger.d.ts.map +1 -0
  59. package/dist/core/mcp/logger.js +50 -0
  60. package/dist/core/mcp/logger.js.map +1 -0
  61. package/dist/core/tools/analyzePatterns.d.ts +3 -0
  62. package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
  63. package/dist/core/tools/analyzePatterns.js +293 -0
  64. package/dist/core/tools/analyzePatterns.js.map +1 -0
  65. package/dist/core/tools/apexPaths.d.ts +14 -0
  66. package/dist/core/tools/apexPaths.d.ts.map +1 -0
  67. package/dist/core/tools/apexPaths.js +54 -0
  68. package/dist/core/tools/apexPaths.js.map +1 -0
  69. package/dist/core/tools/apexUtils.d.ts +36 -0
  70. package/dist/core/tools/apexUtils.d.ts.map +1 -0
  71. package/dist/core/tools/apexUtils.js +83 -0
  72. package/dist/core/tools/apexUtils.js.map +1 -0
  73. package/dist/core/tools/explainIssue.d.ts +3 -0
  74. package/dist/core/tools/explainIssue.d.ts.map +1 -0
  75. package/dist/core/tools/explainIssue.js +181 -0
  76. package/dist/core/tools/explainIssue.js.map +1 -0
  77. package/dist/core/tools/fixGen.d.ts +3 -0
  78. package/dist/core/tools/fixGen.d.ts.map +1 -0
  79. package/dist/core/tools/fixGen.js +338 -0
  80. package/dist/core/tools/fixGen.js.map +1 -0
  81. package/dist/core/tools/generateImprovements.d.ts +21 -0
  82. package/dist/core/tools/generateImprovements.d.ts.map +1 -0
  83. package/dist/core/tools/generateImprovements.js +602 -0
  84. package/dist/core/tools/generateImprovements.js.map +1 -0
  85. package/dist/core/tools/generateReport.d.ts +3 -0
  86. package/dist/core/tools/generateReport.d.ts.map +1 -0
  87. package/dist/core/tools/generateReport.js +315 -0
  88. package/dist/core/tools/generateReport.js.map +1 -0
  89. package/dist/core/tools/index.d.ts +7 -0
  90. package/dist/core/tools/index.d.ts.map +1 -1
  91. package/dist/core/tools/index.js +62 -23
  92. package/dist/core/tools/index.js.map +1 -1
  93. package/dist/core/tools/recommendProfile.d.ts +3 -0
  94. package/dist/core/tools/recommendProfile.d.ts.map +1 -0
  95. package/dist/core/tools/recommendProfile.js +334 -0
  96. package/dist/core/tools/recommendProfile.js.map +1 -0
  97. package/dist/core/tools/ruleGen.d.ts +3 -0
  98. package/dist/core/tools/ruleGen.d.ts.map +1 -0
  99. package/dist/core/tools/ruleGen.js +1103 -0
  100. package/dist/core/tools/ruleGen.js.map +1 -0
  101. package/dist/core/tools/standards.d.ts.map +1 -1
  102. package/dist/core/tools/standards.js +7 -3
  103. package/dist/core/tools/standards.js.map +1 -1
  104. package/dist/ui/App.d.ts.map +1 -1
  105. package/dist/ui/App.js +86 -35
  106. package/dist/ui/App.js.map +1 -1
  107. package/dist/ui/components/InputBox.d.ts +1 -3
  108. package/dist/ui/components/InputBox.d.ts.map +1 -1
  109. package/dist/ui/components/InputBox.js +146 -5
  110. package/dist/ui/components/InputBox.js.map +1 -1
  111. package/dist/ui/components/MessageList.d.ts +3 -1
  112. package/dist/ui/components/MessageList.d.ts.map +1 -1
  113. package/dist/ui/components/MessageList.js +13 -7
  114. package/dist/ui/components/MessageList.js.map +1 -1
  115. package/dist/ui/components/StatusBar.d.ts +1 -1
  116. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  117. package/dist/ui/components/StatusBar.js +3 -2
  118. package/dist/ui/components/StatusBar.js.map +1 -1
  119. package/dist/ui/components/ToolStatus.d.ts +3 -1
  120. package/dist/ui/components/ToolStatus.d.ts.map +1 -1
  121. package/dist/ui/components/ToolStatus.js +19 -4
  122. package/dist/ui/components/ToolStatus.js.map +1 -1
  123. package/package.json +7 -1
  124. package/demo.gif +0 -0
  125. package/demo.tape +0 -53
  126. package/screenshot.png +0 -0
  127. package/src/cli/banner.ts +0 -38
  128. package/src/cli/headless.ts +0 -63
  129. package/src/cli/index.ts +0 -57
  130. package/src/core/agent.ts +0 -711
  131. package/src/core/commands.ts +0 -118
  132. package/src/core/config.ts +0 -98
  133. package/src/core/conversation.ts +0 -235
  134. package/src/core/llm/ollama.ts +0 -351
  135. package/src/core/mcp/client.ts +0 -143
  136. package/src/core/tools/analyzeAll.ts +0 -482
  137. package/src/core/tools/ast.ts +0 -826
  138. package/src/core/tools/builtIn.ts +0 -221
  139. package/src/core/tools/cache.ts +0 -570
  140. package/src/core/tools/cssAnalysis.ts +0 -324
  141. package/src/core/tools/dependencyAnalysis.ts +0 -363
  142. package/src/core/tools/embeddings.ts +0 -746
  143. package/src/core/tools/frontendAst.ts +0 -802
  144. package/src/core/tools/htmlAnalysis.ts +0 -466
  145. package/src/core/tools/index.ts +0 -160
  146. package/src/core/tools/javaAst.ts +0 -1030
  147. package/src/core/tools/javaQuality.integration.test.ts +0 -537
  148. package/src/core/tools/memory.ts +0 -655
  149. package/src/core/tools/mybatisAnalysis.ts +0 -322
  150. package/src/core/tools/openapiAnalysis.ts +0 -431
  151. package/src/core/tools/pythonAnalysis.ts +0 -477
  152. package/src/core/tools/sqlAnalysis.ts +0 -298
  153. package/src/core/tools/standards.test.ts +0 -186
  154. package/src/core/tools/standards.ts +0 -889
  155. package/src/core/tools/types.ts +0 -38
  156. package/src/ui/App.tsx +0 -334
  157. package/src/ui/components/InputBox.tsx +0 -37
  158. package/src/ui/components/MessageList.tsx +0 -80
  159. package/src/ui/components/StatusBar.tsx +0 -36
  160. package/src/ui/components/ToolStatus.tsx +0 -38
  161. package/tsconfig.json +0 -21
@@ -1,477 +0,0 @@
1
- import { Tool, ToolResult } from "./types.js";
2
- import * as fs from "fs";
3
- import * as path from "path";
4
-
5
- interface PythonFunction {
6
- name: string;
7
- line: number;
8
- args: string[];
9
- decorators: string[];
10
- isAsync: boolean;
11
- docstring: boolean;
12
- issues: string[];
13
- }
14
-
15
- interface PythonClass {
16
- name: string;
17
- line: number;
18
- bases: string[];
19
- decorators: string[];
20
- methods: string[];
21
- docstring: boolean;
22
- issues: string[];
23
- }
24
-
25
- interface PythonImport {
26
- module: string;
27
- names: string[];
28
- line: number;
29
- issues: string[];
30
- }
31
-
32
- interface PythonAnalysisResult {
33
- file: string;
34
- framework?: "django" | "flask" | "fastapi" | "general";
35
- imports: PythonImport[];
36
- classes: PythonClass[];
37
- functions: PythonFunction[];
38
- issues: string[];
39
- summary: {
40
- totalClasses: number;
41
- totalFunctions: number;
42
- withDocstring: number;
43
- withIssues: number;
44
- frameworkPatterns: string[];
45
- };
46
- }
47
-
48
- // Django 패턴
49
- const djangoPatterns = {
50
- views: /class\s+\w+View|def\s+\w+\(request/,
51
- models: /class\s+\w+\(.*models\.Model\)/,
52
- forms: /class\s+\w+\(.*forms\.(Form|ModelForm)\)/,
53
- serializers: /class\s+\w+\(.*serializers\.\w+Serializer\)/,
54
- admin: /@admin\.register|admin\.site\.register/,
55
- urls: /urlpatterns\s*=|path\(|re_path\(/,
56
- };
57
-
58
- // Flask 패턴
59
- const flaskPatterns = {
60
- routes: /@app\.route|@blueprint\.route/,
61
- views: /def\s+\w+\(\):|def\s+\w+\(.*\):/,
62
- blueprints: /Blueprint\(/,
63
- };
64
-
65
- // FastAPI 패턴
66
- const fastapiPatterns = {
67
- routes: /@app\.(get|post|put|patch|delete)|@router\.(get|post|put|patch|delete)/,
68
- dependencies: /Depends\(/,
69
- pydantic: /class\s+\w+\(.*BaseModel\)/,
70
- };
71
-
72
- // PEP8/일반 이슈 패턴
73
- const codeIssuePatterns = [
74
- { pattern: /^\s*import \*/, message: "와일드카드 import 사용 - 명시적 import 권장" },
75
- { pattern: /except:$/m, message: "bare except 사용 - 구체적 예외 타입 권장" },
76
- { pattern: /except Exception:$/m, message: "broad except 사용 - 구체적 예외 타입 권장" },
77
- { pattern: /eval\(/, message: "eval() 사용 - 보안 위험" },
78
- { pattern: /exec\(/, message: "exec() 사용 - 보안 위험" },
79
- { pattern: /pickle\.load/, message: "pickle.load() - 신뢰할 수 없는 데이터 역직렬화 위험" },
80
- { pattern: /subprocess\.call\(.*shell\s*=\s*True/, message: "shell=True - 명령 인젝션 위험" },
81
- { pattern: /os\.system\(/, message: "os.system() - subprocess 모듈 권장" },
82
- { pattern: /print\(/, message: "print() 사용 - logging 모듈 권장" },
83
- { pattern: /TODO|FIXME|XXX|HACK/, message: "TODO/FIXME 주석 발견" },
84
- { pattern: /password\s*=\s*['"][^'"]+['"]/, message: "하드코딩된 비밀번호 의심" },
85
- { pattern: /api_key\s*=\s*['"][^'"]+['"]/, message: "하드코딩된 API 키 의심" },
86
- { pattern: /secret\s*=\s*['"][^'"]+['"]/, message: "하드코딩된 시크릿 의심" },
87
- ];
88
-
89
- // Python 파일 분석
90
- function analyzePythonFile(filePath: string): PythonAnalysisResult {
91
- const content = fs.readFileSync(filePath, "utf-8");
92
- const lines = content.split("\n");
93
-
94
- const imports: PythonImport[] = [];
95
- const classes: PythonClass[] = [];
96
- const functions: PythonFunction[] = [];
97
- const fileIssues: string[] = [];
98
- const frameworkPatterns: string[] = [];
99
-
100
- // 프레임워크 감지
101
- let framework: PythonAnalysisResult["framework"] = "general";
102
- if (/from django|import django/.test(content)) {
103
- framework = "django";
104
- } else if (/from flask|import flask/.test(content)) {
105
- framework = "flask";
106
- } else if (/from fastapi|import fastapi/.test(content)) {
107
- framework = "fastapi";
108
- }
109
-
110
- // Django 패턴 검사
111
- if (framework === "django") {
112
- Object.entries(djangoPatterns).forEach(([name, pattern]) => {
113
- if (pattern.test(content)) {
114
- frameworkPatterns.push(`django:${name}`);
115
- }
116
- });
117
- }
118
-
119
- // Flask 패턴 검사
120
- if (framework === "flask") {
121
- Object.entries(flaskPatterns).forEach(([name, pattern]) => {
122
- if (pattern.test(content)) {
123
- frameworkPatterns.push(`flask:${name}`);
124
- }
125
- });
126
- }
127
-
128
- // FastAPI 패턴 검사
129
- if (framework === "fastapi") {
130
- Object.entries(fastapiPatterns).forEach(([name, pattern]) => {
131
- if (pattern.test(content)) {
132
- frameworkPatterns.push(`fastapi:${name}`);
133
- }
134
- });
135
- }
136
-
137
- // Import 추출
138
- const importRegex = /^(?:from\s+([\w.]+)\s+)?import\s+(.+)$/gm;
139
- let match;
140
- while ((match = importRegex.exec(content)) !== null) {
141
- const module = match[1] || match[2].split(",")[0].trim().split(" ")[0];
142
- const namesStr = match[1] ? match[2] : "";
143
- const names = namesStr
144
- ? namesStr.split(",").map((n) => n.trim().split(" as ")[0].trim())
145
- : [];
146
-
147
- const importIssues: string[] = [];
148
-
149
- // 와일드카드 import 검사
150
- if (names.includes("*")) {
151
- importIssues.push("와일드카드 import - 명시적 import 권장");
152
- }
153
-
154
- // deprecated 모듈 검사
155
- const deprecatedModules = ["imp", "optparse", "formatter", "mimetools", "rfc822"];
156
- if (deprecatedModules.includes(module)) {
157
- importIssues.push(`deprecated 모듈: ${module}`);
158
- }
159
-
160
- // 라인 번호 찾기
161
- const importIndex = match.index;
162
- let lineNum = 1;
163
- for (let i = 0; i < importIndex && i < content.length; i++) {
164
- if (content[i] === "\n") lineNum++;
165
- }
166
-
167
- imports.push({
168
- module,
169
- names,
170
- line: lineNum,
171
- issues: importIssues,
172
- });
173
- }
174
-
175
- // 클래스 추출
176
- const classRegex = /^(\s*)(@[\w.]+(?:\([^)]*\))?\s*\n)*\s*class\s+(\w+)(?:\(([^)]*)\))?:/gm;
177
- while ((match = classRegex.exec(content)) !== null) {
178
- const indent = match[1]?.length || 0;
179
- const className = match[3];
180
- const basesStr = match[4] || "";
181
- const bases = basesStr.split(",").map((b) => b.trim()).filter(Boolean);
182
-
183
- const classIssues: string[] = [];
184
-
185
- // 라인 번호
186
- const classIndex = match.index;
187
- let lineNum = 1;
188
- for (let i = 0; i < classIndex && i < content.length; i++) {
189
- if (content[i] === "\n") lineNum++;
190
- }
191
-
192
- // 데코레이터 추출
193
- const decorators: string[] = [];
194
- const preClassContent = content.substring(0, classIndex);
195
- const preLines = preClassContent.split("\n").slice(-10);
196
- for (const line of preLines) {
197
- const decMatch = line.match(/^\s*@([\w.]+)/);
198
- if (decMatch) {
199
- decorators.push(decMatch[1]);
200
- }
201
- }
202
-
203
- // docstring 검사
204
- const classBody = content.substring(classIndex);
205
- const hasDocstring = /class\s+\w+[^:]*:\s*\n\s*(['"]){3}/.test(classBody.substring(0, 200));
206
-
207
- if (!hasDocstring) {
208
- classIssues.push("docstring 없음");
209
- }
210
-
211
- // 메서드 추출 (간단한 버전)
212
- const methods: string[] = [];
213
- const methodRegex = /def\s+(\w+)\s*\(/g;
214
- const classEnd = content.indexOf("\nclass ", classIndex + 1);
215
- const classContent = classEnd > 0
216
- ? content.substring(classIndex, classEnd)
217
- : content.substring(classIndex);
218
-
219
- let methodMatch;
220
- while ((methodMatch = methodRegex.exec(classContent)) !== null) {
221
- methods.push(methodMatch[1]);
222
- }
223
-
224
- // Django Model 이슈 검사
225
- if (bases.some((b) => b.includes("Model"))) {
226
- if (!classContent.includes("class Meta:")) {
227
- classIssues.push("Django Model에 Meta 클래스 없음");
228
- }
229
- if (!classContent.includes("def __str__")) {
230
- classIssues.push("Django Model에 __str__ 메서드 없음");
231
- }
232
- }
233
-
234
- classes.push({
235
- name: className,
236
- line: lineNum,
237
- bases,
238
- decorators,
239
- methods: methods.slice(0, 20), // 최대 20개
240
- docstring: hasDocstring,
241
- issues: classIssues,
242
- });
243
- }
244
-
245
- // 함수 추출 (최상위 레벨)
246
- const funcRegex = /^(@[\w.]+(?:\([^)]*\))?\s*\n)*\s*(async\s+)?def\s+(\w+)\s*\(([^)]*)\)/gm;
247
- while ((match = funcRegex.exec(content)) !== null) {
248
- // 클래스 내부 함수는 제외 (indent로 판단)
249
- const preContent = content.substring(0, match.index);
250
- const lastNewline = preContent.lastIndexOf("\n");
251
- const lineStart = preContent.substring(lastNewline + 1);
252
- if (lineStart.match(/^\s{4,}/)) continue; // 4칸 이상 들여쓰기면 메서드로 간주
253
-
254
- const isAsync = !!match[2];
255
- const funcName = match[3];
256
- const argsStr = match[4];
257
- const args = argsStr
258
- .split(",")
259
- .map((a) => a.trim().split(":")[0].split("=")[0].trim())
260
- .filter(Boolean);
261
-
262
- const funcIssues: string[] = [];
263
-
264
- // 라인 번호
265
- const funcIndex = match.index;
266
- let lineNum = 1;
267
- for (let i = 0; i < funcIndex && i < content.length; i++) {
268
- if (content[i] === "\n") lineNum++;
269
- }
270
-
271
- // 데코레이터 추출
272
- const decorators: string[] = [];
273
- const preFuncContent = content.substring(Math.max(0, funcIndex - 500), funcIndex);
274
- const preFuncLines = preFuncContent.split("\n").slice(-10);
275
- for (const line of preFuncLines) {
276
- const decMatch = line.match(/^\s*@([\w.]+)/);
277
- if (decMatch) {
278
- decorators.push(decMatch[1]);
279
- }
280
- }
281
-
282
- // docstring 검사
283
- const funcBody = content.substring(funcIndex);
284
- const hasDocstring = /def\s+\w+[^:]*:\s*\n\s*(['"]){3}/.test(funcBody.substring(0, 200));
285
-
286
- if (!hasDocstring && !funcName.startsWith("_")) {
287
- funcIssues.push("docstring 없음");
288
- }
289
-
290
- // Flask/FastAPI 라우트 검사
291
- if (decorators.some((d) => d.includes("route") || d.includes("get") || d.includes("post"))) {
292
- if (!hasDocstring) {
293
- funcIssues.push("API 엔드포인트에 docstring 없음");
294
- }
295
- }
296
-
297
- // Django view 검사
298
- if (args.includes("request") && framework === "django") {
299
- if (!decorators.some((d) => d.includes("login_required") || d.includes("permission"))) {
300
- funcIssues.push("Django view에 인증 데코레이터 없음 (확인 필요)");
301
- }
302
- }
303
-
304
- functions.push({
305
- name: funcName,
306
- line: lineNum,
307
- args,
308
- decorators,
309
- isAsync,
310
- docstring: hasDocstring,
311
- issues: funcIssues,
312
- });
313
- }
314
-
315
- // 파일 레벨 이슈 검사
316
- codeIssuePatterns.forEach(({ pattern, message }) => {
317
- if (pattern.test(content)) {
318
- fileIssues.push(message);
319
- }
320
- });
321
-
322
- // 파일 길이 검사
323
- if (lines.length > 500) {
324
- fileIssues.push(`파일 길이 ${lines.length}줄 - 분할 검토`);
325
- }
326
-
327
- // 최대 줄 길이 검사
328
- const longLines = lines.filter((l) => l.length > 120).length;
329
- if (longLines > 10) {
330
- fileIssues.push(`120자 초과 줄 ${longLines}개`);
331
- }
332
-
333
- // docstring 통계
334
- const withDocstring =
335
- classes.filter((c) => c.docstring).length +
336
- functions.filter((f) => f.docstring).length;
337
- const total = classes.length + functions.length;
338
-
339
- return {
340
- file: filePath,
341
- framework,
342
- imports,
343
- classes,
344
- functions,
345
- issues: fileIssues,
346
- summary: {
347
- totalClasses: classes.length,
348
- totalFunctions: functions.length,
349
- withDocstring,
350
- withIssues:
351
- classes.filter((c) => c.issues.length > 0).length +
352
- functions.filter((f) => f.issues.length > 0).length,
353
- frameworkPatterns,
354
- },
355
- };
356
- }
357
-
358
- // 도구 정의
359
- export const pythonTools: Tool[] = [
360
- {
361
- name: "python_check",
362
- description:
363
- "Python 파일을 분석합니다. 클래스, 함수, import를 추출하고 Django/Flask/FastAPI 패턴, PEP8 권장사항, 보안 이슈를 검사합니다.",
364
- parameters: {
365
- type: "object",
366
- properties: {
367
- path: {
368
- type: "string",
369
- description: "분석할 Python 파일 또는 디렉토리 경로",
370
- },
371
- recursive: {
372
- type: "boolean",
373
- description: "디렉토리인 경우 하위 폴더 포함 여부 (기본: true)",
374
- },
375
- },
376
- required: ["path"],
377
- },
378
- handler: async (args: Record<string, unknown>): Promise<ToolResult> => {
379
- const targetPath = args.path as string;
380
- const recursive = args.recursive !== false;
381
-
382
- if (!fs.existsSync(targetPath)) {
383
- return {
384
- success: false,
385
- content: "",
386
- error: `경로를 찾을 수 없습니다: ${targetPath}`,
387
- };
388
- }
389
-
390
- const results: PythonAnalysisResult[] = [];
391
- const stats = fs.statSync(targetPath);
392
-
393
- if (stats.isFile()) {
394
- if (targetPath.endsWith(".py")) {
395
- results.push(analyzePythonFile(targetPath));
396
- }
397
- } else if (stats.isDirectory()) {
398
- const walkDir = (dir: string) => {
399
- const files = fs.readdirSync(dir);
400
- for (const file of files) {
401
- const filePath = path.join(dir, file);
402
- const fileStat = fs.statSync(filePath);
403
- if (fileStat.isDirectory() && recursive) {
404
- if (
405
- !file.startsWith(".") &&
406
- file !== "node_modules" &&
407
- file !== "__pycache__" &&
408
- file !== "venv" &&
409
- file !== ".venv" &&
410
- file !== "env"
411
- ) {
412
- walkDir(filePath);
413
- }
414
- } else if (file.endsWith(".py") && !file.startsWith("__")) {
415
- results.push(analyzePythonFile(filePath));
416
- }
417
- }
418
- };
419
- walkDir(targetPath);
420
- }
421
-
422
- // 전체 통계
423
- const totalClasses = results.reduce((sum, r) => sum + r.summary.totalClasses, 0);
424
- const totalFunctions = results.reduce((sum, r) => sum + r.summary.totalFunctions, 0);
425
- const totalWithDocstring = results.reduce((sum, r) => sum + r.summary.withDocstring, 0);
426
-
427
- // 프레임워크 통계
428
- const frameworks: Record<string, number> = {};
429
- results.forEach((r) => {
430
- if (r.framework) {
431
- frameworks[r.framework] = (frameworks[r.framework] || 0) + 1;
432
- }
433
- });
434
-
435
- const output = {
436
- analyzedFiles: results.length,
437
- totalClasses,
438
- totalFunctions,
439
- docstringCoverage:
440
- totalClasses + totalFunctions > 0
441
- ? Math.round((totalWithDocstring / (totalClasses + totalFunctions)) * 100)
442
- : 0,
443
- frameworks,
444
- files: results.map((r) => ({
445
- file: r.file,
446
- framework: r.framework,
447
- frameworkPatterns: r.summary.frameworkPatterns,
448
- fileIssues: r.issues,
449
- summary: r.summary,
450
- imports: r.imports.filter((i) => i.issues.length > 0),
451
- classes: r.classes.map((c) => ({
452
- name: c.name,
453
- line: c.line,
454
- bases: c.bases,
455
- decorators: c.decorators,
456
- methodCount: c.methods.length,
457
- docstring: c.docstring,
458
- issues: c.issues,
459
- })),
460
- functions: r.functions.map((f) => ({
461
- name: f.name,
462
- line: f.line,
463
- decorators: f.decorators,
464
- isAsync: f.isAsync,
465
- docstring: f.docstring,
466
- issues: f.issues,
467
- })),
468
- })),
469
- };
470
-
471
- return {
472
- success: true,
473
- content: JSON.stringify(output, null, 2),
474
- };
475
- },
476
- },
477
- ];