@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.
- package/dist/analyzer-entry/index.mjs +3 -3
- package/dist/chunk-J2G742QF.mjs +162 -0
- package/dist/chunk-J5CW6NYY.mjs +64 -0
- package/dist/chunk-NQBYYWHJ.mjs +143 -0
- package/dist/chunk-SUUZMLPS.mjs +391 -0
- package/dist/cli.js +336 -303
- package/dist/cli.mjs +347 -303
- package/dist/context-rules-entry/index.d.mts +2 -2
- package/dist/context-rules-entry/index.d.ts +2 -2
- package/dist/context-rules-entry/index.js +2 -25
- package/dist/context-rules-entry/index.mjs +1 -1
- package/dist/detector-entry/index.mjs +2 -2
- package/dist/index-szjQDBsm.d.mts +49 -0
- package/dist/index-szjQDBsm.d.ts +49 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -25
- package/dist/index.mjs +6 -4
- package/package.json +2 -2
- package/dist/__tests__/context-rules.test.d.ts +0 -2
- package/dist/__tests__/context-rules.test.d.ts.map +0 -1
- package/dist/__tests__/context-rules.test.js +0 -189
- package/dist/__tests__/context-rules.test.js.map +0 -1
- package/dist/__tests__/detector.test.d.ts +0 -2
- package/dist/__tests__/detector.test.d.ts.map +0 -1
- package/dist/__tests__/detector.test.js +0 -259
- package/dist/__tests__/detector.test.js.map +0 -1
- package/dist/__tests__/grouping.test.d.ts +0 -2
- package/dist/__tests__/grouping.test.d.ts.map +0 -1
- package/dist/__tests__/grouping.test.js +0 -443
- package/dist/__tests__/grouping.test.js.map +0 -1
- package/dist/__tests__/scoring.test.d.ts +0 -2
- package/dist/__tests__/scoring.test.d.ts.map +0 -1
- package/dist/__tests__/scoring.test.js +0 -102
- 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
|
|
835
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
850
836
|
var import_fs = require("fs");
|
|
851
837
|
var import_path2 = require("path");
|
|
852
|
-
var
|
|
838
|
+
var import_core11 = require("@aiready/core");
|
|
853
839
|
|
|
854
|
-
// src/
|
|
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/
|
|
947
|
-
async function
|
|
948
|
-
|
|
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:
|
|
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,
|
|
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
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
|
1014
|
+
console.log("\n" + (0, import_core10.getTerminalDivider)());
|
|
1095
1015
|
console.log(import_chalk.default.bold.white(" PATTERNS BY TYPE"));
|
|
1096
|
-
console.log((0,
|
|
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
|
-
|
|
1105
|
-
|
|
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
|
|
1045
|
+
`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(file1Name)} \u2194 ${import_chalk.default.bold(file2Name)}`
|
|
1108
1046
|
);
|
|
1109
|
-
console.log(
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
-
`
|
|
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 (
|
|
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
|
-
|
|
1150
|
-
|
|
1061
|
+
console.log();
|
|
1062
|
+
});
|
|
1063
|
+
if (groups.length > topGroups.length) {
|
|
1151
1064
|
console.log(
|
|
1152
|
-
import_chalk.default.
|
|
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
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
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
|
-
|
|
1225
|
-
|
|
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.
|
|
1098
|
+
` ${import_chalk.default.cyan("\u2192")} ${import_chalk.default.italic(cluster.suggestion)}`
|
|
1237
1099
|
);
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
if (
|
|
1244
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1130
|
+
` ${import_chalk.default.gray(dup.file2)}:${import_chalk.default.cyan(dup.line2 + "-" + dup.endLine2)}`
|
|
1261
1131
|
);
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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.
|
|
1142
|
+
import_chalk.default.gray(
|
|
1143
|
+
` ... and ${duplicates.length - topDuplicates.length} more duplicates`
|
|
1144
|
+
)
|
|
1267
1145
|
);
|
|
1268
|
-
console.log("");
|
|
1269
1146
|
}
|
|
1270
|
-
|
|
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
|
-
|
|
1272
|
+
import_chalk2.default.white(
|
|
1274
1273
|
`
|
|
1275
|
-
\u{1F4A1} Run with ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
);
|