@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/chunk-H4ADJYOG.mjs +925 -0
- package/dist/cli.js +176 -174
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +67 -37
- package/dist/index.d.ts +67 -37
- package/dist/index.js +188 -167
- package/dist/index.mjs +13 -3
- package/package.json +2 -2
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/
|
|
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
|
|
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
|
|
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
|
|
630
|
+
const READ_BATCH_SIZE = 50;
|
|
773
631
|
const fileContents = [];
|
|
774
|
-
for (let i = 0; i < files.length; i +=
|
|
775
|
-
const batch = files.slice(i, i +
|
|
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,
|
|
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 ?
|
|
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:
|
|
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: [
|
|
816
|
-
high: [
|
|
817
|
-
medium: [
|
|
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] || [
|
|
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
|
|
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,
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
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 ===
|
|
1407
|
-
if (s ===
|
|
1408
|
-
if (s ===
|
|
1409
|
-
if (s ===
|
|
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
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
-
*
|
|
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
|
|
85
|
-
|
|
79
|
+
declare function groupDuplicatesByFilePair(duplicates: DuplicatePattern[]): DuplicateGroup[];
|
|
86
80
|
/**
|
|
87
|
-
*
|
|
81
|
+
* Create clusters of highly related files (refactor targets)
|
|
82
|
+
* Uses a simple connected components algorithm
|
|
88
83
|
*/
|
|
89
|
-
declare
|
|
90
|
-
|
|
84
|
+
declare function createRefactorClusters(duplicates: DuplicatePattern[]): RefactorCluster[];
|
|
91
85
|
/**
|
|
92
|
-
*
|
|
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
|
|
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
|
-
*
|
|
151
|
+
* Context-aware severity detection for duplicate patterns
|
|
152
|
+
* Identifies intentional duplication patterns and adjusts severity accordingly
|
|
157
153
|
*/
|
|
158
|
-
|
|
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 };
|