awesome-slash 2.4.4 → 2.5.0

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 (144) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +88 -1
  4. package/README.md +173 -161
  5. package/SECURITY.md +25 -81
  6. package/adapters/codex/install.sh +58 -16
  7. package/adapters/opencode/install.sh +92 -23
  8. package/lib/index.js +47 -4
  9. package/lib/patterns/review-patterns.js +58 -11
  10. package/lib/patterns/slop-patterns.js +154 -147
  11. package/lib/platform/detect-platform.js +99 -350
  12. package/lib/platform/detection-configs.js +93 -0
  13. package/lib/platform/verify-tools.js +10 -78
  14. package/lib/schemas/README.md +195 -0
  15. package/lib/schemas/validator.js +247 -0
  16. package/lib/sources/custom-handler.js +199 -0
  17. package/lib/sources/policy-questions.js +239 -0
  18. package/lib/sources/source-cache.js +149 -0
  19. package/lib/state/workflow-state.js +363 -665
  20. package/lib/types/README.md +292 -0
  21. package/lib/types/agent-frontmatter.d.ts +134 -0
  22. package/lib/types/command-frontmatter.d.ts +107 -0
  23. package/lib/types/hook-frontmatter.d.ts +115 -0
  24. package/lib/types/index.d.ts +84 -0
  25. package/lib/types/plugin-manifest.d.ts +102 -0
  26. package/lib/types/skill-frontmatter.d.ts +89 -0
  27. package/lib/utils/cache-manager.js +154 -0
  28. package/lib/utils/context-optimizer.js +5 -36
  29. package/lib/utils/deprecation.js +37 -0
  30. package/lib/utils/shell-escape.js +88 -0
  31. package/mcp-server/index.js +513 -18
  32. package/package.json +6 -2
  33. package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
  34. package/plugins/deslop-around/lib/index.js +170 -0
  35. package/plugins/deslop-around/lib/patterns/review-patterns.js +58 -11
  36. package/plugins/deslop-around/lib/patterns/slop-patterns.js +170 -129
  37. package/plugins/deslop-around/lib/platform/detect-platform.js +212 -123
  38. package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
  39. package/plugins/deslop-around/lib/platform/verify-tools.js +10 -1
  40. package/plugins/deslop-around/lib/schemas/README.md +195 -0
  41. package/plugins/deslop-around/lib/schemas/validator.js +205 -0
  42. package/plugins/deslop-around/lib/sources/custom-handler.js +199 -0
  43. package/plugins/deslop-around/lib/sources/policy-questions.js +239 -0
  44. package/plugins/deslop-around/lib/sources/source-cache.js +149 -0
  45. package/plugins/deslop-around/lib/state/workflow-state.js +382 -484
  46. package/plugins/deslop-around/lib/types/README.md +292 -0
  47. package/plugins/deslop-around/lib/types/agent-frontmatter.d.ts +134 -0
  48. package/plugins/deslop-around/lib/types/command-frontmatter.d.ts +107 -0
  49. package/plugins/deslop-around/lib/types/hook-frontmatter.d.ts +115 -0
  50. package/plugins/deslop-around/lib/types/index.d.ts +84 -0
  51. package/plugins/deslop-around/lib/types/plugin-manifest.d.ts +102 -0
  52. package/plugins/deslop-around/lib/types/skill-frontmatter.d.ts +89 -0
  53. package/plugins/deslop-around/lib/utils/cache-manager.js +154 -0
  54. package/plugins/deslop-around/lib/utils/context-optimizer.js +115 -37
  55. package/plugins/deslop-around/lib/utils/deprecation.js +37 -0
  56. package/plugins/deslop-around/lib/utils/shell-escape.js +88 -0
  57. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  58. package/plugins/next-task/agents/delivery-validator.md +2 -2
  59. package/plugins/next-task/agents/implementation-agent.md +3 -4
  60. package/plugins/next-task/agents/planning-agent.md +77 -19
  61. package/plugins/next-task/agents/review-orchestrator.md +21 -122
  62. package/plugins/next-task/agents/task-discoverer.md +164 -23
  63. package/plugins/next-task/commands/next-task.md +180 -14
  64. package/plugins/next-task/lib/index.js +170 -0
  65. package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
  66. package/plugins/next-task/lib/patterns/slop-patterns.js +170 -129
  67. package/plugins/next-task/lib/platform/detect-platform.js +212 -123
  68. package/plugins/next-task/lib/platform/detection-configs.js +93 -0
  69. package/plugins/next-task/lib/platform/verify-tools.js +10 -1
  70. package/plugins/next-task/lib/schemas/README.md +195 -0
  71. package/plugins/next-task/lib/schemas/validator.js +205 -0
  72. package/plugins/next-task/lib/sources/custom-handler.js +199 -0
  73. package/plugins/next-task/lib/sources/policy-questions.js +239 -0
  74. package/plugins/next-task/lib/sources/source-cache.js +149 -0
  75. package/plugins/next-task/lib/state/workflow-state.js +382 -484
  76. package/plugins/next-task/lib/types/README.md +292 -0
  77. package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
  78. package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
  79. package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
  80. package/plugins/next-task/lib/types/index.d.ts +84 -0
  81. package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
  82. package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
  83. package/plugins/next-task/lib/utils/cache-manager.js +154 -0
  84. package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
  85. package/plugins/next-task/lib/utils/deprecation.js +37 -0
  86. package/plugins/next-task/lib/utils/shell-escape.js +88 -0
  87. package/plugins/project-review/.claude-plugin/plugin.json +1 -1
  88. package/plugins/project-review/lib/index.js +170 -0
  89. package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
  90. package/plugins/project-review/lib/patterns/slop-patterns.js +170 -129
  91. package/plugins/project-review/lib/platform/detect-platform.js +212 -123
  92. package/plugins/project-review/lib/platform/detection-configs.js +93 -0
  93. package/plugins/project-review/lib/platform/verify-tools.js +10 -1
  94. package/plugins/project-review/lib/schemas/README.md +195 -0
  95. package/plugins/project-review/lib/schemas/validator.js +205 -0
  96. package/plugins/project-review/lib/sources/custom-handler.js +199 -0
  97. package/plugins/project-review/lib/sources/policy-questions.js +239 -0
  98. package/plugins/project-review/lib/sources/source-cache.js +149 -0
  99. package/plugins/project-review/lib/state/workflow-state.js +382 -484
  100. package/plugins/project-review/lib/types/README.md +292 -0
  101. package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
  102. package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
  103. package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
  104. package/plugins/project-review/lib/types/index.d.ts +84 -0
  105. package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
  106. package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
  107. package/plugins/project-review/lib/utils/cache-manager.js +154 -0
  108. package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
  109. package/plugins/project-review/lib/utils/deprecation.js +37 -0
  110. package/plugins/project-review/lib/utils/shell-escape.js +88 -0
  111. package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
  112. package/plugins/reality-check/agents/code-explorer.md +1 -1
  113. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  114. package/plugins/ship/lib/index.js +170 -0
  115. package/plugins/ship/lib/patterns/review-patterns.js +58 -11
  116. package/plugins/ship/lib/patterns/slop-patterns.js +170 -129
  117. package/plugins/ship/lib/platform/detect-platform.js +212 -123
  118. package/plugins/ship/lib/platform/detection-configs.js +93 -0
  119. package/plugins/ship/lib/platform/verify-tools.js +10 -1
  120. package/plugins/ship/lib/schemas/README.md +195 -0
  121. package/plugins/ship/lib/schemas/validator.js +205 -0
  122. package/plugins/ship/lib/sources/custom-handler.js +199 -0
  123. package/plugins/ship/lib/sources/policy-questions.js +239 -0
  124. package/plugins/ship/lib/sources/source-cache.js +149 -0
  125. package/plugins/ship/lib/state/workflow-state.js +382 -484
  126. package/plugins/ship/lib/types/README.md +292 -0
  127. package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
  128. package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
  129. package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
  130. package/plugins/ship/lib/types/index.d.ts +84 -0
  131. package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
  132. package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
  133. package/plugins/ship/lib/utils/cache-manager.js +154 -0
  134. package/plugins/ship/lib/utils/context-optimizer.js +115 -37
  135. package/plugins/ship/lib/utils/deprecation.js +37 -0
  136. package/plugins/ship/lib/utils/shell-escape.js +88 -0
  137. package/lib/state/workflow-state.schema.json +0 -282
  138. package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
  139. package/plugins/next-task/agents/policy-selector.md +0 -248
  140. package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
  141. package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
  142. package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
  143. package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
  144. package/plugins/ship/lib/state/workflow-state.schema.json +0 -282
@@ -8,22 +8,34 @@
8
8
 
9
9
  /**
10
10
  * Deep freeze an object for V8 optimization and immutability
11
+ * Optimized: uses for-of instead of forEach to avoid function call overhead
11
12
  * @param {Object} obj - Object to freeze
12
13
  * @returns {Object} Frozen object
13
14
  */
14
15
  function deepFreeze(obj) {
15
- Object.keys(obj).forEach(key => {
16
- if (typeof obj[key] === 'object' && obj[key] !== null && !(obj[key] instanceof RegExp)) {
17
- deepFreeze(obj[key]);
16
+ // Freeze the object first (fast path)
17
+ Object.freeze(obj);
18
+
19
+ // Then recursively freeze nested objects (only if needed)
20
+ // Use Object.keys() for cleaner iteration over own properties
21
+ for (const key of Object.keys(obj)) {
22
+ const value = obj[key];
23
+ if (value && typeof value === 'object' && !(value instanceof RegExp)) {
24
+ deepFreeze(value);
18
25
  }
19
- });
20
- return Object.freeze(obj);
26
+ }
27
+
28
+ return obj;
21
29
  }
22
30
 
23
31
  // Pre-compiled regex cache for performance (limited to prevent memory growth)
24
32
  const MAX_PATTERN_CACHE_SIZE = 50;
25
33
  const _compiledExcludePatterns = new Map();
26
34
 
35
+ // Exclude result cache for directory-level caching (limited to prevent memory growth)
36
+ const MAX_EXCLUDE_RESULT_CACHE_SIZE = 200;
37
+ const _excludeResultCache = new Map();
38
+
27
39
  /**
28
40
  * Maximum allowed wildcards in a glob pattern to prevent ReDoS
29
41
  */
@@ -32,40 +44,66 @@ const MAX_GLOB_WILDCARDS = 10;
32
44
  /**
33
45
  * Get a compiled regex for an exclude pattern (cached)
34
46
  * Uses safe regex construction to prevent catastrophic backtracking
47
+ * Optimized: uses Map.get() instead of has() + get() (eliminates redundant lookup)
35
48
  * @param {string} pattern - Glob pattern to compile
36
49
  * @returns {RegExp} Compiled regex
37
50
  */
38
51
  function getCompiledPattern(pattern) {
39
- if (!_compiledExcludePatterns.has(pattern)) {
40
- // Enforce cache size limit using FIFO eviction
41
- if (_compiledExcludePatterns.size >= MAX_PATTERN_CACHE_SIZE) {
42
- const firstKey = _compiledExcludePatterns.keys().next().value;
43
- _compiledExcludePatterns.delete(firstKey);
44
- }
52
+ // Try to get cached pattern (O(1) lookup)
53
+ let cached = _compiledExcludePatterns.get(pattern);
54
+ if (cached) {
55
+ return cached;
56
+ }
45
57
 
46
- // Count wildcards to prevent overly complex patterns
47
- const wildcardCount = (pattern.match(/\*/g) || []).length;
48
- if (wildcardCount > MAX_GLOB_WILDCARDS) {
49
- // Too many wildcards - use a safe fallback that matches nothing dangerous
50
- _compiledExcludePatterns.set(pattern, /^$/);
51
- return _compiledExcludePatterns.get(pattern);
52
- }
58
+ // Enforce cache size limit using FIFO eviction
59
+ if (_compiledExcludePatterns.size >= MAX_PATTERN_CACHE_SIZE) {
60
+ const firstKey = _compiledExcludePatterns.keys().next().value;
61
+ _compiledExcludePatterns.delete(firstKey);
62
+ }
53
63
 
54
- // Escape all regex metacharacters except *
55
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
64
+ // Count wildcards to prevent overly complex patterns
65
+ const wildcardCount = (pattern.match(/\*/g) || []).length;
66
+ if (wildcardCount > MAX_GLOB_WILDCARDS) {
67
+ // Too many wildcards - use a safe fallback that matches nothing dangerous
68
+ const safeRegex = /^$/;
69
+ _compiledExcludePatterns.set(pattern, safeRegex);
70
+ return safeRegex;
71
+ }
56
72
 
57
- // Convert glob patterns to regex:
58
- // - Both * and ** use .* for backward compatibility (patterns match anywhere in path)
59
- // - ReDoS protection is provided by MAX_GLOB_WILDCARDS limit above
60
- let regexStr = escaped
61
- .replace(/\*\*/g, '\0GLOBSTAR\0') // Temporarily mark globstar
62
- .replace(/\*/g, '.*') // Single star: match anything (backward compatible)
63
- .replace(/\0GLOBSTAR\0/g, '.*'); // Globstar: match anything
73
+ // Escape all regex metacharacters except *
74
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
75
+
76
+ // Convert glob patterns to regex:
77
+ // - Both * and ** use .* for backward compatibility (patterns match anywhere in path)
78
+ // - ReDoS protection is provided by MAX_GLOB_WILDCARDS limit above
79
+ let regexStr = escaped
80
+ .replace(/\*\*/g, '\0GLOBSTAR\0') // Temporarily mark globstar
81
+ .replace(/\*/g, '.*') // Single star: match anything (backward compatible)
82
+ .replace(/\0GLOBSTAR\0/g, '.*'); // Globstar: match anything
83
+
84
+ regexStr = '^' + regexStr + '$';
85
+ const compiledRegex = new RegExp(regexStr);
86
+ _compiledExcludePatterns.set(pattern, compiledRegex);
87
+ return compiledRegex;
88
+ }
64
89
 
65
- regexStr = '^' + regexStr + '$';
66
- _compiledExcludePatterns.set(pattern, new RegExp(regexStr));
67
- }
68
- return _compiledExcludePatterns.get(pattern);
90
+ /**
91
+ * Helper to create secret detection pattern with common metadata
92
+ * Reduces duplication across similar secret patterns (all have same severity/autoFix/language)
93
+ * @param {RegExp} pattern - Detection regex pattern
94
+ * @param {string} description - Human-readable description
95
+ * @param {Array<string>} [additionalExcludes=[]] - Extra files to exclude beyond standard test files
96
+ * @returns {Object} Complete pattern object
97
+ */
98
+ function createSecretPattern(pattern, description, additionalExcludes = []) {
99
+ return {
100
+ pattern,
101
+ exclude: ['*.test.*', '*.spec.*', '*.example.*', ...additionalExcludes],
102
+ severity: 'critical',
103
+ autoFix: 'flag',
104
+ language: null,
105
+ description
106
+ };
69
107
  }
70
108
 
71
109
  /**
@@ -280,146 +318,99 @@ const slopPatterns = {
280
318
  /**
281
319
  * JWT tokens (eyJ prefix indicates base64 JSON header)
282
320
  */
283
- jwt_tokens: {
284
- pattern: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
285
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
286
- severity: 'critical',
287
- autoFix: 'flag',
288
- language: null,
289
- description: 'Hardcoded JWT token'
290
- },
321
+ jwt_tokens: createSecretPattern(
322
+ /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
323
+ 'Hardcoded JWT token'
324
+ ),
291
325
 
292
326
  /**
293
327
  * OpenAI API keys (sk-... format)
294
328
  */
295
- openai_api_key: {
296
- pattern: /sk-[a-zA-Z0-9]{32,}/,
297
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
298
- severity: 'critical',
299
- autoFix: 'flag',
300
- language: null,
301
- description: 'Hardcoded OpenAI API key'
302
- },
329
+ openai_api_key: createSecretPattern(
330
+ /sk-[a-zA-Z0-9]{32,}/,
331
+ 'Hardcoded OpenAI API key'
332
+ ),
303
333
 
304
334
  /**
305
335
  * GitHub tokens (personal access tokens, fine-grained tokens, OAuth)
306
336
  */
307
- github_token: {
308
- pattern: /(ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36}|ghu_[a-zA-Z0-9]{36}|ghs_[a-zA-Z0-9]{36}|ghr_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59})/,
309
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
310
- severity: 'critical',
311
- autoFix: 'flag',
312
- language: null,
313
- description: 'Hardcoded GitHub token'
314
- },
337
+ github_token: createSecretPattern(
338
+ /(ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36}|ghu_[a-zA-Z0-9]{36}|ghs_[a-zA-Z0-9]{36}|ghr_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59})/,
339
+ 'Hardcoded GitHub token'
340
+ ),
315
341
 
316
342
  /**
317
343
  * AWS credentials (access key IDs and secret keys)
318
344
  */
319
- aws_credentials: {
320
- pattern: /(AKIA[0-9A-Z]{16}|aws_secret_access_key\s*[:=]\s*["'`][A-Za-z0-9/+=]{40}["'`])/i,
321
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
322
- severity: 'critical',
323
- autoFix: 'flag',
324
- language: null,
325
- description: 'Hardcoded AWS credentials'
326
- },
345
+ aws_credentials: createSecretPattern(
346
+ /(AKIA[0-9A-Z]{16}|aws_secret_access_key\s*[:=]\s*["'`][A-Za-z0-9/+=]{40}["'`])/i,
347
+ 'Hardcoded AWS credentials'
348
+ ),
327
349
 
328
350
  /**
329
351
  * Google Cloud / Firebase API keys and service accounts
330
352
  */
331
- google_api_key: {
332
- pattern: /(AIza[0-9A-Za-z_-]{35}|[0-9]+-[a-z0-9_]{32}\.apps\.googleusercontent\.com)/,
333
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
334
- severity: 'critical',
335
- autoFix: 'flag',
336
- language: null,
337
- description: 'Hardcoded Google/Firebase API key'
338
- },
353
+ google_api_key: createSecretPattern(
354
+ /(AIza[0-9A-Za-z_-]{35}|[0-9]+-[a-z0-9_]{32}\.apps\.googleusercontent\.com)/,
355
+ 'Hardcoded Google/Firebase API key'
356
+ ),
339
357
 
340
358
  /**
341
359
  * Stripe API keys (live and test)
342
360
  */
343
- stripe_api_key: {
344
- pattern: /(sk_live_[a-zA-Z0-9]{24,}|sk_test_[a-zA-Z0-9]{24,}|rk_live_[a-zA-Z0-9]{24,}|rk_test_[a-zA-Z0-9]{24,})/,
345
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
346
- severity: 'critical',
347
- autoFix: 'flag',
348
- language: null,
349
- description: 'Hardcoded Stripe API key'
350
- },
361
+ stripe_api_key: createSecretPattern(
362
+ /(sk_live_[a-zA-Z0-9]{24,}|sk_test_[a-zA-Z0-9]{24,}|rk_live_[a-zA-Z0-9]{24,}|rk_test_[a-zA-Z0-9]{24,})/,
363
+ 'Hardcoded Stripe API key'
364
+ ),
351
365
 
352
366
  /**
353
367
  * Slack tokens (bot, user, webhook)
354
368
  */
355
- slack_token: {
356
- pattern: /(xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24}|xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24}|xoxa-[0-9]{10,}-[a-zA-Z0-9]{24}|https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]{8}\/B[A-Z0-9]{8,}\/[a-zA-Z0-9]{24})/,
357
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
358
- severity: 'critical',
359
- autoFix: 'flag',
360
- language: null,
361
- description: 'Hardcoded Slack token or webhook URL'
362
- },
369
+ slack_token: createSecretPattern(
370
+ /(xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24}|xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24}|xoxa-[0-9]{10,}-[a-zA-Z0-9]{24}|https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]{8}\/B[A-Z0-9]{8,}\/[a-zA-Z0-9]{24})/,
371
+ 'Hardcoded Slack token or webhook URL'
372
+ ),
363
373
 
364
374
  /**
365
375
  * Discord tokens and webhook URLs
366
376
  */
367
- discord_token: {
368
- pattern: /(discord.*["'`][A-Za-z0-9_-]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27}["'`]|https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+)/i,
369
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
370
- severity: 'critical',
371
- autoFix: 'flag',
372
- language: null,
373
- description: 'Hardcoded Discord token or webhook'
374
- },
377
+ discord_token: createSecretPattern(
378
+ /(discord.*["'`][A-Za-z0-9_-]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27}["'`]|https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+)/i,
379
+ 'Hardcoded Discord token or webhook'
380
+ ),
375
381
 
376
382
  /**
377
383
  * SendGrid API key
378
384
  */
379
- sendgrid_api_key: {
380
- pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/,
381
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
382
- severity: 'critical',
383
- autoFix: 'flag',
384
- language: null,
385
- description: 'Hardcoded SendGrid API key'
386
- },
385
+ sendgrid_api_key: createSecretPattern(
386
+ /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/,
387
+ 'Hardcoded SendGrid API key'
388
+ ),
387
389
 
388
390
  /**
389
391
  * Twilio credentials
390
392
  */
391
- twilio_credentials: {
392
- pattern: /(AC[a-f0-9]{32}|SK[a-f0-9]{32})/,
393
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
394
- severity: 'critical',
395
- autoFix: 'flag',
396
- language: null,
397
- description: 'Hardcoded Twilio credentials'
398
- },
393
+ twilio_credentials: createSecretPattern(
394
+ /(AC[a-f0-9]{32}|SK[a-f0-9]{32})/,
395
+ 'Hardcoded Twilio credentials'
396
+ ),
399
397
 
400
398
  /**
401
399
  * NPM tokens
402
400
  */
403
- npm_token: {
404
- pattern: /npm_[a-zA-Z0-9]{36}/,
405
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
406
- severity: 'critical',
407
- autoFix: 'flag',
408
- language: null,
409
- description: 'Hardcoded NPM token'
410
- },
401
+ npm_token: createSecretPattern(
402
+ /npm_[a-zA-Z0-9]{36}/,
403
+ 'Hardcoded NPM token'
404
+ ),
411
405
 
412
406
  /**
413
407
  * Private keys (RSA, DSA, EC, PGP)
414
408
  */
415
- private_key: {
416
- pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/,
417
- exclude: ['*.test.*', '*.spec.*', '*.example.*', '*.pem.example'],
418
- severity: 'critical',
419
- autoFix: 'flag',
420
- language: null,
421
- description: 'Private key in source code'
422
- },
409
+ private_key: createSecretPattern(
410
+ /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/,
411
+ 'Private key in source code',
412
+ ['*.pem.example'] // Additional excludes beyond standard test files
413
+ ),
423
414
 
424
415
  /**
425
416
  * Generic high-entropy strings (potential secrets)
@@ -582,6 +573,7 @@ function getPatternsByAutoFix(autoFix) {
582
573
 
583
574
  /**
584
575
  * Get patterns matching multiple criteria (language AND severity)
576
+ * Optimized: single-pass filtering instead of chained Object.entries
585
577
  * @param {Object} criteria - Filter criteria
586
578
  * @param {string} [criteria.language] - Language filter
587
579
  * @param {string} [criteria.severity] - Severity filter
@@ -589,27 +581,25 @@ function getPatternsByAutoFix(autoFix) {
589
581
  * @returns {Object} Patterns matching all criteria
590
582
  */
591
583
  function getPatternsByCriteria(criteria = {}) {
592
- let result = { ...slopPatterns };
593
-
594
- if (criteria.language) {
595
- const langPatterns = getPatternsForLanguage(criteria.language);
596
- result = Object.fromEntries(
597
- Object.entries(result).filter(([name]) => name in langPatterns)
598
- );
584
+ // Fast path: no criteria means return all patterns
585
+ if (!criteria.language && !criteria.severity && !criteria.autoFix) {
586
+ return { ...slopPatterns };
599
587
  }
600
588
 
601
- if (criteria.severity) {
602
- const severityPatterns = getPatternsBySeverity(criteria.severity);
603
- result = Object.fromEntries(
604
- Object.entries(result).filter(([name]) => name in severityPatterns)
605
- );
606
- }
589
+ // Pre-fetch filter sets (O(1) Map lookups)
590
+ const langPatterns = criteria.language ? getPatternsForLanguage(criteria.language) : null;
591
+ const severityPatterns = criteria.severity ? getPatternsBySeverity(criteria.severity) : null;
592
+ const autoFixPatterns = criteria.autoFix ? getPatternsByAutoFix(criteria.autoFix) : null;
593
+
594
+ // Single-pass filter: check all criteria at once
595
+ const result = {};
596
+ for (const [name, pattern] of Object.entries(slopPatterns)) {
597
+ // Check all criteria in one pass (short-circuit on first failure)
598
+ if (langPatterns && !(name in langPatterns)) continue;
599
+ if (severityPatterns && !(name in severityPatterns)) continue;
600
+ if (autoFixPatterns && !(name in autoFixPatterns)) continue;
607
601
 
608
- if (criteria.autoFix) {
609
- const autoFixPatterns = getPatternsByAutoFix(criteria.autoFix);
610
- result = Object.fromEntries(
611
- Object.entries(result).filter(([name]) => name in autoFixPatterns)
612
- );
602
+ result[name] = pattern;
613
603
  }
614
604
 
615
605
  return result;
@@ -642,7 +632,7 @@ function hasLanguage(language) {
642
632
 
643
633
  /**
644
634
  * Check if a file should be excluded based on pattern rules
645
- * Uses pre-compiled regex cache for performance
635
+ * Uses pre-compiled regex cache and result cache for performance
646
636
  * @param {string} filePath - File path to check
647
637
  * @param {Array<string>} excludePatterns - Exclude patterns
648
638
  * @returns {boolean} True if file should be excluded
@@ -650,10 +640,27 @@ function hasLanguage(language) {
650
640
  function isFileExcluded(filePath, excludePatterns) {
651
641
  if (!excludePatterns || excludePatterns.length === 0) return false;
652
642
 
653
- return excludePatterns.some(pattern => {
643
+ // Create cache key using JSON.stringify for collision-resistant format
644
+ const cacheKey = JSON.stringify([filePath, excludePatterns]);
645
+
646
+ // Check cache first (O(1) lookup)
647
+ const cached = _excludeResultCache.get(cacheKey);
648
+ if (cached !== undefined) return cached;
649
+
650
+ // Compute result
651
+ const result = excludePatterns.some(pattern => {
654
652
  const regex = getCompiledPattern(pattern);
655
653
  return regex.test(filePath);
656
654
  });
655
+
656
+ // Store in cache with FIFO eviction
657
+ if (_excludeResultCache.size >= MAX_EXCLUDE_RESULT_CACHE_SIZE) {
658
+ const firstKey = _excludeResultCache.keys().next().value;
659
+ _excludeResultCache.delete(firstKey);
660
+ }
661
+ _excludeResultCache.set(cacheKey, result);
662
+
663
+ return result;
657
664
  }
658
665
 
659
666
  module.exports = {