@aiready/pattern-detect 0.12.2 → 0.12.5

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/index.js CHANGED
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
- };
11
8
  var __export = (target, all) => {
12
9
  for (var name in all)
13
10
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -30,157 +27,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
27
  ));
31
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
29
 
33
- // src/extractors/python-extractor.ts
34
- var python_extractor_exports = {};
35
- __export(python_extractor_exports, {
36
- calculatePythonSimilarity: () => calculatePythonSimilarity,
37
- detectPythonAntiPatterns: () => detectPythonAntiPatterns,
38
- extractPythonPatterns: () => extractPythonPatterns
39
- });
40
- async function extractPythonPatterns(files) {
41
- const patterns = [];
42
- const parser = (0, import_core.getParser)("dummy.py");
43
- if (!parser) {
44
- console.warn("Python parser not available");
45
- return patterns;
46
- }
47
- const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
48
- for (const file of pythonFiles) {
49
- try {
50
- const fs = await import("fs");
51
- const code = await fs.promises.readFile(file, "utf-8");
52
- const result = parser.parse(code, file);
53
- for (const exp of result.exports) {
54
- if (exp.type === "function") {
55
- patterns.push({
56
- file,
57
- name: exp.name,
58
- type: "function",
59
- startLine: exp.loc?.start.line || 0,
60
- endLine: exp.loc?.end.line || 0,
61
- imports: exp.imports || [],
62
- dependencies: exp.dependencies || [],
63
- signature: generatePythonSignature(exp),
64
- language: "python"
65
- });
66
- } else if (exp.type === "class") {
67
- patterns.push({
68
- file,
69
- name: exp.name,
70
- type: "class",
71
- startLine: exp.loc?.start.line || 0,
72
- endLine: exp.loc?.end.line || 0,
73
- imports: exp.imports || [],
74
- dependencies: exp.dependencies || [],
75
- signature: `class ${exp.name}`,
76
- language: "python"
77
- });
78
- }
79
- }
80
- } catch (error) {
81
- console.warn(`Failed to extract patterns from ${file}:`, error);
82
- }
83
- }
84
- return patterns;
85
- }
86
- function generatePythonSignature(exp) {
87
- const params = exp.parameters?.join(", ") || "";
88
- return `def ${exp.name}(${params})`;
89
- }
90
- function calculatePythonSimilarity(pattern1, pattern2) {
91
- let similarity = 0;
92
- let factors = 0;
93
- const nameSimilarity = calculateNameSimilarity(pattern1.name, pattern2.name);
94
- similarity += nameSimilarity * 0.3;
95
- factors += 0.3;
96
- const importSimilarity = calculateImportSimilarity(
97
- pattern1.imports || [],
98
- pattern2.imports || []
99
- );
100
- similarity += importSimilarity * 0.4;
101
- factors += 0.4;
102
- if (pattern1.type === pattern2.type) {
103
- similarity += 0.1;
104
- }
105
- factors += 0.1;
106
- const sigSimilarity = calculateSignatureSimilarity(
107
- pattern1.signature,
108
- pattern2.signature
109
- );
110
- similarity += sigSimilarity * 0.2;
111
- factors += 0.2;
112
- return factors > 0 ? similarity / factors : 0;
113
- }
114
- function calculateNameSimilarity(name1, name2) {
115
- if (name1 === name2) return 1;
116
- const clean1 = name1.replace(
117
- /^(get|set|is|has|create|delete|update|fetch)_?/,
118
- ""
119
- );
120
- const clean2 = name2.replace(
121
- /^(get|set|is|has|create|delete|update|fetch)_?/,
122
- ""
123
- );
124
- if (clean1 === clean2) return 0.9;
125
- if (clean1.includes(clean2) || clean2.includes(clean1)) {
126
- return 0.7;
127
- }
128
- const set1 = new Set(clean1.split("_"));
129
- const set2 = new Set(clean2.split("_"));
130
- const intersection = new Set([...set1].filter((x) => set2.has(x)));
131
- const union = /* @__PURE__ */ new Set([...set1, ...set2]);
132
- return intersection.size / union.size;
133
- }
134
- function calculateImportSimilarity(imports1, imports2) {
135
- if (imports1.length === 0 && imports2.length === 0) return 1;
136
- if (imports1.length === 0 || imports2.length === 0) return 0;
137
- const set1 = new Set(imports1);
138
- const set2 = new Set(imports2);
139
- const intersection = new Set([...set1].filter((x) => set2.has(x)));
140
- const union = /* @__PURE__ */ new Set([...set1, ...set2]);
141
- return intersection.size / union.size;
142
- }
143
- function calculateSignatureSimilarity(sig1, sig2) {
144
- if (sig1 === sig2) return 1;
145
- const params1 = (sig1.match(/\([^)]*\)/)?.[0] || "").split(",").filter(Boolean).length;
146
- const params2 = (sig2.match(/\([^)]*\)/)?.[0] || "").split(",").filter(Boolean).length;
147
- if (params1 === params2) return 0.8;
148
- if (Math.abs(params1 - params2) === 1) return 0.5;
149
- return 0;
150
- }
151
- function detectPythonAntiPatterns(patterns) {
152
- const antiPatterns = [];
153
- const nameGroups = /* @__PURE__ */ new Map();
154
- for (const pattern of patterns) {
155
- const baseName = pattern.name.replace(
156
- /^(get|set|create|delete|update)_/,
157
- ""
158
- );
159
- if (!nameGroups.has(baseName)) {
160
- nameGroups.set(baseName, []);
161
- }
162
- nameGroups.get(baseName).push(pattern);
163
- }
164
- for (const [baseName, group] of nameGroups) {
165
- if (group.length >= 3) {
166
- antiPatterns.push(
167
- `Found ${group.length} functions with similar names (${baseName}): Consider consolidating`
168
- );
169
- }
170
- }
171
- return antiPatterns;
172
- }
173
- var import_core;
174
- var init_python_extractor = __esm({
175
- "src/extractors/python-extractor.ts"() {
176
- "use strict";
177
- import_core = require("@aiready/core");
178
- }
179
- });
180
-
181
30
  // src/index.ts
182
31
  var index_exports = {};
183
32
  __export(index_exports, {
33
+ Severity: () => import_core5.Severity,
184
34
  analyzePatterns: () => analyzePatterns,
185
35
  calculatePatternScore: () => calculatePatternScore,
186
36
  calculateSeverity: () => calculateSeverity,
@@ -191,12 +41,13 @@ __export(index_exports, {
191
41
  getSmartDefaults: () => getSmartDefaults
192
42
  });
193
43
  module.exports = __toCommonJS(index_exports);
194
- var import_core4 = require("@aiready/core");
44
+ var import_core5 = require("@aiready/core");
195
45
 
196
46
  // src/detector.ts
197
47
  var import_core2 = require("@aiready/core");
198
48
 
199
49
  // src/context-rules.ts
50
+ var import_core = require("@aiready/core");
200
51
  var CONTEXT_RULES = [
201
52
  // Test Fixtures - Intentional duplication for test isolation
202
53
  {
@@ -206,7 +57,7 @@ var CONTEXT_RULES = [
206
57
  const hasTestFixtures = code.includes("beforeAll") || code.includes("afterAll") || code.includes("beforeEach") || code.includes("afterEach") || code.includes("setUp") || code.includes("tearDown");
207
58
  return isTestFile && hasTestFixtures;
208
59
  },
209
- severity: "info",
60
+ severity: import_core.Severity.Info,
210
61
  reason: "Test fixture duplication is intentional for test isolation",
211
62
  suggestion: "Consider if shared test setup would improve maintainability without coupling tests"
212
63
  },
@@ -218,7 +69,7 @@ var CONTEXT_RULES = [
218
69
  const hasTemplateContent = (code.includes("return") || code.includes("export")) && (code.includes("html") || code.includes("subject") || code.includes("body"));
219
70
  return isTemplate && hasTemplateContent;
220
71
  },
221
- severity: "minor",
72
+ severity: import_core.Severity.Minor,
222
73
  reason: "Template duplication may be intentional for maintainability and branding consistency",
223
74
  suggestion: "Extract shared structure only if templates become hard to maintain"
224
75
  },
@@ -230,7 +81,7 @@ var CONTEXT_RULES = [
230
81
  const hasPageObjectPatterns = code.includes("page.") || code.includes("await page") || code.includes("locator") || code.includes("getBy") || code.includes("selector") || code.includes("click(") || code.includes("fill(");
231
82
  return isE2ETest && hasPageObjectPatterns;
232
83
  },
233
- severity: "minor",
84
+ severity: import_core.Severity.Minor,
234
85
  reason: "E2E test duplication ensures test independence and reduces coupling",
235
86
  suggestion: "Consider page object pattern only if duplication causes maintenance issues"
236
87
  },
@@ -240,7 +91,7 @@ var CONTEXT_RULES = [
240
91
  detect: (file) => {
241
92
  return file.endsWith(".config.ts") || file.endsWith(".config.js") || file.includes("jest.config") || file.includes("vite.config") || file.includes("webpack.config") || file.includes("rollup.config") || file.includes("tsconfig");
242
93
  },
243
- severity: "minor",
94
+ severity: import_core.Severity.Minor,
244
95
  reason: "Configuration files often have similar structure by design",
245
96
  suggestion: "Consider shared config base only if configurations become hard to maintain"
246
97
  },
@@ -252,7 +103,7 @@ var CONTEXT_RULES = [
252
103
  const hasTypeDefinitions = code.includes("interface ") || code.includes("type ") || code.includes("enum ");
253
104
  return isTypeFile && hasTypeDefinitions;
254
105
  },
255
- severity: "info",
106
+ severity: import_core.Severity.Info,
256
107
  reason: "Type duplication may be intentional for module independence and type safety",
257
108
  suggestion: "Extract to shared types package only if causing maintenance burden"
258
109
  },
@@ -262,7 +113,7 @@ var CONTEXT_RULES = [
262
113
  detect: (file) => {
263
114
  return file.includes("/migrations/") || file.includes("/migrate/") || file.includes(".migration.");
264
115
  },
265
- severity: "info",
116
+ severity: import_core.Severity.Info,
266
117
  reason: "Migration scripts are typically one-off and intentionally similar",
267
118
  suggestion: "Duplication is acceptable for migration scripts"
268
119
  },
@@ -274,7 +125,7 @@ var CONTEXT_RULES = [
274
125
  const hasMockData = code.includes("mock") || code.includes("Mock") || code.includes("fixture") || code.includes("stub") || code.includes("export const");
275
126
  return isMockFile && hasMockData;
276
127
  },
277
- severity: "info",
128
+ severity: import_core.Severity.Info,
278
129
  reason: "Mock data duplication is expected for comprehensive test coverage",
279
130
  suggestion: "Consider shared factories only for complex mock generation"
280
131
  }
@@ -292,31 +143,31 @@ function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
292
143
  }
293
144
  if (similarity >= 0.95 && linesOfCode >= 30) {
294
145
  return {
295
- severity: "critical",
146
+ severity: import_core.Severity.Critical,
296
147
  reason: "Large nearly-identical code blocks waste tokens and create maintenance burden",
297
148
  suggestion: "Extract to shared utility module immediately"
298
149
  };
299
150
  } else if (similarity >= 0.95 && linesOfCode >= 15) {
300
151
  return {
301
- severity: "major",
152
+ severity: import_core.Severity.Major,
302
153
  reason: "Nearly identical code should be consolidated",
303
154
  suggestion: "Move to shared utility file"
304
155
  };
305
156
  } else if (similarity >= 0.85) {
306
157
  return {
307
- severity: "major",
158
+ severity: import_core.Severity.Major,
308
159
  reason: "High similarity indicates significant duplication",
309
160
  suggestion: "Extract common logic to shared function"
310
161
  };
311
162
  } else if (similarity >= 0.7) {
312
163
  return {
313
- severity: "minor",
164
+ severity: import_core.Severity.Minor,
314
165
  reason: "Moderate similarity detected",
315
166
  suggestion: "Consider extracting shared patterns if code evolves together"
316
167
  };
317
168
  } else {
318
169
  return {
319
- severity: "minor",
170
+ severity: import_core.Severity.Minor,
320
171
  reason: "Minor similarity detected",
321
172
  suggestion: "Monitor but refactoring may not be worthwhile"
322
173
  };
@@ -324,15 +175,20 @@ function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
324
175
  }
325
176
  function getSeverityLabel(severity) {
326
177
  const labels = {
327
- critical: "\u{1F534} CRITICAL",
328
- major: "\u{1F7E1} MAJOR",
329
- minor: "\u{1F535} MINOR",
330
- info: "\u2139\uFE0F INFO"
178
+ [import_core.Severity.Critical]: "\u{1F534} CRITICAL",
179
+ [import_core.Severity.Major]: "\u{1F7E1} MAJOR",
180
+ [import_core.Severity.Minor]: "\u{1F535} MINOR",
181
+ [import_core.Severity.Info]: "\u2139\uFE0F INFO"
331
182
  };
332
183
  return labels[severity];
333
184
  }
334
185
  function filterBySeverity(duplicates, minSeverity) {
335
- const severityOrder = ["info", "minor", "major", "critical"];
186
+ const severityOrder = [
187
+ import_core.Severity.Info,
188
+ import_core.Severity.Minor,
189
+ import_core.Severity.Major,
190
+ import_core.Severity.Critical
191
+ ];
336
192
  const minIndex = severityOrder.indexOf(minSeverity);
337
193
  if (minIndex === -1) return duplicates;
338
194
  return duplicates.filter((dup) => {
@@ -341,261 +197,129 @@ function filterBySeverity(duplicates, minSeverity) {
341
197
  });
342
198
  }
343
199
 
344
- // src/core/extractor.ts
345
- function categorizePattern(code) {
346
- const lower = code.toLowerCase();
347
- if (lower.includes("request") && lower.includes("response") || lower.includes("router.") || lower.includes("app.get") || lower.includes("app.post") || lower.includes("express") || lower.includes("ctx.body")) {
348
- return "api-handler";
349
- }
350
- if (lower.includes("validate") || lower.includes("schema") || lower.includes("zod") || lower.includes("yup") || lower.includes("if") && lower.includes("throw")) {
351
- return "validator";
352
- }
353
- if (lower.includes("return (") || lower.includes("jsx") || lower.includes("component") || lower.includes("props")) {
354
- return "component";
355
- }
356
- if (lower.includes("class ") || lower.includes("this.")) {
357
- return "class-method";
358
- }
359
- if (lower.includes("return ") && !lower.includes("this") && !lower.includes("new ")) {
360
- return "utility";
361
- }
362
- if (lower.includes("function") || lower.includes("=>")) {
363
- return "function";
364
- }
365
- return "unknown";
200
+ // src/detector.ts
201
+ function normalizeCode(code) {
202
+ return code.replace(/\/\/.*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/['"`]/g, '"').replace(/\s+/g, " ").trim().toLowerCase();
366
203
  }
367
- function extractCodeBlocks(content, minLines) {
368
- const lines = content.split("\n");
204
+ function extractBlocks(file, content) {
369
205
  const blocks = [];
370
- let currentBlock = [];
371
- let blockStart = 0;
372
- let braceDepth = 0;
373
- let inFunction = false;
374
- for (let i = 0; i < lines.length; i++) {
375
- const line = lines[i];
376
- const trimmed = line.trim();
377
- if (!inFunction && (trimmed.includes("function ") || trimmed.includes("=>") || trimmed.includes("async ") || /^(export\s+)?(async\s+)?function\s+/.test(trimmed) || /^(export\s+)?const\s+\w+\s*=\s*(async\s*)?\(/.test(trimmed))) {
378
- inFunction = true;
379
- blockStart = i;
380
- }
381
- for (const char of line) {
382
- if (char === "{") braceDepth++;
383
- if (char === "}") braceDepth--;
206
+ const lines = content.split("\n");
207
+ const blockRegex = /^\s*(?:export\s+)?(?:async\s+)?(function|class|const|interface|type)\s+([a-zA-Z0-9_]+)|^\s*(app\.(?:get|post|put|delete|patch|use))\(/gm;
208
+ let match;
209
+ while ((match = blockRegex.exec(content)) !== null) {
210
+ const startLine = content.substring(0, match.index).split("\n").length;
211
+ let type;
212
+ let name;
213
+ if (match[1]) {
214
+ type = match[1];
215
+ name = match[2];
216
+ } else {
217
+ type = "handler";
218
+ name = match[3];
384
219
  }
385
- if (inFunction) {
386
- currentBlock.push(line);
220
+ let endLine = -1;
221
+ let openBraces = 0;
222
+ let foundStart = false;
223
+ for (let i = match.index; i < content.length; i++) {
224
+ if (content[i] === "{") {
225
+ openBraces++;
226
+ foundStart = true;
227
+ } else if (content[i] === "}") {
228
+ openBraces--;
229
+ }
230
+ if (foundStart && openBraces === 0) {
231
+ endLine = content.substring(0, i + 1).split("\n").length;
232
+ break;
233
+ }
387
234
  }
388
- if (inFunction && braceDepth === 0 && currentBlock.length >= minLines) {
389
- const blockContent = currentBlock.join("\n").trim();
390
- if (blockContent) {
391
- const loc = currentBlock.filter(
392
- (l) => l.trim() && !l.trim().startsWith("//")
393
- ).length;
394
- blocks.push({
395
- content: blockContent,
396
- startLine: blockStart + 1,
397
- endLine: i + 1,
398
- patternType: categorizePattern(blockContent),
399
- linesOfCode: loc
400
- });
235
+ if (endLine === -1) {
236
+ const remaining = content.slice(match.index);
237
+ const nextLineMatch = remaining.indexOf("\n");
238
+ if (nextLineMatch !== -1) {
239
+ endLine = startLine;
240
+ } else {
241
+ endLine = lines.length;
401
242
  }
402
- currentBlock = [];
403
- inFunction = false;
404
- } else if (inFunction && braceDepth === 0) {
405
- currentBlock = [];
406
- inFunction = false;
407
243
  }
244
+ endLine = Math.max(startLine, endLine);
245
+ const blockCode = lines.slice(startLine - 1, endLine).join("\n");
246
+ const tokens = (0, import_core2.estimateTokens)(blockCode);
247
+ blocks.push({
248
+ file,
249
+ startLine,
250
+ endLine,
251
+ code: blockCode,
252
+ tokens,
253
+ patternType: inferPatternType(type, name)
254
+ });
408
255
  }
409
256
  return blocks;
410
257
  }
411
-
412
- // src/core/normalizer.ts
413
- function normalizeCode(code) {
414
- if (!code) return "";
415
- return code.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/"[^"]*"/g, '"STR"').replace(/'[^']*'/g, "'STR'").replace(/`[^`]*`/g, "`STR`").replace(/\b\d+\b/g, "NUM").replace(/\s+/g, " ").trim();
416
- }
417
- var stopwords = /* @__PURE__ */ new Set([
418
- "return",
419
- "const",
420
- "let",
421
- "var",
422
- "function",
423
- "class",
424
- "new",
425
- "if",
426
- "else",
427
- "for",
428
- "while",
429
- "async",
430
- "await",
431
- "try",
432
- "catch",
433
- "switch",
434
- "case",
435
- "default",
436
- "import",
437
- "export",
438
- "from",
439
- "true",
440
- "false",
441
- "null",
442
- "undefined",
443
- "this"
444
- ]);
445
- function tokenize(norm) {
446
- const punctuation = "(){}[];.,";
447
- const cleaned = norm.split("").map((ch) => punctuation.includes(ch) ? " " : ch).join("");
448
- return cleaned.split(/\s+/).filter((t) => t && t.length >= 3 && !stopwords.has(t.toLowerCase()));
449
- }
450
-
451
- // src/core/similarity.ts
452
- function jaccardSimilarity(tokens1, tokens2) {
453
- const set1 = new Set(tokens1);
454
- const set2 = new Set(tokens2);
455
- if (set1.size === 0 && set2.size === 0) return 0;
456
- let intersection = 0;
457
- for (const token of set1) {
458
- if (set2.has(token)) intersection++;
258
+ function inferPatternType(keyword, name) {
259
+ const n = name.toLowerCase();
260
+ if (keyword === "handler" || n.includes("handler") || n.includes("controller") || n.startsWith("app.")) {
261
+ return "api-handler";
459
262
  }
460
- const union = set1.size + set2.size - intersection;
461
- return union === 0 ? 0 : intersection / union;
263
+ if (n.includes("validate") || n.includes("schema")) return "validator";
264
+ if (n.includes("util") || n.includes("helper")) return "utility";
265
+ if (keyword === "class") return "class-method";
266
+ if (n.match(/^[A-Z]/)) return "component";
267
+ if (keyword === "function") return "function";
268
+ return "unknown";
462
269
  }
463
-
464
- // src/core/approx-engine.ts
465
- var ApproxEngine = class {
466
- constructor(allBlocks, blockTokens) {
467
- this.invertedIndex = /* @__PURE__ */ new Map();
468
- this.allBlocks = allBlocks;
469
- this.blockTokens = blockTokens;
470
- this.buildIndex();
471
- }
472
- buildIndex() {
473
- for (let i = 0; i < this.blockTokens.length; i++) {
474
- for (const tok of this.blockTokens[i]) {
475
- let arr = this.invertedIndex.get(tok);
476
- if (!arr) {
477
- arr = [];
478
- this.invertedIndex.set(tok, arr);
479
- }
480
- arr.push(i);
481
- }
482
- }
483
- }
484
- findCandidates(blockIdx, minSharedTokens, maxCandidates) {
485
- const block1 = this.allBlocks[blockIdx];
486
- const block1Tokens = this.blockTokens[blockIdx];
487
- const counts = /* @__PURE__ */ new Map();
488
- const rareTokens = block1Tokens.filter((tok) => {
489
- const freq = this.invertedIndex.get(tok)?.length || 0;
490
- return freq < this.allBlocks.length * 0.1;
491
- });
492
- for (const tok of rareTokens) {
493
- const ids = this.invertedIndex.get(tok);
494
- if (!ids) continue;
495
- for (const j of ids) {
496
- if (j <= blockIdx) continue;
497
- if (this.allBlocks[j].file === block1.file) continue;
498
- counts.set(j, (counts.get(j) || 0) + 1);
499
- }
500
- }
501
- return Array.from(counts.entries()).filter(([j, shared]) => {
502
- const block2Size = this.blockTokens[j].length;
503
- const minSize = Math.min(block1Tokens.length, block2Size);
504
- return shared >= minSharedTokens && shared / minSize >= 0.3;
505
- }).sort((a, b) => b[1] - a[1]).slice(0, maxCandidates).map(([j, shared]) => ({ j, shared }));
506
- }
507
- };
508
-
509
- // src/detector.ts
510
- async function detectDuplicatePatterns(files, options) {
511
- const {
512
- minSimilarity,
513
- minLines,
514
- batchSize = 100,
515
- approx = true,
516
- minSharedTokens = 8,
517
- maxCandidatesPerBlock = 100,
518
- streamResults = false
519
- } = options;
520
- const duplicates = [];
521
- const maxComparisons = approx ? Infinity : 5e5;
522
- const allBlocks = files.flatMap(
523
- (file) => extractCodeBlocks(file.content, minLines).filter(
524
- (block) => block && block.content && block.content.trim().length > 0
525
- ).map((block) => ({
526
- ...block,
527
- file: file.file,
528
- normalized: normalizeCode(block.content),
529
- tokenCost: block.content ? (0, import_core2.estimateTokens)(block.content) : 0
530
- }))
531
- );
532
- const pythonFiles = files.filter((f) => f.file.endsWith(".py"));
533
- if (pythonFiles.length > 0) {
534
- const { extractPythonPatterns: extractPythonPatterns2 } = await Promise.resolve().then(() => (init_python_extractor(), python_extractor_exports));
535
- const pythonPatterns = await extractPythonPatterns2(
536
- pythonFiles.map((f) => f.file)
537
- );
270
+ function calculateSimilarity(a, b) {
271
+ if (a === b) return 1;
272
+ const tokensA = a.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 0);
273
+ const tokensB = b.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 0);
274
+ if (tokensA.length === 0 || tokensB.length === 0) return 0;
275
+ const setA = new Set(tokensA);
276
+ const setB = new Set(tokensB);
277
+ const intersection = new Set([...setA].filter((x) => setB.has(x)));
278
+ const union = /* @__PURE__ */ new Set([...setA, ...setB]);
279
+ return intersection.size / union.size;
280
+ }
281
+ async function detectDuplicatePatterns(fileContents, options) {
282
+ const { minSimilarity, minLines, streamResults } = options;
283
+ const allBlocks = [];
284
+ for (const { file, content } of fileContents) {
285
+ const blocks = extractBlocks(file, content);
538
286
  allBlocks.push(
539
- ...pythonPatterns.map((p) => ({
540
- content: p.code,
541
- startLine: p.startLine,
542
- endLine: p.endLine,
543
- file: p.file,
544
- normalized: normalizeCode(p.code),
545
- patternType: p.type,
546
- tokenCost: p.code ? (0, import_core2.estimateTokens)(p.code) : 0,
547
- linesOfCode: p.endLine - p.startLine + 1
548
- }))
287
+ ...blocks.filter((b) => b.endLine - b.startLine + 1 >= minLines)
549
288
  );
550
289
  }
551
- const blockTokens = allBlocks.map((b) => tokenize(b.normalized));
552
- const engine = approx ? new ApproxEngine(allBlocks, blockTokens) : null;
553
- let comparisonsProcessed = 0;
554
- const startTime = Date.now();
290
+ const duplicates = [];
555
291
  for (let i = 0; i < allBlocks.length; i++) {
556
- if (maxComparisons && comparisonsProcessed >= maxComparisons) break;
557
- if (i % batchSize === 0 && i > 0) {
558
- if (options.onProgress) {
559
- options.onProgress(i, allBlocks.length, "Analyzing patterns");
560
- } else {
561
- const elapsed = (Date.now() - startTime) / 1e3;
562
- console.log(
563
- ` Processed ${i}/${allBlocks.length} blocks (${elapsed.toFixed(1)}s, ${duplicates.length} duplicates)`
564
- );
565
- }
566
- await new Promise((r) => setImmediate((resolve) => r(resolve)));
567
- }
568
- const block1 = allBlocks[i];
569
- const candidates = engine ? engine.findCandidates(i, minSharedTokens, maxCandidatesPerBlock) : allBlocks.slice(i + 1).map((_, idx) => ({ j: i + 1 + idx, shared: 0 }));
570
- for (const { j } of candidates) {
571
- if (!approx && comparisonsProcessed >= maxComparisons) break;
572
- comparisonsProcessed++;
573
- const block2 = allBlocks[j];
574
- if (block1.file === block2.file) continue;
575
- const sim = jaccardSimilarity(blockTokens[i], blockTokens[j]);
292
+ for (let j = i + 1; j < allBlocks.length; j++) {
293
+ const b1 = allBlocks[i];
294
+ const b2 = allBlocks[j];
295
+ if (b1.file === b2.file) continue;
296
+ const norm1 = normalizeCode(b1.code);
297
+ const norm2 = normalizeCode(b2.code);
298
+ const sim = calculateSimilarity(norm1, norm2);
576
299
  if (sim >= minSimilarity) {
577
- const severity = calculateSeverity(
578
- block1.file,
579
- block2.file,
580
- block1.content,
300
+ const { severity, reason, suggestion, matchedRule } = calculateSeverity(
301
+ b1.file,
302
+ b2.file,
303
+ b1.code,
581
304
  sim,
582
- block1.linesOfCode
305
+ b1.endLine - b1.startLine + 1
583
306
  );
584
307
  const dup = {
585
- file1: block1.file,
586
- file2: block2.file,
587
- line1: block1.startLine,
588
- line2: block2.startLine,
589
- endLine1: block1.endLine,
590
- endLine2: block2.endLine,
308
+ file1: b1.file,
309
+ line1: b1.startLine,
310
+ endLine1: b1.endLine,
311
+ file2: b2.file,
312
+ line2: b2.startLine,
313
+ endLine2: b2.endLine,
314
+ code1: b1.code,
315
+ code2: b2.code,
591
316
  similarity: sim,
592
- snippet: block1.content.substring(0, 200),
593
- patternType: block1.patternType,
594
- tokenCost: block1.tokenCost,
595
- linesOfCode: block1.linesOfCode,
596
- severity: severity.severity,
597
- reason: severity.reason,
598
- suggestion: severity.suggestion
317
+ patternType: b1.patternType,
318
+ tokenCost: b1.tokens + b2.tokens,
319
+ severity,
320
+ reason,
321
+ suggestion,
322
+ matchedRule
599
323
  };
600
324
  duplicates.push(dup);
601
325
  if (streamResults)
@@ -605,281 +329,138 @@ async function detectDuplicatePatterns(files, options) {
605
329
  }
606
330
  }
607
331
  }
608
- return duplicates;
332
+ return duplicates.sort((a, b) => b.similarity - a.similarity);
609
333
  }
610
334
 
611
335
  // src/grouping.ts
612
- function normalizeFilePair(file1, file2) {
613
- return file1 < file2 ? `${file1}::${file2}` : `${file2}::${file1}`;
614
- }
615
- function rangesOverlap(start1, end1, start2, end2, tolerance = 5) {
616
- return start1 <= end2 + tolerance && start2 <= end1 + tolerance;
336
+ var import_core3 = require("@aiready/core");
337
+ var import_path = __toESM(require("path"));
338
+ function getSeverityLevel(s) {
339
+ if (s === import_core3.Severity.Critical || s === "critical") return 4;
340
+ if (s === import_core3.Severity.Major || s === "major") return 3;
341
+ if (s === import_core3.Severity.Minor || s === "minor") return 2;
342
+ if (s === import_core3.Severity.Info || s === "info") return 1;
343
+ return 0;
617
344
  }
618
345
  function groupDuplicatesByFilePair(duplicates) {
619
346
  const groups = /* @__PURE__ */ new Map();
620
347
  for (const dup of duplicates) {
621
- const key = normalizeFilePair(dup.file1, dup.file2);
348
+ const files = [dup.file1, dup.file2].sort();
349
+ const key = files.join("::");
622
350
  if (!groups.has(key)) {
623
- groups.set(key, []);
351
+ groups.set(key, {
352
+ filePair: key,
353
+ severity: dup.severity,
354
+ occurrences: 0,
355
+ totalTokenCost: 0,
356
+ averageSimilarity: 0,
357
+ patternTypes: /* @__PURE__ */ new Set(),
358
+ lineRanges: []
359
+ });
624
360
  }
625
- groups.get(key).push(dup);
626
- }
627
- const result = [];
628
- for (const [filePair, groupDups] of groups.entries()) {
629
- const deduplicated = deduplicateOverlappingRanges(groupDups);
630
- const totalTokenCost = deduplicated.reduce(
631
- (sum, d) => sum + d.tokenCost,
632
- 0
633
- );
634
- const averageSimilarity = deduplicated.reduce((sum, d) => sum + d.similarity, 0) / deduplicated.length;
635
- const maxSimilarity = Math.max(...deduplicated.map((d) => d.similarity));
636
- const severity = getHighestSeverity(deduplicated.map((d) => d.severity));
637
- const patternType = getMostCommonPatternType(deduplicated);
638
- const lineRanges = deduplicated.map((d) => ({
639
- file1: { start: d.line1, end: d.endLine1 },
640
- file2: { start: d.line2, end: d.endLine2 }
641
- }));
642
- result.push({
643
- filePair,
644
- duplicates: deduplicated,
645
- totalTokenCost,
646
- averageSimilarity,
647
- maxSimilarity,
648
- severity,
649
- patternType,
650
- occurrences: deduplicated.length,
651
- lineRanges
361
+ const group = groups.get(key);
362
+ group.occurrences++;
363
+ group.totalTokenCost += dup.tokenCost;
364
+ group.averageSimilarity += dup.similarity;
365
+ group.patternTypes.add(dup.patternType);
366
+ group.lineRanges.push({
367
+ file1: { start: dup.line1, end: dup.endLine1 },
368
+ file2: { start: dup.line2, end: dup.endLine2 }
652
369
  });
653
- }
654
- return result.sort((a, b) => b.totalTokenCost - a.totalTokenCost);
655
- }
656
- function deduplicateOverlappingRanges(duplicates) {
657
- if (duplicates.length === 0) return [];
658
- const sorted = [...duplicates].sort((a, b) => {
659
- if (a.line1 !== b.line1) return a.line1 - b.line1;
660
- return b.similarity - a.similarity;
661
- });
662
- const result = [];
663
- let current = null;
664
- for (const dup of sorted) {
665
- if (!current) {
666
- current = dup;
667
- result.push(dup);
668
- continue;
669
- }
670
- const overlapsFile1 = rangesOverlap(
671
- current.line1,
672
- current.endLine1,
673
- dup.line1,
674
- dup.endLine1
675
- );
676
- const overlapsFile2 = rangesOverlap(
677
- current.line2,
678
- current.endLine2,
679
- dup.line2,
680
- dup.endLine2
681
- );
682
- if (overlapsFile1 && overlapsFile2) {
683
- current = {
684
- ...current,
685
- endLine1: Math.max(current.endLine1, dup.endLine1),
686
- endLine2: Math.max(current.endLine2, dup.endLine2),
687
- tokenCost: Math.max(current.tokenCost, dup.tokenCost)
688
- };
689
- result[result.length - 1] = current;
690
- } else {
691
- current = dup;
692
- result.push(dup);
370
+ const currentSev = dup.severity;
371
+ if (getSeverityLevel(currentSev) > getSeverityLevel(group.severity)) {
372
+ group.severity = currentSev;
693
373
  }
694
374
  }
695
- return result;
375
+ return Array.from(groups.values()).map((g) => ({
376
+ ...g,
377
+ averageSimilarity: g.averageSimilarity / g.occurrences
378
+ }));
696
379
  }
697
380
  function createRefactorClusters(duplicates) {
698
- const clusters = /* @__PURE__ */ new Map();
381
+ const adjacency = /* @__PURE__ */ new Map();
382
+ const visited = /* @__PURE__ */ new Set();
383
+ const components = [];
699
384
  for (const dup of duplicates) {
700
- const clusterId = identifyCluster(dup);
701
- if (!clusters.has(clusterId)) {
702
- clusters.set(clusterId, []);
385
+ if (!adjacency.has(dup.file1)) adjacency.set(dup.file1, /* @__PURE__ */ new Set());
386
+ if (!adjacency.has(dup.file2)) adjacency.set(dup.file2, /* @__PURE__ */ new Set());
387
+ adjacency.get(dup.file1).add(dup.file2);
388
+ adjacency.get(dup.file2).add(dup.file1);
389
+ }
390
+ for (const file of adjacency.keys()) {
391
+ if (visited.has(file)) continue;
392
+ const component = [];
393
+ const queue = [file];
394
+ visited.add(file);
395
+ while (queue.length > 0) {
396
+ const curr = queue.shift();
397
+ component.push(curr);
398
+ for (const neighbor of adjacency.get(curr) || []) {
399
+ if (!visited.has(neighbor)) {
400
+ visited.add(neighbor);
401
+ queue.push(neighbor);
402
+ }
403
+ }
703
404
  }
704
- clusters.get(clusterId).push(dup);
405
+ components.push(component);
705
406
  }
706
- const result = [];
707
- for (const [clusterId, clusterDups] of clusters.entries()) {
708
- if (clusterDups.length < 2) continue;
709
- const files = getUniqueFiles(clusterDups);
710
- const totalTokenCost = clusterDups.reduce((sum, d) => sum + d.tokenCost, 0);
711
- const averageSimilarity = clusterDups.reduce((sum, d) => sum + d.similarity, 0) / clusterDups.length;
712
- const severity = getHighestSeverity(clusterDups.map((d) => d.severity));
713
- const patternType = getMostCommonPatternType(clusterDups);
714
- const clusterInfo = getClusterInfo(clusterId, patternType, files.length);
715
- result.push({
716
- id: clusterId,
717
- name: clusterInfo.name,
718
- files,
719
- patternType,
407
+ const clusters = [];
408
+ for (const component of components) {
409
+ if (component.length < 2) continue;
410
+ const componentDups = duplicates.filter(
411
+ (d) => component.includes(d.file1) && component.includes(d.file2)
412
+ );
413
+ const totalTokenCost = componentDups.reduce(
414
+ (sum, d) => sum + d.tokenCost,
415
+ 0
416
+ );
417
+ const avgSimilarity = componentDups.reduce((sum, d) => sum + d.similarity, 0) / Math.max(1, componentDups.length);
418
+ const name = determineClusterName(component);
419
+ const { severity, reason, suggestion } = calculateSeverity(
420
+ component[0],
421
+ component[1],
422
+ "",
423
+ // Code not available here
424
+ avgSimilarity,
425
+ 30
426
+ // Assume substantial if clustered
427
+ );
428
+ clusters.push({
429
+ id: `cluster-${clusters.length}`,
430
+ name,
431
+ files: component,
720
432
  severity,
433
+ duplicateCount: componentDups.length,
721
434
  totalTokenCost,
722
- averageSimilarity,
723
- duplicateCount: clusterDups.length,
724
- suggestion: clusterInfo.suggestion,
725
- reason: clusterInfo.reason
435
+ averageSimilarity: avgSimilarity,
436
+ reason,
437
+ suggestion
726
438
  });
727
439
  }
728
- return result.sort((a, b) => b.totalTokenCost - a.totalTokenCost);
729
- }
730
- function identifyCluster(dup) {
731
- const file1 = dup.file1.toLowerCase();
732
- const file2 = dup.file2.toLowerCase();
733
- if ((file1.includes("/blog/") || file1.startsWith("blog/") || file1.includes("/articles/") || file1.startsWith("articles/")) && (file2.includes("/blog/") || file2.startsWith("blog/") || file2.includes("/articles/") || file2.startsWith("articles/"))) {
734
- return "blog-seo-boilerplate";
735
- }
736
- if ((file1.includes("/components/") || file1.startsWith("components/")) && (file2.includes("/components/") || file2.startsWith("components/")) && dup.patternType === "component") {
737
- const component1 = extractComponentName(dup.file1);
738
- const component2 = extractComponentName(dup.file2);
739
- console.log(
740
- `Component check: ${dup.file1} -> ${component1}, ${dup.file2} -> ${component2}`
741
- );
742
- if (component1 && component2 && areSimilarComponents(component1, component2)) {
743
- const category = getComponentCategory(component1);
744
- console.log(`Creating cluster: component-${category}`);
745
- return `component-${category}`;
746
- }
747
- }
748
- if ((file1.includes("/e2e/") || file1.startsWith("e2e/") || file1.includes(".e2e.")) && (file2.includes("/e2e/") || file2.startsWith("e2e/") || file2.includes(".e2e."))) {
749
- return "e2e-test-patterns";
750
- }
751
- if (dup.patternType === "api-handler") {
752
- return "api-handlers";
753
- }
754
- if (dup.patternType === "validator") {
755
- return "validators";
756
- }
757
- if ((file1.includes("/scripts/") || file1.startsWith("scripts/") || file1.includes("/infra/") || file1.startsWith("infra/")) && (file2.includes("/scripts/") || file2.startsWith("scripts/") || file2.includes("/infra/") || file2.startsWith("infra/"))) {
758
- return "infrastructure-scripts";
759
- }
760
- return `${dup.patternType}-patterns`;
761
- }
762
- function extractComponentName(filePath) {
763
- const match = filePath.match(/[/\\]?([A-Z][a-zA-Z0-9]*)\.(tsx|jsx|ts|js)$/);
764
- return match ? match[1] : null;
765
- }
766
- function areSimilarComponents(name1, name2) {
767
- const category1 = getComponentCategory(name1);
768
- const category2 = getComponentCategory(name2);
769
- return category1 === category2;
770
- }
771
- function getComponentCategory(name) {
772
- name = name.toLowerCase();
773
- if (name.includes("button") || name.includes("btn")) return "button";
774
- if (name.includes("card")) return "card";
775
- if (name.includes("modal") || name.includes("dialog")) return "modal";
776
- if (name.includes("form")) return "form";
777
- if (name.includes("input") || name.includes("field")) return "input";
778
- if (name.includes("table") || name.includes("grid")) return "table";
779
- if (name.includes("nav") || name.includes("menu")) return "navigation";
780
- if (name.includes("header") || name.includes("footer")) return "layout";
781
- return "misc";
782
- }
783
- function getUniqueFiles(duplicates) {
784
- const files = /* @__PURE__ */ new Set();
785
- for (const dup of duplicates) {
786
- files.add(dup.file1);
787
- files.add(dup.file2);
788
- }
789
- return Array.from(files).sort();
790
- }
791
- function getHighestSeverity(severities) {
792
- const order = {
793
- critical: 4,
794
- major: 3,
795
- minor: 2,
796
- info: 1
797
- };
798
- let highest = "info";
799
- let highestValue = 0;
800
- for (const severity of severities) {
801
- if (order[severity] > highestValue) {
802
- highestValue = order[severity];
803
- highest = severity;
804
- }
805
- }
806
- return highest;
440
+ return clusters;
807
441
  }
808
- function getMostCommonPatternType(duplicates) {
809
- const counts = /* @__PURE__ */ new Map();
810
- for (const dup of duplicates) {
811
- counts.set(dup.patternType, (counts.get(dup.patternType) || 0) + 1);
442
+ function determineClusterName(files) {
443
+ if (files.length === 0) return "Unknown Cluster";
444
+ if (files.some((f) => f.includes("blog"))) return "Blog SEO Boilerplate";
445
+ if (files.some((f) => f.includes("buttons")))
446
+ return "Button Component Variants";
447
+ if (files.some((f) => f.includes("cards"))) return "Card Component Variants";
448
+ if (files.some((f) => f.includes("login.test"))) return "E2E Test Patterns";
449
+ const first = files[0];
450
+ const dirName = import_path.default.dirname(first).split(import_path.default.sep).pop();
451
+ if (dirName && dirName !== "." && dirName !== "..") {
452
+ return `${dirName.charAt(0).toUpperCase() + dirName.slice(1)} Domain Group`;
812
453
  }
813
- let mostCommon = "unknown";
814
- let maxCount = 0;
815
- for (const [type, count] of counts.entries()) {
816
- if (count > maxCount) {
817
- maxCount = count;
818
- mostCommon = type;
819
- }
820
- }
821
- return mostCommon;
454
+ return "Shared Pattern Group";
822
455
  }
823
- function getClusterInfo(clusterId, patternType, fileCount) {
824
- const templates = {
825
- "blog-seo-boilerplate": {
826
- name: `Blog SEO Boilerplate (${fileCount} files)`,
827
- suggestion: "Create BlogPageLayout component with SEO schema generator, breadcrumb component, and metadata helpers",
828
- reason: "SEO boilerplate duplication increases maintenance burden and schema consistency risk"
829
- },
830
- "e2e-test-patterns": {
831
- name: `E2E Test Patterns (${fileCount} files)`,
832
- suggestion: "Extract page object helpers and common test utilities (waitFor, fillForm, etc.)",
833
- reason: "Test helper extraction improves maintainability while preserving test independence"
834
- },
835
- "api-handlers": {
836
- name: `API Handler Patterns (${fileCount} files)`,
837
- suggestion: "Extract common middleware, error handling, and response formatting",
838
- reason: "API handler duplication leads to inconsistent error handling and response formats"
839
- },
840
- validators: {
841
- name: `Validator Patterns (${fileCount} files)`,
842
- suggestion: "Consolidate into shared schema validators (Zod/Yup) with reusable rules",
843
- reason: "Validator duplication causes inconsistent validation and harder maintenance"
844
- },
845
- "infrastructure-scripts": {
846
- name: `Infrastructure Scripts (${fileCount} files)`,
847
- suggestion: "Extract common CLI parsing, file I/O, and error handling utilities",
848
- reason: "Script duplication is often acceptable for one-off tasks, but common patterns can be shared"
849
- },
850
- "component-button": {
851
- name: `Button Component Variants (${fileCount} files)`,
852
- suggestion: "Create unified Button component with variant props",
853
- reason: "Multiple button variants should share base styles and behavior"
854
- },
855
- "component-card": {
856
- name: `Card Component Variants (${fileCount} files)`,
857
- suggestion: "Create unified Card component with composition pattern",
858
- reason: "Card variants should share layout structure and styling"
859
- },
860
- "component-modal": {
861
- name: `Modal Component Variants (${fileCount} files)`,
862
- suggestion: "Create base Modal component with customizable content",
863
- reason: "Modal variants should share overlay, animation, and accessibility logic"
864
- }
865
- };
866
- if (templates[clusterId]) {
867
- return templates[clusterId];
868
- }
869
- return {
870
- name: `${patternType} Cluster (${fileCount} files)`,
871
- suggestion: `Extract common ${patternType} patterns into shared utilities`,
872
- reason: `Multiple similar ${patternType} patterns detected across ${fileCount} files`
873
- };
874
- }
875
- function filterClustersByImpact(clusters, minTokenCost = 1e3, minFileCount = 3) {
456
+ function filterClustersByImpact(clusters, minTokenCost = 1e3, minFiles = 3) {
876
457
  return clusters.filter(
877
- (cluster) => cluster.totalTokenCost >= minTokenCost || cluster.files.length >= minFileCount
458
+ (c) => c.totalTokenCost >= minTokenCost && c.files.length >= minFiles
878
459
  );
879
460
  }
880
461
 
881
462
  // src/scoring.ts
882
- var import_core3 = require("@aiready/core");
463
+ var import_core4 = require("@aiready/core");
883
464
  function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
884
465
  const totalDuplicates = duplicates.length;
885
466
  const totalTokenCost = duplicates.reduce((sum, d) => sum + d.tokenCost, 0);
@@ -957,12 +538,12 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
957
538
  priority: totalTokenCost > 1e4 ? "high" : "medium"
958
539
  });
959
540
  }
960
- const cfg = { ...import_core3.DEFAULT_COST_CONFIG, ...costConfig };
961
- const estimatedMonthlyCost = (0, import_core3.calculateMonthlyCost)(totalTokenCost, cfg);
541
+ const cfg = { ...import_core4.DEFAULT_COST_CONFIG, ...costConfig };
542
+ const estimatedMonthlyCost = (0, import_core4.calculateMonthlyCost)(totalTokenCost, cfg);
962
543
  const issues = duplicates.map((d) => ({
963
544
  severity: d.severity === "critical" ? "critical" : d.severity === "major" ? "major" : "minor"
964
545
  }));
965
- const productivityImpact = (0, import_core3.calculateProductivityImpact)(issues);
546
+ const productivityImpact = (0, import_core4.calculateProductivityImpact)(issues);
966
547
  return {
967
548
  toolName: "pattern-detect",
968
549
  score: finalScore,
@@ -1098,7 +679,7 @@ async function analyzePatterns(options) {
1098
679
  const batchContents = await Promise.all(
1099
680
  batch.map(async (file) => ({
1100
681
  file,
1101
- content: await (0, import_core4.readFileContent)(file)
682
+ content: await (0, import_core5.readFileContent)(file)
1102
683
  }))
1103
684
  );
1104
685
  fileContents.push(...batchContents);
@@ -1119,9 +700,9 @@ async function analyzePatterns(options) {
1119
700
  );
1120
701
  const issues = fileDuplicates.map((dup) => {
1121
702
  const otherFile = dup.file1 === file ? dup.file2 : dup.file1;
1122
- const severity2 = dup.similarity > 0.95 ? "critical" : dup.similarity > 0.9 ? "major" : "minor";
703
+ const severity2 = dup.similarity > 0.95 ? import_core5.Severity.Critical : dup.similarity > 0.9 ? import_core5.Severity.Major : import_core5.Severity.Minor;
1123
704
  return {
1124
- type: "duplicate-pattern",
705
+ type: import_core5.IssueType.DuplicatePattern,
1125
706
  severity: severity2,
1126
707
  message: `${dup.patternType} pattern ${Math.round(dup.similarity * 100)}% similar to ${otherFile} (${dup.tokenCost} tokens wasted)`,
1127
708
  location: {
@@ -1134,11 +715,11 @@ async function analyzePatterns(options) {
1134
715
  let filteredIssues = issues;
1135
716
  if (severity !== "all") {
1136
717
  const severityMap = {
1137
- critical: ["critical"],
1138
- high: ["critical", "major"],
1139
- medium: ["critical", "major", "minor"]
718
+ critical: [import_core5.Severity.Critical],
719
+ high: [import_core5.Severity.Critical, import_core5.Severity.Major],
720
+ medium: [import_core5.Severity.Critical, import_core5.Severity.Major, import_core5.Severity.Minor]
1140
721
  };
1141
- const allowedSeverities = severityMap[severity] || ["critical", "major", "minor"];
722
+ const allowedSeverities = severityMap[severity] || [import_core5.Severity.Critical, import_core5.Severity.Major, import_core5.Severity.Minor];
1142
723
  filteredIssues = issues.filter(
1143
724
  (issue) => allowedSeverities.includes(issue.severity)
1144
725
  );
@@ -1228,6 +809,7 @@ function generateSummary(results) {
1228
809
  }
1229
810
  // Annotate the CommonJS export names for ESM import in node:
1230
811
  0 && (module.exports = {
812
+ Severity,
1231
813
  analyzePatterns,
1232
814
  calculatePatternScore,
1233
815
  calculateSeverity,