@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/index.d.ts 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 };
package/dist/index.js CHANGED
@@ -30,20 +30,31 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ CONTEXT_RULES: () => CONTEXT_RULES,
33
34
  PatternDetectProvider: () => PatternDetectProvider,
34
- Severity: () => import_core6.Severity,
35
+ Severity: () => import_core7.Severity,
35
36
  analyzePatterns: () => analyzePatterns,
36
37
  calculatePatternScore: () => calculatePatternScore,
37
38
  calculateSeverity: () => calculateSeverity,
39
+ createRefactorClusters: () => createRefactorClusters,
38
40
  detectDuplicatePatterns: () => detectDuplicatePatterns,
39
41
  filterBySeverity: () => filterBySeverity,
42
+ filterClustersByImpact: () => filterClustersByImpact,
40
43
  generateSummary: () => generateSummary,
41
44
  getSeverityLabel: () => getSeverityLabel,
42
- getSmartDefaults: () => getSmartDefaults
45
+ getSeverityThreshold: () => getSeverityThreshold,
46
+ getSmartDefaults: () => getSmartDefaults,
47
+ groupDuplicatesByFilePair: () => groupDuplicatesByFilePair
43
48
  });
44
49
  module.exports = __toCommonJS(index_exports);
50
+ var import_core7 = require("@aiready/core");
51
+
52
+ // src/provider.ts
45
53
  var import_core6 = require("@aiready/core");
46
54
 
55
+ // src/analyzer.ts
56
+ var import_core4 = require("@aiready/core");
57
+
47
58
  // src/detector.ts
48
59
  var import_core2 = require("@aiready/core");
49
60
 
@@ -197,6 +208,15 @@ function filterBySeverity(duplicates, minSeverity) {
197
208
  return dupIndex >= minIndex;
198
209
  });
199
210
  }
211
+ function getSeverityThreshold(severity) {
212
+ const thresholds = {
213
+ [import_core.Severity.Critical]: 0.95,
214
+ [import_core.Severity.Major]: 0.85,
215
+ [import_core.Severity.Minor]: 0.5,
216
+ [import_core.Severity.Info]: 0
217
+ };
218
+ return thresholds[severity] || 0;
219
+ }
200
220
 
201
221
  // src/detector.ts
202
222
  function normalizeCode(code, isPython = false) {
@@ -541,153 +561,7 @@ function filterClustersByImpact(clusters, minTokenCost = 1e3, minFiles = 3) {
541
561
  );
542
562
  }
543
563
 
544
- // src/scoring.ts
545
- var import_core4 = require("@aiready/core");
546
- function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
547
- const totalDuplicates = duplicates.length;
548
- const totalTokenCost = duplicates.reduce((sum, d) => sum + d.tokenCost, 0);
549
- const highImpactDuplicates = duplicates.filter(
550
- (d) => d.tokenCost > 1e3 || d.similarity > 0.7
551
- ).length;
552
- if (totalFilesAnalyzed === 0) {
553
- return {
554
- toolName: import_core4.ToolName.PatternDetect,
555
- score: 100,
556
- rawMetrics: {
557
- totalDuplicates: 0,
558
- totalTokenCost: 0,
559
- highImpactDuplicates: 0,
560
- totalFilesAnalyzed: 0
561
- },
562
- factors: [],
563
- recommendations: []
564
- };
565
- }
566
- const duplicatesPerFile = totalDuplicates / totalFilesAnalyzed * 100;
567
- const tokenWastePerFile = totalTokenCost / totalFilesAnalyzed;
568
- const duplicatesPenalty = Math.min(60, duplicatesPerFile * 0.6);
569
- const tokenPenalty = Math.min(40, tokenWastePerFile / 125);
570
- const highImpactPenalty = highImpactDuplicates > 0 ? Math.min(15, highImpactDuplicates * 2 - 5) : -5;
571
- const score = 100 - duplicatesPenalty - tokenPenalty - highImpactPenalty;
572
- const finalScore = Math.max(0, Math.min(100, Math.round(score)));
573
- const factors = [
574
- {
575
- name: "Duplication Density",
576
- impact: -Math.round(duplicatesPenalty),
577
- description: `${duplicatesPerFile.toFixed(1)} duplicates per 100 files`
578
- },
579
- {
580
- name: "Token Waste",
581
- impact: -Math.round(tokenPenalty),
582
- description: `${Math.round(tokenWastePerFile)} tokens wasted per file`
583
- }
584
- ];
585
- if (highImpactDuplicates > 0) {
586
- factors.push({
587
- name: "High-Impact Patterns",
588
- impact: -Math.round(highImpactPenalty),
589
- description: `${highImpactDuplicates} high-impact duplicates (>1000 tokens or >70% similar)`
590
- });
591
- } else {
592
- factors.push({
593
- name: "No High-Impact Patterns",
594
- impact: 5,
595
- description: "No severe duplicates detected"
596
- });
597
- }
598
- const recommendations = [];
599
- if (highImpactDuplicates > 0) {
600
- const estimatedImpact = Math.min(15, highImpactDuplicates * 3);
601
- recommendations.push({
602
- action: `Deduplicate ${highImpactDuplicates} high-impact pattern${highImpactDuplicates > 1 ? "s" : ""}`,
603
- estimatedImpact,
604
- priority: "high"
605
- });
606
- }
607
- if (totalDuplicates > 10 && duplicatesPerFile > 20) {
608
- const estimatedImpact = Math.min(10, Math.round(duplicatesPenalty * 0.3));
609
- recommendations.push({
610
- action: "Extract common patterns into shared utilities",
611
- estimatedImpact,
612
- priority: "medium"
613
- });
614
- }
615
- if (tokenWastePerFile > 2e3) {
616
- const estimatedImpact = Math.min(8, Math.round(tokenPenalty * 0.4));
617
- recommendations.push({
618
- action: "Consolidate duplicated logic to reduce AI context waste",
619
- estimatedImpact,
620
- priority: totalTokenCost > 1e4 ? "high" : "medium"
621
- });
622
- }
623
- const cfg = { ...import_core4.DEFAULT_COST_CONFIG, ...costConfig };
624
- const estimatedMonthlyCost = (0, import_core4.calculateMonthlyCost)(totalTokenCost, cfg);
625
- const issues = duplicates.map((d) => ({
626
- severity: d.severity === "critical" ? "critical" : d.severity === "major" ? "major" : "minor"
627
- }));
628
- const productivityImpact = (0, import_core4.calculateProductivityImpact)(issues);
629
- return {
630
- toolName: "pattern-detect",
631
- score: finalScore,
632
- rawMetrics: {
633
- totalDuplicates,
634
- totalTokenCost,
635
- highImpactDuplicates,
636
- totalFilesAnalyzed,
637
- duplicatesPerFile: Math.round(duplicatesPerFile * 10) / 10,
638
- tokenWastePerFile: Math.round(tokenWastePerFile),
639
- // Business value metrics
640
- estimatedMonthlyCost,
641
- estimatedDeveloperHours: productivityImpact.totalHours
642
- },
643
- factors,
644
- recommendations
645
- };
646
- }
647
-
648
- // src/provider.ts
649
- var import_core5 = require("@aiready/core");
650
- var PatternDetectProvider = {
651
- id: import_core5.ToolName.PatternDetect,
652
- alias: ["patterns", "duplicates", "duplication"],
653
- async analyze(options) {
654
- const results = await analyzePatterns(options);
655
- return import_core5.SpokeOutputSchema.parse({
656
- results: results.results,
657
- summary: {
658
- totalFiles: results.files.length,
659
- totalIssues: results.results.reduce(
660
- (sum, r) => sum + r.issues.length,
661
- 0
662
- ),
663
- clusters: results.clusters,
664
- config: Object.fromEntries(
665
- Object.entries(results.config).filter(
666
- ([key]) => !import_core5.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
667
- )
668
- )
669
- },
670
- metadata: {
671
- toolName: import_core5.ToolName.PatternDetect,
672
- version: "0.12.5",
673
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
674
- }
675
- });
676
- },
677
- score(output, options) {
678
- const duplicates = output.summary.duplicates || [];
679
- const totalFiles = output.summary.totalFiles || output.results.length;
680
- return calculatePatternScore(
681
- duplicates,
682
- totalFiles,
683
- options.costConfig
684
- );
685
- },
686
- defaultWeight: 22
687
- };
688
-
689
- // src/index.ts
690
- import_core6.ToolRegistry.register(PatternDetectProvider);
564
+ // src/analyzer.ts
691
565
  function getRefactoringSuggestion(patternType, similarity) {
692
566
  const baseMessages = {
693
567
  "api-handler": "Extract common middleware or create a base handler class",
@@ -721,8 +595,7 @@ async function getSmartDefaults(directory, userOptions) {
721
595
  include: userOptions.include || ["**/*.{ts,tsx,js,jsx,py,java}"],
722
596
  exclude: userOptions.exclude
723
597
  };
724
- const { scanFiles: scanFiles2 } = await import("@aiready/core");
725
- const files = await scanFiles2(scanOptions);
598
+ const files = await (0, import_core4.scanFiles)(scanOptions);
726
599
  const fileCount = files.length;
727
600
  const estimatedBlocks = fileCount * 5;
728
601
  const minLines = Math.max(
@@ -788,19 +661,18 @@ async function analyzePatterns(options) {
788
661
  minClusterFiles = 3,
789
662
  ...scanOptions
790
663
  } = finalOptions;
791
- const { scanFiles: scanFiles2 } = await import("@aiready/core");
792
- const files = await scanFiles2(scanOptions);
664
+ const files = await (0, import_core4.scanFiles)(scanOptions);
793
665
  const estimatedBlocks = files.length * 3;
794
666
  logConfiguration(finalOptions, estimatedBlocks);
795
667
  const results = [];
796
- const BATCH_SIZE = 50;
668
+ const READ_BATCH_SIZE = 50;
797
669
  const fileContents = [];
798
- for (let i = 0; i < files.length; i += BATCH_SIZE) {
799
- const batch = files.slice(i, i + BATCH_SIZE);
670
+ for (let i = 0; i < files.length; i += READ_BATCH_SIZE) {
671
+ const batch = files.slice(i, i + READ_BATCH_SIZE);
800
672
  const batchContents = await Promise.all(
801
673
  batch.map(async (file) => ({
802
674
  file,
803
- content: await (0, import_core6.readFileContent)(file)
675
+ content: await (0, import_core4.readFileContent)(file)
804
676
  }))
805
677
  );
806
678
  fileContents.push(...batchContents);
@@ -821,9 +693,9 @@ async function analyzePatterns(options) {
821
693
  );
822
694
  const issues = fileDuplicates.map((dup) => {
823
695
  const otherFile = dup.file1 === file ? dup.file2 : dup.file1;
824
- const severity2 = dup.similarity > 0.95 ? import_core6.Severity.Critical : dup.similarity > 0.9 ? import_core6.Severity.Major : import_core6.Severity.Minor;
696
+ const severity2 = dup.similarity > 0.95 ? import_core4.Severity.Critical : dup.similarity > 0.9 ? import_core4.Severity.Major : import_core4.Severity.Minor;
825
697
  return {
826
- type: import_core6.IssueType.DuplicatePattern,
698
+ type: import_core4.IssueType.DuplicatePattern,
827
699
  severity: severity2,
828
700
  message: `${dup.patternType} pattern ${Math.round(dup.similarity * 100)}% similar to ${otherFile} (${dup.tokenCost} tokens wasted)`,
829
701
  location: {
@@ -836,11 +708,11 @@ async function analyzePatterns(options) {
836
708
  let filteredIssues = issues;
837
709
  if (severity !== "all") {
838
710
  const severityMap = {
839
- critical: [import_core6.Severity.Critical],
840
- high: [import_core6.Severity.Critical, import_core6.Severity.Major],
841
- medium: [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor]
711
+ critical: [import_core4.Severity.Critical],
712
+ high: [import_core4.Severity.Critical, import_core4.Severity.Major],
713
+ medium: [import_core4.Severity.Critical, import_core4.Severity.Major, import_core4.Severity.Minor]
842
714
  };
843
- const allowedSeverities = severityMap[severity] || [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor];
715
+ const allowedSeverities = severityMap[severity] || [import_core4.Severity.Critical, import_core4.Severity.Major, import_core4.Severity.Minor];
844
716
  filteredIssues = issues.filter(
845
717
  (issue) => allowedSeverities.includes(issue.severity)
846
718
  );
@@ -906,14 +778,11 @@ function generateSummary(results) {
906
778
  path: issue.location.file,
907
779
  startLine: issue.location.line,
908
780
  endLine: 0
909
- // Not available from Issue
910
781
  },
911
782
  {
912
783
  path: fileMatch?.[1] || "unknown",
913
784
  startLine: 0,
914
- // Not available from Issue
915
785
  endLine: 0
916
- // Not available from Issue
917
786
  }
918
787
  ],
919
788
  similarity: similarityMatch ? parseInt(similarityMatch[1]) / 100 : 0,
@@ -928,16 +797,168 @@ function generateSummary(results) {
928
797
  topDuplicates
929
798
  };
930
799
  }
800
+
801
+ // src/scoring.ts
802
+ var import_core5 = require("@aiready/core");
803
+ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
804
+ const totalDuplicates = duplicates.length;
805
+ const totalTokenCost = duplicates.reduce((sum, d) => sum + d.tokenCost, 0);
806
+ const highImpactDuplicates = duplicates.filter(
807
+ (d) => d.tokenCost > 1e3 || d.similarity > 0.7
808
+ ).length;
809
+ if (totalFilesAnalyzed === 0) {
810
+ return {
811
+ toolName: import_core5.ToolName.PatternDetect,
812
+ score: 100,
813
+ rawMetrics: {
814
+ totalDuplicates: 0,
815
+ totalTokenCost: 0,
816
+ highImpactDuplicates: 0,
817
+ totalFilesAnalyzed: 0
818
+ },
819
+ factors: [],
820
+ recommendations: []
821
+ };
822
+ }
823
+ const duplicatesPerFile = totalDuplicates / totalFilesAnalyzed * 100;
824
+ const tokenWastePerFile = totalTokenCost / totalFilesAnalyzed;
825
+ const duplicatesPenalty = Math.min(60, duplicatesPerFile * 0.6);
826
+ const tokenPenalty = Math.min(40, tokenWastePerFile / 125);
827
+ const highImpactPenalty = highImpactDuplicates > 0 ? Math.min(15, highImpactDuplicates * 2 - 5) : -5;
828
+ const score = 100 - duplicatesPenalty - tokenPenalty - highImpactPenalty;
829
+ const finalScore = Math.max(0, Math.min(100, Math.round(score)));
830
+ const factors = [
831
+ {
832
+ name: "Duplication Density",
833
+ impact: -Math.round(duplicatesPenalty),
834
+ description: `${duplicatesPerFile.toFixed(1)} duplicates per 100 files`
835
+ },
836
+ {
837
+ name: "Token Waste",
838
+ impact: -Math.round(tokenPenalty),
839
+ description: `${Math.round(tokenWastePerFile)} tokens wasted per file`
840
+ }
841
+ ];
842
+ if (highImpactDuplicates > 0) {
843
+ factors.push({
844
+ name: "High-Impact Patterns",
845
+ impact: -Math.round(highImpactPenalty),
846
+ description: `${highImpactDuplicates} high-impact duplicates (>1000 tokens or >70% similar)`
847
+ });
848
+ } else {
849
+ factors.push({
850
+ name: "No High-Impact Patterns",
851
+ impact: 5,
852
+ description: "No severe duplicates detected"
853
+ });
854
+ }
855
+ const recommendations = [];
856
+ if (highImpactDuplicates > 0) {
857
+ const estimatedImpact = Math.min(15, highImpactDuplicates * 3);
858
+ recommendations.push({
859
+ action: `Deduplicate ${highImpactDuplicates} high-impact pattern${highImpactDuplicates > 1 ? "s" : ""}`,
860
+ estimatedImpact,
861
+ priority: "high"
862
+ });
863
+ }
864
+ if (totalDuplicates > 10 && duplicatesPerFile > 20) {
865
+ const estimatedImpact = Math.min(10, Math.round(duplicatesPenalty * 0.3));
866
+ recommendations.push({
867
+ action: "Extract common patterns into shared utilities",
868
+ estimatedImpact,
869
+ priority: "medium"
870
+ });
871
+ }
872
+ if (tokenWastePerFile > 2e3) {
873
+ const estimatedImpact = Math.min(8, Math.round(tokenPenalty * 0.4));
874
+ recommendations.push({
875
+ action: "Consolidate duplicated logic to reduce AI context waste",
876
+ estimatedImpact,
877
+ priority: totalTokenCost > 1e4 ? "high" : "medium"
878
+ });
879
+ }
880
+ const cfg = { ...import_core5.DEFAULT_COST_CONFIG, ...costConfig };
881
+ const estimatedMonthlyCost = (0, import_core5.calculateMonthlyCost)(totalTokenCost, cfg);
882
+ const issues = duplicates.map((d) => ({
883
+ severity: d.severity === "critical" ? "critical" : d.severity === "major" ? "major" : "minor"
884
+ }));
885
+ const productivityImpact = (0, import_core5.calculateProductivityImpact)(issues);
886
+ return {
887
+ toolName: "pattern-detect",
888
+ score: finalScore,
889
+ rawMetrics: {
890
+ totalDuplicates,
891
+ totalTokenCost,
892
+ highImpactDuplicates,
893
+ totalFilesAnalyzed,
894
+ duplicatesPerFile: Math.round(duplicatesPerFile * 10) / 10,
895
+ tokenWastePerFile: Math.round(tokenWastePerFile),
896
+ // Business value metrics
897
+ estimatedMonthlyCost,
898
+ estimatedDeveloperHours: productivityImpact.totalHours
899
+ },
900
+ factors,
901
+ recommendations
902
+ };
903
+ }
904
+
905
+ // src/provider.ts
906
+ var PatternDetectProvider = {
907
+ id: import_core6.ToolName.PatternDetect,
908
+ alias: ["patterns", "duplicates", "duplication"],
909
+ async analyze(options) {
910
+ const results = await analyzePatterns(options);
911
+ return import_core6.SpokeOutputSchema.parse({
912
+ results: results.results,
913
+ summary: {
914
+ totalFiles: results.files.length,
915
+ totalIssues: results.results.reduce(
916
+ (sum, r) => sum + r.issues.length,
917
+ 0
918
+ ),
919
+ clusters: results.clusters,
920
+ config: Object.fromEntries(
921
+ Object.entries(results.config).filter(
922
+ ([key]) => !import_core6.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
923
+ )
924
+ )
925
+ },
926
+ metadata: {
927
+ toolName: import_core6.ToolName.PatternDetect,
928
+ version: "0.12.5",
929
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
930
+ }
931
+ });
932
+ },
933
+ score(output, options) {
934
+ const duplicates = output.summary.duplicates || [];
935
+ const totalFiles = output.summary.totalFiles || output.results.length;
936
+ return calculatePatternScore(
937
+ duplicates,
938
+ totalFiles,
939
+ options.costConfig
940
+ );
941
+ },
942
+ defaultWeight: 22
943
+ };
944
+
945
+ // src/index.ts
946
+ import_core7.ToolRegistry.register(PatternDetectProvider);
931
947
  // Annotate the CommonJS export names for ESM import in node:
932
948
  0 && (module.exports = {
949
+ CONTEXT_RULES,
933
950
  PatternDetectProvider,
934
951
  Severity,
935
952
  analyzePatterns,
936
953
  calculatePatternScore,
937
954
  calculateSeverity,
955
+ createRefactorClusters,
938
956
  detectDuplicatePatterns,
939
957
  filterBySeverity,
958
+ filterClustersByImpact,
940
959
  generateSummary,
941
960
  getSeverityLabel,
942
- getSmartDefaults
961
+ getSeverityThreshold,
962
+ getSmartDefaults,
963
+ groupDuplicatesByFilePair
943
964
  });
package/dist/index.mjs CHANGED
@@ -1,24 +1,34 @@
1
1
  import {
2
+ CONTEXT_RULES,
2
3
  PatternDetectProvider,
3
4
  Severity,
4
5
  analyzePatterns,
5
6
  calculatePatternScore,
6
7
  calculateSeverity,
8
+ createRefactorClusters,
7
9
  detectDuplicatePatterns,
8
10
  filterBySeverity,
11
+ filterClustersByImpact,
9
12
  generateSummary,
10
13
  getSeverityLabel,
11
- getSmartDefaults
12
- } from "./chunk-6YUGU4P4.mjs";
14
+ getSeverityThreshold,
15
+ getSmartDefaults,
16
+ groupDuplicatesByFilePair
17
+ } from "./chunk-H4ADJYOG.mjs";
13
18
  export {
19
+ CONTEXT_RULES,
14
20
  PatternDetectProvider,
15
21
  Severity,
16
22
  analyzePatterns,
17
23
  calculatePatternScore,
18
24
  calculateSeverity,
25
+ createRefactorClusters,
19
26
  detectDuplicatePatterns,
20
27
  filterBySeverity,
28
+ filterClustersByImpact,
21
29
  generateSummary,
22
30
  getSeverityLabel,
23
- getSmartDefaults
31
+ getSeverityThreshold,
32
+ getSmartDefaults,
33
+ groupDuplicatesByFilePair
24
34
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/pattern-detect",
3
- "version": "0.14.17",
3
+ "version": "0.14.21",
4
4
  "description": "Semantic duplicate pattern detection for AI-generated code - finds similar implementations that waste AI context tokens",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "commander": "^14.0.0",
47
47
  "chalk": "^5.3.0",
48
- "@aiready/core": "0.21.17"
48
+ "@aiready/core": "0.21.21"
49
49
  },
50
50
  "devDependencies": {
51
51
  "tsup": "^8.3.5",