@aiready/pattern-detect 0.16.19 → 0.16.20
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/dist/analyzer-entry.js +9 -126
- package/dist/analyzer-entry.mjs +2 -2
- package/dist/chunk-65UQ5J2J.mjs +64 -0
- package/dist/chunk-6JTVOBJX.mjs +64 -0
- package/dist/chunk-BKRPSTT2.mjs +64 -0
- package/dist/chunk-CMWW24HW.mjs +259 -0
- package/dist/chunk-DNZS4ESD.mjs +391 -0
- package/dist/chunk-GLKAGFKX.mjs +391 -0
- package/dist/chunk-GREN7X5H.mjs +143 -0
- package/dist/chunk-JBUZ6YHE.mjs +391 -0
- package/dist/chunk-KWMNN3TG.mjs +391 -0
- package/dist/chunk-LYKRYBSM.mjs +64 -0
- package/dist/chunk-MHU3CL4R.mjs +64 -0
- package/dist/chunk-RS73WLNI.mjs +251 -0
- package/dist/chunk-SVCSIZ2A.mjs +259 -0
- package/dist/chunk-VGMM3L3O.mjs +143 -0
- package/dist/chunk-XNPID6FU.mjs +391 -0
- package/dist/cli.js +29 -147
- package/dist/cli.mjs +27 -25
- package/dist/detector-entry.js +9 -126
- package/dist/detector-entry.mjs +1 -1
- package/dist/index.js +9 -126
- package/dist/index.mjs +3 -3
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-6JTVOBJX.mjs";
|
|
3
3
|
import {
|
|
4
4
|
analyzePatterns,
|
|
5
5
|
generateSummary
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-DNZS4ESD.mjs";
|
|
7
|
+
import "./chunk-VGMM3L3O.mjs";
|
|
8
8
|
import {
|
|
9
9
|
filterBySeverity
|
|
10
10
|
} from "./chunk-I6ETJC7L.mjs";
|
|
@@ -23,7 +23,9 @@ import {
|
|
|
23
23
|
resolveOutputPath,
|
|
24
24
|
Severity,
|
|
25
25
|
getSeverityBadge as getSeverityBadge2,
|
|
26
|
-
getSeverityValue as getSeverityValue2
|
|
26
|
+
getSeverityValue as getSeverityValue2,
|
|
27
|
+
printTerminalHeader,
|
|
28
|
+
getTerminalDivider
|
|
27
29
|
} from "@aiready/core";
|
|
28
30
|
|
|
29
31
|
// src/cli-output.ts
|
|
@@ -32,6 +34,7 @@ import {
|
|
|
32
34
|
getSeverityValue,
|
|
33
35
|
generateReportHead,
|
|
34
36
|
generateStatCards,
|
|
37
|
+
generateScoreCard,
|
|
35
38
|
generateTable,
|
|
36
39
|
generateReportFooter
|
|
37
40
|
} from "@aiready/core";
|
|
@@ -52,10 +55,14 @@ function generateHTMLReport(results, summary) {
|
|
|
52
55
|
const { metadata } = data;
|
|
53
56
|
const s = data.summary;
|
|
54
57
|
const head = generateReportHead("AIReady - Pattern Detection Report");
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
const score = Math.max(
|
|
59
|
+
0,
|
|
60
|
+
100 - Math.round((s.duplicates?.length || 0) / (s.totalFiles || 1) * 20)
|
|
61
|
+
);
|
|
62
|
+
const scoreCard = generateScoreCard(
|
|
63
|
+
`${score}%`,
|
|
64
|
+
"AI Ready Score (Deduplication)"
|
|
65
|
+
);
|
|
59
66
|
const stats = generateStatCards([
|
|
60
67
|
{ value: s.totalFiles, label: "Files Analyzed" },
|
|
61
68
|
{ value: s.duplicates?.length || 0, label: "Duplicate Clusters" },
|
|
@@ -254,12 +261,7 @@ async function patternActionHandler(directory, options) {
|
|
|
254
261
|
\u2713 HTML report saved to ${outputPath}`));
|
|
255
262
|
return;
|
|
256
263
|
}
|
|
257
|
-
|
|
258
|
-
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
259
|
-
const divider = "\u2501".repeat(dividerWidth);
|
|
260
|
-
console.log(chalk.cyan(divider));
|
|
261
|
-
console.log(chalk.bold.white(" PATTERN ANALYSIS SUMMARY"));
|
|
262
|
-
console.log(chalk.cyan(divider) + "\n");
|
|
264
|
+
printTerminalHeader("PATTERN ANALYSIS SUMMARY");
|
|
263
265
|
console.log(chalk.white(`\u{1F4C1} Files analyzed: ${chalk.bold(results.length)}`));
|
|
264
266
|
console.log(
|
|
265
267
|
chalk.yellow(
|
|
@@ -274,9 +276,9 @@ async function patternActionHandler(directory, options) {
|
|
|
274
276
|
console.log(chalk.gray(`\u23F1 Analysis time: ${chalk.bold(elapsedTime + "s")}`));
|
|
275
277
|
const sortedTypes = Object.entries(summary.patternsByType).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
276
278
|
if (sortedTypes.length > 0) {
|
|
277
|
-
console.log(
|
|
279
|
+
console.log("\n" + getTerminalDivider());
|
|
278
280
|
console.log(chalk.bold.white(" PATTERNS BY TYPE"));
|
|
279
|
-
console.log(
|
|
281
|
+
console.log(getTerminalDivider() + "\n");
|
|
280
282
|
sortedTypes.forEach(([type, count]) => {
|
|
281
283
|
const icon = getPatternIcon(type);
|
|
282
284
|
console.log(
|
|
@@ -285,11 +287,11 @@ async function patternActionHandler(directory, options) {
|
|
|
285
287
|
});
|
|
286
288
|
}
|
|
287
289
|
if (!finalOptions.showRawDuplicates && groups && groups.length > 0) {
|
|
288
|
-
console.log(
|
|
290
|
+
console.log("\n" + getTerminalDivider());
|
|
289
291
|
console.log(
|
|
290
292
|
chalk.bold.white(` \u{1F4E6} DUPLICATE GROUPS (${groups.length} file pairs)`)
|
|
291
293
|
);
|
|
292
|
-
console.log(
|
|
294
|
+
console.log(getTerminalDivider() + "\n");
|
|
293
295
|
const topGroups = groups.sort((a, b) => {
|
|
294
296
|
const bVal = getSeverityValue2(b.severity);
|
|
295
297
|
const aVal = getSeverityValue2(a.severity);
|
|
@@ -330,11 +332,11 @@ async function patternActionHandler(directory, options) {
|
|
|
330
332
|
}
|
|
331
333
|
}
|
|
332
334
|
if (!finalOptions.showRawDuplicates && clusters && clusters.length > 0) {
|
|
333
|
-
console.log(
|
|
335
|
+
console.log("\n" + getTerminalDivider());
|
|
334
336
|
console.log(
|
|
335
337
|
chalk.bold.white(` \u{1F3AF} REFACTOR CLUSTERS (${clusters.length} patterns)`)
|
|
336
338
|
);
|
|
337
|
-
console.log(
|
|
339
|
+
console.log(getTerminalDivider() + "\n");
|
|
338
340
|
clusters.sort((a, b) => b.totalTokenCost - a.totalTokenCost).forEach((cluster, idx) => {
|
|
339
341
|
const severityBadge = getSeverityBadge2(cluster.severity);
|
|
340
342
|
console.log(`${idx + 1}. ${severityBadge} ${chalk.bold(cluster.name)}`);
|
|
@@ -362,9 +364,9 @@ async function patternActionHandler(directory, options) {
|
|
|
362
364
|
});
|
|
363
365
|
}
|
|
364
366
|
if (totalIssues > 0 && (finalOptions.showRawDuplicates || !groups || groups.length === 0)) {
|
|
365
|
-
console.log(
|
|
367
|
+
console.log("\n" + getTerminalDivider());
|
|
366
368
|
console.log(chalk.bold.white(" TOP DUPLICATE PATTERNS"));
|
|
367
|
-
console.log(
|
|
369
|
+
console.log(getTerminalDivider() + "\n");
|
|
368
370
|
const topDuplicates = filteredDuplicates.sort((a, b) => {
|
|
369
371
|
const bVal = getSeverityValue2(b.severity);
|
|
370
372
|
const aVal = getSeverityValue2(a.severity);
|
|
@@ -411,9 +413,9 @@ async function patternActionHandler(directory, options) {
|
|
|
411
413
|
(issue) => getSeverityValue2(issue.severity) === 4
|
|
412
414
|
);
|
|
413
415
|
if (criticalIssues.length > 0) {
|
|
414
|
-
console.log(
|
|
416
|
+
console.log(getTerminalDivider());
|
|
415
417
|
console.log(chalk.bold.white(" CRITICAL ISSUES (>95% similar)"));
|
|
416
|
-
console.log(
|
|
418
|
+
console.log(getTerminalDivider() + "\n");
|
|
417
419
|
criticalIssues.slice(0, 5).forEach((issue) => {
|
|
418
420
|
console.log(
|
|
419
421
|
chalk.red("\u25CF ") + chalk.white(`${issue.file}:${issue.location.line}`)
|
|
@@ -450,7 +452,7 @@ async function patternActionHandler(directory, options) {
|
|
|
450
452
|
);
|
|
451
453
|
console.log("");
|
|
452
454
|
}
|
|
453
|
-
console.log(
|
|
455
|
+
console.log(getTerminalDivider());
|
|
454
456
|
if (totalIssues > 0) {
|
|
455
457
|
console.log(
|
|
456
458
|
chalk.white(
|
package/dist/detector-entry.js
CHANGED
|
@@ -167,144 +167,27 @@ function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
// src/
|
|
170
|
+
// src/core/normalizer.ts
|
|
171
171
|
function normalizeCode(code, isPython = false) {
|
|
172
|
+
if (!code) return "";
|
|
172
173
|
let normalized = code;
|
|
173
174
|
if (isPython) {
|
|
174
175
|
normalized = normalized.replace(/#.*/g, "");
|
|
175
176
|
} else {
|
|
176
|
-
normalized = normalized.replace(
|
|
177
|
+
normalized = normalized.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
177
178
|
}
|
|
178
|
-
return normalized.replace(/[
|
|
179
|
+
return normalized.replace(/"[^"]*"/g, '"STR"').replace(/'[^']*'/g, "'STR'").replace(/`[^`]*`/g, "`STR`").replace(/\b\d+\b/g, "NUM").replace(/\s+/g, " ").trim().toLowerCase();
|
|
179
180
|
}
|
|
181
|
+
|
|
182
|
+
// src/detector.ts
|
|
180
183
|
function extractBlocks(file, content) {
|
|
181
|
-
|
|
182
|
-
if (isPython) {
|
|
183
|
-
return extractBlocksPython(file, content);
|
|
184
|
-
}
|
|
185
|
-
const blocks = [];
|
|
186
|
-
const lines = content.split("\n");
|
|
187
|
-
const blockRegex = /^\s*(?:export\s+)?(?:async\s+)?(?:public\s+|private\s+|protected\s+|internal\s+|static\s+|readonly\s+|virtual\s+|abstract\s+|override\s+)*(function|class|interface|type|enum|record|struct|void|func|[a-zA-Z0-9_<>[]]+)\s+([a-zA-Z0-9_]+)(?:\s*\(|(?:\s+extends|\s+implements|\s+where)?\s*\{)|^\s*(?:export\s+)?const\s+([a-zA-Z0-9_]+)\s*=\s*[a-zA-Z0-9_.]+\.object\(|^\s*(app\.(?:get|post|put|delete|patch|use))\(/gm;
|
|
188
|
-
let match;
|
|
189
|
-
while ((match = blockRegex.exec(content)) !== null) {
|
|
190
|
-
const startLine = content.substring(0, match.index).split("\n").length;
|
|
191
|
-
let type;
|
|
192
|
-
let name;
|
|
193
|
-
if (match[1]) {
|
|
194
|
-
type = match[1];
|
|
195
|
-
name = match[2];
|
|
196
|
-
} else if (match[3]) {
|
|
197
|
-
type = "const";
|
|
198
|
-
name = match[3];
|
|
199
|
-
} else {
|
|
200
|
-
type = "handler";
|
|
201
|
-
name = match[4];
|
|
202
|
-
}
|
|
203
|
-
let endLine = -1;
|
|
204
|
-
let openBraces = 0;
|
|
205
|
-
let foundStart = false;
|
|
206
|
-
for (let i = match.index; i < content.length; i++) {
|
|
207
|
-
if (content[i] === "{") {
|
|
208
|
-
openBraces++;
|
|
209
|
-
foundStart = true;
|
|
210
|
-
} else if (content[i] === "}") {
|
|
211
|
-
openBraces--;
|
|
212
|
-
}
|
|
213
|
-
if (foundStart && openBraces === 0) {
|
|
214
|
-
endLine = content.substring(0, i + 1).split("\n").length;
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (endLine === -1) {
|
|
219
|
-
const remaining = content.slice(match.index);
|
|
220
|
-
const nextLineMatch = remaining.indexOf("\n");
|
|
221
|
-
if (nextLineMatch !== -1) {
|
|
222
|
-
endLine = startLine;
|
|
223
|
-
} else {
|
|
224
|
-
endLine = lines.length;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
endLine = Math.max(startLine, endLine);
|
|
228
|
-
const blockCode = lines.slice(startLine - 1, endLine).join("\n");
|
|
229
|
-
const tokens = (0, import_core2.estimateTokens)(blockCode);
|
|
230
|
-
blocks.push({
|
|
231
|
-
file,
|
|
232
|
-
startLine,
|
|
233
|
-
endLine,
|
|
234
|
-
code: blockCode,
|
|
235
|
-
tokens,
|
|
236
|
-
patternType: inferPatternType(type, name)
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
return blocks;
|
|
240
|
-
}
|
|
241
|
-
function extractBlocksPython(file, content) {
|
|
242
|
-
const blocks = [];
|
|
243
|
-
const lines = content.split("\n");
|
|
244
|
-
const blockRegex = /^\s*(?:async\s+)?(def|class)\s+([a-zA-Z0-9_]+)/gm;
|
|
245
|
-
let match;
|
|
246
|
-
while ((match = blockRegex.exec(content)) !== null) {
|
|
247
|
-
const startLinePos = content.substring(0, match.index).split("\n").length;
|
|
248
|
-
const startLineIdx = startLinePos - 1;
|
|
249
|
-
const initialIndent = lines[startLineIdx].search(/\S/);
|
|
250
|
-
let endLineIdx = startLineIdx;
|
|
251
|
-
for (let i = startLineIdx + 1; i < lines.length; i++) {
|
|
252
|
-
const line = lines[i];
|
|
253
|
-
if (line.trim().length === 0) {
|
|
254
|
-
endLineIdx = i;
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
const currentIndent = line.search(/\S/);
|
|
258
|
-
if (currentIndent <= initialIndent) {
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
endLineIdx = i;
|
|
262
|
-
}
|
|
263
|
-
while (endLineIdx > startLineIdx && lines[endLineIdx].trim().length === 0) {
|
|
264
|
-
endLineIdx--;
|
|
265
|
-
}
|
|
266
|
-
const blockCode = lines.slice(startLineIdx, endLineIdx + 1).join("\n");
|
|
267
|
-
const tokens = (0, import_core2.estimateTokens)(blockCode);
|
|
268
|
-
blocks.push({
|
|
269
|
-
file,
|
|
270
|
-
startLine: startLinePos,
|
|
271
|
-
endLine: endLineIdx + 1,
|
|
272
|
-
code: blockCode,
|
|
273
|
-
tokens,
|
|
274
|
-
patternType: inferPatternType(match[1], match[2])
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
return blocks;
|
|
278
|
-
}
|
|
279
|
-
function inferPatternType(keyword, name) {
|
|
280
|
-
const n = name.toLowerCase();
|
|
281
|
-
if (keyword === "handler" || n.includes("handler") || n.includes("controller") || n.startsWith("app.")) {
|
|
282
|
-
return "api-handler";
|
|
283
|
-
}
|
|
284
|
-
if (n.includes("validate") || n.includes("schema")) return "validator";
|
|
285
|
-
if (n.includes("util") || n.includes("helper")) return "utility";
|
|
286
|
-
if (keyword === "class") return "class-method";
|
|
287
|
-
if (n.match(/^[A-Z]/)) return "component";
|
|
288
|
-
if (keyword === "function") return "function";
|
|
289
|
-
return "unknown";
|
|
184
|
+
return (0, import_core2.extractCodeBlocks)(file, content);
|
|
290
185
|
}
|
|
291
186
|
function calculateSimilarity(a, b) {
|
|
292
|
-
|
|
293
|
-
const tokensA = a.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 0);
|
|
294
|
-
const tokensB = b.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 0);
|
|
295
|
-
if (tokensA.length === 0 || tokensB.length === 0) return 0;
|
|
296
|
-
const setA = new Set(tokensA);
|
|
297
|
-
const setB = new Set(tokensB);
|
|
298
|
-
const intersection = new Set([...setA].filter((x) => setB.has(x)));
|
|
299
|
-
const union = /* @__PURE__ */ new Set([...setA, ...setB]);
|
|
300
|
-
return intersection.size / union.size;
|
|
187
|
+
return (0, import_core2.calculateStringSimilarity)(a, b);
|
|
301
188
|
}
|
|
302
189
|
function calculateConfidence(similarity, tokens, lines) {
|
|
303
|
-
|
|
304
|
-
if (lines > 20) confidence += 0.05;
|
|
305
|
-
if (tokens > 200) confidence += 0.05;
|
|
306
|
-
if (lines < 5) confidence -= 0.1;
|
|
307
|
-
return Math.max(0, Math.min(1, confidence));
|
|
190
|
+
return (0, import_core2.calculateHeuristicConfidence)(similarity, tokens, lines);
|
|
308
191
|
}
|
|
309
192
|
async function detectDuplicatePatterns(fileContents, options) {
|
|
310
193
|
const {
|
package/dist/detector-entry.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -230,144 +230,27 @@ function getSeverityThreshold(severity) {
|
|
|
230
230
|
return thresholds[severity] || 0;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
// src/
|
|
233
|
+
// src/core/normalizer.ts
|
|
234
234
|
function normalizeCode(code, isPython = false) {
|
|
235
|
+
if (!code) return "";
|
|
235
236
|
let normalized = code;
|
|
236
237
|
if (isPython) {
|
|
237
238
|
normalized = normalized.replace(/#.*/g, "");
|
|
238
239
|
} else {
|
|
239
|
-
normalized = normalized.replace(
|
|
240
|
+
normalized = normalized.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
240
241
|
}
|
|
241
|
-
return normalized.replace(/[
|
|
242
|
+
return normalized.replace(/"[^"]*"/g, '"STR"').replace(/'[^']*'/g, "'STR'").replace(/`[^`]*`/g, "`STR`").replace(/\b\d+\b/g, "NUM").replace(/\s+/g, " ").trim().toLowerCase();
|
|
242
243
|
}
|
|
244
|
+
|
|
245
|
+
// src/detector.ts
|
|
243
246
|
function extractBlocks(file, content) {
|
|
244
|
-
|
|
245
|
-
if (isPython) {
|
|
246
|
-
return extractBlocksPython(file, content);
|
|
247
|
-
}
|
|
248
|
-
const blocks = [];
|
|
249
|
-
const lines = content.split("\n");
|
|
250
|
-
const blockRegex = /^\s*(?:export\s+)?(?:async\s+)?(?:public\s+|private\s+|protected\s+|internal\s+|static\s+|readonly\s+|virtual\s+|abstract\s+|override\s+)*(function|class|interface|type|enum|record|struct|void|func|[a-zA-Z0-9_<>[]]+)\s+([a-zA-Z0-9_]+)(?:\s*\(|(?:\s+extends|\s+implements|\s+where)?\s*\{)|^\s*(?:export\s+)?const\s+([a-zA-Z0-9_]+)\s*=\s*[a-zA-Z0-9_.]+\.object\(|^\s*(app\.(?:get|post|put|delete|patch|use))\(/gm;
|
|
251
|
-
let match;
|
|
252
|
-
while ((match = blockRegex.exec(content)) !== null) {
|
|
253
|
-
const startLine = content.substring(0, match.index).split("\n").length;
|
|
254
|
-
let type;
|
|
255
|
-
let name;
|
|
256
|
-
if (match[1]) {
|
|
257
|
-
type = match[1];
|
|
258
|
-
name = match[2];
|
|
259
|
-
} else if (match[3]) {
|
|
260
|
-
type = "const";
|
|
261
|
-
name = match[3];
|
|
262
|
-
} else {
|
|
263
|
-
type = "handler";
|
|
264
|
-
name = match[4];
|
|
265
|
-
}
|
|
266
|
-
let endLine = -1;
|
|
267
|
-
let openBraces = 0;
|
|
268
|
-
let foundStart = false;
|
|
269
|
-
for (let i = match.index; i < content.length; i++) {
|
|
270
|
-
if (content[i] === "{") {
|
|
271
|
-
openBraces++;
|
|
272
|
-
foundStart = true;
|
|
273
|
-
} else if (content[i] === "}") {
|
|
274
|
-
openBraces--;
|
|
275
|
-
}
|
|
276
|
-
if (foundStart && openBraces === 0) {
|
|
277
|
-
endLine = content.substring(0, i + 1).split("\n").length;
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
if (endLine === -1) {
|
|
282
|
-
const remaining = content.slice(match.index);
|
|
283
|
-
const nextLineMatch = remaining.indexOf("\n");
|
|
284
|
-
if (nextLineMatch !== -1) {
|
|
285
|
-
endLine = startLine;
|
|
286
|
-
} else {
|
|
287
|
-
endLine = lines.length;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
endLine = Math.max(startLine, endLine);
|
|
291
|
-
const blockCode = lines.slice(startLine - 1, endLine).join("\n");
|
|
292
|
-
const tokens = (0, import_core2.estimateTokens)(blockCode);
|
|
293
|
-
blocks.push({
|
|
294
|
-
file,
|
|
295
|
-
startLine,
|
|
296
|
-
endLine,
|
|
297
|
-
code: blockCode,
|
|
298
|
-
tokens,
|
|
299
|
-
patternType: inferPatternType(type, name)
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
return blocks;
|
|
303
|
-
}
|
|
304
|
-
function extractBlocksPython(file, content) {
|
|
305
|
-
const blocks = [];
|
|
306
|
-
const lines = content.split("\n");
|
|
307
|
-
const blockRegex = /^\s*(?:async\s+)?(def|class)\s+([a-zA-Z0-9_]+)/gm;
|
|
308
|
-
let match;
|
|
309
|
-
while ((match = blockRegex.exec(content)) !== null) {
|
|
310
|
-
const startLinePos = content.substring(0, match.index).split("\n").length;
|
|
311
|
-
const startLineIdx = startLinePos - 1;
|
|
312
|
-
const initialIndent = lines[startLineIdx].search(/\S/);
|
|
313
|
-
let endLineIdx = startLineIdx;
|
|
314
|
-
for (let i = startLineIdx + 1; i < lines.length; i++) {
|
|
315
|
-
const line = lines[i];
|
|
316
|
-
if (line.trim().length === 0) {
|
|
317
|
-
endLineIdx = i;
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
const currentIndent = line.search(/\S/);
|
|
321
|
-
if (currentIndent <= initialIndent) {
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
endLineIdx = i;
|
|
325
|
-
}
|
|
326
|
-
while (endLineIdx > startLineIdx && lines[endLineIdx].trim().length === 0) {
|
|
327
|
-
endLineIdx--;
|
|
328
|
-
}
|
|
329
|
-
const blockCode = lines.slice(startLineIdx, endLineIdx + 1).join("\n");
|
|
330
|
-
const tokens = (0, import_core2.estimateTokens)(blockCode);
|
|
331
|
-
blocks.push({
|
|
332
|
-
file,
|
|
333
|
-
startLine: startLinePos,
|
|
334
|
-
endLine: endLineIdx + 1,
|
|
335
|
-
code: blockCode,
|
|
336
|
-
tokens,
|
|
337
|
-
patternType: inferPatternType(match[1], match[2])
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
return blocks;
|
|
341
|
-
}
|
|
342
|
-
function inferPatternType(keyword, name) {
|
|
343
|
-
const n = name.toLowerCase();
|
|
344
|
-
if (keyword === "handler" || n.includes("handler") || n.includes("controller") || n.startsWith("app.")) {
|
|
345
|
-
return "api-handler";
|
|
346
|
-
}
|
|
347
|
-
if (n.includes("validate") || n.includes("schema")) return "validator";
|
|
348
|
-
if (n.includes("util") || n.includes("helper")) return "utility";
|
|
349
|
-
if (keyword === "class") return "class-method";
|
|
350
|
-
if (n.match(/^[A-Z]/)) return "component";
|
|
351
|
-
if (keyword === "function") return "function";
|
|
352
|
-
return "unknown";
|
|
247
|
+
return (0, import_core2.extractCodeBlocks)(file, content);
|
|
353
248
|
}
|
|
354
249
|
function calculateSimilarity(a, b) {
|
|
355
|
-
|
|
356
|
-
const tokensA = a.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 0);
|
|
357
|
-
const tokensB = b.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 0);
|
|
358
|
-
if (tokensA.length === 0 || tokensB.length === 0) return 0;
|
|
359
|
-
const setA = new Set(tokensA);
|
|
360
|
-
const setB = new Set(tokensB);
|
|
361
|
-
const intersection = new Set([...setA].filter((x) => setB.has(x)));
|
|
362
|
-
const union = /* @__PURE__ */ new Set([...setA, ...setB]);
|
|
363
|
-
return intersection.size / union.size;
|
|
250
|
+
return (0, import_core2.calculateStringSimilarity)(a, b);
|
|
364
251
|
}
|
|
365
252
|
function calculateConfidence(similarity, tokens, lines) {
|
|
366
|
-
|
|
367
|
-
if (lines > 20) confidence += 0.05;
|
|
368
|
-
if (tokens > 200) confidence += 0.05;
|
|
369
|
-
if (lines < 5) confidence -= 0.1;
|
|
370
|
-
return Math.max(0, Math.min(1, confidence));
|
|
253
|
+
return (0, import_core2.calculateHeuristicConfidence)(similarity, tokens, lines);
|
|
371
254
|
}
|
|
372
255
|
async function detectDuplicatePatterns(fileContents, options) {
|
|
373
256
|
const {
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PatternDetectProvider,
|
|
3
3
|
Severity
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6JTVOBJX.mjs";
|
|
5
5
|
import {
|
|
6
6
|
analyzePatterns,
|
|
7
7
|
createRefactorClusters,
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
generateSummary,
|
|
10
10
|
getSmartDefaults,
|
|
11
11
|
groupDuplicatesByFilePair
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-DNZS4ESD.mjs";
|
|
13
13
|
import {
|
|
14
14
|
detectDuplicatePatterns
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-VGMM3L3O.mjs";
|
|
16
16
|
import {
|
|
17
17
|
CONTEXT_RULES,
|
|
18
18
|
calculateSeverity,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/pattern-detect",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.20",
|
|
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",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"commander": "^14.0.0",
|
|
67
67
|
"chalk": "^5.3.0",
|
|
68
|
-
"@aiready/core": "0.23.
|
|
68
|
+
"@aiready/core": "0.23.21"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"tsup": "^8.3.5",
|