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,930 @@
1
+ /**
2
+ * Cross-File Semantic Analyzer
3
+ * Analyzes relationships between agents, skills, and workflows
4
+ *
5
+ * Cross-platform compatible: Works with Claude Code, OpenCode, and Codex
6
+ *
7
+ * @author Avi Fenesh
8
+ * @license MIT
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { parseMarkdownFrontmatter } = require('./agent-analyzer');
14
+ const { crossFilePatterns, loadKnownTools } = require('./cross-file-patterns');
15
+
16
+ // ============================================
17
+ // CONSTANTS
18
+ // ============================================
19
+
20
+ /** Minimum instruction length to consider for duplicate/contradiction detection */
21
+ const MIN_INSTRUCTION_LENGTH = 20;
22
+
23
+ /** Minimum number of files for duplicate instruction flagging */
24
+ const MIN_DUPLICATE_COUNT = 3;
25
+
26
+ /** Similarity threshold for contradiction detection (0-1) */
27
+ const CONTRADICTION_SIMILARITY_THRESHOLD = 0.6;
28
+
29
+ /** Length of action text to compare for contradictions */
30
+ const ACTION_COMPARISON_LENGTH = 30;
31
+
32
+ /** Minimum word length for similarity calculation (filters noise) */
33
+ const MIN_WORD_LENGTH = 3;
34
+
35
+ /** Default analysis categories */
36
+ const DEFAULT_ANALYSIS_CATEGORIES = ['tool-consistency', 'workflow', 'consistency', 'skill-alignment'];
37
+
38
+ // ============================================
39
+ // UTILITY FUNCTIONS
40
+ // ============================================
41
+
42
+ /**
43
+ * Escape regex special characters in a string
44
+ * @param {string} str - String to escape
45
+ * @returns {string} Escaped string safe for RegExp
46
+ */
47
+ function escapeRegex(str) {
48
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
49
+ }
50
+
51
+ /**
52
+ * Validate that a path is within the expected root directory
53
+ * Prevents path traversal attacks
54
+ * @param {string} targetPath - Path to validate
55
+ * @param {string} rootDir - Expected root directory
56
+ * @returns {boolean} True if path is safe
57
+ */
58
+ function isPathWithinRoot(targetPath, rootDir) {
59
+ const resolvedTarget = path.resolve(targetPath);
60
+ const resolvedRoot = path.resolve(rootDir);
61
+ return resolvedTarget.startsWith(resolvedRoot + path.sep) || resolvedTarget === resolvedRoot;
62
+ }
63
+
64
+ /**
65
+ * Calculate simple string similarity (Jaccard index on words)
66
+ * @param {string} a - First string
67
+ * @param {string} b - Second string
68
+ * @returns {number} Similarity score 0-1
69
+ */
70
+ function calculateSimilarity(a, b) {
71
+ if (!a || !b) return 0;
72
+
73
+ const wordsA = new Set(a.split(/\s+/).filter(w => w.length >= MIN_WORD_LENGTH));
74
+ const wordsB = new Set(b.split(/\s+/).filter(w => w.length >= MIN_WORD_LENGTH));
75
+
76
+ if (wordsA.size === 0 || wordsB.size === 0) return 0;
77
+
78
+ let intersection = 0;
79
+ for (const word of wordsA) {
80
+ if (wordsB.has(word)) intersection++;
81
+ }
82
+
83
+ const union = wordsA.size + wordsB.size - intersection;
84
+ return intersection / union;
85
+ }
86
+
87
+ // ============================================
88
+ // PRE-COMPILED PATTERNS
89
+ // ============================================
90
+
91
+ /** Pre-compiled shell command patterns for Bash detection */
92
+ const SHELL_PATTERNS = [
93
+ /\bgit\s+(?:add|commit|push|pull|branch|checkout|merge|rebase|status|diff|log)\b/i,
94
+ /\bnpm\s+(?:install|test|run|build|publish)\b/i,
95
+ /\bpnpm\s+/i,
96
+ /\byarn\s+/i,
97
+ /\bcargo\s+/i,
98
+ /\bgo\s+(?:build|test|run|mod)\b/i
99
+ ];
100
+
101
+ /** Pre-compiled critical instruction patterns */
102
+ const CRITICAL_PATTERNS = [
103
+ /\bMUST\b/,
104
+ /\bNEVER\b/,
105
+ /\bALWAYS\b/i,
106
+ /\bREQUIRED\b/i,
107
+ /\bFORBIDDEN\b/i,
108
+ /\bCRITICAL\b/i,
109
+ /\bDO NOT\b/i,
110
+ /\bdon't\b/i
111
+ ];
112
+
113
+ /** Pre-compiled pattern for extracting agent references */
114
+ const SUBAGENT_PATTERN = /subagent_type\s*[=:]\s*["']([^"']+)["']/g;
115
+
116
+ /** Pre-compiled patterns for cleaning content */
117
+ const BAD_EXAMPLE_TAG_PATTERN = /<bad[_\- ]?example>[\s\S]*?<\/bad[_\- ]?example>/gi;
118
+ const BAD_EXAMPLE_CODE_PATTERN = /```[^\n]*bad[^\n]*\n[\s\S]*?```/gi;
119
+
120
+ // ============================================
121
+ // TOOL PATTERN CACHE
122
+ // ============================================
123
+
124
+ /** Cache for compiled tool patterns */
125
+ const toolPatternCache = new Map();
126
+
127
+ /**
128
+ * Get or create compiled patterns for a tool name
129
+ * @param {string} tool - Tool name
130
+ * @returns {Object} Compiled patterns for the tool
131
+ */
132
+ function getToolPatterns(tool) {
133
+ if (toolPatternCache.has(tool)) {
134
+ return toolPatternCache.get(tool);
135
+ }
136
+
137
+ const escapedTool = escapeRegex(tool);
138
+ const patterns = {
139
+ call: new RegExp(`\\b${escapedTool}\\s*\\(`),
140
+ mention: new RegExp(`\\b(?:use|invoke|call|with)\\s+(?:the\\s+)?${escapedTool}\\b`, 'i'),
141
+ noun: new RegExp(`\\b${escapedTool}\\s+tool\\b`, 'i')
142
+ };
143
+
144
+ toolPatternCache.set(tool, patterns);
145
+ return patterns;
146
+ }
147
+
148
+ // ============================================
149
+ // EXTRACTION FUNCTIONS
150
+ // ============================================
151
+
152
+ /**
153
+ * Extract tool mentions from prompt content
154
+ * Detects tool usage patterns in prompt body
155
+ * @param {string} content - Prompt content
156
+ * @param {string[]} knownTools - List of known tool names
157
+ * @returns {string[]} Array of tool names found
158
+ */
159
+ function extractToolMentions(content, knownTools) {
160
+ if (!content || typeof content !== 'string') return [];
161
+ if (!Array.isArray(knownTools)) return [];
162
+
163
+ const found = new Set();
164
+
165
+ // Skip content inside bad-example tags and code blocks with "bad" in info string
166
+ const cleanContent = content
167
+ .replace(BAD_EXAMPLE_TAG_PATTERN, '')
168
+ .replace(BAD_EXAMPLE_CODE_PATTERN, '');
169
+
170
+ for (const tool of knownTools) {
171
+ const patterns = getToolPatterns(tool);
172
+
173
+ // Pattern 1: Tool({ or Tool(
174
+ if (patterns.call.test(cleanContent)) {
175
+ found.add(tool);
176
+ continue;
177
+ }
178
+
179
+ // Pattern 2: "use the Tool tool" or "invoke Tool"
180
+ if (patterns.mention.test(cleanContent)) {
181
+ found.add(tool);
182
+ continue;
183
+ }
184
+
185
+ // Pattern 3: Tool tool (e.g., "Read tool", "Bash tool")
186
+ if (patterns.noun.test(cleanContent)) {
187
+ found.add(tool);
188
+ }
189
+ }
190
+
191
+ // Special case: Bash detection via shell commands
192
+ if (!found.has('Bash') && !found.has('Shell')) {
193
+ for (const pattern of SHELL_PATTERNS) {
194
+ if (pattern.test(cleanContent)) {
195
+ found.add('Bash');
196
+ break;
197
+ }
198
+ }
199
+ }
200
+
201
+ return Array.from(found);
202
+ }
203
+
204
+ /**
205
+ * Extract agent references from content
206
+ * Finds subagent_type references in Task() calls
207
+ * @param {string} content - Content to scan
208
+ * @returns {string[]} Array of referenced agent names (plugin:agent format)
209
+ */
210
+ function extractAgentReferences(content) {
211
+ if (!content || typeof content !== 'string') return [];
212
+
213
+ const references = new Set();
214
+
215
+ // Use matchAll for safer iteration (no lastIndex issues)
216
+ const matches = content.matchAll(new RegExp(SUBAGENT_PATTERN.source, 'g'));
217
+ for (const match of matches) {
218
+ references.add(match[1]);
219
+ }
220
+
221
+ return Array.from(references);
222
+ }
223
+
224
+ /**
225
+ * Extract critical instructions (lines with MUST, NEVER, always, etc.)
226
+ * @param {string} content - Content to scan
227
+ * @returns {Array<{line: string, lineNumber: number}>} Critical instructions
228
+ */
229
+ function extractCriticalInstructions(content) {
230
+ if (!content || typeof content !== 'string') return [];
231
+
232
+ const instructions = [];
233
+ const lines = content.split('\n');
234
+ let inCodeBlock = false;
235
+
236
+ for (let i = 0; i < lines.length; i++) {
237
+ const line = lines[i].trim();
238
+
239
+ // Track code block state
240
+ if (line.startsWith('```')) {
241
+ inCodeBlock = !inCodeBlock;
242
+ continue;
243
+ }
244
+
245
+ // Skip empty lines, headers, and content inside code blocks
246
+ if (!line || line.startsWith('#') || inCodeBlock) continue;
247
+
248
+ for (const pattern of CRITICAL_PATTERNS) {
249
+ if (pattern.test(line)) {
250
+ instructions.push({ line, lineNumber: i + 1 });
251
+ break;
252
+ }
253
+ }
254
+ }
255
+
256
+ return instructions;
257
+ }
258
+
259
+ // ============================================
260
+ // FILE LOADING FUNCTIONS
261
+ // ============================================
262
+
263
+ /**
264
+ * Generic function to load prompt files from plugins directory
265
+ * @param {string} rootDir - Root directory
266
+ * @param {string} subDir - Subdirectory name ('agents' or 'skills')
267
+ * @param {Function} fileFilter - Function to filter files
268
+ * @param {Function} pathBuilder - Function to build file path
269
+ * @param {Object} options - Options including verbose flag
270
+ * @returns {Array<Object>} Array of parsed prompt objects
271
+ */
272
+ function loadPromptFiles(rootDir, subDir, fileFilter, pathBuilder, options = {}) {
273
+ const results = [];
274
+ const errors = [];
275
+
276
+ const pluginsDir = path.join(rootDir, 'plugins');
277
+
278
+ // Validate path is within root
279
+ if (!isPathWithinRoot(pluginsDir, rootDir)) {
280
+ if (options.verbose) {
281
+ console.error('[cross-file] Invalid plugins directory path');
282
+ }
283
+ return results;
284
+ }
285
+
286
+ // Check if plugins directory exists
287
+ try {
288
+ fs.accessSync(pluginsDir);
289
+ } catch {
290
+ return results;
291
+ }
292
+
293
+ let plugins;
294
+ try {
295
+ plugins = fs.readdirSync(pluginsDir).filter(f => {
296
+ const fullPath = path.join(pluginsDir, f);
297
+ if (!isPathWithinRoot(fullPath, rootDir)) return false;
298
+ try {
299
+ return fs.statSync(fullPath).isDirectory();
300
+ } catch {
301
+ return false;
302
+ }
303
+ });
304
+ } catch (err) {
305
+ if (options.verbose) {
306
+ console.error('[cross-file] Failed to read plugins directory:', err.message);
307
+ }
308
+ return results;
309
+ }
310
+
311
+ for (const plugin of plugins) {
312
+ const targetDir = path.join(pluginsDir, plugin, subDir);
313
+
314
+ // Validate path
315
+ if (!isPathWithinRoot(targetDir, rootDir)) continue;
316
+
317
+ // Check if directory exists
318
+ try {
319
+ fs.accessSync(targetDir);
320
+ } catch {
321
+ continue;
322
+ }
323
+
324
+ let files;
325
+ try {
326
+ files = fs.readdirSync(targetDir).filter(fileFilter);
327
+ } catch (err) {
328
+ if (options.verbose) {
329
+ errors.push({ path: targetDir, error: err.message });
330
+ }
331
+ continue;
332
+ }
333
+
334
+ for (const file of files) {
335
+ const filePath = pathBuilder(targetDir, file);
336
+
337
+ // Validate path
338
+ if (!isPathWithinRoot(filePath, rootDir)) continue;
339
+
340
+ try {
341
+ const content = fs.readFileSync(filePath, 'utf8');
342
+ const { frontmatter, body } = parseMarkdownFrontmatter(content);
343
+
344
+ results.push({
345
+ plugin,
346
+ name: file.replace('.md', '').replace(/^SKILL$/, path.basename(path.dirname(filePath))),
347
+ path: filePath,
348
+ frontmatter: frontmatter || {},
349
+ body,
350
+ content
351
+ });
352
+ } catch (err) {
353
+ if (options.verbose) {
354
+ errors.push({ path: filePath, error: err.message });
355
+ }
356
+ // Skip files that can't be read - intentional for robustness
357
+ }
358
+ }
359
+ }
360
+
361
+ if (options.verbose && errors.length > 0) {
362
+ console.error('[cross-file] File read errors:', errors);
363
+ }
364
+
365
+ return results;
366
+ }
367
+
368
+ /**
369
+ * Load all agent files from a directory structure
370
+ * @param {string} rootDir - Root directory to scan
371
+ * @param {Object} options - Options including verbose flag
372
+ * @returns {Array<Object>} Array of parsed agent objects
373
+ */
374
+ function loadAllAgents(rootDir, options = {}) {
375
+ return loadPromptFiles(
376
+ rootDir,
377
+ 'agents',
378
+ f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md',
379
+ (dir, file) => path.join(dir, file),
380
+ options
381
+ );
382
+ }
383
+
384
+ /**
385
+ * Load all command files from a directory structure
386
+ * @param {string} rootDir - Root directory to scan
387
+ * @param {Object} options - Options including verbose flag
388
+ * @returns {Array<Object>} Array of parsed command objects
389
+ */
390
+ function loadAllCommands(rootDir, options = {}) {
391
+ return loadPromptFiles(
392
+ rootDir,
393
+ 'commands',
394
+ f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md',
395
+ (dir, file) => path.join(dir, file),
396
+ options
397
+ );
398
+ }
399
+
400
+ /**
401
+ * Load all skill files from a directory structure
402
+ * @param {string} rootDir - Root directory to scan
403
+ * @param {Object} options - Options including verbose flag
404
+ * @returns {Array<Object>} Array of parsed skill objects
405
+ */
406
+ function loadAllSkills(rootDir, options = {}) {
407
+ const skills = [];
408
+ const pluginsDir = path.join(rootDir, 'plugins');
409
+
410
+ // Validate path
411
+ if (!isPathWithinRoot(pluginsDir, rootDir)) {
412
+ return skills;
413
+ }
414
+
415
+ try {
416
+ fs.accessSync(pluginsDir);
417
+ } catch {
418
+ return skills;
419
+ }
420
+
421
+ let plugins;
422
+ try {
423
+ plugins = fs.readdirSync(pluginsDir).filter(f => {
424
+ const fullPath = path.join(pluginsDir, f);
425
+ if (!isPathWithinRoot(fullPath, rootDir)) return false;
426
+ try {
427
+ return fs.statSync(fullPath).isDirectory();
428
+ } catch {
429
+ return false;
430
+ }
431
+ });
432
+ } catch (err) {
433
+ if (options.verbose) {
434
+ console.error('[cross-file] Failed to read plugins directory:', err.message);
435
+ }
436
+ return skills;
437
+ }
438
+
439
+ for (const plugin of plugins) {
440
+ const skillsDir = path.join(pluginsDir, plugin, 'skills');
441
+
442
+ if (!isPathWithinRoot(skillsDir, rootDir)) continue;
443
+
444
+ try {
445
+ fs.accessSync(skillsDir);
446
+ } catch {
447
+ continue;
448
+ }
449
+
450
+ let skillDirs;
451
+ try {
452
+ skillDirs = fs.readdirSync(skillsDir).filter(f => {
453
+ const fullPath = path.join(skillsDir, f);
454
+ if (!isPathWithinRoot(fullPath, rootDir)) return false;
455
+ try {
456
+ return fs.statSync(fullPath).isDirectory();
457
+ } catch {
458
+ return false;
459
+ }
460
+ });
461
+ } catch (err) {
462
+ if (options.verbose) {
463
+ console.error('[cross-file] Failed to read skills directory:', skillsDir, err.message);
464
+ }
465
+ continue;
466
+ }
467
+
468
+ for (const skillDir of skillDirs) {
469
+ const skillPath = path.join(skillsDir, skillDir, 'SKILL.md');
470
+
471
+ if (!isPathWithinRoot(skillPath, rootDir)) continue;
472
+
473
+ try {
474
+ fs.accessSync(skillPath);
475
+ const content = fs.readFileSync(skillPath, 'utf8');
476
+ const { frontmatter, body } = parseMarkdownFrontmatter(content);
477
+
478
+ skills.push({
479
+ plugin,
480
+ name: skillDir,
481
+ path: skillPath,
482
+ frontmatter: frontmatter || {},
483
+ body,
484
+ content
485
+ });
486
+ } catch (err) {
487
+ if (options.verbose) {
488
+ console.error('[cross-file] Failed to read skill:', skillPath, err.message);
489
+ }
490
+ // Skip files that can't be read - intentional for robustness
491
+ }
492
+ }
493
+ }
494
+
495
+ return skills;
496
+ }
497
+
498
+ // ============================================
499
+ // ANALYSIS FUNCTIONS
500
+ // ============================================
501
+
502
+ /**
503
+ * Analyze tool consistency between frontmatter and body
504
+ * @param {Array<Object>} agents - Parsed agents
505
+ * @param {string[]} knownTools - Known tool names
506
+ * @returns {Array<Object>} Findings
507
+ */
508
+ function analyzeToolConsistency(agents, knownTools) {
509
+ const findings = [];
510
+ const pattern = crossFilePatterns.tool_not_in_allowed_list;
511
+
512
+ for (const agent of agents) {
513
+ const { frontmatter, body, name, path: agentPath } = agent;
514
+
515
+ // Get declared tools from frontmatter
516
+ let declaredTools = [];
517
+ if (frontmatter && frontmatter.tools) {
518
+ declaredTools = Array.isArray(frontmatter.tools)
519
+ ? frontmatter.tools
520
+ : frontmatter.tools.split(',').map(t => t.trim());
521
+ }
522
+
523
+ // Skip if no tools restriction (all tools allowed)
524
+ if (declaredTools.length === 0) continue;
525
+
526
+ // Extract used tools from body
527
+ const usedTools = extractToolMentions(body, knownTools);
528
+
529
+ const result = pattern.check({
530
+ declaredTools,
531
+ usedTools,
532
+ agentName: name
533
+ });
534
+
535
+ if (result) {
536
+ findings.push({
537
+ ...result,
538
+ file: agentPath,
539
+ certainty: pattern.certainty,
540
+ patternId: pattern.id,
541
+ source: 'cross-file'
542
+ });
543
+ }
544
+ }
545
+
546
+ return findings;
547
+ }
548
+
549
+ /**
550
+ * Analyze workflow completeness (referenced agents exist)
551
+ * @param {Array<Object>} agents - Parsed agents
552
+ * @returns {Array<Object>} Findings
553
+ */
554
+ function analyzeWorkflowCompleteness(agents) {
555
+ const findings = [];
556
+ const pattern = crossFilePatterns.missing_workflow_agent;
557
+
558
+ // Build list of existing agents
559
+ const existingAgents = agents.map(a => ({
560
+ plugin: a.plugin,
561
+ name: a.name
562
+ }));
563
+
564
+ // Check each agent for references to other agents
565
+ for (const agent of agents) {
566
+ const { body, path: agentPath } = agent;
567
+ const references = extractAgentReferences(body);
568
+
569
+ for (const ref of references) {
570
+ const result = pattern.check({
571
+ referencedAgent: ref,
572
+ existingAgents,
573
+ sourceFile: path.basename(agentPath)
574
+ });
575
+
576
+ if (result) {
577
+ findings.push({
578
+ ...result,
579
+ file: agentPath,
580
+ certainty: pattern.certainty,
581
+ patternId: pattern.id,
582
+ source: 'cross-file'
583
+ });
584
+ }
585
+ }
586
+ }
587
+
588
+ return findings;
589
+ }
590
+
591
+ /**
592
+ * Analyze prompt consistency (duplicates, contradictions)
593
+ * Uses optimized O(n) contradiction detection via keyword indexing
594
+ * @param {Array<Object>} agents - Parsed agents
595
+ * @returns {Array<Object>} Findings
596
+ */
597
+ function analyzePromptConsistency(agents) {
598
+ const findings = [];
599
+
600
+ // Collect all critical instructions with their sources
601
+ const instructionMap = new Map(); // normalized instruction -> [files]
602
+
603
+ for (const agent of agents) {
604
+ const instructions = extractCriticalInstructions(agent.body);
605
+
606
+ for (const { line } of instructions) {
607
+ const normalized = line.toLowerCase().trim();
608
+ if (normalized.length < MIN_INSTRUCTION_LENGTH) continue;
609
+
610
+ if (!instructionMap.has(normalized)) {
611
+ instructionMap.set(normalized, []);
612
+ }
613
+ instructionMap.get(normalized).push(agent.path);
614
+ }
615
+ }
616
+
617
+ // Find duplicates
618
+ const duplicatePattern = crossFilePatterns.duplicate_instructions;
619
+ for (const [instruction, files] of instructionMap.entries()) {
620
+ if (files.length >= MIN_DUPLICATE_COUNT) {
621
+ const result = duplicatePattern.check({ instruction, files });
622
+ if (result) {
623
+ findings.push({
624
+ ...result,
625
+ file: files[0],
626
+ certainty: duplicatePattern.certainty,
627
+ patternId: duplicatePattern.id,
628
+ source: 'cross-file'
629
+ });
630
+ }
631
+ }
632
+ }
633
+
634
+ // Find contradictions using keyword indexing (O(n) instead of O(n^2))
635
+ const contradictionPattern = crossFilePatterns.contradictory_rules;
636
+
637
+ // Index rules by keywords for efficient lookup
638
+ const alwaysRulesByKeyword = new Map(); // keyword -> [{rule, file}]
639
+ const neverRulesByKeyword = new Map();
640
+
641
+ for (const agent of agents) {
642
+ const instructions = extractCriticalInstructions(agent.body);
643
+
644
+ for (const { line } of instructions) {
645
+ const isAlways = /\bALWAYS\b/i.test(line);
646
+ const isNever = /\bNEVER\b/i.test(line) || /\bDO NOT\b/i.test(line);
647
+
648
+ if (!isAlways && !isNever) continue;
649
+
650
+ // Extract action keywords
651
+ let action;
652
+ if (isAlways) {
653
+ action = line.replace(/.*\bALWAYS\b\s*/i, '').substring(0, ACTION_COMPARISON_LENGTH);
654
+ } else {
655
+ action = line.replace(/.*\b(?:NEVER|DO NOT)\b\s*/i, '').substring(0, ACTION_COMPARISON_LENGTH);
656
+ }
657
+
658
+ // Extract significant keywords from action
659
+ const keywords = action.toLowerCase().split(/\s+/).filter(w => w.length >= MIN_WORD_LENGTH);
660
+
661
+ const ruleData = { line, file: agent.path, action: action.toLowerCase() };
662
+ const targetMap = isAlways ? alwaysRulesByKeyword : neverRulesByKeyword;
663
+
664
+ for (const keyword of keywords) {
665
+ if (!targetMap.has(keyword)) {
666
+ targetMap.set(keyword, []);
667
+ }
668
+ targetMap.get(keyword).push(ruleData);
669
+ }
670
+ }
671
+ }
672
+
673
+ // Find contradictions by checking overlapping keywords
674
+ const checkedPairs = new Set();
675
+
676
+ for (const [keyword, alwaysRules] of alwaysRulesByKeyword.entries()) {
677
+ const neverRules = neverRulesByKeyword.get(keyword);
678
+ if (!neverRules) continue;
679
+
680
+ for (const always of alwaysRules) {
681
+ for (const never of neverRules) {
682
+ // Skip same file
683
+ if (always.file === never.file) continue;
684
+
685
+ // Skip already checked pairs
686
+ const pairKey = `${always.line}|${never.line}`;
687
+ if (checkedPairs.has(pairKey)) continue;
688
+ checkedPairs.add(pairKey);
689
+
690
+ // Check similarity
691
+ const similarity = calculateSimilarity(always.action, never.action);
692
+ if (similarity > CONTRADICTION_SIMILARITY_THRESHOLD) {
693
+ const result = contradictionPattern.check({
694
+ rule1: always.line,
695
+ rule2: never.line,
696
+ file1: path.basename(always.file),
697
+ file2: path.basename(never.file)
698
+ });
699
+
700
+ if (result) {
701
+ findings.push({
702
+ ...result,
703
+ file: always.file,
704
+ certainty: contradictionPattern.certainty,
705
+ patternId: contradictionPattern.id,
706
+ source: 'cross-file'
707
+ });
708
+ }
709
+ }
710
+ }
711
+ }
712
+ }
713
+
714
+ return findings;
715
+ }
716
+
717
+ /**
718
+ * Analyze skill-agent alignment
719
+ * @param {Array<Object>} skills - Parsed skills
720
+ * @param {string[]} knownTools - Known tool names
721
+ * @returns {Array<Object>} Findings
722
+ */
723
+ function analyzeSkillAlignment(skills, knownTools) {
724
+ const findings = [];
725
+ const pattern = crossFilePatterns.skill_tool_mismatch;
726
+
727
+ for (const skill of skills) {
728
+ const { frontmatter, body, name, path: skillPath } = skill;
729
+
730
+ // Get allowed-tools from frontmatter (supports multiple field names)
731
+ let allowedTools = [];
732
+ const toolsField = frontmatter['allowed-tools'] || frontmatter.allowedTools || frontmatter.tools;
733
+ if (toolsField) {
734
+ allowedTools = Array.isArray(toolsField)
735
+ ? toolsField
736
+ : toolsField.split(',').map(t => t.trim());
737
+ }
738
+
739
+ // Skip if no tools restriction
740
+ if (allowedTools.length === 0) continue;
741
+
742
+ // Extract used tools from body
743
+ const usedTools = extractToolMentions(body, knownTools);
744
+
745
+ const result = pattern.check({
746
+ skillName: name,
747
+ skillAllowedTools: allowedTools,
748
+ promptUsedTools: usedTools
749
+ });
750
+
751
+ if (result) {
752
+ findings.push({
753
+ ...result,
754
+ file: skillPath,
755
+ certainty: pattern.certainty,
756
+ patternId: pattern.id,
757
+ source: 'cross-file'
758
+ });
759
+ }
760
+ }
761
+
762
+ return findings;
763
+ }
764
+
765
+ /**
766
+ * Find orphaned agents (not referenced anywhere)
767
+ * @param {Array<Object>} agents - Parsed agents
768
+ * @param {Array<Object>} skills - Parsed skills
769
+ * @param {Array<Object>} commands - Parsed commands
770
+ * @returns {Array<Object>} Findings
771
+ */
772
+ function analyzeOrphanedPrompts(agents, skills, commands = []) {
773
+ const findings = [];
774
+ const pattern = crossFilePatterns.orphaned_prompt;
775
+
776
+ // Collect all agent references
777
+ const allReferences = new Set();
778
+
779
+ // From agents
780
+ for (const agent of agents) {
781
+ const refs = extractAgentReferences(agent.body);
782
+ refs.forEach(r => allReferences.add(r));
783
+ }
784
+
785
+ // From skills
786
+ for (const skill of skills) {
787
+ const refs = extractAgentReferences(skill.body);
788
+ refs.forEach(r => allReferences.add(r));
789
+ }
790
+
791
+ // From commands (addresses false positives for command-invoked agents)
792
+ for (const command of commands) {
793
+ const refs = extractAgentReferences(command.body);
794
+ refs.forEach(r => allReferences.add(r));
795
+ }
796
+
797
+ // Check each agent
798
+ for (const agent of agents) {
799
+ const fullName = `${agent.plugin}:${agent.name}`;
800
+ const shortName = agent.name;
801
+
802
+ // Check if referenced
803
+ const isReferenced = allReferences.has(fullName) || allReferences.has(shortName);
804
+
805
+ // Entry point agents are typically called by commands, not other agents
806
+ const isEntryPoint = /orchestrator|discoverer|validator|monitor|fixer|checker|reporter|manager/i.test(agent.name);
807
+
808
+ if (!isReferenced && !isEntryPoint) {
809
+ const result = pattern.check({
810
+ promptFile: path.basename(agent.path),
811
+ referencedBy: []
812
+ });
813
+
814
+ if (result) {
815
+ findings.push({
816
+ ...result,
817
+ file: agent.path,
818
+ certainty: pattern.certainty,
819
+ patternId: pattern.id,
820
+ source: 'cross-file'
821
+ });
822
+ }
823
+ }
824
+ }
825
+
826
+ return findings;
827
+ }
828
+
829
+ // ============================================
830
+ // MAIN ANALYSIS FUNCTION
831
+ // ============================================
832
+
833
+ /**
834
+ * Main cross-file analysis function
835
+ * @param {string} rootDir - Root directory to analyze
836
+ * @param {Object} options - Analysis options
837
+ * @param {boolean} options.verbose - Include all findings and log errors
838
+ * @param {string[]} options.categories - Specific categories to check
839
+ * @returns {Object} Analysis results
840
+ */
841
+ function analyze(rootDir, options = {}) {
842
+ const {
843
+ verbose = false,
844
+ categories = DEFAULT_ANALYSIS_CATEGORIES
845
+ } = options;
846
+
847
+ const results = {
848
+ rootDir,
849
+ findings: [],
850
+ summary: {
851
+ agentsAnalyzed: 0,
852
+ skillsAnalyzed: 0,
853
+ totalFindings: 0,
854
+ byCategory: {}
855
+ }
856
+ };
857
+
858
+ // Load known tools (config file or platform defaults)
859
+ const knownTools = loadKnownTools(rootDir);
860
+
861
+ // Load all agents, skills, and commands
862
+ const agents = loadAllAgents(rootDir, { verbose });
863
+ const skills = loadAllSkills(rootDir, { verbose });
864
+ const commands = loadAllCommands(rootDir, { verbose });
865
+
866
+ results.summary.agentsAnalyzed = agents.length;
867
+ results.summary.skillsAnalyzed = skills.length;
868
+ results.summary.commandsAnalyzed = commands.length;
869
+
870
+ // Run analysis for each category
871
+ if (categories.includes('tool-consistency')) {
872
+ const toolFindings = analyzeToolConsistency(agents, knownTools);
873
+ results.findings.push(...toolFindings);
874
+ results.summary.byCategory['tool-consistency'] = toolFindings.length;
875
+ }
876
+
877
+ if (categories.includes('workflow')) {
878
+ const workflowFindings = analyzeWorkflowCompleteness(agents);
879
+ results.findings.push(...workflowFindings);
880
+ results.summary.byCategory['workflow'] = workflowFindings.length;
881
+ }
882
+
883
+ if (categories.includes('consistency')) {
884
+ const consistencyFindings = analyzePromptConsistency(agents);
885
+ const orphanFindings = analyzeOrphanedPrompts(agents, skills, commands);
886
+ results.findings.push(...consistencyFindings, ...orphanFindings);
887
+ results.summary.byCategory['consistency'] = consistencyFindings.length + orphanFindings.length;
888
+ }
889
+
890
+ if (categories.includes('skill-alignment')) {
891
+ const skillFindings = analyzeSkillAlignment(skills, knownTools);
892
+ results.findings.push(...skillFindings);
893
+ results.summary.byCategory['skill-alignment'] = skillFindings.length;
894
+ }
895
+
896
+ results.summary.totalFindings = results.findings.length;
897
+
898
+ return results;
899
+ }
900
+
901
+ // ============================================
902
+ // EXPORTS
903
+ // ============================================
904
+
905
+ module.exports = {
906
+ // Extraction functions
907
+ extractToolMentions,
908
+ extractAgentReferences,
909
+ extractCriticalInstructions,
910
+
911
+ // Loading functions
912
+ loadAllAgents,
913
+ loadAllSkills,
914
+ loadAllCommands,
915
+
916
+ // Analysis functions
917
+ analyzeToolConsistency,
918
+ analyzeWorkflowCompleteness,
919
+ analyzePromptConsistency,
920
+ analyzeSkillAlignment,
921
+ analyzeOrphanedPrompts,
922
+
923
+ // Main entry point
924
+ analyze,
925
+
926
+ // Utilities (exported for testing)
927
+ calculateSimilarity,
928
+ escapeRegex,
929
+ isPathWithinRoot
930
+ };