@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
@@ -189,179 +189,391 @@ var INFRA_RULES = [
189
189
  }
190
190
  ];
191
191
 
192
- // src/rules/categories/logic-rules.ts
193
- var import_core4 = require("@aiready/core");
194
- var LOGIC_RULES = [
195
- // Enum Semantic Difference - Different enum names indicate different semantic meanings
196
- {
197
- name: "enum-semantic-difference",
198
- detect: (file, code) => {
199
- const enumRegex = /(?:export\s+)?(?:const\s+)?enum\s+([A-Z][a-zA-Z0-9]*)/g;
200
- const enums = [];
201
- let match;
202
- while ((match = enumRegex.exec(code)) !== null) {
203
- enums.push(match[1]);
192
+ // src/rules/categories/logic/file-detectors.ts
193
+ var FileDetectors = {
194
+ isIndexFile: (file) => file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx"),
195
+ isTypeFile: (file) => file.endsWith(".d.ts") || file.includes("/types/"),
196
+ isUtilFile: (file) => file.endsWith(".util.ts") || file.endsWith(".helper.ts") || file.endsWith(".utils.ts"),
197
+ isHookFile: (file) => file.includes("/hooks/") || file.endsWith(".hook.ts") || file.endsWith(".hook.tsx"),
198
+ isHelperFile: (file) => file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts"),
199
+ isVizFile: (file) => file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected"),
200
+ isApiFile: (file) => file.includes("/api/") || file.includes("/lib/") || file.includes("/utils/") || file.endsWith(".ts"),
201
+ isPackageOrApp: (file) => file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/"),
202
+ getPackageName: (file) => {
203
+ const match = file.match(/\/(packages|apps|core)\/([^/]+)\//);
204
+ return match?.[2] || null;
205
+ }
206
+ };
207
+
208
+ // src/rules/categories/logic/keyword-lists.ts
209
+ var VALIDATION_KEYWORDS = [
210
+ "isValid",
211
+ "validate",
212
+ "checkValid",
213
+ "isEmail",
214
+ "isPhone",
215
+ "isUrl",
216
+ "isNumeric",
217
+ "isAlpha",
218
+ "isAlphanumeric",
219
+ "isEmpty",
220
+ "isNotEmpty",
221
+ "isRequired",
222
+ "isOptional"
223
+ ];
224
+ var HOOK_KEYWORDS = [
225
+ "function use",
226
+ "export function use",
227
+ "const use",
228
+ "export const use"
229
+ ];
230
+ var VIZ_EVENT_KEYWORDS = ["dragstarted", "dragged", "dragended"];
231
+ var VIZ_LIB_KEYWORDS = ["simulation", "d3.", "alphaTarget"];
232
+ var ICON_HELPER_KEYWORDS = [
233
+ "getIcon",
234
+ "getColor",
235
+ "getLabel",
236
+ "getRating"
237
+ ];
238
+ var UTIL_KEYWORDS = [
239
+ "format",
240
+ "parse",
241
+ "sanitize",
242
+ "normalize",
243
+ "convert",
244
+ "utility",
245
+ "helper"
246
+ ];
247
+
248
+ // src/rules/categories/logic/detectors.ts
249
+ var isEnumDefinition = (code) => /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
250
+ var hasSingletonGetter = (code) => /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
251
+ code
252
+ ) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
253
+ code
254
+ );
255
+ var hasSingletonPattern = (code) => code.includes("if (!") && code.includes("instance") && code.includes(" = ") || code.includes("if (!_") && code.includes(" = new ");
256
+ var hasReExportPattern = (code) => {
257
+ const lines = code.split("\n").filter((l) => l.trim());
258
+ if (lines.length === 0) return false;
259
+ const reExportLines = lines.filter(
260
+ (l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
261
+ ).length;
262
+ return reExportLines > 0 && reExportLines / lines.length > 0.5;
263
+ };
264
+ var isInterfaceOnlySnippet = (code) => {
265
+ const hasInterface = code.includes("interface ");
266
+ const hasType = code.includes("type ");
267
+ const hasEnum = code.includes("enum ");
268
+ const hasNoImpl = ![
269
+ "function ",
270
+ "class ",
271
+ "const ",
272
+ "let ",
273
+ "var ",
274
+ "export default",
275
+ "export {"
276
+ ].some((kw) => code.includes(kw));
277
+ return (hasInterface || hasType || hasEnum) && hasNoImpl;
278
+ };
279
+
280
+ // src/rules/categories/logic/code-patterns.ts
281
+ var COMMON_ENUM_PATTERNS = [
282
+ ["LOW", ["'low'", "0", "'LOW'"]],
283
+ ["HIGH", ["'high'", "2", "'HIGH'"]],
284
+ ["MEDIUM", ["'medium'", "1", "'MEDIUM'"]]
285
+ ];
286
+ var hasEnumValue = (code, enumName, variants) => variants.some((v) => code.includes(`${enumName} = ${v}`));
287
+ var CodePatterns = {
288
+ // Enum patterns
289
+ hasCommonEnumValues: (code) => COMMON_ENUM_PATTERNS.every(
290
+ ([name, variants]) => hasEnumValue(code, name, variants)
291
+ ),
292
+ isEnumDefinition,
293
+ // Type patterns - helpers for checking type-only definitions
294
+ hasTypeDefinition: (code) => code.includes("interface ") || code.includes("type ") || code.includes("enum "),
295
+ isTypeOnlyFile: (code) => {
296
+ const hasTypes = CodePatterns.hasTypeDefinition(code);
297
+ const hasNoImpl = ![
298
+ "function ",
299
+ "class ",
300
+ "const ",
301
+ "let ",
302
+ "export default"
303
+ ].some((kw) => code.includes(kw));
304
+ return hasTypes && hasNoImpl;
305
+ },
306
+ hasOnlyTypeDefinitions: (code) => {
307
+ const hasTypes = CodePatterns.hasTypeDefinition(code);
308
+ const hasNoImpl = ![
309
+ "function ",
310
+ "class ",
311
+ "const ",
312
+ "let ",
313
+ "var ",
314
+ "export default",
315
+ "export {"
316
+ ].some((kw) => code.includes(kw));
317
+ return hasTypes && hasNoImpl;
318
+ },
319
+ // Utility patterns - function group patterns
320
+ hasUtilPattern: (code) => CodePatterns.hasFunctionGroup(code, "") || CodePatterns.hasKeywordGroup(code, UTIL_KEYWORDS),
321
+ hasFunctionGroup: (code, prefix) => {
322
+ const regex = new RegExp(
323
+ `${prefix}(format|parse|sanitize|normalize|convert)`,
324
+ "i"
325
+ );
326
+ return regex.test(code);
327
+ },
328
+ // Pattern checkers using keyword lists
329
+ hasKeywordGroup: (code, keywords) => keywords.some((kw) => code.includes(kw)),
330
+ // Validation patterns - unified keyword check
331
+ hasValidationPattern: (code) => CodePatterns.hasKeywordGroup(code, VALIDATION_KEYWORDS),
332
+ // Hook patterns - React/Vue hooks
333
+ hasHookPattern: (code) => CodePatterns.hasKeywordGroup(code, HOOK_KEYWORDS),
334
+ // Score/Rating patterns
335
+ hasScorePattern: (code) => (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3,
336
+ // Visualization patterns - D3/Canvas
337
+ hasVizPattern: (code) => CodePatterns.hasKeywordGroup(code, VIZ_EVENT_KEYWORDS) && CodePatterns.hasKeywordGroup(code, VIZ_LIB_KEYWORDS),
338
+ // Switch/Icon patterns
339
+ hasSwitchPattern: (code) => code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4,
340
+ hasIconPattern: (code) => CodePatterns.hasKeywordGroup(code, ICON_HELPER_KEYWORDS),
341
+ // Singleton patterns
342
+ hasSingletonGetter,
343
+ hasSingletonPattern,
344
+ // Re-export patterns
345
+ hasReExportPattern,
346
+ // Interface-only snippets
347
+ isInterfaceOnlySnippet
348
+ };
349
+
350
+ // src/rules/categories/logic/api-patterns.ts
351
+ var API_PATTERNS = {
352
+ stripe: {
353
+ functions: ["getStripe"],
354
+ keywords: ["process.env.STRIPE_SECRET_KEY"]
355
+ },
356
+ userManagement: {
357
+ functions: ["getUserByEmail"],
358
+ keywords: ["queryItems"]
359
+ },
360
+ userUpdate: {
361
+ functions: ["updateUser"],
362
+ keywords: ["buildUpdateExpression"]
363
+ },
364
+ repositoryListing: {
365
+ functions: ["listUserRepositories", "listTeamRepositories"],
366
+ keywords: ["queryItems"]
367
+ },
368
+ remediationQueries: {
369
+ functions: ["getRemediation"],
370
+ keywords: ["queryItems"]
371
+ },
372
+ keyFormatting: {
373
+ functions: ["formatBreakdownKey"],
374
+ keywords: [".replace(/([A-Z])/g"]
375
+ },
376
+ dynamoDbQueries: {
377
+ functions: ["queryItems"],
378
+ keywords: ["KeyConditionExpression"]
379
+ },
380
+ itemOperations: {
381
+ functions: ["putItem"],
382
+ keywords: ["createdAt"]
383
+ },
384
+ itemUpdates: {
385
+ functions: ["updateItem"],
386
+ keywords: ["buildUpdateExpression"]
387
+ }
388
+ };
389
+ var ApiPatterns = {
390
+ /**
391
+ * Check if code contains a common API pattern
392
+ * Reduces from 11 separate || conditions to single call
393
+ */
394
+ hasCommonApiPattern: (code) => {
395
+ for (const pattern of Object.values(API_PATTERNS)) {
396
+ const hasFunctions = pattern.functions.some((fn) => code.includes(fn));
397
+ const hasKeywords = pattern.keywords.some((kw) => code.includes(kw));
398
+ if (hasFunctions && hasKeywords) {
399
+ return true;
204
400
  }
205
- return enums.length > 0;
206
- },
401
+ }
402
+ return false;
403
+ },
404
+ /**
405
+ * Get all API patterns (useful for documentation/analysis)
406
+ */
407
+ getPatterns: () => API_PATTERNS,
408
+ /**
409
+ * Check if code matches a specific API pattern by name
410
+ */
411
+ matchesPattern: (code, patternName) => {
412
+ const pattern = API_PATTERNS[patternName];
413
+ if (!pattern) return false;
414
+ const hasFunctions = pattern.functions.some((fn) => code.includes(fn));
415
+ const hasKeywords = pattern.keywords.some((kw) => code.includes(kw));
416
+ return hasFunctions && hasKeywords;
417
+ }
418
+ };
419
+
420
+ // src/rules/categories/logic/rule-builders.ts
421
+ var import_core4 = require("@aiready/core");
422
+ function createRule(config) {
423
+ return {
424
+ name: config.name,
425
+ detect: config.detect,
426
+ severity: config.severity || import_core4.Severity.Info,
427
+ reason: config.reason,
428
+ suggestion: config.suggestion
429
+ };
430
+ }
431
+ var RuleTemplates = {
432
+ // Type-related rules
433
+ typeDefinition: {
434
+ severity: import_core4.Severity.Info,
435
+ reason: "Type/interface definitions are intentionally duplicated for module independence",
436
+ suggestion: "Extract to shared types package only if causing maintenance burden"
437
+ },
438
+ crossPackageType: {
439
+ severity: import_core4.Severity.Info,
440
+ reason: "Types in different packages/modules are often intentionally similar for module independence",
441
+ suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
442
+ },
443
+ // Pattern rules
444
+ standardPatterns: {
445
+ severity: import_core4.Severity.Info,
446
+ reason: "This pattern is inherently similar and intentionally duplicated across modules",
447
+ suggestion: "Pattern duplication is acceptable for domain clarity and independence"
448
+ },
449
+ // API rules
450
+ commonApiFunction: {
451
+ severity: import_core4.Severity.Info,
452
+ reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
453
+ suggestion: "Consider extracting to shared utilities only if causing significant duplication"
454
+ },
455
+ // Utility rules
456
+ utilityFunction: {
457
+ severity: import_core4.Severity.Info,
458
+ reason: "Utility functions may be intentionally similar across modules",
459
+ suggestion: "Consider extracting to shared utilities only if causing significant duplication"
460
+ },
461
+ // Boilerplate rules
462
+ boilerplate: {
463
+ severity: import_core4.Severity.Info,
464
+ reason: "This pattern is boilerplate and acceptable duplication",
465
+ suggestion: "Boilerplate reduction is acceptable for standard patterns"
466
+ },
467
+ // Enum rules
468
+ enumSemantic: {
207
469
  severity: import_core4.Severity.Info,
208
470
  reason: "Enums with different names represent different semantic domain concepts, even if they share similar values",
209
471
  suggestion: "Different enums (e.g., EscalationPriority vs HealthSeverity) serve different purposes and should not be merged"
210
472
  },
211
- // Enum Value Similarity - Common enum values like LOW, MEDIUM, HIGH are standard
212
- {
213
- name: "enum-value-similarity",
214
- detect: (file, code) => {
215
- 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'"));
216
- const isEnumDefinition = /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
217
- return hasCommonEnumValues && isEnumDefinition;
218
- },
473
+ enumValue: {
219
474
  severity: import_core4.Severity.Info,
220
475
  reason: "Common enum values (LOW, MEDIUM, HIGH, CRITICAL) are standard patterns used across different domain enums",
221
476
  suggestion: "Enum value similarity is expected for severity/priority enums and should not be flagged as duplication"
222
477
  },
223
- // Re-export / Barrel files - Intentional API surface consolidation
224
- {
225
- name: "re-export-files",
226
- detect: (file, code) => {
227
- const isIndexFile = file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx");
228
- const lines = code.split("\n").filter((l) => l.trim());
229
- if (lines.length === 0) return false;
230
- const reExportLines = lines.filter(
231
- (l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
232
- ).length;
233
- return isIndexFile && reExportLines > 0 && reExportLines / lines.length > 0.5;
234
- },
478
+ // Barrel file rules
479
+ barrelFile: {
235
480
  severity: import_core4.Severity.Info,
236
481
  reason: "Barrel/index files intentionally re-export for API surface consolidation",
237
482
  suggestion: "Re-exports in barrel files are expected and not true duplication"
238
- },
239
- // Type Definitions - Duplication for type safety and module independence
240
- {
483
+ }
484
+ };
485
+
486
+ // src/rules/categories/logic-rules.ts
487
+ var LOGIC_RULES = [
488
+ // Enum patterns - identify by semantic meaning and value patterns
489
+ createRule({
490
+ name: "enum-semantic-difference",
491
+ detect: (file, code) => CodePatterns.isEnumDefinition(code),
492
+ ...RuleTemplates.enumSemantic
493
+ }),
494
+ createRule({
495
+ name: "enum-value-similarity",
496
+ detect: (file, code) => CodePatterns.hasCommonEnumValues(code) && CodePatterns.isEnumDefinition(code),
497
+ ...RuleTemplates.enumValue
498
+ }),
499
+ // Barrel/re-export files - intentional API consolidation
500
+ createRule({
501
+ name: "re-export-files",
502
+ detect: (file, code) => FileDetectors.isIndexFile(file) && CodePatterns.hasReExportPattern(code),
503
+ ...RuleTemplates.barrelFile
504
+ }),
505
+ // Type definitions - consolidated rule for both local and cross-package types
506
+ // PHASE 2 CONSOLIDATION: merged type-definitions + cross-package-types
507
+ createRule({
241
508
  name: "type-definitions",
242
509
  detect: (file, code) => {
243
- const isTypeFile = file.endsWith(".d.ts") || file.includes("/types/");
244
- 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");
245
- const isInterfaceOnlySnippet = code.trim().startsWith("interface ") && code.includes("{") && code.includes("}") && !code.includes("function ") && !code.includes("const ") && !code.includes("return ");
246
- return isTypeFile && hasOnlyTypeDefinitions || isInterfaceOnlySnippet;
247
- },
248
- severity: import_core4.Severity.Info,
249
- reason: "Type/interface definitions are intentionally duplicated for module independence",
250
- suggestion: "Extract to shared types package only if causing maintenance burden"
251
- },
252
- // Cross-Package Type Definitions - Different packages may have similar types
253
- {
254
- name: "cross-package-types",
255
- detect: (file, code) => {
256
- const hasTypeDefinition = code.includes("interface ") || code.includes("type ") || code.includes("enum ");
257
- const isPackageOrApp = file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/");
258
- const packageMatch = file.match(/\/(packages|apps|core)\/([^/]+)\//);
259
- const hasPackageStructure = packageMatch !== null;
260
- return hasTypeDefinition && isPackageOrApp && hasPackageStructure;
510
+ if (FileDetectors.isTypeFile(file)) {
511
+ if (CodePatterns.hasOnlyTypeDefinitions(code)) return true;
512
+ }
513
+ if (CodePatterns.isInterfaceOnlySnippet(code)) return true;
514
+ if (FileDetectors.isPackageOrApp(file) && FileDetectors.getPackageName(file) !== null && CodePatterns.hasTypeDefinition(code)) {
515
+ return true;
516
+ }
517
+ return false;
261
518
  },
262
- severity: import_core4.Severity.Info,
263
- reason: "Types in different packages/modules are often intentionally similar for module independence",
264
- suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
265
- },
266
- // Utility Functions - Small helpers in dedicated utility files
267
- {
519
+ reason: "Type/interface definitions are intentionally duplicated for module independence and decoupled module architecture",
520
+ suggestion: "Extract to shared types package only if causing significant maintenance burden or cross-package conflicts"
521
+ }),
522
+ // Utility functions - dedicated utility file patterns
523
+ createRule({
268
524
  name: "utility-functions",
269
- detect: (file, code) => {
270
- const isUtilFile = file.endsWith(".util.ts") || file.endsWith(".helper.ts") || file.endsWith(".utils.ts");
271
- const hasUtilPattern = code.includes("function format") || code.includes("function parse") || code.includes("function sanitize") || code.includes("function normalize") || code.includes("function convert");
272
- return isUtilFile && hasUtilPattern;
273
- },
274
- severity: import_core4.Severity.Info,
275
- reason: "Utility functions in dedicated utility files may be intentionally similar",
276
- suggestion: "Consider extracting to shared utilities only if causing significant duplication"
277
- },
278
- // React/Vue Hooks - Standard patterns
279
- {
525
+ detect: (file, code) => FileDetectors.isUtilFile(file) && CodePatterns.hasUtilPattern(code),
526
+ ...RuleTemplates.utilityFunction
527
+ }),
528
+ // React/Vue Hooks - standard hook patterns
529
+ createRule({
280
530
  name: "shared-hooks",
281
- detect: (file, code) => {
282
- const isHookFile = file.includes("/hooks/") || file.endsWith(".hook.ts") || file.endsWith(".hook.tsx");
283
- const hasHookPattern = code.includes("function use") || code.includes("export function use") || code.includes("const use") || code.includes("export const use");
284
- return isHookFile && hasHookPattern;
285
- },
286
- severity: import_core4.Severity.Info,
531
+ detect: (file, code) => FileDetectors.isHookFile(file) && CodePatterns.hasHookPattern(code),
287
532
  reason: "Hooks follow standard patterns and are often intentionally similar across components",
288
533
  suggestion: "Consider extracting common hook logic only if hooks become complex"
289
- },
290
- // Score/Rating Helper Functions - Common threshold patterns
291
- {
534
+ }),
535
+ // Score/Rating helpers - common threshold patterns
536
+ createRule({
292
537
  name: "score-helpers",
293
- detect: (file, code) => {
294
- const isHelperFile = file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts");
295
- const hasScorePattern = (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3;
296
- return isHelperFile && hasScorePattern;
297
- },
298
- severity: import_core4.Severity.Info,
538
+ detect: (file, code) => (FileDetectors.isHelperFile(file) || file.includes("/utils/")) && CodePatterns.hasScorePattern(code),
299
539
  reason: "Score/rating helper functions use common threshold patterns that are intentionally similar",
300
540
  suggestion: "Score formatting duplication is acceptable for consistent UI display"
301
- },
302
- // D3/Canvas Event Handlers - Standard visualization patterns
303
- {
541
+ }),
542
+ // D3/Canvas event handlers - visualization patterns
543
+ createRule({
304
544
  name: "visualization-handlers",
305
- detect: (file, code) => {
306
- const isVizFile = file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected");
307
- const hasVizPattern = (code.includes("dragstarted") || code.includes("dragged") || code.includes("dragended")) && (code.includes("simulation") || code.includes("d3.") || code.includes("alphaTarget"));
308
- return isVizFile && hasVizPattern;
309
- },
310
- severity: import_core4.Severity.Info,
545
+ detect: (file, code) => FileDetectors.isVizFile(file) && CodePatterns.hasVizPattern(code),
311
546
  reason: "D3/visualization event handlers follow standard patterns and are intentionally similar",
312
547
  suggestion: "Visualization boilerplate duplication is acceptable for interactive charts"
313
- },
314
- // Icon/Switch Statement Helpers - Common enum-to-value patterns
315
- {
548
+ }),
549
+ // Switch statement helpers - enum-to-value mapping
550
+ createRule({
316
551
  name: "switch-helpers",
317
- detect: (file, code) => {
318
- const hasSwitchPattern = code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4;
319
- const hasIconPattern = code.includes("getIcon") || code.includes("getColor") || code.includes("getLabel") || code.includes("getRating");
320
- return hasSwitchPattern && hasIconPattern;
321
- },
322
- severity: import_core4.Severity.Info,
552
+ detect: (file, code) => CodePatterns.hasSwitchPattern(code) && CodePatterns.hasIconPattern(code),
323
553
  reason: "Switch statement helpers for enum-to-value mapping are inherently similar",
324
554
  suggestion: "Switch duplication is acceptable for mapping enums to display values"
325
- },
326
- // Common API/Utility Functions - Legitimate duplication across modules
327
- {
555
+ }),
556
+ // Common API/Utility functions - legitimately duplicated across modules
557
+ createRule({
328
558
  name: "common-api-functions",
329
- detect: (file, code) => {
330
- const isApiFile = file.includes("/api/") || file.includes("/lib/") || file.includes("/utils/") || file.endsWith(".ts");
331
- 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");
332
- return isApiFile && hasCommonApiPattern;
333
- },
334
- severity: import_core4.Severity.Info,
335
- reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
336
- suggestion: "Consider extracting to shared utilities only if causing significant duplication"
337
- },
338
- // Validation Functions - Inherently similar patterns
339
- {
559
+ detect: (file, code) => FileDetectors.isApiFile(file) && ApiPatterns.hasCommonApiPattern(code),
560
+ ...RuleTemplates.commonApiFunction
561
+ }),
562
+ // Validation functions - inherently similar patterns
563
+ // PHASE 2: Consolidated validation-function branches into single rule
564
+ createRule({
340
565
  name: "validation-functions",
341
- detect: (file, code) => {
342
- 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");
343
- return hasValidationPattern;
344
- },
345
- severity: import_core4.Severity.Info,
566
+ detect: (file, code) => CodePatterns.hasValidationPattern(code),
346
567
  reason: "Validation functions are inherently similar and often intentionally duplicated for domain clarity",
347
568
  suggestion: "Consider extracting to shared validators only if validation logic becomes complex"
348
- },
349
- // Singleton Getter Pattern - Standard singleton initialization pattern
350
- {
569
+ }),
570
+ // Singleton getter pattern - standard initialization
571
+ createRule({
351
572
  name: "singleton-getter",
352
- detect: (file, code) => {
353
- const hasSingletonGetter = /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
354
- code
355
- ) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
356
- code
357
- );
358
- 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 ");
359
- return hasSingletonGetter && hasSingletonPattern;
360
- },
361
- severity: import_core4.Severity.Info,
573
+ detect: (file, code) => CodePatterns.hasSingletonGetter(code) && CodePatterns.hasSingletonPattern(code),
362
574
  reason: "Singleton getter functions follow standard initialization pattern and are intentionally similar",
363
575
  suggestion: "Singleton getters are boilerplate and acceptable duplication for lazy initialization"
364
- }
576
+ })
365
577
  ];
366
578
 
367
579
  // src/context-rules.ts
@@ -394,7 +606,7 @@ function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
394
606
  reason: "Nearly identical code should be consolidated",
395
607
  suggestion: "Move to shared utility file"
396
608
  };
397
- } else if (similarity >= 0.85) {
609
+ } else if (similarity >= 0.85 && linesOfCode >= 10) {
398
610
  return {
399
611
  severity: import_core5.Severity.Major,
400
612
  reason: "High similarity indicates significant duplication",
@@ -3,7 +3,7 @@ import {
3
3
  filterBySeverity,
4
4
  getSeverityLabel,
5
5
  getSeverityThreshold
6
- } from "../chunk-KDXWIT6W.mjs";
6
+ } from "../chunk-XWIBTD67.mjs";
7
7
  export {
8
8
  calculateSeverity,
9
9
  filterBySeverity,
@@ -1,5 +1,5 @@
1
- import { a as DetectionOptions, D as DuplicatePattern } from '../types-DU2mmhwb.mjs';
2
- export { P as PatternType } from '../types-DU2mmhwb.mjs';
1
+ import { a as DetectionOptions, D as DuplicatePattern } from '../types-tgrmUrHE.mjs';
2
+ export { P as PatternType } from '../types-tgrmUrHE.mjs';
3
3
  import { FileContent } from '@aiready/core';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { a as DetectionOptions, D as DuplicatePattern } from '../types-DU2mmhwb.js';
2
- export { P as PatternType } from '../types-DU2mmhwb.js';
1
+ import { a as DetectionOptions, D as DuplicatePattern } from '../types-tgrmUrHE.js';
2
+ export { P as PatternType } from '../types-tgrmUrHE.js';
3
3
  import { FileContent } from '@aiready/core';
4
4
 
5
5
  /**