@aiready/pattern-detect 0.17.15 → 0.17.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/analyzer-entry/index.d.mts +2 -2
  2. package/dist/analyzer-entry/index.d.ts +2 -2
  3. package/dist/analyzer-entry/index.js +357 -140
  4. package/dist/analyzer-entry/index.mjs +4 -4
  5. package/dist/chunk-3LMYFYWG.mjs +514 -0
  6. package/dist/chunk-4YXKUW4P.mjs +143 -0
  7. package/dist/chunk-5A3ULAQ5.mjs +571 -0
  8. package/dist/chunk-5FACKJ7M.mjs +519 -0
  9. package/dist/chunk-6B72OWZA.mjs +143 -0
  10. package/dist/chunk-6SHBBRHF.mjs +600 -0
  11. package/dist/chunk-BKSIA7A2.mjs +516 -0
  12. package/dist/chunk-CM5YJR7G.mjs +516 -0
  13. package/dist/chunk-FSXOU23F.mjs +620 -0
  14. package/dist/chunk-GUYQI3AF.mjs +514 -0
  15. package/dist/chunk-H2TGXGMX.mjs +587 -0
  16. package/dist/chunk-KMAOEVRS.mjs +150 -0
  17. package/dist/chunk-NWG2ZIGX.mjs +146 -0
  18. package/dist/chunk-OFVJFGQW.mjs +514 -0
  19. package/dist/chunk-PCCZREHY.mjs +143 -0
  20. package/dist/chunk-PQS5ACTN.mjs +516 -0
  21. package/dist/chunk-TVE75IDM.mjs +143 -0
  22. package/dist/chunk-UDOGQ42Q.mjs +603 -0
  23. package/dist/chunk-UFI4UDQI.mjs +514 -0
  24. package/dist/chunk-UXV57HN3.mjs +144 -0
  25. package/dist/chunk-VC2BOV6R.mjs +143 -0
  26. package/dist/chunk-VI2OVG73.mjs +514 -0
  27. package/dist/chunk-VKGYNHFY.mjs +514 -0
  28. package/dist/chunk-WBLZYAQ2.mjs +518 -0
  29. package/dist/chunk-WFVXMMB3.mjs +143 -0
  30. package/dist/chunk-WQC43BIO.mjs +516 -0
  31. package/dist/chunk-WTAIM3SG.mjs +146 -0
  32. package/dist/chunk-XC7U55PE.mjs +514 -0
  33. package/dist/chunk-XR373Q6G.mjs +146 -0
  34. package/dist/chunk-XWIBTD67.mjs +620 -0
  35. package/dist/chunk-YUQ2VQVJ.mjs +514 -0
  36. package/dist/chunk-Z4NOH52X.mjs +143 -0
  37. package/dist/cli.js +357 -140
  38. package/dist/cli.mjs +4 -4
  39. package/dist/context-rules-entry/index.js +351 -139
  40. package/dist/context-rules-entry/index.mjs +1 -1
  41. package/dist/detector-entry/index.d.mts +2 -2
  42. package/dist/detector-entry/index.d.ts +2 -2
  43. package/dist/detector-entry/index.js +355 -140
  44. package/dist/detector-entry/index.mjs +2 -2
  45. package/dist/index-BGvkJ9j1.d.mts +136 -0
  46. package/dist/index-BJq32qmj.d.mts +137 -0
  47. package/dist/index-BpoJSgX-.d.mts +136 -0
  48. package/dist/index-C7qLPKmH.d.ts +150 -0
  49. package/dist/index-CThnG9hv.d.ts +155 -0
  50. package/dist/index-D0Hpg9nN.d.mts +150 -0
  51. package/dist/index-DN6XpBOW.d.mts +155 -0
  52. package/dist/index-F8xqZ2PS.d.ts +136 -0
  53. package/dist/index-HNhDr6CV.d.ts +137 -0
  54. package/dist/index-ux0Wo8Ps.d.ts +136 -0
  55. package/dist/index.d.mts +2 -2
  56. package/dist/index.d.ts +2 -2
  57. package/dist/index.js +359 -142
  58. package/dist/index.mjs +4 -4
  59. package/dist/scoring-entry/index.d.mts +1 -1
  60. package/dist/scoring-entry/index.d.ts +1 -1
  61. package/dist/scoring-entry/index.js +2 -2
  62. package/dist/scoring-entry/index.mjs +1 -1
  63. package/dist/types-tgrmUrHE.d.mts +37 -0
  64. package/dist/types-tgrmUrHE.d.ts +37 -0
  65. package/package.json +5 -3
@@ -1,3 +1,3 @@
1
- export { P as PatternDetectOptions, a as PatternSummary, b as analyzePatterns, i as generateSummary, k as getSmartDefaults } from '../index-Dl4BrGIT.mjs';
1
+ export { P as PatternDetectOptions, a as PatternSummary, b as analyzePatterns, i as generateSummary, k as getSmartDefaults } from '../index-DN6XpBOW.mjs';
2
2
  import '@aiready/core';
3
- import '../types-DU2mmhwb.mjs';
3
+ import '../types-tgrmUrHE.mjs';
@@ -1,3 +1,3 @@
1
- export { P as PatternDetectOptions, a as PatternSummary, b as analyzePatterns, i as generateSummary, k as getSmartDefaults } from '../index-CWgYOKaK.js';
1
+ export { P as PatternDetectOptions, a as PatternSummary, b as analyzePatterns, i as generateSummary, k as getSmartDefaults } from '../index-CThnG9hv.js';
2
2
  import '@aiready/core';
3
- import '../types-DU2mmhwb.js';
3
+ import '../types-tgrmUrHE.js';
@@ -204,179 +204,391 @@ var INFRA_RULES = [
204
204
  }
205
205
  ];
206
206
 
207
- // src/rules/categories/logic-rules.ts
208
- var import_core4 = require("@aiready/core");
209
- var LOGIC_RULES = [
210
- // Enum Semantic Difference - Different enum names indicate different semantic meanings
211
- {
212
- name: "enum-semantic-difference",
213
- detect: (file, code) => {
214
- const enumRegex = /(?:export\s+)?(?:const\s+)?enum\s+([A-Z][a-zA-Z0-9]*)/g;
215
- const enums = [];
216
- let match;
217
- while ((match = enumRegex.exec(code)) !== null) {
218
- enums.push(match[1]);
207
+ // src/rules/categories/logic/file-detectors.ts
208
+ var FileDetectors = {
209
+ isIndexFile: (file) => file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx"),
210
+ isTypeFile: (file) => file.endsWith(".d.ts") || file.includes("/types/"),
211
+ isUtilFile: (file) => file.endsWith(".util.ts") || file.endsWith(".helper.ts") || file.endsWith(".utils.ts"),
212
+ isHookFile: (file) => file.includes("/hooks/") || file.endsWith(".hook.ts") || file.endsWith(".hook.tsx"),
213
+ isHelperFile: (file) => file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts"),
214
+ isVizFile: (file) => file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected"),
215
+ isApiFile: (file) => file.includes("/api/") || file.includes("/lib/") || file.includes("/utils/") || file.endsWith(".ts"),
216
+ isPackageOrApp: (file) => file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/"),
217
+ getPackageName: (file) => {
218
+ const match = file.match(/\/(packages|apps|core)\/([^/]+)\//);
219
+ return match?.[2] || null;
220
+ }
221
+ };
222
+
223
+ // src/rules/categories/logic/keyword-lists.ts
224
+ var VALIDATION_KEYWORDS = [
225
+ "isValid",
226
+ "validate",
227
+ "checkValid",
228
+ "isEmail",
229
+ "isPhone",
230
+ "isUrl",
231
+ "isNumeric",
232
+ "isAlpha",
233
+ "isAlphanumeric",
234
+ "isEmpty",
235
+ "isNotEmpty",
236
+ "isRequired",
237
+ "isOptional"
238
+ ];
239
+ var HOOK_KEYWORDS = [
240
+ "function use",
241
+ "export function use",
242
+ "const use",
243
+ "export const use"
244
+ ];
245
+ var VIZ_EVENT_KEYWORDS = ["dragstarted", "dragged", "dragended"];
246
+ var VIZ_LIB_KEYWORDS = ["simulation", "d3.", "alphaTarget"];
247
+ var ICON_HELPER_KEYWORDS = [
248
+ "getIcon",
249
+ "getColor",
250
+ "getLabel",
251
+ "getRating"
252
+ ];
253
+ var UTIL_KEYWORDS = [
254
+ "format",
255
+ "parse",
256
+ "sanitize",
257
+ "normalize",
258
+ "convert",
259
+ "utility",
260
+ "helper"
261
+ ];
262
+
263
+ // src/rules/categories/logic/detectors.ts
264
+ var isEnumDefinition = (code) => /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
265
+ var hasSingletonGetter = (code) => /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
266
+ code
267
+ ) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
268
+ code
269
+ );
270
+ var hasSingletonPattern = (code) => code.includes("if (!") && code.includes("instance") && code.includes(" = ") || code.includes("if (!_") && code.includes(" = new ");
271
+ var hasReExportPattern = (code) => {
272
+ const lines = code.split("\n").filter((l) => l.trim());
273
+ if (lines.length === 0) return false;
274
+ const reExportLines = lines.filter(
275
+ (l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
276
+ ).length;
277
+ return reExportLines > 0 && reExportLines / lines.length > 0.5;
278
+ };
279
+ var isInterfaceOnlySnippet = (code) => {
280
+ const hasInterface = code.includes("interface ");
281
+ const hasType = code.includes("type ");
282
+ const hasEnum = code.includes("enum ");
283
+ const hasNoImpl = ![
284
+ "function ",
285
+ "class ",
286
+ "const ",
287
+ "let ",
288
+ "var ",
289
+ "export default",
290
+ "export {"
291
+ ].some((kw) => code.includes(kw));
292
+ return (hasInterface || hasType || hasEnum) && hasNoImpl;
293
+ };
294
+
295
+ // src/rules/categories/logic/code-patterns.ts
296
+ var COMMON_ENUM_PATTERNS = [
297
+ ["LOW", ["'low'", "0", "'LOW'"]],
298
+ ["HIGH", ["'high'", "2", "'HIGH'"]],
299
+ ["MEDIUM", ["'medium'", "1", "'MEDIUM'"]]
300
+ ];
301
+ var hasEnumValue = (code, enumName, variants) => variants.some((v) => code.includes(`${enumName} = ${v}`));
302
+ var CodePatterns = {
303
+ // Enum patterns
304
+ hasCommonEnumValues: (code) => COMMON_ENUM_PATTERNS.every(
305
+ ([name, variants]) => hasEnumValue(code, name, variants)
306
+ ),
307
+ isEnumDefinition,
308
+ // Type patterns - helpers for checking type-only definitions
309
+ hasTypeDefinition: (code) => code.includes("interface ") || code.includes("type ") || code.includes("enum "),
310
+ isTypeOnlyFile: (code) => {
311
+ const hasTypes = CodePatterns.hasTypeDefinition(code);
312
+ const hasNoImpl = ![
313
+ "function ",
314
+ "class ",
315
+ "const ",
316
+ "let ",
317
+ "export default"
318
+ ].some((kw) => code.includes(kw));
319
+ return hasTypes && hasNoImpl;
320
+ },
321
+ hasOnlyTypeDefinitions: (code) => {
322
+ const hasTypes = CodePatterns.hasTypeDefinition(code);
323
+ const hasNoImpl = ![
324
+ "function ",
325
+ "class ",
326
+ "const ",
327
+ "let ",
328
+ "var ",
329
+ "export default",
330
+ "export {"
331
+ ].some((kw) => code.includes(kw));
332
+ return hasTypes && hasNoImpl;
333
+ },
334
+ // Utility patterns - function group patterns
335
+ hasUtilPattern: (code) => CodePatterns.hasFunctionGroup(code, "") || CodePatterns.hasKeywordGroup(code, UTIL_KEYWORDS),
336
+ hasFunctionGroup: (code, prefix) => {
337
+ const regex = new RegExp(
338
+ `${prefix}(format|parse|sanitize|normalize|convert)`,
339
+ "i"
340
+ );
341
+ return regex.test(code);
342
+ },
343
+ // Pattern checkers using keyword lists
344
+ hasKeywordGroup: (code, keywords) => keywords.some((kw) => code.includes(kw)),
345
+ // Validation patterns - unified keyword check
346
+ hasValidationPattern: (code) => CodePatterns.hasKeywordGroup(code, VALIDATION_KEYWORDS),
347
+ // Hook patterns - React/Vue hooks
348
+ hasHookPattern: (code) => CodePatterns.hasKeywordGroup(code, HOOK_KEYWORDS),
349
+ // Score/Rating patterns
350
+ hasScorePattern: (code) => (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3,
351
+ // Visualization patterns - D3/Canvas
352
+ hasVizPattern: (code) => CodePatterns.hasKeywordGroup(code, VIZ_EVENT_KEYWORDS) && CodePatterns.hasKeywordGroup(code, VIZ_LIB_KEYWORDS),
353
+ // Switch/Icon patterns
354
+ hasSwitchPattern: (code) => code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4,
355
+ hasIconPattern: (code) => CodePatterns.hasKeywordGroup(code, ICON_HELPER_KEYWORDS),
356
+ // Singleton patterns
357
+ hasSingletonGetter,
358
+ hasSingletonPattern,
359
+ // Re-export patterns
360
+ hasReExportPattern,
361
+ // Interface-only snippets
362
+ isInterfaceOnlySnippet
363
+ };
364
+
365
+ // src/rules/categories/logic/api-patterns.ts
366
+ var API_PATTERNS = {
367
+ stripe: {
368
+ functions: ["getStripe"],
369
+ keywords: ["process.env.STRIPE_SECRET_KEY"]
370
+ },
371
+ userManagement: {
372
+ functions: ["getUserByEmail"],
373
+ keywords: ["queryItems"]
374
+ },
375
+ userUpdate: {
376
+ functions: ["updateUser"],
377
+ keywords: ["buildUpdateExpression"]
378
+ },
379
+ repositoryListing: {
380
+ functions: ["listUserRepositories", "listTeamRepositories"],
381
+ keywords: ["queryItems"]
382
+ },
383
+ remediationQueries: {
384
+ functions: ["getRemediation"],
385
+ keywords: ["queryItems"]
386
+ },
387
+ keyFormatting: {
388
+ functions: ["formatBreakdownKey"],
389
+ keywords: [".replace(/([A-Z])/g"]
390
+ },
391
+ dynamoDbQueries: {
392
+ functions: ["queryItems"],
393
+ keywords: ["KeyConditionExpression"]
394
+ },
395
+ itemOperations: {
396
+ functions: ["putItem"],
397
+ keywords: ["createdAt"]
398
+ },
399
+ itemUpdates: {
400
+ functions: ["updateItem"],
401
+ keywords: ["buildUpdateExpression"]
402
+ }
403
+ };
404
+ var ApiPatterns = {
405
+ /**
406
+ * Check if code contains a common API pattern
407
+ * Reduces from 11 separate || conditions to single call
408
+ */
409
+ hasCommonApiPattern: (code) => {
410
+ for (const pattern of Object.values(API_PATTERNS)) {
411
+ const hasFunctions = pattern.functions.some((fn) => code.includes(fn));
412
+ const hasKeywords = pattern.keywords.some((kw) => code.includes(kw));
413
+ if (hasFunctions && hasKeywords) {
414
+ return true;
219
415
  }
220
- return enums.length > 0;
221
- },
416
+ }
417
+ return false;
418
+ },
419
+ /**
420
+ * Get all API patterns (useful for documentation/analysis)
421
+ */
422
+ getPatterns: () => API_PATTERNS,
423
+ /**
424
+ * Check if code matches a specific API pattern by name
425
+ */
426
+ matchesPattern: (code, patternName) => {
427
+ const pattern = API_PATTERNS[patternName];
428
+ if (!pattern) return false;
429
+ const hasFunctions = pattern.functions.some((fn) => code.includes(fn));
430
+ const hasKeywords = pattern.keywords.some((kw) => code.includes(kw));
431
+ return hasFunctions && hasKeywords;
432
+ }
433
+ };
434
+
435
+ // src/rules/categories/logic/rule-builders.ts
436
+ var import_core4 = require("@aiready/core");
437
+ function createRule(config) {
438
+ return {
439
+ name: config.name,
440
+ detect: config.detect,
441
+ severity: config.severity || import_core4.Severity.Info,
442
+ reason: config.reason,
443
+ suggestion: config.suggestion
444
+ };
445
+ }
446
+ var RuleTemplates = {
447
+ // Type-related rules
448
+ typeDefinition: {
449
+ severity: import_core4.Severity.Info,
450
+ reason: "Type/interface definitions are intentionally duplicated for module independence",
451
+ suggestion: "Extract to shared types package only if causing maintenance burden"
452
+ },
453
+ crossPackageType: {
454
+ severity: import_core4.Severity.Info,
455
+ reason: "Types in different packages/modules are often intentionally similar for module independence",
456
+ suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
457
+ },
458
+ // Pattern rules
459
+ standardPatterns: {
460
+ severity: import_core4.Severity.Info,
461
+ reason: "This pattern is inherently similar and intentionally duplicated across modules",
462
+ suggestion: "Pattern duplication is acceptable for domain clarity and independence"
463
+ },
464
+ // API rules
465
+ commonApiFunction: {
466
+ severity: import_core4.Severity.Info,
467
+ reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
468
+ suggestion: "Consider extracting to shared utilities only if causing significant duplication"
469
+ },
470
+ // Utility rules
471
+ utilityFunction: {
472
+ severity: import_core4.Severity.Info,
473
+ reason: "Utility functions may be intentionally similar across modules",
474
+ suggestion: "Consider extracting to shared utilities only if causing significant duplication"
475
+ },
476
+ // Boilerplate rules
477
+ boilerplate: {
478
+ severity: import_core4.Severity.Info,
479
+ reason: "This pattern is boilerplate and acceptable duplication",
480
+ suggestion: "Boilerplate reduction is acceptable for standard patterns"
481
+ },
482
+ // Enum rules
483
+ enumSemantic: {
222
484
  severity: import_core4.Severity.Info,
223
485
  reason: "Enums with different names represent different semantic domain concepts, even if they share similar values",
224
486
  suggestion: "Different enums (e.g., EscalationPriority vs HealthSeverity) serve different purposes and should not be merged"
225
487
  },
226
- // Enum Value Similarity - Common enum values like LOW, MEDIUM, HIGH are standard
227
- {
228
- name: "enum-value-similarity",
229
- detect: (file, code) => {
230
- const hasCommonEnumValues = (code.includes("LOW = 'low'") || code.includes("LOW = 0") || code.includes("LOW = 'LOW'")) && (code.includes("HIGH = 'high'") || code.includes("HIGH = 2") || code.includes("HIGH = 'HIGH'")) && (code.includes("MEDIUM = 'medium'") || code.includes("MEDIUM = 1") || code.includes("MEDIUM = 'MEDIUM'"));
231
- const isEnumDefinition = /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
232
- return hasCommonEnumValues && isEnumDefinition;
233
- },
488
+ enumValue: {
234
489
  severity: import_core4.Severity.Info,
235
490
  reason: "Common enum values (LOW, MEDIUM, HIGH, CRITICAL) are standard patterns used across different domain enums",
236
491
  suggestion: "Enum value similarity is expected for severity/priority enums and should not be flagged as duplication"
237
492
  },
238
- // Re-export / Barrel files - Intentional API surface consolidation
239
- {
240
- name: "re-export-files",
241
- detect: (file, code) => {
242
- const isIndexFile = file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx");
243
- const lines = code.split("\n").filter((l) => l.trim());
244
- if (lines.length === 0) return false;
245
- const reExportLines = lines.filter(
246
- (l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
247
- ).length;
248
- return isIndexFile && reExportLines > 0 && reExportLines / lines.length > 0.5;
249
- },
493
+ // Barrel file rules
494
+ barrelFile: {
250
495
  severity: import_core4.Severity.Info,
251
496
  reason: "Barrel/index files intentionally re-export for API surface consolidation",
252
497
  suggestion: "Re-exports in barrel files are expected and not true duplication"
253
- },
254
- // Type Definitions - Duplication for type safety and module independence
255
- {
498
+ }
499
+ };
500
+
501
+ // src/rules/categories/logic-rules.ts
502
+ var LOGIC_RULES = [
503
+ // Enum patterns - identify by semantic meaning and value patterns
504
+ createRule({
505
+ name: "enum-semantic-difference",
506
+ detect: (file, code) => CodePatterns.isEnumDefinition(code),
507
+ ...RuleTemplates.enumSemantic
508
+ }),
509
+ createRule({
510
+ name: "enum-value-similarity",
511
+ detect: (file, code) => CodePatterns.hasCommonEnumValues(code) && CodePatterns.isEnumDefinition(code),
512
+ ...RuleTemplates.enumValue
513
+ }),
514
+ // Barrel/re-export files - intentional API consolidation
515
+ createRule({
516
+ name: "re-export-files",
517
+ detect: (file, code) => FileDetectors.isIndexFile(file) && CodePatterns.hasReExportPattern(code),
518
+ ...RuleTemplates.barrelFile
519
+ }),
520
+ // Type definitions - consolidated rule for both local and cross-package types
521
+ // PHASE 2 CONSOLIDATION: merged type-definitions + cross-package-types
522
+ createRule({
256
523
  name: "type-definitions",
257
524
  detect: (file, code) => {
258
- const isTypeFile = file.endsWith(".d.ts") || file.includes("/types/");
259
- const hasOnlyTypeDefinitions = (code.includes("interface ") || code.includes("type ") || code.includes("enum ")) && !code.includes("function ") && !code.includes("class ") && !code.includes("const ") && !code.includes("let ") && !code.includes("export default");
260
- const isInterfaceOnlySnippet = code.trim().startsWith("interface ") && code.includes("{") && code.includes("}") && !code.includes("function ") && !code.includes("const ") && !code.includes("return ");
261
- return isTypeFile && hasOnlyTypeDefinitions || isInterfaceOnlySnippet;
262
- },
263
- severity: import_core4.Severity.Info,
264
- reason: "Type/interface definitions are intentionally duplicated for module independence",
265
- suggestion: "Extract to shared types package only if causing maintenance burden"
266
- },
267
- // Cross-Package Type Definitions - Different packages may have similar types
268
- {
269
- name: "cross-package-types",
270
- detect: (file, code) => {
271
- const hasTypeDefinition = code.includes("interface ") || code.includes("type ") || code.includes("enum ");
272
- const isPackageOrApp = file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/");
273
- const packageMatch = file.match(/\/(packages|apps|core)\/([^/]+)\//);
274
- const hasPackageStructure = packageMatch !== null;
275
- return hasTypeDefinition && isPackageOrApp && hasPackageStructure;
525
+ if (FileDetectors.isTypeFile(file)) {
526
+ if (CodePatterns.hasOnlyTypeDefinitions(code)) return true;
527
+ }
528
+ if (CodePatterns.isInterfaceOnlySnippet(code)) return true;
529
+ if (FileDetectors.isPackageOrApp(file) && FileDetectors.getPackageName(file) !== null && CodePatterns.hasTypeDefinition(code)) {
530
+ return true;
531
+ }
532
+ return false;
276
533
  },
277
- severity: import_core4.Severity.Info,
278
- reason: "Types in different packages/modules are often intentionally similar for module independence",
279
- suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
280
- },
281
- // Utility Functions - Small helpers in dedicated utility files
282
- {
534
+ reason: "Type/interface definitions are intentionally duplicated for module independence and decoupled module architecture",
535
+ suggestion: "Extract to shared types package only if causing significant maintenance burden or cross-package conflicts"
536
+ }),
537
+ // Utility functions - dedicated utility file patterns
538
+ createRule({
283
539
  name: "utility-functions",
284
- detect: (file, code) => {
285
- const isUtilFile = file.endsWith(".util.ts") || file.endsWith(".helper.ts") || file.endsWith(".utils.ts");
286
- const hasUtilPattern = code.includes("function format") || code.includes("function parse") || code.includes("function sanitize") || code.includes("function normalize") || code.includes("function convert");
287
- return isUtilFile && hasUtilPattern;
288
- },
289
- severity: import_core4.Severity.Info,
290
- reason: "Utility functions in dedicated utility files may be intentionally similar",
291
- suggestion: "Consider extracting to shared utilities only if causing significant duplication"
292
- },
293
- // React/Vue Hooks - Standard patterns
294
- {
540
+ detect: (file, code) => FileDetectors.isUtilFile(file) && CodePatterns.hasUtilPattern(code),
541
+ ...RuleTemplates.utilityFunction
542
+ }),
543
+ // React/Vue Hooks - standard hook patterns
544
+ createRule({
295
545
  name: "shared-hooks",
296
- detect: (file, code) => {
297
- const isHookFile = file.includes("/hooks/") || file.endsWith(".hook.ts") || file.endsWith(".hook.tsx");
298
- const hasHookPattern = code.includes("function use") || code.includes("export function use") || code.includes("const use") || code.includes("export const use");
299
- return isHookFile && hasHookPattern;
300
- },
301
- severity: import_core4.Severity.Info,
546
+ detect: (file, code) => FileDetectors.isHookFile(file) && CodePatterns.hasHookPattern(code),
302
547
  reason: "Hooks follow standard patterns and are often intentionally similar across components",
303
548
  suggestion: "Consider extracting common hook logic only if hooks become complex"
304
- },
305
- // Score/Rating Helper Functions - Common threshold patterns
306
- {
549
+ }),
550
+ // Score/Rating helpers - common threshold patterns
551
+ createRule({
307
552
  name: "score-helpers",
308
- detect: (file, code) => {
309
- const isHelperFile = file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts");
310
- const hasScorePattern = (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3;
311
- return isHelperFile && hasScorePattern;
312
- },
313
- severity: import_core4.Severity.Info,
553
+ detect: (file, code) => (FileDetectors.isHelperFile(file) || file.includes("/utils/")) && CodePatterns.hasScorePattern(code),
314
554
  reason: "Score/rating helper functions use common threshold patterns that are intentionally similar",
315
555
  suggestion: "Score formatting duplication is acceptable for consistent UI display"
316
- },
317
- // D3/Canvas Event Handlers - Standard visualization patterns
318
- {
556
+ }),
557
+ // D3/Canvas event handlers - visualization patterns
558
+ createRule({
319
559
  name: "visualization-handlers",
320
- detect: (file, code) => {
321
- const isVizFile = file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected");
322
- const hasVizPattern = (code.includes("dragstarted") || code.includes("dragged") || code.includes("dragended")) && (code.includes("simulation") || code.includes("d3.") || code.includes("alphaTarget"));
323
- return isVizFile && hasVizPattern;
324
- },
325
- severity: import_core4.Severity.Info,
560
+ detect: (file, code) => FileDetectors.isVizFile(file) && CodePatterns.hasVizPattern(code),
326
561
  reason: "D3/visualization event handlers follow standard patterns and are intentionally similar",
327
562
  suggestion: "Visualization boilerplate duplication is acceptable for interactive charts"
328
- },
329
- // Icon/Switch Statement Helpers - Common enum-to-value patterns
330
- {
563
+ }),
564
+ // Switch statement helpers - enum-to-value mapping
565
+ createRule({
331
566
  name: "switch-helpers",
332
- detect: (file, code) => {
333
- const hasSwitchPattern = code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4;
334
- const hasIconPattern = code.includes("getIcon") || code.includes("getColor") || code.includes("getLabel") || code.includes("getRating");
335
- return hasSwitchPattern && hasIconPattern;
336
- },
337
- severity: import_core4.Severity.Info,
567
+ detect: (file, code) => CodePatterns.hasSwitchPattern(code) && CodePatterns.hasIconPattern(code),
338
568
  reason: "Switch statement helpers for enum-to-value mapping are inherently similar",
339
569
  suggestion: "Switch duplication is acceptable for mapping enums to display values"
340
- },
341
- // Common API/Utility Functions - Legitimate duplication across modules
342
- {
570
+ }),
571
+ // Common API/Utility functions - legitimately duplicated across modules
572
+ createRule({
343
573
  name: "common-api-functions",
344
- detect: (file, code) => {
345
- const isApiFile = file.includes("/api/") || file.includes("/lib/") || file.includes("/utils/") || file.endsWith(".ts");
346
- const hasCommonApiPattern = code.includes("getStripe") && code.includes("process.env.STRIPE_SECRET_KEY") || code.includes("getUserByEmail") && code.includes("queryItems") || code.includes("updateUser") && code.includes("buildUpdateExpression") || code.includes("listUserRepositories") && code.includes("queryItems") || code.includes("listTeamRepositories") && code.includes("queryItems") || code.includes("getRemediation") && code.includes("queryItems") || code.includes("formatBreakdownKey") && code.includes(".replace(/([A-Z])/g") || code.includes("queryItems") && code.includes("KeyConditionExpression") || code.includes("putItem") && code.includes("createdAt") || code.includes("updateItem") && code.includes("buildUpdateExpression");
347
- return isApiFile && hasCommonApiPattern;
348
- },
349
- severity: import_core4.Severity.Info,
350
- reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
351
- suggestion: "Consider extracting to shared utilities only if causing significant duplication"
352
- },
353
- // Validation Functions - Inherently similar patterns
354
- {
574
+ detect: (file, code) => FileDetectors.isApiFile(file) && ApiPatterns.hasCommonApiPattern(code),
575
+ ...RuleTemplates.commonApiFunction
576
+ }),
577
+ // Validation functions - inherently similar patterns
578
+ // PHASE 2: Consolidated validation-function branches into single rule
579
+ createRule({
355
580
  name: "validation-functions",
356
- detect: (file, code) => {
357
- const hasValidationPattern = code.includes("isValid") || code.includes("validate") || code.includes("checkValid") || code.includes("isEmail") || code.includes("isPhone") || code.includes("isUrl") || code.includes("isNumeric") || code.includes("isAlpha") || code.includes("isAlphanumeric") || code.includes("isEmpty") || code.includes("isNotEmpty") || code.includes("isRequired") || code.includes("isOptional");
358
- return hasValidationPattern;
359
- },
360
- severity: import_core4.Severity.Info,
581
+ detect: (file, code) => CodePatterns.hasValidationPattern(code),
361
582
  reason: "Validation functions are inherently similar and often intentionally duplicated for domain clarity",
362
583
  suggestion: "Consider extracting to shared validators only if validation logic becomes complex"
363
- },
364
- // Singleton Getter Pattern - Standard singleton initialization pattern
365
- {
584
+ }),
585
+ // Singleton getter pattern - standard initialization
586
+ createRule({
366
587
  name: "singleton-getter",
367
- detect: (file, code) => {
368
- const hasSingletonGetter = /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
369
- code
370
- ) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
371
- code
372
- );
373
- const hasSingletonPattern = code.includes("if (!") && code.includes("instance") && code.includes(" = ") || code.includes("if (!_") && code.includes(" = new ") || code.includes("if (") && code.includes(" === null") && code.includes(" = new ");
374
- return hasSingletonGetter && hasSingletonPattern;
375
- },
376
- severity: import_core4.Severity.Info,
588
+ detect: (file, code) => CodePatterns.hasSingletonGetter(code) && CodePatterns.hasSingletonPattern(code),
377
589
  reason: "Singleton getter functions follow standard initialization pattern and are intentionally similar",
378
590
  suggestion: "Singleton getters are boilerplate and acceptable duplication for lazy initialization"
379
- }
591
+ })
380
592
  ];
381
593
 
382
594
  // src/context-rules.ts
@@ -409,7 +621,7 @@ function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
409
621
  reason: "Nearly identical code should be consolidated",
410
622
  suggestion: "Move to shared utility file"
411
623
  };
412
- } else if (similarity >= 0.85) {
624
+ } else if (similarity >= 0.85 && linesOfCode >= 10) {
413
625
  return {
414
626
  severity: import_core5.Severity.Major,
415
627
  reason: "High similarity indicates significant duplication",
@@ -463,7 +675,10 @@ async function detectDuplicatePatterns(fileContents, options) {
463
675
  ignoreWhitelist = []
464
676
  } = options;
465
677
  const allBlocks = [];
466
- const excludeRegexes = excludePatterns.map((p) => new RegExp(p, "i"));
678
+ const regexStr = (f) => f.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "[^/]*").replace(/\[\^\/\]\*\[\^\/\]\*/g, ".*");
679
+ const excludeRegexes = excludePatterns.map(
680
+ (p) => new RegExp(`${regexStr(p)}$`, "i")
681
+ );
467
682
  for (const { file, content } of fileContents) {
468
683
  const blocks = extractBlocks(file, content);
469
684
  for (const b of blocks) {
@@ -952,6 +1167,7 @@ async function analyzePatterns(options) {
952
1167
  minClusterTokenCost = 1e3,
953
1168
  minClusterFiles = 3,
954
1169
  excludePatterns = [],
1170
+ excludeFiles = [],
955
1171
  confidenceThreshold = 0,
956
1172
  ignoreWhitelist = [],
957
1173
  ...scanOptions
@@ -981,6 +1197,7 @@ async function analyzePatterns(options) {
981
1197
  maxCandidatesPerBlock,
982
1198
  streamResults,
983
1199
  excludePatterns,
1200
+ excludeFiles,
984
1201
  confidenceThreshold,
985
1202
  ignoreWhitelist,
986
1203
  onProgress: options.onProgress
@@ -2,10 +2,10 @@ import {
2
2
  analyzePatterns,
3
3
  generateSummary,
4
4
  getSmartDefaults
5
- } from "../chunk-WQX7IHAN.mjs";
6
- import "../chunk-JWP5TCDM.mjs";
7
- import "../chunk-KDXWIT6W.mjs";
8
- import "../chunk-G3GZFYRI.mjs";
5
+ } from "../chunk-WQC43BIO.mjs";
6
+ import "../chunk-XR373Q6G.mjs";
7
+ import "../chunk-XWIBTD67.mjs";
8
+ import "../chunk-UXV57HN3.mjs";
9
9
  export {
10
10
  analyzePatterns,
11
11
  generateSummary,