awesome-slash 4.2.0 → 4.2.2

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 (161) hide show
  1. package/.claude-plugin/marketplace.json +13 -13
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +1 -1
  4. package/CHANGELOG.md +13 -6
  5. package/README.md +0 -14
  6. package/adapters/codex/skills/audit-project-agents/SKILL.md +1 -1
  7. package/adapters/codex/skills/ship-ci-review-loop/SKILL.md +1 -1
  8. package/adapters/codex/skills/ship-deployment/SKILL.md +1 -1
  9. package/adapters/opencode/commands/audit-project-agents.md +11 -0
  10. package/adapters/opencode/commands/ship-ci-review-loop.md +21 -1
  11. package/adapters/opencode/commands/ship-deployment.md +5 -0
  12. package/adapters/opencode/skills/agnix/SKILL.md +1 -1
  13. package/adapters/opencode/skills/consult/SKILL.md +1 -1
  14. package/adapters/opencode/skills/deslop/SKILL.md +1 -1
  15. package/adapters/opencode/skills/discover-tasks/SKILL.md +1 -1
  16. package/adapters/opencode/skills/drift-analysis/SKILL.md +1 -1
  17. package/adapters/opencode/skills/enhance-agent-prompts/SKILL.md +1 -1
  18. package/adapters/opencode/skills/enhance-claude-memory/SKILL.md +1 -1
  19. package/adapters/opencode/skills/enhance-cross-file/SKILL.md +1 -1
  20. package/adapters/opencode/skills/enhance-docs/SKILL.md +1 -1
  21. package/adapters/opencode/skills/enhance-hooks/SKILL.md +1 -1
  22. package/adapters/opencode/skills/enhance-orchestrator/SKILL.md +1 -1
  23. package/adapters/opencode/skills/enhance-plugins/SKILL.md +1 -1
  24. package/adapters/opencode/skills/enhance-prompts/SKILL.md +1 -1
  25. package/adapters/opencode/skills/enhance-skills/SKILL.md +1 -1
  26. package/adapters/opencode/skills/learn/SKILL.md +1 -1
  27. package/adapters/opencode/skills/perf-analyzer/SKILL.md +1 -1
  28. package/adapters/opencode/skills/perf-baseline-manager/SKILL.md +1 -1
  29. package/adapters/opencode/skills/perf-benchmarker/SKILL.md +1 -1
  30. package/adapters/opencode/skills/perf-code-paths/SKILL.md +1 -1
  31. package/adapters/opencode/skills/perf-investigation-logger/SKILL.md +1 -1
  32. package/adapters/opencode/skills/perf-profiler/SKILL.md +1 -1
  33. package/adapters/opencode/skills/perf-theory-gatherer/SKILL.md +1 -1
  34. package/adapters/opencode/skills/perf-theory-tester/SKILL.md +1 -1
  35. package/adapters/opencode/skills/sync-docs/SKILL.md +1 -1
  36. package/adapters/opencode/skills/validate-delivery/SKILL.md +1 -1
  37. package/bin/cli.js +4 -0
  38. package/lib/enhance/docs-patterns.js +1 -1
  39. package/lib/enhance/fixer.js +38 -13
  40. package/lib/enhance/projectmemory-analyzer.js +17 -9
  41. package/lib/enhance/projectmemory-patterns.js +2 -2
  42. package/lib/enhance/prompt-patterns.js +31 -28
  43. package/lib/enhance/security-patterns.js +6 -6
  44. package/lib/package.json +0 -3
  45. package/lib/patterns/slop-analyzers.js +1 -1
  46. package/package.json +1 -1
  47. package/plugins/agnix/.claude-plugin/plugin.json +1 -1
  48. package/plugins/agnix/skills/agnix/SKILL.md +1 -1
  49. package/plugins/audit-project/.claude-plugin/plugin.json +1 -1
  50. package/plugins/audit-project/commands/audit-project-agents.md +5 -0
  51. package/plugins/audit-project/lib/enhance/docs-patterns.js +1 -1
  52. package/plugins/audit-project/lib/enhance/fixer.js +38 -13
  53. package/plugins/audit-project/lib/enhance/projectmemory-analyzer.js +17 -9
  54. package/plugins/audit-project/lib/enhance/projectmemory-patterns.js +2 -2
  55. package/plugins/audit-project/lib/enhance/prompt-patterns.js +31 -28
  56. package/plugins/audit-project/lib/enhance/security-patterns.js +6 -6
  57. package/plugins/audit-project/lib/patterns/slop-analyzers.js +1 -1
  58. package/plugins/consult/.claude-plugin/plugin.json +1 -1
  59. package/plugins/consult/skills/consult/SKILL.md +1 -1
  60. package/plugins/deslop/.claude-plugin/plugin.json +1 -1
  61. package/plugins/deslop/lib/enhance/docs-patterns.js +1 -1
  62. package/plugins/deslop/lib/enhance/fixer.js +38 -13
  63. package/plugins/deslop/lib/enhance/projectmemory-analyzer.js +17 -9
  64. package/plugins/deslop/lib/enhance/projectmemory-patterns.js +2 -2
  65. package/plugins/deslop/lib/enhance/prompt-patterns.js +31 -28
  66. package/plugins/deslop/lib/enhance/security-patterns.js +6 -6
  67. package/plugins/deslop/lib/patterns/slop-analyzers.js +1 -1
  68. package/plugins/deslop/skills/deslop/SKILL.md +1 -1
  69. package/plugins/drift-detect/.claude-plugin/plugin.json +1 -1
  70. package/plugins/drift-detect/lib/enhance/docs-patterns.js +1 -1
  71. package/plugins/drift-detect/lib/enhance/fixer.js +38 -13
  72. package/plugins/drift-detect/lib/enhance/projectmemory-analyzer.js +17 -9
  73. package/plugins/drift-detect/lib/enhance/projectmemory-patterns.js +2 -2
  74. package/plugins/drift-detect/lib/enhance/prompt-patterns.js +31 -28
  75. package/plugins/drift-detect/lib/enhance/security-patterns.js +6 -6
  76. package/plugins/drift-detect/lib/patterns/slop-analyzers.js +1 -1
  77. package/plugins/drift-detect/skills/drift-analysis/SKILL.md +1 -1
  78. package/plugins/enhance/.claude-plugin/plugin.json +1 -1
  79. package/plugins/enhance/lib/enhance/docs-patterns.js +1 -1
  80. package/plugins/enhance/lib/enhance/fixer.js +38 -13
  81. package/plugins/enhance/lib/enhance/projectmemory-analyzer.js +17 -9
  82. package/plugins/enhance/lib/enhance/projectmemory-patterns.js +2 -2
  83. package/plugins/enhance/lib/enhance/prompt-patterns.js +31 -28
  84. package/plugins/enhance/lib/enhance/security-patterns.js +6 -6
  85. package/plugins/enhance/lib/patterns/slop-analyzers.js +1 -1
  86. package/plugins/enhance/skills/enhance-agent-prompts/SKILL.md +1 -1
  87. package/plugins/enhance/skills/enhance-claude-memory/SKILL.md +1 -1
  88. package/plugins/enhance/skills/enhance-cross-file/SKILL.md +1 -1
  89. package/plugins/enhance/skills/enhance-docs/SKILL.md +1 -1
  90. package/plugins/enhance/skills/enhance-hooks/SKILL.md +1 -1
  91. package/plugins/enhance/skills/enhance-orchestrator/SKILL.md +1 -1
  92. package/plugins/enhance/skills/enhance-plugins/SKILL.md +1 -1
  93. package/plugins/enhance/skills/enhance-prompts/SKILL.md +1 -1
  94. package/plugins/enhance/skills/enhance-skills/SKILL.md +1 -1
  95. package/plugins/learn/.claude-plugin/plugin.json +1 -1
  96. package/plugins/learn/lib/enhance/docs-patterns.js +1 -1
  97. package/plugins/learn/lib/enhance/fixer.js +38 -13
  98. package/plugins/learn/lib/enhance/projectmemory-analyzer.js +17 -9
  99. package/plugins/learn/lib/enhance/projectmemory-patterns.js +2 -2
  100. package/plugins/learn/lib/enhance/prompt-patterns.js +31 -28
  101. package/plugins/learn/lib/enhance/security-patterns.js +6 -6
  102. package/plugins/learn/lib/patterns/slop-analyzers.js +1 -1
  103. package/plugins/learn/skills/learn/SKILL.md +1 -1
  104. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  105. package/plugins/next-task/lib/enhance/docs-patterns.js +1 -1
  106. package/plugins/next-task/lib/enhance/fixer.js +38 -13
  107. package/plugins/next-task/lib/enhance/projectmemory-analyzer.js +17 -9
  108. package/plugins/next-task/lib/enhance/projectmemory-patterns.js +2 -2
  109. package/plugins/next-task/lib/enhance/prompt-patterns.js +31 -28
  110. package/plugins/next-task/lib/enhance/security-patterns.js +6 -6
  111. package/plugins/next-task/lib/patterns/slop-analyzers.js +1 -1
  112. package/plugins/next-task/skills/discover-tasks/SKILL.md +1 -1
  113. package/plugins/next-task/skills/validate-delivery/SKILL.md +1 -1
  114. package/plugins/perf/.claude-plugin/plugin.json +1 -1
  115. package/plugins/perf/lib/enhance/docs-patterns.js +1 -1
  116. package/plugins/perf/lib/enhance/fixer.js +38 -13
  117. package/plugins/perf/lib/enhance/projectmemory-analyzer.js +17 -9
  118. package/plugins/perf/lib/enhance/projectmemory-patterns.js +2 -2
  119. package/plugins/perf/lib/enhance/prompt-patterns.js +31 -28
  120. package/plugins/perf/lib/enhance/security-patterns.js +6 -6
  121. package/plugins/perf/lib/patterns/slop-analyzers.js +1 -1
  122. package/plugins/perf/skills/perf-analyzer/SKILL.md +1 -1
  123. package/plugins/perf/skills/perf-baseline-manager/SKILL.md +1 -1
  124. package/plugins/perf/skills/perf-benchmarker/SKILL.md +1 -1
  125. package/plugins/perf/skills/perf-code-paths/SKILL.md +1 -1
  126. package/plugins/perf/skills/perf-investigation-logger/SKILL.md +1 -1
  127. package/plugins/perf/skills/perf-profiler/SKILL.md +1 -1
  128. package/plugins/perf/skills/perf-theory-gatherer/SKILL.md +1 -1
  129. package/plugins/perf/skills/perf-theory-tester/SKILL.md +1 -1
  130. package/plugins/repo-map/.claude-plugin/plugin.json +1 -1
  131. package/plugins/repo-map/lib/enhance/docs-patterns.js +1 -1
  132. package/plugins/repo-map/lib/enhance/fixer.js +38 -13
  133. package/plugins/repo-map/lib/enhance/projectmemory-analyzer.js +17 -9
  134. package/plugins/repo-map/lib/enhance/projectmemory-patterns.js +2 -2
  135. package/plugins/repo-map/lib/enhance/prompt-patterns.js +31 -28
  136. package/plugins/repo-map/lib/enhance/security-patterns.js +6 -6
  137. package/plugins/repo-map/lib/patterns/slop-analyzers.js +1 -1
  138. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  139. package/plugins/ship/commands/ship-ci-review-loop.md +5 -0
  140. package/plugins/ship/commands/ship-deployment.md +5 -0
  141. package/plugins/ship/lib/enhance/docs-patterns.js +1 -1
  142. package/plugins/ship/lib/enhance/fixer.js +38 -13
  143. package/plugins/ship/lib/enhance/projectmemory-analyzer.js +17 -9
  144. package/plugins/ship/lib/enhance/projectmemory-patterns.js +2 -2
  145. package/plugins/ship/lib/enhance/prompt-patterns.js +31 -28
  146. package/plugins/ship/lib/enhance/security-patterns.js +6 -6
  147. package/plugins/ship/lib/patterns/slop-analyzers.js +1 -1
  148. package/plugins/sync-docs/.claude-plugin/plugin.json +1 -1
  149. package/plugins/sync-docs/lib/enhance/docs-patterns.js +1 -1
  150. package/plugins/sync-docs/lib/enhance/fixer.js +38 -13
  151. package/plugins/sync-docs/lib/enhance/projectmemory-analyzer.js +17 -9
  152. package/plugins/sync-docs/lib/enhance/projectmemory-patterns.js +2 -2
  153. package/plugins/sync-docs/lib/enhance/prompt-patterns.js +31 -28
  154. package/plugins/sync-docs/lib/enhance/security-patterns.js +6 -6
  155. package/plugins/sync-docs/lib/patterns/slop-analyzers.js +1 -1
  156. package/plugins/sync-docs/skills/sync-docs/SKILL.md +1 -1
  157. package/scripts/gen-adapters.js +10 -0
  158. package/scripts/generate-docs.js +1 -1
  159. package/scripts/preflight.js +1 -2
  160. package/scripts/stamp-version.js +0 -5
  161. package/site/content.json +8 -1
@@ -27,7 +27,7 @@ const securityPatterns = {
27
27
  if (!frontmatterMatch) return null;
28
28
 
29
29
  const frontmatter = frontmatterMatch[1];
30
- const toolsMatch = frontmatter.match(/^tools:\s*(.*)$/m);
30
+ const toolsMatch = frontmatter.match(/^tools:[ \t]*(\S.*)?$/m);
31
31
  if (!toolsMatch) return null;
32
32
 
33
33
  const tools = toolsMatch[1];
@@ -60,8 +60,8 @@ const securityPatterns = {
60
60
 
61
61
  for (let i = 0; i < lines.length; i++) {
62
62
  const line = lines[i];
63
- // Look for shell commands with interpolation
64
- if (/(?:exec|spawn|system|shell|`|Bash)\s*[(`].*\$\{/.test(line)) {
63
+ // Look for shell commands with interpolation (string checks avoid ReDoS)
64
+ if (/\b(?:exec|spawn|system|shell)\b|`|Bash/i.test(line) && /[(`]/.test(line) && line.includes('${')) {
65
65
  issues.push({
66
66
  issue: 'Command injection risk via string interpolation',
67
67
  fix: 'Validate and escape user input before shell execution',
@@ -91,8 +91,8 @@ const securityPatterns = {
91
91
 
92
92
  for (let i = 0; i < lines.length; i++) {
93
93
  const line = lines[i];
94
- // Look for user-controlled paths with ../
95
- if (/(?:path|file|dir).*\$.*\.\.\/|\.\.\/.*\$/.test(line)) {
94
+ // Look for user-controlled paths with ../ (string checks avoid ReDoS)
95
+ if (/\b(?:path|file|dir)\b/i.test(line) && line.includes('$') && line.includes('../')) {
96
96
  issues.push({
97
97
  issue: 'Path traversal risk - user input may contain ../',
98
98
  fix: 'Validate paths and use path.resolve() with base directory check',
@@ -181,7 +181,7 @@ const securityPatterns = {
181
181
  if (!frontmatterMatch) return null;
182
182
 
183
183
  const frontmatter = frontmatterMatch[1];
184
- const toolsMatch = frontmatter.match(/^tools:\s*(.*)$/m);
184
+ const toolsMatch = frontmatter.match(/^tools:[ \t]*(\S.*)?$/m);
185
185
  if (!toolsMatch) return null;
186
186
 
187
187
  const tools = toolsMatch[1];
@@ -1803,7 +1803,7 @@ function analyzeDeadCode(content, options = {}) {
1803
1803
  // Skip if termination is part of a one-line conditional (e.g., "if (x) return;")
1804
1804
  // These don't make subsequent code unreachable
1805
1805
  if (/^\s*(if|elif|else\s+if)\s*\(/.test(trimmed) ||
1806
- /^\s*if\s+.*:/.test(trimmed)) {
1806
+ /^[ \t]*if\s[^:\n]*:/.test(trimmed)) {
1807
1807
  continue;
1808
1808
  }
1809
1809
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "consult",
3
- "version": "4.2.0",
3
+ "version": "4.2.2",
4
4
  "description": "Cross-tool AI consultation: get second opinions from Gemini, Codex, Claude, OpenCode, or Copilot CLI",
5
5
  "author": {
6
6
  "name": "Avi Fenesh",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: consult
3
3
  description: "Cross-tool AI consultation. Use when user asks to 'consult gemini', 'ask codex', 'get second opinion', 'cross-check with claude', 'consult another AI', 'ask opencode', 'copilot opinion', or wants a second opinion from a different AI tool."
4
- version: 4.2.0
4
+ version: 4.2.2
5
5
  argument-hint: "[question] [--tool] [--effort] [--model] [--context] [--continue]"
6
6
  ---
7
7
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deslop",
3
- "version": "4.2.0",
3
+ "version": "4.2.2",
4
4
  "description": "AI slop cleanup with minimal diffs and behavior preservation",
5
5
  "author": {
6
6
  "name": "Avi Fenesh",
@@ -418,7 +418,7 @@ const docsPatterns = {
418
418
  }
419
419
 
420
420
  // Check for very long lists that could be tables
421
- const longLists = content.match(/(?:^[-*]\s+.+\n){10,}/gm);
421
+ const longLists = content.match(/(?:^[-*][ \t]+\S[^\n]*\n){10,}/gm);
422
422
  if (longLists) {
423
423
  suggestions.push('Long lists (10+ items) might be more efficient as tables');
424
424
  }
@@ -179,7 +179,7 @@ function applyAtPath(obj, pathStr, fixFn) {
179
179
  const part = parts[i];
180
180
  if (part.includes('[')) {
181
181
  // Array access
182
- const match = part.match(/(\w+)\[(\d+)\]/);
182
+ const match = part.match(/^((?!__proto__|constructor|prototype)[a-zA-Z_]\w*)\[(\d{1,10})\]$/);
183
183
  if (match) {
184
184
  current = current[match[1]][parseInt(match[2], 10)];
185
185
  }
@@ -190,7 +190,7 @@ function applyAtPath(obj, pathStr, fixFn) {
190
190
 
191
191
  const lastPart = parts[parts.length - 1];
192
192
  if (lastPart.includes('[')) {
193
- const match = lastPart.match(/(\w+)\[(\d+)\]/);
193
+ const match = lastPart.match(/^((?!__proto__|constructor|prototype)[a-zA-Z_]\w*)\[(\d{1,10})\]$/);
194
194
  if (match) {
195
195
  current[match[1]][parseInt(match[2], 10)] = fixFn(current[match[1]][parseInt(match[2], 10)]);
196
196
  }
@@ -403,7 +403,7 @@ function fixInconsistentHeadings(content) {
403
403
 
404
404
  if (inCodeBlock) continue;
405
405
 
406
- const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
406
+ const headingMatch = line.match(/^(#{1,6})[ \t]+(\S.*)$/);
407
407
  if (headingMatch) {
408
408
  const currentLevel = headingMatch[1].length;
409
409
  const headingText = headingMatch[2];
@@ -535,6 +535,37 @@ Why bad: [explanation]
535
535
  return content.trim() + exampleSection;
536
536
  }
537
537
 
538
+ /**
539
+ * Wrap a markdown section (heading to next heading/separator) in XML tags.
540
+ * Uses line-by-line scanning to avoid ReDoS from [\s\S]*? with lookaheads.
541
+ */
542
+ function wrapSection(text, headingPattern, tagName) {
543
+ const lines = text.split('\n');
544
+ let sectionStart = -1;
545
+ for (let i = 0; i < lines.length; i++) {
546
+ if (sectionStart === -1) {
547
+ if (headingPattern.test(lines[i])) {
548
+ sectionStart = i;
549
+ }
550
+ } else {
551
+ // End section at next heading or horizontal rule
552
+ if (/^#{1,6}\s/.test(lines[i]) || /^---/.test(lines[i])) {
553
+ const before = lines.slice(0, sectionStart);
554
+ const section = lines.slice(sectionStart, i);
555
+ const after = lines.slice(i);
556
+ return [...before, `<${tagName}>`, ...section, `</${tagName}>`, ...after].join('\n');
557
+ }
558
+ }
559
+ }
560
+ // Section runs to end of content
561
+ if (sectionStart !== -1) {
562
+ const before = lines.slice(0, sectionStart);
563
+ const section = lines.slice(sectionStart);
564
+ return [...before, `<${tagName}>`, ...section, `</${tagName}>`].join('\n');
565
+ }
566
+ return text;
567
+ }
568
+
538
569
  /**
539
570
  * Add XML structure tags to complex prompt
540
571
  * @param {string} content - Prompt content
@@ -551,17 +582,11 @@ function fixMissingXmlStructure(content) {
551
582
  // Wrap role section if exists
552
583
  let result = content;
553
584
 
554
- // Find and wrap role section
555
- result = result.replace(
556
- /^(##\s*(?:your\s+)?role\s*\n)([\s\S]*?)(?=\n##|\n---|\Z)/im,
557
- '<role>\n$1$2</role>\n'
558
- );
585
+ // Find and wrap role section (use non-regex approach to avoid ReDoS)
586
+ result = wrapSection(result, /^##[ \t]*(?:your[ \t]+)?role[ \t]*$/im, 'role');
559
587
 
560
588
  // Find and wrap constraints section
561
- result = result.replace(
562
- /^(##\s*(?:constraints?|rules?)\s*\n)([\s\S]*?)(?=\n##|\n---|\Z)/im,
563
- '<constraints>\n$1$2</constraints>\n'
564
- );
589
+ result = wrapSection(result, /^##[ \t]*(?:constraints?|rules?)[ \t]*$/im, 'constraints');
565
590
 
566
591
  return result;
567
592
  }
@@ -622,7 +647,7 @@ function fixMissingTriggerPhrase(content) {
622
647
  // Check if already has trigger phrase
623
648
  if (!/use when user asks/i.test(descLine)) {
624
649
  // Extract current description
625
- const match = descLine.match(/^description:\s*(.+)$/);
650
+ const match = descLine.match(/^description:[ \t]*(\S.*)$/);
626
651
  if (match) {
627
652
  const currentDesc = match[1].trim();
628
653
  // Add trigger phrase
@@ -43,17 +43,25 @@ function extractFileReferences(content) {
43
43
 
44
44
  const references = [];
45
45
 
46
- // Match markdown links: [text](path)
47
- const linkMatches = content.match(/\[([^\]]+)\]\(([^)]+)\)/g) || [];
48
- for (const match of linkMatches) {
49
- const pathMatch = match.match(/\]\(([^)]+)\)/);
50
- if (pathMatch && pathMatch[1]) {
51
- const href = pathMatch[1];
52
- // Skip URLs and anchors
53
- if (!href.startsWith('http') && !href.startsWith('#') && !href.startsWith('mailto:')) {
54
- references.push(href.split('#')[0]); // Remove anchor
46
+ // Extract markdown links [text](path) using indexOf scanning (ReDoS-safe)
47
+ let pos = 0;
48
+ while (pos < content.length) {
49
+ const openBracket = content.indexOf('[', pos);
50
+ if (openBracket === -1) break;
51
+ const closeBracket = content.indexOf(']', openBracket + 1);
52
+ if (closeBracket === -1) break;
53
+ if (content[closeBracket + 1] === '(') {
54
+ const closeParen = content.indexOf(')', closeBracket + 2);
55
+ if (closeParen !== -1 && closeParen - closeBracket - 2 <= 500) {
56
+ const href = content.substring(closeBracket + 2, closeParen);
57
+ if (!href.startsWith('http') && !href.startsWith('#') && !href.startsWith('mailto:')) {
58
+ references.push(href.split('#')[0]); // Remove anchor
59
+ }
60
+ pos = closeParen + 1;
61
+ continue;
55
62
  }
56
63
  }
64
+ pos = openBracket + 1;
57
65
  }
58
66
 
59
67
  // Match backtick paths: `path/to/file.ext` or `file.ext` (root files)
@@ -57,7 +57,7 @@ const projectMemoryPatterns = {
57
57
  const hasArchitecture = /##\s+architecture/i.test(content);
58
58
  const hasStructure = /##\s+(?:project\s+)?structure/i.test(content);
59
59
  const hasOverview = /##\s+overview/i.test(content);
60
- const hasDirectoryTree = /```[\s\S]*?(?:├──|└──|lib\/|src\/)[\s\S]*?```/.test(content);
60
+ const hasDirectoryTree = content.includes('```') && /├──|└──|lib\/|src\//.test(content);
61
61
 
62
62
  if (!hasArchitecture && !hasStructure && !hasOverview && !hasDirectoryTree) {
63
63
  return {
@@ -85,7 +85,7 @@ const projectMemoryPatterns = {
85
85
  const hasCommands = /##\s+(?:key\s+)?commands/i.test(content);
86
86
  const hasScripts = /##\s+scripts/i.test(content);
87
87
  const hasUsage = /##\s+usage/i.test(content);
88
- const hasCodeBlocks = /```(?:bash|sh|shell)[\s\S]*?(?:npm|yarn|pnpm|git|make)/i.test(content);
88
+ const hasCodeBlocks = /```(?:bash|sh|shell)/i.test(content) && /\b(?:npm|yarn|pnpm|git|make)\b/i.test(content);
89
89
 
90
90
  if (!hasCommands && !hasScripts && !hasUsage && !hasCodeBlocks) {
91
91
  return {
@@ -29,7 +29,7 @@ const LOOKS_LIKE_JSON_CONTENT = /[:,]/;
29
29
  const NOT_JSON_KEYWORDS = /(function|const|let|var|if|for|while|class)\b/;
30
30
  // JS patterns require syntax context (not just keywords that might appear in JSON strings)
31
31
  const LOOKS_LIKE_JS = /\b(function\s*\(|const\s+\w+\s*=|let\s+\w+\s*=|var\s+\w+\s*=|=>\s*[{(]|async\s+function|await\s+\w|class\s+\w+\s*{|import\s+\{|export\s+(const|function|class|default)|require\s*\()/;
32
- const LOOKS_LIKE_PYTHON = /\b(def\s+\w+|import\s+\w+|from\s+\w+\s+import|class\s+\w+:|if\s+.*:|\s{4}|print\()\b/;
32
+ const LOOKS_LIKE_PYTHON = /\b(def\s+\w+|import\s+\w+|from\s+\w+\s+import|class\s+\w+:|if\s[^:\n]*:|\s{4}|print\()\b/;
33
33
 
34
34
  // Memoization caches for performance (keyed by content hash)
35
35
  let _lastContent = null;
@@ -220,7 +220,7 @@ const promptPatterns = {
220
220
  // Skip lines listing vague terms as documentation
221
221
  if (/vague\s*(instructions?|terms?|language|patterns?)\s*[:"]/.test(trimmed)) return false;
222
222
  // Skip lines with quoted lists of vague words
223
- if (/["']usually["'].*["']sometimes["']/.test(trimmed)) return false;
223
+ if (trimmed.includes('usually') && trimmed.includes('sometimes') && /["']/.test(trimmed)) return false;
224
224
  return true;
225
225
  });
226
226
  const filteredContent = lines.join('\n');
@@ -331,7 +331,11 @@ const promptPatterns = {
331
331
  if (!content || typeof content !== 'string') return null;
332
332
 
333
333
  // Skip workflow orchestrators that spawn agents/skills rather than produce output directly
334
- const isOrchestrator = /##\s*Phase\s+\d+|Task\(\{|spawn.*agent|subagent_type|await Task\(|invoke.*skill|Skill\s*tool/i.test(content);
334
+ const lc = content.toLowerCase();
335
+ const isOrchestrator = /##\s*Phase\s+\d+/i.test(content) || content.includes('Task({') ||
336
+ (lc.includes('spawn') && lc.includes('agent')) || content.includes('subagent_type') ||
337
+ content.includes('await Task(') || (lc.includes('invoke') && lc.includes('skill')) ||
338
+ (/\bSkill\b/.test(content) && lc.includes('tool'));
335
339
  if (isOrchestrator) return null;
336
340
 
337
341
  // Skip reference docs and hooks (not prompts that produce conversational output)
@@ -590,7 +594,9 @@ const promptPatterns = {
590
594
  if (isNonPrompt) return null;
591
595
 
592
596
  // Skip workflow orchestrators and command files
593
- const isOrchestrator = /##\s*Phase\s+\d+|Task\(\{|spawn.*agent|subagent_type/i.test(content);
597
+ const lc2 = content.toLowerCase();
598
+ const isOrchestrator = /##\s*Phase\s+\d+/i.test(content) || content.includes('Task({') ||
599
+ (lc2.includes('spawn') && lc2.includes('agent')) || content.includes('subagent_type');
594
600
  if (isOrchestrator) return null;
595
601
 
596
602
  // Check for example indicators
@@ -793,12 +799,12 @@ const promptPatterns = {
793
799
  /\b(?:highest|lowest)\s+priority\b/i,
794
800
  /\b(?:first|second|third)\s+priority\b/i,
795
801
  // Numbered rules section (implicit priority order)
796
- /##\s*(?:critical|important)\s*rules?\s*\n+\s*1\.\s/i,
802
+ /##[ \t]*(?:critical|important)[ \t]*rules?[ \t]*\n[ \t]*1\.\s/i,
797
803
  // Precedence language
798
804
  /\btakes?\s+precedence\b/i,
799
805
  /\boverride[sd]?\b/i,
800
806
  // Ordered constraint list
801
- /##\s*constraints?\s*\n+\s*1\.\s/i
807
+ /##[ \t]*constraints?[ \t]*\n[ \t]*1\.\s/i
802
808
  ];
803
809
 
804
810
  for (const pattern of priorityIndicators) {
@@ -846,7 +852,8 @@ const promptPatterns = {
846
852
 
847
853
  // Skip if this is documentation ABOUT CoT (describes the anti-pattern)
848
854
  // These files explain why step-by-step is redundant, not actually use it
849
- if (/step[- ]by[- ]step.*(?:is\s+)?redundant|redundant.*step[- ]by[- ]step/i.test(content)) {
855
+ const lcContent = content.toLowerCase();
856
+ if (/step[- ]by[- ]step/i.test(content) && lcContent.includes('redundant')) {
850
857
  return null;
851
858
  }
852
859
 
@@ -961,7 +968,7 @@ const promptPatterns = {
961
968
  // Check if requests JSON (exclude CLI flags and function descriptions)
962
969
  // Exclude: "--output json", "analyzer returns JSON", "function returns JSON"
963
970
  const requestsJson = (
964
- (/\b(?:respond|output|return)\s+(?:with|in|as)?\s*JSON\b/i.test(content) &&
971
+ (/\b(?:respond|output|return)[ \t]+(?:(?:with|in|as)[ \t]+)?JSON\b/i.test(content) &&
965
972
  !/--output\s+json/i.test(content) &&
966
973
  !/(?:analyzer|function|method)\s+returns?\s+JSON/i.test(content))
967
974
  ) ||
@@ -971,18 +978,18 @@ const promptPatterns = {
971
978
 
972
979
  // Check if provides schema or example
973
980
  const hasSchema = /\bproperties\b.{1,200}\btype\b/is.test(content) ||
974
- /```json\s*\n\s*\{/i.test(content) ||
981
+ (content.includes('```json') && content.includes('{')) ||
975
982
  /<json[_-]?schema>/i.test(content) ||
976
983
  // JSON in JavaScript/TypeScript code blocks (quoted keys)
977
- /```(?:javascript|js|typescript|ts)\s*\n[\s\S]*?\{\s*\n?\s*"[a-zA-Z]+"/i.test(content) ||
984
+ (/```(?:javascript|js|typescript|ts)\b/.test(content) && /\{\s*"[a-zA-Z]+"/i.test(content)) ||
978
985
  // JavaScript object literal assignment (const x = { prop: ... })
979
- /(?:const|let|var)\s+\w+\s*=\s*\{\s*\n\s*[a-zA-Z_]+\s*:/i.test(content) ||
986
+ (/\b(?:const|let|var)\b/.test(content) && /=\s*\{/.test(content)) ||
980
987
  // JSON example with quoted property names in prose
981
- /\{\s*\n?\s*"[a-zA-Z_]+"\s*:\s*["\[\{]/i.test(content) ||
988
+ /\{"[a-zA-Z_]+"[ \t]*:/i.test(content) || /\{\n[ \t]*"[a-zA-Z_]+"[ \t]*:/i.test(content) ||
982
989
  // Inline schema description: { prop, prop, prop } or { prop: type, ... }
983
- /\{\s*[a-zA-Z_]+\s*,\s*[a-zA-Z_]+\s*,\s*[a-zA-Z_]+/i.test(content) ||
990
+ /\{[ \t]*[a-zA-Z_]+[ \t]*,[ \t]*[a-zA-Z_]+[ \t]*,[ \t]*[a-zA-Z_]+/i.test(content) ||
984
991
  // Interface-style: { prop: value } patterns with multiple lines
985
- /\{\s*\n\s+[a-zA-Z_]+\s*:\s*[\[\{"']/i.test(content);
992
+ /\{\n[ \t]+[a-zA-Z_]+[ \t]*:[ \t]*[\[\{"']/i.test(content);
986
993
 
987
994
  if (!hasSchema) {
988
995
  return {
@@ -1133,19 +1140,15 @@ const promptPatterns = {
1133
1140
  if (!creationTasks.test(content)) return null;
1134
1141
 
1135
1142
  // Check for pattern reference indicators
1136
- const patternRefs = [
1137
- /\blike\s+\S+\b/i,
1138
- /\bsimilar\s+to\b/i,
1139
- /\bfollow(?:ing)?\s+(?:the\s+)?(?:same\s+)?pattern\b/i,
1140
- /\bsee\s+\S+\s+(?:for|as)\s+(?:an?\s+)?example\b/i,
1141
- /\blook\s+at\s+(?:how|the)\b/i,
1142
- /\S+\.(?:js|ts|py)\s+(?:is|as)\s+(?:a\s+)?(?:good\s+)?example/i
1143
- ];
1144
-
1145
- for (const pattern of patternRefs) {
1146
- if (pattern.test(content)) {
1147
- return null;
1148
- }
1143
+ // Use string-based checks to avoid ReDoS from overlapping optional groups
1144
+ const lcRef = content.toLowerCase();
1145
+ if (/\blike\s+\S+\b/i.test(content) ||
1146
+ lcRef.includes('similar to') ||
1147
+ (lcRef.includes('follow') && lcRef.includes('pattern')) ||
1148
+ (lcRef.includes('see ') && lcRef.includes('example')) ||
1149
+ (lcRef.includes('look at')) ||
1150
+ (/\.(?:js|ts|py)\b/.test(content) && lcRef.includes('example'))) {
1151
+ return null;
1149
1152
  }
1150
1153
 
1151
1154
  return {
@@ -1386,7 +1389,7 @@ const promptPatterns = {
1386
1389
  if (codeBlockLines.has(lineNum)) continue;
1387
1390
 
1388
1391
  const line = lines[i];
1389
- const match = line.match(/^(#{1,6})\s+(.+)$/);
1392
+ const match = line.match(/^(#{1,6})[ \t]+(\S.*)$/);
1390
1393
  if (match) {
1391
1394
  headings.push({
1392
1395
  level: match[1].length,
@@ -27,7 +27,7 @@ const securityPatterns = {
27
27
  if (!frontmatterMatch) return null;
28
28
 
29
29
  const frontmatter = frontmatterMatch[1];
30
- const toolsMatch = frontmatter.match(/^tools:\s*(.*)$/m);
30
+ const toolsMatch = frontmatter.match(/^tools:[ \t]*(\S.*)?$/m);
31
31
  if (!toolsMatch) return null;
32
32
 
33
33
  const tools = toolsMatch[1];
@@ -60,8 +60,8 @@ const securityPatterns = {
60
60
 
61
61
  for (let i = 0; i < lines.length; i++) {
62
62
  const line = lines[i];
63
- // Look for shell commands with interpolation
64
- if (/(?:exec|spawn|system|shell|`|Bash)\s*[(`].*\$\{/.test(line)) {
63
+ // Look for shell commands with interpolation (string checks avoid ReDoS)
64
+ if (/\b(?:exec|spawn|system|shell)\b|`|Bash/i.test(line) && /[(`]/.test(line) && line.includes('${')) {
65
65
  issues.push({
66
66
  issue: 'Command injection risk via string interpolation',
67
67
  fix: 'Validate and escape user input before shell execution',
@@ -91,8 +91,8 @@ const securityPatterns = {
91
91
 
92
92
  for (let i = 0; i < lines.length; i++) {
93
93
  const line = lines[i];
94
- // Look for user-controlled paths with ../
95
- if (/(?:path|file|dir).*\$.*\.\.\/|\.\.\/.*\$/.test(line)) {
94
+ // Look for user-controlled paths with ../ (string checks avoid ReDoS)
95
+ if (/\b(?:path|file|dir)\b/i.test(line) && line.includes('$') && line.includes('../')) {
96
96
  issues.push({
97
97
  issue: 'Path traversal risk - user input may contain ../',
98
98
  fix: 'Validate paths and use path.resolve() with base directory check',
@@ -181,7 +181,7 @@ const securityPatterns = {
181
181
  if (!frontmatterMatch) return null;
182
182
 
183
183
  const frontmatter = frontmatterMatch[1];
184
- const toolsMatch = frontmatter.match(/^tools:\s*(.*)$/m);
184
+ const toolsMatch = frontmatter.match(/^tools:[ \t]*(\S.*)?$/m);
185
185
  if (!toolsMatch) return null;
186
186
 
187
187
  const tools = toolsMatch[1];
@@ -1803,7 +1803,7 @@ function analyzeDeadCode(content, options = {}) {
1803
1803
  // Skip if termination is part of a one-line conditional (e.g., "if (x) return;")
1804
1804
  // These don't make subsequent code unreachable
1805
1805
  if (/^\s*(if|elif|else\s+if)\s*\(/.test(trimmed) ||
1806
- /^\s*if\s+.*:/.test(trimmed)) {
1806
+ /^[ \t]*if\s[^:\n]*:/.test(trimmed)) {
1807
1807
  continue;
1808
1808
  }
1809
1809
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: deslop
3
3
  description: "Use when user wants to clean AI slop from code. Use for cleanup, remove debug statements, find ghost code, repo hygiene."
4
- version: 4.2.0
4
+ version: 4.2.2
5
5
  argument-hint: "[report|apply] [--scope=all|diff|path] [--thoroughness=quick|normal|deep]"
6
6
  ---
7
7
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drift-detect",
3
- "version": "4.2.0",
3
+ "version": "4.2.2",
4
4
  "description": "Deep repository analysis to realign project plans with actual code reality - discovers drift, gaps, and produces prioritized reconstruction plans",
5
5
  "author": {
6
6
  "name": "Avi Fenesh",
@@ -418,7 +418,7 @@ const docsPatterns = {
418
418
  }
419
419
 
420
420
  // Check for very long lists that could be tables
421
- const longLists = content.match(/(?:^[-*]\s+.+\n){10,}/gm);
421
+ const longLists = content.match(/(?:^[-*][ \t]+\S[^\n]*\n){10,}/gm);
422
422
  if (longLists) {
423
423
  suggestions.push('Long lists (10+ items) might be more efficient as tables');
424
424
  }
@@ -179,7 +179,7 @@ function applyAtPath(obj, pathStr, fixFn) {
179
179
  const part = parts[i];
180
180
  if (part.includes('[')) {
181
181
  // Array access
182
- const match = part.match(/(\w+)\[(\d+)\]/);
182
+ const match = part.match(/^((?!__proto__|constructor|prototype)[a-zA-Z_]\w*)\[(\d{1,10})\]$/);
183
183
  if (match) {
184
184
  current = current[match[1]][parseInt(match[2], 10)];
185
185
  }
@@ -190,7 +190,7 @@ function applyAtPath(obj, pathStr, fixFn) {
190
190
 
191
191
  const lastPart = parts[parts.length - 1];
192
192
  if (lastPart.includes('[')) {
193
- const match = lastPart.match(/(\w+)\[(\d+)\]/);
193
+ const match = lastPart.match(/^((?!__proto__|constructor|prototype)[a-zA-Z_]\w*)\[(\d{1,10})\]$/);
194
194
  if (match) {
195
195
  current[match[1]][parseInt(match[2], 10)] = fixFn(current[match[1]][parseInt(match[2], 10)]);
196
196
  }
@@ -403,7 +403,7 @@ function fixInconsistentHeadings(content) {
403
403
 
404
404
  if (inCodeBlock) continue;
405
405
 
406
- const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
406
+ const headingMatch = line.match(/^(#{1,6})[ \t]+(\S.*)$/);
407
407
  if (headingMatch) {
408
408
  const currentLevel = headingMatch[1].length;
409
409
  const headingText = headingMatch[2];
@@ -535,6 +535,37 @@ Why bad: [explanation]
535
535
  return content.trim() + exampleSection;
536
536
  }
537
537
 
538
+ /**
539
+ * Wrap a markdown section (heading to next heading/separator) in XML tags.
540
+ * Uses line-by-line scanning to avoid ReDoS from [\s\S]*? with lookaheads.
541
+ */
542
+ function wrapSection(text, headingPattern, tagName) {
543
+ const lines = text.split('\n');
544
+ let sectionStart = -1;
545
+ for (let i = 0; i < lines.length; i++) {
546
+ if (sectionStart === -1) {
547
+ if (headingPattern.test(lines[i])) {
548
+ sectionStart = i;
549
+ }
550
+ } else {
551
+ // End section at next heading or horizontal rule
552
+ if (/^#{1,6}\s/.test(lines[i]) || /^---/.test(lines[i])) {
553
+ const before = lines.slice(0, sectionStart);
554
+ const section = lines.slice(sectionStart, i);
555
+ const after = lines.slice(i);
556
+ return [...before, `<${tagName}>`, ...section, `</${tagName}>`, ...after].join('\n');
557
+ }
558
+ }
559
+ }
560
+ // Section runs to end of content
561
+ if (sectionStart !== -1) {
562
+ const before = lines.slice(0, sectionStart);
563
+ const section = lines.slice(sectionStart);
564
+ return [...before, `<${tagName}>`, ...section, `</${tagName}>`].join('\n');
565
+ }
566
+ return text;
567
+ }
568
+
538
569
  /**
539
570
  * Add XML structure tags to complex prompt
540
571
  * @param {string} content - Prompt content
@@ -551,17 +582,11 @@ function fixMissingXmlStructure(content) {
551
582
  // Wrap role section if exists
552
583
  let result = content;
553
584
 
554
- // Find and wrap role section
555
- result = result.replace(
556
- /^(##\s*(?:your\s+)?role\s*\n)([\s\S]*?)(?=\n##|\n---|\Z)/im,
557
- '<role>\n$1$2</role>\n'
558
- );
585
+ // Find and wrap role section (use non-regex approach to avoid ReDoS)
586
+ result = wrapSection(result, /^##[ \t]*(?:your[ \t]+)?role[ \t]*$/im, 'role');
559
587
 
560
588
  // Find and wrap constraints section
561
- result = result.replace(
562
- /^(##\s*(?:constraints?|rules?)\s*\n)([\s\S]*?)(?=\n##|\n---|\Z)/im,
563
- '<constraints>\n$1$2</constraints>\n'
564
- );
589
+ result = wrapSection(result, /^##[ \t]*(?:constraints?|rules?)[ \t]*$/im, 'constraints');
565
590
 
566
591
  return result;
567
592
  }
@@ -622,7 +647,7 @@ function fixMissingTriggerPhrase(content) {
622
647
  // Check if already has trigger phrase
623
648
  if (!/use when user asks/i.test(descLine)) {
624
649
  // Extract current description
625
- const match = descLine.match(/^description:\s*(.+)$/);
650
+ const match = descLine.match(/^description:[ \t]*(\S.*)$/);
626
651
  if (match) {
627
652
  const currentDesc = match[1].trim();
628
653
  // Add trigger phrase
@@ -43,17 +43,25 @@ function extractFileReferences(content) {
43
43
 
44
44
  const references = [];
45
45
 
46
- // Match markdown links: [text](path)
47
- const linkMatches = content.match(/\[([^\]]+)\]\(([^)]+)\)/g) || [];
48
- for (const match of linkMatches) {
49
- const pathMatch = match.match(/\]\(([^)]+)\)/);
50
- if (pathMatch && pathMatch[1]) {
51
- const href = pathMatch[1];
52
- // Skip URLs and anchors
53
- if (!href.startsWith('http') && !href.startsWith('#') && !href.startsWith('mailto:')) {
54
- references.push(href.split('#')[0]); // Remove anchor
46
+ // Extract markdown links [text](path) using indexOf scanning (ReDoS-safe)
47
+ let pos = 0;
48
+ while (pos < content.length) {
49
+ const openBracket = content.indexOf('[', pos);
50
+ if (openBracket === -1) break;
51
+ const closeBracket = content.indexOf(']', openBracket + 1);
52
+ if (closeBracket === -1) break;
53
+ if (content[closeBracket + 1] === '(') {
54
+ const closeParen = content.indexOf(')', closeBracket + 2);
55
+ if (closeParen !== -1 && closeParen - closeBracket - 2 <= 500) {
56
+ const href = content.substring(closeBracket + 2, closeParen);
57
+ if (!href.startsWith('http') && !href.startsWith('#') && !href.startsWith('mailto:')) {
58
+ references.push(href.split('#')[0]); // Remove anchor
59
+ }
60
+ pos = closeParen + 1;
61
+ continue;
55
62
  }
56
63
  }
64
+ pos = openBracket + 1;
57
65
  }
58
66
 
59
67
  // Match backtick paths: `path/to/file.ext` or `file.ext` (root files)
@@ -57,7 +57,7 @@ const projectMemoryPatterns = {
57
57
  const hasArchitecture = /##\s+architecture/i.test(content);
58
58
  const hasStructure = /##\s+(?:project\s+)?structure/i.test(content);
59
59
  const hasOverview = /##\s+overview/i.test(content);
60
- const hasDirectoryTree = /```[\s\S]*?(?:├──|└──|lib\/|src\/)[\s\S]*?```/.test(content);
60
+ const hasDirectoryTree = content.includes('```') && /├──|└──|lib\/|src\//.test(content);
61
61
 
62
62
  if (!hasArchitecture && !hasStructure && !hasOverview && !hasDirectoryTree) {
63
63
  return {
@@ -85,7 +85,7 @@ const projectMemoryPatterns = {
85
85
  const hasCommands = /##\s+(?:key\s+)?commands/i.test(content);
86
86
  const hasScripts = /##\s+scripts/i.test(content);
87
87
  const hasUsage = /##\s+usage/i.test(content);
88
- const hasCodeBlocks = /```(?:bash|sh|shell)[\s\S]*?(?:npm|yarn|pnpm|git|make)/i.test(content);
88
+ const hasCodeBlocks = /```(?:bash|sh|shell)/i.test(content) && /\b(?:npm|yarn|pnpm|git|make)\b/i.test(content);
89
89
 
90
90
  if (!hasCommands && !hasScripts && !hasUsage && !hasCodeBlocks) {
91
91
  return {