@compilr-dev/agents-coding-python 0.1.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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +40 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +27 -0
  5. package/dist/parser/index.d.ts +6 -0
  6. package/dist/parser/index.d.ts.map +1 -0
  7. package/dist/parser/index.js +5 -0
  8. package/dist/parser/node-types.d.ts +119 -0
  9. package/dist/parser/node-types.d.ts.map +1 -0
  10. package/dist/parser/node-types.js +4 -0
  11. package/dist/parser/python-parser.d.ts +85 -0
  12. package/dist/parser/python-parser.d.ts.map +1 -0
  13. package/dist/parser/python-parser.js +477 -0
  14. package/dist/skills/index.d.ts +26 -0
  15. package/dist/skills/index.d.ts.map +1 -0
  16. package/dist/skills/index.js +36 -0
  17. package/dist/skills/python-best-practices.d.ts +7 -0
  18. package/dist/skills/python-best-practices.d.ts.map +1 -0
  19. package/dist/skills/python-best-practices.js +78 -0
  20. package/dist/skills/python-code-health.d.ts +7 -0
  21. package/dist/skills/python-code-health.d.ts.map +1 -0
  22. package/dist/skills/python-code-health.js +209 -0
  23. package/dist/skills/python-code-structure.d.ts +7 -0
  24. package/dist/skills/python-code-structure.d.ts.map +1 -0
  25. package/dist/skills/python-code-structure.js +155 -0
  26. package/dist/skills/python-dependency-audit.d.ts +7 -0
  27. package/dist/skills/python-dependency-audit.d.ts.map +1 -0
  28. package/dist/skills/python-dependency-audit.js +246 -0
  29. package/dist/skills/python-refactor-impact.d.ts +7 -0
  30. package/dist/skills/python-refactor-impact.d.ts.map +1 -0
  31. package/dist/skills/python-refactor-impact.js +232 -0
  32. package/dist/tools/extract-docstrings.d.ts +70 -0
  33. package/dist/tools/extract-docstrings.d.ts.map +1 -0
  34. package/dist/tools/extract-docstrings.js +575 -0
  35. package/dist/tools/find-dead-code.d.ts +62 -0
  36. package/dist/tools/find-dead-code.d.ts.map +1 -0
  37. package/dist/tools/find-dead-code.js +422 -0
  38. package/dist/tools/find-duplicates.d.ts +65 -0
  39. package/dist/tools/find-duplicates.d.ts.map +1 -0
  40. package/dist/tools/find-duplicates.js +289 -0
  41. package/dist/tools/find-implementations.d.ts +71 -0
  42. package/dist/tools/find-implementations.d.ts.map +1 -0
  43. package/dist/tools/find-implementations.js +342 -0
  44. package/dist/tools/find-patterns.d.ts +71 -0
  45. package/dist/tools/find-patterns.d.ts.map +1 -0
  46. package/dist/tools/find-patterns.js +477 -0
  47. package/dist/tools/find-references.d.ts +66 -0
  48. package/dist/tools/find-references.d.ts.map +1 -0
  49. package/dist/tools/find-references.js +306 -0
  50. package/dist/tools/find-symbol.d.ts +86 -0
  51. package/dist/tools/find-symbol.d.ts.map +1 -0
  52. package/dist/tools/find-symbol.js +414 -0
  53. package/dist/tools/get-call-graph.d.ts +89 -0
  54. package/dist/tools/get-call-graph.d.ts.map +1 -0
  55. package/dist/tools/get-call-graph.js +431 -0
  56. package/dist/tools/get-class-hierarchy.d.ts +38 -0
  57. package/dist/tools/get-class-hierarchy.d.ts.map +1 -0
  58. package/dist/tools/get-class-hierarchy.js +289 -0
  59. package/dist/tools/get-complexity.d.ts +61 -0
  60. package/dist/tools/get-complexity.d.ts.map +1 -0
  61. package/dist/tools/get-complexity.js +384 -0
  62. package/dist/tools/get-dependency-graph.d.ts +85 -0
  63. package/dist/tools/get-dependency-graph.d.ts.map +1 -0
  64. package/dist/tools/get-dependency-graph.js +387 -0
  65. package/dist/tools/get-exports.d.ts +78 -0
  66. package/dist/tools/get-exports.d.ts.map +1 -0
  67. package/dist/tools/get-exports.js +437 -0
  68. package/dist/tools/get-file-structure.d.ts +28 -0
  69. package/dist/tools/get-file-structure.d.ts.map +1 -0
  70. package/dist/tools/get-file-structure.js +186 -0
  71. package/dist/tools/get-imports.d.ts +34 -0
  72. package/dist/tools/get-imports.d.ts.map +1 -0
  73. package/dist/tools/get-imports.js +455 -0
  74. package/dist/tools/get-signature.d.ts +100 -0
  75. package/dist/tools/get-signature.d.ts.map +1 -0
  76. package/dist/tools/get-signature.js +800 -0
  77. package/dist/tools/index.d.ts +55 -0
  78. package/dist/tools/index.d.ts.map +1 -0
  79. package/dist/tools/index.js +75 -0
  80. package/dist/tools/types.d.ts +378 -0
  81. package/dist/tools/types.d.ts.map +1 -0
  82. package/dist/tools/types.js +4 -0
  83. package/package.json +85 -0
@@ -0,0 +1,422 @@
1
+ /**
2
+ * findDeadCode Tool
3
+ *
4
+ * Find potentially unused functions, classes, and variables in Python code.
5
+ * Uses static analysis to identify code that may be dead.
6
+ */
7
+ import * as fs from "node:fs/promises";
8
+ import * as path from "node:path";
9
+ import { defineTool, createSuccessResult, createErrorResult, } from "@compilr-dev/agents";
10
+ import { parseFile, parseFunction, parseClass, } from "../parser/python-parser.js";
11
+ // Tool description
12
+ const TOOL_DESCRIPTION = `Find potentially unused functions, classes, and variables in Python code.
13
+ Analyzes the codebase to identify code that may be dead (never used).
14
+ Useful for cleanup and reducing code size.`;
15
+ // Tool input schema
16
+ const TOOL_INPUT_SCHEMA = {
17
+ type: "object",
18
+ properties: {
19
+ path: {
20
+ type: "string",
21
+ description: "Directory to analyze",
22
+ },
23
+ includeTests: {
24
+ type: "boolean",
25
+ description: "Include test files in analysis (default: false)",
26
+ default: false,
27
+ },
28
+ checkFunctions: {
29
+ type: "boolean",
30
+ description: "Check for unused functions (default: true)",
31
+ default: true,
32
+ },
33
+ checkClasses: {
34
+ type: "boolean",
35
+ description: "Check for unused classes (default: true)",
36
+ default: true,
37
+ },
38
+ checkVariables: {
39
+ type: "boolean",
40
+ description: "Check for unused variables (default: false)",
41
+ default: false,
42
+ },
43
+ maxFiles: {
44
+ type: "number",
45
+ description: "Maximum files to analyze (default: 100)",
46
+ default: 100,
47
+ },
48
+ },
49
+ required: ["path"],
50
+ };
51
+ // Default exclusions
52
+ const DEFAULT_EXCLUDE = [
53
+ "node_modules",
54
+ "__pycache__",
55
+ ".git",
56
+ "venv",
57
+ ".venv",
58
+ "env",
59
+ "dist",
60
+ "build",
61
+ ".tox",
62
+ ".eggs",
63
+ ];
64
+ const TEST_PATTERNS = ["test_", "_test.py", "tests/", "test/", "conftest.py"];
65
+ /**
66
+ * findDeadCode tool
67
+ */
68
+ export const findDeadCodeTool = defineTool({
69
+ name: "find_dead_code_python",
70
+ description: TOOL_DESCRIPTION,
71
+ inputSchema: TOOL_INPUT_SCHEMA,
72
+ execute: executeFindDeadCode,
73
+ });
74
+ /**
75
+ * Execute the findDeadCode tool
76
+ */
77
+ async function executeFindDeadCode(input) {
78
+ const { path: inputPath, includeTests = false, checkFunctions = true, checkClasses = true, checkVariables = false, maxFiles = 100, } = input;
79
+ try {
80
+ const resolvedPath = path.resolve(inputPath);
81
+ // Check if path exists
82
+ try {
83
+ await fs.access(resolvedPath);
84
+ }
85
+ catch {
86
+ return createErrorResult(`Path not found: ${resolvedPath}`);
87
+ }
88
+ const stats = await fs.stat(resolvedPath);
89
+ if (!stats.isDirectory()) {
90
+ return createErrorResult("findDeadCode requires a directory path");
91
+ }
92
+ // Collect files
93
+ const files = [];
94
+ await collectFiles(resolvedPath, files, includeTests, maxFiles);
95
+ // Build symbol index
96
+ const definitions = [];
97
+ const usages = new Set();
98
+ const importedSymbols = new Set();
99
+ // First pass: collect all definitions and usages
100
+ for (const file of files) {
101
+ await analyzeFile(file, definitions, usages, importedSymbols);
102
+ }
103
+ // Find dead code
104
+ const deadCode = [];
105
+ // Stats
106
+ let totalFunctions = 0;
107
+ let unusedFunctions = 0;
108
+ let totalClasses = 0;
109
+ let unusedClasses = 0;
110
+ let totalVariables = 0;
111
+ let unusedVariables = 0;
112
+ for (const def of definitions) {
113
+ // Skip __init__.py files (typically exports)
114
+ if (def.path.endsWith("__init__.py")) {
115
+ continue;
116
+ }
117
+ // Skip dunder methods (always considered used)
118
+ if (def.isDunderMethod) {
119
+ continue;
120
+ }
121
+ const isUsed = usages.has(def.name) || importedSymbols.has(def.name);
122
+ // Check functions
123
+ if (def.kind === "function" && checkFunctions) {
124
+ totalFunctions++;
125
+ if (!isUsed && !def.isPrivate) {
126
+ // Non-private, not used
127
+ unusedFunctions++;
128
+ deadCode.push({
129
+ name: def.name,
130
+ path: def.path,
131
+ line: def.line,
132
+ kind: "function",
133
+ confidence: "medium",
134
+ reason: "Function is not called or imported anywhere in the codebase",
135
+ });
136
+ }
137
+ else if (!isUsed && def.isPrivate) {
138
+ // Private function not used in the file
139
+ unusedFunctions++;
140
+ deadCode.push({
141
+ name: def.name,
142
+ path: def.path,
143
+ line: def.line,
144
+ kind: "function",
145
+ confidence: "high",
146
+ reason: "Private function is not called anywhere in the file",
147
+ });
148
+ }
149
+ }
150
+ // Check classes
151
+ if (def.kind === "class" && checkClasses) {
152
+ totalClasses++;
153
+ if (!isUsed) {
154
+ unusedClasses++;
155
+ deadCode.push({
156
+ name: def.name,
157
+ path: def.path,
158
+ line: def.line,
159
+ kind: "class",
160
+ confidence: def.isPrivate ? "high" : "medium",
161
+ reason: def.isPrivate
162
+ ? "Private class is not used anywhere in the file"
163
+ : "Class is not instantiated or referenced anywhere in the codebase",
164
+ });
165
+ }
166
+ }
167
+ // Check variables
168
+ if (def.kind === "variable" && checkVariables) {
169
+ totalVariables++;
170
+ if (!isUsed) {
171
+ unusedVariables++;
172
+ deadCode.push({
173
+ name: def.name,
174
+ path: def.path,
175
+ line: def.line,
176
+ kind: "variable",
177
+ confidence: "medium",
178
+ reason: "Variable is not referenced after declaration",
179
+ });
180
+ }
181
+ }
182
+ }
183
+ const result = {
184
+ path: resolvedPath,
185
+ deadCode,
186
+ stats: {
187
+ filesAnalyzed: files.length,
188
+ totalFunctions,
189
+ unusedFunctions,
190
+ totalClasses,
191
+ unusedClasses,
192
+ totalVariables,
193
+ unusedVariables,
194
+ },
195
+ };
196
+ return createSuccessResult(result);
197
+ }
198
+ catch (error) {
199
+ return createErrorResult(`Failed to find dead code: ${error instanceof Error ? error.message : String(error)}`);
200
+ }
201
+ }
202
+ /**
203
+ * Collect files to analyze
204
+ */
205
+ async function collectFiles(dirPath, files, includeTests, maxFiles, currentDepth = 0) {
206
+ if (currentDepth > 10 || files.length >= maxFiles)
207
+ return;
208
+ try {
209
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
210
+ for (const entry of entries) {
211
+ if (files.length >= maxFiles)
212
+ break;
213
+ const fullPath = path.join(dirPath, entry.name);
214
+ // Skip excluded directories
215
+ if (entry.isDirectory()) {
216
+ if (DEFAULT_EXCLUDE.includes(entry.name))
217
+ continue;
218
+ if (!includeTests && (entry.name === "tests" || entry.name === "test"))
219
+ continue;
220
+ await collectFiles(fullPath, files, includeTests, maxFiles, currentDepth + 1);
221
+ }
222
+ else if (entry.isFile()) {
223
+ // Only include Python files
224
+ if (fullPath.endsWith(".py") && !fullPath.endsWith(".pyi")) {
225
+ // Skip test files if not including tests
226
+ if (!includeTests &&
227
+ TEST_PATTERNS.some((p) => fullPath.includes(p))) {
228
+ continue;
229
+ }
230
+ files.push(fullPath);
231
+ }
232
+ }
233
+ }
234
+ }
235
+ catch {
236
+ // Ignore permission errors
237
+ }
238
+ }
239
+ /**
240
+ * Analyze a file for definitions and usages
241
+ */
242
+ async function analyzeFile(filePath, definitions, usages, importedSymbols) {
243
+ try {
244
+ const parseResult = await parseFile(filePath);
245
+ const { tree, source } = parseResult;
246
+ const rootNode = tree.rootNode;
247
+ // Collect definitions
248
+ for (const child of rootNode.children) {
249
+ // Function definitions
250
+ if (child.type === "function_definition" ||
251
+ child.type === "async_function_definition") {
252
+ const funcInfo = parseFunction(child, source);
253
+ const isPrivate = funcInfo.name.startsWith("_") && !funcInfo.name.startsWith("__");
254
+ const isDunderMethod = funcInfo.name.startsWith("__") && funcInfo.name.endsWith("__");
255
+ definitions.push({
256
+ name: funcInfo.name,
257
+ path: filePath,
258
+ line: funcInfo.line,
259
+ kind: "function",
260
+ isPrivate,
261
+ isDunderMethod,
262
+ });
263
+ }
264
+ // Class definitions
265
+ if (child.type === "class_definition") {
266
+ const classInfo = parseClass(child, source);
267
+ const isPrivate = classInfo.name.startsWith("_");
268
+ definitions.push({
269
+ name: classInfo.name,
270
+ path: filePath,
271
+ line: classInfo.line,
272
+ kind: "class",
273
+ isPrivate,
274
+ isDunderMethod: false,
275
+ });
276
+ // Also collect methods
277
+ const body = child.childForFieldName("body");
278
+ if (body) {
279
+ for (const member of body.children) {
280
+ if (member.type === "function_definition" ||
281
+ member.type === "async_function_definition") {
282
+ const methodInfo = parseFunction(member, source);
283
+ const isMethodPrivate = methodInfo.name.startsWith("_") &&
284
+ !methodInfo.name.startsWith("__");
285
+ const isMethodDunder = methodInfo.name.startsWith("__") &&
286
+ methodInfo.name.endsWith("__");
287
+ // Don't add methods as separate definitions for dead code detection
288
+ // (they're part of the class)
289
+ if (!isMethodDunder && isMethodPrivate) {
290
+ definitions.push({
291
+ name: `${classInfo.name}.${methodInfo.name}`,
292
+ path: filePath,
293
+ line: methodInfo.line,
294
+ kind: "method",
295
+ isPrivate: true,
296
+ isDunderMethod: false,
297
+ });
298
+ }
299
+ }
300
+ }
301
+ }
302
+ }
303
+ // Import statements - track what's imported
304
+ if (child.type === "import_statement") {
305
+ const nameNode = child.childForFieldName("name");
306
+ if (nameNode) {
307
+ importedSymbols.add(nameNode.text);
308
+ }
309
+ }
310
+ if (child.type === "import_from_statement") {
311
+ // from module import name1, name2
312
+ for (const c of child.children) {
313
+ if (c.type === "dotted_name" || c.type === "aliased_import") {
314
+ const alias = c.childForFieldName("alias");
315
+ const name = c.childForFieldName("name");
316
+ if (alias) {
317
+ importedSymbols.add(alias.text);
318
+ }
319
+ else if (name) {
320
+ importedSymbols.add(name.text);
321
+ }
322
+ else if (c.type === "dotted_name") {
323
+ importedSymbols.add(c.text.split(".")[0]);
324
+ }
325
+ }
326
+ }
327
+ }
328
+ // Top-level assignments (module-level variables)
329
+ if (child.type === "expression_statement") {
330
+ const expr = child.children[0];
331
+ if (expr?.type === "assignment") {
332
+ const left = expr.childForFieldName("left");
333
+ if (left?.type === "identifier") {
334
+ const isPrivate = left.text.startsWith("_");
335
+ definitions.push({
336
+ name: left.text,
337
+ path: filePath,
338
+ line: child.startPosition.row + 1,
339
+ kind: "variable",
340
+ isPrivate,
341
+ isDunderMethod: false,
342
+ });
343
+ }
344
+ }
345
+ }
346
+ }
347
+ // Collect usages (identifiers)
348
+ collectUsages(rootNode, usages, definitions);
349
+ }
350
+ catch {
351
+ // Ignore errors
352
+ }
353
+ }
354
+ /**
355
+ * Collect all identifier usages in the AST
356
+ */
357
+ function collectUsages(node, usages, definitions) {
358
+ // Skip definition nodes
359
+ if (node.type === "function_definition" ||
360
+ node.type === "async_function_definition" ||
361
+ node.type === "class_definition") {
362
+ const nameNode = node.childForFieldName("name");
363
+ // Still recurse into the body
364
+ for (const child of node.children) {
365
+ if (child !== nameNode) {
366
+ collectUsages(child, usages, definitions);
367
+ }
368
+ }
369
+ return;
370
+ }
371
+ // Track identifier usages
372
+ if (node.type === "identifier") {
373
+ const parent = node.parent;
374
+ // Skip if this is a definition site
375
+ if (parent &&
376
+ (parent.type === "function_definition" ||
377
+ parent.type === "async_function_definition" ||
378
+ parent.type === "class_definition") &&
379
+ parent.childForFieldName("name") === node) {
380
+ return;
381
+ }
382
+ // Skip if this is the left side of an assignment (definition)
383
+ if (parent?.type === "assignment" &&
384
+ parent.childForFieldName("left") === node) {
385
+ return;
386
+ }
387
+ // Skip if this is a parameter
388
+ if (parent?.type === "typed_parameter" ||
389
+ parent?.type === "default_parameter") {
390
+ return;
391
+ }
392
+ usages.add(node.text);
393
+ }
394
+ // Track attribute access (obj.method)
395
+ if (node.type === "attribute") {
396
+ const attr = node.childForFieldName("attribute");
397
+ if (attr) {
398
+ usages.add(attr.text);
399
+ }
400
+ }
401
+ // Recurse
402
+ for (const child of node.children) {
403
+ collectUsages(child, usages, definitions);
404
+ }
405
+ }
406
+ /**
407
+ * Factory function to create a customized findDeadCode tool
408
+ */
409
+ export function createFindDeadCodeTool(options) {
410
+ return defineTool({
411
+ name: "find_dead_code_python",
412
+ description: TOOL_DESCRIPTION,
413
+ inputSchema: TOOL_INPUT_SCHEMA,
414
+ execute: async (input) => {
415
+ const modifiedInput = {
416
+ ...input,
417
+ maxFiles: input.maxFiles ?? options?.defaultMaxFiles,
418
+ };
419
+ return executeFindDeadCode(modifiedInput);
420
+ },
421
+ });
422
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * findDuplicates Tool
3
+ *
4
+ * Detect duplicate code blocks in Python files using content hashing.
5
+ * Helps identify opportunities for refactoring and code reuse.
6
+ */
7
+ import type { Tool } from "@compilr-dev/agents";
8
+ /**
9
+ * Input for findDuplicates tool
10
+ */
11
+ export interface FindDuplicatesInput {
12
+ /** Directory to analyze */
13
+ path: string;
14
+ /** Minimum lines for a duplicate (default: 6) */
15
+ minLines?: number;
16
+ /** Minimum tokens for a duplicate (default: 50) */
17
+ minTokens?: number;
18
+ /** Ignore identical files (default: true) */
19
+ ignoreIdenticalFiles?: boolean;
20
+ /** Maximum files to analyze (default: 100) */
21
+ maxFiles?: number;
22
+ }
23
+ /**
24
+ * Code location
25
+ */
26
+ export interface CodeLocation {
27
+ path: string;
28
+ startLine: number;
29
+ endLine: number;
30
+ }
31
+ /**
32
+ * Duplicate group
33
+ */
34
+ export interface DuplicateGroup {
35
+ id: string;
36
+ lines: number;
37
+ tokens: number;
38
+ locations: CodeLocation[];
39
+ sample: string;
40
+ }
41
+ /**
42
+ * Result of findDuplicates
43
+ */
44
+ export interface FindDuplicatesResult {
45
+ path: string;
46
+ duplicates: DuplicateGroup[];
47
+ stats: {
48
+ filesAnalyzed: number;
49
+ duplicateGroups: number;
50
+ totalDuplicateLines: number;
51
+ percentageDuplicate: number;
52
+ };
53
+ }
54
+ /**
55
+ * findDuplicates tool
56
+ */
57
+ export declare const findDuplicatesTool: Tool<FindDuplicatesInput>;
58
+ /**
59
+ * Factory function to create a customized findDuplicates tool
60
+ */
61
+ export declare function createFindDuplicatesTool(options?: {
62
+ defaultMinLines?: number;
63
+ defaultMaxFiles?: number;
64
+ }): Tool<FindDuplicatesInput>;
65
+ //# sourceMappingURL=find-duplicates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-duplicates.d.ts","sourceRoot":"","sources":["../../src/tools/find-duplicates.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EAAE,IAAI,EAAuB,MAAM,qBAAqB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,KAAK,EAAE;QACL,aAAa,EAAE,MAAM,CAAC;QACtB,eAAe,EAAE,MAAM,CAAC;QACxB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AAiED;;GAEG;AACH,eAAO,MAAM,kBAAkB,2BAK7B,CAAC;AAmQH;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE;IACjD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,IAAI,CAAC,mBAAmB,CAAC,CAc5B"}