@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
package/dist/index.js CHANGED
@@ -221,179 +221,391 @@ var INFRA_RULES = [
221
221
  }
222
222
  ];
223
223
 
224
- // src/rules/categories/logic-rules.ts
225
- var import_core4 = require("@aiready/core");
226
- var LOGIC_RULES = [
227
- // Enum Semantic Difference - Different enum names indicate different semantic meanings
228
- {
229
- name: "enum-semantic-difference",
230
- detect: (file, code) => {
231
- const enumRegex = /(?:export\s+)?(?:const\s+)?enum\s+([A-Z][a-zA-Z0-9]*)/g;
232
- const enums = [];
233
- let match;
234
- while ((match = enumRegex.exec(code)) !== null) {
235
- enums.push(match[1]);
224
+ // src/rules/categories/logic/file-detectors.ts
225
+ var FileDetectors = {
226
+ isIndexFile: (file) => file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx"),
227
+ isTypeFile: (file) => file.endsWith(".d.ts") || file.includes("/types/"),
228
+ isUtilFile: (file) => file.endsWith(".util.ts") || file.endsWith(".helper.ts") || file.endsWith(".utils.ts"),
229
+ isHookFile: (file) => file.includes("/hooks/") || file.endsWith(".hook.ts") || file.endsWith(".hook.tsx"),
230
+ isHelperFile: (file) => file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts"),
231
+ isVizFile: (file) => file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected"),
232
+ isApiFile: (file) => file.includes("/api/") || file.includes("/lib/") || file.includes("/utils/") || file.endsWith(".ts"),
233
+ isPackageOrApp: (file) => file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/"),
234
+ getPackageName: (file) => {
235
+ const match = file.match(/\/(packages|apps|core)\/([^/]+)\//);
236
+ return match?.[2] || null;
237
+ }
238
+ };
239
+
240
+ // src/rules/categories/logic/keyword-lists.ts
241
+ var VALIDATION_KEYWORDS = [
242
+ "isValid",
243
+ "validate",
244
+ "checkValid",
245
+ "isEmail",
246
+ "isPhone",
247
+ "isUrl",
248
+ "isNumeric",
249
+ "isAlpha",
250
+ "isAlphanumeric",
251
+ "isEmpty",
252
+ "isNotEmpty",
253
+ "isRequired",
254
+ "isOptional"
255
+ ];
256
+ var HOOK_KEYWORDS = [
257
+ "function use",
258
+ "export function use",
259
+ "const use",
260
+ "export const use"
261
+ ];
262
+ var VIZ_EVENT_KEYWORDS = ["dragstarted", "dragged", "dragended"];
263
+ var VIZ_LIB_KEYWORDS = ["simulation", "d3.", "alphaTarget"];
264
+ var ICON_HELPER_KEYWORDS = [
265
+ "getIcon",
266
+ "getColor",
267
+ "getLabel",
268
+ "getRating"
269
+ ];
270
+ var UTIL_KEYWORDS = [
271
+ "format",
272
+ "parse",
273
+ "sanitize",
274
+ "normalize",
275
+ "convert",
276
+ "utility",
277
+ "helper"
278
+ ];
279
+
280
+ // src/rules/categories/logic/detectors.ts
281
+ var isEnumDefinition = (code) => /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
282
+ var hasSingletonGetter = (code) => /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
283
+ code
284
+ ) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
285
+ code
286
+ );
287
+ var hasSingletonPattern = (code) => code.includes("if (!") && code.includes("instance") && code.includes(" = ") || code.includes("if (!_") && code.includes(" = new ");
288
+ var hasReExportPattern = (code) => {
289
+ const lines = code.split("\n").filter((l) => l.trim());
290
+ if (lines.length === 0) return false;
291
+ const reExportLines = lines.filter(
292
+ (l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
293
+ ).length;
294
+ return reExportLines > 0 && reExportLines / lines.length > 0.5;
295
+ };
296
+ var isInterfaceOnlySnippet = (code) => {
297
+ const hasInterface = code.includes("interface ");
298
+ const hasType = code.includes("type ");
299
+ const hasEnum = code.includes("enum ");
300
+ const hasNoImpl = ![
301
+ "function ",
302
+ "class ",
303
+ "const ",
304
+ "let ",
305
+ "var ",
306
+ "export default",
307
+ "export {"
308
+ ].some((kw) => code.includes(kw));
309
+ return (hasInterface || hasType || hasEnum) && hasNoImpl;
310
+ };
311
+
312
+ // src/rules/categories/logic/code-patterns.ts
313
+ var COMMON_ENUM_PATTERNS = [
314
+ ["LOW", ["'low'", "0", "'LOW'"]],
315
+ ["HIGH", ["'high'", "2", "'HIGH'"]],
316
+ ["MEDIUM", ["'medium'", "1", "'MEDIUM'"]]
317
+ ];
318
+ var hasEnumValue = (code, enumName, variants) => variants.some((v) => code.includes(`${enumName} = ${v}`));
319
+ var CodePatterns = {
320
+ // Enum patterns
321
+ hasCommonEnumValues: (code) => COMMON_ENUM_PATTERNS.every(
322
+ ([name, variants]) => hasEnumValue(code, name, variants)
323
+ ),
324
+ isEnumDefinition,
325
+ // Type patterns - helpers for checking type-only definitions
326
+ hasTypeDefinition: (code) => code.includes("interface ") || code.includes("type ") || code.includes("enum "),
327
+ isTypeOnlyFile: (code) => {
328
+ const hasTypes = CodePatterns.hasTypeDefinition(code);
329
+ const hasNoImpl = ![
330
+ "function ",
331
+ "class ",
332
+ "const ",
333
+ "let ",
334
+ "export default"
335
+ ].some((kw) => code.includes(kw));
336
+ return hasTypes && hasNoImpl;
337
+ },
338
+ hasOnlyTypeDefinitions: (code) => {
339
+ const hasTypes = CodePatterns.hasTypeDefinition(code);
340
+ const hasNoImpl = ![
341
+ "function ",
342
+ "class ",
343
+ "const ",
344
+ "let ",
345
+ "var ",
346
+ "export default",
347
+ "export {"
348
+ ].some((kw) => code.includes(kw));
349
+ return hasTypes && hasNoImpl;
350
+ },
351
+ // Utility patterns - function group patterns
352
+ hasUtilPattern: (code) => CodePatterns.hasFunctionGroup(code, "") || CodePatterns.hasKeywordGroup(code, UTIL_KEYWORDS),
353
+ hasFunctionGroup: (code, prefix) => {
354
+ const regex = new RegExp(
355
+ `${prefix}(format|parse|sanitize|normalize|convert)`,
356
+ "i"
357
+ );
358
+ return regex.test(code);
359
+ },
360
+ // Pattern checkers using keyword lists
361
+ hasKeywordGroup: (code, keywords) => keywords.some((kw) => code.includes(kw)),
362
+ // Validation patterns - unified keyword check
363
+ hasValidationPattern: (code) => CodePatterns.hasKeywordGroup(code, VALIDATION_KEYWORDS),
364
+ // Hook patterns - React/Vue hooks
365
+ hasHookPattern: (code) => CodePatterns.hasKeywordGroup(code, HOOK_KEYWORDS),
366
+ // Score/Rating patterns
367
+ hasScorePattern: (code) => (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3,
368
+ // Visualization patterns - D3/Canvas
369
+ hasVizPattern: (code) => CodePatterns.hasKeywordGroup(code, VIZ_EVENT_KEYWORDS) && CodePatterns.hasKeywordGroup(code, VIZ_LIB_KEYWORDS),
370
+ // Switch/Icon patterns
371
+ hasSwitchPattern: (code) => code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4,
372
+ hasIconPattern: (code) => CodePatterns.hasKeywordGroup(code, ICON_HELPER_KEYWORDS),
373
+ // Singleton patterns
374
+ hasSingletonGetter,
375
+ hasSingletonPattern,
376
+ // Re-export patterns
377
+ hasReExportPattern,
378
+ // Interface-only snippets
379
+ isInterfaceOnlySnippet
380
+ };
381
+
382
+ // src/rules/categories/logic/api-patterns.ts
383
+ var API_PATTERNS = {
384
+ stripe: {
385
+ functions: ["getStripe"],
386
+ keywords: ["process.env.STRIPE_SECRET_KEY"]
387
+ },
388
+ userManagement: {
389
+ functions: ["getUserByEmail"],
390
+ keywords: ["queryItems"]
391
+ },
392
+ userUpdate: {
393
+ functions: ["updateUser"],
394
+ keywords: ["buildUpdateExpression"]
395
+ },
396
+ repositoryListing: {
397
+ functions: ["listUserRepositories", "listTeamRepositories"],
398
+ keywords: ["queryItems"]
399
+ },
400
+ remediationQueries: {
401
+ functions: ["getRemediation"],
402
+ keywords: ["queryItems"]
403
+ },
404
+ keyFormatting: {
405
+ functions: ["formatBreakdownKey"],
406
+ keywords: [".replace(/([A-Z])/g"]
407
+ },
408
+ dynamoDbQueries: {
409
+ functions: ["queryItems"],
410
+ keywords: ["KeyConditionExpression"]
411
+ },
412
+ itemOperations: {
413
+ functions: ["putItem"],
414
+ keywords: ["createdAt"]
415
+ },
416
+ itemUpdates: {
417
+ functions: ["updateItem"],
418
+ keywords: ["buildUpdateExpression"]
419
+ }
420
+ };
421
+ var ApiPatterns = {
422
+ /**
423
+ * Check if code contains a common API pattern
424
+ * Reduces from 11 separate || conditions to single call
425
+ */
426
+ hasCommonApiPattern: (code) => {
427
+ for (const pattern of Object.values(API_PATTERNS)) {
428
+ const hasFunctions = pattern.functions.some((fn) => code.includes(fn));
429
+ const hasKeywords = pattern.keywords.some((kw) => code.includes(kw));
430
+ if (hasFunctions && hasKeywords) {
431
+ return true;
236
432
  }
237
- return enums.length > 0;
238
- },
433
+ }
434
+ return false;
435
+ },
436
+ /**
437
+ * Get all API patterns (useful for documentation/analysis)
438
+ */
439
+ getPatterns: () => API_PATTERNS,
440
+ /**
441
+ * Check if code matches a specific API pattern by name
442
+ */
443
+ matchesPattern: (code, patternName) => {
444
+ const pattern = API_PATTERNS[patternName];
445
+ if (!pattern) return false;
446
+ const hasFunctions = pattern.functions.some((fn) => code.includes(fn));
447
+ const hasKeywords = pattern.keywords.some((kw) => code.includes(kw));
448
+ return hasFunctions && hasKeywords;
449
+ }
450
+ };
451
+
452
+ // src/rules/categories/logic/rule-builders.ts
453
+ var import_core4 = require("@aiready/core");
454
+ function createRule(config) {
455
+ return {
456
+ name: config.name,
457
+ detect: config.detect,
458
+ severity: config.severity || import_core4.Severity.Info,
459
+ reason: config.reason,
460
+ suggestion: config.suggestion
461
+ };
462
+ }
463
+ var RuleTemplates = {
464
+ // Type-related rules
465
+ typeDefinition: {
466
+ severity: import_core4.Severity.Info,
467
+ reason: "Type/interface definitions are intentionally duplicated for module independence",
468
+ suggestion: "Extract to shared types package only if causing maintenance burden"
469
+ },
470
+ crossPackageType: {
471
+ severity: import_core4.Severity.Info,
472
+ reason: "Types in different packages/modules are often intentionally similar for module independence",
473
+ suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
474
+ },
475
+ // Pattern rules
476
+ standardPatterns: {
477
+ severity: import_core4.Severity.Info,
478
+ reason: "This pattern is inherently similar and intentionally duplicated across modules",
479
+ suggestion: "Pattern duplication is acceptable for domain clarity and independence"
480
+ },
481
+ // API rules
482
+ commonApiFunction: {
483
+ severity: import_core4.Severity.Info,
484
+ reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
485
+ suggestion: "Consider extracting to shared utilities only if causing significant duplication"
486
+ },
487
+ // Utility rules
488
+ utilityFunction: {
489
+ severity: import_core4.Severity.Info,
490
+ reason: "Utility functions may be intentionally similar across modules",
491
+ suggestion: "Consider extracting to shared utilities only if causing significant duplication"
492
+ },
493
+ // Boilerplate rules
494
+ boilerplate: {
495
+ severity: import_core4.Severity.Info,
496
+ reason: "This pattern is boilerplate and acceptable duplication",
497
+ suggestion: "Boilerplate reduction is acceptable for standard patterns"
498
+ },
499
+ // Enum rules
500
+ enumSemantic: {
239
501
  severity: import_core4.Severity.Info,
240
502
  reason: "Enums with different names represent different semantic domain concepts, even if they share similar values",
241
503
  suggestion: "Different enums (e.g., EscalationPriority vs HealthSeverity) serve different purposes and should not be merged"
242
504
  },
243
- // Enum Value Similarity - Common enum values like LOW, MEDIUM, HIGH are standard
244
- {
245
- name: "enum-value-similarity",
246
- detect: (file, code) => {
247
- 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'"));
248
- const isEnumDefinition = /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
249
- return hasCommonEnumValues && isEnumDefinition;
250
- },
505
+ enumValue: {
251
506
  severity: import_core4.Severity.Info,
252
507
  reason: "Common enum values (LOW, MEDIUM, HIGH, CRITICAL) are standard patterns used across different domain enums",
253
508
  suggestion: "Enum value similarity is expected for severity/priority enums and should not be flagged as duplication"
254
509
  },
255
- // Re-export / Barrel files - Intentional API surface consolidation
256
- {
257
- name: "re-export-files",
258
- detect: (file, code) => {
259
- const isIndexFile = file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx");
260
- const lines = code.split("\n").filter((l) => l.trim());
261
- if (lines.length === 0) return false;
262
- const reExportLines = lines.filter(
263
- (l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
264
- ).length;
265
- return isIndexFile && reExportLines > 0 && reExportLines / lines.length > 0.5;
266
- },
510
+ // Barrel file rules
511
+ barrelFile: {
267
512
  severity: import_core4.Severity.Info,
268
513
  reason: "Barrel/index files intentionally re-export for API surface consolidation",
269
514
  suggestion: "Re-exports in barrel files are expected and not true duplication"
270
- },
271
- // Type Definitions - Duplication for type safety and module independence
272
- {
515
+ }
516
+ };
517
+
518
+ // src/rules/categories/logic-rules.ts
519
+ var LOGIC_RULES = [
520
+ // Enum patterns - identify by semantic meaning and value patterns
521
+ createRule({
522
+ name: "enum-semantic-difference",
523
+ detect: (file, code) => CodePatterns.isEnumDefinition(code),
524
+ ...RuleTemplates.enumSemantic
525
+ }),
526
+ createRule({
527
+ name: "enum-value-similarity",
528
+ detect: (file, code) => CodePatterns.hasCommonEnumValues(code) && CodePatterns.isEnumDefinition(code),
529
+ ...RuleTemplates.enumValue
530
+ }),
531
+ // Barrel/re-export files - intentional API consolidation
532
+ createRule({
533
+ name: "re-export-files",
534
+ detect: (file, code) => FileDetectors.isIndexFile(file) && CodePatterns.hasReExportPattern(code),
535
+ ...RuleTemplates.barrelFile
536
+ }),
537
+ // Type definitions - consolidated rule for both local and cross-package types
538
+ // PHASE 2 CONSOLIDATION: merged type-definitions + cross-package-types
539
+ createRule({
273
540
  name: "type-definitions",
274
541
  detect: (file, code) => {
275
- const isTypeFile = file.endsWith(".d.ts") || file.includes("/types/");
276
- 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");
277
- const isInterfaceOnlySnippet = code.trim().startsWith("interface ") && code.includes("{") && code.includes("}") && !code.includes("function ") && !code.includes("const ") && !code.includes("return ");
278
- return isTypeFile && hasOnlyTypeDefinitions || isInterfaceOnlySnippet;
279
- },
280
- severity: import_core4.Severity.Info,
281
- reason: "Type/interface definitions are intentionally duplicated for module independence",
282
- suggestion: "Extract to shared types package only if causing maintenance burden"
283
- },
284
- // Cross-Package Type Definitions - Different packages may have similar types
285
- {
286
- name: "cross-package-types",
287
- detect: (file, code) => {
288
- const hasTypeDefinition = code.includes("interface ") || code.includes("type ") || code.includes("enum ");
289
- const isPackageOrApp = file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/");
290
- const packageMatch = file.match(/\/(packages|apps|core)\/([^/]+)\//);
291
- const hasPackageStructure = packageMatch !== null;
292
- return hasTypeDefinition && isPackageOrApp && hasPackageStructure;
542
+ if (FileDetectors.isTypeFile(file)) {
543
+ if (CodePatterns.hasOnlyTypeDefinitions(code)) return true;
544
+ }
545
+ if (CodePatterns.isInterfaceOnlySnippet(code)) return true;
546
+ if (FileDetectors.isPackageOrApp(file) && FileDetectors.getPackageName(file) !== null && CodePatterns.hasTypeDefinition(code)) {
547
+ return true;
548
+ }
549
+ return false;
293
550
  },
294
- severity: import_core4.Severity.Info,
295
- reason: "Types in different packages/modules are often intentionally similar for module independence",
296
- suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
297
- },
298
- // Utility Functions - Small helpers in dedicated utility files
299
- {
551
+ reason: "Type/interface definitions are intentionally duplicated for module independence and decoupled module architecture",
552
+ suggestion: "Extract to shared types package only if causing significant maintenance burden or cross-package conflicts"
553
+ }),
554
+ // Utility functions - dedicated utility file patterns
555
+ createRule({
300
556
  name: "utility-functions",
301
- detect: (file, code) => {
302
- const isUtilFile = file.endsWith(".util.ts") || file.endsWith(".helper.ts") || file.endsWith(".utils.ts");
303
- const hasUtilPattern = code.includes("function format") || code.includes("function parse") || code.includes("function sanitize") || code.includes("function normalize") || code.includes("function convert");
304
- return isUtilFile && hasUtilPattern;
305
- },
306
- severity: import_core4.Severity.Info,
307
- reason: "Utility functions in dedicated utility files may be intentionally similar",
308
- suggestion: "Consider extracting to shared utilities only if causing significant duplication"
309
- },
310
- // React/Vue Hooks - Standard patterns
311
- {
557
+ detect: (file, code) => FileDetectors.isUtilFile(file) && CodePatterns.hasUtilPattern(code),
558
+ ...RuleTemplates.utilityFunction
559
+ }),
560
+ // React/Vue Hooks - standard hook patterns
561
+ createRule({
312
562
  name: "shared-hooks",
313
- detect: (file, code) => {
314
- const isHookFile = file.includes("/hooks/") || file.endsWith(".hook.ts") || file.endsWith(".hook.tsx");
315
- const hasHookPattern = code.includes("function use") || code.includes("export function use") || code.includes("const use") || code.includes("export const use");
316
- return isHookFile && hasHookPattern;
317
- },
318
- severity: import_core4.Severity.Info,
563
+ detect: (file, code) => FileDetectors.isHookFile(file) && CodePatterns.hasHookPattern(code),
319
564
  reason: "Hooks follow standard patterns and are often intentionally similar across components",
320
565
  suggestion: "Consider extracting common hook logic only if hooks become complex"
321
- },
322
- // Score/Rating Helper Functions - Common threshold patterns
323
- {
566
+ }),
567
+ // Score/Rating helpers - common threshold patterns
568
+ createRule({
324
569
  name: "score-helpers",
325
- detect: (file, code) => {
326
- const isHelperFile = file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts");
327
- const hasScorePattern = (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3;
328
- return isHelperFile && hasScorePattern;
329
- },
330
- severity: import_core4.Severity.Info,
570
+ detect: (file, code) => (FileDetectors.isHelperFile(file) || file.includes("/utils/")) && CodePatterns.hasScorePattern(code),
331
571
  reason: "Score/rating helper functions use common threshold patterns that are intentionally similar",
332
572
  suggestion: "Score formatting duplication is acceptable for consistent UI display"
333
- },
334
- // D3/Canvas Event Handlers - Standard visualization patterns
335
- {
573
+ }),
574
+ // D3/Canvas event handlers - visualization patterns
575
+ createRule({
336
576
  name: "visualization-handlers",
337
- detect: (file, code) => {
338
- const isVizFile = file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected");
339
- const hasVizPattern = (code.includes("dragstarted") || code.includes("dragged") || code.includes("dragended")) && (code.includes("simulation") || code.includes("d3.") || code.includes("alphaTarget"));
340
- return isVizFile && hasVizPattern;
341
- },
342
- severity: import_core4.Severity.Info,
577
+ detect: (file, code) => FileDetectors.isVizFile(file) && CodePatterns.hasVizPattern(code),
343
578
  reason: "D3/visualization event handlers follow standard patterns and are intentionally similar",
344
579
  suggestion: "Visualization boilerplate duplication is acceptable for interactive charts"
345
- },
346
- // Icon/Switch Statement Helpers - Common enum-to-value patterns
347
- {
580
+ }),
581
+ // Switch statement helpers - enum-to-value mapping
582
+ createRule({
348
583
  name: "switch-helpers",
349
- detect: (file, code) => {
350
- const hasSwitchPattern = code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4;
351
- const hasIconPattern = code.includes("getIcon") || code.includes("getColor") || code.includes("getLabel") || code.includes("getRating");
352
- return hasSwitchPattern && hasIconPattern;
353
- },
354
- severity: import_core4.Severity.Info,
584
+ detect: (file, code) => CodePatterns.hasSwitchPattern(code) && CodePatterns.hasIconPattern(code),
355
585
  reason: "Switch statement helpers for enum-to-value mapping are inherently similar",
356
586
  suggestion: "Switch duplication is acceptable for mapping enums to display values"
357
- },
358
- // Common API/Utility Functions - Legitimate duplication across modules
359
- {
587
+ }),
588
+ // Common API/Utility functions - legitimately duplicated across modules
589
+ createRule({
360
590
  name: "common-api-functions",
361
- detect: (file, code) => {
362
- const isApiFile = file.includes("/api/") || file.includes("/lib/") || file.includes("/utils/") || file.endsWith(".ts");
363
- 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");
364
- return isApiFile && hasCommonApiPattern;
365
- },
366
- severity: import_core4.Severity.Info,
367
- reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
368
- suggestion: "Consider extracting to shared utilities only if causing significant duplication"
369
- },
370
- // Validation Functions - Inherently similar patterns
371
- {
591
+ detect: (file, code) => FileDetectors.isApiFile(file) && ApiPatterns.hasCommonApiPattern(code),
592
+ ...RuleTemplates.commonApiFunction
593
+ }),
594
+ // Validation functions - inherently similar patterns
595
+ // PHASE 2: Consolidated validation-function branches into single rule
596
+ createRule({
372
597
  name: "validation-functions",
373
- detect: (file, code) => {
374
- 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");
375
- return hasValidationPattern;
376
- },
377
- severity: import_core4.Severity.Info,
598
+ detect: (file, code) => CodePatterns.hasValidationPattern(code),
378
599
  reason: "Validation functions are inherently similar and often intentionally duplicated for domain clarity",
379
600
  suggestion: "Consider extracting to shared validators only if validation logic becomes complex"
380
- },
381
- // Singleton Getter Pattern - Standard singleton initialization pattern
382
- {
601
+ }),
602
+ // Singleton getter pattern - standard initialization
603
+ createRule({
383
604
  name: "singleton-getter",
384
- detect: (file, code) => {
385
- const hasSingletonGetter = /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
386
- code
387
- ) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
388
- code
389
- );
390
- 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 ");
391
- return hasSingletonGetter && hasSingletonPattern;
392
- },
393
- severity: import_core4.Severity.Info,
605
+ detect: (file, code) => CodePatterns.hasSingletonGetter(code) && CodePatterns.hasSingletonPattern(code),
394
606
  reason: "Singleton getter functions follow standard initialization pattern and are intentionally similar",
395
607
  suggestion: "Singleton getters are boilerplate and acceptable duplication for lazy initialization"
396
- }
608
+ })
397
609
  ];
398
610
 
399
611
  // src/context-rules.ts
@@ -426,7 +638,7 @@ function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
426
638
  reason: "Nearly identical code should be consolidated",
427
639
  suggestion: "Move to shared utility file"
428
640
  };
429
- } else if (similarity >= 0.85) {
641
+ } else if (similarity >= 0.85 && linesOfCode >= 10) {
430
642
  return {
431
643
  severity: import_core5.Severity.Major,
432
644
  reason: "High similarity indicates significant duplication",
@@ -480,7 +692,10 @@ async function detectDuplicatePatterns(fileContents, options) {
480
692
  ignoreWhitelist = []
481
693
  } = options;
482
694
  const allBlocks = [];
483
- const excludeRegexes = excludePatterns.map((p) => new RegExp(p, "i"));
695
+ const regexStr = (f) => f.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "[^/]*").replace(/\[\^\/\]\*\[\^\/\]\*/g, ".*");
696
+ const excludeRegexes = excludePatterns.map(
697
+ (p) => new RegExp(`${regexStr(p)}$`, "i")
698
+ );
484
699
  for (const { file, content } of fileContents) {
485
700
  const blocks = extractBlocks(file, content);
486
701
  for (const b of blocks) {
@@ -1000,7 +1215,7 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
1000
1215
  0
1001
1216
  );
1002
1217
  const highImpactDuplicates = actionableDuplicates.filter(
1003
- (d) => d.tokenCost > 1e3 || d.similarity > 0.7
1218
+ (d) => d.severity === "critical" || d.severity === "major" || d.tokenCost > 1e3
1004
1219
  ).length;
1005
1220
  if (totalFilesAnalyzed === 0) {
1006
1221
  return {
@@ -1039,7 +1254,7 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
1039
1254
  factors.push({
1040
1255
  name: "High-Impact Patterns",
1041
1256
  impact: -Math.round(highImpactPenalty),
1042
- description: `${highImpactDuplicates} high-impact duplicates (>1000 tokens or >70% similar)`
1257
+ description: `${highImpactDuplicates} high-impact duplicates (Critical/Major or >1000 tokens)`
1043
1258
  });
1044
1259
  } else {
1045
1260
  factors.push({
@@ -1116,6 +1331,7 @@ async function analyzePatterns(options) {
1116
1331
  minClusterTokenCost = 1e3,
1117
1332
  minClusterFiles = 3,
1118
1333
  excludePatterns = [],
1334
+ excludeFiles = [],
1119
1335
  confidenceThreshold = 0,
1120
1336
  ignoreWhitelist = [],
1121
1337
  ...scanOptions
@@ -1145,6 +1361,7 @@ async function analyzePatterns(options) {
1145
1361
  maxCandidatesPerBlock,
1146
1362
  streamResults,
1147
1363
  excludePatterns,
1364
+ excludeFiles,
1148
1365
  confidenceThreshold,
1149
1366
  ignoreWhitelist,
1150
1367
  onProgress: options.onProgress
package/dist/index.mjs CHANGED
@@ -12,16 +12,16 @@ import {
12
12
  getSmartDefaults,
13
13
  groupDuplicatesByFilePair,
14
14
  logConfiguration
15
- } from "./chunk-WQX7IHAN.mjs";
15
+ } from "./chunk-WQC43BIO.mjs";
16
16
  import {
17
17
  detectDuplicatePatterns
18
- } from "./chunk-JWP5TCDM.mjs";
18
+ } from "./chunk-XR373Q6G.mjs";
19
19
  import {
20
20
  filterBySeverity
21
- } from "./chunk-KDXWIT6W.mjs";
21
+ } from "./chunk-XWIBTD67.mjs";
22
22
  import {
23
23
  calculatePatternScore
24
- } from "./chunk-G3GZFYRI.mjs";
24
+ } from "./chunk-UXV57HN3.mjs";
25
25
 
26
26
  // src/index.ts
27
27
  import { ToolRegistry } from "@aiready/core";
@@ -1,5 +1,5 @@
1
1
  import { CostConfig, ToolScoringOutput } from '@aiready/core';
2
- import { D as DuplicatePattern } from '../types-DU2mmhwb.mjs';
2
+ import { D as DuplicatePattern } from '../types-tgrmUrHE.mjs';
3
3
 
4
4
  /**
5
5
  * Calculate AI Readiness Score for pattern duplication (0-100)
@@ -1,5 +1,5 @@
1
1
  import { CostConfig, ToolScoringOutput } from '@aiready/core';
2
- import { D as DuplicatePattern } from '../types-DU2mmhwb.js';
2
+ import { D as DuplicatePattern } from '../types-tgrmUrHE.js';
3
3
 
4
4
  /**
5
5
  * Calculate AI Readiness Score for pattern duplication (0-100)
@@ -62,7 +62,7 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
62
62
  0
63
63
  );
64
64
  const highImpactDuplicates = actionableDuplicates.filter(
65
- (d) => d.tokenCost > 1e3 || d.similarity > 0.7
65
+ (d) => d.severity === "critical" || d.severity === "major" || d.tokenCost > 1e3
66
66
  ).length;
67
67
  if (totalFilesAnalyzed === 0) {
68
68
  return {
@@ -101,7 +101,7 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
101
101
  factors.push({
102
102
  name: "High-Impact Patterns",
103
103
  impact: -Math.round(highImpactPenalty),
104
- description: `${highImpactDuplicates} high-impact duplicates (>1000 tokens or >70% similar)`
104
+ description: `${highImpactDuplicates} high-impact duplicates (Critical/Major or >1000 tokens)`
105
105
  });
106
106
  } else {
107
107
  factors.push({
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  calculatePatternScore
3
- } from "../chunk-G3GZFYRI.mjs";
3
+ } from "../chunk-UXV57HN3.mjs";
4
4
  export {
5
5
  calculatePatternScore
6
6
  };