@aiready/pattern-detect 0.16.22 → 0.16.23

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.
Files changed (35) hide show
  1. package/dist/analyzer-entry/index.mjs +3 -3
  2. package/dist/chunk-J2G742QF.mjs +162 -0
  3. package/dist/chunk-J5CW6NYY.mjs +64 -0
  4. package/dist/chunk-NQBYYWHJ.mjs +143 -0
  5. package/dist/chunk-SUUZMLPS.mjs +391 -0
  6. package/dist/cli.js +336 -303
  7. package/dist/cli.mjs +347 -303
  8. package/dist/context-rules-entry/index.d.mts +2 -2
  9. package/dist/context-rules-entry/index.d.ts +2 -2
  10. package/dist/context-rules-entry/index.js +2 -25
  11. package/dist/context-rules-entry/index.mjs +1 -1
  12. package/dist/detector-entry/index.mjs +2 -2
  13. package/dist/index-szjQDBsm.d.mts +49 -0
  14. package/dist/index-szjQDBsm.d.ts +49 -0
  15. package/dist/index.d.mts +2 -2
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +4 -25
  18. package/dist/index.mjs +6 -4
  19. package/package.json +2 -2
  20. package/dist/__tests__/context-rules.test.d.ts +0 -2
  21. package/dist/__tests__/context-rules.test.d.ts.map +0 -1
  22. package/dist/__tests__/context-rules.test.js +0 -189
  23. package/dist/__tests__/context-rules.test.js.map +0 -1
  24. package/dist/__tests__/detector.test.d.ts +0 -2
  25. package/dist/__tests__/detector.test.d.ts.map +0 -1
  26. package/dist/__tests__/detector.test.js +0 -259
  27. package/dist/__tests__/detector.test.js.map +0 -1
  28. package/dist/__tests__/grouping.test.d.ts +0 -2
  29. package/dist/__tests__/grouping.test.d.ts.map +0 -1
  30. package/dist/__tests__/grouping.test.js +0 -443
  31. package/dist/__tests__/grouping.test.js.map +0 -1
  32. package/dist/__tests__/scoring.test.d.ts +0 -2
  33. package/dist/__tests__/scoring.test.d.ts.map +0 -1
  34. package/dist/__tests__/scoring.test.js +0 -102
  35. package/dist/__tests__/scoring.test.js.map +0 -1
package/dist/cli.js CHANGED
@@ -177,20 +177,6 @@ function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
177
177
  };
178
178
  }
179
179
  }
180
- function filterBySeverity(duplicates, minSeverity) {
181
- const severityOrder = [
182
- import_core.Severity.Info,
183
- import_core.Severity.Minor,
184
- import_core.Severity.Major,
185
- import_core.Severity.Critical
186
- ];
187
- const minIndex = severityOrder.indexOf(minSeverity);
188
- if (minIndex === -1) return duplicates;
189
- return duplicates.filter((dup) => {
190
- const dupIndex = severityOrder.indexOf(dup.severity);
191
- return dupIndex >= minIndex;
192
- });
193
- }
194
180
 
195
181
  // src/core/normalizer.ts
196
182
  function normalizeCode(code, isPython = false) {
@@ -846,74 +832,13 @@ var PatternDetectProvider = {
846
832
  import_core7.ToolRegistry.register(PatternDetectProvider);
847
833
 
848
834
  // src/cli-action.ts
849
- var import_chalk = __toESM(require("chalk"));
835
+ var import_chalk2 = __toESM(require("chalk"));
850
836
  var import_fs = require("fs");
851
837
  var import_path2 = require("path");
852
- var import_core9 = require("@aiready/core");
838
+ var import_core11 = require("@aiready/core");
853
839
 
854
- // src/cli-output.ts
840
+ // src/config-resolver.ts
855
841
  var import_core8 = require("@aiready/core");
856
- function getPatternIcon(type) {
857
- const icons = {
858
- "api-handler": "\u{1F50C}",
859
- validator: "\u{1F6E1}\uFE0F",
860
- utility: "\u2699\uFE0F",
861
- "class-method": "\u{1F3DB}\uFE0F",
862
- component: "\u{1F9E9}",
863
- function: "\u{1D453}",
864
- unknown: "\u2753"
865
- };
866
- return icons[type] || icons.unknown;
867
- }
868
- function generateHTMLReport(results, summary) {
869
- const data = summary ? { results, summary, metadata: { version: "0.11.22" } } : results;
870
- const { metadata } = data;
871
- const s = data.summary;
872
- const head = (0, import_core8.generateReportHead)("AIReady - Pattern Detection Report");
873
- const score = Math.max(
874
- 0,
875
- 100 - Math.round((s.duplicates?.length || 0) / (s.totalFiles || 1) * 20)
876
- );
877
- const scoreCard = (0, import_core8.generateScoreCard)(
878
- `${score}%`,
879
- "AI Ready Score (Deduplication)"
880
- );
881
- const stats = (0, import_core8.generateStatCards)([
882
- { value: s.totalFiles, label: "Files Analyzed" },
883
- { value: s.duplicates?.length || 0, label: "Duplicate Clusters" },
884
- { value: s.totalIssues, label: "Total Issues" }
885
- ]);
886
- const tableRows = (s.duplicates || []).map((dup) => [
887
- `<span class="${dup.similarity > 0.95 ? "critical" : dup.similarity > 0.9 ? "major" : "minor"}">${Math.round(dup.similarity * 100)}%</span>`,
888
- dup.patternType,
889
- dup.files.map((f) => `<code>${f.path}:${f.startLine}-${f.endLine}</code>`).join("<br>\u2194<br>"),
890
- dup.tokenCost.toLocaleString()
891
- ]);
892
- const table = (0, import_core8.generateTable)({
893
- headers: ["Similarity", "Type", "Locations", "Tokens Wasted"],
894
- rows: tableRows
895
- });
896
- const body = `${scoreCard}
897
- ${stats}
898
- <div class="card">
899
- <h2>Duplicate Patterns</h2>
900
- ${table}
901
- </div>`;
902
- const footer = (0, import_core8.generateReportFooter)({
903
- title: "Pattern Detection Report",
904
- packageName: "pattern-detect",
905
- packageUrl: "https://github.com/caopengau/aiready-pattern-detect",
906
- bugUrl: "https://github.com/caopengau/aiready-pattern-detect/issues",
907
- version: metadata.version
908
- });
909
- return `${head}
910
- <body>
911
- <h1>Pattern Detection Report</h1>
912
- ${body}
913
- ${footer}
914
- </body>
915
- </html>`;
916
- }
917
842
 
918
843
  // src/constants.ts
919
844
  var DEFAULT_MIN_SIMILARITY = 0.4;
@@ -943,11 +868,9 @@ EXAMPLES:
943
868
  aiready-patterns . --max-candidates 50 --no-approx # Slower but more thorough
944
869
  aiready-patterns . --output json > report.json # JSON export`;
945
870
 
946
- // src/cli-action.ts
947
- async function patternActionHandler(directory, options) {
948
- console.log(import_chalk.default.blue("\u{1F50D} Analyzing patterns...\n"));
949
- const startTime = Date.now();
950
- const config = await (0, import_core9.loadConfig)(directory);
871
+ // src/config-resolver.ts
872
+ async function resolvePatternConfig(directory, options) {
873
+ const fileConfig = await (0, import_core8.loadConfig)(directory);
951
874
  const defaults = {
952
875
  minSimilarity: DEFAULT_MIN_SIMILARITY,
953
876
  minLines: DEFAULT_MIN_LINES,
@@ -961,7 +884,7 @@ async function patternActionHandler(directory, options) {
961
884
  excludePatterns: void 0,
962
885
  confidenceThreshold: 0,
963
886
  ignoreWhitelist: void 0,
964
- minSeverity: import_core9.Severity.Minor,
887
+ minSeverity: import_core8.Severity.Minor,
965
888
  excludeTestFixtures: false,
966
889
  excludeTemplates: false,
967
890
  includeTests: false,
@@ -972,14 +895,13 @@ async function patternActionHandler(directory, options) {
972
895
  minClusterFiles: DEFAULT_MIN_CLUSTER_FILES,
973
896
  showRawDuplicates: false
974
897
  };
975
- const mergedConfig = (0, import_core9.mergeConfigWithDefaults)(config, defaults);
898
+ const mergedConfig = (0, import_core8.mergeConfigWithDefaults)(fileConfig, defaults);
976
899
  const finalOptions = {
977
900
  rootDir: directory,
978
901
  minSimilarity: options.similarity ? parseFloat(options.similarity) : mergedConfig.minSimilarity,
979
902
  minLines: options.minLines ? parseInt(options.minLines) : mergedConfig.minLines,
980
903
  batchSize: options.batchSize ? parseInt(options.batchSize) : mergedConfig.batchSize,
981
904
  approx: options.approx !== false && mergedConfig.approx,
982
- // CLI --no-approx takes precedence
983
905
  minSharedTokens: options.minSharedTokens ? parseInt(options.minSharedTokens) : mergedConfig.minSharedTokens,
984
906
  maxCandidatesPerBlock: options.maxCandidates ? parseInt(options.maxCandidates) : mergedConfig.maxCandidatesPerBlock,
985
907
  streamResults: options.streamResults !== false && mergedConfig.streamResults,
@@ -1011,73 +933,69 @@ async function patternActionHandler(directory, options) {
1011
933
  (pattern) => !testPatterns.includes(pattern)
1012
934
  );
1013
935
  }
1014
- const {
1015
- results,
1016
- duplicates: rawDuplicates,
1017
- groups,
1018
- clusters
1019
- } = await analyzePatterns(finalOptions);
1020
- let filteredDuplicates = rawDuplicates;
1021
- if (finalOptions.minSeverity) {
1022
- filteredDuplicates = filterBySeverity(
1023
- filteredDuplicates,
1024
- finalOptions.minSeverity
1025
- );
1026
- }
1027
- if (finalOptions.excludeTestFixtures) {
1028
- filteredDuplicates = filteredDuplicates.filter(
1029
- (d) => d.matchedRule !== "test-fixtures"
1030
- );
1031
- }
1032
- if (finalOptions.excludeTemplates) {
1033
- filteredDuplicates = filteredDuplicates.filter(
1034
- (d) => d.matchedRule !== "templates"
1035
- );
1036
- }
1037
- const elapsedTime = ((Date.now() - startTime) / 1e3).toFixed(2);
1038
- const summary = generateSummary(results);
1039
- const totalIssues = results.reduce((sum, r) => sum + r.issues.length, 0);
1040
- if (options.output === "json") {
1041
- const jsonOutput = {
1042
- summary,
1043
- results,
1044
- duplicates: rawDuplicates,
1045
- groups: groups || [],
1046
- clusters: clusters || [],
1047
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1048
- };
1049
- const outputPath = (0, import_core9.resolveOutputPath)(
1050
- options.outputFile,
1051
- `pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
1052
- directory
1053
- );
1054
- const dir = (0, import_path2.dirname)(outputPath);
1055
- if (!(0, import_fs.existsSync)(dir)) {
1056
- (0, import_fs.mkdirSync)(dir, { recursive: true });
1057
- }
1058
- (0, import_fs.writeFileSync)(outputPath, JSON.stringify(jsonOutput, null, 2));
1059
- console.log(import_chalk.default.green(`
1060
- \u2713 JSON report saved to ${outputPath}`));
1061
- return;
1062
- }
1063
- if (options.output === "html") {
1064
- const html = generateHTMLReport(summary, results);
1065
- const outputPath = (0, import_core9.resolveOutputPath)(
1066
- options.outputFile,
1067
- `pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
1068
- directory
1069
- );
1070
- const dir = (0, import_path2.dirname)(outputPath);
1071
- if (!(0, import_fs.existsSync)(dir)) {
1072
- (0, import_fs.mkdirSync)(dir, { recursive: true });
1073
- }
1074
- (0, import_fs.writeFileSync)(outputPath, html);
1075
- console.log(import_chalk.default.green(`
1076
- \u2713 HTML report saved to ${outputPath}`));
1077
- return;
1078
- }
1079
- (0, import_core9.printTerminalHeader)("PATTERN ANALYSIS SUMMARY");
1080
- console.log(import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(results.length)}`));
936
+ return finalOptions;
937
+ }
938
+
939
+ // src/cli-output.ts
940
+ var import_core9 = require("@aiready/core");
941
+ function getPatternIcon(type) {
942
+ const icons = {
943
+ "api-handler": "\u{1F50C}",
944
+ validator: "\u{1F6E1}\uFE0F",
945
+ utility: "\u2699\uFE0F",
946
+ "class-method": "\u{1F3DB}\uFE0F",
947
+ component: "\u{1F9E9}",
948
+ function: "\u{1D453}",
949
+ unknown: "\u2753"
950
+ };
951
+ return icons[type] || icons.unknown;
952
+ }
953
+ function generateHTMLReport(results, summary) {
954
+ const data = summary ? { results, summary, metadata: { version: "0.11.22" } } : results;
955
+ const { metadata, summary: s } = data;
956
+ const scoreValue = Math.max(
957
+ 0,
958
+ 100 - Math.round((s.duplicates?.length || 0) / (s.totalFiles || 1) * 20)
959
+ );
960
+ const tableRows = (s.duplicates || []).map((dup) => [
961
+ `<span class="${dup.similarity > 0.95 ? "critical" : dup.similarity > 0.9 ? "major" : "minor"}">${Math.round(dup.similarity * 100)}%</span>`,
962
+ dup.patternType,
963
+ dup.files.map((f) => `<code>${f.path}:${f.startLine}-${f.endLine}</code>`).join("<br>\u2194<br>"),
964
+ dup.tokenCost.toLocaleString()
965
+ ]);
966
+ return (0, import_core9.generateStandardHtmlReport)(
967
+ {
968
+ title: "Pattern Detection Report",
969
+ packageName: "pattern-detect",
970
+ packageUrl: "https://github.com/caopengau/aiready-pattern-detect",
971
+ bugUrl: "https://github.com/caopengau/aiready-pattern-detect/issues",
972
+ version: metadata.version,
973
+ emoji: "\u{1F50D}"
974
+ },
975
+ [
976
+ { value: s.totalFiles, label: "Files Analyzed" },
977
+ { value: s.duplicates?.length || 0, label: "Duplicate Clusters" },
978
+ { value: s.totalIssues, label: "Total Issues" }
979
+ ],
980
+ [
981
+ {
982
+ title: "Duplicate Patterns",
983
+ content: (0, import_core9.generateTable)({
984
+ headers: ["Similarity", "Type", "Locations", "Tokens Wasted"],
985
+ rows: tableRows
986
+ })
987
+ }
988
+ ],
989
+ { value: `${scoreValue}%`, label: "AI Ready Score (Deduplication)" }
990
+ );
991
+ }
992
+
993
+ // src/terminal-output.ts
994
+ var import_chalk = __toESM(require("chalk"));
995
+ var import_core10 = require("@aiready/core");
996
+ function printAnalysisSummary(resultsLength, totalIssues, totalTokenCost, elapsedTime) {
997
+ (0, import_core10.printTerminalHeader)("PATTERN ANALYSIS SUMMARY");
998
+ console.log(import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(resultsLength)}`));
1081
999
  console.log(
1082
1000
  import_chalk.default.yellow(
1083
1001
  `\u26A0 AI confusion patterns detected: ${import_chalk.default.bold(totalIssues)}`
@@ -1085,15 +1003,17 @@ async function patternActionHandler(directory, options) {
1085
1003
  );
1086
1004
  console.log(
1087
1005
  import_chalk.default.red(
1088
- `\u{1F4B0} Token cost (wasted): ${import_chalk.default.bold(summary.totalTokenCost.toLocaleString())}`
1006
+ `\u{1F4B0} Token cost (wasted): ${import_chalk.default.bold(totalTokenCost.toLocaleString())}`
1089
1007
  )
1090
1008
  );
1091
1009
  console.log(import_chalk.default.gray(`\u23F1 Analysis time: ${import_chalk.default.bold(elapsedTime + "s")}`));
1092
- const sortedTypes = Object.entries(summary.patternsByType).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
1010
+ }
1011
+ function printPatternBreakdown(patternsByType) {
1012
+ const sortedTypes = Object.entries(patternsByType).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
1093
1013
  if (sortedTypes.length > 0) {
1094
- console.log("\n" + (0, import_core9.getTerminalDivider)());
1014
+ console.log("\n" + (0, import_core10.getTerminalDivider)());
1095
1015
  console.log(import_chalk.default.bold.white(" PATTERNS BY TYPE"));
1096
- console.log((0, import_core9.getTerminalDivider)() + "\n");
1016
+ console.log((0, import_core10.getTerminalDivider)() + "\n");
1097
1017
  sortedTypes.forEach(([type, count]) => {
1098
1018
  const icon = getPatternIcon(type);
1099
1019
  console.log(
@@ -1101,188 +1021,301 @@ async function patternActionHandler(directory, options) {
1101
1021
  );
1102
1022
  });
1103
1023
  }
1104
- if (!finalOptions.showRawDuplicates && groups && groups.length > 0) {
1105
- console.log("\n" + (0, import_core9.getTerminalDivider)());
1024
+ }
1025
+ function printDuplicateGroups(groups, maxResults) {
1026
+ if (groups.length === 0) return;
1027
+ console.log("\n" + (0, import_core10.getTerminalDivider)());
1028
+ console.log(
1029
+ import_chalk.default.bold.white(` \u{1F4E6} DUPLICATE GROUPS (${groups.length} file pairs)`)
1030
+ );
1031
+ console.log((0, import_core10.getTerminalDivider)() + "\n");
1032
+ const topGroups = groups.sort((a, b) => {
1033
+ const bVal = (0, import_core10.getSeverityValue)(b.severity);
1034
+ const aVal = (0, import_core10.getSeverityValue)(a.severity);
1035
+ const severityDiff = bVal - aVal;
1036
+ if (severityDiff !== 0) return severityDiff;
1037
+ return b.totalTokenCost - a.totalTokenCost;
1038
+ }).slice(0, maxResults);
1039
+ topGroups.forEach((group, idx) => {
1040
+ const severityBadge = (0, import_core10.getSeverityBadge)(group.severity);
1041
+ const [file1, file2] = group.filePair.split("::");
1042
+ const file1Name = file1.split("/").pop() || file1;
1043
+ const file2Name = file2.split("/").pop() || file2;
1106
1044
  console.log(
1107
- import_chalk.default.bold.white(` \u{1F4E6} DUPLICATE GROUPS (${groups.length} file pairs)`)
1045
+ `${idx + 1}. ${severityBadge} ${import_chalk.default.bold(file1Name)} \u2194 ${import_chalk.default.bold(file2Name)}`
1108
1046
  );
1109
- console.log((0, import_core9.getTerminalDivider)() + "\n");
1110
- const topGroups = groups.sort((a, b) => {
1111
- const bVal = (0, import_core9.getSeverityValue)(b.severity);
1112
- const aVal = (0, import_core9.getSeverityValue)(a.severity);
1113
- const severityDiff = bVal - aVal;
1114
- if (severityDiff !== 0) return severityDiff;
1115
- return b.totalTokenCost - a.totalTokenCost;
1116
- }).slice(0, finalOptions.maxResults);
1117
- topGroups.forEach((group, idx) => {
1118
- const severityBadge = (0, import_core9.getSeverityBadge)(group.severity);
1119
- const [file1, file2] = group.filePair.split("::");
1120
- const file1Name = file1.split("/").pop() || file1;
1121
- const file2Name = file2.split("/").pop() || file2;
1122
- console.log(
1123
- `${idx + 1}. ${severityBadge} ${import_chalk.default.bold(file1Name)} \u2194 ${import_chalk.default.bold(file2Name)}`
1124
- );
1047
+ console.log(
1048
+ ` Occurrences: ${import_chalk.default.bold(group.occurrences)} | Total tokens: ${import_chalk.default.bold(group.totalTokenCost.toLocaleString())} | Avg similarity: ${import_chalk.default.bold(Math.round(group.averageSimilarity * 100) + "%")}`
1049
+ );
1050
+ const displayRanges = group.lineRanges.slice(0, 3);
1051
+ displayRanges.forEach((range) => {
1125
1052
  console.log(
1126
- ` Occurrences: ${import_chalk.default.bold(group.occurrences)} | Total tokens: ${import_chalk.default.bold(group.totalTokenCost.toLocaleString())} | Avg similarity: ${import_chalk.default.bold(Math.round(group.averageSimilarity * 100) + "%")}`
1053
+ ` ${import_chalk.default.gray(file1)}:${import_chalk.default.cyan(`${range.file1.start}-${range.file1.end}`)} \u2194 ${import_chalk.default.gray(file2)}:${import_chalk.default.cyan(`${range.file2.start}-${range.file2.end}`)}`
1127
1054
  );
1128
- const displayRanges = group.lineRanges.slice(0, 3);
1129
- displayRanges.forEach((range) => {
1130
- console.log(
1131
- ` ${import_chalk.default.gray(file1)}:${import_chalk.default.cyan(`${range.file1.start}-${range.file1.end}`)} \u2194 ${import_chalk.default.gray(file2)}:${import_chalk.default.cyan(`${range.file2.start}-${range.file2.end}`)}`
1132
- );
1133
- });
1134
- if (group.lineRanges.length > 3) {
1135
- console.log(
1136
- ` ${import_chalk.default.gray(`... and ${group.lineRanges.length - 3} more ranges`)}`
1137
- );
1138
- }
1139
- console.log();
1140
1055
  });
1141
- if (groups.length > topGroups.length) {
1056
+ if (group.lineRanges.length > 3) {
1142
1057
  console.log(
1143
- import_chalk.default.gray(
1144
- ` ... and ${groups.length - topGroups.length} more file pairs`
1145
- )
1058
+ ` ${import_chalk.default.gray(`... and ${group.lineRanges.length - 3} more ranges`)}`
1146
1059
  );
1147
1060
  }
1148
- }
1149
- if (!finalOptions.showRawDuplicates && clusters && clusters.length > 0) {
1150
- console.log("\n" + (0, import_core9.getTerminalDivider)());
1061
+ console.log();
1062
+ });
1063
+ if (groups.length > topGroups.length) {
1151
1064
  console.log(
1152
- import_chalk.default.bold.white(` \u{1F3AF} REFACTOR CLUSTERS (${clusters.length} patterns)`)
1065
+ import_chalk.default.gray(
1066
+ ` ... and ${groups.length - topGroups.length} more file pairs`
1067
+ )
1153
1068
  );
1154
- console.log((0, import_core9.getTerminalDivider)() + "\n");
1155
- clusters.sort((a, b) => b.totalTokenCost - a.totalTokenCost).forEach((cluster, idx) => {
1156
- const severityBadge = (0, import_core9.getSeverityBadge)(cluster.severity);
1157
- console.log(`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(cluster.name)}`);
1158
- console.log(
1159
- ` Total tokens: ${import_chalk.default.bold(cluster.totalTokenCost.toLocaleString())} | Avg similarity: ${import_chalk.default.bold(Math.round(cluster.averageSimilarity * 100) + "%")} | Duplicates: ${import_chalk.default.bold(cluster.duplicateCount)}`
1160
- );
1161
- const displayFiles = cluster.files.slice(0, 5);
1162
- console.log(
1163
- ` Files (${cluster.files.length}): ${displayFiles.map((f) => import_chalk.default.gray(f.split("/").pop() || f)).join(", ")}`
1164
- );
1165
- if (cluster.files.length > 5) {
1166
- console.log(
1167
- ` ${import_chalk.default.gray(`... and ${cluster.files.length - 5} more files`)}`
1168
- );
1169
- }
1170
- if (cluster.reason) {
1171
- console.log(` ${import_chalk.default.italic.gray(cluster.reason)}`);
1172
- }
1173
- if (cluster.suggestion) {
1174
- console.log(
1175
- ` ${import_chalk.default.cyan("\u2192")} ${import_chalk.default.italic(cluster.suggestion)}`
1176
- );
1177
- }
1178
- console.log();
1179
- });
1180
1069
  }
1181
- if (totalIssues > 0 && (finalOptions.showRawDuplicates || !groups || groups.length === 0)) {
1182
- console.log("\n" + (0, import_core9.getTerminalDivider)());
1183
- console.log(import_chalk.default.bold.white(" TOP DUPLICATE PATTERNS"));
1184
- console.log((0, import_core9.getTerminalDivider)() + "\n");
1185
- const topDuplicates = filteredDuplicates.sort((a, b) => {
1186
- const bVal = (0, import_core9.getSeverityValue)(b.severity);
1187
- const aVal = (0, import_core9.getSeverityValue)(a.severity);
1188
- const severityDiff = bVal - aVal;
1189
- if (severityDiff !== 0) return severityDiff;
1190
- return b.similarity - a.similarity;
1191
- }).slice(0, finalOptions.maxResults);
1192
- topDuplicates.forEach((dup) => {
1193
- const severityBadge = (0, import_core9.getSeverityBadge)(dup.severity);
1194
- const file1Name = dup.file1.split("/").pop() || dup.file1;
1195
- const file2Name = dup.file2.split("/").pop() || dup.file2;
1196
- console.log(
1197
- `${severityBadge} ${import_chalk.default.bold(file1Name)} \u2194 ${import_chalk.default.bold(file2Name)}`
1198
- );
1199
- console.log(
1200
- ` Similarity: ${import_chalk.default.bold(Math.round(dup.similarity * 100) + "%")} | Pattern: ${dup.patternType} | Tokens: ${import_chalk.default.bold(dup.tokenCost.toLocaleString())}`
1201
- );
1202
- console.log(
1203
- ` ${import_chalk.default.gray(dup.file1)}:${import_chalk.default.cyan(dup.line1 + "-" + dup.endLine1)}`
1204
- );
1205
- console.log(
1206
- ` ${import_chalk.default.gray(dup.file2)}:${import_chalk.default.cyan(dup.line2 + "-" + dup.endLine2)}`
1207
- );
1208
- if (dup.reason) {
1209
- console.log(` ${import_chalk.default.italic.gray(dup.reason)}`);
1210
- }
1211
- if (dup.suggestion) {
1212
- console.log(` ${import_chalk.default.cyan("\u2192")} ${import_chalk.default.italic(dup.suggestion)}`);
1213
- }
1214
- console.log();
1215
- });
1216
- if (filteredDuplicates.length > topDuplicates.length) {
1070
+ }
1071
+ function printRefactorClusters(clusters) {
1072
+ if (clusters.length === 0) return;
1073
+ console.log("\n" + (0, import_core10.getTerminalDivider)());
1074
+ console.log(
1075
+ import_chalk.default.bold.white(` \u{1F3AF} REFACTOR CLUSTERS (${clusters.length} patterns)`)
1076
+ );
1077
+ console.log((0, import_core10.getTerminalDivider)() + "\n");
1078
+ clusters.sort((a, b) => b.totalTokenCost - a.totalTokenCost).forEach((cluster, idx) => {
1079
+ const severityBadge = (0, import_core10.getSeverityBadge)(cluster.severity);
1080
+ console.log(`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(cluster.name)}`);
1081
+ console.log(
1082
+ ` Total tokens: ${import_chalk.default.bold(cluster.totalTokenCost.toLocaleString())} | Avg similarity: ${import_chalk.default.bold(Math.round(cluster.averageSimilarity * 100) + "%")} | Duplicates: ${import_chalk.default.bold(cluster.duplicateCount)}`
1083
+ );
1084
+ const displayFiles = cluster.files.slice(0, 5);
1085
+ console.log(
1086
+ ` Files (${cluster.files.length}): ${displayFiles.map((f) => import_chalk.default.gray(f.split("/").pop() || f)).join(", ")}`
1087
+ );
1088
+ if (cluster.files.length > 5) {
1217
1089
  console.log(
1218
- import_chalk.default.gray(
1219
- ` ... and ${filteredDuplicates.length - topDuplicates.length} more duplicates`
1220
- )
1090
+ ` ${import_chalk.default.gray(`... and ${cluster.files.length - 5} more files`)}`
1221
1091
  );
1222
1092
  }
1223
- }
1224
- const allIssues = results.flatMap(
1225
- (r) => r.issues.map((issue) => ({ ...issue, file: r.fileName }))
1226
- );
1227
- const criticalIssues = allIssues.filter(
1228
- (issue) => (0, import_core9.getSeverityValue)(issue.severity) === 4
1229
- );
1230
- if (criticalIssues.length > 0) {
1231
- console.log((0, import_core9.getTerminalDivider)());
1232
- console.log(import_chalk.default.bold.white(" CRITICAL ISSUES (>95% similar)"));
1233
- console.log((0, import_core9.getTerminalDivider)() + "\n");
1234
- criticalIssues.slice(0, 5).forEach((issue) => {
1093
+ if (cluster.reason) {
1094
+ console.log(` ${import_chalk.default.italic.gray(cluster.reason)}`);
1095
+ }
1096
+ if (cluster.suggestion) {
1235
1097
  console.log(
1236
- import_chalk.default.red("\u25CF ") + import_chalk.default.white(`${issue.file}:${issue.location.line}`)
1098
+ ` ${import_chalk.default.cyan("\u2192")} ${import_chalk.default.italic(cluster.suggestion)}`
1237
1099
  );
1238
- console.log(` ${import_chalk.default.dim(issue.message)}`);
1239
- console.log(` ${import_chalk.default.green("\u2192")} ${import_chalk.default.italic(issue.suggestion)}
1240
- `);
1241
- });
1242
- }
1243
- if (totalIssues === 0) {
1244
- console.log(import_chalk.default.green("\n\u2728 Great! No duplicate patterns detected.\n"));
1100
+ }
1101
+ console.log();
1102
+ });
1103
+ }
1104
+ function printRawDuplicates(duplicates, maxResults) {
1105
+ if (duplicates.length === 0) return;
1106
+ console.log("\n" + (0, import_core10.getTerminalDivider)());
1107
+ console.log(import_chalk.default.bold.white(" TOP DUPLICATE PATTERNS"));
1108
+ console.log((0, import_core10.getTerminalDivider)() + "\n");
1109
+ const topDuplicates = duplicates.sort((a, b) => {
1110
+ const bVal = (0, import_core10.getSeverityValue)(b.severity);
1111
+ const aVal = (0, import_core10.getSeverityValue)(a.severity);
1112
+ const severityDiff = bVal - aVal;
1113
+ if (severityDiff !== 0) return severityDiff;
1114
+ return b.similarity - a.similarity;
1115
+ }).slice(0, maxResults);
1116
+ topDuplicates.forEach((dup) => {
1117
+ const severityBadge = (0, import_core10.getSeverityBadge)(dup.severity);
1118
+ const file1Name = dup.file1.split("/").pop() || dup.file1;
1119
+ const file2Name = dup.file2.split("/").pop() || dup.file2;
1245
1120
  console.log(
1246
- import_chalk.default.yellow(
1247
- "\u{1F4A1} If you expected to find duplicates, try adjusting parameters:"
1248
- )
1121
+ `${severityBadge} ${import_chalk.default.bold(file1Name)} \u2194 ${import_chalk.default.bold(file2Name)}`
1249
1122
  );
1250
- console.log(import_chalk.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3"));
1251
- console.log(import_chalk.default.dim(" \u2022 Reduce minimum lines: --min-lines 3"));
1252
- console.log(import_chalk.default.dim(" \u2022 Include test files: --include-tests"));
1253
1123
  console.log(
1254
- import_chalk.default.dim(" \u2022 Lower shared tokens threshold: --min-shared-tokens 5")
1124
+ ` Similarity: ${import_chalk.default.bold(Math.round(dup.similarity * 100) + "%")} | Pattern: ${dup.patternType} | Tokens: ${import_chalk.default.bold(dup.tokenCost.toLocaleString())}`
1125
+ );
1126
+ console.log(
1127
+ ` ${import_chalk.default.gray(dup.file1)}:${import_chalk.default.cyan(dup.line1 + "-" + dup.endLine1)}`
1255
1128
  );
1256
- console.log("");
1257
- }
1258
- if (totalIssues > 0 && totalIssues < 5) {
1259
1129
  console.log(
1260
- import_chalk.default.yellow("\n\u{1F4A1} Few results found. To find more duplicates, try:")
1130
+ ` ${import_chalk.default.gray(dup.file2)}:${import_chalk.default.cyan(dup.line2 + "-" + dup.endLine2)}`
1261
1131
  );
1262
- console.log(import_chalk.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3"));
1263
- console.log(import_chalk.default.dim(" \u2022 Reduce minimum lines: --min-lines 3"));
1264
- console.log(import_chalk.default.dim(" \u2022 Include test files: --include-tests"));
1132
+ if (dup.reason) {
1133
+ console.log(` ${import_chalk.default.italic.gray(dup.reason)}`);
1134
+ }
1135
+ if (dup.suggestion) {
1136
+ console.log(` ${import_chalk.default.cyan("\u2192")} ${import_chalk.default.italic(dup.suggestion)}`);
1137
+ }
1138
+ console.log();
1139
+ });
1140
+ if (duplicates.length > topDuplicates.length) {
1265
1141
  console.log(
1266
- import_chalk.default.dim(" \u2022 Lower shared tokens threshold: --min-shared-tokens 5")
1142
+ import_chalk.default.gray(
1143
+ ` ... and ${duplicates.length - topDuplicates.length} more duplicates`
1144
+ )
1267
1145
  );
1268
- console.log("");
1269
1146
  }
1270
- console.log((0, import_core9.getTerminalDivider)());
1147
+ }
1148
+ function printCriticalIssues(issues) {
1149
+ if (issues.length === 0) return;
1150
+ console.log((0, import_core10.getTerminalDivider)());
1151
+ console.log(import_chalk.default.bold.white(" CRITICAL ISSUES (>95% similar)"));
1152
+ console.log((0, import_core10.getTerminalDivider)() + "\n");
1153
+ issues.slice(0, 5).forEach((issue) => {
1154
+ console.log(
1155
+ import_chalk.default.red("\u25CF ") + import_chalk.default.white(`${issue.file}:${issue.location.line}`)
1156
+ );
1157
+ console.log(` ${import_chalk.default.dim(issue.message)}`);
1158
+ console.log(` ${import_chalk.default.green("\u2192")} ${import_chalk.default.italic(issue.suggestion)}
1159
+ `);
1160
+ });
1161
+ }
1162
+
1163
+ // src/cli-action.ts
1164
+ async function patternActionHandler(directory, options) {
1165
+ console.log(import_chalk2.default.blue("\u{1F50D} Analyzing patterns...\n"));
1166
+ const startTime = Date.now();
1167
+ const finalOptions = await resolvePatternConfig(directory, options);
1168
+ const {
1169
+ results,
1170
+ duplicates: rawDuplicates,
1171
+ groups,
1172
+ clusters
1173
+ } = await analyzePatterns(finalOptions);
1174
+ let filteredDuplicates = rawDuplicates;
1175
+ if (finalOptions.minSeverity) {
1176
+ filteredDuplicates = (0, import_core.filterBySeverity)(
1177
+ filteredDuplicates,
1178
+ finalOptions.minSeverity
1179
+ );
1180
+ }
1181
+ if (finalOptions.excludeTestFixtures) {
1182
+ filteredDuplicates = filteredDuplicates.filter(
1183
+ (d) => d.matchedRule !== "test-fixtures"
1184
+ );
1185
+ }
1186
+ if (finalOptions.excludeTemplates) {
1187
+ filteredDuplicates = filteredDuplicates.filter(
1188
+ (d) => d.matchedRule !== "templates"
1189
+ );
1190
+ }
1191
+ const elapsedTime = ((Date.now() - startTime) / 1e3).toFixed(2);
1192
+ const summary = generateSummary(results);
1193
+ const totalIssues = results.reduce((sum, r) => sum + r.issues.length, 0);
1194
+ if (options.output === "json") {
1195
+ handleJsonOutput(options.outputFile, directory, {
1196
+ summary,
1197
+ results,
1198
+ duplicates: rawDuplicates,
1199
+ groups: groups || [],
1200
+ clusters: clusters || [],
1201
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1202
+ });
1203
+ return;
1204
+ }
1205
+ if (options.output === "html") {
1206
+ handleHtmlOutput(options.outputFile, directory, summary, results);
1207
+ return;
1208
+ }
1209
+ renderTerminalOutput(
1210
+ results.length,
1211
+ totalIssues,
1212
+ summary,
1213
+ elapsedTime,
1214
+ finalOptions,
1215
+ groups,
1216
+ clusters,
1217
+ filteredDuplicates
1218
+ );
1219
+ }
1220
+ function handleJsonOutput(outputFile, directory, data) {
1221
+ const outputPath = (0, import_core11.resolveOutputPath)(
1222
+ outputFile,
1223
+ `pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
1224
+ directory
1225
+ );
1226
+ const dir = (0, import_path2.dirname)(outputPath);
1227
+ if (!(0, import_fs.existsSync)(dir)) (0, import_fs.mkdirSync)(dir, { recursive: true });
1228
+ (0, import_fs.writeFileSync)(outputPath, JSON.stringify(data, null, 2));
1229
+ console.log(import_chalk2.default.green(`
1230
+ \u2713 JSON report saved to ${outputPath}`));
1231
+ }
1232
+ function handleHtmlOutput(outputFile, directory, summary, results) {
1233
+ const html = generateHTMLReport(summary, results);
1234
+ const outputPath = (0, import_core11.resolveOutputPath)(
1235
+ outputFile,
1236
+ `pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
1237
+ directory
1238
+ );
1239
+ const dir = (0, import_path2.dirname)(outputPath);
1240
+ if (!(0, import_fs.existsSync)(dir)) (0, import_fs.mkdirSync)(dir, { recursive: true });
1241
+ (0, import_fs.writeFileSync)(outputPath, html);
1242
+ console.log(import_chalk2.default.green(`
1243
+ \u2713 HTML report saved to ${outputPath}`));
1244
+ }
1245
+ function renderTerminalOutput(fileCount, totalIssues, summary, elapsedTime, options, groups, clusters, filteredDuplicates) {
1246
+ printAnalysisSummary(
1247
+ fileCount,
1248
+ totalIssues,
1249
+ summary.totalTokenCost,
1250
+ elapsedTime
1251
+ );
1252
+ printPatternBreakdown(summary.patternsByType);
1253
+ if (!options.showRawDuplicates && groups && groups.length > 0) {
1254
+ printDuplicateGroups(groups, options.maxResults);
1255
+ }
1256
+ if (!options.showRawDuplicates && clusters && clusters.length > 0) {
1257
+ printRefactorClusters(clusters);
1258
+ }
1259
+ if (totalIssues > 0 && (options.showRawDuplicates || !groups || groups.length === 0)) {
1260
+ printRawDuplicates(filteredDuplicates, options.maxResults);
1261
+ }
1262
+ const criticalIssues = resultsToCriticalIssues(summary, filteredDuplicates);
1263
+ printCriticalIssues(criticalIssues);
1264
+ if (totalIssues === 0) {
1265
+ printSuccessMessage();
1266
+ } else if (totalIssues < 5) {
1267
+ printGuidance();
1268
+ }
1269
+ console.log((0, import_core11.getTerminalDivider)());
1271
1270
  if (totalIssues > 0) {
1272
1271
  console.log(
1273
- import_chalk.default.white(
1272
+ import_chalk2.default.white(
1274
1273
  `
1275
- \u{1F4A1} Run with ${import_chalk.default.bold("--output json")} or ${import_chalk.default.bold("--output html")} for detailed reports`
1274
+ \u{1F4A1} Run with ${import_chalk2.default.bold("--output json")} or ${import_chalk2.default.bold("--output html")} for detailed reports`
1276
1275
  )
1277
1276
  );
1278
1277
  }
1278
+ printFooter();
1279
+ }
1280
+ function resultsToCriticalIssues(summary, duplicates) {
1281
+ return duplicates.filter((d) => (0, import_core11.getSeverityValue)(d.severity) === 4).map((d) => ({
1282
+ file: d.file1,
1283
+ location: { line: d.line1 },
1284
+ message: `${d.patternType} pattern highly similar to ${d.file2}`,
1285
+ suggestion: d.suggestion,
1286
+ severity: d.severity
1287
+ }));
1288
+ }
1289
+ function printSuccessMessage() {
1290
+ console.log(import_chalk2.default.green("\n\u2728 Great! No duplicate patterns detected.\n"));
1291
+ console.log(
1292
+ import_chalk2.default.yellow(
1293
+ "\u{1F4A1} If you expected to find duplicates, try adjusting parameters:"
1294
+ )
1295
+ );
1296
+ console.log(import_chalk2.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3"));
1297
+ console.log(import_chalk2.default.dim(" \u2022 Reduce minimum lines: --min-lines 3"));
1298
+ console.log(import_chalk2.default.dim(" \u2022 Include test files: --include-tests"));
1299
+ console.log(
1300
+ import_chalk2.default.dim(" \u2022 Lower shared tokens threshold: --min-shared-tokens 5\n")
1301
+ );
1302
+ }
1303
+ function printGuidance() {
1304
+ console.log(
1305
+ import_chalk2.default.yellow("\n\u{1F4A1} Few results found. To find more duplicates, try:")
1306
+ );
1307
+ console.log(import_chalk2.default.dim(" \u2022 Lower similarity threshold: --similarity 0.3"));
1308
+ console.log(import_chalk2.default.dim(" \u2022 Reduce minimum lines: --min-lines 3"));
1309
+ console.log(import_chalk2.default.dim(" \u2022 Include test files: --include-tests\n"));
1310
+ }
1311
+ function printFooter() {
1279
1312
  console.log(
1280
- import_chalk.default.dim(
1313
+ import_chalk2.default.dim(
1281
1314
  "\n\u2B50 Like AIReady? Star us on GitHub: https://github.com/caopengau/aiready-pattern-detect"
1282
1315
  )
1283
1316
  );
1284
1317
  console.log(
1285
- import_chalk.default.dim(
1318
+ import_chalk2.default.dim(
1286
1319
  "\u{1F41B} Found a bug? Report it: https://github.com/caopengau/aiready-pattern-detect/issues\n"
1287
1320
  )
1288
1321
  );