@aiready/pattern-detect 0.16.5 → 0.16.7

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
@@ -312,14 +312,33 @@ function calculateSimilarity(a, b) {
312
312
  const union = /* @__PURE__ */ new Set([...setA, ...setB]);
313
313
  return intersection.size / union.size;
314
314
  }
315
+ function calculateConfidence(similarity, tokens, lines) {
316
+ let confidence = similarity;
317
+ if (lines > 20) confidence += 0.05;
318
+ if (tokens > 200) confidence += 0.05;
319
+ if (lines < 5) confidence -= 0.1;
320
+ return Math.max(0, Math.min(1, confidence));
321
+ }
315
322
  async function detectDuplicatePatterns(fileContents, options) {
316
- const { minSimilarity, minLines, streamResults, onProgress } = options;
323
+ const {
324
+ minSimilarity,
325
+ minLines,
326
+ streamResults,
327
+ onProgress,
328
+ excludePatterns = [],
329
+ confidenceThreshold = 0,
330
+ ignoreWhitelist = []
331
+ } = options;
317
332
  const allBlocks = [];
333
+ const excludeRegexes = excludePatterns.map((p) => new RegExp(p, "i"));
318
334
  for (const { file, content } of fileContents) {
319
335
  const blocks = extractBlocks(file, content);
320
- allBlocks.push(
321
- ...blocks.filter((b) => b.endLine - b.startLine + 1 >= minLines)
322
- );
336
+ for (const b of blocks) {
337
+ if (b.endLine - b.startLine + 1 < minLines) continue;
338
+ const isExcluded = excludeRegexes.some((regex) => regex.test(b.code));
339
+ if (isExcluded) continue;
340
+ allBlocks.push(b);
341
+ }
323
342
  }
324
343
  const duplicates = [];
325
344
  const totalBlocks = allBlocks.length;
@@ -350,10 +369,20 @@ async function detectDuplicatePatterns(fileContents, options) {
350
369
  comparisons++;
351
370
  const b2 = allBlocks[j];
352
371
  if (b1.file === b2.file) continue;
372
+ const isWhitelisted = ignoreWhitelist.some((pattern) => {
373
+ return b1.file.includes(pattern) && b2.file.includes(pattern) || pattern === `${b1.file}::${b2.file}` || pattern === `${b2.file}::${b1.file}`;
374
+ });
375
+ if (isWhitelisted) continue;
353
376
  const isPython2 = b2.file.toLowerCase().endsWith(".py");
354
377
  const norm2 = normalizeCode(b2.code, isPython2);
355
378
  const sim = calculateSimilarity(norm1, norm2);
356
379
  if (sim >= minSimilarity) {
380
+ const confidence = calculateConfidence(
381
+ sim,
382
+ b1.tokens,
383
+ b1.endLine - b1.startLine + 1
384
+ );
385
+ if (confidence < confidenceThreshold) continue;
357
386
  const { severity, reason, suggestion, matchedRule } = calculateSeverity(
358
387
  b1.file,
359
388
  b2.file,
@@ -371,6 +400,7 @@ async function detectDuplicatePatterns(fileContents, options) {
371
400
  code1: b1.code,
372
401
  code2: b2.code,
373
402
  similarity: sim,
403
+ confidence,
374
404
  patternType: b1.patternType,
375
405
  tokenCost: b1.tokens + b2.tokens,
376
406
  severity,
@@ -381,7 +411,7 @@ async function detectDuplicatePatterns(fileContents, options) {
381
411
  duplicates.push(dup);
382
412
  if (streamResults)
383
413
  console.log(
384
- `[DUPLICATE] ${dup.file1}:${dup.line1} <-> ${dup.file2}:${dup.line2} (${Math.round(sim * 100)}%)`
414
+ `[DUPLICATE] ${dup.file1}:${dup.line1} <-> ${dup.file2}:${dup.line2} (${Math.round(sim * 100)}%, conf: ${Math.round(confidence * 100)}%)`
385
415
  );
386
416
  }
387
417
  }
@@ -595,6 +625,17 @@ function logConfiguration(config, estimatedBlocks) {
595
625
  console.log(` Min shared tokens: ${config.minSharedTokens}`);
596
626
  console.log(` Severity filter: ${config.severity}`);
597
627
  console.log(` Include tests: ${config.includeTests}`);
628
+ if (config.excludePatterns && config.excludePatterns.length > 0) {
629
+ console.log(` Exclude patterns: ${config.excludePatterns.length} active`);
630
+ }
631
+ if (config.confidenceThreshold && config.confidenceThreshold > 0) {
632
+ console.log(` Confidence threshold: ${config.confidenceThreshold}`);
633
+ }
634
+ if (config.ignoreWhitelist && config.ignoreWhitelist.length > 0) {
635
+ console.log(
636
+ ` Ignore whitelist: ${config.ignoreWhitelist.length} entries`
637
+ );
638
+ }
598
639
  console.log("");
599
640
  }
600
641
  async function analyzePatterns(options) {
@@ -613,6 +654,9 @@ async function analyzePatterns(options) {
613
654
  createClusters = true,
614
655
  minClusterTokenCost = 1e3,
615
656
  minClusterFiles = 3,
657
+ excludePatterns = [],
658
+ confidenceThreshold = 0,
659
+ ignoreWhitelist = [],
616
660
  ...scanOptions
617
661
  } = finalOptions;
618
662
  const files = await (0, import_core4.scanFiles)(scanOptions);
@@ -639,6 +683,9 @@ async function analyzePatterns(options) {
639
683
  minSharedTokens,
640
684
  maxCandidatesPerBlock,
641
685
  streamResults,
686
+ excludePatterns,
687
+ confidenceThreshold,
688
+ ignoreWhitelist,
642
689
  onProgress: options.onProgress
643
690
  });
644
691
  for (const file of files) {
@@ -740,6 +787,8 @@ function generateSummary(results) {
740
787
  }
741
788
  ],
742
789
  similarity: similarityMatch ? parseInt(similarityMatch[1]) / 100 : 0,
790
+ confidence: similarityMatch ? parseInt(similarityMatch[1]) / 100 : 0,
791
+ // Fallback for summary
743
792
  patternType: typeMatch?.[1] || "unknown",
744
793
  tokenCost: tokenMatch ? parseInt(tokenMatch[1]) : 0
745
794
  };
@@ -901,7 +950,7 @@ var PatternDetectProvider = {
901
950
  // src/index.ts
902
951
  import_core7.ToolRegistry.register(PatternDetectProvider);
903
952
 
904
- // src/cli.ts
953
+ // src/cli-action.ts
905
954
  var import_chalk = __toESM(require("chalk"));
906
955
  var import_fs = require("fs");
907
956
  var import_path2 = require("path");
@@ -1006,80 +1055,61 @@ function generateHTMLReport(results, summary) {
1006
1055
  </html>`;
1007
1056
  }
1008
1057
 
1009
- // src/cli.ts
1010
- var program = new import_commander.Command();
1011
- program.name("aiready-patterns").description("Detect duplicate patterns in your codebase").version("0.1.0").addHelpText(
1012
- "after",
1013
- "\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"
1014
- ).argument("<directory>", "Directory to analyze").option(
1015
- "-s, --similarity <number>",
1016
- "Minimum similarity score (0-1). Lower = more results, higher = fewer but more accurate. Default: 0.4"
1017
- ).option(
1018
- "-l, --min-lines <number>",
1019
- "Minimum lines to consider. Lower = more results, higher = faster analysis. Default: 5"
1020
- ).option(
1021
- "--batch-size <number>",
1022
- "Batch size for comparisons. Higher = faster but more memory. Default: 100"
1023
- ).option(
1024
- "--no-approx",
1025
- "Disable approximate candidate selection. Slower but more thorough on small repos"
1026
- ).option(
1027
- "--min-shared-tokens <number>",
1028
- "Minimum shared tokens to consider a candidate. Higher = faster, fewer results. Default: 8"
1029
- ).option(
1030
- "--max-candidates <number>",
1031
- "Maximum candidates per block. Higher = more thorough but slower. Default: 100"
1032
- ).option(
1033
- "--no-stream-results",
1034
- "Disable incremental output (default: enabled)"
1035
- ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option(
1036
- "--min-severity <level>",
1037
- "Minimum severity to show: critical|major|minor|info. Default: minor"
1038
- ).option(
1039
- "--exclude-test-fixtures",
1040
- "Exclude test fixture duplication (beforeAll/afterAll)"
1041
- ).option("--exclude-templates", "Exclude template file duplication").option(
1042
- "--include-tests",
1043
- "Include test files in analysis (excluded by default)"
1044
- ).option(
1045
- "--max-results <number>",
1046
- "Maximum number of results to show in console output. Default: 10"
1047
- ).option("--no-group-by-file-pair", "Disable grouping duplicates by file pair").option("--no-create-clusters", "Disable creating refactor clusters").option(
1048
- "--min-cluster-tokens <number>",
1049
- "Minimum token cost for cluster reporting. Default: 1000"
1050
- ).option(
1051
- "--min-cluster-files <number>",
1052
- "Minimum files for cluster reporting. Default: 3"
1053
- ).option(
1054
- "--show-raw-duplicates",
1055
- "Show raw duplicates instead of grouped view"
1056
- ).option(
1057
- "-o, --output <format>",
1058
- "Output format: console, json, html",
1059
- "console"
1060
- ).option("--output-file <path>", "Output file path (for json/html)").action(async (directory, options) => {
1058
+ // src/constants.ts
1059
+ var DEFAULT_MIN_SIMILARITY = 0.4;
1060
+ var DEFAULT_MIN_LINES = 5;
1061
+ var DEFAULT_BATCH_SIZE = 100;
1062
+ var DEFAULT_MIN_SHARED_TOKENS = 8;
1063
+ var DEFAULT_MAX_CANDIDATES_PER_BLOCK = 100;
1064
+ var DEFAULT_MAX_RESULTS = 10;
1065
+ var DEFAULT_MIN_CLUSTER_TOKEN_COST = 1e3;
1066
+ var DEFAULT_MIN_CLUSTER_FILES = 3;
1067
+ var COMMAND_NAME = "aiready-patterns";
1068
+ var COMMAND_VERSION = "0.1.0";
1069
+ var DEFAULT_OUTPUT_FORMAT = "console";
1070
+ var HELP_TEXT_AFTER = `
1071
+ CONFIGURATION:
1072
+ Supports config files: aiready.json, aiready.config.json, .aiready.json, .aireadyrc.json, aiready.config.js, .aireadyrc.js
1073
+ CLI options override config file settings
1074
+
1075
+ PARAMETER TUNING:
1076
+ If you get too few results: decrease --similarity, --min-lines, or --min-shared-tokens
1077
+ If analysis is too slow: increase --min-lines, --min-shared-tokens, or decrease --max-candidates
1078
+ If you get too many false positives: increase --similarity or --min-lines
1079
+
1080
+ EXAMPLES:
1081
+ aiready-patterns . # Basic analysis with smart defaults
1082
+ aiready-patterns . --similarity 0.3 --min-lines 3 # More sensitive detection
1083
+ aiready-patterns . --max-candidates 50 --no-approx # Slower but more thorough
1084
+ aiready-patterns . --output json > report.json # JSON export`;
1085
+
1086
+ // src/cli-action.ts
1087
+ async function patternActionHandler(directory, options) {
1061
1088
  console.log(import_chalk.default.blue("\u{1F50D} Analyzing patterns...\n"));
1062
1089
  const startTime = Date.now();
1063
1090
  const config = await (0, import_core9.loadConfig)(directory);
1064
1091
  const defaults = {
1065
- minSimilarity: 0.4,
1066
- minLines: 5,
1067
- batchSize: 100,
1092
+ minSimilarity: DEFAULT_MIN_SIMILARITY,
1093
+ minLines: DEFAULT_MIN_LINES,
1094
+ batchSize: DEFAULT_BATCH_SIZE,
1068
1095
  approx: true,
1069
- minSharedTokens: 8,
1070
- maxCandidatesPerBlock: 100,
1096
+ minSharedTokens: DEFAULT_MIN_SHARED_TOKENS,
1097
+ maxCandidatesPerBlock: DEFAULT_MAX_CANDIDATES_PER_BLOCK,
1071
1098
  streamResults: true,
1072
1099
  include: void 0,
1073
1100
  exclude: void 0,
1101
+ excludePatterns: void 0,
1102
+ confidenceThreshold: 0,
1103
+ ignoreWhitelist: void 0,
1074
1104
  minSeverity: import_core9.Severity.Minor,
1075
1105
  excludeTestFixtures: false,
1076
1106
  excludeTemplates: false,
1077
1107
  includeTests: false,
1078
- maxResults: 10,
1108
+ maxResults: DEFAULT_MAX_RESULTS,
1079
1109
  groupByFilePair: true,
1080
1110
  createClusters: true,
1081
- minClusterTokenCost: 1e3,
1082
- minClusterFiles: 3,
1111
+ minClusterTokenCost: DEFAULT_MIN_CLUSTER_TOKEN_COST,
1112
+ minClusterFiles: DEFAULT_MIN_CLUSTER_FILES,
1083
1113
  showRawDuplicates: false
1084
1114
  };
1085
1115
  const mergedConfig = (0, import_core9.mergeConfigWithDefaults)(config, defaults);
@@ -1095,15 +1125,18 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
1095
1125
  streamResults: options.streamResults !== false && mergedConfig.streamResults,
1096
1126
  include: options.include?.split(",") || mergedConfig.include,
1097
1127
  exclude: options.exclude?.split(",") || mergedConfig.exclude,
1128
+ excludePatterns: options.excludePatterns?.split(",") || mergedConfig.excludePatterns,
1129
+ confidenceThreshold: options.confidenceThreshold ? parseFloat(options.confidenceThreshold) : mergedConfig.confidenceThreshold,
1130
+ ignoreWhitelist: options.ignoreWhitelist?.split(",") || mergedConfig.ignoreWhitelist,
1098
1131
  minSeverity: options.minSeverity || mergedConfig.minSeverity,
1099
1132
  excludeTestFixtures: options.excludeTestFixtures || mergedConfig.excludeTestFixtures,
1100
1133
  excludeTemplates: options.excludeTemplates || mergedConfig.excludeTemplates,
1101
1134
  includeTests: options.includeTests || mergedConfig.includeTests,
1102
1135
  maxResults: options.maxResults ? parseInt(options.maxResults) : mergedConfig.maxResults,
1103
- groupByFilePair: options.groupBy_file_pair !== false && mergedConfig.groupByFilePair,
1104
- createClusters: options.create_clusters !== false && mergedConfig.createClusters,
1105
- minClusterTokenCost: options.min_cluster_tokens ? parseInt(options.min_cluster_tokens) : mergedConfig.minClusterTokenCost,
1106
- minClusterFiles: options.min_cluster_files ? parseInt(options.min_cluster_files) : mergedConfig.minClusterFiles,
1136
+ groupByFilePair: options.groupByFilePair !== false && mergedConfig.groupByFilePair,
1137
+ createClusters: options.createClusters !== false && mergedConfig.createClusters,
1138
+ minClusterTokenCost: options.minClusterTokens ? parseInt(options.minClusterTokens) : mergedConfig.minClusterTokenCost,
1139
+ minClusterFiles: options.minClusterFiles ? parseInt(options.minClusterFiles) : mergedConfig.minClusterFiles,
1107
1140
  showRawDuplicates: options.showRawDuplicates || mergedConfig.showRawDuplicates
1108
1141
  };
1109
1142
  if (finalOptions.includeTests && finalOptions.exclude) {
@@ -1189,9 +1222,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
1189
1222
  console.log(import_chalk.default.cyan(divider));
1190
1223
  console.log(import_chalk.default.bold.white(" PATTERN ANALYSIS SUMMARY"));
1191
1224
  console.log(import_chalk.default.cyan(divider) + "\n");
1192
- console.log(
1193
- import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(results.length)}`)
1194
- );
1225
+ console.log(import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(results.length)}`));
1195
1226
  console.log(
1196
1227
  import_chalk.default.yellow(
1197
1228
  `\u26A0 AI confusion patterns detected: ${import_chalk.default.bold(totalIssues)}`
@@ -1202,9 +1233,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
1202
1233
  `\u{1F4B0} Token cost (wasted): ${import_chalk.default.bold(summary.totalTokenCost.toLocaleString())}`
1203
1234
  )
1204
1235
  );
1205
- console.log(
1206
- import_chalk.default.gray(`\u23F1 Analysis time: ${import_chalk.default.bold(elapsedTime + "s")}`)
1207
- );
1236
+ console.log(import_chalk.default.gray(`\u23F1 Analysis time: ${import_chalk.default.bold(elapsedTime + "s")}`));
1208
1237
  const sortedTypes = Object.entries(summary.patternsByType).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
1209
1238
  if (sortedTypes.length > 0) {
1210
1239
  console.log(import_chalk.default.cyan("\n" + divider));
@@ -1270,9 +1299,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
1270
1299
  console.log(import_chalk.default.cyan(divider) + "\n");
1271
1300
  clusters.sort((a, b) => b.totalTokenCost - a.totalTokenCost).forEach((cluster, idx) => {
1272
1301
  const severityBadge = (0, import_core9.getSeverityBadge)(cluster.severity);
1273
- console.log(
1274
- `${idx + 1}. ${severityBadge} ${import_chalk.default.bold(cluster.name)}`
1275
- );
1302
+ console.log(`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(cluster.name)}`);
1276
1303
  console.log(
1277
1304
  ` 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)}`
1278
1305
  );
@@ -1354,10 +1381,8 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
1354
1381
  import_chalk.default.red("\u25CF ") + import_chalk.default.white(`${issue.file}:${issue.location.line}`)
1355
1382
  );
1356
1383
  console.log(` ${import_chalk.default.dim(issue.message)}`);
1357
- console.log(
1358
- ` ${import_chalk.default.green("\u2192")} ${import_chalk.default.italic(issue.suggestion)}
1359
- `
1360
- );
1384
+ console.log(` ${import_chalk.default.green("\u2192")} ${import_chalk.default.italic(issue.suggestion)}
1385
+ `);
1361
1386
  });
1362
1387
  }
1363
1388
  if (totalIssues === 0) {
@@ -1367,9 +1392,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
1367
1392
  "\u{1F4A1} If you expected to find duplicates, try adjusting parameters:"
1368
1393
  )
1369
1394
  );
1370
- console.log(
1371
- import_chalk.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3")
1372
- );
1395
+ console.log(import_chalk.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3"));
1373
1396
  console.log(import_chalk.default.dim(" \u2022 Reduce minimum lines: --min-lines 3"));
1374
1397
  console.log(import_chalk.default.dim(" \u2022 Include test files: --include-tests"));
1375
1398
  console.log(
@@ -1381,9 +1404,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
1381
1404
  console.log(
1382
1405
  import_chalk.default.yellow("\n\u{1F4A1} Few results found. To find more duplicates, try:")
1383
1406
  );
1384
- console.log(
1385
- import_chalk.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3")
1386
- );
1407
+ console.log(import_chalk.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3"));
1387
1408
  console.log(import_chalk.default.dim(" \u2022 Reduce minimum lines: --min-lines 3"));
1388
1409
  console.log(import_chalk.default.dim(" \u2022 Include test files: --include-tests"));
1389
1410
  console.log(
@@ -1410,5 +1431,64 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
1410
1431
  "\u{1F41B} Found a bug? Report it: https://github.com/caopengau/aiready-pattern-detect/issues\n"
1411
1432
  )
1412
1433
  );
1413
- });
1434
+ }
1435
+
1436
+ // src/cli.ts
1437
+ var program = new import_commander.Command();
1438
+ program.name(COMMAND_NAME).description("Detect duplicate patterns in your codebase").version(COMMAND_VERSION).addHelpText("after", HELP_TEXT_AFTER).argument("<directory>", "Directory to analyze").option(
1439
+ "-s, --similarity <number>",
1440
+ `Minimum similarity score (0-1). Default: ${DEFAULT_MIN_SIMILARITY}`
1441
+ ).option(
1442
+ "-l, --min-lines <number>",
1443
+ `Minimum lines to consider. Default: ${DEFAULT_MIN_LINES}`
1444
+ ).option(
1445
+ "--batch-size <number>",
1446
+ `Batch size for comparisons. Default: ${DEFAULT_BATCH_SIZE}`
1447
+ ).option(
1448
+ "--no-approx",
1449
+ "Disable approximate candidate selection. Slower but more thorough on small repos"
1450
+ ).option(
1451
+ "--min-shared-tokens <number>",
1452
+ `Minimum shared tokens to consider a candidate. Default: ${DEFAULT_MIN_SHARED_TOKENS}`
1453
+ ).option(
1454
+ "--max-candidates <number>",
1455
+ `Maximum candidates per block. Default: ${DEFAULT_MAX_CANDIDATES_PER_BLOCK}`
1456
+ ).option(
1457
+ "--no-stream-results",
1458
+ "Disable incremental output (default: enabled)"
1459
+ ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option(
1460
+ "--exclude-patterns <regexes>",
1461
+ "Regex patterns to exclude specific code content (comma-separated)"
1462
+ ).option(
1463
+ "--confidence-threshold <number>",
1464
+ "Minimum confidence score (0-1). Default: 0"
1465
+ ).option(
1466
+ "--ignore-whitelist <patterns>",
1467
+ "List of file pairs or patterns to ignore (comma-separated)"
1468
+ ).option(
1469
+ "--min-severity <level>",
1470
+ "Minimum severity to show: critical|major|minor|info. Default: minor"
1471
+ ).option(
1472
+ "--exclude-test-fixtures",
1473
+ "Exclude test fixture duplication (beforeAll/afterAll)"
1474
+ ).option("--exclude-templates", "Exclude template file duplication").option(
1475
+ "--include-tests",
1476
+ "Include test files in analysis (excluded by default)"
1477
+ ).option(
1478
+ "--max-results <number>",
1479
+ `Maximum number of results to show in console output. Default: ${DEFAULT_MAX_RESULTS}`
1480
+ ).option("--no-group-by-file-pair", "Disable grouping duplicates by file pair").option("--no-create-clusters", "Disable creating refactor clusters").option(
1481
+ "--min-cluster-tokens <number>",
1482
+ `Minimum token cost for cluster reporting. Default: ${DEFAULT_MIN_CLUSTER_TOKEN_COST}`
1483
+ ).option(
1484
+ "--min-cluster-files <number>",
1485
+ `Minimum files for cluster reporting. Default: ${DEFAULT_MIN_CLUSTER_FILES}`
1486
+ ).option(
1487
+ "--show-raw-duplicates",
1488
+ "Show raw duplicates instead of grouped view"
1489
+ ).option(
1490
+ "-o, --output <format>",
1491
+ "Output format: console, json, html",
1492
+ DEFAULT_OUTPUT_FORMAT
1493
+ ).option("--output-file <path>", "Output file path (for json/html)").action(patternActionHandler);
1414
1494
  program.parse();