@aiready/pattern-detect 0.16.18 → 0.16.20
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-BVz-HnZd.d.mts +119 -0
- package/dist/analyzer-entry-BwuoiCNm.d.ts +119 -0
- package/dist/analyzer-entry.d.mts +3 -0
- package/dist/analyzer-entry.d.ts +3 -0
- package/dist/analyzer-entry.js +693 -0
- package/dist/analyzer-entry.mjs +12 -0
- package/dist/chunk-65UQ5J2J.mjs +64 -0
- package/dist/chunk-6JTVOBJX.mjs +64 -0
- package/dist/chunk-BKRPSTT2.mjs +64 -0
- package/dist/chunk-CMWW24HW.mjs +259 -0
- package/dist/chunk-DNZS4ESD.mjs +391 -0
- package/dist/chunk-GLKAGFKX.mjs +391 -0
- package/dist/chunk-GREN7X5H.mjs +143 -0
- package/dist/chunk-I6ETJC7L.mjs +179 -0
- package/dist/chunk-JBUZ6YHE.mjs +391 -0
- package/dist/chunk-KWMNN3TG.mjs +391 -0
- package/dist/chunk-LYKRYBSM.mjs +64 -0
- package/dist/chunk-MHU3CL4R.mjs +64 -0
- package/dist/chunk-RS73WLNI.mjs +251 -0
- package/dist/chunk-SVCSIZ2A.mjs +259 -0
- package/dist/chunk-THF4RW63.mjs +254 -0
- package/dist/chunk-UB3CGOQ7.mjs +64 -0
- package/dist/chunk-VGMM3L3O.mjs +143 -0
- package/dist/chunk-WBBO35SC.mjs +112 -0
- package/dist/chunk-WMOGJFME.mjs +391 -0
- package/dist/chunk-XNPID6FU.mjs +391 -0
- package/dist/cli.js +62 -219
- package/dist/cli.mjs +72 -97
- package/dist/context-rules-entry-y2uJSngh.d.mts +60 -0
- package/dist/context-rules-entry-y2uJSngh.d.ts +60 -0
- package/dist/context-rules-entry.d.mts +2 -0
- package/dist/context-rules-entry.d.ts +2 -0
- package/dist/context-rules-entry.js +207 -0
- package/dist/context-rules-entry.mjs +12 -0
- package/dist/detector-entry.d.mts +14 -0
- package/dist/detector-entry.d.ts +14 -0
- package/dist/detector-entry.js +301 -0
- package/dist/detector-entry.mjs +7 -0
- package/dist/index.d.mts +7 -235
- package/dist/index.d.ts +7 -235
- package/dist/index.js +9 -126
- package/dist/index.mjs +17 -9
- package/dist/scoring-entry.d.mts +23 -0
- package/dist/scoring-entry.d.ts +23 -0
- package/dist/scoring-entry.js +133 -0
- package/dist/scoring-entry.mjs +6 -0
- package/dist/types-DU2mmhwb.d.mts +36 -0
- package/dist/types-DU2mmhwb.d.ts +36 -0
- package/package.json +24 -4
package/dist/cli.js
CHANGED
|
@@ -192,144 +192,27 @@ function filterBySeverity(duplicates, minSeverity) {
|
|
|
192
192
|
});
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
// src/
|
|
195
|
+
// src/core/normalizer.ts
|
|
196
196
|
function normalizeCode(code, isPython = false) {
|
|
197
|
+
if (!code) return "";
|
|
197
198
|
let normalized = code;
|
|
198
199
|
if (isPython) {
|
|
199
200
|
normalized = normalized.replace(/#.*/g, "");
|
|
200
201
|
} else {
|
|
201
|
-
normalized = normalized.replace(
|
|
202
|
+
normalized = normalized.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
202
203
|
}
|
|
203
|
-
return normalized.replace(/[
|
|
204
|
+
return normalized.replace(/"[^"]*"/g, '"STR"').replace(/'[^']*'/g, "'STR'").replace(/`[^`]*`/g, "`STR`").replace(/\b\d+\b/g, "NUM").replace(/\s+/g, " ").trim().toLowerCase();
|
|
204
205
|
}
|
|
206
|
+
|
|
207
|
+
// src/detector.ts
|
|
205
208
|
function extractBlocks(file, content) {
|
|
206
|
-
|
|
207
|
-
if (isPython) {
|
|
208
|
-
return extractBlocksPython(file, content);
|
|
209
|
-
}
|
|
210
|
-
const blocks = [];
|
|
211
|
-
const lines = content.split("\n");
|
|
212
|
-
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;
|
|
213
|
-
let match;
|
|
214
|
-
while ((match = blockRegex.exec(content)) !== null) {
|
|
215
|
-
const startLine = content.substring(0, match.index).split("\n").length;
|
|
216
|
-
let type;
|
|
217
|
-
let name;
|
|
218
|
-
if (match[1]) {
|
|
219
|
-
type = match[1];
|
|
220
|
-
name = match[2];
|
|
221
|
-
} else if (match[3]) {
|
|
222
|
-
type = "const";
|
|
223
|
-
name = match[3];
|
|
224
|
-
} else {
|
|
225
|
-
type = "handler";
|
|
226
|
-
name = match[4];
|
|
227
|
-
}
|
|
228
|
-
let endLine = -1;
|
|
229
|
-
let openBraces = 0;
|
|
230
|
-
let foundStart = false;
|
|
231
|
-
for (let i = match.index; i < content.length; i++) {
|
|
232
|
-
if (content[i] === "{") {
|
|
233
|
-
openBraces++;
|
|
234
|
-
foundStart = true;
|
|
235
|
-
} else if (content[i] === "}") {
|
|
236
|
-
openBraces--;
|
|
237
|
-
}
|
|
238
|
-
if (foundStart && openBraces === 0) {
|
|
239
|
-
endLine = content.substring(0, i + 1).split("\n").length;
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (endLine === -1) {
|
|
244
|
-
const remaining = content.slice(match.index);
|
|
245
|
-
const nextLineMatch = remaining.indexOf("\n");
|
|
246
|
-
if (nextLineMatch !== -1) {
|
|
247
|
-
endLine = startLine;
|
|
248
|
-
} else {
|
|
249
|
-
endLine = lines.length;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
endLine = Math.max(startLine, endLine);
|
|
253
|
-
const blockCode = lines.slice(startLine - 1, endLine).join("\n");
|
|
254
|
-
const tokens = (0, import_core2.estimateTokens)(blockCode);
|
|
255
|
-
blocks.push({
|
|
256
|
-
file,
|
|
257
|
-
startLine,
|
|
258
|
-
endLine,
|
|
259
|
-
code: blockCode,
|
|
260
|
-
tokens,
|
|
261
|
-
patternType: inferPatternType(type, name)
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
return blocks;
|
|
265
|
-
}
|
|
266
|
-
function extractBlocksPython(file, content) {
|
|
267
|
-
const blocks = [];
|
|
268
|
-
const lines = content.split("\n");
|
|
269
|
-
const blockRegex = /^\s*(?:async\s+)?(def|class)\s+([a-zA-Z0-9_]+)/gm;
|
|
270
|
-
let match;
|
|
271
|
-
while ((match = blockRegex.exec(content)) !== null) {
|
|
272
|
-
const startLinePos = content.substring(0, match.index).split("\n").length;
|
|
273
|
-
const startLineIdx = startLinePos - 1;
|
|
274
|
-
const initialIndent = lines[startLineIdx].search(/\S/);
|
|
275
|
-
let endLineIdx = startLineIdx;
|
|
276
|
-
for (let i = startLineIdx + 1; i < lines.length; i++) {
|
|
277
|
-
const line = lines[i];
|
|
278
|
-
if (line.trim().length === 0) {
|
|
279
|
-
endLineIdx = i;
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
const currentIndent = line.search(/\S/);
|
|
283
|
-
if (currentIndent <= initialIndent) {
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
endLineIdx = i;
|
|
287
|
-
}
|
|
288
|
-
while (endLineIdx > startLineIdx && lines[endLineIdx].trim().length === 0) {
|
|
289
|
-
endLineIdx--;
|
|
290
|
-
}
|
|
291
|
-
const blockCode = lines.slice(startLineIdx, endLineIdx + 1).join("\n");
|
|
292
|
-
const tokens = (0, import_core2.estimateTokens)(blockCode);
|
|
293
|
-
blocks.push({
|
|
294
|
-
file,
|
|
295
|
-
startLine: startLinePos,
|
|
296
|
-
endLine: endLineIdx + 1,
|
|
297
|
-
code: blockCode,
|
|
298
|
-
tokens,
|
|
299
|
-
patternType: inferPatternType(match[1], match[2])
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
return blocks;
|
|
303
|
-
}
|
|
304
|
-
function inferPatternType(keyword, name) {
|
|
305
|
-
const n = name.toLowerCase();
|
|
306
|
-
if (keyword === "handler" || n.includes("handler") || n.includes("controller") || n.startsWith("app.")) {
|
|
307
|
-
return "api-handler";
|
|
308
|
-
}
|
|
309
|
-
if (n.includes("validate") || n.includes("schema")) return "validator";
|
|
310
|
-
if (n.includes("util") || n.includes("helper")) return "utility";
|
|
311
|
-
if (keyword === "class") return "class-method";
|
|
312
|
-
if (n.match(/^[A-Z]/)) return "component";
|
|
313
|
-
if (keyword === "function") return "function";
|
|
314
|
-
return "unknown";
|
|
209
|
+
return (0, import_core2.extractCodeBlocks)(file, content);
|
|
315
210
|
}
|
|
316
211
|
function calculateSimilarity(a, b) {
|
|
317
|
-
|
|
318
|
-
const tokensA = a.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 0);
|
|
319
|
-
const tokensB = b.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 0);
|
|
320
|
-
if (tokensA.length === 0 || tokensB.length === 0) return 0;
|
|
321
|
-
const setA = new Set(tokensA);
|
|
322
|
-
const setB = new Set(tokensB);
|
|
323
|
-
const intersection = new Set([...setA].filter((x) => setB.has(x)));
|
|
324
|
-
const union = /* @__PURE__ */ new Set([...setA, ...setB]);
|
|
325
|
-
return intersection.size / union.size;
|
|
212
|
+
return (0, import_core2.calculateStringSimilarity)(a, b);
|
|
326
213
|
}
|
|
327
214
|
function calculateConfidence(similarity, tokens, lines) {
|
|
328
|
-
|
|
329
|
-
if (lines > 20) confidence += 0.05;
|
|
330
|
-
if (tokens > 200) confidence += 0.05;
|
|
331
|
-
if (lines < 5) confidence -= 0.1;
|
|
332
|
-
return Math.max(0, Math.min(1, confidence));
|
|
215
|
+
return (0, import_core2.calculateHeuristicConfidence)(similarity, tokens, lines);
|
|
333
216
|
}
|
|
334
217
|
async function detectDuplicatePatterns(fileContents, options) {
|
|
335
218
|
const {
|
|
@@ -985,84 +868,49 @@ function getPatternIcon(type) {
|
|
|
985
868
|
function generateHTMLReport(results, summary) {
|
|
986
869
|
const data = summary ? { results, summary, metadata: { version: "0.11.22" } } : results;
|
|
987
870
|
const { metadata } = data;
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
.
|
|
1007
|
-
.
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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}
|
|
1013
910
|
<body>
|
|
1014
911
|
<h1>Pattern Detection Report</h1>
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
<div class="stat-value">${Math.max(0, 100 - Math.round((summary.duplicates?.length || 0) / (summary.totalFiles || 1) * 20))}%</div>
|
|
1018
|
-
</div>
|
|
1019
|
-
|
|
1020
|
-
<div class="stats">
|
|
1021
|
-
<div class="stat-card">
|
|
1022
|
-
<div class="stat-value">${summary.totalFiles}</div>
|
|
1023
|
-
<div class="stat-label">Files Analyzed</div>
|
|
1024
|
-
</div>
|
|
1025
|
-
<div class="stat-card">
|
|
1026
|
-
<div class="stat-value">${summary.duplicates?.length || 0}</div>
|
|
1027
|
-
<div class="stat-label">Duplicate Clusters</div>
|
|
1028
|
-
</div>
|
|
1029
|
-
<div class="stat-card">
|
|
1030
|
-
<div class="stat-value">${summary.totalIssues}</div>
|
|
1031
|
-
<div class="stat-label">Total Issues</div>
|
|
1032
|
-
</div>
|
|
1033
|
-
</div>
|
|
1034
|
-
|
|
1035
|
-
<div class="card">
|
|
1036
|
-
<h2>Duplicate Patterns</h2>
|
|
1037
|
-
<table>
|
|
1038
|
-
<thead>
|
|
1039
|
-
<tr>
|
|
1040
|
-
<th>Similarity</th>
|
|
1041
|
-
<th>Type</th>
|
|
1042
|
-
<th>Locations</th>
|
|
1043
|
-
<th>Tokens Wasted</th>
|
|
1044
|
-
</tr>
|
|
1045
|
-
</thead>
|
|
1046
|
-
<tbody>
|
|
1047
|
-
${(summary.duplicates || []).map(
|
|
1048
|
-
(dup) => `
|
|
1049
|
-
<tr>
|
|
1050
|
-
<td class="${dup.similarity > 0.95 ? "critical" : dup.similarity > 0.9 ? "major" : "minor"}">${Math.round(dup.similarity * 100)}%</td>
|
|
1051
|
-
<td>${dup.patternType}</td>
|
|
1052
|
-
<td>${dup.files.map((f) => `<code>${f.path}:${f.startLine}-${f.endLine}</code>`).join("<br>\u2194<br>")}</td>
|
|
1053
|
-
<td>${dup.tokenCost.toLocaleString()}</td>
|
|
1054
|
-
</tr>
|
|
1055
|
-
`
|
|
1056
|
-
).join("")}
|
|
1057
|
-
</tbody>
|
|
1058
|
-
</table>
|
|
1059
|
-
</div>
|
|
1060
|
-
|
|
1061
|
-
<div class="footer">
|
|
1062
|
-
<p>Generated by <strong>@aiready/pattern-detect</strong> v${metadata.version}</p>
|
|
1063
|
-
<p>Like AIReady? <a href="https://github.com/caopengau/aiready-pattern-detect">Star us on GitHub</a></p>
|
|
1064
|
-
<p>Found a bug? <a href="https://github.com/caopengau/aiready-pattern-detect/issues">Report it here</a></p>
|
|
1065
|
-
</div>
|
|
912
|
+
${body}
|
|
913
|
+
${footer}
|
|
1066
914
|
</body>
|
|
1067
915
|
</html>`;
|
|
1068
916
|
}
|
|
@@ -1228,12 +1076,7 @@ async function patternActionHandler(directory, options) {
|
|
|
1228
1076
|
\u2713 HTML report saved to ${outputPath}`));
|
|
1229
1077
|
return;
|
|
1230
1078
|
}
|
|
1231
|
-
|
|
1232
|
-
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
1233
|
-
const divider = "\u2501".repeat(dividerWidth);
|
|
1234
|
-
console.log(import_chalk.default.cyan(divider));
|
|
1235
|
-
console.log(import_chalk.default.bold.white(" PATTERN ANALYSIS SUMMARY"));
|
|
1236
|
-
console.log(import_chalk.default.cyan(divider) + "\n");
|
|
1079
|
+
(0, import_core9.printTerminalHeader)("PATTERN ANALYSIS SUMMARY");
|
|
1237
1080
|
console.log(import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(results.length)}`));
|
|
1238
1081
|
console.log(
|
|
1239
1082
|
import_chalk.default.yellow(
|
|
@@ -1248,9 +1091,9 @@ async function patternActionHandler(directory, options) {
|
|
|
1248
1091
|
console.log(import_chalk.default.gray(`\u23F1 Analysis time: ${import_chalk.default.bold(elapsedTime + "s")}`));
|
|
1249
1092
|
const sortedTypes = Object.entries(summary.patternsByType).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
1250
1093
|
if (sortedTypes.length > 0) {
|
|
1251
|
-
console.log(
|
|
1094
|
+
console.log("\n" + (0, import_core9.getTerminalDivider)());
|
|
1252
1095
|
console.log(import_chalk.default.bold.white(" PATTERNS BY TYPE"));
|
|
1253
|
-
console.log(
|
|
1096
|
+
console.log((0, import_core9.getTerminalDivider)() + "\n");
|
|
1254
1097
|
sortedTypes.forEach(([type, count]) => {
|
|
1255
1098
|
const icon = getPatternIcon(type);
|
|
1256
1099
|
console.log(
|
|
@@ -1259,11 +1102,11 @@ async function patternActionHandler(directory, options) {
|
|
|
1259
1102
|
});
|
|
1260
1103
|
}
|
|
1261
1104
|
if (!finalOptions.showRawDuplicates && groups && groups.length > 0) {
|
|
1262
|
-
console.log(
|
|
1105
|
+
console.log("\n" + (0, import_core9.getTerminalDivider)());
|
|
1263
1106
|
console.log(
|
|
1264
1107
|
import_chalk.default.bold.white(` \u{1F4E6} DUPLICATE GROUPS (${groups.length} file pairs)`)
|
|
1265
1108
|
);
|
|
1266
|
-
console.log(
|
|
1109
|
+
console.log((0, import_core9.getTerminalDivider)() + "\n");
|
|
1267
1110
|
const topGroups = groups.sort((a, b) => {
|
|
1268
1111
|
const bVal = (0, import_core9.getSeverityValue)(b.severity);
|
|
1269
1112
|
const aVal = (0, import_core9.getSeverityValue)(a.severity);
|
|
@@ -1304,11 +1147,11 @@ async function patternActionHandler(directory, options) {
|
|
|
1304
1147
|
}
|
|
1305
1148
|
}
|
|
1306
1149
|
if (!finalOptions.showRawDuplicates && clusters && clusters.length > 0) {
|
|
1307
|
-
console.log(
|
|
1150
|
+
console.log("\n" + (0, import_core9.getTerminalDivider)());
|
|
1308
1151
|
console.log(
|
|
1309
1152
|
import_chalk.default.bold.white(` \u{1F3AF} REFACTOR CLUSTERS (${clusters.length} patterns)`)
|
|
1310
1153
|
);
|
|
1311
|
-
console.log(
|
|
1154
|
+
console.log((0, import_core9.getTerminalDivider)() + "\n");
|
|
1312
1155
|
clusters.sort((a, b) => b.totalTokenCost - a.totalTokenCost).forEach((cluster, idx) => {
|
|
1313
1156
|
const severityBadge = (0, import_core9.getSeverityBadge)(cluster.severity);
|
|
1314
1157
|
console.log(`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(cluster.name)}`);
|
|
@@ -1336,9 +1179,9 @@ async function patternActionHandler(directory, options) {
|
|
|
1336
1179
|
});
|
|
1337
1180
|
}
|
|
1338
1181
|
if (totalIssues > 0 && (finalOptions.showRawDuplicates || !groups || groups.length === 0)) {
|
|
1339
|
-
console.log(
|
|
1182
|
+
console.log("\n" + (0, import_core9.getTerminalDivider)());
|
|
1340
1183
|
console.log(import_chalk.default.bold.white(" TOP DUPLICATE PATTERNS"));
|
|
1341
|
-
console.log(
|
|
1184
|
+
console.log((0, import_core9.getTerminalDivider)() + "\n");
|
|
1342
1185
|
const topDuplicates = filteredDuplicates.sort((a, b) => {
|
|
1343
1186
|
const bVal = (0, import_core9.getSeverityValue)(b.severity);
|
|
1344
1187
|
const aVal = (0, import_core9.getSeverityValue)(a.severity);
|
|
@@ -1385,9 +1228,9 @@ async function patternActionHandler(directory, options) {
|
|
|
1385
1228
|
(issue) => (0, import_core9.getSeverityValue)(issue.severity) === 4
|
|
1386
1229
|
);
|
|
1387
1230
|
if (criticalIssues.length > 0) {
|
|
1388
|
-
console.log(
|
|
1231
|
+
console.log((0, import_core9.getTerminalDivider)());
|
|
1389
1232
|
console.log(import_chalk.default.bold.white(" CRITICAL ISSUES (>95% similar)"));
|
|
1390
|
-
console.log(
|
|
1233
|
+
console.log((0, import_core9.getTerminalDivider)() + "\n");
|
|
1391
1234
|
criticalIssues.slice(0, 5).forEach((issue) => {
|
|
1392
1235
|
console.log(
|
|
1393
1236
|
import_chalk.default.red("\u25CF ") + import_chalk.default.white(`${issue.file}:${issue.location.line}`)
|
|
@@ -1424,7 +1267,7 @@ async function patternActionHandler(directory, options) {
|
|
|
1424
1267
|
);
|
|
1425
1268
|
console.log("");
|
|
1426
1269
|
}
|
|
1427
|
-
console.log(
|
|
1270
|
+
console.log((0, import_core9.getTerminalDivider)());
|
|
1428
1271
|
if (totalIssues > 0) {
|
|
1429
1272
|
console.log(
|
|
1430
1273
|
import_chalk.default.white(
|
package/dist/cli.mjs
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-6JTVOBJX.mjs";
|
|
2
3
|
import {
|
|
3
4
|
analyzePatterns,
|
|
4
|
-
filterBySeverity,
|
|
5
5
|
generateSummary
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DNZS4ESD.mjs";
|
|
7
|
+
import "./chunk-VGMM3L3O.mjs";
|
|
8
|
+
import {
|
|
9
|
+
filterBySeverity
|
|
10
|
+
} from "./chunk-I6ETJC7L.mjs";
|
|
11
|
+
import "./chunk-WBBO35SC.mjs";
|
|
7
12
|
|
|
8
13
|
// src/cli.ts
|
|
9
14
|
import { Command } from "commander";
|
|
@@ -18,11 +23,21 @@ import {
|
|
|
18
23
|
resolveOutputPath,
|
|
19
24
|
Severity,
|
|
20
25
|
getSeverityBadge as getSeverityBadge2,
|
|
21
|
-
getSeverityValue as getSeverityValue2
|
|
26
|
+
getSeverityValue as getSeverityValue2,
|
|
27
|
+
printTerminalHeader,
|
|
28
|
+
getTerminalDivider
|
|
22
29
|
} from "@aiready/core";
|
|
23
30
|
|
|
24
31
|
// src/cli-output.ts
|
|
25
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
getSeverityBadge,
|
|
34
|
+
getSeverityValue,
|
|
35
|
+
generateReportHead,
|
|
36
|
+
generateStatCards,
|
|
37
|
+
generateScoreCard,
|
|
38
|
+
generateTable,
|
|
39
|
+
generateReportFooter
|
|
40
|
+
} from "@aiready/core";
|
|
26
41
|
function getPatternIcon(type) {
|
|
27
42
|
const icons = {
|
|
28
43
|
"api-handler": "\u{1F50C}",
|
|
@@ -38,84 +53,49 @@ function getPatternIcon(type) {
|
|
|
38
53
|
function generateHTMLReport(results, summary) {
|
|
39
54
|
const data = summary ? { results, summary, metadata: { version: "0.11.22" } } : results;
|
|
40
55
|
const { metadata } = data;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
const s = data.summary;
|
|
57
|
+
const head = generateReportHead("AIReady - Pattern Detection Report");
|
|
58
|
+
const score = Math.max(
|
|
59
|
+
0,
|
|
60
|
+
100 - Math.round((s.duplicates?.length || 0) / (s.totalFiles || 1) * 20)
|
|
61
|
+
);
|
|
62
|
+
const scoreCard = generateScoreCard(
|
|
63
|
+
`${score}%`,
|
|
64
|
+
"AI Ready Score (Deduplication)"
|
|
65
|
+
);
|
|
66
|
+
const stats = generateStatCards([
|
|
67
|
+
{ value: s.totalFiles, label: "Files Analyzed" },
|
|
68
|
+
{ value: s.duplicates?.length || 0, label: "Duplicate Clusters" },
|
|
69
|
+
{ value: s.totalIssues, label: "Total Issues" }
|
|
70
|
+
]);
|
|
71
|
+
const tableRows = (s.duplicates || []).map((dup) => [
|
|
72
|
+
`<span class="${dup.similarity > 0.95 ? "critical" : dup.similarity > 0.9 ? "major" : "minor"}">${Math.round(dup.similarity * 100)}%</span>`,
|
|
73
|
+
dup.patternType,
|
|
74
|
+
dup.files.map((f) => `<code>${f.path}:${f.startLine}-${f.endLine}</code>`).join("<br>\u2194<br>"),
|
|
75
|
+
dup.tokenCost.toLocaleString()
|
|
76
|
+
]);
|
|
77
|
+
const table = generateTable({
|
|
78
|
+
headers: ["Similarity", "Type", "Locations", "Tokens Wasted"],
|
|
79
|
+
rows: tableRows
|
|
80
|
+
});
|
|
81
|
+
const body = `${scoreCard}
|
|
82
|
+
${stats}
|
|
83
|
+
<div class="card">
|
|
84
|
+
<h2>Duplicate Patterns</h2>
|
|
85
|
+
${table}
|
|
86
|
+
</div>`;
|
|
87
|
+
const footer = generateReportFooter({
|
|
88
|
+
title: "Pattern Detection Report",
|
|
89
|
+
packageName: "pattern-detect",
|
|
90
|
+
packageUrl: "https://github.com/caopengau/aiready-pattern-detect",
|
|
91
|
+
bugUrl: "https://github.com/caopengau/aiready-pattern-detect/issues",
|
|
92
|
+
version: metadata.version
|
|
93
|
+
});
|
|
94
|
+
return `${head}
|
|
66
95
|
<body>
|
|
67
96
|
<h1>Pattern Detection Report</h1>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<div class="stat-value">${Math.max(0, 100 - Math.round((summary.duplicates?.length || 0) / (summary.totalFiles || 1) * 20))}%</div>
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
<div class="stats">
|
|
74
|
-
<div class="stat-card">
|
|
75
|
-
<div class="stat-value">${summary.totalFiles}</div>
|
|
76
|
-
<div class="stat-label">Files Analyzed</div>
|
|
77
|
-
</div>
|
|
78
|
-
<div class="stat-card">
|
|
79
|
-
<div class="stat-value">${summary.duplicates?.length || 0}</div>
|
|
80
|
-
<div class="stat-label">Duplicate Clusters</div>
|
|
81
|
-
</div>
|
|
82
|
-
<div class="stat-card">
|
|
83
|
-
<div class="stat-value">${summary.totalIssues}</div>
|
|
84
|
-
<div class="stat-label">Total Issues</div>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
<div class="card">
|
|
89
|
-
<h2>Duplicate Patterns</h2>
|
|
90
|
-
<table>
|
|
91
|
-
<thead>
|
|
92
|
-
<tr>
|
|
93
|
-
<th>Similarity</th>
|
|
94
|
-
<th>Type</th>
|
|
95
|
-
<th>Locations</th>
|
|
96
|
-
<th>Tokens Wasted</th>
|
|
97
|
-
</tr>
|
|
98
|
-
</thead>
|
|
99
|
-
<tbody>
|
|
100
|
-
${(summary.duplicates || []).map(
|
|
101
|
-
(dup) => `
|
|
102
|
-
<tr>
|
|
103
|
-
<td class="${dup.similarity > 0.95 ? "critical" : dup.similarity > 0.9 ? "major" : "minor"}">${Math.round(dup.similarity * 100)}%</td>
|
|
104
|
-
<td>${dup.patternType}</td>
|
|
105
|
-
<td>${dup.files.map((f) => `<code>${f.path}:${f.startLine}-${f.endLine}</code>`).join("<br>\u2194<br>")}</td>
|
|
106
|
-
<td>${dup.tokenCost.toLocaleString()}</td>
|
|
107
|
-
</tr>
|
|
108
|
-
`
|
|
109
|
-
).join("")}
|
|
110
|
-
</tbody>
|
|
111
|
-
</table>
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
<div class="footer">
|
|
115
|
-
<p>Generated by <strong>@aiready/pattern-detect</strong> v${metadata.version}</p>
|
|
116
|
-
<p>Like AIReady? <a href="https://github.com/caopengau/aiready-pattern-detect">Star us on GitHub</a></p>
|
|
117
|
-
<p>Found a bug? <a href="https://github.com/caopengau/aiready-pattern-detect/issues">Report it here</a></p>
|
|
118
|
-
</div>
|
|
97
|
+
${body}
|
|
98
|
+
${footer}
|
|
119
99
|
</body>
|
|
120
100
|
</html>`;
|
|
121
101
|
}
|
|
@@ -281,12 +261,7 @@ async function patternActionHandler(directory, options) {
|
|
|
281
261
|
\u2713 HTML report saved to ${outputPath}`));
|
|
282
262
|
return;
|
|
283
263
|
}
|
|
284
|
-
|
|
285
|
-
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
286
|
-
const divider = "\u2501".repeat(dividerWidth);
|
|
287
|
-
console.log(chalk.cyan(divider));
|
|
288
|
-
console.log(chalk.bold.white(" PATTERN ANALYSIS SUMMARY"));
|
|
289
|
-
console.log(chalk.cyan(divider) + "\n");
|
|
264
|
+
printTerminalHeader("PATTERN ANALYSIS SUMMARY");
|
|
290
265
|
console.log(chalk.white(`\u{1F4C1} Files analyzed: ${chalk.bold(results.length)}`));
|
|
291
266
|
console.log(
|
|
292
267
|
chalk.yellow(
|
|
@@ -301,9 +276,9 @@ async function patternActionHandler(directory, options) {
|
|
|
301
276
|
console.log(chalk.gray(`\u23F1 Analysis time: ${chalk.bold(elapsedTime + "s")}`));
|
|
302
277
|
const sortedTypes = Object.entries(summary.patternsByType).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
303
278
|
if (sortedTypes.length > 0) {
|
|
304
|
-
console.log(
|
|
279
|
+
console.log("\n" + getTerminalDivider());
|
|
305
280
|
console.log(chalk.bold.white(" PATTERNS BY TYPE"));
|
|
306
|
-
console.log(
|
|
281
|
+
console.log(getTerminalDivider() + "\n");
|
|
307
282
|
sortedTypes.forEach(([type, count]) => {
|
|
308
283
|
const icon = getPatternIcon(type);
|
|
309
284
|
console.log(
|
|
@@ -312,11 +287,11 @@ async function patternActionHandler(directory, options) {
|
|
|
312
287
|
});
|
|
313
288
|
}
|
|
314
289
|
if (!finalOptions.showRawDuplicates && groups && groups.length > 0) {
|
|
315
|
-
console.log(
|
|
290
|
+
console.log("\n" + getTerminalDivider());
|
|
316
291
|
console.log(
|
|
317
292
|
chalk.bold.white(` \u{1F4E6} DUPLICATE GROUPS (${groups.length} file pairs)`)
|
|
318
293
|
);
|
|
319
|
-
console.log(
|
|
294
|
+
console.log(getTerminalDivider() + "\n");
|
|
320
295
|
const topGroups = groups.sort((a, b) => {
|
|
321
296
|
const bVal = getSeverityValue2(b.severity);
|
|
322
297
|
const aVal = getSeverityValue2(a.severity);
|
|
@@ -357,11 +332,11 @@ async function patternActionHandler(directory, options) {
|
|
|
357
332
|
}
|
|
358
333
|
}
|
|
359
334
|
if (!finalOptions.showRawDuplicates && clusters && clusters.length > 0) {
|
|
360
|
-
console.log(
|
|
335
|
+
console.log("\n" + getTerminalDivider());
|
|
361
336
|
console.log(
|
|
362
337
|
chalk.bold.white(` \u{1F3AF} REFACTOR CLUSTERS (${clusters.length} patterns)`)
|
|
363
338
|
);
|
|
364
|
-
console.log(
|
|
339
|
+
console.log(getTerminalDivider() + "\n");
|
|
365
340
|
clusters.sort((a, b) => b.totalTokenCost - a.totalTokenCost).forEach((cluster, idx) => {
|
|
366
341
|
const severityBadge = getSeverityBadge2(cluster.severity);
|
|
367
342
|
console.log(`${idx + 1}. ${severityBadge} ${chalk.bold(cluster.name)}`);
|
|
@@ -389,9 +364,9 @@ async function patternActionHandler(directory, options) {
|
|
|
389
364
|
});
|
|
390
365
|
}
|
|
391
366
|
if (totalIssues > 0 && (finalOptions.showRawDuplicates || !groups || groups.length === 0)) {
|
|
392
|
-
console.log(
|
|
367
|
+
console.log("\n" + getTerminalDivider());
|
|
393
368
|
console.log(chalk.bold.white(" TOP DUPLICATE PATTERNS"));
|
|
394
|
-
console.log(
|
|
369
|
+
console.log(getTerminalDivider() + "\n");
|
|
395
370
|
const topDuplicates = filteredDuplicates.sort((a, b) => {
|
|
396
371
|
const bVal = getSeverityValue2(b.severity);
|
|
397
372
|
const aVal = getSeverityValue2(a.severity);
|
|
@@ -438,9 +413,9 @@ async function patternActionHandler(directory, options) {
|
|
|
438
413
|
(issue) => getSeverityValue2(issue.severity) === 4
|
|
439
414
|
);
|
|
440
415
|
if (criticalIssues.length > 0) {
|
|
441
|
-
console.log(
|
|
416
|
+
console.log(getTerminalDivider());
|
|
442
417
|
console.log(chalk.bold.white(" CRITICAL ISSUES (>95% similar)"));
|
|
443
|
-
console.log(
|
|
418
|
+
console.log(getTerminalDivider() + "\n");
|
|
444
419
|
criticalIssues.slice(0, 5).forEach((issue) => {
|
|
445
420
|
console.log(
|
|
446
421
|
chalk.red("\u25CF ") + chalk.white(`${issue.file}:${issue.location.line}`)
|
|
@@ -477,7 +452,7 @@ async function patternActionHandler(directory, options) {
|
|
|
477
452
|
);
|
|
478
453
|
console.log("");
|
|
479
454
|
}
|
|
480
|
-
console.log(
|
|
455
|
+
console.log(getTerminalDivider());
|
|
481
456
|
if (totalIssues > 0) {
|
|
482
457
|
console.log(
|
|
483
458
|
chalk.white(
|