activo 0.2.2 → 0.3.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 +79 -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/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 +10 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +21 -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/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/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 +21 -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/sqlAnalysis.ts +298 -0
- package/FINAL_SIMPLIFIED_SPEC.md +0 -456
- package/TODO.md +0 -193
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { Tool, ToolResult } from "./types.js";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
interface CssRule {
|
|
6
|
+
selector: string;
|
|
7
|
+
line: number;
|
|
8
|
+
properties: number;
|
|
9
|
+
issues: string[];
|
|
10
|
+
specificity?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface CssAnalysisResult {
|
|
14
|
+
file: string;
|
|
15
|
+
type: "css" | "scss" | "less";
|
|
16
|
+
rules: CssRule[];
|
|
17
|
+
imports: string[];
|
|
18
|
+
variables: string[];
|
|
19
|
+
mixins: string[];
|
|
20
|
+
summary: {
|
|
21
|
+
totalRules: number;
|
|
22
|
+
rulesWithIssues: number;
|
|
23
|
+
importantCount: number;
|
|
24
|
+
maxNestingDepth: number;
|
|
25
|
+
vendorPrefixes: number;
|
|
26
|
+
issueTypes: Record<string, number>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// CSS/SCSS/LESS 분석
|
|
31
|
+
function analyzeCssFile(filePath: string): CssAnalysisResult {
|
|
32
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
33
|
+
const lines = content.split("\n");
|
|
34
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
35
|
+
const type: "css" | "scss" | "less" = ext === ".scss" ? "scss" : ext === ".less" ? "less" : "css";
|
|
36
|
+
|
|
37
|
+
const rules: CssRule[] = [];
|
|
38
|
+
const imports: string[] = [];
|
|
39
|
+
const variables: string[] = [];
|
|
40
|
+
const mixins: string[] = [];
|
|
41
|
+
const issueTypes: Record<string, number> = {};
|
|
42
|
+
|
|
43
|
+
let importantCount = 0;
|
|
44
|
+
let maxNestingDepth = 0;
|
|
45
|
+
let vendorPrefixes = 0;
|
|
46
|
+
|
|
47
|
+
// @import 추출
|
|
48
|
+
const importRegex = /@import\s+["']([^"']+)["']/g;
|
|
49
|
+
let match;
|
|
50
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
51
|
+
imports.push(match[1]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 변수 추출 (SCSS: $var, CSS: --var, LESS: @var)
|
|
55
|
+
if (type === "scss") {
|
|
56
|
+
const scssVarRegex = /\$([a-zA-Z_][\w-]*)\s*:/g;
|
|
57
|
+
while ((match = scssVarRegex.exec(content)) !== null) {
|
|
58
|
+
variables.push("$" + match[1]);
|
|
59
|
+
}
|
|
60
|
+
} else if (type === "less") {
|
|
61
|
+
const lessVarRegex = /@([a-zA-Z_][\w-]*)\s*:/g;
|
|
62
|
+
while ((match = lessVarRegex.exec(content)) !== null) {
|
|
63
|
+
if (!["import", "media", "keyframes", "font-face", "charset", "supports"].includes(match[1])) {
|
|
64
|
+
variables.push("@" + match[1]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
const cssVarRegex = /--([\w-]+)\s*:/g;
|
|
69
|
+
while ((match = cssVarRegex.exec(content)) !== null) {
|
|
70
|
+
variables.push("--" + match[1]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// mixin 추출 (SCSS/LESS)
|
|
75
|
+
if (type === "scss") {
|
|
76
|
+
const mixinRegex = /@mixin\s+([\w-]+)/g;
|
|
77
|
+
while ((match = mixinRegex.exec(content)) !== null) {
|
|
78
|
+
mixins.push(match[1]);
|
|
79
|
+
}
|
|
80
|
+
} else if (type === "less") {
|
|
81
|
+
const lessMixinRegex = /\.([\w-]+)\s*\([^)]*\)\s*\{/g;
|
|
82
|
+
while ((match = lessMixinRegex.exec(content)) !== null) {
|
|
83
|
+
mixins.push("." + match[1]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// !important 개수
|
|
88
|
+
importantCount = (content.match(/!important/gi) || []).length;
|
|
89
|
+
|
|
90
|
+
// vendor prefix 개수
|
|
91
|
+
vendorPrefixes = (content.match(/-webkit-|-moz-|-ms-|-o-/g) || []).length;
|
|
92
|
+
|
|
93
|
+
// 중첩 깊이 계산 (SCSS/LESS)
|
|
94
|
+
if (type === "scss" || type === "less") {
|
|
95
|
+
let depth = 0;
|
|
96
|
+
for (const char of content) {
|
|
97
|
+
if (char === "{") {
|
|
98
|
+
depth++;
|
|
99
|
+
maxNestingDepth = Math.max(maxNestingDepth, depth);
|
|
100
|
+
} else if (char === "}") {
|
|
101
|
+
depth = Math.max(0, depth - 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 셀렉터 및 규칙 분석
|
|
107
|
+
// 간단한 규칙 파싱 (중첩 미포함)
|
|
108
|
+
const ruleRegex = /([^{}@]+)\{([^{}]+)\}/g;
|
|
109
|
+
while ((match = ruleRegex.exec(content)) !== null) {
|
|
110
|
+
const selector = match[1].trim();
|
|
111
|
+
const properties = match[2];
|
|
112
|
+
|
|
113
|
+
if (!selector || selector.startsWith("@") || selector.startsWith("//") || selector.startsWith("/*")) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const issues: string[] = [];
|
|
118
|
+
|
|
119
|
+
// 라인 번호 찾기
|
|
120
|
+
const ruleIndex = match.index;
|
|
121
|
+
let lineNum = 1;
|
|
122
|
+
for (let i = 0; i < ruleIndex && i < content.length; i++) {
|
|
123
|
+
if (content[i] === "\n") lineNum++;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 속성 개수
|
|
127
|
+
const propCount = (properties.match(/[^;{}]+:[^;{}]+;?/g) || []).length;
|
|
128
|
+
|
|
129
|
+
// !important 사용
|
|
130
|
+
const importantInRule = (properties.match(/!important/gi) || []).length;
|
|
131
|
+
if (importantInRule > 0) {
|
|
132
|
+
issues.push(`!important ${importantInRule}회 사용`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 과도한 셀렉터 체이닝
|
|
136
|
+
const selectorParts = selector.split(/\s+/).filter((s) => s.trim());
|
|
137
|
+
if (selectorParts.length > 4) {
|
|
138
|
+
issues.push(`셀렉터 깊이 ${selectorParts.length} - 단순화 권장`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ID 셀렉터 남용
|
|
142
|
+
const idCount = (selector.match(/#[\w-]+/g) || []).length;
|
|
143
|
+
if (idCount > 1) {
|
|
144
|
+
issues.push(`ID 셀렉터 ${idCount}개 - 특이성 과다`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// universal 셀렉터
|
|
148
|
+
if (/^\s*\*\s*$/.test(selector) || /\s\*\s/.test(selector)) {
|
|
149
|
+
issues.push("* 셀렉터 사용 - 성능 영향");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// float 사용 (레거시)
|
|
153
|
+
if (/float\s*:/i.test(properties)) {
|
|
154
|
+
issues.push("float 사용 - flexbox/grid 권장");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// vendor prefix 직접 사용
|
|
158
|
+
if (/-webkit-|-moz-|-ms-|-o-/.test(properties)) {
|
|
159
|
+
issues.push("vendor prefix 직접 사용 - autoprefixer 권장");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 색상 하드코딩
|
|
163
|
+
const hexColors = (properties.match(/#[0-9a-fA-F]{3,8}/g) || []).length;
|
|
164
|
+
const rgbColors = (properties.match(/rgb\(|rgba\(/gi) || []).length;
|
|
165
|
+
if (hexColors + rgbColors > 2) {
|
|
166
|
+
issues.push("색상 하드코딩 - 변수 사용 권장");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// z-index 과다
|
|
170
|
+
const zIndexMatch = properties.match(/z-index\s*:\s*(\d+)/);
|
|
171
|
+
if (zIndexMatch && parseInt(zIndexMatch[1]) > 100) {
|
|
172
|
+
issues.push(`z-index: ${zIndexMatch[1]} - 관리 필요`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 이슈 통계
|
|
176
|
+
issues.forEach((issue) => {
|
|
177
|
+
const key = issue.split(" - ")[0].split(" ")[0];
|
|
178
|
+
issueTypes[key] = (issueTypes[key] || 0) + 1;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
rules.push({
|
|
182
|
+
selector: selector.length > 60 ? selector.substring(0, 60) + "..." : selector,
|
|
183
|
+
line: lineNum,
|
|
184
|
+
properties: propCount,
|
|
185
|
+
issues,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// !important 이슈 추가
|
|
190
|
+
if (importantCount > 5) {
|
|
191
|
+
issueTypes["!important과다"] = importantCount;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 중첩 깊이 이슈
|
|
195
|
+
if (maxNestingDepth > 4) {
|
|
196
|
+
issueTypes["중첩과다"] = maxNestingDepth;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
file: filePath,
|
|
201
|
+
type,
|
|
202
|
+
rules,
|
|
203
|
+
imports,
|
|
204
|
+
variables: [...new Set(variables)],
|
|
205
|
+
mixins,
|
|
206
|
+
summary: {
|
|
207
|
+
totalRules: rules.length,
|
|
208
|
+
rulesWithIssues: rules.filter((r) => r.issues.length > 0).length,
|
|
209
|
+
importantCount,
|
|
210
|
+
maxNestingDepth,
|
|
211
|
+
vendorPrefixes,
|
|
212
|
+
issueTypes,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 도구 정의
|
|
218
|
+
export const cssTools: Tool[] = [
|
|
219
|
+
{
|
|
220
|
+
name: "css_check",
|
|
221
|
+
description:
|
|
222
|
+
"CSS/SCSS/LESS 파일을 분석합니다. !important 남용, 셀렉터 복잡도, 중첩 깊이, vendor prefix, 레거시 속성(float) 등을 검사합니다.",
|
|
223
|
+
parameters: {
|
|
224
|
+
type: "object",
|
|
225
|
+
properties: {
|
|
226
|
+
path: {
|
|
227
|
+
type: "string",
|
|
228
|
+
description: "분석할 CSS/SCSS/LESS 파일 또는 디렉토리 경로",
|
|
229
|
+
},
|
|
230
|
+
recursive: {
|
|
231
|
+
type: "boolean",
|
|
232
|
+
description: "디렉토리인 경우 하위 폴더 포함 여부 (기본: true)",
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
required: ["path"],
|
|
236
|
+
},
|
|
237
|
+
handler: async (args: Record<string, unknown>): Promise<ToolResult> => {
|
|
238
|
+
const targetPath = args.path as string;
|
|
239
|
+
const recursive = args.recursive !== false;
|
|
240
|
+
|
|
241
|
+
if (!fs.existsSync(targetPath)) {
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
content: "",
|
|
245
|
+
error: `경로를 찾을 수 없습니다: ${targetPath}`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const results: CssAnalysisResult[] = [];
|
|
250
|
+
const stats = fs.statSync(targetPath);
|
|
251
|
+
const cssExtensions = [".css", ".scss", ".less"];
|
|
252
|
+
|
|
253
|
+
if (stats.isFile()) {
|
|
254
|
+
const ext = path.extname(targetPath).toLowerCase();
|
|
255
|
+
if (cssExtensions.includes(ext)) {
|
|
256
|
+
results.push(analyzeCssFile(targetPath));
|
|
257
|
+
}
|
|
258
|
+
} else if (stats.isDirectory()) {
|
|
259
|
+
const walkDir = (dir: string) => {
|
|
260
|
+
const files = fs.readdirSync(dir);
|
|
261
|
+
for (const file of files) {
|
|
262
|
+
const filePath = path.join(dir, file);
|
|
263
|
+
const fileStat = fs.statSync(filePath);
|
|
264
|
+
if (fileStat.isDirectory() && recursive) {
|
|
265
|
+
if (!file.startsWith(".") && file !== "node_modules" && file !== "target" && file !== "build") {
|
|
266
|
+
walkDir(filePath);
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
const ext = path.extname(file).toLowerCase();
|
|
270
|
+
if (cssExtensions.includes(ext)) {
|
|
271
|
+
results.push(analyzeCssFile(filePath));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
walkDir(targetPath);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 전체 통계
|
|
280
|
+
const totalRules = results.reduce((sum, r) => sum + r.summary.totalRules, 0);
|
|
281
|
+
const totalWithIssues = results.reduce((sum, r) => sum + r.summary.rulesWithIssues, 0);
|
|
282
|
+
const totalImportant = results.reduce((sum, r) => sum + r.summary.importantCount, 0);
|
|
283
|
+
const totalVendorPrefixes = results.reduce((sum, r) => sum + r.summary.vendorPrefixes, 0);
|
|
284
|
+
const maxNesting = Math.max(...results.map((r) => r.summary.maxNestingDepth), 0);
|
|
285
|
+
|
|
286
|
+
const allIssueTypes: Record<string, number> = {};
|
|
287
|
+
results.forEach((r) => {
|
|
288
|
+
Object.entries(r.summary.issueTypes).forEach(([key, count]) => {
|
|
289
|
+
allIssueTypes[key] = (allIssueTypes[key] || 0) + count;
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const output = {
|
|
294
|
+
analyzedFiles: results.length,
|
|
295
|
+
totalRules,
|
|
296
|
+
rulesWithIssues: totalWithIssues,
|
|
297
|
+
totalImportant,
|
|
298
|
+
totalVendorPrefixes,
|
|
299
|
+
maxNestingDepth: maxNesting,
|
|
300
|
+
issueTypes: allIssueTypes,
|
|
301
|
+
files: results.map((r) => ({
|
|
302
|
+
file: r.file,
|
|
303
|
+
type: r.type,
|
|
304
|
+
imports: r.imports,
|
|
305
|
+
variables: r.variables.slice(0, 10),
|
|
306
|
+
mixins: r.mixins,
|
|
307
|
+
summary: r.summary,
|
|
308
|
+
rulesWithIssues: r.rules
|
|
309
|
+
.filter((rule) => rule.issues.length > 0)
|
|
310
|
+
.map((rule) => ({
|
|
311
|
+
selector: rule.selector,
|
|
312
|
+
line: rule.line,
|
|
313
|
+
issues: rule.issues,
|
|
314
|
+
})),
|
|
315
|
+
})),
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
success: true,
|
|
320
|
+
content: JSON.stringify(output, null, 2),
|
|
321
|
+
};
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
];
|