@aiready/pattern-detect 0.11.31 → 0.11.32
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/CONTRIBUTING.md +8 -1
- package/README.md +1 -1
- package/dist/chunk-SLDK5PQK.mjs +1129 -0
- package/dist/cli.js +244 -63
- package/dist/cli.mjs +160 -36
- package/dist/index.js +96 -30
- package/dist/index.mjs +1 -1
- package/dist/python-extractor-ELAKYK2W.mjs +140 -0
- package/package.json +2 -2
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/extractors/python-extractor.ts
|
|
2
|
+
import { getParser } from "@aiready/core";
|
|
3
|
+
async function extractPythonPatterns(files) {
|
|
4
|
+
const patterns = [];
|
|
5
|
+
const parser = getParser("dummy.py");
|
|
6
|
+
if (!parser) {
|
|
7
|
+
console.warn("Python parser not available");
|
|
8
|
+
return patterns;
|
|
9
|
+
}
|
|
10
|
+
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
|
|
11
|
+
for (const file of pythonFiles) {
|
|
12
|
+
try {
|
|
13
|
+
const fs = await import("fs");
|
|
14
|
+
const code = await fs.promises.readFile(file, "utf-8");
|
|
15
|
+
const result = parser.parse(code, file);
|
|
16
|
+
for (const exp of result.exports) {
|
|
17
|
+
if (exp.type === "function") {
|
|
18
|
+
patterns.push({
|
|
19
|
+
file,
|
|
20
|
+
name: exp.name,
|
|
21
|
+
type: "function",
|
|
22
|
+
startLine: exp.loc?.start.line || 0,
|
|
23
|
+
endLine: exp.loc?.end.line || 0,
|
|
24
|
+
imports: exp.imports || [],
|
|
25
|
+
dependencies: exp.dependencies || [],
|
|
26
|
+
signature: generatePythonSignature(exp),
|
|
27
|
+
language: "python"
|
|
28
|
+
});
|
|
29
|
+
} else if (exp.type === "class") {
|
|
30
|
+
patterns.push({
|
|
31
|
+
file,
|
|
32
|
+
name: exp.name,
|
|
33
|
+
type: "class",
|
|
34
|
+
startLine: exp.loc?.start.line || 0,
|
|
35
|
+
endLine: exp.loc?.end.line || 0,
|
|
36
|
+
imports: exp.imports || [],
|
|
37
|
+
dependencies: exp.dependencies || [],
|
|
38
|
+
signature: `class ${exp.name}`,
|
|
39
|
+
language: "python"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.warn(`Failed to extract patterns from ${file}:`, error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return patterns;
|
|
48
|
+
}
|
|
49
|
+
function generatePythonSignature(exp) {
|
|
50
|
+
const params = exp.parameters?.join(", ") || "";
|
|
51
|
+
return `def ${exp.name}(${params})`;
|
|
52
|
+
}
|
|
53
|
+
function calculatePythonSimilarity(pattern1, pattern2) {
|
|
54
|
+
let similarity = 0;
|
|
55
|
+
let factors = 0;
|
|
56
|
+
const nameSimilarity = calculateNameSimilarity(pattern1.name, pattern2.name);
|
|
57
|
+
similarity += nameSimilarity * 0.3;
|
|
58
|
+
factors += 0.3;
|
|
59
|
+
const importSimilarity = calculateImportSimilarity(
|
|
60
|
+
pattern1.imports || [],
|
|
61
|
+
pattern2.imports || []
|
|
62
|
+
);
|
|
63
|
+
similarity += importSimilarity * 0.4;
|
|
64
|
+
factors += 0.4;
|
|
65
|
+
if (pattern1.type === pattern2.type) {
|
|
66
|
+
similarity += 0.1;
|
|
67
|
+
}
|
|
68
|
+
factors += 0.1;
|
|
69
|
+
const sigSimilarity = calculateSignatureSimilarity(
|
|
70
|
+
pattern1.signature,
|
|
71
|
+
pattern2.signature
|
|
72
|
+
);
|
|
73
|
+
similarity += sigSimilarity * 0.2;
|
|
74
|
+
factors += 0.2;
|
|
75
|
+
return factors > 0 ? similarity / factors : 0;
|
|
76
|
+
}
|
|
77
|
+
function calculateNameSimilarity(name1, name2) {
|
|
78
|
+
if (name1 === name2) return 1;
|
|
79
|
+
const clean1 = name1.replace(
|
|
80
|
+
/^(get|set|is|has|create|delete|update|fetch)_?/,
|
|
81
|
+
""
|
|
82
|
+
);
|
|
83
|
+
const clean2 = name2.replace(
|
|
84
|
+
/^(get|set|is|has|create|delete|update|fetch)_?/,
|
|
85
|
+
""
|
|
86
|
+
);
|
|
87
|
+
if (clean1 === clean2) return 0.9;
|
|
88
|
+
if (clean1.includes(clean2) || clean2.includes(clean1)) {
|
|
89
|
+
return 0.7;
|
|
90
|
+
}
|
|
91
|
+
const set1 = new Set(clean1.split("_"));
|
|
92
|
+
const set2 = new Set(clean2.split("_"));
|
|
93
|
+
const intersection = new Set([...set1].filter((x) => set2.has(x)));
|
|
94
|
+
const union = /* @__PURE__ */ new Set([...set1, ...set2]);
|
|
95
|
+
return intersection.size / union.size;
|
|
96
|
+
}
|
|
97
|
+
function calculateImportSimilarity(imports1, imports2) {
|
|
98
|
+
if (imports1.length === 0 && imports2.length === 0) return 1;
|
|
99
|
+
if (imports1.length === 0 || imports2.length === 0) return 0;
|
|
100
|
+
const set1 = new Set(imports1);
|
|
101
|
+
const set2 = new Set(imports2);
|
|
102
|
+
const intersection = new Set([...set1].filter((x) => set2.has(x)));
|
|
103
|
+
const union = /* @__PURE__ */ new Set([...set1, ...set2]);
|
|
104
|
+
return intersection.size / union.size;
|
|
105
|
+
}
|
|
106
|
+
function calculateSignatureSimilarity(sig1, sig2) {
|
|
107
|
+
if (sig1 === sig2) return 1;
|
|
108
|
+
const params1 = (sig1.match(/\([^)]*\)/)?.[0] || "").split(",").filter(Boolean).length;
|
|
109
|
+
const params2 = (sig2.match(/\([^)]*\)/)?.[0] || "").split(",").filter(Boolean).length;
|
|
110
|
+
if (params1 === params2) return 0.8;
|
|
111
|
+
if (Math.abs(params1 - params2) === 1) return 0.5;
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
function detectPythonAntiPatterns(patterns) {
|
|
115
|
+
const antiPatterns = [];
|
|
116
|
+
const nameGroups = /* @__PURE__ */ new Map();
|
|
117
|
+
for (const pattern of patterns) {
|
|
118
|
+
const baseName = pattern.name.replace(
|
|
119
|
+
/^(get|set|create|delete|update)_/,
|
|
120
|
+
""
|
|
121
|
+
);
|
|
122
|
+
if (!nameGroups.has(baseName)) {
|
|
123
|
+
nameGroups.set(baseName, []);
|
|
124
|
+
}
|
|
125
|
+
nameGroups.get(baseName).push(pattern);
|
|
126
|
+
}
|
|
127
|
+
for (const [baseName, group] of nameGroups) {
|
|
128
|
+
if (group.length >= 3) {
|
|
129
|
+
antiPatterns.push(
|
|
130
|
+
`Found ${group.length} functions with similar names (${baseName}): Consider consolidating`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return antiPatterns;
|
|
135
|
+
}
|
|
136
|
+
export {
|
|
137
|
+
calculatePythonSimilarity,
|
|
138
|
+
detectPythonAntiPatterns,
|
|
139
|
+
extractPythonPatterns
|
|
140
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/pattern-detect",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.32",
|
|
4
4
|
"description": "Semantic duplicate pattern detection for AI-generated code - finds similar implementations that waste AI context tokens",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"commander": "^14.0.0",
|
|
47
47
|
"chalk": "^5.3.0",
|
|
48
|
-
"@aiready/core": "0.9.
|
|
48
|
+
"@aiready/core": "0.9.33"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"tsup": "^8.3.5",
|