@aiready/pattern-detect 0.11.30 → 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/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(/^(get|set|is|has|create|delete|update|fetch)_?/, "");
117
- const clean2 = name2.replace(/^(get|set|is|has|create|delete|update|fetch)_?/, "");
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(/^(get|set|create|delete|update)_/, "");
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(pythonFiles.map((f) => f.file));
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(`\u26A0\uFE0F Using --no-approx mode with ${allBlocks.length} blocks may be slow (O(B\xB2) complexity).`);
442
- console.log(` Consider using approximate mode (default) for better performance.`);
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(`Processing ${totalComparisons.toLocaleString()} comparisons in batches...`);
504
+ console.log(
505
+ `Processing ${totalComparisons.toLocaleString()} comparisons in batches...`
506
+ );
490
507
  } else {
491
- console.log(`Using approximate candidate selection to reduce comparisons...`);
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(` ${progress}% (${comparisonsProcessed.toLocaleString()}/${totalComparisons.toLocaleString()} comparisons, ${elapsed}s elapsed, ~${eta}s remaining, ${duplicatesFound} duplicates)`);
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(` Processed ${i.toLocaleString()}/${allBlocks.length} blocks (${elapsed}s elapsed, ${duplicatesFound} duplicates)`);
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(`\u26A0\uFE0F Comparison safety limit reached (${maxComparisons.toLocaleString()} comparisons in --no-approx mode).`);
546
- console.log(` This prevents excessive runtime on large repos. Consider using approximate mode (default) or --min-lines to reduce blocks.`);
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
- \u2705 Found: ${duplicate.patternType} ${Math.round(similarity * 100)}% similar`);
581
- console.log(` ${duplicate.file1}:${duplicate.line1}-${duplicate.endLine1} \u21D4 ${duplicate.file2}:${duplicate.line2}-${duplicate.endLine2}`);
582
- console.log(` Token cost: ${duplicate.tokenCost.toLocaleString()}`);
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
- \u2705 Found: ${duplicate.patternType} ${Math.round(similarity * 100)}% similar`);
622
- console.log(` ${duplicate.file1}:${duplicate.line1}-${duplicate.endLine1} \u21D4 ${duplicate.file2}:${duplicate.line2}-${duplicate.endLine2}`);
623
- console.log(` Token cost: ${duplicate.tokenCost.toLocaleString()}`);
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(`\u26A0\uFE0F Comparison budget exhausted (${maxComparisons.toLocaleString()} comparisons). Use --max-comparisons to increase.`);
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((sum, d) => sum + d.tokenCost, 0);
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(`Component check: ${dup.file1} -> ${component1}, ${dup.file2} -> ${component2}`);
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
- "validators": {
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(3, Math.min(10, Math.floor(3e4 / estimatedBlocks)));
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(6, Math.min(12, 6 + Math.floor(estimatedBlocks / 2e3)));
945
- const minSharedTokens = Math.max(10, Math.min(20, 10 + Math.floor(estimatedBlocks / 2e3)));
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((issue) => allowedSeverities.includes(issue.severity));
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(allClusters, minClusterTokenCost, minClusterFiles);
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("after", "\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").argument("<directory>", "Directory to analyze").option("-s, --similarity <number>", "Minimum similarity score (0-1). Lower = more results, higher = fewer but more accurate. Default: 0.4").option("-l, --min-lines <number>", "Minimum lines to consider. Lower = more results, higher = faster analysis. Default: 5").option("--batch-size <number>", "Batch size for comparisons. Higher = faster but more memory. Default: 100").option("--no-approx", "Disable approximate candidate selection. Slower but more thorough on small repos").option("--min-shared-tokens <number>", "Minimum shared tokens to consider a candidate. Higher = faster, fewer results. Default: 8").option("--max-candidates <number>", "Maximum candidates per block. Higher = more thorough but slower. Default: 100").option("--no-stream-results", "Disable incremental output (default: enabled)").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("--min-severity <level>", "Minimum severity to show: critical|major|minor|info. Default: minor").option("--exclude-test-fixtures", "Exclude test fixture duplication (beforeAll/afterAll)").option("--exclude-templates", "Exclude template file duplication").option("--include-tests", "Include test files in analysis (excluded by default)").option("--max-results <number>", "Maximum number of results to show in console output. Default: 10").option("--no-group-by-file-pair", "Disable grouping duplicates by file pair").option("--no-create-clusters", "Disable creating refactor clusters").option("--min-cluster-tokens <number>", "Minimum token cost for cluster reporting. Default: 1000").option("--min-cluster-files <number>", "Minimum files for cluster reporting. Default: 3").option("--show-raw-duplicates", "Show raw duplicates instead of grouped view").option(
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 { results, duplicates: rawDuplicates, files, groups, clusters } = await analyzePatterns(finalOptions);
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(filteredDuplicates, finalOptions.minSeverity);
1314
+ filteredDuplicates = filterBySeverity(
1315
+ filteredDuplicates,
1316
+ finalOptions.minSeverity
1317
+ );
1203
1318
  }
1204
1319
  if (finalOptions.excludeTestFixtures) {
1205
- filteredDuplicates = filteredDuplicates.filter((d) => d.matchedRule !== "test-fixtures");
1320
+ filteredDuplicates = filteredDuplicates.filter(
1321
+ (d) => d.matchedRule !== "test-fixtures"
1322
+ );
1206
1323
  }
1207
1324
  if (finalOptions.excludeTemplates) {
1208
- filteredDuplicates = filteredDuplicates.filter((d) => d.matchedRule !== "templates");
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(`\u26A0 AI confusion patterns detected: ${import_chalk.default.bold(totalIssues)}`)
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(`${icon} ${import_chalk.default.white(type.padEnd(15))} ${import_chalk.default.bold(count)}`);
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(import_chalk.default.bold.white(` \u{1F4E6} DUPLICATE GROUPS (${groups.length} file pairs)`));
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(`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(file1Name)} \u2194 ${import_chalk.default.bold(file2Name)}`);
1303
- console.log(` 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) + "%")}`);
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(` ${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}`)}`);
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(` ${import_chalk.default.gray(`... and ${group.lineRanges.length - 3} more ranges`)}`);
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(import_chalk.default.gray(` ... and ${groups.length - topGroups.length} more file pairs`));
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(import_chalk.default.bold.white(` \u{1F3AF} REFACTOR CLUSTERS (${clusters.length} patterns)`));
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(`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(cluster.name)}`);
1324
- console.log(` 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)}`);
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(` Files (${cluster.files.length}): ${displayFiles.map((f) => import_chalk.default.gray(f.split("/").pop() || f)).join(", ")}`);
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(` ${import_chalk.default.gray(`... and ${cluster.files.length - 5} more files`)}`);
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(` ${import_chalk.default.cyan("\u2192")} ${import_chalk.default.italic(cluster.suggestion)}`);
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(`${severityBadge} ${import_chalk.default.bold(file1Name)} \u2194 ${import_chalk.default.bold(file2Name)}`);
1359
- console.log(` Similarity: ${import_chalk.default.bold(Math.round(dup.similarity * 100) + "%")} | Pattern: ${dup.patternType} | Tokens: ${import_chalk.default.bold(dup.tokenCost.toLocaleString())}`);
1360
- console.log(` ${import_chalk.default.gray(dup.file1)}:${import_chalk.default.cyan(dup.line1 + "-" + dup.endLine1)}`);
1361
- console.log(` ${import_chalk.default.gray(dup.file2)}:${import_chalk.default.cyan(dup.line2 + "-" + dup.endLine2)}`);
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(import_chalk.default.gray(` ... and ${filteredDuplicates.length - topDuplicates.length} more duplicates`));
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(import_chalk.default.red("\u25CF ") + import_chalk.default.white(`${issue.file}:${issue.location.line}`));
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(` ${import_chalk.default.green("\u2192")} ${import_chalk.default.italic(issue.suggestion)}
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(import_chalk.default.yellow("\u{1F4A1} If you expected to find duplicates, try adjusting parameters:"));
1394
- console.log(import_chalk.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3"));
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(import_chalk.default.dim(" \u2022 Lower shared tokens threshold: --min-shared-tokens 5"));
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(import_chalk.default.yellow("\n\u{1F4A1} Few results found. To find more duplicates, try:"));
1402
- console.log(import_chalk.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3"));
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(import_chalk.default.dim(" \u2022 Lower shared tokens threshold: --min-shared-tokens 5"));
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("\u{1F41B} Found a bug? Report it: https://github.com/caopengau/aiready-pattern-detect/issues\n")
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) {