@aiready/pattern-detect 0.12.5 → 0.14.1
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-3WK24ZOX.mjs +860 -0
- package/dist/chunk-AJZUNNFH.mjs +817 -0
- package/dist/chunk-KDWGWBP5.mjs +832 -0
- package/dist/chunk-MH6LBXZF.mjs +816 -0
- package/dist/cli.js +187 -19
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +80 -11
- 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");
|
|
@@ -256,7 +256,7 @@ function calculateSimilarity(a, b) {
|
|
|
256
256
|
return intersection.size / union.size;
|
|
257
257
|
}
|
|
258
258
|
async function detectDuplicatePatterns(fileContents, options) {
|
|
259
|
-
const { minSimilarity, minLines, streamResults } = options;
|
|
259
|
+
const { minSimilarity, minLines, streamResults, onProgress } = options;
|
|
260
260
|
const allBlocks = [];
|
|
261
261
|
for (const { file, content } of fileContents) {
|
|
262
262
|
const blocks = extractBlocks(file, content);
|
|
@@ -265,8 +265,29 @@ async function detectDuplicatePatterns(fileContents, options) {
|
|
|
265
265
|
);
|
|
266
266
|
}
|
|
267
267
|
const duplicates = [];
|
|
268
|
+
const totalBlocks = allBlocks.length;
|
|
269
|
+
let comparisons = 0;
|
|
270
|
+
const totalComparisons = totalBlocks * (totalBlocks - 1) / 2;
|
|
271
|
+
if (onProgress) {
|
|
272
|
+
onProgress(
|
|
273
|
+
0,
|
|
274
|
+
totalComparisons,
|
|
275
|
+
`Starting duplicate detection on ${totalBlocks} blocks...`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
268
278
|
for (let i = 0; i < allBlocks.length; i++) {
|
|
279
|
+
if (i % 50 === 0 && i > 0) {
|
|
280
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
281
|
+
if (onProgress) {
|
|
282
|
+
onProgress(
|
|
283
|
+
comparisons,
|
|
284
|
+
totalComparisons,
|
|
285
|
+
`Analyzing blocks (${i}/${totalBlocks})...`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
269
289
|
for (let j = i + 1; j < allBlocks.length; j++) {
|
|
290
|
+
comparisons++;
|
|
270
291
|
const b1 = allBlocks[i];
|
|
271
292
|
const b2 = allBlocks[j];
|
|
272
293
|
if (b1.file === b2.file) continue;
|
|
@@ -306,6 +327,13 @@ async function detectDuplicatePatterns(fileContents, options) {
|
|
|
306
327
|
}
|
|
307
328
|
}
|
|
308
329
|
}
|
|
330
|
+
if (onProgress) {
|
|
331
|
+
onProgress(
|
|
332
|
+
totalComparisons,
|
|
333
|
+
totalComparisons,
|
|
334
|
+
`Duplicate detection complete. Found ${duplicates.length} patterns.`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
309
337
|
return duplicates.sort((a, b) => b.similarity - a.similarity);
|
|
310
338
|
}
|
|
311
339
|
|
|
@@ -438,8 +466,148 @@ function filterClustersByImpact(clusters, minTokenCost = 1e3, minFiles = 3) {
|
|
|
438
466
|
|
|
439
467
|
// src/scoring.ts
|
|
440
468
|
var import_core4 = require("@aiready/core");
|
|
469
|
+
function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
470
|
+
const totalDuplicates = duplicates.length;
|
|
471
|
+
const totalTokenCost = duplicates.reduce((sum, d) => sum + d.tokenCost, 0);
|
|
472
|
+
const highImpactDuplicates = duplicates.filter(
|
|
473
|
+
(d) => d.tokenCost > 1e3 || d.similarity > 0.7
|
|
474
|
+
).length;
|
|
475
|
+
if (totalFilesAnalyzed === 0) {
|
|
476
|
+
return {
|
|
477
|
+
toolName: import_core4.ToolName.PatternDetect,
|
|
478
|
+
score: 100,
|
|
479
|
+
rawMetrics: {
|
|
480
|
+
totalDuplicates: 0,
|
|
481
|
+
totalTokenCost: 0,
|
|
482
|
+
highImpactDuplicates: 0,
|
|
483
|
+
totalFilesAnalyzed: 0
|
|
484
|
+
},
|
|
485
|
+
factors: [],
|
|
486
|
+
recommendations: []
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
const duplicatesPerFile = totalDuplicates / totalFilesAnalyzed * 100;
|
|
490
|
+
const tokenWastePerFile = totalTokenCost / totalFilesAnalyzed;
|
|
491
|
+
const duplicatesPenalty = Math.min(60, duplicatesPerFile * 0.6);
|
|
492
|
+
const tokenPenalty = Math.min(40, tokenWastePerFile / 125);
|
|
493
|
+
const highImpactPenalty = highImpactDuplicates > 0 ? Math.min(15, highImpactDuplicates * 2 - 5) : -5;
|
|
494
|
+
const score = 100 - duplicatesPenalty - tokenPenalty - highImpactPenalty;
|
|
495
|
+
const finalScore = Math.max(0, Math.min(100, Math.round(score)));
|
|
496
|
+
const factors = [
|
|
497
|
+
{
|
|
498
|
+
name: "Duplication Density",
|
|
499
|
+
impact: -Math.round(duplicatesPenalty),
|
|
500
|
+
description: `${duplicatesPerFile.toFixed(1)} duplicates per 100 files`
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: "Token Waste",
|
|
504
|
+
impact: -Math.round(tokenPenalty),
|
|
505
|
+
description: `${Math.round(tokenWastePerFile)} tokens wasted per file`
|
|
506
|
+
}
|
|
507
|
+
];
|
|
508
|
+
if (highImpactDuplicates > 0) {
|
|
509
|
+
factors.push({
|
|
510
|
+
name: "High-Impact Patterns",
|
|
511
|
+
impact: -Math.round(highImpactPenalty),
|
|
512
|
+
description: `${highImpactDuplicates} high-impact duplicates (>1000 tokens or >70% similar)`
|
|
513
|
+
});
|
|
514
|
+
} else {
|
|
515
|
+
factors.push({
|
|
516
|
+
name: "No High-Impact Patterns",
|
|
517
|
+
impact: 5,
|
|
518
|
+
description: "No severe duplicates detected"
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
const recommendations = [];
|
|
522
|
+
if (highImpactDuplicates > 0) {
|
|
523
|
+
const estimatedImpact = Math.min(15, highImpactDuplicates * 3);
|
|
524
|
+
recommendations.push({
|
|
525
|
+
action: `Deduplicate ${highImpactDuplicates} high-impact pattern${highImpactDuplicates > 1 ? "s" : ""}`,
|
|
526
|
+
estimatedImpact,
|
|
527
|
+
priority: "high"
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
if (totalDuplicates > 10 && duplicatesPerFile > 20) {
|
|
531
|
+
const estimatedImpact = Math.min(10, Math.round(duplicatesPenalty * 0.3));
|
|
532
|
+
recommendations.push({
|
|
533
|
+
action: "Extract common patterns into shared utilities",
|
|
534
|
+
estimatedImpact,
|
|
535
|
+
priority: "medium"
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
if (tokenWastePerFile > 2e3) {
|
|
539
|
+
const estimatedImpact = Math.min(8, Math.round(tokenPenalty * 0.4));
|
|
540
|
+
recommendations.push({
|
|
541
|
+
action: "Consolidate duplicated logic to reduce AI context waste",
|
|
542
|
+
estimatedImpact,
|
|
543
|
+
priority: totalTokenCost > 1e4 ? "high" : "medium"
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
const cfg = { ...import_core4.DEFAULT_COST_CONFIG, ...costConfig };
|
|
547
|
+
const estimatedMonthlyCost = (0, import_core4.calculateMonthlyCost)(totalTokenCost, cfg);
|
|
548
|
+
const issues = duplicates.map((d) => ({
|
|
549
|
+
severity: d.severity === "critical" ? "critical" : d.severity === "major" ? "major" : "minor"
|
|
550
|
+
}));
|
|
551
|
+
const productivityImpact = (0, import_core4.calculateProductivityImpact)(issues);
|
|
552
|
+
return {
|
|
553
|
+
toolName: "pattern-detect",
|
|
554
|
+
score: finalScore,
|
|
555
|
+
rawMetrics: {
|
|
556
|
+
totalDuplicates,
|
|
557
|
+
totalTokenCost,
|
|
558
|
+
highImpactDuplicates,
|
|
559
|
+
totalFilesAnalyzed,
|
|
560
|
+
duplicatesPerFile: Math.round(duplicatesPerFile * 10) / 10,
|
|
561
|
+
tokenWastePerFile: Math.round(tokenWastePerFile),
|
|
562
|
+
// Business value metrics
|
|
563
|
+
estimatedMonthlyCost,
|
|
564
|
+
estimatedDeveloperHours: productivityImpact.totalHours
|
|
565
|
+
},
|
|
566
|
+
factors,
|
|
567
|
+
recommendations
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/provider.ts
|
|
572
|
+
var import_core5 = require("@aiready/core");
|
|
573
|
+
var PatternDetectProvider = {
|
|
574
|
+
id: import_core5.ToolName.PatternDetect,
|
|
575
|
+
alias: ["patterns", "duplicates", "duplication"],
|
|
576
|
+
async analyze(options) {
|
|
577
|
+
const results = await analyzePatterns(options);
|
|
578
|
+
return import_core5.SpokeOutputSchema.parse({
|
|
579
|
+
results: results.results,
|
|
580
|
+
summary: {
|
|
581
|
+
totalFiles: results.files.length,
|
|
582
|
+
totalIssues: results.results.reduce(
|
|
583
|
+
(sum, r) => sum + r.issues.length,
|
|
584
|
+
0
|
|
585
|
+
),
|
|
586
|
+
duplicates: results.duplicates,
|
|
587
|
+
groups: results.groups,
|
|
588
|
+
clusters: results.clusters
|
|
589
|
+
},
|
|
590
|
+
metadata: {
|
|
591
|
+
toolName: import_core5.ToolName.PatternDetect,
|
|
592
|
+
version: "0.12.5",
|
|
593
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
},
|
|
597
|
+
score(output, options) {
|
|
598
|
+
const duplicates = output.summary.duplicates || [];
|
|
599
|
+
const totalFiles = output.summary.totalFiles || output.results.length;
|
|
600
|
+
return calculatePatternScore(
|
|
601
|
+
duplicates,
|
|
602
|
+
totalFiles,
|
|
603
|
+
options.costConfig
|
|
604
|
+
);
|
|
605
|
+
},
|
|
606
|
+
defaultWeight: 22
|
|
607
|
+
};
|
|
441
608
|
|
|
442
609
|
// src/index.ts
|
|
610
|
+
import_core6.ToolRegistry.register(PatternDetectProvider);
|
|
443
611
|
function getRefactoringSuggestion(patternType, similarity) {
|
|
444
612
|
const baseMessages = {
|
|
445
613
|
"api-handler": "Extract common middleware or create a base handler class",
|
|
@@ -555,7 +723,7 @@ async function analyzePatterns(options) {
|
|
|
555
723
|
const batchContents = await Promise.all(
|
|
556
724
|
batch.map(async (file) => ({
|
|
557
725
|
file,
|
|
558
|
-
content: await (0,
|
|
726
|
+
content: await (0, import_core6.readFileContent)(file)
|
|
559
727
|
}))
|
|
560
728
|
);
|
|
561
729
|
fileContents.push(...batchContents);
|
|
@@ -576,9 +744,9 @@ async function analyzePatterns(options) {
|
|
|
576
744
|
);
|
|
577
745
|
const issues = fileDuplicates.map((dup) => {
|
|
578
746
|
const otherFile = dup.file1 === file ? dup.file2 : dup.file1;
|
|
579
|
-
const severity2 = dup.similarity > 0.95 ?
|
|
747
|
+
const severity2 = dup.similarity > 0.95 ? import_core6.Severity.Critical : dup.similarity > 0.9 ? import_core6.Severity.Major : import_core6.Severity.Minor;
|
|
580
748
|
return {
|
|
581
|
-
type:
|
|
749
|
+
type: import_core6.IssueType.DuplicatePattern,
|
|
582
750
|
severity: severity2,
|
|
583
751
|
message: `${dup.patternType} pattern ${Math.round(dup.similarity * 100)}% similar to ${otherFile} (${dup.tokenCost} tokens wasted)`,
|
|
584
752
|
location: {
|
|
@@ -591,11 +759,11 @@ async function analyzePatterns(options) {
|
|
|
591
759
|
let filteredIssues = issues;
|
|
592
760
|
if (severity !== "all") {
|
|
593
761
|
const severityMap = {
|
|
594
|
-
critical: [
|
|
595
|
-
high: [
|
|
596
|
-
medium: [
|
|
762
|
+
critical: [import_core6.Severity.Critical],
|
|
763
|
+
high: [import_core6.Severity.Critical, import_core6.Severity.Major],
|
|
764
|
+
medium: [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor]
|
|
597
765
|
};
|
|
598
|
-
const allowedSeverities = severityMap[severity] || [
|
|
766
|
+
const allowedSeverities = severityMap[severity] || [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor];
|
|
599
767
|
filteredIssues = issues.filter(
|
|
600
768
|
(issue) => allowedSeverities.includes(issue.severity)
|
|
601
769
|
);
|
|
@@ -688,7 +856,7 @@ function generateSummary(results) {
|
|
|
688
856
|
var import_chalk = __toESM(require("chalk"));
|
|
689
857
|
var import_fs = require("fs");
|
|
690
858
|
var import_path2 = require("path");
|
|
691
|
-
var
|
|
859
|
+
var import_core7 = require("@aiready/core");
|
|
692
860
|
var program = new import_commander.Command();
|
|
693
861
|
program.name("aiready-patterns").description("Detect duplicate patterns in your codebase").version("0.1.0").addHelpText(
|
|
694
862
|
"after",
|
|
@@ -742,7 +910,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
742
910
|
).option("--output-file <path>", "Output file path (for json/html)").action(async (directory, options) => {
|
|
743
911
|
console.log(import_chalk.default.blue("\u{1F50D} Analyzing patterns...\n"));
|
|
744
912
|
const startTime = Date.now();
|
|
745
|
-
const config = await (0,
|
|
913
|
+
const config = await (0, import_core7.loadConfig)(directory);
|
|
746
914
|
const defaults = {
|
|
747
915
|
minSimilarity: 0.4,
|
|
748
916
|
minLines: 5,
|
|
@@ -753,7 +921,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
753
921
|
streamResults: true,
|
|
754
922
|
include: void 0,
|
|
755
923
|
exclude: void 0,
|
|
756
|
-
minSeverity:
|
|
924
|
+
minSeverity: import_core7.Severity.Minor,
|
|
757
925
|
excludeTestFixtures: false,
|
|
758
926
|
excludeTemplates: false,
|
|
759
927
|
includeTests: false,
|
|
@@ -764,7 +932,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
764
932
|
minClusterFiles: 3,
|
|
765
933
|
showRawDuplicates: false
|
|
766
934
|
};
|
|
767
|
-
const mergedConfig = (0,
|
|
935
|
+
const mergedConfig = (0, import_core7.mergeConfigWithDefaults)(config, defaults);
|
|
768
936
|
const finalOptions = {
|
|
769
937
|
rootDir: directory,
|
|
770
938
|
minSimilarity: options.similarity ? parseFloat(options.similarity) : mergedConfig.minSimilarity,
|
|
@@ -836,7 +1004,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
836
1004
|
clusters: clusters || [],
|
|
837
1005
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
838
1006
|
};
|
|
839
|
-
const outputPath = (0,
|
|
1007
|
+
const outputPath = (0, import_core7.resolveOutputPath)(
|
|
840
1008
|
options.outputFile,
|
|
841
1009
|
`pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
|
|
842
1010
|
directory
|
|
@@ -852,7 +1020,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
852
1020
|
}
|
|
853
1021
|
if (options.output === "html") {
|
|
854
1022
|
const html = generateHTMLReport(summary, results);
|
|
855
|
-
const outputPath = (0,
|
|
1023
|
+
const outputPath = (0, import_core7.resolveOutputPath)(
|
|
856
1024
|
options.outputFile,
|
|
857
1025
|
`pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
|
|
858
1026
|
directory
|
|
@@ -1182,10 +1350,10 @@ function generateHTMLReport(summary, results) {
|
|
|
1182
1350
|
}
|
|
1183
1351
|
program.parse();
|
|
1184
1352
|
function getSeverityValue(s) {
|
|
1185
|
-
if (s ===
|
|
1186
|
-
if (s ===
|
|
1187
|
-
if (s ===
|
|
1188
|
-
if (s ===
|
|
1353
|
+
if (s === import_core7.Severity.Critical || s === "critical") return 4;
|
|
1354
|
+
if (s === import_core7.Severity.Major || s === "major") return 3;
|
|
1355
|
+
if (s === import_core7.Severity.Minor || s === "minor") return 2;
|
|
1356
|
+
if (s === import_core7.Severity.Info || s === "info") return 1;
|
|
1189
1357
|
return 0;
|
|
1190
1358
|
}
|
|
1191
1359
|
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");
|
|
@@ -279,7 +280,7 @@ function calculateSimilarity(a, b) {
|
|
|
279
280
|
return intersection.size / union.size;
|
|
280
281
|
}
|
|
281
282
|
async function detectDuplicatePatterns(fileContents, options) {
|
|
282
|
-
const { minSimilarity, minLines, streamResults } = options;
|
|
283
|
+
const { minSimilarity, minLines, streamResults, onProgress } = options;
|
|
283
284
|
const allBlocks = [];
|
|
284
285
|
for (const { file, content } of fileContents) {
|
|
285
286
|
const blocks = extractBlocks(file, content);
|
|
@@ -288,8 +289,29 @@ async function detectDuplicatePatterns(fileContents, options) {
|
|
|
288
289
|
);
|
|
289
290
|
}
|
|
290
291
|
const duplicates = [];
|
|
292
|
+
const totalBlocks = allBlocks.length;
|
|
293
|
+
let comparisons = 0;
|
|
294
|
+
const totalComparisons = totalBlocks * (totalBlocks - 1) / 2;
|
|
295
|
+
if (onProgress) {
|
|
296
|
+
onProgress(
|
|
297
|
+
0,
|
|
298
|
+
totalComparisons,
|
|
299
|
+
`Starting duplicate detection on ${totalBlocks} blocks...`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
291
302
|
for (let i = 0; i < allBlocks.length; i++) {
|
|
303
|
+
if (i % 50 === 0 && i > 0) {
|
|
304
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
305
|
+
if (onProgress) {
|
|
306
|
+
onProgress(
|
|
307
|
+
comparisons,
|
|
308
|
+
totalComparisons,
|
|
309
|
+
`Analyzing blocks (${i}/${totalBlocks})...`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
292
313
|
for (let j = i + 1; j < allBlocks.length; j++) {
|
|
314
|
+
comparisons++;
|
|
293
315
|
const b1 = allBlocks[i];
|
|
294
316
|
const b2 = allBlocks[j];
|
|
295
317
|
if (b1.file === b2.file) continue;
|
|
@@ -329,6 +351,13 @@ async function detectDuplicatePatterns(fileContents, options) {
|
|
|
329
351
|
}
|
|
330
352
|
}
|
|
331
353
|
}
|
|
354
|
+
if (onProgress) {
|
|
355
|
+
onProgress(
|
|
356
|
+
totalComparisons,
|
|
357
|
+
totalComparisons,
|
|
358
|
+
`Duplicate detection complete. Found ${duplicates.length} patterns.`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
332
361
|
return duplicates.sort((a, b) => b.similarity - a.similarity);
|
|
333
362
|
}
|
|
334
363
|
|
|
@@ -469,7 +498,7 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
|
469
498
|
).length;
|
|
470
499
|
if (totalFilesAnalyzed === 0) {
|
|
471
500
|
return {
|
|
472
|
-
toolName:
|
|
501
|
+
toolName: import_core4.ToolName.PatternDetect,
|
|
473
502
|
score: 100,
|
|
474
503
|
rawMetrics: {
|
|
475
504
|
totalDuplicates: 0,
|
|
@@ -563,7 +592,46 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
|
563
592
|
};
|
|
564
593
|
}
|
|
565
594
|
|
|
595
|
+
// src/provider.ts
|
|
596
|
+
var import_core5 = require("@aiready/core");
|
|
597
|
+
var PatternDetectProvider = {
|
|
598
|
+
id: import_core5.ToolName.PatternDetect,
|
|
599
|
+
alias: ["patterns", "duplicates", "duplication"],
|
|
600
|
+
async analyze(options) {
|
|
601
|
+
const results = await analyzePatterns(options);
|
|
602
|
+
return import_core5.SpokeOutputSchema.parse({
|
|
603
|
+
results: results.results,
|
|
604
|
+
summary: {
|
|
605
|
+
totalFiles: results.files.length,
|
|
606
|
+
totalIssues: results.results.reduce(
|
|
607
|
+
(sum, r) => sum + r.issues.length,
|
|
608
|
+
0
|
|
609
|
+
),
|
|
610
|
+
duplicates: results.duplicates,
|
|
611
|
+
groups: results.groups,
|
|
612
|
+
clusters: results.clusters
|
|
613
|
+
},
|
|
614
|
+
metadata: {
|
|
615
|
+
toolName: import_core5.ToolName.PatternDetect,
|
|
616
|
+
version: "0.12.5",
|
|
617
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
},
|
|
621
|
+
score(output, options) {
|
|
622
|
+
const duplicates = output.summary.duplicates || [];
|
|
623
|
+
const totalFiles = output.summary.totalFiles || output.results.length;
|
|
624
|
+
return calculatePatternScore(
|
|
625
|
+
duplicates,
|
|
626
|
+
totalFiles,
|
|
627
|
+
options.costConfig
|
|
628
|
+
);
|
|
629
|
+
},
|
|
630
|
+
defaultWeight: 22
|
|
631
|
+
};
|
|
632
|
+
|
|
566
633
|
// src/index.ts
|
|
634
|
+
import_core6.ToolRegistry.register(PatternDetectProvider);
|
|
567
635
|
function getRefactoringSuggestion(patternType, similarity) {
|
|
568
636
|
const baseMessages = {
|
|
569
637
|
"api-handler": "Extract common middleware or create a base handler class",
|
|
@@ -679,7 +747,7 @@ async function analyzePatterns(options) {
|
|
|
679
747
|
const batchContents = await Promise.all(
|
|
680
748
|
batch.map(async (file) => ({
|
|
681
749
|
file,
|
|
682
|
-
content: await (0,
|
|
750
|
+
content: await (0, import_core6.readFileContent)(file)
|
|
683
751
|
}))
|
|
684
752
|
);
|
|
685
753
|
fileContents.push(...batchContents);
|
|
@@ -700,9 +768,9 @@ async function analyzePatterns(options) {
|
|
|
700
768
|
);
|
|
701
769
|
const issues = fileDuplicates.map((dup) => {
|
|
702
770
|
const otherFile = dup.file1 === file ? dup.file2 : dup.file1;
|
|
703
|
-
const severity2 = dup.similarity > 0.95 ?
|
|
771
|
+
const severity2 = dup.similarity > 0.95 ? import_core6.Severity.Critical : dup.similarity > 0.9 ? import_core6.Severity.Major : import_core6.Severity.Minor;
|
|
704
772
|
return {
|
|
705
|
-
type:
|
|
773
|
+
type: import_core6.IssueType.DuplicatePattern,
|
|
706
774
|
severity: severity2,
|
|
707
775
|
message: `${dup.patternType} pattern ${Math.round(dup.similarity * 100)}% similar to ${otherFile} (${dup.tokenCost} tokens wasted)`,
|
|
708
776
|
location: {
|
|
@@ -715,11 +783,11 @@ async function analyzePatterns(options) {
|
|
|
715
783
|
let filteredIssues = issues;
|
|
716
784
|
if (severity !== "all") {
|
|
717
785
|
const severityMap = {
|
|
718
|
-
critical: [
|
|
719
|
-
high: [
|
|
720
|
-
medium: [
|
|
786
|
+
critical: [import_core6.Severity.Critical],
|
|
787
|
+
high: [import_core6.Severity.Critical, import_core6.Severity.Major],
|
|
788
|
+
medium: [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor]
|
|
721
789
|
};
|
|
722
|
-
const allowedSeverities = severityMap[severity] || [
|
|
790
|
+
const allowedSeverities = severityMap[severity] || [import_core6.Severity.Critical, import_core6.Severity.Major, import_core6.Severity.Minor];
|
|
723
791
|
filteredIssues = issues.filter(
|
|
724
792
|
(issue) => allowedSeverities.includes(issue.severity)
|
|
725
793
|
);
|
|
@@ -809,6 +877,7 @@ function generateSummary(results) {
|
|
|
809
877
|
}
|
|
810
878
|
// Annotate the CommonJS export names for ESM import in node:
|
|
811
879
|
0 && (module.exports = {
|
|
880
|
+
PatternDetectProvider,
|
|
812
881
|
Severity,
|
|
813
882
|
analyzePatterns,
|
|
814
883
|
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-3WK24ZOX.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.1",
|
|
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.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"tsup": "^8.3.5",
|