@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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/parser/index.d.ts +6 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +5 -0
- package/dist/parser/node-types.d.ts +119 -0
- package/dist/parser/node-types.d.ts.map +1 -0
- package/dist/parser/node-types.js +4 -0
- package/dist/parser/python-parser.d.ts +85 -0
- package/dist/parser/python-parser.d.ts.map +1 -0
- package/dist/parser/python-parser.js +477 -0
- package/dist/skills/index.d.ts +26 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +36 -0
- package/dist/skills/python-best-practices.d.ts +7 -0
- package/dist/skills/python-best-practices.d.ts.map +1 -0
- package/dist/skills/python-best-practices.js +78 -0
- package/dist/skills/python-code-health.d.ts +7 -0
- package/dist/skills/python-code-health.d.ts.map +1 -0
- package/dist/skills/python-code-health.js +209 -0
- package/dist/skills/python-code-structure.d.ts +7 -0
- package/dist/skills/python-code-structure.d.ts.map +1 -0
- package/dist/skills/python-code-structure.js +155 -0
- package/dist/skills/python-dependency-audit.d.ts +7 -0
- package/dist/skills/python-dependency-audit.d.ts.map +1 -0
- package/dist/skills/python-dependency-audit.js +246 -0
- package/dist/skills/python-refactor-impact.d.ts +7 -0
- package/dist/skills/python-refactor-impact.d.ts.map +1 -0
- package/dist/skills/python-refactor-impact.js +232 -0
- package/dist/tools/extract-docstrings.d.ts +70 -0
- package/dist/tools/extract-docstrings.d.ts.map +1 -0
- package/dist/tools/extract-docstrings.js +575 -0
- package/dist/tools/find-dead-code.d.ts +62 -0
- package/dist/tools/find-dead-code.d.ts.map +1 -0
- package/dist/tools/find-dead-code.js +422 -0
- package/dist/tools/find-duplicates.d.ts +65 -0
- package/dist/tools/find-duplicates.d.ts.map +1 -0
- package/dist/tools/find-duplicates.js +289 -0
- package/dist/tools/find-implementations.d.ts +71 -0
- package/dist/tools/find-implementations.d.ts.map +1 -0
- package/dist/tools/find-implementations.js +342 -0
- package/dist/tools/find-patterns.d.ts +71 -0
- package/dist/tools/find-patterns.d.ts.map +1 -0
- package/dist/tools/find-patterns.js +477 -0
- package/dist/tools/find-references.d.ts +66 -0
- package/dist/tools/find-references.d.ts.map +1 -0
- package/dist/tools/find-references.js +306 -0
- package/dist/tools/find-symbol.d.ts +86 -0
- package/dist/tools/find-symbol.d.ts.map +1 -0
- package/dist/tools/find-symbol.js +414 -0
- package/dist/tools/get-call-graph.d.ts +89 -0
- package/dist/tools/get-call-graph.d.ts.map +1 -0
- package/dist/tools/get-call-graph.js +431 -0
- package/dist/tools/get-class-hierarchy.d.ts +38 -0
- package/dist/tools/get-class-hierarchy.d.ts.map +1 -0
- package/dist/tools/get-class-hierarchy.js +289 -0
- package/dist/tools/get-complexity.d.ts +61 -0
- package/dist/tools/get-complexity.d.ts.map +1 -0
- package/dist/tools/get-complexity.js +384 -0
- package/dist/tools/get-dependency-graph.d.ts +85 -0
- package/dist/tools/get-dependency-graph.d.ts.map +1 -0
- package/dist/tools/get-dependency-graph.js +387 -0
- package/dist/tools/get-exports.d.ts +78 -0
- package/dist/tools/get-exports.d.ts.map +1 -0
- package/dist/tools/get-exports.js +437 -0
- package/dist/tools/get-file-structure.d.ts +28 -0
- package/dist/tools/get-file-structure.d.ts.map +1 -0
- package/dist/tools/get-file-structure.js +186 -0
- package/dist/tools/get-imports.d.ts +34 -0
- package/dist/tools/get-imports.d.ts.map +1 -0
- package/dist/tools/get-imports.js +455 -0
- package/dist/tools/get-signature.d.ts +100 -0
- package/dist/tools/get-signature.d.ts.map +1 -0
- package/dist/tools/get-signature.js +800 -0
- package/dist/tools/index.d.ts +55 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +75 -0
- package/dist/tools/types.d.ts +378 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +4 -0
- package/package.json +85 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getClassHierarchy Tool
|
|
3
|
+
*
|
|
4
|
+
* Analyze class inheritance hierarchy in Python codebases.
|
|
5
|
+
* Finds parent classes (ancestors) and child classes (descendants).
|
|
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, parseClass, parseDecorators, } from "../parser/python-parser.js";
|
|
11
|
+
// Tool description
|
|
12
|
+
const TOOL_DESCRIPTION = `Analyze the class inheritance hierarchy for a Python class.
|
|
13
|
+
Returns parent classes (ancestors) and child classes (descendants).
|
|
14
|
+
Includes Method Resolution Order (MRO) for multiple inheritance.`;
|
|
15
|
+
// Tool input schema
|
|
16
|
+
const TOOL_INPUT_SCHEMA = {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
name: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Class name to analyze",
|
|
22
|
+
},
|
|
23
|
+
file: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "File where the class is defined (improves accuracy)",
|
|
26
|
+
},
|
|
27
|
+
direction: {
|
|
28
|
+
type: "string",
|
|
29
|
+
enum: ["ancestors", "descendants", "both"],
|
|
30
|
+
description: "Direction of hierarchy traversal (default: both)",
|
|
31
|
+
default: "both",
|
|
32
|
+
},
|
|
33
|
+
maxDepth: {
|
|
34
|
+
type: "number",
|
|
35
|
+
description: "Maximum depth to traverse (default: 5)",
|
|
36
|
+
default: 5,
|
|
37
|
+
},
|
|
38
|
+
scope: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Directory scope for finding descendants",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
required: ["name"],
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* getClassHierarchy tool - Analyze class inheritance
|
|
47
|
+
*/
|
|
48
|
+
export const getClassHierarchyTool = defineTool({
|
|
49
|
+
name: "get_class_hierarchy_python",
|
|
50
|
+
description: TOOL_DESCRIPTION,
|
|
51
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
52
|
+
execute: executeGetClassHierarchy,
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Execute the getClassHierarchy tool
|
|
56
|
+
*/
|
|
57
|
+
async function executeGetClassHierarchy(input) {
|
|
58
|
+
const { name: className, file, direction = "both", maxDepth = 5, scope, } = input;
|
|
59
|
+
if (!className || className.trim().length === 0) {
|
|
60
|
+
return createErrorResult("Class name is required");
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
// Find the class definition
|
|
64
|
+
let classFile;
|
|
65
|
+
let classLine;
|
|
66
|
+
let classBases;
|
|
67
|
+
if (file) {
|
|
68
|
+
// Search in the specified file
|
|
69
|
+
const classInfo = await findClassInFile(file, className);
|
|
70
|
+
if (classInfo) {
|
|
71
|
+
classFile = file;
|
|
72
|
+
classLine = classInfo.line;
|
|
73
|
+
classBases = classInfo.bases;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
return createErrorResult(`Class '${className}' not found in ${file}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Search in scope or current directory
|
|
81
|
+
const searchPath = scope || process.cwd();
|
|
82
|
+
const found = await findClassInDirectory(searchPath, className);
|
|
83
|
+
if (found) {
|
|
84
|
+
classFile = found.path;
|
|
85
|
+
classLine = found.line;
|
|
86
|
+
classBases = found.bases;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
return createErrorResult(`Class '${className}' not found`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Build parent list
|
|
93
|
+
const parents = [];
|
|
94
|
+
if (direction === "ancestors" || direction === "both") {
|
|
95
|
+
for (const base of classBases) {
|
|
96
|
+
// Try to find the parent class definition
|
|
97
|
+
const parentInfo = await findClassInDirectory(scope || path.dirname(classFile), base);
|
|
98
|
+
parents.push({
|
|
99
|
+
name: base,
|
|
100
|
+
path: parentInfo?.path,
|
|
101
|
+
isExternal: !parentInfo,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Find descendants
|
|
106
|
+
const children = [];
|
|
107
|
+
if (direction === "descendants" || direction === "both") {
|
|
108
|
+
const searchDir = scope || path.dirname(classFile);
|
|
109
|
+
const descendants = await findDescendants(searchDir, className, maxDepth);
|
|
110
|
+
children.push(...descendants);
|
|
111
|
+
}
|
|
112
|
+
// Build MRO (simplified - just the class + bases + object)
|
|
113
|
+
const mro = buildSimpleMRO(className, classBases);
|
|
114
|
+
const result = {
|
|
115
|
+
className,
|
|
116
|
+
path: classFile,
|
|
117
|
+
line: classLine,
|
|
118
|
+
parents,
|
|
119
|
+
children,
|
|
120
|
+
mro,
|
|
121
|
+
};
|
|
122
|
+
return createSuccessResult(result);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
126
|
+
return createErrorResult(`Failed to analyze class hierarchy: ${message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Find a class in a specific file
|
|
131
|
+
*/
|
|
132
|
+
async function findClassInFile(filePath, className) {
|
|
133
|
+
try {
|
|
134
|
+
const parseResult = await parseFile(filePath);
|
|
135
|
+
const { tree, source } = parseResult;
|
|
136
|
+
for (const child of tree.rootNode.children) {
|
|
137
|
+
if (child.type === "class_definition") {
|
|
138
|
+
const classInfo = parseClass(child, source);
|
|
139
|
+
if (classInfo.name === className) {
|
|
140
|
+
return { line: classInfo.line, bases: classInfo.bases };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (child.type === "decorated_definition") {
|
|
144
|
+
const decorators = parseDecorators(child, source);
|
|
145
|
+
const classNode = child.children.find((c) => c.type === "class_definition");
|
|
146
|
+
if (classNode) {
|
|
147
|
+
const classInfo = parseClass(classNode, source, decorators);
|
|
148
|
+
if (classInfo.name === className) {
|
|
149
|
+
return { line: classInfo.line, bases: classInfo.bases };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Skip files that can't be parsed
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Find a class in a directory
|
|
162
|
+
*/
|
|
163
|
+
async function findClassInDirectory(dir, className) {
|
|
164
|
+
const files = await collectPythonFiles(dir);
|
|
165
|
+
for (const filePath of files) {
|
|
166
|
+
const result = await findClassInFile(filePath, className);
|
|
167
|
+
if (result) {
|
|
168
|
+
return { path: filePath, ...result };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Find all classes that inherit from a given class
|
|
175
|
+
*/
|
|
176
|
+
async function findDescendants(dir, className, maxDepth) {
|
|
177
|
+
const descendants = [];
|
|
178
|
+
const files = await collectPythonFiles(dir);
|
|
179
|
+
for (const filePath of files) {
|
|
180
|
+
try {
|
|
181
|
+
const parseResult = await parseFile(filePath);
|
|
182
|
+
const { tree, source } = parseResult;
|
|
183
|
+
for (const child of tree.rootNode.children) {
|
|
184
|
+
if (child.type === "class_definition") {
|
|
185
|
+
const classInfo = parseClass(child, source);
|
|
186
|
+
if (classInfo.bases.includes(className)) {
|
|
187
|
+
descendants.push({
|
|
188
|
+
name: classInfo.name,
|
|
189
|
+
path: filePath,
|
|
190
|
+
line: classInfo.line,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (child.type === "decorated_definition") {
|
|
195
|
+
const classNode = child.children.find((c) => c.type === "class_definition");
|
|
196
|
+
if (classNode) {
|
|
197
|
+
const classInfo = parseClass(classNode, source);
|
|
198
|
+
if (classInfo.bases.includes(className)) {
|
|
199
|
+
descendants.push({
|
|
200
|
+
name: classInfo.name,
|
|
201
|
+
path: filePath,
|
|
202
|
+
line: classInfo.line,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Skip files that can't be parsed
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Recursively find descendants of descendants (up to maxDepth)
|
|
214
|
+
if (maxDepth > 1) {
|
|
215
|
+
const childNames = descendants.map((d) => d.name);
|
|
216
|
+
for (const childName of childNames) {
|
|
217
|
+
const childDescendants = await findDescendants(dir, childName, maxDepth - 1);
|
|
218
|
+
for (const desc of childDescendants) {
|
|
219
|
+
if (!descendants.some((d) => d.name === desc.name)) {
|
|
220
|
+
descendants.push(desc);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return descendants;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Build a simplified MRO (Method Resolution Order)
|
|
229
|
+
*/
|
|
230
|
+
function buildSimpleMRO(className, bases) {
|
|
231
|
+
// Simplified MRO: class -> bases (in order) -> object
|
|
232
|
+
const mro = [className];
|
|
233
|
+
mro.push(...bases);
|
|
234
|
+
if (!bases.includes("object")) {
|
|
235
|
+
mro.push("object");
|
|
236
|
+
}
|
|
237
|
+
return mro;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Collect Python files in a directory
|
|
241
|
+
*/
|
|
242
|
+
async function collectPythonFiles(dir) {
|
|
243
|
+
const files = [];
|
|
244
|
+
async function walk(currentDir) {
|
|
245
|
+
try {
|
|
246
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
247
|
+
for (const entry of entries) {
|
|
248
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
249
|
+
if (entry.isDirectory()) {
|
|
250
|
+
if (entry.name.startsWith(".") ||
|
|
251
|
+
entry.name === "__pycache__" ||
|
|
252
|
+
entry.name === "venv" ||
|
|
253
|
+
entry.name === ".venv" ||
|
|
254
|
+
entry.name === "node_modules") {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
await walk(fullPath);
|
|
258
|
+
}
|
|
259
|
+
else if (entry.isFile() &&
|
|
260
|
+
(fullPath.endsWith(".py") || fullPath.endsWith(".pyi"))) {
|
|
261
|
+
files.push(fullPath);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Skip directories we can't read
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
await walk(dir);
|
|
270
|
+
return files;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Factory function to create a customized getClassHierarchy tool
|
|
274
|
+
*/
|
|
275
|
+
export function createGetClassHierarchyTool(options) {
|
|
276
|
+
const { defaultDirection = "both", defaultMaxDepth = 5 } = options ?? {};
|
|
277
|
+
return defineTool({
|
|
278
|
+
name: "get_class_hierarchy_python",
|
|
279
|
+
description: TOOL_DESCRIPTION,
|
|
280
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
281
|
+
execute: async (input) => {
|
|
282
|
+
return executeGetClassHierarchy({
|
|
283
|
+
...input,
|
|
284
|
+
direction: input.direction ?? defaultDirection,
|
|
285
|
+
maxDepth: input.maxDepth ?? defaultMaxDepth,
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getComplexity Tool
|
|
3
|
+
*
|
|
4
|
+
* Calculate cyclomatic and cognitive complexity for Python functions.
|
|
5
|
+
* Identifies complex code that may need refactoring.
|
|
6
|
+
*/
|
|
7
|
+
import type { Tool } from "@compilr-dev/agents";
|
|
8
|
+
import type { FileComplexityResult } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Input for getComplexity tool
|
|
11
|
+
*/
|
|
12
|
+
export interface GetComplexityInput {
|
|
13
|
+
/** File or directory to analyze */
|
|
14
|
+
path: string;
|
|
15
|
+
/** Recursive analysis for directories (default: false) */
|
|
16
|
+
recursive?: boolean;
|
|
17
|
+
/** Complexity threshold for warnings (default: 10) */
|
|
18
|
+
threshold?: number;
|
|
19
|
+
/** Only return items above threshold (default: false) */
|
|
20
|
+
onlyAboveThreshold?: boolean;
|
|
21
|
+
/** Maximum files to analyze (default: 50) */
|
|
22
|
+
maxFiles?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Complexity result for multiple files
|
|
26
|
+
*/
|
|
27
|
+
export interface GetComplexityResult {
|
|
28
|
+
/** Analyzed path */
|
|
29
|
+
path: string;
|
|
30
|
+
/** File-level complexity */
|
|
31
|
+
files: FileComplexityResult[];
|
|
32
|
+
/** Summary statistics */
|
|
33
|
+
summary: {
|
|
34
|
+
totalFiles: number;
|
|
35
|
+
totalFunctions: number;
|
|
36
|
+
averageComplexity: number;
|
|
37
|
+
maxComplexity: number;
|
|
38
|
+
aboveThreshold: number;
|
|
39
|
+
threshold: number;
|
|
40
|
+
};
|
|
41
|
+
/** Most complex functions (top 5) */
|
|
42
|
+
hotspots: Array<{
|
|
43
|
+
name: string;
|
|
44
|
+
path: string;
|
|
45
|
+
line: number;
|
|
46
|
+
cyclomatic: number;
|
|
47
|
+
cognitive: number;
|
|
48
|
+
}>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* getComplexity tool - Calculate code complexity
|
|
52
|
+
*/
|
|
53
|
+
export declare const getComplexityTool: Tool<GetComplexityInput>;
|
|
54
|
+
/**
|
|
55
|
+
* Factory function to create a customized getComplexity tool
|
|
56
|
+
*/
|
|
57
|
+
export declare function createGetComplexityTool(options?: {
|
|
58
|
+
defaultThreshold?: number;
|
|
59
|
+
defaultRecursive?: boolean;
|
|
60
|
+
}): Tool<GetComplexityInput>;
|
|
61
|
+
//# sourceMappingURL=get-complexity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-complexity.d.ts","sourceRoot":"","sources":["../../src/tools/get-complexity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,IAAI,EAAuB,MAAM,qBAAqB,CAAC;AAQrE,OAAO,KAAK,EACV,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,yBAAyB;IACzB,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,qCAAqC;IACrC,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;CACJ;AAuCD;;GAEG;AACH,eAAO,MAAM,iBAAiB,0BAK5B,CAAC;AAmZH;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE;IAChD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,IAAI,CAAC,kBAAkB,CAAC,CAiB3B"}
|