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.
Files changed (64) hide show
  1. package/README.md +79 -3
  2. package/dist/core/llm/ollama.d.ts +2 -0
  3. package/dist/core/llm/ollama.d.ts.map +1 -1
  4. package/dist/core/llm/ollama.js +26 -0
  5. package/dist/core/llm/ollama.js.map +1 -1
  6. package/dist/core/tools/ast.d.ts +81 -0
  7. package/dist/core/tools/ast.d.ts.map +1 -0
  8. package/dist/core/tools/ast.js +700 -0
  9. package/dist/core/tools/ast.js.map +1 -0
  10. package/dist/core/tools/cache.d.ts +19 -0
  11. package/dist/core/tools/cache.d.ts.map +1 -0
  12. package/dist/core/tools/cache.js +497 -0
  13. package/dist/core/tools/cache.js.map +1 -0
  14. package/dist/core/tools/cssAnalysis.d.ts +3 -0
  15. package/dist/core/tools/cssAnalysis.d.ts.map +1 -0
  16. package/dist/core/tools/cssAnalysis.js +270 -0
  17. package/dist/core/tools/cssAnalysis.js.map +1 -0
  18. package/dist/core/tools/embeddings.d.ts +8 -0
  19. package/dist/core/tools/embeddings.d.ts.map +1 -0
  20. package/dist/core/tools/embeddings.js +631 -0
  21. package/dist/core/tools/embeddings.js.map +1 -0
  22. package/dist/core/tools/frontendAst.d.ts +6 -0
  23. package/dist/core/tools/frontendAst.d.ts.map +1 -0
  24. package/dist/core/tools/frontendAst.js +680 -0
  25. package/dist/core/tools/frontendAst.js.map +1 -0
  26. package/dist/core/tools/htmlAnalysis.d.ts +3 -0
  27. package/dist/core/tools/htmlAnalysis.d.ts.map +1 -0
  28. package/dist/core/tools/htmlAnalysis.js +398 -0
  29. package/dist/core/tools/htmlAnalysis.js.map +1 -0
  30. package/dist/core/tools/index.d.ts +10 -0
  31. package/dist/core/tools/index.d.ts.map +1 -1
  32. package/dist/core/tools/index.js +21 -1
  33. package/dist/core/tools/index.js.map +1 -1
  34. package/dist/core/tools/javaAst.d.ts +6 -0
  35. package/dist/core/tools/javaAst.d.ts.map +1 -0
  36. package/dist/core/tools/javaAst.js +678 -0
  37. package/dist/core/tools/javaAst.js.map +1 -0
  38. package/dist/core/tools/memory.d.ts +11 -0
  39. package/dist/core/tools/memory.d.ts.map +1 -0
  40. package/dist/core/tools/memory.js +551 -0
  41. package/dist/core/tools/memory.js.map +1 -0
  42. package/dist/core/tools/mybatisAnalysis.d.ts +3 -0
  43. package/dist/core/tools/mybatisAnalysis.d.ts.map +1 -0
  44. package/dist/core/tools/mybatisAnalysis.js +251 -0
  45. package/dist/core/tools/mybatisAnalysis.js.map +1 -0
  46. package/dist/core/tools/sqlAnalysis.d.ts +3 -0
  47. package/dist/core/tools/sqlAnalysis.d.ts.map +1 -0
  48. package/dist/core/tools/sqlAnalysis.js +250 -0
  49. package/dist/core/tools/sqlAnalysis.js.map +1 -0
  50. package/package.json +2 -1
  51. package/src/core/llm/ollama.ts +30 -0
  52. package/src/core/tools/ast.ts +826 -0
  53. package/src/core/tools/cache.ts +570 -0
  54. package/src/core/tools/cssAnalysis.ts +324 -0
  55. package/src/core/tools/embeddings.ts +746 -0
  56. package/src/core/tools/frontendAst.ts +802 -0
  57. package/src/core/tools/htmlAnalysis.ts +466 -0
  58. package/src/core/tools/index.ts +21 -1
  59. package/src/core/tools/javaAst.ts +812 -0
  60. package/src/core/tools/memory.ts +655 -0
  61. package/src/core/tools/mybatisAnalysis.ts +322 -0
  62. package/src/core/tools/sqlAnalysis.ts +298 -0
  63. package/FINAL_SIMPLIFIED_SPEC.md +0 -456
  64. package/TODO.md +0 -193
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontendAst.d.ts","sourceRoot":"","sources":["../../../src/core/tools/frontendAst.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAc,MAAM,YAAY,CAAC;AA4d9C,eAAO,MAAM,cAAc,EAAE,IAiG5B,CAAC;AAGF,eAAO,MAAM,YAAY,EAAE,IAsG1B,CAAC;AAGF,eAAO,MAAM,eAAe,EAAE,IA8G7B,CAAC;AAGF,eAAO,MAAM,aAAa,EAAE,IAAI,EAI/B,CAAC"}
@@ -0,0 +1,680 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import ts from "typescript";
4
+ // Deprecated jQuery methods
5
+ const DEPRECATED_JQUERY = {
6
+ ".bind(": "Use .on() instead",
7
+ ".unbind(": "Use .off() instead",
8
+ ".delegate(": "Use .on() instead",
9
+ ".undelegate(": "Use .off() instead",
10
+ ".live(": "Use .on() instead",
11
+ ".die(": "Use .off() instead",
12
+ ".load(": "Use .on('load', ...) instead",
13
+ ".unload(": "Use .on('unload', ...) instead",
14
+ ".error(": "Use .on('error', ...) instead",
15
+ ".size(": "Use .length instead",
16
+ ".andSelf(": "Use .addBack() instead",
17
+ ".toggle(": "Use .on('click', ...) with state instead",
18
+ "$.browser": "Use feature detection instead",
19
+ "$.sub(": "Removed in jQuery 1.9",
20
+ "$.isArray(": "Use Array.isArray() instead",
21
+ "$.parseJSON(": "Use JSON.parse() instead",
22
+ "$.trim(": "Use String.trim() instead",
23
+ "$.now()": "Use Date.now() instead",
24
+ };
25
+ // React hooks list
26
+ const REACT_HOOKS = [
27
+ "useState",
28
+ "useEffect",
29
+ "useContext",
30
+ "useReducer",
31
+ "useCallback",
32
+ "useMemo",
33
+ "useRef",
34
+ "useImperativeHandle",
35
+ "useLayoutEffect",
36
+ "useDebugValue",
37
+ "useDeferredValue",
38
+ "useTransition",
39
+ "useId",
40
+ "useSyncExternalStore",
41
+ "useInsertionEffect",
42
+ ];
43
+ // Analyze React component
44
+ function analyzeReactFile(content, filepath) {
45
+ const components = [];
46
+ const lines = content.split("\n");
47
+ try {
48
+ const sourceFile = ts.createSourceFile(filepath, content, ts.ScriptTarget.Latest, true, filepath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.JSX);
49
+ function visit(node) {
50
+ const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
51
+ // Function component
52
+ if (ts.isFunctionDeclaration(node) && node.name) {
53
+ const name = node.name.text;
54
+ if (/^[A-Z]/.test(name)) {
55
+ const component = analyzeReactComponent(node, name, "function", line, content, filepath);
56
+ if (component)
57
+ components.push(component);
58
+ }
59
+ }
60
+ // Arrow function component
61
+ if (ts.isVariableStatement(node)) {
62
+ for (const decl of node.declarationList.declarations) {
63
+ if (ts.isIdentifier(decl.name) && decl.initializer) {
64
+ const name = decl.name.text;
65
+ if (/^[A-Z]/.test(name)) {
66
+ if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {
67
+ const component = analyzeReactComponent(decl.initializer, name, "arrow", line, content, filepath);
68
+ if (component)
69
+ components.push(component);
70
+ }
71
+ // Check for React.memo, React.forwardRef
72
+ if (ts.isCallExpression(decl.initializer)) {
73
+ const expr = decl.initializer.expression;
74
+ if (ts.isPropertyAccessExpression(expr)) {
75
+ const methodName = expr.name.text;
76
+ if (["memo", "forwardRef"].includes(methodName)) {
77
+ const arg = decl.initializer.arguments[0];
78
+ if (arg && (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg))) {
79
+ const component = analyzeReactComponent(arg, name, "arrow", line, content, filepath);
80
+ if (component) {
81
+ component.memoized = methodName === "memo";
82
+ components.push(component);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ // Class component
93
+ if (ts.isClassDeclaration(node) && node.name) {
94
+ const name = node.name.text;
95
+ // Check if extends React.Component or Component
96
+ if (node.heritageClauses) {
97
+ for (const clause of node.heritageClauses) {
98
+ const extendsText = clause.types.map((t) => t.expression.getText(sourceFile)).join(", ");
99
+ if (extendsText.includes("Component") || extendsText.includes("PureComponent")) {
100
+ const component = {
101
+ name,
102
+ type: "class",
103
+ filepath,
104
+ line,
105
+ props: [],
106
+ hooks: [],
107
+ stateVariables: [],
108
+ effects: 0,
109
+ memoized: extendsText.includes("PureComponent"),
110
+ issues: ["Class components are legacy - consider converting to function components"],
111
+ };
112
+ components.push(component);
113
+ }
114
+ }
115
+ }
116
+ }
117
+ ts.forEachChild(node, visit);
118
+ }
119
+ visit(sourceFile);
120
+ }
121
+ catch {
122
+ // Fallback to regex
123
+ const componentRegex = /(?:function|const|let|var)\s+([A-Z]\w+)\s*[=:]\s*(?:\([^)]*\)|[^=])*=>/g;
124
+ let match;
125
+ while ((match = componentRegex.exec(content)) !== null) {
126
+ const line = content.substring(0, match.index).split("\n").length;
127
+ components.push({
128
+ name: match[1],
129
+ type: "arrow",
130
+ filepath,
131
+ line,
132
+ props: [],
133
+ hooks: [],
134
+ stateVariables: [],
135
+ effects: 0,
136
+ memoized: false,
137
+ issues: [],
138
+ });
139
+ }
140
+ }
141
+ return components;
142
+ }
143
+ // Analyze a React component node
144
+ function analyzeReactComponent(node, name, type, line, content, filepath) {
145
+ const component = {
146
+ name,
147
+ type,
148
+ filepath,
149
+ line,
150
+ props: [],
151
+ hooks: [],
152
+ stateVariables: [],
153
+ effects: 0,
154
+ memoized: false,
155
+ issues: [],
156
+ };
157
+ const nodeText = node.getText();
158
+ // Find hooks
159
+ for (const hook of REACT_HOOKS) {
160
+ const hookRegex = new RegExp(`\\b${hook}\\s*\\(`, "g");
161
+ const matches = nodeText.match(hookRegex);
162
+ if (matches) {
163
+ component.hooks.push(`${hook} (${matches.length})`);
164
+ if (hook === "useEffect" || hook === "useLayoutEffect") {
165
+ component.effects += matches.length;
166
+ }
167
+ }
168
+ }
169
+ // Find useState variables
170
+ const useStateRegex = /const\s+\[(\w+),\s*set\w+\]\s*=\s*useState/g;
171
+ let match;
172
+ while ((match = useStateRegex.exec(nodeText)) !== null) {
173
+ component.stateVariables.push(match[1]);
174
+ }
175
+ // Check for issues
176
+ // 1. Missing dependency array in useEffect
177
+ const effectWithoutDeps = /useEffect\s*\(\s*\([^)]*\)\s*=>\s*\{[^}]+\}\s*\)/g;
178
+ if (effectWithoutDeps.test(nodeText)) {
179
+ component.issues.push("useEffect without dependency array - may cause infinite loops");
180
+ }
181
+ // 2. Too many state variables
182
+ if (component.stateVariables.length > 5) {
183
+ component.issues.push(`Too many useState (${component.stateVariables.length}) - consider useReducer`);
184
+ }
185
+ // 3. Too many effects
186
+ if (component.effects > 3) {
187
+ component.issues.push(`Too many useEffect (${component.effects}) - consider splitting component`);
188
+ }
189
+ // 4. Check for inline function in JSX (performance issue)
190
+ const inlineHandler = /on\w+\s*=\s*\{\s*\([^)]*\)\s*=>/g;
191
+ if (inlineHandler.test(nodeText)) {
192
+ component.issues.push("Inline arrow functions in JSX props - consider useCallback");
193
+ }
194
+ return component;
195
+ }
196
+ // Analyze Vue file
197
+ function analyzeVueFile(content, filepath) {
198
+ const component = {
199
+ name: path.basename(filepath, path.extname(filepath)),
200
+ filepath,
201
+ type: "options",
202
+ props: [],
203
+ emits: [],
204
+ data: [],
205
+ computed: [],
206
+ methods: [],
207
+ watchers: [],
208
+ lifecycle: [],
209
+ composables: [],
210
+ issues: [],
211
+ };
212
+ // Check for script setup (Composition API with <script setup>)
213
+ if (/<script\s+setup/.test(content)) {
214
+ component.type = "script-setup";
215
+ // Find defineProps
216
+ const propsMatch = content.match(/defineProps\s*[<(]([^>)]+)[>)]/);
217
+ if (propsMatch) {
218
+ const propsContent = propsMatch[1];
219
+ const propNames = propsContent.match(/\w+(?=\s*[?:])/g);
220
+ if (propNames)
221
+ component.props = propNames;
222
+ }
223
+ // Find defineEmits
224
+ const emitsMatch = content.match(/defineEmits\s*[<(]([^>)]+)[>)]/);
225
+ if (emitsMatch) {
226
+ const emitsContent = emitsMatch[1];
227
+ const emitNames = emitsContent.match(/['"](\w+)['"]/g);
228
+ if (emitNames)
229
+ component.emits = emitNames.map((e) => e.replace(/['"]/g, ""));
230
+ }
231
+ // Find composables (useXxx)
232
+ const composableRegex = /\buse[A-Z]\w+\s*\(/g;
233
+ const composables = content.match(composableRegex);
234
+ if (composables) {
235
+ component.composables = [...new Set(composables.map((c) => c.replace("(", "")))];
236
+ }
237
+ // Find refs and reactives
238
+ const refRegex = /(?:const|let)\s+(\w+)\s*=\s*(?:ref|reactive|computed)\s*\(/g;
239
+ let match;
240
+ while ((match = refRegex.exec(content)) !== null) {
241
+ component.data.push(match[1]);
242
+ }
243
+ }
244
+ // Check for Composition API (setup function)
245
+ else if (/setup\s*\(\s*(?:props|[^)]*)\s*\)\s*\{/.test(content)) {
246
+ component.type = "composition";
247
+ // Find return object properties
248
+ const returnMatch = content.match(/return\s*\{([^}]+)\}/);
249
+ if (returnMatch) {
250
+ const returnContent = returnMatch[1];
251
+ const names = returnContent.match(/\w+(?=\s*[,}])/g);
252
+ if (names)
253
+ component.methods = names;
254
+ }
255
+ }
256
+ // Options API
257
+ else {
258
+ component.type = "options";
259
+ // Props
260
+ const propsMatch = content.match(/props\s*:\s*\[([^\]]+)\]/);
261
+ if (propsMatch) {
262
+ component.props = propsMatch[1].match(/['"](\w+)['"]/g)?.map((p) => p.replace(/['"]/g, "")) || [];
263
+ }
264
+ const propsObjMatch = content.match(/props\s*:\s*\{([^}]+)\}/);
265
+ if (propsObjMatch) {
266
+ const propNames = propsObjMatch[1].match(/(\w+)\s*:/g);
267
+ if (propNames)
268
+ component.props = propNames.map((p) => p.replace(":", "").trim());
269
+ }
270
+ // Data
271
+ const dataMatch = content.match(/data\s*\(\s*\)\s*\{[^}]*return\s*\{([^}]+)\}/);
272
+ if (dataMatch) {
273
+ const dataNames = dataMatch[1].match(/(\w+)\s*:/g);
274
+ if (dataNames)
275
+ component.data = dataNames.map((d) => d.replace(":", "").trim());
276
+ }
277
+ // Computed
278
+ const computedMatch = content.match(/computed\s*:\s*\{([^}]+)\}/);
279
+ if (computedMatch) {
280
+ const computedNames = computedMatch[1].match(/(\w+)\s*[:(]/g);
281
+ if (computedNames)
282
+ component.computed = computedNames.map((c) => c.replace(/[:(]/g, "").trim());
283
+ }
284
+ // Methods
285
+ const methodsMatch = content.match(/methods\s*:\s*\{([^}]+)\}/);
286
+ if (methodsMatch) {
287
+ const methodNames = methodsMatch[1].match(/(\w+)\s*\(/g);
288
+ if (methodNames)
289
+ component.methods = methodNames.map((m) => m.replace("(", "").trim());
290
+ }
291
+ // Watch
292
+ const watchMatch = content.match(/watch\s*:\s*\{([^}]+)\}/);
293
+ if (watchMatch) {
294
+ const watchNames = watchMatch[1].match(/['"]?(\w+)['"]?\s*[:(]/g);
295
+ if (watchNames)
296
+ component.watchers = watchNames.map((w) => w.replace(/['":()]/g, "").trim());
297
+ }
298
+ }
299
+ // Lifecycle hooks (both APIs)
300
+ const lifecycleHooks = [
301
+ "beforeCreate", "created", "beforeMount", "mounted",
302
+ "beforeUpdate", "updated", "beforeUnmount", "unmounted",
303
+ "onBeforeMount", "onMounted", "onBeforeUpdate", "onUpdated",
304
+ "onBeforeUnmount", "onUnmounted",
305
+ ];
306
+ for (const hook of lifecycleHooks) {
307
+ if (content.includes(hook)) {
308
+ component.lifecycle.push(hook);
309
+ }
310
+ }
311
+ // Issues
312
+ if (component.type === "options" && component.methods.length > 10) {
313
+ component.issues.push("Too many methods - consider splitting component");
314
+ }
315
+ if (component.data.length > 10) {
316
+ component.issues.push("Too many data properties - consider splitting component");
317
+ }
318
+ if (component.watchers.length > 5) {
319
+ component.issues.push("Too many watchers - may impact performance");
320
+ }
321
+ return component;
322
+ }
323
+ // Analyze jQuery usage
324
+ function analyzeJQueryFile(content, filepath) {
325
+ const info = {
326
+ filepath,
327
+ selectors: [],
328
+ events: [],
329
+ ajax: [],
330
+ deprecated: [],
331
+ domManipulations: 0,
332
+ issues: [],
333
+ };
334
+ const lines = content.split("\n");
335
+ for (let i = 0; i < lines.length; i++) {
336
+ const line = lines[i];
337
+ const lineNum = i + 1;
338
+ // Find selectors
339
+ const selectorRegex = /\$\(\s*['"]([^'"]+)['"]\s*\)/g;
340
+ let match;
341
+ while ((match = selectorRegex.exec(line)) !== null) {
342
+ info.selectors.push({ selector: match[1], line: lineNum });
343
+ }
344
+ // Find events
345
+ const eventRegex = /\.(on|click|submit|change|focus|blur|keyup|keydown|mouseover|mouseout)\s*\(/g;
346
+ while ((match = eventRegex.exec(line)) !== null) {
347
+ info.events.push({ event: match[1], line: lineNum });
348
+ }
349
+ // Find AJAX
350
+ const ajaxRegex = /\$\.(ajax|get|post|getJSON)\s*\(/g;
351
+ while ((match = ajaxRegex.exec(line)) !== null) {
352
+ info.ajax.push({ method: match[1], line: lineNum });
353
+ }
354
+ // Find deprecated methods
355
+ for (const [deprecated, suggestion] of Object.entries(DEPRECATED_JQUERY)) {
356
+ if (line.includes(deprecated)) {
357
+ info.deprecated.push({ method: deprecated, line: lineNum, suggestion });
358
+ }
359
+ }
360
+ // Count DOM manipulations
361
+ const domMethods = [".html(", ".text(", ".append(", ".prepend(", ".after(", ".before(", ".remove(", ".empty(", ".attr(", ".css(", ".addClass(", ".removeClass("];
362
+ for (const method of domMethods) {
363
+ if (line.includes(method)) {
364
+ info.domManipulations++;
365
+ }
366
+ }
367
+ }
368
+ // Issues
369
+ if (info.deprecated.length > 0) {
370
+ info.issues.push(`${info.deprecated.length} deprecated jQuery methods found`);
371
+ }
372
+ // Check for inefficient selectors
373
+ const inefficientSelectors = info.selectors.filter((s) => s.selector.startsWith("*") ||
374
+ s.selector.includes(" > * ") ||
375
+ /^\w+\s+\w+\s+\w+/.test(s.selector) // Deep nesting
376
+ );
377
+ if (inefficientSelectors.length > 0) {
378
+ info.issues.push(`${inefficientSelectors.length} potentially inefficient selectors`);
379
+ }
380
+ // Check for too many DOM manipulations
381
+ if (info.domManipulations > 20) {
382
+ info.issues.push("High DOM manipulation count - consider batching or virtual DOM");
383
+ }
384
+ return info;
385
+ }
386
+ // React Check Tool
387
+ export const reactCheckTool = {
388
+ name: "react_check",
389
+ description: "Analyze React components (React 뢄석). Finds components, hooks usage, potential issues. Use when user asks: 'react check', 'react 뢄석', 'component 뢄석'.",
390
+ parameters: {
391
+ type: "object",
392
+ required: ["pattern"],
393
+ properties: {
394
+ pattern: {
395
+ type: "string",
396
+ description: "Glob pattern (e.g., src/**/*.{tsx,jsx})",
397
+ },
398
+ },
399
+ },
400
+ handler: async (args) => {
401
+ try {
402
+ const { glob } = await import("glob");
403
+ const pattern = args.pattern;
404
+ const files = await glob(pattern, {
405
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
406
+ });
407
+ const allComponents = [];
408
+ for (const file of files) {
409
+ if (!/\.(tsx|jsx)$/.test(file))
410
+ continue;
411
+ try {
412
+ const content = fs.readFileSync(file, "utf-8");
413
+ const components = analyzeReactFile(content, file);
414
+ allComponents.push(...components);
415
+ }
416
+ catch {
417
+ // Skip unparseable files
418
+ }
419
+ }
420
+ const lines = [];
421
+ lines.push("=== React μ»΄ν¬λ„ŒνŠΈ 뢄석 ===");
422
+ lines.push("");
423
+ lines.push(`πŸ“Š 총 ${allComponents.length}개 μ»΄ν¬λ„ŒνŠΈ 발견`);
424
+ lines.push("");
425
+ // Group by type
426
+ const byType = {
427
+ function: allComponents.filter((c) => c.type === "function"),
428
+ arrow: allComponents.filter((c) => c.type === "arrow"),
429
+ class: allComponents.filter((c) => c.type === "class"),
430
+ };
431
+ lines.push(` Function: ${byType.function.length}개`);
432
+ lines.push(` Arrow: ${byType.arrow.length}개`);
433
+ lines.push(` Class: ${byType.class.length}개 ${byType.class.length > 0 ? "(λ ˆκ±°μ‹œ)" : ""}`);
434
+ lines.push("");
435
+ // List components with issues
436
+ const withIssues = allComponents.filter((c) => c.issues.length > 0);
437
+ if (withIssues.length > 0) {
438
+ lines.push("⚠️ 이슈 발견:");
439
+ for (const comp of withIssues) {
440
+ lines.push(` ${comp.name} (${path.relative(process.cwd(), comp.filepath)}:${comp.line})`);
441
+ for (const issue of comp.issues) {
442
+ lines.push(` - ${issue}`);
443
+ }
444
+ }
445
+ lines.push("");
446
+ }
447
+ // Hooks usage summary
448
+ const hooksUsage = {};
449
+ for (const comp of allComponents) {
450
+ for (const hook of comp.hooks) {
451
+ const hookName = hook.split(" ")[0];
452
+ hooksUsage[hookName] = (hooksUsage[hookName] || 0) + 1;
453
+ }
454
+ }
455
+ if (Object.keys(hooksUsage).length > 0) {
456
+ lines.push("πŸͺ Hooks μ‚¬μš©:");
457
+ for (const [hook, count] of Object.entries(hooksUsage).sort((a, b) => b[1] - a[1])) {
458
+ lines.push(` ${hook}: ${count}회`);
459
+ }
460
+ lines.push("");
461
+ }
462
+ // Memoized components
463
+ const memoized = allComponents.filter((c) => c.memoized);
464
+ if (memoized.length > 0) {
465
+ lines.push(`βœ… Memoized μ»΄ν¬λ„ŒνŠΈ: ${memoized.length}개`);
466
+ for (const comp of memoized) {
467
+ lines.push(` ${comp.name}`);
468
+ }
469
+ }
470
+ return { success: true, content: lines.join("\n") };
471
+ }
472
+ catch (error) {
473
+ return { success: false, content: "", error: String(error) };
474
+ }
475
+ },
476
+ };
477
+ // Vue Check Tool
478
+ export const vueCheckTool = {
479
+ name: "vue_check",
480
+ description: "Analyze Vue components (Vue 뢄석). Finds components, composition API usage, issues. Use when user asks: 'vue check', 'vue 뢄석', 'vue component'.",
481
+ parameters: {
482
+ type: "object",
483
+ required: ["pattern"],
484
+ properties: {
485
+ pattern: {
486
+ type: "string",
487
+ description: "Glob pattern (e.g., src/**/*.vue)",
488
+ },
489
+ },
490
+ },
491
+ handler: async (args) => {
492
+ try {
493
+ const { glob } = await import("glob");
494
+ const pattern = args.pattern;
495
+ const files = await glob(pattern, {
496
+ ignore: ["**/node_modules/**", "**/dist/**"],
497
+ });
498
+ const allComponents = [];
499
+ for (const file of files) {
500
+ if (!file.endsWith(".vue"))
501
+ continue;
502
+ try {
503
+ const content = fs.readFileSync(file, "utf-8");
504
+ const component = analyzeVueFile(content, file);
505
+ allComponents.push(component);
506
+ }
507
+ catch {
508
+ // Skip unparseable files
509
+ }
510
+ }
511
+ const lines = [];
512
+ lines.push("=== Vue μ»΄ν¬λ„ŒνŠΈ 뢄석 ===");
513
+ lines.push("");
514
+ lines.push(`πŸ“Š 총 ${allComponents.length}개 μ»΄ν¬λ„ŒνŠΈ 발견`);
515
+ lines.push("");
516
+ // Group by type
517
+ const byType = {
518
+ "script-setup": allComponents.filter((c) => c.type === "script-setup"),
519
+ composition: allComponents.filter((c) => c.type === "composition"),
520
+ options: allComponents.filter((c) => c.type === "options"),
521
+ };
522
+ lines.push(` Script Setup: ${byType["script-setup"].length}개 (ꢌμž₯)`);
523
+ lines.push(` Composition API: ${byType.composition.length}개`);
524
+ lines.push(` Options API: ${byType.options.length}개`);
525
+ lines.push("");
526
+ // List components with issues
527
+ const withIssues = allComponents.filter((c) => c.issues.length > 0);
528
+ if (withIssues.length > 0) {
529
+ lines.push("⚠️ 이슈 발견:");
530
+ for (const comp of withIssues) {
531
+ lines.push(` ${comp.name}`);
532
+ for (const issue of comp.issues) {
533
+ lines.push(` - ${issue}`);
534
+ }
535
+ }
536
+ lines.push("");
537
+ }
538
+ // Composables usage
539
+ const composables = {};
540
+ for (const comp of allComponents) {
541
+ for (const c of comp.composables) {
542
+ composables[c] = (composables[c] || 0) + 1;
543
+ }
544
+ }
545
+ if (Object.keys(composables).length > 0) {
546
+ lines.push("πŸ”§ Composables μ‚¬μš©:");
547
+ for (const [name, count] of Object.entries(composables).sort((a, b) => b[1] - a[1]).slice(0, 10)) {
548
+ lines.push(` ${name}: ${count}회`);
549
+ }
550
+ lines.push("");
551
+ }
552
+ // Lifecycle hooks usage
553
+ const lifecycleCount = {};
554
+ for (const comp of allComponents) {
555
+ for (const hook of comp.lifecycle) {
556
+ lifecycleCount[hook] = (lifecycleCount[hook] || 0) + 1;
557
+ }
558
+ }
559
+ if (Object.keys(lifecycleCount).length > 0) {
560
+ lines.push("πŸ”„ Lifecycle Hooks:");
561
+ for (const [hook, count] of Object.entries(lifecycleCount).sort((a, b) => b[1] - a[1])) {
562
+ lines.push(` ${hook}: ${count}회`);
563
+ }
564
+ }
565
+ return { success: true, content: lines.join("\n") };
566
+ }
567
+ catch (error) {
568
+ return { success: false, content: "", error: String(error) };
569
+ }
570
+ },
571
+ };
572
+ // jQuery Check Tool
573
+ export const jqueryCheckTool = {
574
+ name: "jquery_check",
575
+ description: "Analyze jQuery usage (jQuery 뢄석). Finds selectors, events, deprecated methods. Use when user asks: 'jquery check', 'jquery 뢄석', 'jquery deprecated'.",
576
+ parameters: {
577
+ type: "object",
578
+ required: ["pattern"],
579
+ properties: {
580
+ pattern: {
581
+ type: "string",
582
+ description: "Glob pattern (e.g., src/**/*.js)",
583
+ },
584
+ },
585
+ },
586
+ handler: async (args) => {
587
+ try {
588
+ const { glob } = await import("glob");
589
+ const pattern = args.pattern;
590
+ const files = await glob(pattern, {
591
+ ignore: ["**/node_modules/**", "**/dist/**", "**/*.min.js"],
592
+ });
593
+ const allUsages = [];
594
+ for (const file of files) {
595
+ if (!/\.js$/.test(file))
596
+ continue;
597
+ try {
598
+ const content = fs.readFileSync(file, "utf-8");
599
+ // Check if file uses jQuery
600
+ if (content.includes("$(") || content.includes("jQuery(")) {
601
+ const usage = analyzeJQueryFile(content, file);
602
+ allUsages.push(usage);
603
+ }
604
+ }
605
+ catch {
606
+ // Skip unreadable files
607
+ }
608
+ }
609
+ if (allUsages.length === 0) {
610
+ return { success: true, content: "jQuery μ‚¬μš©μ„ μ°Ύμ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€." };
611
+ }
612
+ const lines = [];
613
+ lines.push("=== jQuery μ‚¬μš© 뢄석 ===");
614
+ lines.push("");
615
+ lines.push(`πŸ“Š ${allUsages.length}개 νŒŒμΌμ—μ„œ jQuery μ‚¬μš© 발견`);
616
+ lines.push("");
617
+ // Total stats
618
+ const totalSelectors = allUsages.reduce((sum, u) => sum + u.selectors.length, 0);
619
+ const totalEvents = allUsages.reduce((sum, u) => sum + u.events.length, 0);
620
+ const totalAjax = allUsages.reduce((sum, u) => sum + u.ajax.length, 0);
621
+ const totalDeprecated = allUsages.reduce((sum, u) => sum + u.deprecated.length, 0);
622
+ lines.push("πŸ“ˆ 톡계:");
623
+ lines.push(` μ…€λ ‰ν„°: ${totalSelectors}개`);
624
+ lines.push(` 이벀트 바인딩: ${totalEvents}개`);
625
+ lines.push(` AJAX 호좜: ${totalAjax}개`);
626
+ lines.push(` Deprecated λ©”μ„œλ“œ: ${totalDeprecated}개`);
627
+ lines.push("");
628
+ // Deprecated methods
629
+ if (totalDeprecated > 0) {
630
+ lines.push("⚠️ Deprecated λ©”μ„œλ“œ:");
631
+ for (const usage of allUsages) {
632
+ for (const dep of usage.deprecated) {
633
+ lines.push(` ${path.relative(process.cwd(), usage.filepath)}:${dep.line}`);
634
+ lines.push(` ${dep.method} β†’ ${dep.suggestion}`);
635
+ }
636
+ }
637
+ lines.push("");
638
+ }
639
+ // Files with issues
640
+ const withIssues = allUsages.filter((u) => u.issues.length > 0);
641
+ if (withIssues.length > 0) {
642
+ lines.push("πŸ“‹ νŒŒμΌλ³„ 이슈:");
643
+ for (const usage of withIssues) {
644
+ lines.push(` ${path.relative(process.cwd(), usage.filepath)}`);
645
+ for (const issue of usage.issues) {
646
+ lines.push(` - ${issue}`);
647
+ }
648
+ }
649
+ lines.push("");
650
+ }
651
+ // Common selectors
652
+ const selectorCounts = {};
653
+ for (const usage of allUsages) {
654
+ for (const sel of usage.selectors) {
655
+ selectorCounts[sel.selector] = (selectorCounts[sel.selector] || 0) + 1;
656
+ }
657
+ }
658
+ const commonSelectors = Object.entries(selectorCounts)
659
+ .sort((a, b) => b[1] - a[1])
660
+ .slice(0, 10);
661
+ if (commonSelectors.length > 0) {
662
+ lines.push("🎯 자주 μ‚¬μš©λ˜λŠ” μ…€λ ‰ν„°:");
663
+ for (const [selector, count] of commonSelectors) {
664
+ lines.push(` "${selector}": ${count}회`);
665
+ }
666
+ }
667
+ return { success: true, content: lines.join("\n") };
668
+ }
669
+ catch (error) {
670
+ return { success: false, content: "", error: String(error) };
671
+ }
672
+ },
673
+ };
674
+ // Export all frontend tools
675
+ export const frontendTools = [
676
+ reactCheckTool,
677
+ vueCheckTool,
678
+ jqueryCheckTool,
679
+ ];
680
+ //# sourceMappingURL=frontendAst.js.map