activo 0.4.3 → 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 (166) 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 +255 -17
  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/analyzeAll.d.ts.map +1 -1
  62. package/dist/core/tools/analyzeAll.js +16 -28
  63. package/dist/core/tools/analyzeAll.js.map +1 -1
  64. package/dist/core/tools/analyzePatterns.d.ts +3 -0
  65. package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
  66. package/dist/core/tools/analyzePatterns.js +293 -0
  67. package/dist/core/tools/analyzePatterns.js.map +1 -0
  68. package/dist/core/tools/apexPaths.d.ts +14 -0
  69. package/dist/core/tools/apexPaths.d.ts.map +1 -0
  70. package/dist/core/tools/apexPaths.js +54 -0
  71. package/dist/core/tools/apexPaths.js.map +1 -0
  72. package/dist/core/tools/apexUtils.d.ts +36 -0
  73. package/dist/core/tools/apexUtils.d.ts.map +1 -0
  74. package/dist/core/tools/apexUtils.js +83 -0
  75. package/dist/core/tools/apexUtils.js.map +1 -0
  76. package/dist/core/tools/explainIssue.d.ts +3 -0
  77. package/dist/core/tools/explainIssue.d.ts.map +1 -0
  78. package/dist/core/tools/explainIssue.js +181 -0
  79. package/dist/core/tools/explainIssue.js.map +1 -0
  80. package/dist/core/tools/fixGen.d.ts +3 -0
  81. package/dist/core/tools/fixGen.d.ts.map +1 -0
  82. package/dist/core/tools/fixGen.js +338 -0
  83. package/dist/core/tools/fixGen.js.map +1 -0
  84. package/dist/core/tools/generateImprovements.d.ts +21 -0
  85. package/dist/core/tools/generateImprovements.d.ts.map +1 -0
  86. package/dist/core/tools/generateImprovements.js +602 -0
  87. package/dist/core/tools/generateImprovements.js.map +1 -0
  88. package/dist/core/tools/generateReport.d.ts +3 -0
  89. package/dist/core/tools/generateReport.d.ts.map +1 -0
  90. package/dist/core/tools/generateReport.js +315 -0
  91. package/dist/core/tools/generateReport.js.map +1 -0
  92. package/dist/core/tools/index.d.ts +7 -0
  93. package/dist/core/tools/index.d.ts.map +1 -1
  94. package/dist/core/tools/index.js +62 -23
  95. package/dist/core/tools/index.js.map +1 -1
  96. package/dist/core/tools/javaAst.d.ts.map +1 -1
  97. package/dist/core/tools/javaAst.js +191 -0
  98. package/dist/core/tools/javaAst.js.map +1 -1
  99. package/dist/core/tools/recommendProfile.d.ts +3 -0
  100. package/dist/core/tools/recommendProfile.d.ts.map +1 -0
  101. package/dist/core/tools/recommendProfile.js +334 -0
  102. package/dist/core/tools/recommendProfile.js.map +1 -0
  103. package/dist/core/tools/ruleGen.d.ts +3 -0
  104. package/dist/core/tools/ruleGen.d.ts.map +1 -0
  105. package/dist/core/tools/ruleGen.js +1103 -0
  106. package/dist/core/tools/ruleGen.js.map +1 -0
  107. package/dist/core/tools/standards.d.ts.map +1 -1
  108. package/dist/core/tools/standards.js +7 -3
  109. package/dist/core/tools/standards.js.map +1 -1
  110. package/dist/ui/App.d.ts.map +1 -1
  111. package/dist/ui/App.js +86 -35
  112. package/dist/ui/App.js.map +1 -1
  113. package/dist/ui/components/InputBox.d.ts +1 -3
  114. package/dist/ui/components/InputBox.d.ts.map +1 -1
  115. package/dist/ui/components/InputBox.js +146 -5
  116. package/dist/ui/components/InputBox.js.map +1 -1
  117. package/dist/ui/components/MessageList.d.ts +3 -1
  118. package/dist/ui/components/MessageList.d.ts.map +1 -1
  119. package/dist/ui/components/MessageList.js +13 -7
  120. package/dist/ui/components/MessageList.js.map +1 -1
  121. package/dist/ui/components/StatusBar.d.ts +1 -1
  122. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  123. package/dist/ui/components/StatusBar.js +3 -2
  124. package/dist/ui/components/StatusBar.js.map +1 -1
  125. package/dist/ui/components/ToolStatus.d.ts +3 -1
  126. package/dist/ui/components/ToolStatus.d.ts.map +1 -1
  127. package/dist/ui/components/ToolStatus.js +19 -4
  128. package/dist/ui/components/ToolStatus.js.map +1 -1
  129. package/package.json +7 -1
  130. package/demo.gif +0 -0
  131. package/demo.tape +0 -53
  132. package/screenshot.png +0 -0
  133. package/src/cli/banner.ts +0 -38
  134. package/src/cli/headless.ts +0 -63
  135. package/src/cli/index.ts +0 -57
  136. package/src/core/agent.ts +0 -237
  137. package/src/core/commands.ts +0 -118
  138. package/src/core/config.ts +0 -98
  139. package/src/core/conversation.ts +0 -235
  140. package/src/core/llm/ollama.ts +0 -351
  141. package/src/core/mcp/client.ts +0 -143
  142. package/src/core/tools/analyzeAll.ts +0 -494
  143. package/src/core/tools/ast.ts +0 -826
  144. package/src/core/tools/builtIn.ts +0 -221
  145. package/src/core/tools/cache.ts +0 -570
  146. package/src/core/tools/cssAnalysis.ts +0 -324
  147. package/src/core/tools/dependencyAnalysis.ts +0 -363
  148. package/src/core/tools/embeddings.ts +0 -746
  149. package/src/core/tools/frontendAst.ts +0 -802
  150. package/src/core/tools/htmlAnalysis.ts +0 -466
  151. package/src/core/tools/index.ts +0 -160
  152. package/src/core/tools/javaAst.ts +0 -812
  153. package/src/core/tools/memory.ts +0 -655
  154. package/src/core/tools/mybatisAnalysis.ts +0 -322
  155. package/src/core/tools/openapiAnalysis.ts +0 -431
  156. package/src/core/tools/pythonAnalysis.ts +0 -477
  157. package/src/core/tools/sqlAnalysis.ts +0 -298
  158. package/src/core/tools/standards.test.ts +0 -186
  159. package/src/core/tools/standards.ts +0 -889
  160. package/src/core/tools/types.ts +0 -38
  161. package/src/ui/App.tsx +0 -334
  162. package/src/ui/components/InputBox.tsx +0 -37
  163. package/src/ui/components/MessageList.tsx +0 -80
  164. package/src/ui/components/StatusBar.tsx +0 -36
  165. package/src/ui/components/ToolStatus.tsx +0 -38
  166. package/tsconfig.json +0 -21
@@ -1,324 +0,0 @@
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
- ];
@@ -1,363 +0,0 @@
1
- import { Tool, ToolResult } from "./types.js";
2
- import * as fs from "fs";
3
- import * as path from "path";
4
-
5
- interface Dependency {
6
- name: string;
7
- version: string;
8
- type: "dependency" | "devDependency" | "peerDependency" | "compile" | "runtime" | "test" | "provided";
9
- issues: string[];
10
- }
11
-
12
- interface DependencyAnalysisResult {
13
- file: string;
14
- type: "npm" | "maven" | "gradle";
15
- projectName?: string;
16
- projectVersion?: string;
17
- dependencies: Dependency[];
18
- summary: {
19
- total: number;
20
- withIssues: number;
21
- outdatedPatterns: number;
22
- securityConcerns: number;
23
- };
24
- }
25
-
26
- // 알려진 취약점/문제 패턴
27
- const knownIssues: Record<string, { pattern: RegExp; message: string }[]> = {
28
- npm: [
29
- { pattern: /^lodash@[0-3]\./, message: "lodash 4.x 이전 버전 - 보안 취약점" },
30
- { pattern: /^moment@/, message: "moment.js deprecated - dayjs/date-fns 권장" },
31
- { pattern: /^request@/, message: "request deprecated - axios/node-fetch 권장" },
32
- { pattern: /^jquery@[12]\./, message: "jQuery 3.x 이전 버전 - XSS 취약점" },
33
- { pattern: /^angular@1\./, message: "AngularJS 1.x EOL - Angular 권장" },
34
- { pattern: /^react@1[0-5]\./, message: "React 16 이전 버전 - 업데이트 권장" },
35
- { pattern: /^vue@[12]\./, message: "Vue 2.x - Vue 3.x 마이그레이션 검토" },
36
- { pattern: /^webpack@[1-3]\./, message: "Webpack 4 이전 버전 - 업데이트 권장" },
37
- { pattern: /^node-sass@/, message: "node-sass deprecated - sass(dart-sass) 권장" },
38
- { pattern: /^tslint@/, message: "TSLint deprecated - ESLint 권장" },
39
- { pattern: /^crypto-js@[0-3]\./, message: "crypto-js 구버전 - 보안 취약점" },
40
- { pattern: /^express@[0-3]\./, message: "Express 4 이전 버전 - 보안 취약점" },
41
- { pattern: /^axios@0\.[0-1]/, message: "axios 0.21 이전 - SSRF 취약점" },
42
- ],
43
- maven: [
44
- { pattern: /log4j.*1\./, message: "Log4j 1.x EOL - Log4j 2.x 또는 Logback 권장" },
45
- { pattern: /log4j.*2\.[0-9]\./, message: "Log4j 2.0-2.14 - Log4Shell 취약점 (CVE-2021-44228)" },
46
- { pattern: /log4j.*2\.1[0-4]\./, message: "Log4j 2.10-2.14 - Log4Shell 취약점" },
47
- { pattern: /commons-collections.*[0-3]\./, message: "Commons Collections 3.x - 역직렬화 취약점" },
48
- { pattern: /spring-core.*[0-4]\./, message: "Spring 5 이전 버전 - 업데이트 권장" },
49
- { pattern: /jackson-databind.*2\.[0-8]\./, message: "Jackson 2.9 이전 - 역직렬화 취약점" },
50
- { pattern: /struts.*1\./, message: "Struts 1.x EOL - 보안 취약점 다수" },
51
- { pattern: /struts2.*2\.[0-4]\./, message: "Struts 2.5 이전 - 원격 코드 실행 취약점" },
52
- { pattern: /hibernate.*[0-4]\./, message: "Hibernate 5 이전 버전 - 업데이트 권장" },
53
- { pattern: /mysql-connector.*5\./, message: "MySQL Connector 8.x 권장" },
54
- { pattern: /fastjson.*1\.[12]\.[0-5]/, message: "Fastjson 1.2.68 이전 - 원격 코드 실행 취약점" },
55
- { pattern: /commons-fileupload.*1\.[0-3]\./, message: "Commons FileUpload 1.4 이전 - DoS 취약점" },
56
- { pattern: /shiro.*1\.[0-5]\./, message: "Apache Shiro 1.6 이전 - 인증 우회 취약점" },
57
- ],
58
- };
59
-
60
- // 버전 패턴 검사
61
- const versionPatterns = {
62
- range: /^[\^~><=]/,
63
- latest: /^(latest|\*)$/,
64
- git: /^(git|github|gitlab)/,
65
- file: /^file:/,
66
- };
67
-
68
- // package.json 분석
69
- function analyzePackageJson(filePath: string): DependencyAnalysisResult {
70
- const content = fs.readFileSync(filePath, "utf-8");
71
- const pkg = JSON.parse(content);
72
- const dependencies: Dependency[] = [];
73
-
74
- const analyzeDeps = (deps: Record<string, string> | undefined, type: Dependency["type"]) => {
75
- if (!deps) return;
76
-
77
- Object.entries(deps).forEach(([name, version]) => {
78
- const issues: string[] = [];
79
- const fullName = `${name}@${version}`;
80
-
81
- // 알려진 이슈 검사
82
- knownIssues.npm.forEach(({ pattern, message }) => {
83
- if (pattern.test(fullName)) {
84
- issues.push(message);
85
- }
86
- });
87
-
88
- // 버전 패턴 검사
89
- if (versionPatterns.latest.test(version)) {
90
- issues.push("'latest' 또는 '*' 사용 - 버전 고정 권장");
91
- }
92
- if (versionPatterns.git.test(version)) {
93
- issues.push("Git 의존성 - 버전 태그 사용 권장");
94
- }
95
- if (version.startsWith("^0.") || version.startsWith("~0.")) {
96
- issues.push("0.x 버전 - 불안정 버전 주의");
97
- }
98
-
99
- dependencies.push({ name, version, type, issues });
100
- });
101
- };
102
-
103
- analyzeDeps(pkg.dependencies, "dependency");
104
- analyzeDeps(pkg.devDependencies, "devDependency");
105
- analyzeDeps(pkg.peerDependencies, "peerDependency");
106
-
107
- const withIssues = dependencies.filter((d) => d.issues.length > 0);
108
- const securityConcerns = dependencies.filter((d) =>
109
- d.issues.some((i) => i.includes("취약점") || i.includes("보안"))
110
- ).length;
111
-
112
- return {
113
- file: filePath,
114
- type: "npm",
115
- projectName: pkg.name,
116
- projectVersion: pkg.version,
117
- dependencies,
118
- summary: {
119
- total: dependencies.length,
120
- withIssues: withIssues.length,
121
- outdatedPatterns: dependencies.filter((d) =>
122
- d.issues.some((i) => i.includes("deprecated") || i.includes("EOL") || i.includes("이전"))
123
- ).length,
124
- securityConcerns,
125
- },
126
- };
127
- }
128
-
129
- // pom.xml 분석
130
- function analyzePomXml(filePath: string): DependencyAnalysisResult {
131
- const content = fs.readFileSync(filePath, "utf-8");
132
- const dependencies: Dependency[] = [];
133
-
134
- // 프로젝트 정보 추출
135
- const artifactIdMatch = content.match(/<artifactId>([^<]+)<\/artifactId>/);
136
- const versionMatch = content.match(/<version>([^<]+)<\/version>/);
137
-
138
- // 의존성 추출
139
- const depRegex = /<dependency>\s*<groupId>([^<]+)<\/groupId>\s*<artifactId>([^<]+)<\/artifactId>(?:\s*<version>([^<]*)<\/version>)?(?:\s*<scope>([^<]*)<\/scope>)?/g;
140
-
141
- let match;
142
- while ((match = depRegex.exec(content)) !== null) {
143
- const groupId = match[1];
144
- const artifactId = match[2];
145
- const version = match[3] || "미지정";
146
- const scope = match[4] || "compile";
147
- const name = `${groupId}:${artifactId}`;
148
- const issues: string[] = [];
149
- const fullName = `${artifactId}.*${version}`;
150
-
151
- // 알려진 이슈 검사
152
- knownIssues.maven.forEach(({ pattern, message }) => {
153
- if (pattern.test(fullName) || pattern.test(`${groupId}.${artifactId}.*${version}`)) {
154
- issues.push(message);
155
- }
156
- });
157
-
158
- // 버전 미지정 검사
159
- if (version === "미지정" || version.includes("${")) {
160
- if (!version.includes("${")) {
161
- issues.push("버전 미지정 - 명시적 버전 지정 권장");
162
- }
163
- }
164
-
165
- // SNAPSHOT 버전 검사
166
- if (version.includes("SNAPSHOT")) {
167
- issues.push("SNAPSHOT 버전 - 프로덕션에서 릴리즈 버전 사용 권장");
168
- }
169
-
170
- const type: Dependency["type"] =
171
- scope === "test" ? "test" :
172
- scope === "provided" ? "provided" :
173
- scope === "runtime" ? "runtime" : "compile";
174
-
175
- dependencies.push({ name, version, type, issues });
176
- }
177
-
178
- const withIssues = dependencies.filter((d) => d.issues.length > 0);
179
- const securityConcerns = dependencies.filter((d) =>
180
- d.issues.some((i) => i.includes("취약점") || i.includes("보안") || i.includes("CVE"))
181
- ).length;
182
-
183
- return {
184
- file: filePath,
185
- type: "maven",
186
- projectName: artifactIdMatch ? artifactIdMatch[1] : undefined,
187
- projectVersion: versionMatch ? versionMatch[1] : undefined,
188
- dependencies,
189
- summary: {
190
- total: dependencies.length,
191
- withIssues: withIssues.length,
192
- outdatedPatterns: dependencies.filter((d) =>
193
- d.issues.some((i) => i.includes("EOL") || i.includes("이전"))
194
- ).length,
195
- securityConcerns,
196
- },
197
- };
198
- }
199
-
200
- // build.gradle 분석 (간단한 버전)
201
- function analyzeBuildGradle(filePath: string): DependencyAnalysisResult {
202
- const content = fs.readFileSync(filePath, "utf-8");
203
- const dependencies: Dependency[] = [];
204
-
205
- // 의존성 추출 (implementation, compile, testImplementation 등)
206
- const depRegex = /(?:implementation|compile|testImplementation|testCompile|runtimeOnly|compileOnly|api)\s*['"(]([^'"()]+)['"()]/g;
207
-
208
- let match;
209
- while ((match = depRegex.exec(content)) !== null) {
210
- const depString = match[1];
211
- const parts = depString.split(":");
212
-
213
- if (parts.length >= 2) {
214
- const name = `${parts[0]}:${parts[1]}`;
215
- const version = parts[2] || "미지정";
216
- const issues: string[] = [];
217
-
218
- // 알려진 이슈 검사
219
- knownIssues.maven.forEach(({ pattern, message }) => {
220
- if (pattern.test(`${parts[1]}.*${version}`)) {
221
- issues.push(message);
222
- }
223
- });
224
-
225
- if (version === "미지정" || version.includes("$")) {
226
- if (!version.includes("$")) {
227
- issues.push("버전 미지정");
228
- }
229
- }
230
-
231
- dependencies.push({
232
- name,
233
- version,
234
- type: match[0].includes("test") ? "test" : "compile",
235
- issues,
236
- });
237
- }
238
- }
239
-
240
- const withIssues = dependencies.filter((d) => d.issues.length > 0);
241
-
242
- return {
243
- file: filePath,
244
- type: "gradle",
245
- dependencies,
246
- summary: {
247
- total: dependencies.length,
248
- withIssues: withIssues.length,
249
- outdatedPatterns: dependencies.filter((d) =>
250
- d.issues.some((i) => i.includes("EOL") || i.includes("이전"))
251
- ).length,
252
- securityConcerns: dependencies.filter((d) =>
253
- d.issues.some((i) => i.includes("취약점") || i.includes("CVE"))
254
- ).length,
255
- },
256
- };
257
- }
258
-
259
- // 도구 정의
260
- export const dependencyTools: Tool[] = [
261
- {
262
- name: "dependency_check",
263
- description:
264
- "프로젝트 의존성을 분석합니다. package.json, pom.xml, build.gradle에서 의존성을 추출하고 알려진 취약점, deprecated 패키지, 버전 문제를 검사합니다.",
265
- parameters: {
266
- type: "object",
267
- properties: {
268
- path: {
269
- type: "string",
270
- description: "분석할 파일 또는 디렉토리 경로",
271
- },
272
- recursive: {
273
- type: "boolean",
274
- description: "디렉토리인 경우 하위 폴더 포함 여부 (기본: true)",
275
- },
276
- },
277
- required: ["path"],
278
- },
279
- handler: async (args: Record<string, unknown>): Promise<ToolResult> => {
280
- const targetPath = args.path as string;
281
- const recursive = args.recursive !== false;
282
-
283
- if (!fs.existsSync(targetPath)) {
284
- return {
285
- success: false,
286
- content: "",
287
- error: `경로를 찾을 수 없습니다: ${targetPath}`,
288
- };
289
- }
290
-
291
- const results: DependencyAnalysisResult[] = [];
292
- const stats = fs.statSync(targetPath);
293
- const depFiles = ["package.json", "pom.xml", "build.gradle"];
294
-
295
- const analyzeFile = (filePath: string) => {
296
- const fileName = path.basename(filePath);
297
- if (fileName === "package.json") {
298
- try {
299
- results.push(analyzePackageJson(filePath));
300
- } catch (e) {
301
- // JSON 파싱 실패 무시
302
- }
303
- } else if (fileName === "pom.xml") {
304
- results.push(analyzePomXml(filePath));
305
- } else if (fileName === "build.gradle") {
306
- results.push(analyzeBuildGradle(filePath));
307
- }
308
- };
309
-
310
- if (stats.isFile()) {
311
- analyzeFile(targetPath);
312
- } else if (stats.isDirectory()) {
313
- const walkDir = (dir: string) => {
314
- const files = fs.readdirSync(dir);
315
- for (const file of files) {
316
- const filePath = path.join(dir, file);
317
- const fileStat = fs.statSync(filePath);
318
- if (fileStat.isDirectory() && recursive) {
319
- if (!file.startsWith(".") && file !== "node_modules" && file !== "target" && file !== "build") {
320
- walkDir(filePath);
321
- }
322
- } else if (depFiles.includes(file)) {
323
- analyzeFile(filePath);
324
- }
325
- }
326
- };
327
- walkDir(targetPath);
328
- }
329
-
330
- // 전체 통계
331
- const totalDeps = results.reduce((sum, r) => sum + r.summary.total, 0);
332
- const totalWithIssues = results.reduce((sum, r) => sum + r.summary.withIssues, 0);
333
- const totalSecurity = results.reduce((sum, r) => sum + r.summary.securityConcerns, 0);
334
-
335
- const output = {
336
- analyzedFiles: results.length,
337
- totalDependencies: totalDeps,
338
- dependenciesWithIssues: totalWithIssues,
339
- securityConcerns: totalSecurity,
340
- projects: results.map((r) => ({
341
- file: r.file,
342
- type: r.type,
343
- projectName: r.projectName,
344
- projectVersion: r.projectVersion,
345
- summary: r.summary,
346
- issuesFound: r.dependencies
347
- .filter((d) => d.issues.length > 0)
348
- .map((d) => ({
349
- name: d.name,
350
- version: d.version,
351
- type: d.type,
352
- issues: d.issues,
353
- })),
354
- })),
355
- };
356
-
357
- return {
358
- success: true,
359
- content: JSON.stringify(output, null, 2),
360
- };
361
- },
362
- },
363
- ];