activo 0.4.4 β†’ 0.5.0

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