@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,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"}