@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,575 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* extractDocstrings Tool
|
|
3
|
+
*
|
|
4
|
+
* Extract and parse docstrings from Python files.
|
|
5
|
+
* Supports Google, NumPy, Sphinx, and Epytext formats.
|
|
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, parseDecorators, extractDocstring, } from "../parser/python-parser.js";
|
|
11
|
+
// Tool description
|
|
12
|
+
const TOOL_DESCRIPTION = `Extract and parse docstrings from Python files.
|
|
13
|
+
Supports Google, NumPy, Sphinx, and Epytext docstring formats.
|
|
14
|
+
Returns structured documentation with parameters, returns, raises, and examples.`;
|
|
15
|
+
// Tool input schema
|
|
16
|
+
const TOOL_INPUT_SCHEMA = {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
path: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "File or directory to analyze",
|
|
22
|
+
},
|
|
23
|
+
recursive: {
|
|
24
|
+
type: "boolean",
|
|
25
|
+
description: "Recursively analyze directory (default: false)",
|
|
26
|
+
default: false,
|
|
27
|
+
},
|
|
28
|
+
kinds: {
|
|
29
|
+
type: "array",
|
|
30
|
+
items: {
|
|
31
|
+
type: "string",
|
|
32
|
+
enum: ["function", "class", "method", "module"],
|
|
33
|
+
},
|
|
34
|
+
description: "Filter by symbol kinds",
|
|
35
|
+
},
|
|
36
|
+
documentedOnly: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description: "Only include documented symbols (default: false)",
|
|
39
|
+
default: false,
|
|
40
|
+
},
|
|
41
|
+
maxFiles: {
|
|
42
|
+
type: "number",
|
|
43
|
+
description: "Maximum files to analyze (default: 50)",
|
|
44
|
+
default: 50,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ["path"],
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* extractDocstrings tool - Extract Python documentation
|
|
51
|
+
*/
|
|
52
|
+
export const extractDocstringsTool = defineTool({
|
|
53
|
+
name: "extract_docstrings_python",
|
|
54
|
+
description: TOOL_DESCRIPTION,
|
|
55
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
56
|
+
execute: executeExtractDocstrings,
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Execute the extractDocstrings tool
|
|
60
|
+
*/
|
|
61
|
+
async function executeExtractDocstrings(input) {
|
|
62
|
+
const { path: targetPath, recursive = false, kinds, documentedOnly = false, maxFiles = 50, } = input;
|
|
63
|
+
const resolvedPath = path.resolve(targetPath);
|
|
64
|
+
try {
|
|
65
|
+
await fs.access(resolvedPath);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return createErrorResult(`Path not found: ${resolvedPath}`);
|
|
69
|
+
}
|
|
70
|
+
const stats = await fs.stat(resolvedPath);
|
|
71
|
+
try {
|
|
72
|
+
const files = [];
|
|
73
|
+
if (stats.isFile()) {
|
|
74
|
+
if (!resolvedPath.endsWith(".py") && !resolvedPath.endsWith(".pyi")) {
|
|
75
|
+
return createErrorResult(`Not a Python file: ${resolvedPath}`);
|
|
76
|
+
}
|
|
77
|
+
const fileDoc = await analyzeFileDocumentation(resolvedPath, kinds, documentedOnly);
|
|
78
|
+
files.push(fileDoc);
|
|
79
|
+
}
|
|
80
|
+
else if (stats.isDirectory()) {
|
|
81
|
+
const pythonFiles = await collectPythonFiles(resolvedPath, recursive, maxFiles);
|
|
82
|
+
for (const filePath of pythonFiles) {
|
|
83
|
+
const fileDoc = await analyzeFileDocumentation(filePath, kinds, documentedOnly);
|
|
84
|
+
files.push(fileDoc);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Calculate summary
|
|
88
|
+
let totalSymbols = 0;
|
|
89
|
+
let documentedSymbols = 0;
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
totalSymbols += file.stats.totalSymbols;
|
|
92
|
+
documentedSymbols += file.stats.documentedSymbols;
|
|
93
|
+
}
|
|
94
|
+
const result = {
|
|
95
|
+
path: resolvedPath,
|
|
96
|
+
files,
|
|
97
|
+
summary: {
|
|
98
|
+
totalFiles: files.length,
|
|
99
|
+
totalSymbols,
|
|
100
|
+
documentedSymbols,
|
|
101
|
+
undocumentedSymbols: totalSymbols - documentedSymbols,
|
|
102
|
+
coveragePercent: totalSymbols > 0 ? (documentedSymbols / totalSymbols) * 100 : 100,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
return createSuccessResult(result);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
return createErrorResult(`Failed to extract docstrings: ${message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Analyze documentation in a single file
|
|
114
|
+
*/
|
|
115
|
+
async function analyzeFileDocumentation(filePath, kinds, documentedOnly) {
|
|
116
|
+
const parseResult = await parseFile(filePath);
|
|
117
|
+
const { tree, source } = parseResult;
|
|
118
|
+
const symbols = [];
|
|
119
|
+
let moduleDoc;
|
|
120
|
+
// Extract module docstring
|
|
121
|
+
const moduleDocstring = extractDocstring({ childForFieldName: () => tree.rootNode }, source);
|
|
122
|
+
if (moduleDocstring) {
|
|
123
|
+
moduleDoc = parseDocstring(moduleDocstring);
|
|
124
|
+
}
|
|
125
|
+
// Check if we should include certain kinds
|
|
126
|
+
const _includeModule = !kinds || kinds.includes("module"); // Reserved for future use
|
|
127
|
+
const includeFunction = !kinds || kinds.includes("function");
|
|
128
|
+
const includeClass = !kinds || kinds.includes("class");
|
|
129
|
+
const includeMethod = !kinds || kinds.includes("method");
|
|
130
|
+
// Extract function and class docstrings
|
|
131
|
+
for (const child of tree.rootNode.children) {
|
|
132
|
+
// Functions
|
|
133
|
+
if (includeFunction &&
|
|
134
|
+
(child.type === "function_definition" ||
|
|
135
|
+
child.type === "async_function_definition")) {
|
|
136
|
+
const funcInfo = parseFunction(child, source);
|
|
137
|
+
const hasDoc = !!funcInfo.docstring;
|
|
138
|
+
if (!documentedOnly || hasDoc) {
|
|
139
|
+
symbols.push({
|
|
140
|
+
name: funcInfo.name,
|
|
141
|
+
path: filePath,
|
|
142
|
+
line: funcInfo.line,
|
|
143
|
+
docstring: funcInfo.docstring,
|
|
144
|
+
parsed: funcInfo.docstring
|
|
145
|
+
? parseDocstring(funcInfo.docstring)
|
|
146
|
+
: undefined,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Decorated functions
|
|
151
|
+
if (includeFunction && child.type === "decorated_definition") {
|
|
152
|
+
const funcNode = child.children.find((c) => c.type === "function_definition" ||
|
|
153
|
+
c.type === "async_function_definition");
|
|
154
|
+
if (funcNode) {
|
|
155
|
+
const decorators = parseDecorators(child, source);
|
|
156
|
+
const funcInfo = parseFunction(funcNode, source, decorators);
|
|
157
|
+
const hasDoc = !!funcInfo.docstring;
|
|
158
|
+
if (!documentedOnly || hasDoc) {
|
|
159
|
+
symbols.push({
|
|
160
|
+
name: funcInfo.name,
|
|
161
|
+
path: filePath,
|
|
162
|
+
line: funcInfo.line,
|
|
163
|
+
docstring: funcInfo.docstring,
|
|
164
|
+
parsed: funcInfo.docstring
|
|
165
|
+
? parseDocstring(funcInfo.docstring)
|
|
166
|
+
: undefined,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Classes
|
|
172
|
+
if (includeClass && child.type === "class_definition") {
|
|
173
|
+
const classInfo = parseClass(child, source);
|
|
174
|
+
const hasDoc = !!classInfo.docstring;
|
|
175
|
+
if (!documentedOnly || hasDoc) {
|
|
176
|
+
symbols.push({
|
|
177
|
+
name: classInfo.name,
|
|
178
|
+
path: filePath,
|
|
179
|
+
line: classInfo.line,
|
|
180
|
+
docstring: classInfo.docstring,
|
|
181
|
+
parsed: classInfo.docstring
|
|
182
|
+
? parseDocstring(classInfo.docstring)
|
|
183
|
+
: undefined,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// Class methods
|
|
187
|
+
if (includeMethod) {
|
|
188
|
+
for (const method of classInfo.methods) {
|
|
189
|
+
const hasMethodDoc = !!method.docstring;
|
|
190
|
+
if (!documentedOnly || hasMethodDoc) {
|
|
191
|
+
symbols.push({
|
|
192
|
+
name: `${classInfo.name}.${method.name}`,
|
|
193
|
+
path: filePath,
|
|
194
|
+
line: method.line,
|
|
195
|
+
docstring: method.docstring,
|
|
196
|
+
parsed: method.docstring
|
|
197
|
+
? parseDocstring(method.docstring)
|
|
198
|
+
: undefined,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Decorated classes
|
|
205
|
+
if (includeClass && child.type === "decorated_definition") {
|
|
206
|
+
const classNode = child.children.find((c) => c.type === "class_definition");
|
|
207
|
+
if (classNode) {
|
|
208
|
+
const decorators = parseDecorators(child, source);
|
|
209
|
+
const classInfo = parseClass(classNode, source, decorators);
|
|
210
|
+
const hasDoc = !!classInfo.docstring;
|
|
211
|
+
if (!documentedOnly || hasDoc) {
|
|
212
|
+
symbols.push({
|
|
213
|
+
name: classInfo.name,
|
|
214
|
+
path: filePath,
|
|
215
|
+
line: classInfo.line,
|
|
216
|
+
docstring: classInfo.docstring,
|
|
217
|
+
parsed: classInfo.docstring
|
|
218
|
+
? parseDocstring(classInfo.docstring)
|
|
219
|
+
: undefined,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// Class methods
|
|
223
|
+
if (includeMethod) {
|
|
224
|
+
for (const method of classInfo.methods) {
|
|
225
|
+
const hasMethodDoc = !!method.docstring;
|
|
226
|
+
if (!documentedOnly || hasMethodDoc) {
|
|
227
|
+
symbols.push({
|
|
228
|
+
name: `${classInfo.name}.${method.name}`,
|
|
229
|
+
path: filePath,
|
|
230
|
+
line: method.line,
|
|
231
|
+
docstring: method.docstring,
|
|
232
|
+
parsed: method.docstring
|
|
233
|
+
? parseDocstring(method.docstring)
|
|
234
|
+
: undefined,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const documented = symbols.filter((s) => s.docstring).length;
|
|
243
|
+
return {
|
|
244
|
+
path: filePath,
|
|
245
|
+
moduleDoc,
|
|
246
|
+
symbols,
|
|
247
|
+
stats: {
|
|
248
|
+
totalSymbols: symbols.length,
|
|
249
|
+
documentedSymbols: documented,
|
|
250
|
+
undocumentedSymbols: symbols.length - documented,
|
|
251
|
+
coveragePercent: symbols.length > 0 ? (documented / symbols.length) * 100 : 100,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Parse a docstring into structured format
|
|
257
|
+
*/
|
|
258
|
+
function parseDocstring(docstring) {
|
|
259
|
+
const lines = docstring.split("\n").map((l) => l.trim());
|
|
260
|
+
const format = detectDocstringFormat(docstring);
|
|
261
|
+
// Extract summary (first paragraph)
|
|
262
|
+
let summary = "";
|
|
263
|
+
let descriptionStart = 0;
|
|
264
|
+
for (let i = 0; i < lines.length; i++) {
|
|
265
|
+
if (lines[i] === "") {
|
|
266
|
+
descriptionStart = i + 1;
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
summary += (summary ? " " : "") + lines[i];
|
|
270
|
+
descriptionStart = i + 1;
|
|
271
|
+
}
|
|
272
|
+
// Parse based on format
|
|
273
|
+
const parameters = [];
|
|
274
|
+
let returns;
|
|
275
|
+
const raises = [];
|
|
276
|
+
const examples = [];
|
|
277
|
+
let description;
|
|
278
|
+
let notes;
|
|
279
|
+
if (format === "google") {
|
|
280
|
+
const sections = parseGoogleStyle(docstring);
|
|
281
|
+
parameters.push(...(sections.params || []));
|
|
282
|
+
returns = sections.returns;
|
|
283
|
+
raises.push(...(sections.raises || []));
|
|
284
|
+
examples.push(...(sections.examples || []));
|
|
285
|
+
description = sections.description;
|
|
286
|
+
notes = sections.notes;
|
|
287
|
+
}
|
|
288
|
+
else if (format === "numpy") {
|
|
289
|
+
const sections = parseNumpyStyle(docstring);
|
|
290
|
+
parameters.push(...(sections.params || []));
|
|
291
|
+
returns = sections.returns;
|
|
292
|
+
raises.push(...(sections.raises || []));
|
|
293
|
+
examples.push(...(sections.examples || []));
|
|
294
|
+
description = sections.description;
|
|
295
|
+
notes = sections.notes;
|
|
296
|
+
}
|
|
297
|
+
else if (format === "sphinx") {
|
|
298
|
+
const sections = parseSphinxStyle(docstring);
|
|
299
|
+
parameters.push(...(sections.params || []));
|
|
300
|
+
returns = sections.returns;
|
|
301
|
+
raises.push(...(sections.raises || []));
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
// Try to extract description from remaining lines
|
|
305
|
+
if (descriptionStart < lines.length) {
|
|
306
|
+
description =
|
|
307
|
+
lines.slice(descriptionStart).join("\n").trim() || undefined;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
summary,
|
|
312
|
+
description,
|
|
313
|
+
parameters,
|
|
314
|
+
returns,
|
|
315
|
+
raises,
|
|
316
|
+
examples,
|
|
317
|
+
notes,
|
|
318
|
+
format,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Detect docstring format
|
|
323
|
+
*/
|
|
324
|
+
function detectDocstringFormat(docstring) {
|
|
325
|
+
// Google style: "Args:", "Returns:", "Raises:"
|
|
326
|
+
if (/^\s*(Args|Arguments|Returns|Raises|Yields|Attributes):\s*$/m.test(docstring)) {
|
|
327
|
+
return "google";
|
|
328
|
+
}
|
|
329
|
+
// NumPy style: "Parameters", "Returns" with dashes
|
|
330
|
+
if (/^\s*(Parameters|Returns|Raises)\s*\n\s*-{3,}/m.test(docstring)) {
|
|
331
|
+
return "numpy";
|
|
332
|
+
}
|
|
333
|
+
// Sphinx style: ":param", ":returns:", ":raises:"
|
|
334
|
+
if (/:(param|returns?|raises?|type|rtype)/.test(docstring)) {
|
|
335
|
+
return "sphinx";
|
|
336
|
+
}
|
|
337
|
+
// Epytext: "@param", "@return", "@raise"
|
|
338
|
+
if (/@(param|return|raise|type)/.test(docstring)) {
|
|
339
|
+
return "epytext";
|
|
340
|
+
}
|
|
341
|
+
return "unknown";
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Parse Google-style docstring
|
|
345
|
+
*/
|
|
346
|
+
function parseGoogleStyle(docstring) {
|
|
347
|
+
const result = {};
|
|
348
|
+
const lines = docstring.split("\n");
|
|
349
|
+
let currentSection = "";
|
|
350
|
+
let currentContent = [];
|
|
351
|
+
for (const line of lines) {
|
|
352
|
+
const sectionMatch = line.match(/^\s*(Args|Arguments|Returns|Raises|Yields|Example|Examples|Note|Notes):\s*$/);
|
|
353
|
+
if (sectionMatch) {
|
|
354
|
+
// Process previous section
|
|
355
|
+
processSectionContent(result, currentSection, currentContent);
|
|
356
|
+
currentSection = sectionMatch[1].toLowerCase();
|
|
357
|
+
currentContent = [];
|
|
358
|
+
}
|
|
359
|
+
else if (currentSection) {
|
|
360
|
+
currentContent.push(line);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Process last section
|
|
364
|
+
processSectionContent(result, currentSection, currentContent);
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Process section content for Google-style
|
|
369
|
+
*/
|
|
370
|
+
function processSectionContent(result, section, content) {
|
|
371
|
+
const text = content.join("\n").trim();
|
|
372
|
+
if (!text)
|
|
373
|
+
return;
|
|
374
|
+
switch (section) {
|
|
375
|
+
case "args":
|
|
376
|
+
case "arguments":
|
|
377
|
+
result.params = parseGoogleParams(text);
|
|
378
|
+
break;
|
|
379
|
+
case "returns":
|
|
380
|
+
case "yields":
|
|
381
|
+
result.returns = { description: text };
|
|
382
|
+
break;
|
|
383
|
+
case "raises":
|
|
384
|
+
result.raises = parseGoogleRaises(text);
|
|
385
|
+
break;
|
|
386
|
+
case "example":
|
|
387
|
+
case "examples":
|
|
388
|
+
result.examples = [text];
|
|
389
|
+
break;
|
|
390
|
+
case "note":
|
|
391
|
+
case "notes":
|
|
392
|
+
result.notes = text;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Parse Google-style parameters
|
|
398
|
+
*/
|
|
399
|
+
function parseGoogleParams(text) {
|
|
400
|
+
const params = [];
|
|
401
|
+
const paramRegex = /^\s*(\w+)(?:\s*\(([^)]+)\))?:\s*(.+)$/gm;
|
|
402
|
+
let match;
|
|
403
|
+
while ((match = paramRegex.exec(text)) !== null) {
|
|
404
|
+
params.push({
|
|
405
|
+
name: match[1],
|
|
406
|
+
type: match[2],
|
|
407
|
+
description: match[3].trim(),
|
|
408
|
+
optional: match[2]?.includes("optional") || false,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return params;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Parse Google-style raises
|
|
415
|
+
*/
|
|
416
|
+
function parseGoogleRaises(text) {
|
|
417
|
+
const raises = [];
|
|
418
|
+
const raiseRegex = /^\s*(\w+):\s*(.+)$/gm;
|
|
419
|
+
let match;
|
|
420
|
+
while ((match = raiseRegex.exec(text)) !== null) {
|
|
421
|
+
raises.push({
|
|
422
|
+
type: match[1],
|
|
423
|
+
description: match[2].trim(),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return raises;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Parse NumPy-style docstring
|
|
430
|
+
*/
|
|
431
|
+
function parseNumpyStyle(docstring) {
|
|
432
|
+
// Simplified NumPy parsing - similar structure to Google but with different section headers
|
|
433
|
+
const result = {};
|
|
434
|
+
const sections = docstring.split(/\n(?=\w+\n-{3,})/);
|
|
435
|
+
for (const section of sections) {
|
|
436
|
+
const headerMatch = section.match(/^(\w+)\n-{3,}\n([\s\S]*)/);
|
|
437
|
+
if (headerMatch) {
|
|
438
|
+
const name = headerMatch[1].toLowerCase();
|
|
439
|
+
const content = headerMatch[2].trim();
|
|
440
|
+
switch (name) {
|
|
441
|
+
case "parameters":
|
|
442
|
+
result.params = parseNumpyParams(content);
|
|
443
|
+
break;
|
|
444
|
+
case "returns":
|
|
445
|
+
result.returns = { description: content };
|
|
446
|
+
break;
|
|
447
|
+
case "raises":
|
|
448
|
+
result.raises = parseGoogleRaises(content);
|
|
449
|
+
break;
|
|
450
|
+
case "examples":
|
|
451
|
+
result.examples = [content];
|
|
452
|
+
break;
|
|
453
|
+
case "notes":
|
|
454
|
+
result.notes = content;
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Parse NumPy-style parameters
|
|
463
|
+
*/
|
|
464
|
+
function parseNumpyParams(text) {
|
|
465
|
+
const params = [];
|
|
466
|
+
const paramRegex = /^(\w+)\s*:\s*(\S+)?\s*\n\s+(.+)$/gm;
|
|
467
|
+
let match;
|
|
468
|
+
while ((match = paramRegex.exec(text)) !== null) {
|
|
469
|
+
params.push({
|
|
470
|
+
name: match[1],
|
|
471
|
+
type: match[2],
|
|
472
|
+
description: match[3].trim(),
|
|
473
|
+
optional: match[2]?.includes("optional") || false,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return params;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Parse Sphinx-style docstring
|
|
480
|
+
*/
|
|
481
|
+
function parseSphinxStyle(docstring) {
|
|
482
|
+
const result = {};
|
|
483
|
+
const params = [];
|
|
484
|
+
const raises = [];
|
|
485
|
+
// :param name: description
|
|
486
|
+
const paramRegex = /:param\s+(\w+):\s*(.+)/g;
|
|
487
|
+
let match;
|
|
488
|
+
while ((match = paramRegex.exec(docstring)) !== null) {
|
|
489
|
+
params.push({
|
|
490
|
+
name: match[1],
|
|
491
|
+
description: match[2].trim(),
|
|
492
|
+
optional: false,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
// :type name: type
|
|
496
|
+
const typeRegex = /:type\s+(\w+):\s*(.+)/g;
|
|
497
|
+
let typeMatch;
|
|
498
|
+
while ((typeMatch = typeRegex.exec(docstring)) !== null) {
|
|
499
|
+
const tm = typeMatch; // Capture for TypeScript narrowing
|
|
500
|
+
const param = params.find((p) => p.name === tm[1]);
|
|
501
|
+
if (param) {
|
|
502
|
+
param.type = tm[2].trim();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// :returns: description
|
|
506
|
+
const returnMatch = docstring.match(/:returns?:\s*(.+)/);
|
|
507
|
+
if (returnMatch) {
|
|
508
|
+
result.returns = { description: returnMatch[1].trim() };
|
|
509
|
+
}
|
|
510
|
+
// :rtype: type
|
|
511
|
+
const rtypeMatch = docstring.match(/:rtype:\s*(.+)/);
|
|
512
|
+
if (rtypeMatch && result.returns) {
|
|
513
|
+
result.returns.type = rtypeMatch[1].trim();
|
|
514
|
+
}
|
|
515
|
+
// :raises Type: description
|
|
516
|
+
const raiseRegex = /:raises?\s+(\w+):\s*(.+)/g;
|
|
517
|
+
while ((match = raiseRegex.exec(docstring)) !== null) {
|
|
518
|
+
raises.push({
|
|
519
|
+
type: match[1],
|
|
520
|
+
description: match[2].trim(),
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
result.params = params;
|
|
524
|
+
result.raises = raises;
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Collect Python files in a directory
|
|
529
|
+
*/
|
|
530
|
+
async function collectPythonFiles(dir, recursive, maxFiles) {
|
|
531
|
+
const files = [];
|
|
532
|
+
async function walk(currentDir) {
|
|
533
|
+
if (files.length >= maxFiles)
|
|
534
|
+
return;
|
|
535
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
536
|
+
for (const entry of entries) {
|
|
537
|
+
if (files.length >= maxFiles)
|
|
538
|
+
break;
|
|
539
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
540
|
+
if (entry.isDirectory() && recursive) {
|
|
541
|
+
if (entry.name.startsWith(".") ||
|
|
542
|
+
entry.name === "__pycache__" ||
|
|
543
|
+
entry.name === "venv" ||
|
|
544
|
+
entry.name === ".venv") {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
await walk(fullPath);
|
|
548
|
+
}
|
|
549
|
+
else if (entry.isFile() &&
|
|
550
|
+
(fullPath.endsWith(".py") || fullPath.endsWith(".pyi"))) {
|
|
551
|
+
files.push(fullPath);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
await walk(dir);
|
|
556
|
+
return files;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Factory function to create a customized extractDocstrings tool
|
|
560
|
+
*/
|
|
561
|
+
export function createExtractDocstringsTool(options) {
|
|
562
|
+
const { defaultRecursive = false, defaultDocumentedOnly = false } = options ?? {};
|
|
563
|
+
return defineTool({
|
|
564
|
+
name: "extract_docstrings_python",
|
|
565
|
+
description: TOOL_DESCRIPTION,
|
|
566
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
567
|
+
execute: async (input) => {
|
|
568
|
+
return executeExtractDocstrings({
|
|
569
|
+
...input,
|
|
570
|
+
recursive: input.recursive ?? defaultRecursive,
|
|
571
|
+
documentedOnly: input.documentedOnly ?? defaultDocumentedOnly,
|
|
572
|
+
});
|
|
573
|
+
},
|
|
574
|
+
});
|
|
575
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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 type { Tool } from "@compilr-dev/agents";
|
|
8
|
+
/**
|
|
9
|
+
* Input for findDeadCode tool
|
|
10
|
+
*/
|
|
11
|
+
export interface FindDeadCodeInput {
|
|
12
|
+
/** Directory to analyze */
|
|
13
|
+
path: string;
|
|
14
|
+
/** Include test files in analysis (default: false) */
|
|
15
|
+
includeTests?: boolean;
|
|
16
|
+
/** Check for unused functions (default: true) */
|
|
17
|
+
checkFunctions?: boolean;
|
|
18
|
+
/** Check for unused classes (default: true) */
|
|
19
|
+
checkClasses?: boolean;
|
|
20
|
+
/** Check for unused variables (default: false) */
|
|
21
|
+
checkVariables?: boolean;
|
|
22
|
+
/** Maximum files to analyze (default: 100) */
|
|
23
|
+
maxFiles?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Dead code item
|
|
27
|
+
*/
|
|
28
|
+
export interface DeadCodeItem {
|
|
29
|
+
name: string;
|
|
30
|
+
path: string;
|
|
31
|
+
line: number;
|
|
32
|
+
kind: "function" | "class" | "variable" | "method";
|
|
33
|
+
confidence: "high" | "medium" | "low";
|
|
34
|
+
reason: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Result of findDeadCode
|
|
38
|
+
*/
|
|
39
|
+
export interface FindDeadCodeResult {
|
|
40
|
+
path: string;
|
|
41
|
+
deadCode: DeadCodeItem[];
|
|
42
|
+
stats: {
|
|
43
|
+
filesAnalyzed: number;
|
|
44
|
+
totalFunctions: number;
|
|
45
|
+
unusedFunctions: number;
|
|
46
|
+
totalClasses: number;
|
|
47
|
+
unusedClasses: number;
|
|
48
|
+
totalVariables: number;
|
|
49
|
+
unusedVariables: number;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* findDeadCode tool
|
|
54
|
+
*/
|
|
55
|
+
export declare const findDeadCodeTool: Tool<FindDeadCodeInput>;
|
|
56
|
+
/**
|
|
57
|
+
* Factory function to create a customized findDeadCode tool
|
|
58
|
+
*/
|
|
59
|
+
export declare function createFindDeadCodeTool(options?: {
|
|
60
|
+
defaultMaxFiles?: number;
|
|
61
|
+
}): Tool<FindDeadCodeInput>;
|
|
62
|
+
//# sourceMappingURL=find-dead-code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-dead-code.d.ts","sourceRoot":"","sources":["../../src/tools/find-dead-code.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,IAAI,EAAuB,MAAM,qBAAqB,CAAC;AAQrE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iDAAiD;IACjD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,kDAAkD;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;IACnD,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,EAAE;QACL,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAuED;;GAEG;AACH,eAAO,MAAM,gBAAgB,yBAK3B,CAAC;AA4ZH;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAa1B"}
|