@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/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_<>\[\]]+)\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;
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 [key, value] of Object.entries(defaults)) {
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, import_core8.loadConfig)(directory);
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: import_core8.Severity.Minor,
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, import_core8.mergeConfigWithDefaults)(config, defaults);
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, import_core8.resolveOutputPath)(
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, import_core8.resolveOutputPath)(
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, idx) => {
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-EVBFDILL.mjs";
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: Severity.Minor,
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 = getSeverityValue(b.severity);
238
- const aVal = getSeverityValue(a.severity);
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 = getSeverityBadge(group.severity);
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 = getSeverityBadge(cluster.severity);
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 = getSeverityValue(b.severity);
315
- const aVal = getSeverityValue(a.severity);
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, idx) => {
321
- const severityBadge = getSeverityBadge(dup.severity);
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) => getSeverityValue(issue.severity) === 4
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
- }