agentsys 5.0.3 → 5.1.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 (264) hide show
  1. package/.claude-plugin/marketplace.json +21 -14
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +2 -1
  4. package/CHANGELOG.md +18 -0
  5. package/README.md +7 -6
  6. package/adapters/codex/skills/agnix/SKILL.md +0 -1
  7. package/adapters/codex/skills/audit-project/SKILL.md +0 -1
  8. package/adapters/codex/skills/audit-project-agents/SKILL.md +0 -1
  9. package/adapters/codex/skills/audit-project-github/SKILL.md +0 -1
  10. package/adapters/codex/skills/consult/SKILL.md +132 -57
  11. package/adapters/codex/skills/debate/SKILL.md +214 -0
  12. package/adapters/codex/skills/delivery-approval/SKILL.md +0 -1
  13. package/adapters/codex/skills/deslop/SKILL.md +0 -1
  14. package/adapters/codex/skills/drift-detect/SKILL.md +0 -1
  15. package/adapters/codex/skills/enhance/SKILL.md +0 -1
  16. package/adapters/codex/skills/learn/SKILL.md +0 -1
  17. package/adapters/codex/skills/next-task/SKILL.md +0 -1
  18. package/adapters/codex/skills/perf/SKILL.md +0 -1
  19. package/adapters/codex/skills/repo-map/SKILL.md +0 -1
  20. package/adapters/codex/skills/ship/SKILL.md +0 -1
  21. package/adapters/codex/skills/ship-ci-review-loop/SKILL.md +0 -1
  22. package/adapters/codex/skills/ship-deployment/SKILL.md +0 -1
  23. package/adapters/codex/skills/ship-error-handling/SKILL.md +0 -1
  24. package/adapters/codex/skills/sync-docs/SKILL.md +0 -1
  25. package/adapters/opencode/agents/agent-enhancer.md +0 -1
  26. package/adapters/opencode/agents/agnix-agent.md +0 -1
  27. package/adapters/opencode/agents/ci-fixer.md +0 -1
  28. package/adapters/opencode/agents/ci-monitor.md +0 -1
  29. package/adapters/opencode/agents/claudemd-enhancer.md +0 -1
  30. package/adapters/opencode/agents/consult-agent.md +122 -30
  31. package/adapters/opencode/agents/cross-file-enhancer.md +0 -1
  32. package/adapters/opencode/agents/debate-orchestrator.md +169 -0
  33. package/adapters/opencode/agents/delivery-validator.md +0 -1
  34. package/adapters/opencode/agents/deslop-agent.md +0 -1
  35. package/adapters/opencode/agents/docs-enhancer.md +0 -1
  36. package/adapters/opencode/agents/exploration-agent.md +0 -1
  37. package/adapters/opencode/agents/hooks-enhancer.md +0 -1
  38. package/adapters/opencode/agents/implementation-agent.md +0 -1
  39. package/adapters/opencode/agents/learn-agent.md +0 -1
  40. package/adapters/opencode/agents/map-validator.md +0 -1
  41. package/adapters/opencode/agents/perf-analyzer.md +0 -1
  42. package/adapters/opencode/agents/perf-code-paths.md +0 -1
  43. package/adapters/opencode/agents/perf-investigation-logger.md +0 -1
  44. package/adapters/opencode/agents/perf-orchestrator.md +0 -1
  45. package/adapters/opencode/agents/perf-theory-gatherer.md +0 -1
  46. package/adapters/opencode/agents/perf-theory-tester.md +0 -1
  47. package/adapters/opencode/agents/plan-synthesizer.md +0 -1
  48. package/adapters/opencode/agents/planning-agent.md +0 -1
  49. package/adapters/opencode/agents/plugin-enhancer.md +0 -1
  50. package/adapters/opencode/agents/prompt-enhancer.md +0 -1
  51. package/adapters/opencode/agents/simple-fixer.md +0 -1
  52. package/adapters/opencode/agents/skills-enhancer.md +0 -1
  53. package/adapters/opencode/agents/sync-docs-agent.md +0 -1
  54. package/adapters/opencode/agents/task-discoverer.md +0 -1
  55. package/adapters/opencode/agents/test-coverage-checker.md +0 -1
  56. package/adapters/opencode/agents/worktree-manager.md +0 -1
  57. package/adapters/opencode/commands/agnix.md +0 -1
  58. package/adapters/opencode/commands/audit-project-agents.md +0 -1
  59. package/adapters/opencode/commands/audit-project-github.md +0 -1
  60. package/adapters/opencode/commands/audit-project.md +0 -1
  61. package/adapters/opencode/commands/consult.md +133 -57
  62. package/adapters/opencode/commands/debate.md +224 -0
  63. package/adapters/opencode/commands/delivery-approval.md +0 -1
  64. package/adapters/opencode/commands/deslop.md +0 -1
  65. package/adapters/opencode/commands/drift-detect.md +0 -1
  66. package/adapters/opencode/commands/enhance.md +0 -1
  67. package/adapters/opencode/commands/learn.md +0 -1
  68. package/adapters/opencode/commands/next-task.md +0 -1
  69. package/adapters/opencode/commands/perf.md +0 -1
  70. package/adapters/opencode/commands/repo-map.md +0 -1
  71. package/adapters/opencode/commands/ship-ci-review-loop.md +0 -1
  72. package/adapters/opencode/commands/ship-deployment.md +0 -1
  73. package/adapters/opencode/commands/ship-error-handling.md +0 -1
  74. package/adapters/opencode/commands/ship.md +0 -1
  75. package/adapters/opencode/commands/sync-docs.md +0 -1
  76. package/adapters/opencode/skills/agnix/SKILL.md +1 -2
  77. package/adapters/opencode/skills/consult/SKILL.md +33 -23
  78. package/adapters/opencode/skills/debate/SKILL.md +245 -0
  79. package/adapters/opencode/skills/deslop/SKILL.md +1 -2
  80. package/adapters/opencode/skills/discover-tasks/SKILL.md +1 -2
  81. package/adapters/opencode/skills/drift-analysis/SKILL.md +1 -2
  82. package/adapters/opencode/skills/enhance-agent-prompts/SKILL.md +1 -2
  83. package/adapters/opencode/skills/enhance-claude-memory/SKILL.md +1 -2
  84. package/adapters/opencode/skills/enhance-cross-file/SKILL.md +1 -2
  85. package/adapters/opencode/skills/enhance-docs/SKILL.md +1 -2
  86. package/adapters/opencode/skills/enhance-hooks/SKILL.md +1 -2
  87. package/adapters/opencode/skills/enhance-orchestrator/SKILL.md +1 -2
  88. package/adapters/opencode/skills/enhance-plugins/SKILL.md +1 -2
  89. package/adapters/opencode/skills/enhance-prompts/SKILL.md +1 -2
  90. package/adapters/opencode/skills/enhance-skills/SKILL.md +1 -2
  91. package/adapters/opencode/skills/learn/SKILL.md +1 -2
  92. package/adapters/opencode/skills/orchestrate-review/SKILL.md +0 -1
  93. package/adapters/opencode/skills/perf-analyzer/SKILL.md +1 -2
  94. package/adapters/opencode/skills/perf-baseline-manager/SKILL.md +1 -2
  95. package/adapters/opencode/skills/perf-benchmarker/SKILL.md +1 -2
  96. package/adapters/opencode/skills/perf-code-paths/SKILL.md +1 -2
  97. package/adapters/opencode/skills/perf-investigation-logger/SKILL.md +1 -2
  98. package/adapters/opencode/skills/perf-profiler/SKILL.md +1 -2
  99. package/adapters/opencode/skills/perf-theory-gatherer/SKILL.md +1 -2
  100. package/adapters/opencode/skills/perf-theory-tester/SKILL.md +1 -2
  101. package/adapters/opencode/skills/repo-mapping/SKILL.md +1 -2
  102. package/adapters/opencode/skills/sync-docs/SKILL.md +1 -2
  103. package/adapters/opencode/skills/validate-delivery/SKILL.md +1 -2
  104. package/lib/adapter-transforms.js +24 -4
  105. package/package.json +1 -1
  106. package/plugins/agnix/.claude-plugin/plugin.json +1 -1
  107. package/plugins/agnix/skills/agnix/SKILL.md +1 -1
  108. package/plugins/audit-project/.claude-plugin/plugin.json +1 -1
  109. package/plugins/audit-project/lib/adapter-transforms.js +24 -4
  110. package/plugins/consult/.claude-plugin/plugin.json +1 -1
  111. package/plugins/consult/agents/consult-agent.md +122 -29
  112. package/plugins/consult/commands/consult.md +135 -58
  113. package/plugins/consult/skills/consult/SKILL.md +31 -20
  114. package/plugins/debate/.claude-plugin/plugin.json +21 -0
  115. package/plugins/debate/agents/debate-orchestrator.md +175 -0
  116. package/plugins/debate/commands/debate.md +221 -0
  117. package/plugins/debate/lib/adapter-transforms.js +298 -0
  118. package/plugins/debate/lib/collectors/codebase.js +392 -0
  119. package/plugins/debate/lib/collectors/docs-patterns.js +713 -0
  120. package/plugins/debate/lib/collectors/documentation.js +219 -0
  121. package/plugins/debate/lib/collectors/github.js +330 -0
  122. package/plugins/debate/lib/collectors/index.js +126 -0
  123. package/plugins/debate/lib/config/index.js +14 -0
  124. package/plugins/debate/lib/cross-platform/index.js +539 -0
  125. package/plugins/debate/lib/discovery/index.js +352 -0
  126. package/plugins/debate/lib/drift-detect/collectors.js +37 -0
  127. package/plugins/debate/lib/enhance/agent-analyzer.js +421 -0
  128. package/plugins/debate/lib/enhance/agent-patterns.js +571 -0
  129. package/plugins/debate/lib/enhance/auto-suppression.js +622 -0
  130. package/plugins/debate/lib/enhance/benchmark.js +417 -0
  131. package/plugins/debate/lib/enhance/cross-file-analyzer.js +930 -0
  132. package/plugins/debate/lib/enhance/cross-file-patterns.js +370 -0
  133. package/plugins/debate/lib/enhance/docs-analyzer.js +325 -0
  134. package/plugins/debate/lib/enhance/docs-patterns.js +671 -0
  135. package/plugins/debate/lib/enhance/fixer.js +721 -0
  136. package/plugins/debate/lib/enhance/hook-analyzer.js +135 -0
  137. package/plugins/debate/lib/enhance/hook-patterns.js +40 -0
  138. package/plugins/debate/lib/enhance/index.js +127 -0
  139. package/plugins/debate/lib/enhance/plugin-analyzer.js +402 -0
  140. package/plugins/debate/lib/enhance/plugin-patterns.js +326 -0
  141. package/plugins/debate/lib/enhance/projectmemory-analyzer.js +551 -0
  142. package/plugins/debate/lib/enhance/projectmemory-patterns.js +617 -0
  143. package/plugins/debate/lib/enhance/prompt-analyzer.js +457 -0
  144. package/plugins/debate/lib/enhance/prompt-patterns.js +1484 -0
  145. package/plugins/debate/lib/enhance/reporter.js +1348 -0
  146. package/plugins/debate/lib/enhance/security-patterns.js +284 -0
  147. package/plugins/debate/lib/enhance/skill-analyzer.js +182 -0
  148. package/plugins/debate/lib/enhance/skill-patterns.js +147 -0
  149. package/plugins/debate/lib/enhance/suppression.js +352 -0
  150. package/plugins/debate/lib/enhance/tool-patterns.js +373 -0
  151. package/plugins/debate/lib/index.js +270 -0
  152. package/plugins/debate/lib/patterns/cli-enhancers.js +611 -0
  153. package/plugins/debate/lib/patterns/pipeline.js +948 -0
  154. package/plugins/debate/lib/patterns/review-patterns.js +558 -0
  155. package/plugins/debate/lib/patterns/slop-analyzers.js +2305 -0
  156. package/plugins/debate/lib/patterns/slop-patterns.js +1187 -0
  157. package/plugins/debate/lib/perf/analyzer/index.js +22 -0
  158. package/plugins/debate/lib/perf/argument-parser.js +105 -0
  159. package/plugins/debate/lib/perf/baseline-comparator.js +50 -0
  160. package/plugins/debate/lib/perf/baseline-store.js +127 -0
  161. package/plugins/debate/lib/perf/benchmark-runner.js +404 -0
  162. package/plugins/debate/lib/perf/breaking-point-finder.js +52 -0
  163. package/plugins/debate/lib/perf/breaking-point-runner.js +60 -0
  164. package/plugins/debate/lib/perf/checkpoint.js +123 -0
  165. package/plugins/debate/lib/perf/code-paths.js +86 -0
  166. package/plugins/debate/lib/perf/consolidation.js +37 -0
  167. package/plugins/debate/lib/perf/constraint-runner.js +71 -0
  168. package/plugins/debate/lib/perf/experiment-runner.js +32 -0
  169. package/plugins/debate/lib/perf/index.js +41 -0
  170. package/plugins/debate/lib/perf/investigation-state.js +874 -0
  171. package/plugins/debate/lib/perf/optimization-runner.js +79 -0
  172. package/plugins/debate/lib/perf/profilers/go.js +22 -0
  173. package/plugins/debate/lib/perf/profilers/index.js +46 -0
  174. package/plugins/debate/lib/perf/profilers/java.js +23 -0
  175. package/plugins/debate/lib/perf/profilers/node.js +27 -0
  176. package/plugins/debate/lib/perf/profilers/python.js +23 -0
  177. package/plugins/debate/lib/perf/profilers/rust.js +23 -0
  178. package/plugins/debate/lib/perf/profiling-runner.js +75 -0
  179. package/plugins/debate/lib/perf/schemas.js +140 -0
  180. package/plugins/debate/lib/platform/detect-platform.js +413 -0
  181. package/plugins/debate/lib/platform/detection-configs.js +93 -0
  182. package/plugins/debate/lib/platform/state-dir.js +132 -0
  183. package/plugins/debate/lib/platform/verify-tools.js +182 -0
  184. package/plugins/debate/lib/repo-map/cache.js +152 -0
  185. package/plugins/debate/lib/repo-map/concurrency.js +29 -0
  186. package/plugins/debate/lib/repo-map/index.js +222 -0
  187. package/plugins/debate/lib/repo-map/installer.js +212 -0
  188. package/plugins/debate/lib/repo-map/queries/go.js +27 -0
  189. package/plugins/debate/lib/repo-map/queries/index.js +100 -0
  190. package/plugins/debate/lib/repo-map/queries/java.js +38 -0
  191. package/plugins/debate/lib/repo-map/queries/javascript.js +55 -0
  192. package/plugins/debate/lib/repo-map/queries/python.js +24 -0
  193. package/plugins/debate/lib/repo-map/queries/rust.js +73 -0
  194. package/plugins/debate/lib/repo-map/queries/typescript.js +38 -0
  195. package/plugins/debate/lib/repo-map/runner.js +1364 -0
  196. package/plugins/debate/lib/repo-map/updater.js +562 -0
  197. package/plugins/debate/lib/repo-map/usage-analyzer.js +407 -0
  198. package/plugins/debate/lib/schemas/plugin-manifest.schema.json +57 -0
  199. package/plugins/debate/lib/schemas/validator.js +247 -0
  200. package/plugins/debate/lib/sources/custom-handler.js +199 -0
  201. package/plugins/debate/lib/sources/policy-questions.js +246 -0
  202. package/plugins/debate/lib/sources/source-cache.js +165 -0
  203. package/plugins/debate/lib/state/workflow-state.js +576 -0
  204. package/plugins/debate/lib/types/agent-frontmatter.d.ts +134 -0
  205. package/plugins/debate/lib/types/command-frontmatter.d.ts +107 -0
  206. package/plugins/debate/lib/types/hook-frontmatter.d.ts +115 -0
  207. package/plugins/debate/lib/types/index.d.ts +84 -0
  208. package/plugins/debate/lib/types/plugin-manifest.d.ts +102 -0
  209. package/plugins/debate/lib/types/skill-frontmatter.d.ts +89 -0
  210. package/plugins/debate/lib/utils/atomic-write.js +94 -0
  211. package/plugins/debate/lib/utils/cache-manager.js +159 -0
  212. package/plugins/debate/lib/utils/command-parser.js +0 -0
  213. package/plugins/debate/lib/utils/context-optimizer.js +300 -0
  214. package/plugins/debate/lib/utils/deprecation.js +37 -0
  215. package/plugins/debate/lib/utils/shell-escape.js +88 -0
  216. package/plugins/debate/lib/utils/state-helpers.js +61 -0
  217. package/plugins/debate/skills/debate/SKILL.md +264 -0
  218. package/plugins/deslop/.claude-plugin/plugin.json +1 -1
  219. package/plugins/deslop/lib/adapter-transforms.js +24 -4
  220. package/plugins/deslop/skills/deslop/SKILL.md +1 -1
  221. package/plugins/drift-detect/.claude-plugin/plugin.json +1 -1
  222. package/plugins/drift-detect/lib/adapter-transforms.js +24 -4
  223. package/plugins/drift-detect/skills/drift-analysis/SKILL.md +1 -1
  224. package/plugins/enhance/.claude-plugin/plugin.json +1 -1
  225. package/plugins/enhance/lib/adapter-transforms.js +24 -4
  226. package/plugins/enhance/skills/enhance-agent-prompts/SKILL.md +1 -1
  227. package/plugins/enhance/skills/enhance-claude-memory/SKILL.md +1 -1
  228. package/plugins/enhance/skills/enhance-cross-file/SKILL.md +1 -1
  229. package/plugins/enhance/skills/enhance-docs/SKILL.md +1 -1
  230. package/plugins/enhance/skills/enhance-hooks/SKILL.md +1 -1
  231. package/plugins/enhance/skills/enhance-orchestrator/SKILL.md +1 -1
  232. package/plugins/enhance/skills/enhance-plugins/SKILL.md +1 -1
  233. package/plugins/enhance/skills/enhance-prompts/SKILL.md +1 -1
  234. package/plugins/enhance/skills/enhance-skills/SKILL.md +1 -1
  235. package/plugins/learn/.claude-plugin/plugin.json +1 -1
  236. package/plugins/learn/agents/learn-agent.md +1 -1
  237. package/plugins/learn/lib/adapter-transforms.js +24 -4
  238. package/plugins/learn/skills/learn/SKILL.md +1 -1
  239. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  240. package/plugins/next-task/agents/exploration-agent.md +1 -1
  241. package/plugins/next-task/lib/adapter-transforms.js +24 -4
  242. package/plugins/next-task/skills/discover-tasks/SKILL.md +1 -1
  243. package/plugins/next-task/skills/validate-delivery/SKILL.md +1 -1
  244. package/plugins/perf/.claude-plugin/plugin.json +1 -1
  245. package/plugins/perf/lib/adapter-transforms.js +24 -4
  246. package/plugins/perf/skills/perf-analyzer/SKILL.md +1 -1
  247. package/plugins/perf/skills/perf-baseline-manager/SKILL.md +1 -1
  248. package/plugins/perf/skills/perf-benchmarker/SKILL.md +1 -1
  249. package/plugins/perf/skills/perf-code-paths/SKILL.md +1 -1
  250. package/plugins/perf/skills/perf-investigation-logger/SKILL.md +1 -1
  251. package/plugins/perf/skills/perf-profiler/SKILL.md +1 -1
  252. package/plugins/perf/skills/perf-theory-gatherer/SKILL.md +1 -1
  253. package/plugins/perf/skills/perf-theory-tester/SKILL.md +1 -1
  254. package/plugins/repo-map/.claude-plugin/plugin.json +1 -1
  255. package/plugins/repo-map/lib/adapter-transforms.js +24 -4
  256. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  257. package/plugins/ship/lib/adapter-transforms.js +24 -4
  258. package/plugins/sync-docs/.claude-plugin/plugin.json +1 -1
  259. package/plugins/sync-docs/lib/adapter-transforms.js +24 -4
  260. package/plugins/sync-docs/skills/sync-docs/SKILL.md +1 -1
  261. package/scripts/gen-adapters.js +6 -7
  262. package/scripts/generate-docs.js +4 -2
  263. package/scripts/plugins.txt +1 -0
  264. package/site/content.json +6 -6
@@ -0,0 +1,622 @@
1
+ /**
2
+ * Auto-Learning Suppression System for /enhance
3
+ *
4
+ * Automatically detects obvious false positives and stores them
5
+ * for future runs, making the enhance tool smarter over time.
6
+ *
7
+ * Key features:
8
+ * - Pattern-specific heuristics for common false positives
9
+ * - Cross-platform storage using getSuppressionPath()
10
+ * - 0.90+ confidence threshold for auto-suppression
11
+ * - Backward compatible with existing suppression.js
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { execFileSync } = require('child_process');
17
+
18
+ // Try to import cross-platform helpers
19
+ let getSuppressionPath;
20
+ try {
21
+ const crossPlatform = require('../cross-platform');
22
+ getSuppressionPath = crossPlatform.getSuppressionPath;
23
+ } catch {
24
+ // Fallback for when running from plugin directory
25
+ const os = require('os');
26
+ getSuppressionPath = () => path.join(os.homedir(), '.claude', 'enhance', 'suppressions.json');
27
+ }
28
+
29
+ /**
30
+ * Minimum confidence threshold for auto-suppression
31
+ */
32
+ const CONFIDENCE_THRESHOLD = 0.90;
33
+
34
+ /**
35
+ * Maximum suppressions per project (prevents file bloat)
36
+ */
37
+ const MAX_SUPPRESSIONS_PER_PROJECT = 100;
38
+
39
+ /**
40
+ * Suppression expiry in milliseconds (6 months)
41
+ */
42
+ const SUPPRESSION_EXPIRY_MS = 6 * 30 * 24 * 60 * 60 * 1000;
43
+
44
+ /**
45
+ * Pattern-specific heuristics for detecting false positives
46
+ * Each heuristic returns { reason, confidence } or null
47
+ */
48
+ const PATTERN_HEURISTICS = {
49
+ /**
50
+ * vague_instructions: Detects when vague terms appear in pattern documentation
51
+ * False positive when content describes the pattern itself
52
+ */
53
+ vague_instructions: (finding, content, context) => {
54
+ const contentLower = content.toLowerCase();
55
+
56
+ // Check if file is pattern documentation describing vague language detection
57
+ const isPatternDoc =
58
+ /pattern.*detect.*usually|example.*vague|fuzzy.*language.*like/i.test(content) ||
59
+ /vague.*terms.*like|"usually".*"sometimes"/i.test(content);
60
+
61
+ if (isPatternDoc) {
62
+ return {
63
+ reason: 'Pattern documentation self-reference (describes vague language detection)',
64
+ confidence: 0.98
65
+ };
66
+ }
67
+
68
+ // Check if in table describing patterns
69
+ const line = finding.line || 0;
70
+ const lines = content.split('\n');
71
+ const surroundingLines = lines.slice(Math.max(0, line - 5), line + 5).join('\n');
72
+ if (/\|.*vague.*\||\|.*usually.*sometimes.*\|/i.test(surroundingLines)) {
73
+ return {
74
+ reason: 'Pattern table documentation',
75
+ confidence: 0.95
76
+ };
77
+ }
78
+
79
+ return null;
80
+ },
81
+
82
+ /**
83
+ * aggressive_emphasis: Detects legitimate workflow enforcement usage
84
+ * False positive in workflow gates and critical agent constraints
85
+ */
86
+ aggressive_emphasis: (finding, content, context) => {
87
+ const line = finding.line || 0;
88
+ const lines = content.split('\n');
89
+ const surroundingLines = lines.slice(Math.max(0, line - 20), line + 20).join('\n');
90
+
91
+ // Check for workflow gate context
92
+ const isWorkflowGate =
93
+ /WORKFLOW\s+GATES?/i.test(surroundingLines) ||
94
+ /\[CRITICAL\]\s*NO\s+AGENT\s+may/i.test(surroundingLines) ||
95
+ /MUST\s+NOT\s+DO|NEVER\s+skip|DO\s+NOT\s+proceed/i.test(surroundingLines) ||
96
+ /SubagentStop\s+hook|phase\s+9\s+review/i.test(surroundingLines);
97
+
98
+ if (isWorkflowGate) {
99
+ return {
100
+ reason: 'Workflow enforcement requires emphasis for gates',
101
+ confidence: 0.95
102
+ };
103
+ }
104
+
105
+ // Check for critical rules section
106
+ const isCriticalRules =
107
+ /critical-rules|Critical\s+Rules.*Priority/i.test(surroundingLines) ||
108
+ /<critical-rules>/i.test(surroundingLines);
109
+
110
+ if (isCriticalRules) {
111
+ return {
112
+ reason: 'Critical rules section requires emphasis',
113
+ confidence: 0.93
114
+ };
115
+ }
116
+
117
+ return null;
118
+ },
119
+
120
+ /**
121
+ * missing_examples: Detects orchestrator/workflow files that delegate to subagents
122
+ * False positive when file is orchestrator that spawns other agents
123
+ */
124
+ missing_examples: (finding, content, context) => {
125
+ const filePath = finding.file || context?.file || '';
126
+ const fileNameLower = path.basename(filePath).toLowerCase();
127
+
128
+ // Check if file is an orchestrator
129
+ const isOrchestrator =
130
+ fileNameLower.includes('orchestrator') ||
131
+ fileNameLower.includes('coordinator') ||
132
+ /Task\s*\(\s*\{[\s\S]*subagent_type/i.test(content);
133
+
134
+ if (isOrchestrator) {
135
+ return {
136
+ reason: 'Orchestrator file delegates to subagents (examples in subagents)',
137
+ confidence: 0.92
138
+ };
139
+ }
140
+
141
+ // Check if workflow command that invokes agents
142
+ const isWorkflowCommand =
143
+ /spawn.*agent|invoke.*agent|Task\s*\(\s*\{/i.test(content) &&
144
+ fileNameLower.endsWith('.md');
145
+
146
+ if (isWorkflowCommand) {
147
+ return {
148
+ reason: 'Workflow command invokes agents with examples',
149
+ confidence: 0.90
150
+ };
151
+ }
152
+
153
+ return null;
154
+ },
155
+
156
+ /**
157
+ * missing_output_format: Detects files that spawn subagents with output specs
158
+ * False positive when subagent is responsible for output format
159
+ */
160
+ missing_output_format: (finding, content, context) => {
161
+ // Check if content spawns subagents with their own output specs
162
+ const spawnsSubagent =
163
+ /subagent_type|spawn.*agent|Task\s*\(\s*\{/i.test(content) ||
164
+ /enhance:.*-enhancer|enhance:.*-reporter/i.test(content);
165
+
166
+ if (spawnsSubagent) {
167
+ return {
168
+ reason: 'Delegates output to subagent (subagent defines format)',
169
+ confidence: 0.91
170
+ };
171
+ }
172
+
173
+ return null;
174
+ },
175
+
176
+ /**
177
+ * missing_constraints: Detects files that already have constraint sections
178
+ * False positive when "## What Agent MUST NOT Do" or similar exists
179
+ */
180
+ missing_constraints: (finding, content, context) => {
181
+ // Check for constraint section presence
182
+ const hasConstraintSection =
183
+ /##\s*What\s+.*MUST\s+NOT\s+Do/i.test(content) ||
184
+ /##\s*Constraints/i.test(content) ||
185
+ /<constraints>/i.test(content) ||
186
+ /##\s*Critical\s+Constraints/i.test(content) ||
187
+ /WORKFLOW\s+GATES/i.test(content);
188
+
189
+ if (hasConstraintSection) {
190
+ return {
191
+ reason: 'File has constraint section (different heading format)',
192
+ confidence: 0.94
193
+ };
194
+ }
195
+
196
+ return null;
197
+ },
198
+
199
+ /**
200
+ * redundant_cot: Detects legitimate step-by-step for complex workflows
201
+ * False positive in multi-phase workflow prompts
202
+ */
203
+ redundant_cot: (finding, content, context) => {
204
+ // Check if multi-phase workflow
205
+ const isMultiPhase =
206
+ /Phase\s+\d+:|Step\s+\d+:|###\s+Phase/i.test(content) &&
207
+ /Phase\s+[2-9]:|Step\s+[2-9]:/i.test(content);
208
+
209
+ if (isMultiPhase) {
210
+ return {
211
+ reason: 'Multi-phase workflow requires step guidance',
212
+ confidence: 0.91
213
+ };
214
+ }
215
+
216
+ return null;
217
+ }
218
+ };
219
+
220
+ /**
221
+ * Check if a finding is likely a false positive
222
+ *
223
+ * @param {Object} finding - The finding to check
224
+ * @param {string} content - File content
225
+ * @param {Object} context - Additional context { file, analyzer, suppressions }
226
+ * @returns {{ reason: string, confidence: number } | null}
227
+ */
228
+ function isLikelyFalsePositive(finding, content, context = {}) {
229
+ const patternId = (finding.patternId || finding.id || '').toLowerCase();
230
+
231
+ // Skip if no content
232
+ if (!content || typeof content !== 'string') {
233
+ return null;
234
+ }
235
+
236
+ // Check pattern-specific heuristic
237
+ const heuristic = PATTERN_HEURISTICS[patternId];
238
+ if (heuristic) {
239
+ const result = heuristic(finding, content, context);
240
+ if (result && result.confidence >= CONFIDENCE_THRESHOLD) {
241
+ return result;
242
+ }
243
+ }
244
+
245
+ // Generic self-reference detection: pattern names in pattern docs
246
+ if (finding.file && isPatternDocumentation(finding.file, content, patternId)) {
247
+ return {
248
+ reason: 'Pattern self-reference in documentation',
249
+ confidence: 0.96
250
+ };
251
+ }
252
+
253
+ return null;
254
+ }
255
+
256
+ /**
257
+ * Check if file is pattern documentation that mentions the pattern
258
+ *
259
+ * @param {string} filePath - File path
260
+ * @param {string} content - File content
261
+ * @param {string} patternId - Pattern being checked
262
+ * @returns {boolean}
263
+ */
264
+ function isPatternDocumentation(filePath, content, patternId) {
265
+ const fileName = path.basename(filePath).toLowerCase();
266
+
267
+ // Pattern documentation file names
268
+ const isPatternFile =
269
+ fileName.includes('pattern') ||
270
+ fileName.includes('enhance.md') ||
271
+ fileName.includes('enhancer');
272
+
273
+ if (!isPatternFile) return false;
274
+
275
+ // Check if content documents patterns in table format
276
+ const patternIdReadable = patternId.replace(/_/g, ' ');
277
+ const describesPattern =
278
+ new RegExp(`\\|[^|]*${patternId}[^|]*\\|`, 'i').test(content) ||
279
+ new RegExp(`\\|[^|]*${patternIdReadable}[^|]*\\|`, 'i').test(content);
280
+
281
+ return describesPattern;
282
+ }
283
+
284
+ /**
285
+ * Get project identifier from git remote or directory hash
286
+ *
287
+ * @param {string} projectRoot - Project root directory
288
+ * @returns {string} Project identifier
289
+ */
290
+ function getProjectId(projectRoot = process.cwd()) {
291
+ try {
292
+ // Try to get git remote
293
+ const remote = execFileSync('git', ['remote', 'get-url', 'origin'], {
294
+ cwd: projectRoot,
295
+ encoding: 'utf8',
296
+ stdio: ['pipe', 'pipe', 'pipe']
297
+ }).trim();
298
+
299
+ if (remote) {
300
+ // Normalize remote URL to identifier
301
+ // https://github.com/user/repo.git -> github.com/user/repo
302
+ // git@github.com:user/repo.git -> github.com/user/repo
303
+ return remote
304
+ .replace(/^https?:\/\//, '')
305
+ .replace(/^git@/, '')
306
+ .replace(/\.git$/, '')
307
+ .replace(':', '/');
308
+ }
309
+ } catch {
310
+ // Git not available or not a git repo
311
+ }
312
+
313
+ // Fallback to directory name hash
314
+ const absPath = path.resolve(projectRoot);
315
+ return `local:${path.basename(absPath)}`;
316
+ }
317
+
318
+ /**
319
+ * Load auto-learned suppressions for a project
320
+ *
321
+ * @param {string} suppressionPath - Path to suppressions.json
322
+ * @param {string} projectId - Project identifier
323
+ * @returns {Object} Auto-learned suppressions { patterns: {}, stats: {} }
324
+ */
325
+ function loadAutoSuppressions(suppressionPath, projectId) {
326
+ const defaultResult = { patterns: {}, stats: { totalSuppressed: 0 } };
327
+
328
+ try {
329
+ if (!fs.existsSync(suppressionPath)) {
330
+ return defaultResult;
331
+ }
332
+
333
+ const data = JSON.parse(fs.readFileSync(suppressionPath, 'utf8'));
334
+ const projectData = data.projects?.[projectId];
335
+
336
+ if (!projectData?.auto_learned) {
337
+ return defaultResult;
338
+ }
339
+
340
+ // Prune expired suppressions
341
+ const autoLearned = projectData.auto_learned;
342
+ const now = Date.now();
343
+ const prunedPatterns = {};
344
+
345
+ for (const [patternId, suppression] of Object.entries(autoLearned.patterns || {})) {
346
+ const learnedAt = new Date(suppression.learnedAt).getTime();
347
+ if (now - learnedAt < SUPPRESSION_EXPIRY_MS) {
348
+ prunedPatterns[patternId] = suppression;
349
+ }
350
+ }
351
+
352
+ return {
353
+ patterns: prunedPatterns,
354
+ stats: autoLearned.stats || { totalSuppressed: 0 }
355
+ };
356
+ } catch {
357
+ return defaultResult;
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Save auto-learned suppressions
363
+ *
364
+ * @param {string} suppressionPath - Path to suppressions.json
365
+ * @param {string} projectId - Project identifier
366
+ * @param {Array} findings - Findings to save as suppressions
367
+ */
368
+ function saveAutoSuppressions(suppressionPath, projectId, findings) {
369
+ if (!findings || findings.length === 0) return;
370
+
371
+ // Ensure directory exists
372
+ const dir = path.dirname(suppressionPath);
373
+ if (!fs.existsSync(dir)) {
374
+ fs.mkdirSync(dir, { recursive: true });
375
+ }
376
+
377
+ // Load existing data
378
+ let data = { version: '2.0', projects: {} };
379
+ try {
380
+ if (fs.existsSync(suppressionPath)) {
381
+ data = JSON.parse(fs.readFileSync(suppressionPath, 'utf8'));
382
+ }
383
+ } catch {
384
+ // Start fresh on error
385
+ }
386
+
387
+ // Ensure structure
388
+ if (!data.projects) data.projects = {};
389
+ if (!data.projects[projectId]) data.projects[projectId] = {};
390
+ if (!data.projects[projectId].auto_learned) {
391
+ data.projects[projectId].auto_learned = {
392
+ patterns: {},
393
+ stats: { totalSuppressed: 0, lastAnalysis: null }
394
+ };
395
+ }
396
+
397
+ const autoLearned = data.projects[projectId].auto_learned;
398
+ const now = new Date().toISOString();
399
+
400
+ // Group findings by pattern
401
+ const byPattern = {};
402
+ for (const finding of findings) {
403
+ const patternId = (finding.patternId || finding.id || '').toLowerCase();
404
+ if (!patternId) continue;
405
+
406
+ if (!byPattern[patternId]) {
407
+ byPattern[patternId] = {
408
+ files: [],
409
+ reason: finding.suppressionReason || 'Auto-detected false positive',
410
+ confidence: finding.confidence || CONFIDENCE_THRESHOLD,
411
+ learnedAt: now,
412
+ occurrences: 0
413
+ };
414
+ }
415
+
416
+ if (finding.file && !byPattern[patternId].files.includes(finding.file)) {
417
+ byPattern[patternId].files.push(finding.file);
418
+ }
419
+ byPattern[patternId].occurrences++;
420
+
421
+ // Keep highest confidence
422
+ if (finding.confidence > byPattern[patternId].confidence) {
423
+ byPattern[patternId].confidence = finding.confidence;
424
+ byPattern[patternId].reason = finding.suppressionReason;
425
+ }
426
+ }
427
+
428
+ // Merge with existing patterns
429
+ for (const [patternId, newSuppression] of Object.entries(byPattern)) {
430
+ const existing = autoLearned.patterns[patternId];
431
+ if (existing) {
432
+ // Merge files
433
+ const allFiles = [...new Set([...existing.files, ...newSuppression.files])];
434
+ existing.files = allFiles.slice(0, 50); // Cap files per pattern
435
+ existing.occurrences = (existing.occurrences || 0) + newSuppression.occurrences;
436
+ existing.lastSeen = now;
437
+ // Update confidence if higher
438
+ if (newSuppression.confidence > existing.confidence) {
439
+ existing.confidence = newSuppression.confidence;
440
+ existing.reason = newSuppression.reason;
441
+ }
442
+ } else {
443
+ autoLearned.patterns[patternId] = newSuppression;
444
+ }
445
+ }
446
+
447
+ // Enforce max suppressions per project
448
+ const patternIds = Object.keys(autoLearned.patterns);
449
+ if (patternIds.length > MAX_SUPPRESSIONS_PER_PROJECT) {
450
+ // Remove oldest patterns
451
+ const sorted = patternIds.sort((a, b) => {
452
+ const aDate = new Date(autoLearned.patterns[a].learnedAt);
453
+ const bDate = new Date(autoLearned.patterns[b].learnedAt);
454
+ return aDate - bDate;
455
+ });
456
+
457
+ const toRemove = sorted.slice(0, patternIds.length - MAX_SUPPRESSIONS_PER_PROJECT);
458
+ for (const id of toRemove) {
459
+ delete autoLearned.patterns[id];
460
+ }
461
+ }
462
+
463
+ // Update stats
464
+ autoLearned.stats.totalSuppressed = Object.keys(autoLearned.patterns).length;
465
+ autoLearned.stats.lastAnalysis = now;
466
+
467
+ // Write file
468
+ fs.writeFileSync(suppressionPath, JSON.stringify(data, null, 2));
469
+ }
470
+
471
+ /**
472
+ * Clear auto-learned suppressions for a project
473
+ *
474
+ * @param {string} suppressionPath - Path to suppressions.json
475
+ * @param {string} projectId - Project identifier
476
+ */
477
+ function clearAutoSuppressions(suppressionPath, projectId) {
478
+ try {
479
+ if (!fs.existsSync(suppressionPath)) return;
480
+
481
+ const data = JSON.parse(fs.readFileSync(suppressionPath, 'utf8'));
482
+
483
+ if (data.projects?.[projectId]?.auto_learned) {
484
+ data.projects[projectId].auto_learned = {
485
+ patterns: {},
486
+ stats: { totalSuppressed: 0, lastAnalysis: new Date().toISOString() }
487
+ };
488
+ fs.writeFileSync(suppressionPath, JSON.stringify(data, null, 2));
489
+ }
490
+ } catch {
491
+ // Ignore errors
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Merge auto-learned suppressions with manual suppressions
497
+ *
498
+ * @param {Object} autoLearned - Auto-learned suppressions
499
+ * @param {Object} manual - Manual suppressions (from config)
500
+ * @returns {Object} Merged suppressions
501
+ */
502
+ function mergeSuppressions(autoLearned, manual) {
503
+ return {
504
+ ignore: {
505
+ patterns: [...(manual.ignore?.patterns || [])],
506
+ files: [...(manual.ignore?.files || [])],
507
+ rules: { ...(manual.ignore?.rules || {}) }
508
+ },
509
+ severity: { ...(manual.severity || {}) },
510
+ auto_learned: autoLearned
511
+ };
512
+ }
513
+
514
+ /**
515
+ * Export learned suppressions for team sharing
516
+ *
517
+ * @param {string} suppressionPath - Path to suppressions.json
518
+ * @param {string} projectId - Project identifier
519
+ * @returns {Object} Exportable suppression data
520
+ */
521
+ function exportAutoSuppressions(suppressionPath, projectId) {
522
+ const autoLearned = loadAutoSuppressions(suppressionPath, projectId);
523
+
524
+ return {
525
+ exportedAt: new Date().toISOString(),
526
+ projectId,
527
+ suppressions: autoLearned.patterns,
528
+ stats: autoLearned.stats
529
+ };
530
+ }
531
+
532
+ /**
533
+ * Import shared suppressions
534
+ *
535
+ * @param {string} suppressionPath - Path to suppressions.json
536
+ * @param {string} projectId - Project identifier
537
+ * @param {Object} importData - Data from exportAutoSuppressions
538
+ */
539
+ function importAutoSuppressions(suppressionPath, projectId, importData) {
540
+ if (!importData?.suppressions) return;
541
+
542
+ // Convert imported suppressions to findings format
543
+ const findings = [];
544
+ for (const [patternId, suppression] of Object.entries(importData.suppressions)) {
545
+ for (const file of suppression.files || []) {
546
+ findings.push({
547
+ patternId,
548
+ file,
549
+ suppressionReason: suppression.reason,
550
+ confidence: suppression.confidence
551
+ });
552
+ }
553
+ }
554
+
555
+ saveAutoSuppressions(suppressionPath, projectId, findings);
556
+ }
557
+
558
+ /**
559
+ * Analyze findings for potential auto-suppression
560
+ * Returns findings that should be auto-suppressed
561
+ *
562
+ * @param {Array} findings - All findings
563
+ * @param {Map<string, string>} fileContents - Map of file path to content
564
+ * @param {Object} options - Options { noLearn, projectRoot }
565
+ * @returns {Array} Findings to be auto-suppressed
566
+ */
567
+ function analyzeForAutoSuppression(findings, fileContents, options = {}) {
568
+ if (options.noLearn) return [];
569
+
570
+ const toSuppress = [];
571
+
572
+ for (const finding of findings) {
573
+ const filePath = finding.file || finding.filePath;
574
+ const content = fileContents.get(filePath);
575
+
576
+ if (!content) continue;
577
+
578
+ const fpCheck = isLikelyFalsePositive(finding, content, {
579
+ file: filePath,
580
+ projectRoot: options.projectRoot
581
+ });
582
+
583
+ if (fpCheck) {
584
+ toSuppress.push({
585
+ ...finding,
586
+ suppressed: true,
587
+ suppressionReason: fpCheck.reason,
588
+ confidence: fpCheck.confidence
589
+ });
590
+ }
591
+ }
592
+
593
+ return toSuppress;
594
+ }
595
+
596
+ module.exports = {
597
+ // Constants
598
+ CONFIDENCE_THRESHOLD,
599
+ MAX_SUPPRESSIONS_PER_PROJECT,
600
+ SUPPRESSION_EXPIRY_MS,
601
+
602
+ // Core functions
603
+ isLikelyFalsePositive,
604
+ getProjectId,
605
+
606
+ // Storage functions
607
+ loadAutoSuppressions,
608
+ saveAutoSuppressions,
609
+ clearAutoSuppressions,
610
+ mergeSuppressions,
611
+
612
+ // Import/export
613
+ exportAutoSuppressions,
614
+ importAutoSuppressions,
615
+
616
+ // Analysis helper
617
+ analyzeForAutoSuppression,
618
+
619
+ // For testing
620
+ PATTERN_HEURISTICS,
621
+ isPatternDocumentation
622
+ };