@aiready/pattern-detect 0.16.2 → 0.16.3
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-4UHDGB7U.mjs +920 -0
- package/dist/chunk-KPEK5REL.mjs +919 -0
- package/dist/cli.js +118 -134
- package/dist/cli.mjs +115 -121
- package/dist/index.d.mts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +3 -11
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -197,7 +197,7 @@ function extractBlocks(file, content) {
|
|
|
197
197
|
}
|
|
198
198
|
const blocks = [];
|
|
199
199
|
const lines = content.split("\n");
|
|
200
|
-
const blockRegex = /^\s*(?:export\s+)?(?:async\s+)?(?:public\s+|private\s+|protected\s+|internal\s+|static\s+|readonly\s+|virtual\s+|abstract\s+|override\s+)*(function|class|interface|type|enum|record|struct|void|func|[a-zA-Z0-9_
|
|
200
|
+
const blockRegex = /^\s*(?:export\s+)?(?:async\s+)?(?:public\s+|private\s+|protected\s+|internal\s+|static\s+|readonly\s+|virtual\s+|abstract\s+|override\s+)*(function|class|interface|type|enum|record|struct|void|func|[a-zA-Z0-9_<>[]]+)\s+([a-zA-Z0-9_]+)(?:\s*\(|(?:\s+extends|\s+implements|\s+where)?\s*\{)|^\s*(?:export\s+)?const\s+([a-zA-Z0-9_]+)\s*=\s*[a-zA-Z0-9_.]+\.object\(|^\s*(app\.(?:get|post|put|delete|patch|use))\(/gm;
|
|
201
201
|
let match;
|
|
202
202
|
while ((match = blockRegex.exec(content)) !== null) {
|
|
203
203
|
const startLine = content.substring(0, match.index).split("\n").length;
|
|
@@ -399,13 +399,6 @@ async function detectDuplicatePatterns(fileContents, options) {
|
|
|
399
399
|
// src/grouping.ts
|
|
400
400
|
var import_core3 = require("@aiready/core");
|
|
401
401
|
var import_path = __toESM(require("path"));
|
|
402
|
-
function getSeverityLevel(s) {
|
|
403
|
-
if (s === import_core3.Severity.Critical || s === "critical") return 4;
|
|
404
|
-
if (s === import_core3.Severity.Major || s === "major") return 3;
|
|
405
|
-
if (s === import_core3.Severity.Minor || s === "minor") return 2;
|
|
406
|
-
if (s === import_core3.Severity.Info || s === "info") return 1;
|
|
407
|
-
return 0;
|
|
408
|
-
}
|
|
409
402
|
function groupDuplicatesByFilePair(duplicates) {
|
|
410
403
|
const groups = /* @__PURE__ */ new Map();
|
|
411
404
|
for (const dup of duplicates) {
|
|
@@ -432,7 +425,7 @@ function groupDuplicatesByFilePair(duplicates) {
|
|
|
432
425
|
file2: { start: dup.line2, end: dup.endLine2 }
|
|
433
426
|
});
|
|
434
427
|
const currentSev = dup.severity;
|
|
435
|
-
if (getSeverityLevel(currentSev) > getSeverityLevel(group.severity)) {
|
|
428
|
+
if ((0, import_core3.getSeverityLevel)(currentSev) > (0, import_core3.getSeverityLevel)(group.severity)) {
|
|
436
429
|
group.severity = currentSev;
|
|
437
430
|
}
|
|
438
431
|
}
|
|
@@ -584,7 +577,7 @@ async function getSmartDefaults(directory, userOptions) {
|
|
|
584
577
|
includeTests: false
|
|
585
578
|
};
|
|
586
579
|
const result = { ...defaults };
|
|
587
|
-
for (const
|
|
580
|
+
for (const key of Object.keys(defaults)) {
|
|
588
581
|
if (key in userOptions && userOptions[key] !== void 0) {
|
|
589
582
|
result[key] = userOptions[key];
|
|
590
583
|
}
|
|
@@ -616,7 +609,6 @@ async function analyzePatterns(options) {
|
|
|
616
609
|
maxCandidatesPerBlock = 100,
|
|
617
610
|
streamResults = false,
|
|
618
611
|
severity = "all",
|
|
619
|
-
includeTests = false,
|
|
620
612
|
groupByFilePair = true,
|
|
621
613
|
createClusters = true,
|
|
622
614
|
minClusterTokenCost = 1e3,
|
|
@@ -913,7 +905,108 @@ import_core7.ToolRegistry.register(PatternDetectProvider);
|
|
|
913
905
|
var import_chalk = __toESM(require("chalk"));
|
|
914
906
|
var import_fs = require("fs");
|
|
915
907
|
var import_path2 = require("path");
|
|
908
|
+
var import_core9 = require("@aiready/core");
|
|
909
|
+
|
|
910
|
+
// src/cli-output.ts
|
|
916
911
|
var import_core8 = require("@aiready/core");
|
|
912
|
+
function getPatternIcon(type) {
|
|
913
|
+
const icons = {
|
|
914
|
+
"api-handler": "\u{1F50C}",
|
|
915
|
+
validator: "\u{1F6E1}\uFE0F",
|
|
916
|
+
utility: "\u2699\uFE0F",
|
|
917
|
+
"class-method": "\u{1F3DB}\uFE0F",
|
|
918
|
+
component: "\u{1F9E9}",
|
|
919
|
+
function: "\u{1D453}",
|
|
920
|
+
unknown: "\u2753"
|
|
921
|
+
};
|
|
922
|
+
return icons[type] || icons.unknown;
|
|
923
|
+
}
|
|
924
|
+
function generateHTMLReport(results, summary) {
|
|
925
|
+
const data = summary ? { results, summary, metadata: { version: "0.11.22" } } : results;
|
|
926
|
+
const { results: duplicates, summary: reportSummary, metadata } = data;
|
|
927
|
+
return `<!DOCTYPE html>
|
|
928
|
+
<html lang="en">
|
|
929
|
+
<head>
|
|
930
|
+
<meta charset="UTF-8">
|
|
931
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
932
|
+
<title>AIReady - Pattern Detection Report</title>
|
|
933
|
+
<style>
|
|
934
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; line-height: 1.6; color: #333; max-width: 1200px; margin: 0 auto; padding: 2rem; background-color: #f9f9f9; }
|
|
935
|
+
h1, h2 { color: #1a1a1a; border-bottom: 2px solid #eaeaea; padding-bottom: 0.5rem; }
|
|
936
|
+
.card { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); margin-bottom: 2rem; border: 1px solid #eaeaea; }
|
|
937
|
+
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
|
|
938
|
+
.stat-card { background: #fff; padding: 1rem; border-radius: 6px; text-align: center; border: 1px solid #eaeaea; }
|
|
939
|
+
.stat-value { font-size: 1.8rem; font-weight: bold; color: #2563eb; }
|
|
940
|
+
.stat-label { font-size: 0.875rem; color: #666; text-transform: uppercase; }
|
|
941
|
+
table { width: 100%; border-collapse: collapse; margin-top: 1rem; background: white; border-radius: 4px; overflow: hidden; }
|
|
942
|
+
th, td { text-align: left; padding: 0.875rem 1rem; border-bottom: 1px solid #eaeaea; }
|
|
943
|
+
th { background-color: #f8fafc; font-weight: 600; color: #475569; }
|
|
944
|
+
tr:last-child td { border-bottom: none; }
|
|
945
|
+
.critical { color: #dc2626; font-weight: bold; }
|
|
946
|
+
.major { color: #ea580c; font-weight: bold; }
|
|
947
|
+
.minor { color: #2563eb; }
|
|
948
|
+
code { background: #f1f5f9; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.875rem; color: #334155; }
|
|
949
|
+
.footer { margin-top: 4rem; text-align: center; color: #94a3b8; font-size: 0.875rem; }
|
|
950
|
+
</style>
|
|
951
|
+
</head>
|
|
952
|
+
<body>
|
|
953
|
+
<h1>Pattern Detection Report</h1>
|
|
954
|
+
<div class="stat-card" style="margin-bottom: 2rem;">
|
|
955
|
+
<div class="stat-label">AI Ready Score (Deduplication)</div>
|
|
956
|
+
<div class="stat-value">${Math.max(0, 100 - Math.round((summary.duplicates?.length || 0) / (summary.totalFiles || 1) * 20))}%</div>
|
|
957
|
+
</div>
|
|
958
|
+
|
|
959
|
+
<div class="stats">
|
|
960
|
+
<div class="stat-card">
|
|
961
|
+
<div class="stat-value">${summary.totalFiles}</div>
|
|
962
|
+
<div class="stat-label">Files Analyzed</div>
|
|
963
|
+
</div>
|
|
964
|
+
<div class="stat-card">
|
|
965
|
+
<div class="stat-value">${summary.duplicates?.length || 0}</div>
|
|
966
|
+
<div class="stat-label">Duplicate Clusters</div>
|
|
967
|
+
</div>
|
|
968
|
+
<div class="stat-card">
|
|
969
|
+
<div class="stat-value">${summary.totalIssues}</div>
|
|
970
|
+
<div class="stat-label">Total Issues</div>
|
|
971
|
+
</div>
|
|
972
|
+
</div>
|
|
973
|
+
|
|
974
|
+
<div class="card">
|
|
975
|
+
<h2>Duplicate Patterns</h2>
|
|
976
|
+
<table>
|
|
977
|
+
<thead>
|
|
978
|
+
<tr>
|
|
979
|
+
<th>Similarity</th>
|
|
980
|
+
<th>Type</th>
|
|
981
|
+
<th>Locations</th>
|
|
982
|
+
<th>Tokens Wasted</th>
|
|
983
|
+
</tr>
|
|
984
|
+
</thead>
|
|
985
|
+
<tbody>
|
|
986
|
+
${(summary.duplicates || []).map(
|
|
987
|
+
(dup) => `
|
|
988
|
+
<tr>
|
|
989
|
+
<td class="${dup.similarity > 0.95 ? "critical" : dup.similarity > 0.9 ? "major" : "minor"}">${Math.round(dup.similarity * 100)}%</td>
|
|
990
|
+
<td>${dup.patternType}</td>
|
|
991
|
+
<td>${dup.files.map((f) => `<code>${f.path}:${f.startLine}-${f.endLine}</code>`).join("<br>\u2194<br>")}</td>
|
|
992
|
+
<td>${dup.tokenCost.toLocaleString()}</td>
|
|
993
|
+
</tr>
|
|
994
|
+
`
|
|
995
|
+
).join("")}
|
|
996
|
+
</tbody>
|
|
997
|
+
</table>
|
|
998
|
+
</div>
|
|
999
|
+
|
|
1000
|
+
<div class="footer">
|
|
1001
|
+
<p>Generated by <strong>@aiready/pattern-detect</strong> v${metadata.version}</p>
|
|
1002
|
+
<p>Like AIReady? <a href="https://github.com/caopengau/aiready-pattern-detect">Star us on GitHub</a></p>
|
|
1003
|
+
<p>Found a bug? <a href="https://github.com/caopengau/aiready-pattern-detect/issues">Report it here</a></p>
|
|
1004
|
+
</div>
|
|
1005
|
+
</body>
|
|
1006
|
+
</html>`;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/cli.ts
|
|
917
1010
|
var program = new import_commander.Command();
|
|
918
1011
|
program.name("aiready-patterns").description("Detect duplicate patterns in your codebase").version("0.1.0").addHelpText(
|
|
919
1012
|
"after",
|
|
@@ -967,7 +1060,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
967
1060
|
).option("--output-file <path>", "Output file path (for json/html)").action(async (directory, options) => {
|
|
968
1061
|
console.log(import_chalk.default.blue("\u{1F50D} Analyzing patterns...\n"));
|
|
969
1062
|
const startTime = Date.now();
|
|
970
|
-
const config = await (0,
|
|
1063
|
+
const config = await (0, import_core9.loadConfig)(directory);
|
|
971
1064
|
const defaults = {
|
|
972
1065
|
minSimilarity: 0.4,
|
|
973
1066
|
minLines: 5,
|
|
@@ -978,7 +1071,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
978
1071
|
streamResults: true,
|
|
979
1072
|
include: void 0,
|
|
980
1073
|
exclude: void 0,
|
|
981
|
-
minSeverity:
|
|
1074
|
+
minSeverity: import_core9.Severity.Minor,
|
|
982
1075
|
excludeTestFixtures: false,
|
|
983
1076
|
excludeTemplates: false,
|
|
984
1077
|
includeTests: false,
|
|
@@ -989,7 +1082,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
989
1082
|
minClusterFiles: 3,
|
|
990
1083
|
showRawDuplicates: false
|
|
991
1084
|
};
|
|
992
|
-
const mergedConfig = (0,
|
|
1085
|
+
const mergedConfig = (0, import_core9.mergeConfigWithDefaults)(config, defaults);
|
|
993
1086
|
const finalOptions = {
|
|
994
1087
|
rootDir: directory,
|
|
995
1088
|
minSimilarity: options.similarity ? parseFloat(options.similarity) : mergedConfig.minSimilarity,
|
|
@@ -1028,7 +1121,6 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1028
1121
|
const {
|
|
1029
1122
|
results,
|
|
1030
1123
|
duplicates: rawDuplicates,
|
|
1031
|
-
files,
|
|
1032
1124
|
groups,
|
|
1033
1125
|
clusters
|
|
1034
1126
|
} = await analyzePatterns(finalOptions);
|
|
@@ -1061,7 +1153,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1061
1153
|
clusters: clusters || [],
|
|
1062
1154
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1063
1155
|
};
|
|
1064
|
-
const outputPath = (0,
|
|
1156
|
+
const outputPath = (0, import_core9.resolveOutputPath)(
|
|
1065
1157
|
options.outputFile,
|
|
1066
1158
|
`pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
|
|
1067
1159
|
directory
|
|
@@ -1077,7 +1169,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1077
1169
|
}
|
|
1078
1170
|
if (options.output === "html") {
|
|
1079
1171
|
const html = generateHTMLReport(summary, results);
|
|
1080
|
-
const outputPath = (0,
|
|
1172
|
+
const outputPath = (0, import_core9.resolveOutputPath)(
|
|
1081
1173
|
options.outputFile,
|
|
1082
1174
|
`pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
|
|
1083
1175
|
directory
|
|
@@ -1132,14 +1224,14 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1132
1224
|
);
|
|
1133
1225
|
console.log(import_chalk.default.cyan(divider) + "\n");
|
|
1134
1226
|
const topGroups = groups.sort((a, b) => {
|
|
1135
|
-
const bVal = getSeverityValue(b.severity);
|
|
1136
|
-
const aVal = getSeverityValue(a.severity);
|
|
1227
|
+
const bVal = (0, import_core9.getSeverityValue)(b.severity);
|
|
1228
|
+
const aVal = (0, import_core9.getSeverityValue)(a.severity);
|
|
1137
1229
|
const severityDiff = bVal - aVal;
|
|
1138
1230
|
if (severityDiff !== 0) return severityDiff;
|
|
1139
1231
|
return b.totalTokenCost - a.totalTokenCost;
|
|
1140
1232
|
}).slice(0, finalOptions.maxResults);
|
|
1141
1233
|
topGroups.forEach((group, idx) => {
|
|
1142
|
-
const severityBadge = getSeverityBadge(group.severity);
|
|
1234
|
+
const severityBadge = (0, import_core9.getSeverityBadge)(group.severity);
|
|
1143
1235
|
const [file1, file2] = group.filePair.split("::");
|
|
1144
1236
|
const file1Name = file1.split("/").pop() || file1;
|
|
1145
1237
|
const file2Name = file2.split("/").pop() || file2;
|
|
@@ -1177,7 +1269,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1177
1269
|
);
|
|
1178
1270
|
console.log(import_chalk.default.cyan(divider) + "\n");
|
|
1179
1271
|
clusters.sort((a, b) => b.totalTokenCost - a.totalTokenCost).forEach((cluster, idx) => {
|
|
1180
|
-
const severityBadge = getSeverityBadge(cluster.severity);
|
|
1272
|
+
const severityBadge = (0, import_core9.getSeverityBadge)(cluster.severity);
|
|
1181
1273
|
console.log(
|
|
1182
1274
|
`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(cluster.name)}`
|
|
1183
1275
|
);
|
|
@@ -1209,14 +1301,14 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1209
1301
|
console.log(import_chalk.default.bold.white(" TOP DUPLICATE PATTERNS"));
|
|
1210
1302
|
console.log(import_chalk.default.cyan(divider) + "\n");
|
|
1211
1303
|
const topDuplicates = filteredDuplicates.sort((a, b) => {
|
|
1212
|
-
const bVal = getSeverityValue(b.severity);
|
|
1213
|
-
const aVal = getSeverityValue(a.severity);
|
|
1304
|
+
const bVal = (0, import_core9.getSeverityValue)(b.severity);
|
|
1305
|
+
const aVal = (0, import_core9.getSeverityValue)(a.severity);
|
|
1214
1306
|
const severityDiff = bVal - aVal;
|
|
1215
1307
|
if (severityDiff !== 0) return severityDiff;
|
|
1216
1308
|
return b.similarity - a.similarity;
|
|
1217
1309
|
}).slice(0, finalOptions.maxResults);
|
|
1218
|
-
topDuplicates.forEach((dup
|
|
1219
|
-
const severityBadge = getSeverityBadge(dup.severity);
|
|
1310
|
+
topDuplicates.forEach((dup) => {
|
|
1311
|
+
const severityBadge = (0, import_core9.getSeverityBadge)(dup.severity);
|
|
1220
1312
|
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
1221
1313
|
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
1222
1314
|
console.log(
|
|
@@ -1251,7 +1343,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1251
1343
|
(r) => r.issues.map((issue) => ({ ...issue, file: r.fileName }))
|
|
1252
1344
|
);
|
|
1253
1345
|
const criticalIssues = allIssues.filter(
|
|
1254
|
-
(issue) => getSeverityValue(issue.severity) === 4
|
|
1346
|
+
(issue) => (0, import_core9.getSeverityValue)(issue.severity) === 4
|
|
1255
1347
|
);
|
|
1256
1348
|
if (criticalIssues.length > 0) {
|
|
1257
1349
|
console.log(import_chalk.default.cyan(divider));
|
|
@@ -1319,112 +1411,4 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
1319
1411
|
)
|
|
1320
1412
|
);
|
|
1321
1413
|
});
|
|
1322
|
-
function getPatternIcon(type) {
|
|
1323
|
-
const icons = {
|
|
1324
|
-
"api-handler": "\u{1F310}",
|
|
1325
|
-
validator: "\u2713",
|
|
1326
|
-
utility: "\u{1F527}",
|
|
1327
|
-
"class-method": "\u{1F4E6}",
|
|
1328
|
-
component: "\u269B\uFE0F",
|
|
1329
|
-
function: "\u0192",
|
|
1330
|
-
unknown: "\u2753"
|
|
1331
|
-
};
|
|
1332
|
-
return icons[type];
|
|
1333
|
-
}
|
|
1334
|
-
function generateHTMLReport(summary, results) {
|
|
1335
|
-
return `<!DOCTYPE html>
|
|
1336
|
-
<html>
|
|
1337
|
-
<head>
|
|
1338
|
-
<title>Pattern Detection Report</title>
|
|
1339
|
-
<style>
|
|
1340
|
-
body { font-family: system-ui, -apple-system, sans-serif; margin: 40px; background: #f5f5f5; }
|
|
1341
|
-
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
|
1342
|
-
h1 { color: #333; border-bottom: 3px solid #007acc; padding-bottom: 10px; }
|
|
1343
|
-
.summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 30px 0; }
|
|
1344
|
-
.stat-card { background: #f9f9f9; padding: 20px; border-radius: 6px; border-left: 4px solid #007acc; }
|
|
1345
|
-
.stat-value { font-size: 32px; font-weight: bold; color: #007acc; }
|
|
1346
|
-
.stat-label { color: #666; font-size: 14px; text-transform: uppercase; }
|
|
1347
|
-
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
|
1348
|
-
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #ddd; }
|
|
1349
|
-
th { background: #f5f5f5; font-weight: 600; }
|
|
1350
|
-
.critical { color: #d32f2f; }
|
|
1351
|
-
.major { color: #f57c00; }
|
|
1352
|
-
.minor { color: #1976d2; }
|
|
1353
|
-
</style>
|
|
1354
|
-
</head>
|
|
1355
|
-
<body>
|
|
1356
|
-
<div class="container">
|
|
1357
|
-
<h1>\u{1F50D} Pattern Detection Report</h1>
|
|
1358
|
-
<p>Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()}</p>
|
|
1359
|
-
|
|
1360
|
-
<div class="summary">
|
|
1361
|
-
<div class="stat-card">
|
|
1362
|
-
<div class="stat-value">${results.length}</div>
|
|
1363
|
-
<div class="stat-label">Files Analyzed</div>
|
|
1364
|
-
</div>
|
|
1365
|
-
<div class="stat-card">
|
|
1366
|
-
<div class="stat-value">${summary.totalPatterns}</div>
|
|
1367
|
-
<div class="stat-label">Duplicate Patterns</div>
|
|
1368
|
-
</div>
|
|
1369
|
-
<div class="stat-card">
|
|
1370
|
-
<div class="stat-value">${summary.totalTokenCost.toLocaleString()}</div>
|
|
1371
|
-
<div class="stat-label">Tokens Wasted</div>
|
|
1372
|
-
</div>
|
|
1373
|
-
</div>
|
|
1374
|
-
|
|
1375
|
-
<h2>Top Duplicate Patterns</h2>
|
|
1376
|
-
<table>
|
|
1377
|
-
<thead>
|
|
1378
|
-
<tr>
|
|
1379
|
-
<th>Similarity</th>
|
|
1380
|
-
<th>Type</th>
|
|
1381
|
-
<th>Files</th>
|
|
1382
|
-
<th>Token Cost</th>
|
|
1383
|
-
</tr>
|
|
1384
|
-
</thead>
|
|
1385
|
-
<tbody>
|
|
1386
|
-
${summary.topDuplicates.slice(0, 20).map(
|
|
1387
|
-
(dup) => `
|
|
1388
|
-
<tr>
|
|
1389
|
-
<td class="${dup.similarity > 0.95 ? "critical" : dup.similarity > 0.9 ? "major" : "minor"}">${Math.round(dup.similarity * 100)}%</td>
|
|
1390
|
-
<td>${dup.patternType}</td>
|
|
1391
|
-
<td>${dup.files.map((f) => `<code>${f.path}:${f.startLine}-${f.endLine}</code>`).join("<br>\u2194<br>")}</td>
|
|
1392
|
-
<td>${dup.tokenCost.toLocaleString()}</td>
|
|
1393
|
-
</tr>
|
|
1394
|
-
`
|
|
1395
|
-
).join("")}
|
|
1396
|
-
</tbody>
|
|
1397
|
-
</table>
|
|
1398
|
-
</div>
|
|
1399
|
-
|
|
1400
|
-
<div class="footer">
|
|
1401
|
-
<p>Generated by <strong>@aiready/pattern-detect</strong></p>
|
|
1402
|
-
<p>Like AIReady? <a href="https://github.com/caopengau/aiready-pattern-detect">Star us on GitHub</a></p>
|
|
1403
|
-
<p>Found a bug? <a href="https://github.com/caopengau/aiready-pattern-detect/issues">Report it here</a></p>
|
|
1404
|
-
</div>
|
|
1405
|
-
</body>
|
|
1406
|
-
</html>`;
|
|
1407
|
-
}
|
|
1408
1414
|
program.parse();
|
|
1409
|
-
function getSeverityValue(s) {
|
|
1410
|
-
if (s === import_core8.Severity.Critical || s === "critical") return 4;
|
|
1411
|
-
if (s === import_core8.Severity.Major || s === "major") return 3;
|
|
1412
|
-
if (s === import_core8.Severity.Minor || s === "minor") return 2;
|
|
1413
|
-
if (s === import_core8.Severity.Info || s === "info") return 1;
|
|
1414
|
-
return 0;
|
|
1415
|
-
}
|
|
1416
|
-
function getSeverityBadge(severity) {
|
|
1417
|
-
const val = getSeverityValue(severity);
|
|
1418
|
-
switch (val) {
|
|
1419
|
-
case 4:
|
|
1420
|
-
return import_chalk.default.bgRed.white.bold(" CRITICAL ");
|
|
1421
|
-
case 3:
|
|
1422
|
-
return import_chalk.default.bgYellow.black.bold(" MAJOR ");
|
|
1423
|
-
case 2:
|
|
1424
|
-
return import_chalk.default.bgBlue.white.bold(" MINOR ");
|
|
1425
|
-
case 1:
|
|
1426
|
-
return import_chalk.default.bgCyan.black(" INFO ");
|
|
1427
|
-
default:
|
|
1428
|
-
return import_chalk.default.bgCyan.black(" INFO ");
|
|
1429
|
-
}
|
|
1430
|
-
}
|
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
analyzePatterns,
|
|
4
4
|
filterBySeverity,
|
|
5
5
|
generateSummary
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KPEK5REL.mjs";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { Command } from "commander";
|
|
@@ -14,8 +14,111 @@ import {
|
|
|
14
14
|
loadConfig,
|
|
15
15
|
mergeConfigWithDefaults,
|
|
16
16
|
resolveOutputPath,
|
|
17
|
-
Severity
|
|
17
|
+
Severity as Severity2,
|
|
18
|
+
getSeverityBadge as getSeverityBadge2,
|
|
19
|
+
getSeverityValue as getSeverityValue2
|
|
18
20
|
} from "@aiready/core";
|
|
21
|
+
|
|
22
|
+
// src/cli-output.ts
|
|
23
|
+
import { getSeverityBadge, getSeverityValue } from "@aiready/core";
|
|
24
|
+
function getPatternIcon(type) {
|
|
25
|
+
const icons = {
|
|
26
|
+
"api-handler": "\u{1F50C}",
|
|
27
|
+
validator: "\u{1F6E1}\uFE0F",
|
|
28
|
+
utility: "\u2699\uFE0F",
|
|
29
|
+
"class-method": "\u{1F3DB}\uFE0F",
|
|
30
|
+
component: "\u{1F9E9}",
|
|
31
|
+
function: "\u{1D453}",
|
|
32
|
+
unknown: "\u2753"
|
|
33
|
+
};
|
|
34
|
+
return icons[type] || icons.unknown;
|
|
35
|
+
}
|
|
36
|
+
function generateHTMLReport(results, summary) {
|
|
37
|
+
const data = summary ? { results, summary, metadata: { version: "0.11.22" } } : results;
|
|
38
|
+
const { results: duplicates, summary: reportSummary, metadata } = data;
|
|
39
|
+
return `<!DOCTYPE html>
|
|
40
|
+
<html lang="en">
|
|
41
|
+
<head>
|
|
42
|
+
<meta charset="UTF-8">
|
|
43
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
44
|
+
<title>AIReady - Pattern Detection Report</title>
|
|
45
|
+
<style>
|
|
46
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; line-height: 1.6; color: #333; max-width: 1200px; margin: 0 auto; padding: 2rem; background-color: #f9f9f9; }
|
|
47
|
+
h1, h2 { color: #1a1a1a; border-bottom: 2px solid #eaeaea; padding-bottom: 0.5rem; }
|
|
48
|
+
.card { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); margin-bottom: 2rem; border: 1px solid #eaeaea; }
|
|
49
|
+
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
|
|
50
|
+
.stat-card { background: #fff; padding: 1rem; border-radius: 6px; text-align: center; border: 1px solid #eaeaea; }
|
|
51
|
+
.stat-value { font-size: 1.8rem; font-weight: bold; color: #2563eb; }
|
|
52
|
+
.stat-label { font-size: 0.875rem; color: #666; text-transform: uppercase; }
|
|
53
|
+
table { width: 100%; border-collapse: collapse; margin-top: 1rem; background: white; border-radius: 4px; overflow: hidden; }
|
|
54
|
+
th, td { text-align: left; padding: 0.875rem 1rem; border-bottom: 1px solid #eaeaea; }
|
|
55
|
+
th { background-color: #f8fafc; font-weight: 600; color: #475569; }
|
|
56
|
+
tr:last-child td { border-bottom: none; }
|
|
57
|
+
.critical { color: #dc2626; font-weight: bold; }
|
|
58
|
+
.major { color: #ea580c; font-weight: bold; }
|
|
59
|
+
.minor { color: #2563eb; }
|
|
60
|
+
code { background: #f1f5f9; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.875rem; color: #334155; }
|
|
61
|
+
.footer { margin-top: 4rem; text-align: center; color: #94a3b8; font-size: 0.875rem; }
|
|
62
|
+
</style>
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
<h1>Pattern Detection Report</h1>
|
|
66
|
+
<div class="stat-card" style="margin-bottom: 2rem;">
|
|
67
|
+
<div class="stat-label">AI Ready Score (Deduplication)</div>
|
|
68
|
+
<div class="stat-value">${Math.max(0, 100 - Math.round((summary.duplicates?.length || 0) / (summary.totalFiles || 1) * 20))}%</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="stats">
|
|
72
|
+
<div class="stat-card">
|
|
73
|
+
<div class="stat-value">${summary.totalFiles}</div>
|
|
74
|
+
<div class="stat-label">Files Analyzed</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="stat-card">
|
|
77
|
+
<div class="stat-value">${summary.duplicates?.length || 0}</div>
|
|
78
|
+
<div class="stat-label">Duplicate Clusters</div>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="stat-card">
|
|
81
|
+
<div class="stat-value">${summary.totalIssues}</div>
|
|
82
|
+
<div class="stat-label">Total Issues</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="card">
|
|
87
|
+
<h2>Duplicate Patterns</h2>
|
|
88
|
+
<table>
|
|
89
|
+
<thead>
|
|
90
|
+
<tr>
|
|
91
|
+
<th>Similarity</th>
|
|
92
|
+
<th>Type</th>
|
|
93
|
+
<th>Locations</th>
|
|
94
|
+
<th>Tokens Wasted</th>
|
|
95
|
+
</tr>
|
|
96
|
+
</thead>
|
|
97
|
+
<tbody>
|
|
98
|
+
${(summary.duplicates || []).map(
|
|
99
|
+
(dup) => `
|
|
100
|
+
<tr>
|
|
101
|
+
<td class="${dup.similarity > 0.95 ? "critical" : dup.similarity > 0.9 ? "major" : "minor"}">${Math.round(dup.similarity * 100)}%</td>
|
|
102
|
+
<td>${dup.patternType}</td>
|
|
103
|
+
<td>${dup.files.map((f) => `<code>${f.path}:${f.startLine}-${f.endLine}</code>`).join("<br>\u2194<br>")}</td>
|
|
104
|
+
<td>${dup.tokenCost.toLocaleString()}</td>
|
|
105
|
+
</tr>
|
|
106
|
+
`
|
|
107
|
+
).join("")}
|
|
108
|
+
</tbody>
|
|
109
|
+
</table>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div class="footer">
|
|
113
|
+
<p>Generated by <strong>@aiready/pattern-detect</strong> v${metadata.version}</p>
|
|
114
|
+
<p>Like AIReady? <a href="https://github.com/caopengau/aiready-pattern-detect">Star us on GitHub</a></p>
|
|
115
|
+
<p>Found a bug? <a href="https://github.com/caopengau/aiready-pattern-detect/issues">Report it here</a></p>
|
|
116
|
+
</div>
|
|
117
|
+
</body>
|
|
118
|
+
</html>`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/cli.ts
|
|
19
122
|
var program = new Command();
|
|
20
123
|
program.name("aiready-patterns").description("Detect duplicate patterns in your codebase").version("0.1.0").addHelpText(
|
|
21
124
|
"after",
|
|
@@ -80,7 +183,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
80
183
|
streamResults: true,
|
|
81
184
|
include: void 0,
|
|
82
185
|
exclude: void 0,
|
|
83
|
-
minSeverity:
|
|
186
|
+
minSeverity: Severity2.Minor,
|
|
84
187
|
excludeTestFixtures: false,
|
|
85
188
|
excludeTemplates: false,
|
|
86
189
|
includeTests: false,
|
|
@@ -130,7 +233,6 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
130
233
|
const {
|
|
131
234
|
results,
|
|
132
235
|
duplicates: rawDuplicates,
|
|
133
|
-
files,
|
|
134
236
|
groups,
|
|
135
237
|
clusters
|
|
136
238
|
} = await analyzePatterns(finalOptions);
|
|
@@ -234,14 +336,14 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
234
336
|
);
|
|
235
337
|
console.log(chalk.cyan(divider) + "\n");
|
|
236
338
|
const topGroups = groups.sort((a, b) => {
|
|
237
|
-
const bVal =
|
|
238
|
-
const aVal =
|
|
339
|
+
const bVal = getSeverityValue2(b.severity);
|
|
340
|
+
const aVal = getSeverityValue2(a.severity);
|
|
239
341
|
const severityDiff = bVal - aVal;
|
|
240
342
|
if (severityDiff !== 0) return severityDiff;
|
|
241
343
|
return b.totalTokenCost - a.totalTokenCost;
|
|
242
344
|
}).slice(0, finalOptions.maxResults);
|
|
243
345
|
topGroups.forEach((group, idx) => {
|
|
244
|
-
const severityBadge =
|
|
346
|
+
const severityBadge = getSeverityBadge2(group.severity);
|
|
245
347
|
const [file1, file2] = group.filePair.split("::");
|
|
246
348
|
const file1Name = file1.split("/").pop() || file1;
|
|
247
349
|
const file2Name = file2.split("/").pop() || file2;
|
|
@@ -279,7 +381,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
279
381
|
);
|
|
280
382
|
console.log(chalk.cyan(divider) + "\n");
|
|
281
383
|
clusters.sort((a, b) => b.totalTokenCost - a.totalTokenCost).forEach((cluster, idx) => {
|
|
282
|
-
const severityBadge =
|
|
384
|
+
const severityBadge = getSeverityBadge2(cluster.severity);
|
|
283
385
|
console.log(
|
|
284
386
|
`${idx + 1}. ${severityBadge} ${chalk.bold(cluster.name)}`
|
|
285
387
|
);
|
|
@@ -311,14 +413,14 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
311
413
|
console.log(chalk.bold.white(" TOP DUPLICATE PATTERNS"));
|
|
312
414
|
console.log(chalk.cyan(divider) + "\n");
|
|
313
415
|
const topDuplicates = filteredDuplicates.sort((a, b) => {
|
|
314
|
-
const bVal =
|
|
315
|
-
const aVal =
|
|
416
|
+
const bVal = getSeverityValue2(b.severity);
|
|
417
|
+
const aVal = getSeverityValue2(a.severity);
|
|
316
418
|
const severityDiff = bVal - aVal;
|
|
317
419
|
if (severityDiff !== 0) return severityDiff;
|
|
318
420
|
return b.similarity - a.similarity;
|
|
319
421
|
}).slice(0, finalOptions.maxResults);
|
|
320
|
-
topDuplicates.forEach((dup
|
|
321
|
-
const severityBadge =
|
|
422
|
+
topDuplicates.forEach((dup) => {
|
|
423
|
+
const severityBadge = getSeverityBadge2(dup.severity);
|
|
322
424
|
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
323
425
|
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
324
426
|
console.log(
|
|
@@ -353,7 +455,7 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
353
455
|
(r) => r.issues.map((issue) => ({ ...issue, file: r.fileName }))
|
|
354
456
|
);
|
|
355
457
|
const criticalIssues = allIssues.filter(
|
|
356
|
-
(issue) =>
|
|
458
|
+
(issue) => getSeverityValue2(issue.severity) === 4
|
|
357
459
|
);
|
|
358
460
|
if (criticalIssues.length > 0) {
|
|
359
461
|
console.log(chalk.cyan(divider));
|
|
@@ -421,112 +523,4 @@ program.name("aiready-patterns").description("Detect duplicate patterns in your
|
|
|
421
523
|
)
|
|
422
524
|
);
|
|
423
525
|
});
|
|
424
|
-
function getPatternIcon(type) {
|
|
425
|
-
const icons = {
|
|
426
|
-
"api-handler": "\u{1F310}",
|
|
427
|
-
validator: "\u2713",
|
|
428
|
-
utility: "\u{1F527}",
|
|
429
|
-
"class-method": "\u{1F4E6}",
|
|
430
|
-
component: "\u269B\uFE0F",
|
|
431
|
-
function: "\u0192",
|
|
432
|
-
unknown: "\u2753"
|
|
433
|
-
};
|
|
434
|
-
return icons[type];
|
|
435
|
-
}
|
|
436
|
-
function generateHTMLReport(summary, results) {
|
|
437
|
-
return `<!DOCTYPE html>
|
|
438
|
-
<html>
|
|
439
|
-
<head>
|
|
440
|
-
<title>Pattern Detection Report</title>
|
|
441
|
-
<style>
|
|
442
|
-
body { font-family: system-ui, -apple-system, sans-serif; margin: 40px; background: #f5f5f5; }
|
|
443
|
-
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
|
444
|
-
h1 { color: #333; border-bottom: 3px solid #007acc; padding-bottom: 10px; }
|
|
445
|
-
.summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 30px 0; }
|
|
446
|
-
.stat-card { background: #f9f9f9; padding: 20px; border-radius: 6px; border-left: 4px solid #007acc; }
|
|
447
|
-
.stat-value { font-size: 32px; font-weight: bold; color: #007acc; }
|
|
448
|
-
.stat-label { color: #666; font-size: 14px; text-transform: uppercase; }
|
|
449
|
-
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
|
450
|
-
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #ddd; }
|
|
451
|
-
th { background: #f5f5f5; font-weight: 600; }
|
|
452
|
-
.critical { color: #d32f2f; }
|
|
453
|
-
.major { color: #f57c00; }
|
|
454
|
-
.minor { color: #1976d2; }
|
|
455
|
-
</style>
|
|
456
|
-
</head>
|
|
457
|
-
<body>
|
|
458
|
-
<div class="container">
|
|
459
|
-
<h1>\u{1F50D} Pattern Detection Report</h1>
|
|
460
|
-
<p>Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()}</p>
|
|
461
|
-
|
|
462
|
-
<div class="summary">
|
|
463
|
-
<div class="stat-card">
|
|
464
|
-
<div class="stat-value">${results.length}</div>
|
|
465
|
-
<div class="stat-label">Files Analyzed</div>
|
|
466
|
-
</div>
|
|
467
|
-
<div class="stat-card">
|
|
468
|
-
<div class="stat-value">${summary.totalPatterns}</div>
|
|
469
|
-
<div class="stat-label">Duplicate Patterns</div>
|
|
470
|
-
</div>
|
|
471
|
-
<div class="stat-card">
|
|
472
|
-
<div class="stat-value">${summary.totalTokenCost.toLocaleString()}</div>
|
|
473
|
-
<div class="stat-label">Tokens Wasted</div>
|
|
474
|
-
</div>
|
|
475
|
-
</div>
|
|
476
|
-
|
|
477
|
-
<h2>Top Duplicate Patterns</h2>
|
|
478
|
-
<table>
|
|
479
|
-
<thead>
|
|
480
|
-
<tr>
|
|
481
|
-
<th>Similarity</th>
|
|
482
|
-
<th>Type</th>
|
|
483
|
-
<th>Files</th>
|
|
484
|
-
<th>Token Cost</th>
|
|
485
|
-
</tr>
|
|
486
|
-
</thead>
|
|
487
|
-
<tbody>
|
|
488
|
-
${summary.topDuplicates.slice(0, 20).map(
|
|
489
|
-
(dup) => `
|
|
490
|
-
<tr>
|
|
491
|
-
<td class="${dup.similarity > 0.95 ? "critical" : dup.similarity > 0.9 ? "major" : "minor"}">${Math.round(dup.similarity * 100)}%</td>
|
|
492
|
-
<td>${dup.patternType}</td>
|
|
493
|
-
<td>${dup.files.map((f) => `<code>${f.path}:${f.startLine}-${f.endLine}</code>`).join("<br>\u2194<br>")}</td>
|
|
494
|
-
<td>${dup.tokenCost.toLocaleString()}</td>
|
|
495
|
-
</tr>
|
|
496
|
-
`
|
|
497
|
-
).join("")}
|
|
498
|
-
</tbody>
|
|
499
|
-
</table>
|
|
500
|
-
</div>
|
|
501
|
-
|
|
502
|
-
<div class="footer">
|
|
503
|
-
<p>Generated by <strong>@aiready/pattern-detect</strong></p>
|
|
504
|
-
<p>Like AIReady? <a href="https://github.com/caopengau/aiready-pattern-detect">Star us on GitHub</a></p>
|
|
505
|
-
<p>Found a bug? <a href="https://github.com/caopengau/aiready-pattern-detect/issues">Report it here</a></p>
|
|
506
|
-
</div>
|
|
507
|
-
</body>
|
|
508
|
-
</html>`;
|
|
509
|
-
}
|
|
510
526
|
program.parse();
|
|
511
|
-
function getSeverityValue(s) {
|
|
512
|
-
if (s === Severity.Critical || s === "critical") return 4;
|
|
513
|
-
if (s === Severity.Major || s === "major") return 3;
|
|
514
|
-
if (s === Severity.Minor || s === "minor") return 2;
|
|
515
|
-
if (s === Severity.Info || s === "info") return 1;
|
|
516
|
-
return 0;
|
|
517
|
-
}
|
|
518
|
-
function getSeverityBadge(severity) {
|
|
519
|
-
const val = getSeverityValue(severity);
|
|
520
|
-
switch (val) {
|
|
521
|
-
case 4:
|
|
522
|
-
return chalk.bgRed.white.bold(" CRITICAL ");
|
|
523
|
-
case 3:
|
|
524
|
-
return chalk.bgYellow.black.bold(" MAJOR ");
|
|
525
|
-
case 2:
|
|
526
|
-
return chalk.bgBlue.white.bold(" MINOR ");
|
|
527
|
-
case 1:
|
|
528
|
-
return chalk.bgCyan.black(" INFO ");
|
|
529
|
-
default:
|
|
530
|
-
return chalk.bgCyan.black(" INFO ");
|
|
531
|
-
}
|
|
532
|
-
}
|