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