@aiready/pattern-detect 0.12.3 → 0.14.0
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-AJZUNNFH.mjs +817 -0
- package/dist/chunk-KDWGWBP5.mjs +832 -0
- package/dist/chunk-MH6LBXZF.mjs +816 -0
- package/dist/chunk-SNSDVGWW.mjs +783 -0
- package/dist/cli.js +167 -21
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +60 -13
- package/dist/index.mjs +3 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ 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
|
|
30
|
+
var import_core6 = require("@aiready/core");
|
|
31
31
|
|
|
32
32
|
// src/detector.ts
|
|
33
33
|
var import_core2 = require("@aiready/core");
|
|
@@ -260,7 +260,9 @@ async function detectDuplicatePatterns(fileContents, options) {
|
|
|
260
260
|
const allBlocks = [];
|
|
261
261
|
for (const { file, content } of fileContents) {
|
|
262
262
|
const blocks = extractBlocks(file, content);
|
|
263
|
-
allBlocks.push(
|
|
263
|
+
allBlocks.push(
|
|
264
|
+
...blocks.filter((b) => b.endLine - b.startLine + 1 >= minLines)
|
|
265
|
+
);
|
|
264
266
|
}
|
|
265
267
|
const duplicates = [];
|
|
266
268
|
for (let i = 0; i < allBlocks.length; i++) {
|
|
@@ -385,7 +387,10 @@ function createRefactorClusters(duplicates) {
|
|
|
385
387
|
const componentDups = duplicates.filter(
|
|
386
388
|
(d) => component.includes(d.file1) && component.includes(d.file2)
|
|
387
389
|
);
|
|
388
|
-
const totalTokenCost = componentDups.reduce(
|
|
390
|
+
const totalTokenCost = componentDups.reduce(
|
|
391
|
+
(sum, d) => sum + d.tokenCost,
|
|
392
|
+
0
|
|
393
|
+
);
|
|
389
394
|
const avgSimilarity = componentDups.reduce((sum, d) => sum + d.similarity, 0) / Math.max(1, componentDups.length);
|
|
390
395
|
const name = determineClusterName(component);
|
|
391
396
|
const { severity, reason, suggestion } = calculateSeverity(
|
|
@@ -414,7 +419,8 @@ function createRefactorClusters(duplicates) {
|
|
|
414
419
|
function determineClusterName(files) {
|
|
415
420
|
if (files.length === 0) return "Unknown Cluster";
|
|
416
421
|
if (files.some((f) => f.includes("blog"))) return "Blog SEO Boilerplate";
|
|
417
|
-
if (files.some((f) => f.includes("buttons")))
|
|
422
|
+
if (files.some((f) => f.includes("buttons")))
|
|
423
|
+
return "Button Component Variants";
|
|
418
424
|
if (files.some((f) => f.includes("cards"))) return "Card Component Variants";
|
|
419
425
|
if (files.some((f) => f.includes("login.test"))) return "E2E Test Patterns";
|
|
420
426
|
const first = files[0];
|
|
@@ -432,8 +438,148 @@ function filterClustersByImpact(clusters, minTokenCost = 1e3, minFiles = 3) {
|
|
|
432
438
|
|
|
433
439
|
// src/scoring.ts
|
|
434
440
|
var import_core4 = require("@aiready/core");
|
|
441
|
+
function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
442
|
+
const totalDuplicates = duplicates.length;
|
|
443
|
+
const totalTokenCost = duplicates.reduce((sum, d) => sum + d.tokenCost, 0);
|
|
444
|
+
const highImpactDuplicates = duplicates.filter(
|
|
445
|
+
(d) => d.tokenCost > 1e3 || d.similarity > 0.7
|
|
446
|
+
).length;
|
|
447
|
+
if (totalFilesAnalyzed === 0) {
|
|
448
|
+
return {
|
|
449
|
+
toolName: import_core4.ToolName.PatternDetect,
|
|
450
|
+
score: 100,
|
|
451
|
+
rawMetrics: {
|
|
452
|
+
totalDuplicates: 0,
|
|
453
|
+
totalTokenCost: 0,
|
|
454
|
+
highImpactDuplicates: 0,
|
|
455
|
+
totalFilesAnalyzed: 0
|
|
456
|
+
},
|
|
457
|
+
factors: [],
|
|
458
|
+
recommendations: []
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const duplicatesPerFile = totalDuplicates / totalFilesAnalyzed * 100;
|
|
462
|
+
const tokenWastePerFile = totalTokenCost / totalFilesAnalyzed;
|
|
463
|
+
const duplicatesPenalty = Math.min(60, duplicatesPerFile * 0.6);
|
|
464
|
+
const tokenPenalty = Math.min(40, tokenWastePerFile / 125);
|
|
465
|
+
const highImpactPenalty = highImpactDuplicates > 0 ? Math.min(15, highImpactDuplicates * 2 - 5) : -5;
|
|
466
|
+
const score = 100 - duplicatesPenalty - tokenPenalty - highImpactPenalty;
|
|
467
|
+
const finalScore = Math.max(0, Math.min(100, Math.round(score)));
|
|
468
|
+
const factors = [
|
|
469
|
+
{
|
|
470
|
+
name: "Duplication Density",
|
|
471
|
+
impact: -Math.round(duplicatesPenalty),
|
|
472
|
+
description: `${duplicatesPerFile.toFixed(1)} duplicates per 100 files`
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: "Token Waste",
|
|
476
|
+
impact: -Math.round(tokenPenalty),
|
|
477
|
+
description: `${Math.round(tokenWastePerFile)} tokens wasted per file`
|
|
478
|
+
}
|
|
479
|
+
];
|
|
480
|
+
if (highImpactDuplicates > 0) {
|
|
481
|
+
factors.push({
|
|
482
|
+
name: "High-Impact Patterns",
|
|
483
|
+
impact: -Math.round(highImpactPenalty),
|
|
484
|
+
description: `${highImpactDuplicates} high-impact duplicates (>1000 tokens or >70% similar)`
|
|
485
|
+
});
|
|
486
|
+
} else {
|
|
487
|
+
factors.push({
|
|
488
|
+
name: "No High-Impact Patterns",
|
|
489
|
+
impact: 5,
|
|
490
|
+
description: "No severe duplicates detected"
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
const recommendations = [];
|
|
494
|
+
if (highImpactDuplicates > 0) {
|
|
495
|
+
const estimatedImpact = Math.min(15, highImpactDuplicates * 3);
|
|
496
|
+
recommendations.push({
|
|
497
|
+
action: `Deduplicate ${highImpactDuplicates} high-impact pattern${highImpactDuplicates > 1 ? "s" : ""}`,
|
|
498
|
+
estimatedImpact,
|
|
499
|
+
priority: "high"
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
if (totalDuplicates > 10 && duplicatesPerFile > 20) {
|
|
503
|
+
const estimatedImpact = Math.min(10, Math.round(duplicatesPenalty * 0.3));
|
|
504
|
+
recommendations.push({
|
|
505
|
+
action: "Extract common patterns into shared utilities",
|
|
506
|
+
estimatedImpact,
|
|
507
|
+
priority: "medium"
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
if (tokenWastePerFile > 2e3) {
|
|
511
|
+
const estimatedImpact = Math.min(8, Math.round(tokenPenalty * 0.4));
|
|
512
|
+
recommendations.push({
|
|
513
|
+
action: "Consolidate duplicated logic to reduce AI context waste",
|
|
514
|
+
estimatedImpact,
|
|
515
|
+
priority: totalTokenCost > 1e4 ? "high" : "medium"
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
const cfg = { ...import_core4.DEFAULT_COST_CONFIG, ...costConfig };
|
|
519
|
+
const estimatedMonthlyCost = (0, import_core4.calculateMonthlyCost)(totalTokenCost, cfg);
|
|
520
|
+
const issues = duplicates.map((d) => ({
|
|
521
|
+
severity: d.severity === "critical" ? "critical" : d.severity === "major" ? "major" : "minor"
|
|
522
|
+
}));
|
|
523
|
+
const productivityImpact = (0, import_core4.calculateProductivityImpact)(issues);
|
|
524
|
+
return {
|
|
525
|
+
toolName: "pattern-detect",
|
|
526
|
+
score: finalScore,
|
|
527
|
+
rawMetrics: {
|
|
528
|
+
totalDuplicates,
|
|
529
|
+
totalTokenCost,
|
|
530
|
+
highImpactDuplicates,
|
|
531
|
+
totalFilesAnalyzed,
|
|
532
|
+
duplicatesPerFile: Math.round(duplicatesPerFile * 10) / 10,
|
|
533
|
+
tokenWastePerFile: Math.round(tokenWastePerFile),
|
|
534
|
+
// Business value metrics
|
|
535
|
+
estimatedMonthlyCost,
|
|
536
|
+
estimatedDeveloperHours: productivityImpact.totalHours
|
|
537
|
+
},
|
|
538
|
+
factors,
|
|
539
|
+
recommendations
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/provider.ts
|
|
544
|
+
var import_core5 = require("@aiready/core");
|
|
545
|
+
var PatternDetectProvider = {
|
|
546
|
+
id: import_core5.ToolName.PatternDetect,
|
|
547
|
+
alias: ["patterns", "duplicates", "duplication"],
|
|
548
|
+
async analyze(options) {
|
|
549
|
+
const results = await analyzePatterns(options);
|
|
550
|
+
return import_core5.SpokeOutputSchema.parse({
|
|
551
|
+
results: results.results,
|
|
552
|
+
summary: {
|
|
553
|
+
totalFiles: results.files.length,
|
|
554
|
+
totalIssues: results.results.reduce(
|
|
555
|
+
(sum, r) => sum + r.issues.length,
|
|
556
|
+
0
|
|
557
|
+
),
|
|
558
|
+
duplicates: results.duplicates,
|
|
559
|
+
groups: results.groups,
|
|
560
|
+
clusters: results.clusters
|
|
561
|
+
},
|
|
562
|
+
metadata: {
|
|
563
|
+
toolName: import_core5.ToolName.PatternDetect,
|
|
564
|
+
version: "0.12.5",
|
|
565
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
},
|
|
569
|
+
score(output, options) {
|
|
570
|
+
const duplicates = output.summary.duplicates || [];
|
|
571
|
+
const totalFiles = output.summary.totalFiles || output.results.length;
|
|
572
|
+
return calculatePatternScore(
|
|
573
|
+
duplicates,
|
|
574
|
+
totalFiles,
|
|
575
|
+
options.costConfig
|
|
576
|
+
);
|
|
577
|
+
},
|
|
578
|
+
defaultWeight: 22
|
|
579
|
+
};
|
|
435
580
|
|
|
436
581
|
// src/index.ts
|
|
582
|
+
import_core6.ToolRegistry.register(PatternDetectProvider);
|
|
437
583
|
function getRefactoringSuggestion(patternType, similarity) {
|
|
438
584
|
const baseMessages = {
|
|
439
585
|
"api-handler": "Extract common middleware or create a base handler class",
|
|
@@ -549,7 +695,7 @@ async function analyzePatterns(options) {
|
|
|
549
695
|
const batchContents = await Promise.all(
|
|
550
696
|
batch.map(async (file) => ({
|
|
551
697
|
file,
|
|
552
|
-
content: await (0,
|
|
698
|
+
content: await (0, import_core6.readFileContent)(file)
|
|
553
699
|
}))
|
|
554
700
|
);
|
|
555
701
|
fileContents.push(...batchContents);
|
|
@@ -570,9 +716,9 @@ async function analyzePatterns(options) {
|
|
|
570
716
|
);
|
|
571
717
|
const issues = fileDuplicates.map((dup) => {
|
|
572
718
|
const otherFile = dup.file1 === file ? dup.file2 : dup.file1;
|
|
573
|
-
const severity2 = dup.similarity > 0.95 ?
|
|
719
|
+
const severity2 = dup.similarity > 0.95 ? import_core6.Severity.Critical : dup.similarity > 0.9 ? import_core6.Severity.Major : import_core6.Severity.Minor;
|
|
574
720
|
return {
|
|
575
|
-
type:
|
|
721
|
+
type: import_core6.IssueType.DuplicatePattern,
|
|
576
722
|
severity: severity2,
|
|
577
723
|
message: `${dup.patternType} pattern ${Math.round(dup.similarity * 100)}% similar to ${otherFile} (${dup.tokenCost} tokens wasted)`,
|
|
578
724
|
location: {
|
|
@@ -585,11 +731,11 @@ async function analyzePatterns(options) {
|
|
|
585
731
|
let filteredIssues = issues;
|
|
586
732
|
if (severity !== "all") {
|
|
587
733
|
const severityMap = {
|
|
588
|
-
critical: [
|
|
589
|
-
high: [
|
|
590
|
-
medium: [
|
|
734
|
+
critical: [import_core6.Severity.Critical],
|
|
735
|
+
high: [import_core6.Severity.Critical, import_core6.Severity.Major],
|
|
736
|
+
medium: [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor]
|
|
591
737
|
};
|
|
592
|
-
const allowedSeverities = severityMap[severity] || [
|
|
738
|
+
const allowedSeverities = severityMap[severity] || [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor];
|
|
593
739
|
filteredIssues = issues.filter(
|
|
594
740
|
(issue) => allowedSeverities.includes(issue.severity)
|
|
595
741
|
);
|
|
@@ -682,7 +828,7 @@ function generateSummary(results) {
|
|
|
682
828
|
var import_chalk = __toESM(require("chalk"));
|
|
683
829
|
var import_fs = require("fs");
|
|
684
830
|
var import_path2 = require("path");
|
|
685
|
-
var
|
|
831
|
+
var import_core7 = require("@aiready/core");
|
|
686
832
|
var program = new import_commander.Command();
|
|
687
833
|
program.name("aiready-patterns").description("Detect duplicate patterns in your codebase").version("0.1.0").addHelpText(
|
|
688
834
|
"after",
|
|
@@ -736,7 +882,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
736
882
|
).option("--output-file <path>", "Output file path (for json/html)").action(async (directory, options) => {
|
|
737
883
|
console.log(import_chalk.default.blue("\u{1F50D} Analyzing patterns...\n"));
|
|
738
884
|
const startTime = Date.now();
|
|
739
|
-
const config = await (0,
|
|
885
|
+
const config = await (0, import_core7.loadConfig)(directory);
|
|
740
886
|
const defaults = {
|
|
741
887
|
minSimilarity: 0.4,
|
|
742
888
|
minLines: 5,
|
|
@@ -747,7 +893,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
747
893
|
streamResults: true,
|
|
748
894
|
include: void 0,
|
|
749
895
|
exclude: void 0,
|
|
750
|
-
minSeverity:
|
|
896
|
+
minSeverity: import_core7.Severity.Minor,
|
|
751
897
|
excludeTestFixtures: false,
|
|
752
898
|
excludeTemplates: false,
|
|
753
899
|
includeTests: false,
|
|
@@ -758,7 +904,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
758
904
|
minClusterFiles: 3,
|
|
759
905
|
showRawDuplicates: false
|
|
760
906
|
};
|
|
761
|
-
const mergedConfig = (0,
|
|
907
|
+
const mergedConfig = (0, import_core7.mergeConfigWithDefaults)(config, defaults);
|
|
762
908
|
const finalOptions = {
|
|
763
909
|
rootDir: directory,
|
|
764
910
|
minSimilarity: options.similarity ? parseFloat(options.similarity) : mergedConfig.minSimilarity,
|
|
@@ -830,7 +976,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
830
976
|
clusters: clusters || [],
|
|
831
977
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
832
978
|
};
|
|
833
|
-
const outputPath = (0,
|
|
979
|
+
const outputPath = (0, import_core7.resolveOutputPath)(
|
|
834
980
|
options.outputFile,
|
|
835
981
|
`pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
|
|
836
982
|
directory
|
|
@@ -846,7 +992,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
846
992
|
}
|
|
847
993
|
if (options.output === "html") {
|
|
848
994
|
const html = generateHTMLReport(summary, results);
|
|
849
|
-
const outputPath = (0,
|
|
995
|
+
const outputPath = (0, import_core7.resolveOutputPath)(
|
|
850
996
|
options.outputFile,
|
|
851
997
|
`pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
|
|
852
998
|
directory
|
|
@@ -1176,10 +1322,10 @@ function generateHTMLReport(summary, results) {
|
|
|
1176
1322
|
}
|
|
1177
1323
|
program.parse();
|
|
1178
1324
|
function getSeverityValue(s) {
|
|
1179
|
-
if (s ===
|
|
1180
|
-
if (s ===
|
|
1181
|
-
if (s ===
|
|
1182
|
-
if (s ===
|
|
1325
|
+
if (s === import_core7.Severity.Critical || s === "critical") return 4;
|
|
1326
|
+
if (s === import_core7.Severity.Major || s === "major") return 3;
|
|
1327
|
+
if (s === import_core7.Severity.Minor || s === "minor") return 2;
|
|
1328
|
+
if (s === import_core7.Severity.Info || s === "info") return 1;
|
|
1183
1329
|
return 0;
|
|
1184
1330
|
}
|
|
1185
1331
|
function getSeverityBadge(severity) {
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Severity, CostConfig, ToolScoringOutput, ScanOptions, AnalysisResult } from '@aiready/core';
|
|
1
|
+
import { Severity, CostConfig, ToolScoringOutput, ToolProvider, ScanOptions, AnalysisResult } from '@aiready/core';
|
|
2
2
|
export { Severity } from '@aiready/core';
|
|
3
3
|
|
|
4
4
|
type PatternType = 'api-handler' | 'validator' | 'utility' | 'class-method' | 'component' | 'function' | 'unknown';
|
|
@@ -83,6 +83,11 @@ interface RefactorCluster {
|
|
|
83
83
|
*/
|
|
84
84
|
declare function calculatePatternScore(duplicates: DuplicatePattern[], totalFilesAnalyzed: number, costConfig?: Partial<CostConfig>): ToolScoringOutput;
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Pattern Detection Tool Provider
|
|
88
|
+
*/
|
|
89
|
+
declare const PatternDetectProvider: ToolProvider;
|
|
90
|
+
|
|
86
91
|
/**
|
|
87
92
|
* Calculate severity based on context rules and code characteristics
|
|
88
93
|
*/
|
|
@@ -151,4 +156,4 @@ declare function analyzePatterns(options: PatternDetectOptions): Promise<{
|
|
|
151
156
|
*/
|
|
152
157
|
declare function generateSummary(results: AnalysisResult[]): PatternSummary;
|
|
153
158
|
|
|
154
|
-
export { type DuplicateGroup, type DuplicatePattern, type PatternDetectOptions, type PatternSummary, type PatternType, type RefactorCluster, analyzePatterns, calculatePatternScore, calculateSeverity, detectDuplicatePatterns, filterBySeverity, generateSummary, getSeverityLabel, getSmartDefaults };
|
|
159
|
+
export { type DuplicateGroup, type DuplicatePattern, type PatternDetectOptions, PatternDetectProvider, type PatternSummary, type PatternType, type RefactorCluster, analyzePatterns, calculatePatternScore, calculateSeverity, detectDuplicatePatterns, filterBySeverity, generateSummary, getSeverityLabel, getSmartDefaults };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Severity, CostConfig, ToolScoringOutput, ScanOptions, AnalysisResult } from '@aiready/core';
|
|
1
|
+
import { Severity, CostConfig, ToolScoringOutput, ToolProvider, ScanOptions, AnalysisResult } from '@aiready/core';
|
|
2
2
|
export { Severity } from '@aiready/core';
|
|
3
3
|
|
|
4
4
|
type PatternType = 'api-handler' | 'validator' | 'utility' | 'class-method' | 'component' | 'function' | 'unknown';
|
|
@@ -83,6 +83,11 @@ interface RefactorCluster {
|
|
|
83
83
|
*/
|
|
84
84
|
declare function calculatePatternScore(duplicates: DuplicatePattern[], totalFilesAnalyzed: number, costConfig?: Partial<CostConfig>): ToolScoringOutput;
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Pattern Detection Tool Provider
|
|
88
|
+
*/
|
|
89
|
+
declare const PatternDetectProvider: ToolProvider;
|
|
90
|
+
|
|
86
91
|
/**
|
|
87
92
|
* Calculate severity based on context rules and code characteristics
|
|
88
93
|
*/
|
|
@@ -151,4 +156,4 @@ declare function analyzePatterns(options: PatternDetectOptions): Promise<{
|
|
|
151
156
|
*/
|
|
152
157
|
declare function generateSummary(results: AnalysisResult[]): PatternSummary;
|
|
153
158
|
|
|
154
|
-
export { type DuplicateGroup, type DuplicatePattern, type PatternDetectOptions, type PatternSummary, type PatternType, type RefactorCluster, analyzePatterns, calculatePatternScore, calculateSeverity, detectDuplicatePatterns, filterBySeverity, generateSummary, getSeverityLabel, getSmartDefaults };
|
|
159
|
+
export { type DuplicateGroup, type DuplicatePattern, type PatternDetectOptions, PatternDetectProvider, type PatternSummary, type PatternType, type RefactorCluster, analyzePatterns, calculatePatternScore, calculateSeverity, detectDuplicatePatterns, filterBySeverity, generateSummary, getSeverityLabel, getSmartDefaults };
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
|
|
33
|
+
PatternDetectProvider: () => PatternDetectProvider,
|
|
34
|
+
Severity: () => import_core6.Severity,
|
|
34
35
|
analyzePatterns: () => analyzePatterns,
|
|
35
36
|
calculatePatternScore: () => calculatePatternScore,
|
|
36
37
|
calculateSeverity: () => calculateSeverity,
|
|
@@ -41,7 +42,7 @@ __export(index_exports, {
|
|
|
41
42
|
getSmartDefaults: () => getSmartDefaults
|
|
42
43
|
});
|
|
43
44
|
module.exports = __toCommonJS(index_exports);
|
|
44
|
-
var
|
|
45
|
+
var import_core6 = require("@aiready/core");
|
|
45
46
|
|
|
46
47
|
// src/detector.ts
|
|
47
48
|
var import_core2 = require("@aiready/core");
|
|
@@ -283,7 +284,9 @@ async function detectDuplicatePatterns(fileContents, options) {
|
|
|
283
284
|
const allBlocks = [];
|
|
284
285
|
for (const { file, content } of fileContents) {
|
|
285
286
|
const blocks = extractBlocks(file, content);
|
|
286
|
-
allBlocks.push(
|
|
287
|
+
allBlocks.push(
|
|
288
|
+
...blocks.filter((b) => b.endLine - b.startLine + 1 >= minLines)
|
|
289
|
+
);
|
|
287
290
|
}
|
|
288
291
|
const duplicates = [];
|
|
289
292
|
for (let i = 0; i < allBlocks.length; i++) {
|
|
@@ -408,7 +411,10 @@ function createRefactorClusters(duplicates) {
|
|
|
408
411
|
const componentDups = duplicates.filter(
|
|
409
412
|
(d) => component.includes(d.file1) && component.includes(d.file2)
|
|
410
413
|
);
|
|
411
|
-
const totalTokenCost = componentDups.reduce(
|
|
414
|
+
const totalTokenCost = componentDups.reduce(
|
|
415
|
+
(sum, d) => sum + d.tokenCost,
|
|
416
|
+
0
|
|
417
|
+
);
|
|
412
418
|
const avgSimilarity = componentDups.reduce((sum, d) => sum + d.similarity, 0) / Math.max(1, componentDups.length);
|
|
413
419
|
const name = determineClusterName(component);
|
|
414
420
|
const { severity, reason, suggestion } = calculateSeverity(
|
|
@@ -437,7 +443,8 @@ function createRefactorClusters(duplicates) {
|
|
|
437
443
|
function determineClusterName(files) {
|
|
438
444
|
if (files.length === 0) return "Unknown Cluster";
|
|
439
445
|
if (files.some((f) => f.includes("blog"))) return "Blog SEO Boilerplate";
|
|
440
|
-
if (files.some((f) => f.includes("buttons")))
|
|
446
|
+
if (files.some((f) => f.includes("buttons")))
|
|
447
|
+
return "Button Component Variants";
|
|
441
448
|
if (files.some((f) => f.includes("cards"))) return "Card Component Variants";
|
|
442
449
|
if (files.some((f) => f.includes("login.test"))) return "E2E Test Patterns";
|
|
443
450
|
const first = files[0];
|
|
@@ -463,7 +470,7 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
|
463
470
|
).length;
|
|
464
471
|
if (totalFilesAnalyzed === 0) {
|
|
465
472
|
return {
|
|
466
|
-
toolName:
|
|
473
|
+
toolName: import_core4.ToolName.PatternDetect,
|
|
467
474
|
score: 100,
|
|
468
475
|
rawMetrics: {
|
|
469
476
|
totalDuplicates: 0,
|
|
@@ -557,7 +564,46 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
|
557
564
|
};
|
|
558
565
|
}
|
|
559
566
|
|
|
567
|
+
// src/provider.ts
|
|
568
|
+
var import_core5 = require("@aiready/core");
|
|
569
|
+
var PatternDetectProvider = {
|
|
570
|
+
id: import_core5.ToolName.PatternDetect,
|
|
571
|
+
alias: ["patterns", "duplicates", "duplication"],
|
|
572
|
+
async analyze(options) {
|
|
573
|
+
const results = await analyzePatterns(options);
|
|
574
|
+
return import_core5.SpokeOutputSchema.parse({
|
|
575
|
+
results: results.results,
|
|
576
|
+
summary: {
|
|
577
|
+
totalFiles: results.files.length,
|
|
578
|
+
totalIssues: results.results.reduce(
|
|
579
|
+
(sum, r) => sum + r.issues.length,
|
|
580
|
+
0
|
|
581
|
+
),
|
|
582
|
+
duplicates: results.duplicates,
|
|
583
|
+
groups: results.groups,
|
|
584
|
+
clusters: results.clusters
|
|
585
|
+
},
|
|
586
|
+
metadata: {
|
|
587
|
+
toolName: import_core5.ToolName.PatternDetect,
|
|
588
|
+
version: "0.12.5",
|
|
589
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
},
|
|
593
|
+
score(output, options) {
|
|
594
|
+
const duplicates = output.summary.duplicates || [];
|
|
595
|
+
const totalFiles = output.summary.totalFiles || output.results.length;
|
|
596
|
+
return calculatePatternScore(
|
|
597
|
+
duplicates,
|
|
598
|
+
totalFiles,
|
|
599
|
+
options.costConfig
|
|
600
|
+
);
|
|
601
|
+
},
|
|
602
|
+
defaultWeight: 22
|
|
603
|
+
};
|
|
604
|
+
|
|
560
605
|
// src/index.ts
|
|
606
|
+
import_core6.ToolRegistry.register(PatternDetectProvider);
|
|
561
607
|
function getRefactoringSuggestion(patternType, similarity) {
|
|
562
608
|
const baseMessages = {
|
|
563
609
|
"api-handler": "Extract common middleware or create a base handler class",
|
|
@@ -673,7 +719,7 @@ async function analyzePatterns(options) {
|
|
|
673
719
|
const batchContents = await Promise.all(
|
|
674
720
|
batch.map(async (file) => ({
|
|
675
721
|
file,
|
|
676
|
-
content: await (0,
|
|
722
|
+
content: await (0, import_core6.readFileContent)(file)
|
|
677
723
|
}))
|
|
678
724
|
);
|
|
679
725
|
fileContents.push(...batchContents);
|
|
@@ -694,9 +740,9 @@ async function analyzePatterns(options) {
|
|
|
694
740
|
);
|
|
695
741
|
const issues = fileDuplicates.map((dup) => {
|
|
696
742
|
const otherFile = dup.file1 === file ? dup.file2 : dup.file1;
|
|
697
|
-
const severity2 = dup.similarity > 0.95 ?
|
|
743
|
+
const severity2 = dup.similarity > 0.95 ? import_core6.Severity.Critical : dup.similarity > 0.9 ? import_core6.Severity.Major : import_core6.Severity.Minor;
|
|
698
744
|
return {
|
|
699
|
-
type:
|
|
745
|
+
type: import_core6.IssueType.DuplicatePattern,
|
|
700
746
|
severity: severity2,
|
|
701
747
|
message: `${dup.patternType} pattern ${Math.round(dup.similarity * 100)}% similar to ${otherFile} (${dup.tokenCost} tokens wasted)`,
|
|
702
748
|
location: {
|
|
@@ -709,11 +755,11 @@ async function analyzePatterns(options) {
|
|
|
709
755
|
let filteredIssues = issues;
|
|
710
756
|
if (severity !== "all") {
|
|
711
757
|
const severityMap = {
|
|
712
|
-
critical: [
|
|
713
|
-
high: [
|
|
714
|
-
medium: [
|
|
758
|
+
critical: [import_core6.Severity.Critical],
|
|
759
|
+
high: [import_core6.Severity.Critical, import_core6.Severity.Major],
|
|
760
|
+
medium: [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor]
|
|
715
761
|
};
|
|
716
|
-
const allowedSeverities = severityMap[severity] || [
|
|
762
|
+
const allowedSeverities = severityMap[severity] || [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor];
|
|
717
763
|
filteredIssues = issues.filter(
|
|
718
764
|
(issue) => allowedSeverities.includes(issue.severity)
|
|
719
765
|
);
|
|
@@ -803,6 +849,7 @@ function generateSummary(results) {
|
|
|
803
849
|
}
|
|
804
850
|
// Annotate the CommonJS export names for ESM import in node:
|
|
805
851
|
0 && (module.exports = {
|
|
852
|
+
PatternDetectProvider,
|
|
806
853
|
Severity,
|
|
807
854
|
analyzePatterns,
|
|
808
855
|
calculatePatternScore,
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
PatternDetectProvider,
|
|
2
3
|
Severity,
|
|
3
4
|
analyzePatterns,
|
|
4
5
|
calculatePatternScore,
|
|
@@ -8,8 +9,9 @@ import {
|
|
|
8
9
|
generateSummary,
|
|
9
10
|
getSeverityLabel,
|
|
10
11
|
getSmartDefaults
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-KDWGWBP5.mjs";
|
|
12
13
|
export {
|
|
14
|
+
PatternDetectProvider,
|
|
13
15
|
Severity,
|
|
14
16
|
analyzePatterns,
|
|
15
17
|
calculatePatternScore,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/pattern-detect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
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.
|
|
48
|
+
"@aiready/core": "0.21.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"tsup": "^8.3.5",
|