@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,477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* findPatterns Tool
|
|
3
|
+
*
|
|
4
|
+
* Find code patterns, anti-patterns, and code smells in Python code.
|
|
5
|
+
* Uses regex and AST-based pattern matching.
|
|
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 } from "../parser/python-parser.js";
|
|
11
|
+
// Tool description
|
|
12
|
+
const TOOL_DESCRIPTION = `Find code patterns, anti-patterns, and code smells in Python code.
|
|
13
|
+
Searches for common issues like security vulnerabilities, performance problems, and maintainability concerns.
|
|
14
|
+
Supports built-in patterns or custom pattern definitions.`;
|
|
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
|
+
patterns: {
|
|
24
|
+
type: "array",
|
|
25
|
+
description: "Custom patterns to search for (uses built-in if not specified)",
|
|
26
|
+
items: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
name: { type: "string" },
|
|
30
|
+
description: { type: "string" },
|
|
31
|
+
type: { type: "string" },
|
|
32
|
+
matcher: { type: "string" },
|
|
33
|
+
severity: { type: "string" },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
categories: {
|
|
38
|
+
type: "array",
|
|
39
|
+
description: "Pattern categories to include (default: all)",
|
|
40
|
+
items: {
|
|
41
|
+
type: "string",
|
|
42
|
+
enum: ["security", "performance", "maintainability", "all"],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
maxFiles: {
|
|
46
|
+
type: "number",
|
|
47
|
+
description: "Maximum files to analyze (default: 100)",
|
|
48
|
+
default: 100,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ["path"],
|
|
52
|
+
};
|
|
53
|
+
// Default exclusions
|
|
54
|
+
const DEFAULT_EXCLUDE = [
|
|
55
|
+
"node_modules",
|
|
56
|
+
"__pycache__",
|
|
57
|
+
".git",
|
|
58
|
+
"venv",
|
|
59
|
+
".venv",
|
|
60
|
+
"env",
|
|
61
|
+
"dist",
|
|
62
|
+
"build",
|
|
63
|
+
];
|
|
64
|
+
// Built-in Python patterns
|
|
65
|
+
const BUILTIN_PATTERNS = [
|
|
66
|
+
// Security patterns
|
|
67
|
+
{
|
|
68
|
+
name: "eval-usage",
|
|
69
|
+
description: "Use of eval() can lead to code injection vulnerabilities",
|
|
70
|
+
type: "anti-pattern",
|
|
71
|
+
matcher: "\\beval\\s*\\(",
|
|
72
|
+
severity: "error",
|
|
73
|
+
category: "security",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "exec-usage",
|
|
77
|
+
description: "Use of exec() can lead to code injection vulnerabilities",
|
|
78
|
+
type: "anti-pattern",
|
|
79
|
+
matcher: "\\bexec\\s*\\(",
|
|
80
|
+
severity: "error",
|
|
81
|
+
category: "security",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "hardcoded-secret",
|
|
85
|
+
description: "Potential hardcoded secret or API key",
|
|
86
|
+
type: "anti-pattern",
|
|
87
|
+
matcher: "(password|secret|api_key|apikey|token|private_key)\\s*=\\s*[\"'][^\"']+[\"']",
|
|
88
|
+
severity: "error",
|
|
89
|
+
category: "security",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "sql-injection-risk",
|
|
93
|
+
description: "String formatting in SQL query (potential injection)",
|
|
94
|
+
type: "anti-pattern",
|
|
95
|
+
matcher: "(execute|cursor\\.execute)\\s*\\([^)]*(%|format|f['\"])",
|
|
96
|
+
severity: "error",
|
|
97
|
+
category: "security",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "pickle-usage",
|
|
101
|
+
description: "pickle can execute arbitrary code when loading untrusted data",
|
|
102
|
+
type: "anti-pattern",
|
|
103
|
+
matcher: "pickle\\.load",
|
|
104
|
+
severity: "warning",
|
|
105
|
+
category: "security",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "shell-injection-risk",
|
|
109
|
+
description: "shell=True with user input can lead to command injection",
|
|
110
|
+
type: "anti-pattern",
|
|
111
|
+
matcher: "subprocess\\.(call|run|Popen)\\([^)]*shell\\s*=\\s*True",
|
|
112
|
+
severity: "error",
|
|
113
|
+
category: "security",
|
|
114
|
+
},
|
|
115
|
+
// Performance patterns
|
|
116
|
+
{
|
|
117
|
+
name: "print-statement",
|
|
118
|
+
description: "print() left in code (should use logging in production)",
|
|
119
|
+
type: "smell",
|
|
120
|
+
matcher: "\\bprint\\s*\\(",
|
|
121
|
+
severity: "info",
|
|
122
|
+
category: "performance",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "mutable-default-arg",
|
|
126
|
+
description: "Mutable default argument (list, dict, set) is a common bug source",
|
|
127
|
+
type: "anti-pattern",
|
|
128
|
+
matcher: "def\\s+\\w+\\s*\\([^)]*=\\s*\\[\\]|def\\s+\\w+\\s*\\([^)]*=\\s*\\{\\}",
|
|
129
|
+
severity: "warning",
|
|
130
|
+
category: "performance",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "global-variable",
|
|
134
|
+
description: "Global variable usage makes code harder to test and reason about",
|
|
135
|
+
type: "smell",
|
|
136
|
+
matcher: "\\bglobal\\s+\\w+",
|
|
137
|
+
severity: "warning",
|
|
138
|
+
category: "performance",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "nested-loops",
|
|
142
|
+
description: "Deeply nested loops may indicate O(n²) or worse complexity",
|
|
143
|
+
type: "smell",
|
|
144
|
+
matcher: "for\\s+[^:]+:\\s*[^#]*for\\s+[^:]+:\\s*[^#]*for\\s+",
|
|
145
|
+
severity: "warning",
|
|
146
|
+
category: "performance",
|
|
147
|
+
},
|
|
148
|
+
// Maintainability patterns
|
|
149
|
+
{
|
|
150
|
+
name: "bare-except",
|
|
151
|
+
description: "Bare except catches all exceptions including SystemExit and KeyboardInterrupt",
|
|
152
|
+
type: "anti-pattern",
|
|
153
|
+
matcher: "except\\s*:",
|
|
154
|
+
severity: "warning",
|
|
155
|
+
category: "maintainability",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "star-import",
|
|
159
|
+
description: "Star imports pollute namespace and hide dependencies",
|
|
160
|
+
type: "anti-pattern",
|
|
161
|
+
matcher: "from\\s+\\S+\\s+import\\s+\\*",
|
|
162
|
+
severity: "warning",
|
|
163
|
+
category: "maintainability",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "todo-fixme",
|
|
167
|
+
description: "TODO or FIXME comment indicating incomplete work",
|
|
168
|
+
type: "smell",
|
|
169
|
+
matcher: "(TODO|FIXME|HACK|XXX)\\s*:?",
|
|
170
|
+
severity: "info",
|
|
171
|
+
category: "maintainability",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "type-ignore",
|
|
175
|
+
description: "# type: ignore suppresses type checking",
|
|
176
|
+
type: "smell",
|
|
177
|
+
matcher: "#\\s*type:\\s*ignore",
|
|
178
|
+
severity: "warning",
|
|
179
|
+
category: "maintainability",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "noqa-comment",
|
|
183
|
+
description: "# noqa suppresses linting rules",
|
|
184
|
+
type: "smell",
|
|
185
|
+
matcher: "#\\s*noqa",
|
|
186
|
+
severity: "info",
|
|
187
|
+
category: "maintainability",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "long-function",
|
|
191
|
+
description: "Function body exceeds recommended length",
|
|
192
|
+
type: "smell",
|
|
193
|
+
matcher: "AST:long-function",
|
|
194
|
+
severity: "warning",
|
|
195
|
+
category: "maintainability",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "many-parameters",
|
|
199
|
+
description: "Function has too many parameters (consider using dataclass or kwargs)",
|
|
200
|
+
type: "smell",
|
|
201
|
+
matcher: "AST:many-parameters",
|
|
202
|
+
severity: "info",
|
|
203
|
+
category: "maintainability",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "assert-usage",
|
|
207
|
+
description: "assert can be disabled with -O flag, don't use for validation",
|
|
208
|
+
type: "smell",
|
|
209
|
+
matcher: "\\bassert\\s+",
|
|
210
|
+
severity: "info",
|
|
211
|
+
category: "maintainability",
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
/**
|
|
215
|
+
* findPatterns tool
|
|
216
|
+
*/
|
|
217
|
+
export const findPatternsTool = defineTool({
|
|
218
|
+
name: "find_patterns_python",
|
|
219
|
+
description: TOOL_DESCRIPTION,
|
|
220
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
221
|
+
execute: executeFindPatterns,
|
|
222
|
+
});
|
|
223
|
+
/**
|
|
224
|
+
* Execute the findPatterns tool
|
|
225
|
+
*/
|
|
226
|
+
async function executeFindPatterns(input) {
|
|
227
|
+
const { path: inputPath, patterns, categories = ["all"], maxFiles = 100, } = input;
|
|
228
|
+
try {
|
|
229
|
+
const resolvedPath = path.resolve(inputPath);
|
|
230
|
+
// Check if path exists
|
|
231
|
+
try {
|
|
232
|
+
await fs.access(resolvedPath);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return createErrorResult(`Path not found: ${resolvedPath}`);
|
|
236
|
+
}
|
|
237
|
+
const stats = await fs.stat(resolvedPath);
|
|
238
|
+
if (!stats.isDirectory()) {
|
|
239
|
+
return createErrorResult("findPatterns requires a directory path");
|
|
240
|
+
}
|
|
241
|
+
// Determine which patterns to use
|
|
242
|
+
const patternsToUse = patterns ?? getBuiltinPatterns(categories);
|
|
243
|
+
// Collect files
|
|
244
|
+
const files = [];
|
|
245
|
+
await collectFiles(resolvedPath, files, maxFiles);
|
|
246
|
+
// Find matches
|
|
247
|
+
const allMatches = [];
|
|
248
|
+
const byPattern = {};
|
|
249
|
+
let errorCount = 0;
|
|
250
|
+
let warningCount = 0;
|
|
251
|
+
let infoCount = 0;
|
|
252
|
+
for (const file of files) {
|
|
253
|
+
const fileMatches = await analyzeFileForPatterns(file, patternsToUse);
|
|
254
|
+
allMatches.push(...fileMatches);
|
|
255
|
+
}
|
|
256
|
+
// Count by pattern and severity
|
|
257
|
+
for (const match of allMatches) {
|
|
258
|
+
byPattern[match.pattern] = (byPattern[match.pattern] ?? 0) + 1;
|
|
259
|
+
if (match.severity === "error")
|
|
260
|
+
errorCount++;
|
|
261
|
+
else if (match.severity === "warning")
|
|
262
|
+
warningCount++;
|
|
263
|
+
else
|
|
264
|
+
infoCount++;
|
|
265
|
+
}
|
|
266
|
+
// Sort by severity (errors first)
|
|
267
|
+
allMatches.sort((a, b) => {
|
|
268
|
+
const severityOrder = { error: 0, warning: 1, info: 2 };
|
|
269
|
+
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
270
|
+
});
|
|
271
|
+
// Limit results
|
|
272
|
+
const limitedMatches = allMatches.slice(0, 100);
|
|
273
|
+
const result = {
|
|
274
|
+
path: resolvedPath,
|
|
275
|
+
matches: limitedMatches,
|
|
276
|
+
stats: {
|
|
277
|
+
filesAnalyzed: files.length,
|
|
278
|
+
totalMatches: allMatches.length,
|
|
279
|
+
byPattern,
|
|
280
|
+
bySeverity: {
|
|
281
|
+
error: errorCount,
|
|
282
|
+
warning: warningCount,
|
|
283
|
+
info: infoCount,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
return createSuccessResult(result);
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
return createErrorResult(`Failed to find patterns: ${error instanceof Error ? error.message : String(error)}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Get built-in patterns filtered by category
|
|
295
|
+
*/
|
|
296
|
+
function getBuiltinPatterns(categories) {
|
|
297
|
+
if (categories.includes("all")) {
|
|
298
|
+
return BUILTIN_PATTERNS;
|
|
299
|
+
}
|
|
300
|
+
return BUILTIN_PATTERNS.filter((p) => categories.includes(p.category));
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Collect files to analyze
|
|
304
|
+
*/
|
|
305
|
+
async function collectFiles(dirPath, files, maxFiles, currentDepth = 0) {
|
|
306
|
+
if (currentDepth > 10 || files.length >= maxFiles)
|
|
307
|
+
return;
|
|
308
|
+
try {
|
|
309
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
310
|
+
for (const entry of entries) {
|
|
311
|
+
if (files.length >= maxFiles)
|
|
312
|
+
break;
|
|
313
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
314
|
+
if (entry.isDirectory()) {
|
|
315
|
+
if (DEFAULT_EXCLUDE.includes(entry.name))
|
|
316
|
+
continue;
|
|
317
|
+
await collectFiles(fullPath, files, maxFiles, currentDepth + 1);
|
|
318
|
+
}
|
|
319
|
+
else if (entry.isFile()) {
|
|
320
|
+
// Only include Python files
|
|
321
|
+
if (fullPath.endsWith(".py") && !fullPath.endsWith(".pyi")) {
|
|
322
|
+
files.push(fullPath);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// Ignore permission errors
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Analyze a file for patterns
|
|
333
|
+
*/
|
|
334
|
+
async function analyzeFileForPatterns(filePath, patterns) {
|
|
335
|
+
try {
|
|
336
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
337
|
+
const lines = content.split("\n");
|
|
338
|
+
const matches = [];
|
|
339
|
+
// Separate regex patterns from AST patterns
|
|
340
|
+
const regexPatterns = patterns.filter((p) => !p.matcher.startsWith("AST:"));
|
|
341
|
+
const astPatterns = patterns.filter((p) => p.matcher.startsWith("AST:"));
|
|
342
|
+
// Check regex patterns line by line
|
|
343
|
+
for (let i = 0; i < lines.length; i++) {
|
|
344
|
+
const line = lines[i];
|
|
345
|
+
for (const pattern of regexPatterns) {
|
|
346
|
+
try {
|
|
347
|
+
const regex = new RegExp(pattern.matcher, "gi");
|
|
348
|
+
if (regex.test(line)) {
|
|
349
|
+
matches.push({
|
|
350
|
+
pattern: pattern.name,
|
|
351
|
+
description: pattern.description,
|
|
352
|
+
severity: pattern.severity,
|
|
353
|
+
path: filePath,
|
|
354
|
+
line: i + 1,
|
|
355
|
+
snippet: line.trim().slice(0, 100),
|
|
356
|
+
suggestion: getSuggestion(pattern.name),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
// Invalid regex, skip
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Check AST patterns
|
|
366
|
+
if (astPatterns.length > 0) {
|
|
367
|
+
const astMatches = await analyzeFileForAstPatterns(filePath, astPatterns);
|
|
368
|
+
matches.push(...astMatches);
|
|
369
|
+
}
|
|
370
|
+
return matches;
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Analyze file for AST-based patterns
|
|
378
|
+
*/
|
|
379
|
+
async function analyzeFileForAstPatterns(filePath, patterns) {
|
|
380
|
+
const matches = [];
|
|
381
|
+
try {
|
|
382
|
+
const parseResult = await parseFile(filePath);
|
|
383
|
+
const { tree, source } = parseResult;
|
|
384
|
+
const checkLongFunction = patterns.some((p) => p.matcher === "AST:long-function");
|
|
385
|
+
const checkManyParameters = patterns.some((p) => p.matcher === "AST:many-parameters");
|
|
386
|
+
// Walk the AST
|
|
387
|
+
function visit(node) {
|
|
388
|
+
if (checkLongFunction &&
|
|
389
|
+
(node.type === "function_definition" ||
|
|
390
|
+
node.type === "async_function_definition")) {
|
|
391
|
+
const startLine = node.startPosition.row;
|
|
392
|
+
const endLine = node.endPosition.row;
|
|
393
|
+
const lines = endLine - startLine;
|
|
394
|
+
if (lines > 50) {
|
|
395
|
+
const funcInfo = parseFunction(node, source);
|
|
396
|
+
matches.push({
|
|
397
|
+
pattern: "long-function",
|
|
398
|
+
description: `Function "${funcInfo.name}" has ${String(lines)} lines (recommended: <50)`,
|
|
399
|
+
severity: "warning",
|
|
400
|
+
path: filePath,
|
|
401
|
+
line: startLine + 1,
|
|
402
|
+
snippet: `def ${funcInfo.name}(...): # ${String(lines)} lines`,
|
|
403
|
+
suggestion: "Consider breaking this function into smaller functions",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (checkManyParameters &&
|
|
408
|
+
(node.type === "function_definition" ||
|
|
409
|
+
node.type === "async_function_definition")) {
|
|
410
|
+
const funcInfo = parseFunction(node, source);
|
|
411
|
+
// Exclude 'self' and 'cls' from count
|
|
412
|
+
const realParamCount = funcInfo.parameters.filter((p) => p.name !== "self" && p.name !== "cls").length;
|
|
413
|
+
if (realParamCount > 5) {
|
|
414
|
+
matches.push({
|
|
415
|
+
pattern: "many-parameters",
|
|
416
|
+
description: `Function "${funcInfo.name}" has ${String(realParamCount)} parameters (recommended: <5)`,
|
|
417
|
+
severity: "info",
|
|
418
|
+
path: filePath,
|
|
419
|
+
line: funcInfo.line,
|
|
420
|
+
snippet: `def ${funcInfo.name}(${String(realParamCount)} params...)`,
|
|
421
|
+
suggestion: "Consider using a dataclass, TypedDict, or **kwargs for many parameters",
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
for (const child of node.children) {
|
|
426
|
+
visit(child);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
visit(tree.rootNode);
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
// Ignore parse errors
|
|
433
|
+
}
|
|
434
|
+
return matches;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Get suggestion for a pattern
|
|
438
|
+
*/
|
|
439
|
+
function getSuggestion(patternName) {
|
|
440
|
+
const suggestions = {
|
|
441
|
+
"eval-usage": "Use ast.literal_eval() for safe evaluation or refactor to avoid eval",
|
|
442
|
+
"exec-usage": "Refactor to avoid exec(), use importlib for dynamic imports",
|
|
443
|
+
"hardcoded-secret": "Use environment variables or a secrets manager",
|
|
444
|
+
"sql-injection-risk": "Use parameterized queries with ? or %s placeholders",
|
|
445
|
+
"pickle-usage": "Use JSON or a safer serialization format for untrusted data",
|
|
446
|
+
"shell-injection-risk": "Avoid shell=True or sanitize input carefully",
|
|
447
|
+
"print-statement": "Use logging module for production code",
|
|
448
|
+
"mutable-default-arg": "Use None as default and create the mutable object inside the function",
|
|
449
|
+
"global-variable": "Pass values as function parameters or use a class",
|
|
450
|
+
"bare-except": "Catch specific exceptions like 'except ValueError:' or at least 'except Exception:'",
|
|
451
|
+
"star-import": "Import specific names: 'from module import name1, name2'",
|
|
452
|
+
"type-ignore": "Fix the underlying type issue instead of suppressing",
|
|
453
|
+
"assert-usage": "Use explicit validation with if/raise for runtime checks",
|
|
454
|
+
};
|
|
455
|
+
return suggestions[patternName];
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Factory function to create a customized findPatterns tool
|
|
459
|
+
*/
|
|
460
|
+
export function createFindPatternsTool(options) {
|
|
461
|
+
return defineTool({
|
|
462
|
+
name: "find_patterns_python",
|
|
463
|
+
description: TOOL_DESCRIPTION,
|
|
464
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
465
|
+
execute: async (input) => {
|
|
466
|
+
const modifiedInput = {
|
|
467
|
+
...input,
|
|
468
|
+
maxFiles: input.maxFiles ?? options?.defaultMaxFiles,
|
|
469
|
+
patterns: input.patterns ?? [
|
|
470
|
+
...BUILTIN_PATTERNS,
|
|
471
|
+
...(options?.additionalPatterns ?? []),
|
|
472
|
+
],
|
|
473
|
+
};
|
|
474
|
+
return executeFindPatterns(modifiedInput);
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* findReferences Tool
|
|
3
|
+
*
|
|
4
|
+
* Find all references to a symbol across Python codebases.
|
|
5
|
+
* Uses Tree-sitter for AST analysis.
|
|
6
|
+
*/
|
|
7
|
+
import type { Tool } from "@compilr-dev/agents";
|
|
8
|
+
import type { SymbolReference } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Reference type
|
|
11
|
+
*/
|
|
12
|
+
export type ReferenceType = "read" | "write" | "call" | "import" | "definition" | "attribute";
|
|
13
|
+
/**
|
|
14
|
+
* Input for findReferences tool
|
|
15
|
+
*/
|
|
16
|
+
export interface FindReferencesInput {
|
|
17
|
+
/** Symbol name to find */
|
|
18
|
+
symbol: string;
|
|
19
|
+
/** Scope the search to a specific file or directory */
|
|
20
|
+
scope?: string;
|
|
21
|
+
/** Maximum results to return (default: 50) */
|
|
22
|
+
limit?: number;
|
|
23
|
+
/** Include the definition itself (default: false) */
|
|
24
|
+
includeDefinition?: boolean;
|
|
25
|
+
/** Include virtual environments (default: false) */
|
|
26
|
+
includeVenv?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* References grouped by file
|
|
30
|
+
*/
|
|
31
|
+
export interface FileReferences {
|
|
32
|
+
/** File path */
|
|
33
|
+
path: string;
|
|
34
|
+
/** References in this file */
|
|
35
|
+
references: SymbolReference[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Output from findReferences tool
|
|
39
|
+
*/
|
|
40
|
+
export interface FindReferencesResult {
|
|
41
|
+
/** Symbol that was searched */
|
|
42
|
+
symbol: string;
|
|
43
|
+
/** References grouped by file */
|
|
44
|
+
byFile: FileReferences[];
|
|
45
|
+
/** Total reference count */
|
|
46
|
+
totalCount: number;
|
|
47
|
+
/** Whether results were truncated */
|
|
48
|
+
truncated: boolean;
|
|
49
|
+
/** Statistics */
|
|
50
|
+
stats: {
|
|
51
|
+
filesSearched: number;
|
|
52
|
+
timeMs: number;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* findReferences tool - Find all references to a symbol
|
|
57
|
+
*/
|
|
58
|
+
export declare const findReferencesTool: Tool<FindReferencesInput>;
|
|
59
|
+
/**
|
|
60
|
+
* Factory function to create a customized findReferences tool
|
|
61
|
+
*/
|
|
62
|
+
export declare function createFindReferencesTool(options?: {
|
|
63
|
+
defaultLimit?: number;
|
|
64
|
+
defaultIncludeDefinition?: boolean;
|
|
65
|
+
}): Tool<FindReferencesInput>;
|
|
66
|
+
//# sourceMappingURL=find-references.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-references.d.ts","sourceRoot":"","sources":["../../src/tools/find-references.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,IAAI,EAAuB,MAAM,qBAAqB,CAAC;AAGrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAKlD;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,OAAO,GACP,MAAM,GACN,QAAQ,GACR,YAAY,GACZ,WAAW,CAAC;AAEhB;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oDAAoD;IACpD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,UAAU,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB;IACjB,KAAK,EAAE;QACL,aAAa,EAAE,MAAM,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAsCD;;GAEG;AACH,eAAO,MAAM,kBAAkB,2BAK7B,CAAC;AA4UH;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,GAAG,IAAI,CAAC,mBAAmB,CAAC,CAiB5B"}
|