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,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
|
-
];
|