@aiready/pattern-detect 0.14.17 → 0.14.21

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
@@ -27,8 +27,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var import_commander = require("commander");
28
28
 
29
29
  // src/index.ts
30
+ var import_core7 = require("@aiready/core");
31
+
32
+ // src/provider.ts
30
33
  var import_core6 = require("@aiready/core");
31
34
 
35
+ // src/analyzer.ts
36
+ var import_core4 = require("@aiready/core");
37
+
32
38
  // src/detector.ts
33
39
  var import_core2 = require("@aiready/core");
34
40
 
@@ -517,153 +523,7 @@ function filterClustersByImpact(clusters, minTokenCost = 1e3, minFiles = 3) {
517
523
  );
518
524
  }
519
525
 
520
- // src/scoring.ts
521
- var import_core4 = require("@aiready/core");
522
- function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
523
- const totalDuplicates = duplicates.length;
524
- const totalTokenCost = duplicates.reduce((sum, d) => sum + d.tokenCost, 0);
525
- const highImpactDuplicates = duplicates.filter(
526
- (d) => d.tokenCost > 1e3 || d.similarity > 0.7
527
- ).length;
528
- if (totalFilesAnalyzed === 0) {
529
- return {
530
- toolName: import_core4.ToolName.PatternDetect,
531
- score: 100,
532
- rawMetrics: {
533
- totalDuplicates: 0,
534
- totalTokenCost: 0,
535
- highImpactDuplicates: 0,
536
- totalFilesAnalyzed: 0
537
- },
538
- factors: [],
539
- recommendations: []
540
- };
541
- }
542
- const duplicatesPerFile = totalDuplicates / totalFilesAnalyzed * 100;
543
- const tokenWastePerFile = totalTokenCost / totalFilesAnalyzed;
544
- const duplicatesPenalty = Math.min(60, duplicatesPerFile * 0.6);
545
- const tokenPenalty = Math.min(40, tokenWastePerFile / 125);
546
- const highImpactPenalty = highImpactDuplicates > 0 ? Math.min(15, highImpactDuplicates * 2 - 5) : -5;
547
- const score = 100 - duplicatesPenalty - tokenPenalty - highImpactPenalty;
548
- const finalScore = Math.max(0, Math.min(100, Math.round(score)));
549
- const factors = [
550
- {
551
- name: "Duplication Density",
552
- impact: -Math.round(duplicatesPenalty),
553
- description: `${duplicatesPerFile.toFixed(1)} duplicates per 100 files`
554
- },
555
- {
556
- name: "Token Waste",
557
- impact: -Math.round(tokenPenalty),
558
- description: `${Math.round(tokenWastePerFile)} tokens wasted per file`
559
- }
560
- ];
561
- if (highImpactDuplicates > 0) {
562
- factors.push({
563
- name: "High-Impact Patterns",
564
- impact: -Math.round(highImpactPenalty),
565
- description: `${highImpactDuplicates} high-impact duplicates (>1000 tokens or >70% similar)`
566
- });
567
- } else {
568
- factors.push({
569
- name: "No High-Impact Patterns",
570
- impact: 5,
571
- description: "No severe duplicates detected"
572
- });
573
- }
574
- const recommendations = [];
575
- if (highImpactDuplicates > 0) {
576
- const estimatedImpact = Math.min(15, highImpactDuplicates * 3);
577
- recommendations.push({
578
- action: `Deduplicate ${highImpactDuplicates} high-impact pattern${highImpactDuplicates > 1 ? "s" : ""}`,
579
- estimatedImpact,
580
- priority: "high"
581
- });
582
- }
583
- if (totalDuplicates > 10 && duplicatesPerFile > 20) {
584
- const estimatedImpact = Math.min(10, Math.round(duplicatesPenalty * 0.3));
585
- recommendations.push({
586
- action: "Extract common patterns into shared utilities",
587
- estimatedImpact,
588
- priority: "medium"
589
- });
590
- }
591
- if (tokenWastePerFile > 2e3) {
592
- const estimatedImpact = Math.min(8, Math.round(tokenPenalty * 0.4));
593
- recommendations.push({
594
- action: "Consolidate duplicated logic to reduce AI context waste",
595
- estimatedImpact,
596
- priority: totalTokenCost > 1e4 ? "high" : "medium"
597
- });
598
- }
599
- const cfg = { ...import_core4.DEFAULT_COST_CONFIG, ...costConfig };
600
- const estimatedMonthlyCost = (0, import_core4.calculateMonthlyCost)(totalTokenCost, cfg);
601
- const issues = duplicates.map((d) => ({
602
- severity: d.severity === "critical" ? "critical" : d.severity === "major" ? "major" : "minor"
603
- }));
604
- const productivityImpact = (0, import_core4.calculateProductivityImpact)(issues);
605
- return {
606
- toolName: "pattern-detect",
607
- score: finalScore,
608
- rawMetrics: {
609
- totalDuplicates,
610
- totalTokenCost,
611
- highImpactDuplicates,
612
- totalFilesAnalyzed,
613
- duplicatesPerFile: Math.round(duplicatesPerFile * 10) / 10,
614
- tokenWastePerFile: Math.round(tokenWastePerFile),
615
- // Business value metrics
616
- estimatedMonthlyCost,
617
- estimatedDeveloperHours: productivityImpact.totalHours
618
- },
619
- factors,
620
- recommendations
621
- };
622
- }
623
-
624
- // src/provider.ts
625
- var import_core5 = require("@aiready/core");
626
- var PatternDetectProvider = {
627
- id: import_core5.ToolName.PatternDetect,
628
- alias: ["patterns", "duplicates", "duplication"],
629
- async analyze(options) {
630
- const results = await analyzePatterns(options);
631
- return import_core5.SpokeOutputSchema.parse({
632
- results: results.results,
633
- summary: {
634
- totalFiles: results.files.length,
635
- totalIssues: results.results.reduce(
636
- (sum, r) => sum + r.issues.length,
637
- 0
638
- ),
639
- clusters: results.clusters,
640
- config: Object.fromEntries(
641
- Object.entries(results.config).filter(
642
- ([key]) => !import_core5.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
643
- )
644
- )
645
- },
646
- metadata: {
647
- toolName: import_core5.ToolName.PatternDetect,
648
- version: "0.12.5",
649
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
650
- }
651
- });
652
- },
653
- score(output, options) {
654
- const duplicates = output.summary.duplicates || [];
655
- const totalFiles = output.summary.totalFiles || output.results.length;
656
- return calculatePatternScore(
657
- duplicates,
658
- totalFiles,
659
- options.costConfig
660
- );
661
- },
662
- defaultWeight: 22
663
- };
664
-
665
- // src/index.ts
666
- import_core6.ToolRegistry.register(PatternDetectProvider);
526
+ // src/analyzer.ts
667
527
  function getRefactoringSuggestion(patternType, similarity) {
668
528
  const baseMessages = {
669
529
  "api-handler": "Extract common middleware or create a base handler class",
@@ -697,8 +557,7 @@ async function getSmartDefaults(directory, userOptions) {
697
557
  include: userOptions.include || ["**/*.{ts,tsx,js,jsx,py,java}"],
698
558
  exclude: userOptions.exclude
699
559
  };
700
- const { scanFiles: scanFiles2 } = await import("@aiready/core");
701
- const files = await scanFiles2(scanOptions);
560
+ const files = await (0, import_core4.scanFiles)(scanOptions);
702
561
  const fileCount = files.length;
703
562
  const estimatedBlocks = fileCount * 5;
704
563
  const minLines = Math.max(
@@ -764,19 +623,18 @@ async function analyzePatterns(options) {
764
623
  minClusterFiles = 3,
765
624
  ...scanOptions
766
625
  } = finalOptions;
767
- const { scanFiles: scanFiles2 } = await import("@aiready/core");
768
- const files = await scanFiles2(scanOptions);
626
+ const files = await (0, import_core4.scanFiles)(scanOptions);
769
627
  const estimatedBlocks = files.length * 3;
770
628
  logConfiguration(finalOptions, estimatedBlocks);
771
629
  const results = [];
772
- const BATCH_SIZE = 50;
630
+ const READ_BATCH_SIZE = 50;
773
631
  const fileContents = [];
774
- for (let i = 0; i < files.length; i += BATCH_SIZE) {
775
- const batch = files.slice(i, i + BATCH_SIZE);
632
+ for (let i = 0; i < files.length; i += READ_BATCH_SIZE) {
633
+ const batch = files.slice(i, i + READ_BATCH_SIZE);
776
634
  const batchContents = await Promise.all(
777
635
  batch.map(async (file) => ({
778
636
  file,
779
- content: await (0, import_core6.readFileContent)(file)
637
+ content: await (0, import_core4.readFileContent)(file)
780
638
  }))
781
639
  );
782
640
  fileContents.push(...batchContents);
@@ -797,9 +655,9 @@ async function analyzePatterns(options) {
797
655
  );
798
656
  const issues = fileDuplicates.map((dup) => {
799
657
  const otherFile = dup.file1 === file ? dup.file2 : dup.file1;
800
- const severity2 = dup.similarity > 0.95 ? import_core6.Severity.Critical : dup.similarity > 0.9 ? import_core6.Severity.Major : import_core6.Severity.Minor;
658
+ const severity2 = dup.similarity > 0.95 ? import_core4.Severity.Critical : dup.similarity > 0.9 ? import_core4.Severity.Major : import_core4.Severity.Minor;
801
659
  return {
802
- type: import_core6.IssueType.DuplicatePattern,
660
+ type: import_core4.IssueType.DuplicatePattern,
803
661
  severity: severity2,
804
662
  message: `${dup.patternType} pattern ${Math.round(dup.similarity * 100)}% similar to ${otherFile} (${dup.tokenCost} tokens wasted)`,
805
663
  location: {
@@ -812,11 +670,11 @@ async function analyzePatterns(options) {
812
670
  let filteredIssues = issues;
813
671
  if (severity !== "all") {
814
672
  const severityMap = {
815
- critical: [import_core6.Severity.Critical],
816
- high: [import_core6.Severity.Critical, import_core6.Severity.Major],
817
- medium: [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor]
673
+ critical: [import_core4.Severity.Critical],
674
+ high: [import_core4.Severity.Critical, import_core4.Severity.Major],
675
+ medium: [import_core4.Severity.Critical, import_core4.Severity.Major, import_core4.Severity.Minor]
818
676
  };
819
- const allowedSeverities = severityMap[severity] || [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor];
677
+ const allowedSeverities = severityMap[severity] || [import_core4.Severity.Critical, import_core4.Severity.Major, import_core4.Severity.Minor];
820
678
  filteredIssues = issues.filter(
821
679
  (issue) => allowedSeverities.includes(issue.severity)
822
680
  );
@@ -882,14 +740,11 @@ function generateSummary(results) {
882
740
  path: issue.location.file,
883
741
  startLine: issue.location.line,
884
742
  endLine: 0
885
- // Not available from Issue
886
743
  },
887
744
  {
888
745
  path: fileMatch?.[1] || "unknown",
889
746
  startLine: 0,
890
- // Not available from Issue
891
747
  endLine: 0
892
- // Not available from Issue
893
748
  }
894
749
  ],
895
750
  similarity: similarityMatch ? parseInt(similarityMatch[1]) / 100 : 0,
@@ -905,11 +760,158 @@ function generateSummary(results) {
905
760
  };
906
761
  }
907
762
 
763
+ // src/scoring.ts
764
+ var import_core5 = require("@aiready/core");
765
+ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
766
+ const totalDuplicates = duplicates.length;
767
+ const totalTokenCost = duplicates.reduce((sum, d) => sum + d.tokenCost, 0);
768
+ const highImpactDuplicates = duplicates.filter(
769
+ (d) => d.tokenCost > 1e3 || d.similarity > 0.7
770
+ ).length;
771
+ if (totalFilesAnalyzed === 0) {
772
+ return {
773
+ toolName: import_core5.ToolName.PatternDetect,
774
+ score: 100,
775
+ rawMetrics: {
776
+ totalDuplicates: 0,
777
+ totalTokenCost: 0,
778
+ highImpactDuplicates: 0,
779
+ totalFilesAnalyzed: 0
780
+ },
781
+ factors: [],
782
+ recommendations: []
783
+ };
784
+ }
785
+ const duplicatesPerFile = totalDuplicates / totalFilesAnalyzed * 100;
786
+ const tokenWastePerFile = totalTokenCost / totalFilesAnalyzed;
787
+ const duplicatesPenalty = Math.min(60, duplicatesPerFile * 0.6);
788
+ const tokenPenalty = Math.min(40, tokenWastePerFile / 125);
789
+ const highImpactPenalty = highImpactDuplicates > 0 ? Math.min(15, highImpactDuplicates * 2 - 5) : -5;
790
+ const score = 100 - duplicatesPenalty - tokenPenalty - highImpactPenalty;
791
+ const finalScore = Math.max(0, Math.min(100, Math.round(score)));
792
+ const factors = [
793
+ {
794
+ name: "Duplication Density",
795
+ impact: -Math.round(duplicatesPenalty),
796
+ description: `${duplicatesPerFile.toFixed(1)} duplicates per 100 files`
797
+ },
798
+ {
799
+ name: "Token Waste",
800
+ impact: -Math.round(tokenPenalty),
801
+ description: `${Math.round(tokenWastePerFile)} tokens wasted per file`
802
+ }
803
+ ];
804
+ if (highImpactDuplicates > 0) {
805
+ factors.push({
806
+ name: "High-Impact Patterns",
807
+ impact: -Math.round(highImpactPenalty),
808
+ description: `${highImpactDuplicates} high-impact duplicates (>1000 tokens or >70% similar)`
809
+ });
810
+ } else {
811
+ factors.push({
812
+ name: "No High-Impact Patterns",
813
+ impact: 5,
814
+ description: "No severe duplicates detected"
815
+ });
816
+ }
817
+ const recommendations = [];
818
+ if (highImpactDuplicates > 0) {
819
+ const estimatedImpact = Math.min(15, highImpactDuplicates * 3);
820
+ recommendations.push({
821
+ action: `Deduplicate ${highImpactDuplicates} high-impact pattern${highImpactDuplicates > 1 ? "s" : ""}`,
822
+ estimatedImpact,
823
+ priority: "high"
824
+ });
825
+ }
826
+ if (totalDuplicates > 10 && duplicatesPerFile > 20) {
827
+ const estimatedImpact = Math.min(10, Math.round(duplicatesPenalty * 0.3));
828
+ recommendations.push({
829
+ action: "Extract common patterns into shared utilities",
830
+ estimatedImpact,
831
+ priority: "medium"
832
+ });
833
+ }
834
+ if (tokenWastePerFile > 2e3) {
835
+ const estimatedImpact = Math.min(8, Math.round(tokenPenalty * 0.4));
836
+ recommendations.push({
837
+ action: "Consolidate duplicated logic to reduce AI context waste",
838
+ estimatedImpact,
839
+ priority: totalTokenCost > 1e4 ? "high" : "medium"
840
+ });
841
+ }
842
+ const cfg = { ...import_core5.DEFAULT_COST_CONFIG, ...costConfig };
843
+ const estimatedMonthlyCost = (0, import_core5.calculateMonthlyCost)(totalTokenCost, cfg);
844
+ const issues = duplicates.map((d) => ({
845
+ severity: d.severity === "critical" ? "critical" : d.severity === "major" ? "major" : "minor"
846
+ }));
847
+ const productivityImpact = (0, import_core5.calculateProductivityImpact)(issues);
848
+ return {
849
+ toolName: "pattern-detect",
850
+ score: finalScore,
851
+ rawMetrics: {
852
+ totalDuplicates,
853
+ totalTokenCost,
854
+ highImpactDuplicates,
855
+ totalFilesAnalyzed,
856
+ duplicatesPerFile: Math.round(duplicatesPerFile * 10) / 10,
857
+ tokenWastePerFile: Math.round(tokenWastePerFile),
858
+ // Business value metrics
859
+ estimatedMonthlyCost,
860
+ estimatedDeveloperHours: productivityImpact.totalHours
861
+ },
862
+ factors,
863
+ recommendations
864
+ };
865
+ }
866
+
867
+ // src/provider.ts
868
+ var PatternDetectProvider = {
869
+ id: import_core6.ToolName.PatternDetect,
870
+ alias: ["patterns", "duplicates", "duplication"],
871
+ async analyze(options) {
872
+ const results = await analyzePatterns(options);
873
+ return import_core6.SpokeOutputSchema.parse({
874
+ results: results.results,
875
+ summary: {
876
+ totalFiles: results.files.length,
877
+ totalIssues: results.results.reduce(
878
+ (sum, r) => sum + r.issues.length,
879
+ 0
880
+ ),
881
+ clusters: results.clusters,
882
+ config: Object.fromEntries(
883
+ Object.entries(results.config).filter(
884
+ ([key]) => !import_core6.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
885
+ )
886
+ )
887
+ },
888
+ metadata: {
889
+ toolName: import_core6.ToolName.PatternDetect,
890
+ version: "0.12.5",
891
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
892
+ }
893
+ });
894
+ },
895
+ score(output, options) {
896
+ const duplicates = output.summary.duplicates || [];
897
+ const totalFiles = output.summary.totalFiles || output.results.length;
898
+ return calculatePatternScore(
899
+ duplicates,
900
+ totalFiles,
901
+ options.costConfig
902
+ );
903
+ },
904
+ defaultWeight: 22
905
+ };
906
+
907
+ // src/index.ts
908
+ import_core7.ToolRegistry.register(PatternDetectProvider);
909
+
908
910
  // src/cli.ts
909
911
  var import_chalk = __toESM(require("chalk"));
910
912
  var import_fs = require("fs");
911
913
  var import_path2 = require("path");
912
- var import_core7 = require("@aiready/core");
914
+ var import_core8 = require("@aiready/core");
913
915
  var program = new import_commander.Command();
914
916
  program.name("aiready-patterns").description("Detect duplicate patterns in your codebase").version("0.1.0").addHelpText(
915
917
  "after",
@@ -963,7 +965,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
963
965
  ).option("--output-file <path>", "Output file path (for json/html)").action(async (directory, options) => {
964
966
  console.log(import_chalk.default.blue("\u{1F50D} Analyzing patterns...\n"));
965
967
  const startTime = Date.now();
966
- const config = await (0, import_core7.loadConfig)(directory);
968
+ const config = await (0, import_core8.loadConfig)(directory);
967
969
  const defaults = {
968
970
  minSimilarity: 0.4,
969
971
  minLines: 5,
@@ -974,7 +976,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
974
976
  streamResults: true,
975
977
  include: void 0,
976
978
  exclude: void 0,
977
- minSeverity: import_core7.Severity.Minor,
979
+ minSeverity: import_core8.Severity.Minor,
978
980
  excludeTestFixtures: false,
979
981
  excludeTemplates: false,
980
982
  includeTests: false,
@@ -985,7 +987,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
985
987
  minClusterFiles: 3,
986
988
  showRawDuplicates: false
987
989
  };
988
- const mergedConfig = (0, import_core7.mergeConfigWithDefaults)(config, defaults);
990
+ const mergedConfig = (0, import_core8.mergeConfigWithDefaults)(config, defaults);
989
991
  const finalOptions = {
990
992
  rootDir: directory,
991
993
  minSimilarity: options.similarity ? parseFloat(options.similarity) : mergedConfig.minSimilarity,
@@ -1057,7 +1059,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
1057
1059
  clusters: clusters || [],
1058
1060
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1059
1061
  };
1060
- const outputPath = (0, import_core7.resolveOutputPath)(
1062
+ const outputPath = (0, import_core8.resolveOutputPath)(
1061
1063
  options.outputFile,
1062
1064
  `pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
1063
1065
  directory
@@ -1073,7 +1075,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
1073
1075
  }
1074
1076
  if (options.output === "html") {
1075
1077
  const html = generateHTMLReport(summary, results);
1076
- const outputPath = (0, import_core7.resolveOutputPath)(
1078
+ const outputPath = (0, import_core8.resolveOutputPath)(
1077
1079
  options.outputFile,
1078
1080
  `pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
1079
1081
  directory
@@ -1403,10 +1405,10 @@ function generateHTMLReport(summary, results) {
1403
1405
  }
1404
1406
  program.parse();
1405
1407
  function getSeverityValue(s) {
1406
- if (s === import_core7.Severity.Critical || s === "critical") return 4;
1407
- if (s === import_core7.Severity.Major || s === "major") return 3;
1408
- if (s === import_core7.Severity.Minor || s === "minor") return 2;
1409
- if (s === import_core7.Severity.Info || s === "info") return 1;
1408
+ if (s === import_core8.Severity.Critical || s === "critical") return 4;
1409
+ if (s === import_core8.Severity.Major || s === "major") return 3;
1410
+ if (s === import_core8.Severity.Minor || s === "minor") return 2;
1411
+ if (s === import_core8.Severity.Info || s === "info") return 1;
1410
1412
  return 0;
1411
1413
  }
1412
1414
  function getSeverityBadge(severity) {
package/dist/cli.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  analyzePatterns,
4
4
  filterBySeverity,
5
5
  generateSummary
6
- } from "./chunk-6YUGU4P4.mjs";
6
+ } from "./chunk-H4ADJYOG.mjs";
7
7
 
8
8
  // src/cli.ts
9
9
  import { Command } from "commander";
package/dist/index.d.mts CHANGED
@@ -1,6 +1,11 @@
1
- import { Severity, CostConfig, ToolScoringOutput, ToolProvider, ScanOptions, AnalysisResult } from '@aiready/core';
1
+ import { ToolProvider, Severity, ScanOptions, AnalysisResult, CostConfig, ToolScoringOutput } from '@aiready/core';
2
2
  export { Severity } from '@aiready/core';
3
3
 
4
+ /**
5
+ * Pattern Detection Tool Provider
6
+ */
7
+ declare const PatternDetectProvider: ToolProvider;
8
+
4
9
  type PatternType = 'api-handler' | 'validator' | 'utility' | 'class-method' | 'component' | 'function' | 'unknown';
5
10
  interface DuplicatePattern {
6
11
  file1: string;
@@ -68,45 +73,19 @@ interface RefactorCluster {
68
73
  reason?: string;
69
74
  suggestion?: string;
70
75
  }
71
-
72
76
  /**
73
- * Calculate AI Readiness Score for pattern duplication (0-100)
74
- *
75
- * Based on:
76
- * - Number of duplicates per file
77
- * - Token waste per file
78
- * - High-impact duplicates (>1000 tokens or >70% similarity)
79
- *
80
- * Includes business value metrics:
81
- * - Estimated monthly cost of token waste
82
- * - Estimated developer hours to fix
77
+ * Group raw duplicates by file pairs to reduce noise
83
78
  */
84
- declare function calculatePatternScore(duplicates: DuplicatePattern[], totalFilesAnalyzed: number, costConfig?: Partial<CostConfig>): ToolScoringOutput;
85
-
79
+ declare function groupDuplicatesByFilePair(duplicates: DuplicatePattern[]): DuplicateGroup[];
86
80
  /**
87
- * Pattern Detection Tool Provider
81
+ * Create clusters of highly related files (refactor targets)
82
+ * Uses a simple connected components algorithm
88
83
  */
89
- declare const PatternDetectProvider: ToolProvider;
90
-
84
+ declare function createRefactorClusters(duplicates: DuplicatePattern[]): RefactorCluster[];
91
85
  /**
92
- * Calculate severity based on context rules and code characteristics
93
- */
94
- declare function calculateSeverity(file1: string, file2: string, code: string, similarity: number, linesOfCode: number): {
95
- severity: Severity;
96
- reason?: string;
97
- suggestion?: string;
98
- matchedRule?: string;
99
- };
100
- /**
101
- * Get a human-readable severity label with emoji
86
+ * Filter clusters by impact threshold
102
87
  */
103
- declare function getSeverityLabel(severity: Severity): string;
104
- /**
105
- * Filter duplicates by minimum severity threshold
106
- */
107
- declare function filterBySeverity<T extends {
108
- severity: Severity;
109
- }>(duplicates: T[], minSeverity: Severity): T[];
88
+ declare function filterClustersByImpact(clusters: RefactorCluster[], minTokenCost?: number, minFiles?: number): RefactorCluster[];
110
89
 
111
90
  interface PatternDetectOptions extends ScanOptions {
112
91
  minSimilarity?: number;
@@ -152,9 +131,60 @@ declare function analyzePatterns(options: PatternDetectOptions): Promise<{
152
131
  clusters?: RefactorCluster[];
153
132
  config: PatternDetectOptions;
154
133
  }>;
134
+ declare function generateSummary(results: AnalysisResult[]): PatternSummary;
135
+
136
+ /**
137
+ * Calculate AI Readiness Score for pattern duplication (0-100)
138
+ *
139
+ * Based on:
140
+ * - Number of duplicates per file
141
+ * - Token waste per file
142
+ * - High-impact duplicates (>1000 tokens or >70% similarity)
143
+ *
144
+ * Includes business value metrics:
145
+ * - Estimated monthly cost of token waste
146
+ * - Estimated developer hours to fix
147
+ */
148
+ declare function calculatePatternScore(duplicates: DuplicatePattern[], totalFilesAnalyzed: number, costConfig?: Partial<CostConfig>): ToolScoringOutput;
149
+
155
150
  /**
156
- * Generate a summary of pattern analysis
151
+ * Context-aware severity detection for duplicate patterns
152
+ * Identifies intentional duplication patterns and adjusts severity accordingly
157
153
  */
158
- declare function generateSummary(results: AnalysisResult[]): PatternSummary;
154
+ interface ContextRule {
155
+ name: string;
156
+ detect: (file: string, code: string) => boolean;
157
+ severity: Severity;
158
+ reason: string;
159
+ suggestion?: string;
160
+ }
161
+ /**
162
+ * Context rules for detecting intentional or acceptable duplication patterns
163
+ * Rules are checked in order - first match wins
164
+ */
165
+ declare const CONTEXT_RULES: ContextRule[];
166
+ /**
167
+ * Calculate severity based on context rules and code characteristics
168
+ */
169
+ declare function calculateSeverity(file1: string, file2: string, code: string, similarity: number, linesOfCode: number): {
170
+ severity: Severity;
171
+ reason?: string;
172
+ suggestion?: string;
173
+ matchedRule?: string;
174
+ };
175
+ /**
176
+ * Get a human-readable severity label with emoji
177
+ */
178
+ declare function getSeverityLabel(severity: Severity): string;
179
+ /**
180
+ * Filter duplicates by minimum severity threshold
181
+ */
182
+ declare function filterBySeverity<T extends {
183
+ severity: Severity;
184
+ }>(duplicates: T[], minSeverity: Severity): T[];
185
+ /**
186
+ * Get severity threshold for filtering
187
+ */
188
+ declare function getSeverityThreshold(severity: Severity): number;
159
189
 
160
- export { type DuplicateGroup, type DuplicatePattern, type PatternDetectOptions, PatternDetectProvider, type PatternSummary, type PatternType, type RefactorCluster, analyzePatterns, calculatePatternScore, calculateSeverity, detectDuplicatePatterns, filterBySeverity, generateSummary, getSeverityLabel, getSmartDefaults };
190
+ export { CONTEXT_RULES, type ContextRule, type DuplicateGroup, type DuplicatePattern, type PatternDetectOptions, PatternDetectProvider, type PatternSummary, type PatternType, type RefactorCluster, analyzePatterns, calculatePatternScore, calculateSeverity, createRefactorClusters, detectDuplicatePatterns, filterBySeverity, filterClustersByImpact, generateSummary, getSeverityLabel, getSeverityThreshold, getSmartDefaults, groupDuplicatesByFilePair };