@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
package/dist/cli.js
CHANGED
|
@@ -113,8 +113,14 @@ function calculatePythonSimilarity(pattern1, pattern2) {
|
|
|
113
113
|
}
|
|
114
114
|
function calculateNameSimilarity(name1, name2) {
|
|
115
115
|
if (name1 === name2) return 1;
|
|
116
|
-
const clean1 = name1.replace(
|
|
117
|
-
|
|
116
|
+
const clean1 = name1.replace(
|
|
117
|
+
/^(get|set|is|has|create|delete|update|fetch)_?/,
|
|
118
|
+
""
|
|
119
|
+
);
|
|
120
|
+
const clean2 = name2.replace(
|
|
121
|
+
/^(get|set|is|has|create|delete|update|fetch)_?/,
|
|
122
|
+
""
|
|
123
|
+
);
|
|
118
124
|
if (clean1 === clean2) return 0.9;
|
|
119
125
|
if (clean1.includes(clean2) || clean2.includes(clean1)) {
|
|
120
126
|
return 0.7;
|
|
@@ -146,7 +152,10 @@ function detectPythonAntiPatterns(patterns) {
|
|
|
146
152
|
const antiPatterns = [];
|
|
147
153
|
const nameGroups = /* @__PURE__ */ new Map();
|
|
148
154
|
for (const pattern of patterns) {
|
|
149
|
-
const baseName = pattern.name.replace(
|
|
155
|
+
const baseName = pattern.name.replace(
|
|
156
|
+
/^(get|set|create|delete|update)_/,
|
|
157
|
+
""
|
|
158
|
+
);
|
|
150
159
|
if (!nameGroups.has(baseName)) {
|
|
151
160
|
nameGroups.set(baseName, []);
|
|
152
161
|
}
|
|
@@ -423,7 +432,9 @@ async function detectDuplicatePatterns(files, options) {
|
|
|
423
432
|
const pythonFiles = files.filter((f) => f.file.toLowerCase().endsWith(".py"));
|
|
424
433
|
if (pythonFiles.length > 0) {
|
|
425
434
|
const { extractPythonPatterns: extractPythonPatterns2 } = await Promise.resolve().then(() => (init_python_extractor(), python_extractor_exports));
|
|
426
|
-
const patterns = await extractPythonPatterns2(
|
|
435
|
+
const patterns = await extractPythonPatterns2(
|
|
436
|
+
pythonFiles.map((f) => f.file)
|
|
437
|
+
);
|
|
427
438
|
const pythonBlocks = patterns.filter((p) => p.code && p.code.trim().length > 0).map((p) => ({
|
|
428
439
|
content: p.code,
|
|
429
440
|
startLine: p.startLine,
|
|
@@ -438,8 +449,12 @@ async function detectDuplicatePatterns(files, options) {
|
|
|
438
449
|
console.log(`Added ${pythonBlocks.length} Python patterns`);
|
|
439
450
|
}
|
|
440
451
|
if (!approx && allBlocks.length > 500) {
|
|
441
|
-
console.log(
|
|
442
|
-
|
|
452
|
+
console.log(
|
|
453
|
+
`\u26A0\uFE0F Using --no-approx mode with ${allBlocks.length} blocks may be slow (O(B\xB2) complexity).`
|
|
454
|
+
);
|
|
455
|
+
console.log(
|
|
456
|
+
` Consider using approximate mode (default) for better performance.`
|
|
457
|
+
);
|
|
443
458
|
}
|
|
444
459
|
const stopwords = /* @__PURE__ */ new Set([
|
|
445
460
|
"return",
|
|
@@ -486,9 +501,13 @@ async function detectDuplicatePatterns(files, options) {
|
|
|
486
501
|
}
|
|
487
502
|
const totalComparisons = approx ? void 0 : allBlocks.length * (allBlocks.length - 1) / 2;
|
|
488
503
|
if (totalComparisons !== void 0) {
|
|
489
|
-
console.log(
|
|
504
|
+
console.log(
|
|
505
|
+
`Processing ${totalComparisons.toLocaleString()} comparisons in batches...`
|
|
506
|
+
);
|
|
490
507
|
} else {
|
|
491
|
-
console.log(
|
|
508
|
+
console.log(
|
|
509
|
+
`Using approximate candidate selection to reduce comparisons...`
|
|
510
|
+
);
|
|
492
511
|
}
|
|
493
512
|
let comparisonsProcessed = 0;
|
|
494
513
|
let comparisonsBudgetExhausted = false;
|
|
@@ -506,9 +525,13 @@ async function detectDuplicatePatterns(files, options) {
|
|
|
506
525
|
const remaining = totalComparisons - comparisonsProcessed;
|
|
507
526
|
const rate = comparisonsProcessed / parseFloat(elapsed);
|
|
508
527
|
const eta = remaining > 0 ? (remaining / rate).toFixed(0) : 0;
|
|
509
|
-
console.log(
|
|
528
|
+
console.log(
|
|
529
|
+
` ${progress}% (${comparisonsProcessed.toLocaleString()}/${totalComparisons.toLocaleString()} comparisons, ${elapsed}s elapsed, ~${eta}s remaining, ${duplicatesFound} duplicates)`
|
|
530
|
+
);
|
|
510
531
|
} else {
|
|
511
|
-
console.log(
|
|
532
|
+
console.log(
|
|
533
|
+
` Processed ${i.toLocaleString()}/${allBlocks.length} blocks (${elapsed}s elapsed, ${duplicatesFound} duplicates)`
|
|
534
|
+
);
|
|
512
535
|
}
|
|
513
536
|
await new Promise((resolve) => setImmediate(resolve));
|
|
514
537
|
}
|
|
@@ -542,8 +565,12 @@ async function detectDuplicatePatterns(files, options) {
|
|
|
542
565
|
if (approx && candidates) {
|
|
543
566
|
for (const { j } of candidates) {
|
|
544
567
|
if (!approx && maxComparisons !== Infinity && comparisonsProcessed >= maxComparisons) {
|
|
545
|
-
console.log(
|
|
546
|
-
|
|
568
|
+
console.log(
|
|
569
|
+
`\u26A0\uFE0F Comparison safety limit reached (${maxComparisons.toLocaleString()} comparisons in --no-approx mode).`
|
|
570
|
+
);
|
|
571
|
+
console.log(
|
|
572
|
+
` This prevents excessive runtime on large repos. Consider using approximate mode (default) or --min-lines to reduce blocks.`
|
|
573
|
+
);
|
|
547
574
|
break;
|
|
548
575
|
}
|
|
549
576
|
comparisonsProcessed++;
|
|
@@ -576,10 +603,16 @@ async function detectDuplicatePatterns(files, options) {
|
|
|
576
603
|
};
|
|
577
604
|
duplicates.push(duplicate);
|
|
578
605
|
if (streamResults) {
|
|
579
|
-
console.log(
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
606
|
+
console.log(
|
|
607
|
+
`
|
|
608
|
+
\u2705 Found: ${duplicate.patternType} ${Math.round(similarity * 100)}% similar`
|
|
609
|
+
);
|
|
610
|
+
console.log(
|
|
611
|
+
` ${duplicate.file1}:${duplicate.line1}-${duplicate.endLine1} \u21D4 ${duplicate.file2}:${duplicate.line2}-${duplicate.endLine2}`
|
|
612
|
+
);
|
|
613
|
+
console.log(
|
|
614
|
+
` Token cost: ${duplicate.tokenCost.toLocaleString()}`
|
|
615
|
+
);
|
|
583
616
|
}
|
|
584
617
|
}
|
|
585
618
|
}
|
|
@@ -617,17 +650,25 @@ async function detectDuplicatePatterns(files, options) {
|
|
|
617
650
|
};
|
|
618
651
|
duplicates.push(duplicate);
|
|
619
652
|
if (streamResults) {
|
|
620
|
-
console.log(
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
653
|
+
console.log(
|
|
654
|
+
`
|
|
655
|
+
\u2705 Found: ${duplicate.patternType} ${Math.round(similarity * 100)}% similar`
|
|
656
|
+
);
|
|
657
|
+
console.log(
|
|
658
|
+
` ${duplicate.file1}:${duplicate.line1}-${duplicate.endLine1} \u21D4 ${duplicate.file2}:${duplicate.line2}-${duplicate.endLine2}`
|
|
659
|
+
);
|
|
660
|
+
console.log(
|
|
661
|
+
` Token cost: ${duplicate.tokenCost.toLocaleString()}`
|
|
662
|
+
);
|
|
624
663
|
}
|
|
625
664
|
}
|
|
626
665
|
}
|
|
627
666
|
}
|
|
628
667
|
}
|
|
629
668
|
if (comparisonsBudgetExhausted) {
|
|
630
|
-
console.log(
|
|
669
|
+
console.log(
|
|
670
|
+
`\u26A0\uFE0F Comparison budget exhausted (${maxComparisons.toLocaleString()} comparisons). Use --max-comparisons to increase.`
|
|
671
|
+
);
|
|
631
672
|
}
|
|
632
673
|
return duplicates.sort(
|
|
633
674
|
(a, b) => b.similarity - a.similarity || b.tokenCost - a.tokenCost
|
|
@@ -653,7 +694,10 @@ function groupDuplicatesByFilePair(duplicates) {
|
|
|
653
694
|
const result = [];
|
|
654
695
|
for (const [filePair, groupDups] of groups.entries()) {
|
|
655
696
|
const deduplicated = deduplicateOverlappingRanges(groupDups);
|
|
656
|
-
const totalTokenCost = deduplicated.reduce(
|
|
697
|
+
const totalTokenCost = deduplicated.reduce(
|
|
698
|
+
(sum, d) => sum + d.tokenCost,
|
|
699
|
+
0
|
|
700
|
+
);
|
|
657
701
|
const averageSimilarity = deduplicated.reduce((sum, d) => sum + d.similarity, 0) / deduplicated.length;
|
|
658
702
|
const maxSimilarity = Math.max(...deduplicated.map((d) => d.similarity));
|
|
659
703
|
const severity = getHighestSeverity(deduplicated.map((d) => d.severity));
|
|
@@ -759,7 +803,9 @@ function identifyCluster(dup) {
|
|
|
759
803
|
if ((file1.includes("/components/") || file1.startsWith("components/")) && (file2.includes("/components/") || file2.startsWith("components/")) && dup.patternType === "component") {
|
|
760
804
|
const component1 = extractComponentName(dup.file1);
|
|
761
805
|
const component2 = extractComponentName(dup.file2);
|
|
762
|
-
console.log(
|
|
806
|
+
console.log(
|
|
807
|
+
`Component check: ${dup.file1} -> ${component1}, ${dup.file2} -> ${component2}`
|
|
808
|
+
);
|
|
763
809
|
if (component1 && component2 && areSimilarComponents(component1, component2)) {
|
|
764
810
|
const category = getComponentCategory(component1);
|
|
765
811
|
console.log(`Creating cluster: component-${category}`);
|
|
@@ -858,7 +904,7 @@ function getClusterInfo(clusterId, patternType, fileCount) {
|
|
|
858
904
|
suggestion: "Extract common middleware, error handling, and response formatting",
|
|
859
905
|
reason: "API handler duplication leads to inconsistent error handling and response formats"
|
|
860
906
|
},
|
|
861
|
-
|
|
907
|
+
validators: {
|
|
862
908
|
name: `Validator Patterns (${fileCount} files)`,
|
|
863
909
|
suggestion: "Consolidate into shared schema validators (Zod/Yup) with reusable rules",
|
|
864
910
|
reason: "Validator duplication causes inconsistent validation and harder maintenance"
|
|
@@ -939,10 +985,19 @@ async function getSmartDefaults(directory, userOptions) {
|
|
|
939
985
|
const { scanFiles: scanFiles2 } = await import("@aiready/core");
|
|
940
986
|
const files = await scanFiles2(scanOptions);
|
|
941
987
|
const estimatedBlocks = files.length * 3;
|
|
942
|
-
const maxCandidatesPerBlock = Math.max(
|
|
988
|
+
const maxCandidatesPerBlock = Math.max(
|
|
989
|
+
3,
|
|
990
|
+
Math.min(10, Math.floor(3e4 / estimatedBlocks))
|
|
991
|
+
);
|
|
943
992
|
const minSimilarity = Math.min(0.75, 0.5 + estimatedBlocks / 1e4 * 0.25);
|
|
944
|
-
const minLines = Math.max(
|
|
945
|
-
|
|
993
|
+
const minLines = Math.max(
|
|
994
|
+
6,
|
|
995
|
+
Math.min(12, 6 + Math.floor(estimatedBlocks / 2e3))
|
|
996
|
+
);
|
|
997
|
+
const minSharedTokens = Math.max(
|
|
998
|
+
10,
|
|
999
|
+
Math.min(20, 10 + Math.floor(estimatedBlocks / 2e3))
|
|
1000
|
+
);
|
|
946
1001
|
const batchSize = estimatedBlocks > 1e3 ? 200 : 100;
|
|
947
1002
|
const severity = estimatedBlocks > 5e3 ? "high" : "all";
|
|
948
1003
|
let defaults = {
|
|
@@ -1043,7 +1098,9 @@ async function analyzePatterns(options) {
|
|
|
1043
1098
|
medium: ["critical", "major", "minor"]
|
|
1044
1099
|
};
|
|
1045
1100
|
const allowedSeverities = severityMap[severity] || ["critical", "major", "minor"];
|
|
1046
|
-
filteredIssues = issues.filter(
|
|
1101
|
+
filteredIssues = issues.filter(
|
|
1102
|
+
(issue) => allowedSeverities.includes(issue.severity)
|
|
1103
|
+
);
|
|
1047
1104
|
}
|
|
1048
1105
|
const totalTokenCost = fileDuplicates.reduce(
|
|
1049
1106
|
(sum, dup) => sum + dup.tokenCost,
|
|
@@ -1065,7 +1122,11 @@ async function analyzePatterns(options) {
|
|
|
1065
1122
|
}
|
|
1066
1123
|
if (createClusters) {
|
|
1067
1124
|
const allClusters = createRefactorClusters(duplicates);
|
|
1068
|
-
clusters = filterClustersByImpact(
|
|
1125
|
+
clusters = filterClustersByImpact(
|
|
1126
|
+
allClusters,
|
|
1127
|
+
minClusterTokenCost,
|
|
1128
|
+
minClusterFiles
|
|
1129
|
+
);
|
|
1069
1130
|
}
|
|
1070
1131
|
return { results, duplicates, files, groups, clusters };
|
|
1071
1132
|
}
|
|
@@ -1131,7 +1192,52 @@ var import_fs = require("fs");
|
|
|
1131
1192
|
var import_path = require("path");
|
|
1132
1193
|
var import_core5 = require("@aiready/core");
|
|
1133
1194
|
var program = new import_commander.Command();
|
|
1134
|
-
program.name("aiready-patterns").description("Detect duplicate patterns in your codebase").version("0.1.0").addHelpText(
|
|
1195
|
+
program.name("aiready-patterns").description("Detect duplicate patterns in your codebase").version("0.1.0").addHelpText(
|
|
1196
|
+
"after",
|
|
1197
|
+
"\nCONFIGURATION:\n Supports config files: aiready.json, aiready.config.json, .aiready.json, .aireadyrc.json, aiready.config.js, .aireadyrc.js\n CLI options override config file settings\n\nPARAMETER TUNING:\n If you get too few results: decrease --similarity, --min-lines, or --min-shared-tokens\n If analysis is too slow: increase --min-lines, --min-shared-tokens, or decrease --max-candidates\n If you get too many false positives: increase --similarity or --min-lines\n\nEXAMPLES:\n aiready-patterns . # Basic analysis with smart defaults\n aiready-patterns . --similarity 0.3 --min-lines 3 # More sensitive detection\n aiready-patterns . --max-candidates 50 --no-approx # Slower but more thorough\n aiready-patterns . --output json > report.json # JSON export"
|
|
1198
|
+
).argument("<directory>", "Directory to analyze").option(
|
|
1199
|
+
"-s, --similarity <number>",
|
|
1200
|
+
"Minimum similarity score (0-1). Lower = more results, higher = fewer but more accurate. Default: 0.4"
|
|
1201
|
+
).option(
|
|
1202
|
+
"-l, --min-lines <number>",
|
|
1203
|
+
"Minimum lines to consider. Lower = more results, higher = faster analysis. Default: 5"
|
|
1204
|
+
).option(
|
|
1205
|
+
"--batch-size <number>",
|
|
1206
|
+
"Batch size for comparisons. Higher = faster but more memory. Default: 100"
|
|
1207
|
+
).option(
|
|
1208
|
+
"--no-approx",
|
|
1209
|
+
"Disable approximate candidate selection. Slower but more thorough on small repos"
|
|
1210
|
+
).option(
|
|
1211
|
+
"--min-shared-tokens <number>",
|
|
1212
|
+
"Minimum shared tokens to consider a candidate. Higher = faster, fewer results. Default: 8"
|
|
1213
|
+
).option(
|
|
1214
|
+
"--max-candidates <number>",
|
|
1215
|
+
"Maximum candidates per block. Higher = more thorough but slower. Default: 100"
|
|
1216
|
+
).option(
|
|
1217
|
+
"--no-stream-results",
|
|
1218
|
+
"Disable incremental output (default: enabled)"
|
|
1219
|
+
).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option(
|
|
1220
|
+
"--min-severity <level>",
|
|
1221
|
+
"Minimum severity to show: critical|major|minor|info. Default: minor"
|
|
1222
|
+
).option(
|
|
1223
|
+
"--exclude-test-fixtures",
|
|
1224
|
+
"Exclude test fixture duplication (beforeAll/afterAll)"
|
|
1225
|
+
).option("--exclude-templates", "Exclude template file duplication").option(
|
|
1226
|
+
"--include-tests",
|
|
1227
|
+
"Include test files in analysis (excluded by default)"
|
|
1228
|
+
).option(
|
|
1229
|
+
"--max-results <number>",
|
|
1230
|
+
"Maximum number of results to show in console output. Default: 10"
|
|
1231
|
+
).option("--no-group-by-file-pair", "Disable grouping duplicates by file pair").option("--no-create-clusters", "Disable creating refactor clusters").option(
|
|
1232
|
+
"--min-cluster-tokens <number>",
|
|
1233
|
+
"Minimum token cost for cluster reporting. Default: 1000"
|
|
1234
|
+
).option(
|
|
1235
|
+
"--min-cluster-files <number>",
|
|
1236
|
+
"Minimum files for cluster reporting. Default: 3"
|
|
1237
|
+
).option(
|
|
1238
|
+
"--show-raw-duplicates",
|
|
1239
|
+
"Show raw duplicates instead of grouped view"
|
|
1240
|
+
).option(
|
|
1135
1241
|
"-o, --output <format>",
|
|
1136
1242
|
"Output format: console, json, html",
|
|
1137
1243
|
"console"
|
|
@@ -1196,16 +1302,29 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1196
1302
|
(pattern) => !testPatterns.includes(pattern)
|
|
1197
1303
|
);
|
|
1198
1304
|
}
|
|
1199
|
-
const {
|
|
1305
|
+
const {
|
|
1306
|
+
results,
|
|
1307
|
+
duplicates: rawDuplicates,
|
|
1308
|
+
files,
|
|
1309
|
+
groups,
|
|
1310
|
+
clusters
|
|
1311
|
+
} = await analyzePatterns(finalOptions);
|
|
1200
1312
|
let filteredDuplicates = rawDuplicates;
|
|
1201
1313
|
if (finalOptions.minSeverity) {
|
|
1202
|
-
filteredDuplicates = filterBySeverity(
|
|
1314
|
+
filteredDuplicates = filterBySeverity(
|
|
1315
|
+
filteredDuplicates,
|
|
1316
|
+
finalOptions.minSeverity
|
|
1317
|
+
);
|
|
1203
1318
|
}
|
|
1204
1319
|
if (finalOptions.excludeTestFixtures) {
|
|
1205
|
-
filteredDuplicates = filteredDuplicates.filter(
|
|
1320
|
+
filteredDuplicates = filteredDuplicates.filter(
|
|
1321
|
+
(d) => d.matchedRule !== "test-fixtures"
|
|
1322
|
+
);
|
|
1206
1323
|
}
|
|
1207
1324
|
if (finalOptions.excludeTemplates) {
|
|
1208
|
-
filteredDuplicates = filteredDuplicates.filter(
|
|
1325
|
+
filteredDuplicates = filteredDuplicates.filter(
|
|
1326
|
+
(d) => d.matchedRule !== "templates"
|
|
1327
|
+
);
|
|
1209
1328
|
}
|
|
1210
1329
|
const elapsedTime = ((Date.now() - startTime) / 1e3).toFixed(2);
|
|
1211
1330
|
const summary = generateSummary(results);
|
|
@@ -1259,7 +1378,9 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1259
1378
|
import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(results.length)}`)
|
|
1260
1379
|
);
|
|
1261
1380
|
console.log(
|
|
1262
|
-
import_chalk.default.yellow(
|
|
1381
|
+
import_chalk.default.yellow(
|
|
1382
|
+
`\u26A0 AI confusion patterns detected: ${import_chalk.default.bold(totalIssues)}`
|
|
1383
|
+
)
|
|
1263
1384
|
);
|
|
1264
1385
|
console.log(
|
|
1265
1386
|
import_chalk.default.red(
|
|
@@ -1276,12 +1397,16 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1276
1397
|
console.log(import_chalk.default.cyan(divider) + "\n");
|
|
1277
1398
|
sortedTypes.forEach(([type, count]) => {
|
|
1278
1399
|
const icon = getPatternIcon(type);
|
|
1279
|
-
console.log(
|
|
1400
|
+
console.log(
|
|
1401
|
+
`${icon} ${import_chalk.default.white(type.padEnd(15))} ${import_chalk.default.bold(count)}`
|
|
1402
|
+
);
|
|
1280
1403
|
});
|
|
1281
1404
|
}
|
|
1282
1405
|
if (!finalOptions.showRawDuplicates && groups && groups.length > 0) {
|
|
1283
1406
|
console.log(import_chalk.default.cyan("\n" + divider));
|
|
1284
|
-
console.log(
|
|
1407
|
+
console.log(
|
|
1408
|
+
import_chalk.default.bold.white(` \u{1F4E6} DUPLICATE GROUPS (${groups.length} file pairs)`)
|
|
1409
|
+
);
|
|
1285
1410
|
console.log(import_chalk.default.cyan(divider) + "\n");
|
|
1286
1411
|
const severityOrder = {
|
|
1287
1412
|
critical: 4,
|
|
@@ -1299,39 +1424,63 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1299
1424
|
const [file1, file2] = group.filePair.split("::");
|
|
1300
1425
|
const file1Name = file1.split("/").pop() || file1;
|
|
1301
1426
|
const file2Name = file2.split("/").pop() || file2;
|
|
1302
|
-
console.log(
|
|
1303
|
-
|
|
1427
|
+
console.log(
|
|
1428
|
+
`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(file1Name)} \u2194 ${import_chalk.default.bold(file2Name)}`
|
|
1429
|
+
);
|
|
1430
|
+
console.log(
|
|
1431
|
+
` Occurrences: ${import_chalk.default.bold(group.occurrences)} | Total tokens: ${import_chalk.default.bold(group.totalTokenCost.toLocaleString())} | Avg similarity: ${import_chalk.default.bold(Math.round(group.averageSimilarity * 100) + "%")}`
|
|
1432
|
+
);
|
|
1304
1433
|
const displayRanges = group.lineRanges.slice(0, 3);
|
|
1305
1434
|
displayRanges.forEach((range) => {
|
|
1306
|
-
console.log(
|
|
1435
|
+
console.log(
|
|
1436
|
+
` ${import_chalk.default.gray(file1)}:${import_chalk.default.cyan(`${range.file1.start}-${range.file1.end}`)} \u2194 ${import_chalk.default.gray(file2)}:${import_chalk.default.cyan(`${range.file2.start}-${range.file2.end}`)}`
|
|
1437
|
+
);
|
|
1307
1438
|
});
|
|
1308
1439
|
if (group.lineRanges.length > 3) {
|
|
1309
|
-
console.log(
|
|
1440
|
+
console.log(
|
|
1441
|
+
` ${import_chalk.default.gray(`... and ${group.lineRanges.length - 3} more ranges`)}`
|
|
1442
|
+
);
|
|
1310
1443
|
}
|
|
1311
1444
|
console.log();
|
|
1312
1445
|
});
|
|
1313
1446
|
if (groups.length > topGroups.length) {
|
|
1314
|
-
console.log(
|
|
1447
|
+
console.log(
|
|
1448
|
+
import_chalk.default.gray(
|
|
1449
|
+
` ... and ${groups.length - topGroups.length} more file pairs`
|
|
1450
|
+
)
|
|
1451
|
+
);
|
|
1315
1452
|
}
|
|
1316
1453
|
}
|
|
1317
1454
|
if (!finalOptions.showRawDuplicates && clusters && clusters.length > 0) {
|
|
1318
1455
|
console.log(import_chalk.default.cyan("\n" + divider));
|
|
1319
|
-
console.log(
|
|
1456
|
+
console.log(
|
|
1457
|
+
import_chalk.default.bold.white(` \u{1F3AF} REFACTOR CLUSTERS (${clusters.length} patterns)`)
|
|
1458
|
+
);
|
|
1320
1459
|
console.log(import_chalk.default.cyan(divider) + "\n");
|
|
1321
1460
|
clusters.sort((a, b) => b.totalTokenCost - a.totalTokenCost).forEach((cluster, idx) => {
|
|
1322
1461
|
const severityBadge = getSeverityBadge(cluster.severity);
|
|
1323
|
-
console.log(
|
|
1324
|
-
|
|
1462
|
+
console.log(
|
|
1463
|
+
`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(cluster.name)}`
|
|
1464
|
+
);
|
|
1465
|
+
console.log(
|
|
1466
|
+
` Total tokens: ${import_chalk.default.bold(cluster.totalTokenCost.toLocaleString())} | Avg similarity: ${import_chalk.default.bold(Math.round(cluster.averageSimilarity * 100) + "%")} | Duplicates: ${import_chalk.default.bold(cluster.duplicateCount)}`
|
|
1467
|
+
);
|
|
1325
1468
|
const displayFiles = cluster.files.slice(0, 5);
|
|
1326
|
-
console.log(
|
|
1469
|
+
console.log(
|
|
1470
|
+
` Files (${cluster.files.length}): ${displayFiles.map((f) => import_chalk.default.gray(f.split("/").pop() || f)).join(", ")}`
|
|
1471
|
+
);
|
|
1327
1472
|
if (cluster.files.length > 5) {
|
|
1328
|
-
console.log(
|
|
1473
|
+
console.log(
|
|
1474
|
+
` ${import_chalk.default.gray(`... and ${cluster.files.length - 5} more files`)}`
|
|
1475
|
+
);
|
|
1329
1476
|
}
|
|
1330
1477
|
if (cluster.reason) {
|
|
1331
1478
|
console.log(` ${import_chalk.default.italic.gray(cluster.reason)}`);
|
|
1332
1479
|
}
|
|
1333
1480
|
if (cluster.suggestion) {
|
|
1334
|
-
console.log(
|
|
1481
|
+
console.log(
|
|
1482
|
+
` ${import_chalk.default.cyan("\u2192")} ${import_chalk.default.italic(cluster.suggestion)}`
|
|
1483
|
+
);
|
|
1335
1484
|
}
|
|
1336
1485
|
console.log();
|
|
1337
1486
|
});
|
|
@@ -1355,10 +1504,18 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1355
1504
|
const severityBadge = getSeverityBadge(dup.severity);
|
|
1356
1505
|
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
1357
1506
|
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
1358
|
-
console.log(
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
console.log(
|
|
1507
|
+
console.log(
|
|
1508
|
+
`${severityBadge} ${import_chalk.default.bold(file1Name)} \u2194 ${import_chalk.default.bold(file2Name)}`
|
|
1509
|
+
);
|
|
1510
|
+
console.log(
|
|
1511
|
+
` Similarity: ${import_chalk.default.bold(Math.round(dup.similarity * 100) + "%")} | Pattern: ${dup.patternType} | Tokens: ${import_chalk.default.bold(dup.tokenCost.toLocaleString())}`
|
|
1512
|
+
);
|
|
1513
|
+
console.log(
|
|
1514
|
+
` ${import_chalk.default.gray(dup.file1)}:${import_chalk.default.cyan(dup.line1 + "-" + dup.endLine1)}`
|
|
1515
|
+
);
|
|
1516
|
+
console.log(
|
|
1517
|
+
` ${import_chalk.default.gray(dup.file2)}:${import_chalk.default.cyan(dup.line2 + "-" + dup.endLine2)}`
|
|
1518
|
+
);
|
|
1362
1519
|
if (dup.reason) {
|
|
1363
1520
|
console.log(` ${import_chalk.default.italic.gray(dup.reason)}`);
|
|
1364
1521
|
}
|
|
@@ -1368,7 +1525,11 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1368
1525
|
console.log();
|
|
1369
1526
|
});
|
|
1370
1527
|
if (filteredDuplicates.length > topDuplicates.length) {
|
|
1371
|
-
console.log(
|
|
1528
|
+
console.log(
|
|
1529
|
+
import_chalk.default.gray(
|
|
1530
|
+
` ... and ${filteredDuplicates.length - topDuplicates.length} more duplicates`
|
|
1531
|
+
)
|
|
1532
|
+
);
|
|
1372
1533
|
}
|
|
1373
1534
|
}
|
|
1374
1535
|
const allIssues = results.flatMap(
|
|
@@ -1382,27 +1543,45 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1382
1543
|
console.log(import_chalk.default.bold.white(" CRITICAL ISSUES (>95% similar)"));
|
|
1383
1544
|
console.log(import_chalk.default.cyan(divider) + "\n");
|
|
1384
1545
|
criticalIssues.slice(0, 5).forEach((issue) => {
|
|
1385
|
-
console.log(
|
|
1546
|
+
console.log(
|
|
1547
|
+
import_chalk.default.red("\u25CF ") + import_chalk.default.white(`${issue.file}:${issue.location.line}`)
|
|
1548
|
+
);
|
|
1386
1549
|
console.log(` ${import_chalk.default.dim(issue.message)}`);
|
|
1387
|
-
console.log(
|
|
1388
|
-
`)
|
|
1550
|
+
console.log(
|
|
1551
|
+
` ${import_chalk.default.green("\u2192")} ${import_chalk.default.italic(issue.suggestion)}
|
|
1552
|
+
`
|
|
1553
|
+
);
|
|
1389
1554
|
});
|
|
1390
1555
|
}
|
|
1391
1556
|
if (totalIssues === 0) {
|
|
1392
1557
|
console.log(import_chalk.default.green("\n\u2728 Great! No duplicate patterns detected.\n"));
|
|
1393
|
-
console.log(
|
|
1394
|
-
|
|
1558
|
+
console.log(
|
|
1559
|
+
import_chalk.default.yellow(
|
|
1560
|
+
"\u{1F4A1} If you expected to find duplicates, try adjusting parameters:"
|
|
1561
|
+
)
|
|
1562
|
+
);
|
|
1563
|
+
console.log(
|
|
1564
|
+
import_chalk.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3")
|
|
1565
|
+
);
|
|
1395
1566
|
console.log(import_chalk.default.dim(" \u2022 Reduce minimum lines: --min-lines 3"));
|
|
1396
1567
|
console.log(import_chalk.default.dim(" \u2022 Include test files: --include-tests"));
|
|
1397
|
-
console.log(
|
|
1568
|
+
console.log(
|
|
1569
|
+
import_chalk.default.dim(" \u2022 Lower shared tokens threshold: --min-shared-tokens 5")
|
|
1570
|
+
);
|
|
1398
1571
|
console.log("");
|
|
1399
1572
|
}
|
|
1400
1573
|
if (totalIssues > 0 && totalIssues < 5) {
|
|
1401
|
-
console.log(
|
|
1402
|
-
|
|
1574
|
+
console.log(
|
|
1575
|
+
import_chalk.default.yellow("\n\u{1F4A1} Few results found. To find more duplicates, try:")
|
|
1576
|
+
);
|
|
1577
|
+
console.log(
|
|
1578
|
+
import_chalk.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3")
|
|
1579
|
+
);
|
|
1403
1580
|
console.log(import_chalk.default.dim(" \u2022 Reduce minimum lines: --min-lines 3"));
|
|
1404
1581
|
console.log(import_chalk.default.dim(" \u2022 Include test files: --include-tests"));
|
|
1405
|
-
console.log(
|
|
1582
|
+
console.log(
|
|
1583
|
+
import_chalk.default.dim(" \u2022 Lower shared tokens threshold: --min-shared-tokens 5")
|
|
1584
|
+
);
|
|
1406
1585
|
console.log("");
|
|
1407
1586
|
}
|
|
1408
1587
|
console.log(import_chalk.default.cyan(divider));
|
|
@@ -1420,7 +1599,9 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1420
1599
|
)
|
|
1421
1600
|
);
|
|
1422
1601
|
console.log(
|
|
1423
|
-
import_chalk.default.dim(
|
|
1602
|
+
import_chalk.default.dim(
|
|
1603
|
+
"\u{1F41B} Found a bug? Report it: https://github.com/caopengau/aiready-pattern-detect/issues\n"
|
|
1604
|
+
)
|
|
1424
1605
|
);
|
|
1425
1606
|
});
|
|
1426
1607
|
function getPatternIcon(type) {
|