agentsys 5.0.2 → 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 +24 -1
  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 +133 -59
  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 +123 -31
  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 +134 -59
  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 +41 -27
  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 +123 -30
  112. package/plugins/consult/commands/consult.md +136 -60
  113. package/plugins/consult/skills/consult/SKILL.md +39 -24
  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,611 @@
1
+ /**
2
+ * CLI Enhancers for Slop Detection Pipeline
3
+ *
4
+ * Optional CLI tool integration for Phase 2 detection.
5
+ * All tools are user-installed globally - zero npm dependencies for this module.
6
+ * Functions gracefully degrade when tools are not available.
7
+ *
8
+ * Supported languages: javascript, typescript, python, rust, go
9
+ *
10
+ * @module patterns/cli-enhancers
11
+ * @author Avi Fenesh
12
+ * @license MIT
13
+ */
14
+
15
+ const { execSync, execFileSync } = require('child_process');
16
+ const path = require('path');
17
+ const fs = require('fs');
18
+ // Note: escapeDoubleQuotes no longer needed - using execFileSync with arg arrays
19
+
20
+ /**
21
+ * Cache for tool availability (per-repo)
22
+ * Key: repoPath, Value: { tools: {...}, languages: [...], timestamp: Date }
23
+ */
24
+ const toolCache = new Map();
25
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
26
+
27
+ /**
28
+ * Supported languages (must match slop-patterns.js)
29
+ */
30
+ const SUPPORTED_LANGUAGES = ['javascript', 'typescript', 'python', 'rust', 'go'];
31
+
32
+ /**
33
+ * CLI tool definitions organized by language
34
+ * Only includes tools for supported languages
35
+ */
36
+ const CLI_TOOLS = {
37
+ // Cross-language tools
38
+ jscpd: {
39
+ name: 'jscpd',
40
+ description: 'Copy/paste detector for code duplication',
41
+ checkCommand: 'jscpd --version',
42
+ installHint: 'npm install -g jscpd',
43
+ languages: ['javascript', 'typescript', 'python', 'go', 'rust']
44
+ },
45
+
46
+ // JavaScript/TypeScript tools
47
+ madge: {
48
+ name: 'madge',
49
+ description: 'Circular dependency detector',
50
+ checkCommand: 'madge --version',
51
+ installHint: 'npm install -g madge',
52
+ languages: ['javascript', 'typescript']
53
+ },
54
+ escomplex: {
55
+ name: 'escomplex',
56
+ description: 'Cyclomatic complexity analyzer',
57
+ checkCommand: 'escomplex --version',
58
+ installHint: 'npm install -g escomplex',
59
+ languages: ['javascript']
60
+ },
61
+
62
+ // Python tools
63
+ pylint: {
64
+ name: 'pylint',
65
+ description: 'Python linter with complexity analysis',
66
+ checkCommand: 'pylint --version',
67
+ installHint: 'pip install pylint',
68
+ languages: ['python']
69
+ },
70
+ radon: {
71
+ name: 'radon',
72
+ description: 'Python complexity and maintainability metrics',
73
+ checkCommand: 'radon --version',
74
+ installHint: 'pip install radon',
75
+ languages: ['python']
76
+ },
77
+
78
+ // Go tools
79
+ golangci_lint: {
80
+ name: 'golangci-lint',
81
+ description: 'Go linters aggregator with complexity checks',
82
+ checkCommand: 'golangci-lint --version',
83
+ installHint: 'go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest',
84
+ languages: ['go']
85
+ },
86
+
87
+ // Rust tools
88
+ clippy: {
89
+ name: 'cargo-clippy',
90
+ description: 'Rust linter with code smell detection',
91
+ checkCommand: 'cargo clippy --version',
92
+ installHint: 'rustup component add clippy',
93
+ languages: ['rust']
94
+ }
95
+ };
96
+
97
+ /**
98
+ * Check if a CLI tool is available in PATH
99
+ * Uses execFileSync to avoid shell injection risks
100
+ *
101
+ * @param {string} command - Command to check (e.g., 'jscpd --version')
102
+ * @returns {boolean} True if tool is available
103
+ */
104
+ function isToolAvailable(command) {
105
+ try {
106
+ // Parse command into executable and args for safer execution
107
+ const parts = command.split(/\s+/);
108
+ const executable = parts[0];
109
+ const args = parts.slice(1);
110
+ execFileSync(executable, args, {
111
+ stdio: 'pipe',
112
+ timeout: 5000,
113
+ windowsHide: true
114
+ });
115
+ return true;
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Get cache key for a repo
123
+ * @param {string} repoPath - Repository root path
124
+ * @returns {string} Cache key
125
+ */
126
+ function getCacheKey(repoPath) {
127
+ return path.resolve(repoPath);
128
+ }
129
+
130
+ /**
131
+ * Check if cache is valid
132
+ * @param {Object} cacheEntry - Cache entry
133
+ * @returns {boolean} True if cache is still valid
134
+ */
135
+ function isCacheValid(cacheEntry) {
136
+ if (!cacheEntry) return false;
137
+ return Date.now() - cacheEntry.timestamp < CACHE_TTL_MS;
138
+ }
139
+
140
+ /**
141
+ * Clear the tool cache (useful for testing)
142
+ */
143
+ function clearCache() {
144
+ toolCache.clear();
145
+ }
146
+
147
+ /**
148
+ * Detect primary language(s) of a repository based on file extensions and config files
149
+ *
150
+ * @param {string} repoPath - Repository root path
151
+ * @returns {string[]} Array of detected languages (only supported ones)
152
+ */
153
+ function detectProjectLanguages(repoPath) {
154
+ const languages = new Set();
155
+
156
+ // Check for language-specific config files
157
+ const configIndicators = {
158
+ 'package.json': ['javascript', 'typescript'],
159
+ 'tsconfig.json': ['typescript'],
160
+ 'requirements.txt': ['python'],
161
+ 'setup.py': ['python'],
162
+ 'pyproject.toml': ['python'],
163
+ 'Pipfile': ['python'],
164
+ 'go.mod': ['go'],
165
+ 'go.sum': ['go'],
166
+ 'Cargo.toml': ['rust']
167
+ };
168
+
169
+ for (const [file, langs] of Object.entries(configIndicators)) {
170
+ if (fs.existsSync(path.join(repoPath, file))) {
171
+ langs.forEach(l => languages.add(l));
172
+ }
173
+ }
174
+
175
+ // If no config files found, scan for source files
176
+ if (languages.size === 0) {
177
+ const extensionMap = {
178
+ '.js': 'javascript',
179
+ '.jsx': 'javascript',
180
+ '.mjs': 'javascript',
181
+ '.cjs': 'javascript',
182
+ '.ts': 'typescript',
183
+ '.tsx': 'typescript',
184
+ '.py': 'python',
185
+ '.go': 'go',
186
+ '.rs': 'rust'
187
+ };
188
+
189
+ // Quick scan of top-level and src/ directories
190
+ const dirsToScan = [repoPath, path.join(repoPath, 'src'), path.join(repoPath, 'lib')];
191
+
192
+ for (const dir of dirsToScan) {
193
+ if (!fs.existsSync(dir)) continue;
194
+ try {
195
+ const files = fs.readdirSync(dir);
196
+ for (const file of files) {
197
+ const ext = path.extname(file).toLowerCase();
198
+ if (extensionMap[ext]) {
199
+ languages.add(extensionMap[ext]);
200
+ }
201
+ }
202
+ } catch {
203
+ // Directory not readable
204
+ }
205
+ }
206
+ }
207
+
208
+ // Filter to only supported languages
209
+ const result = Array.from(languages).filter(l => SUPPORTED_LANGUAGES.includes(l));
210
+
211
+ // Default to javascript if nothing detected
212
+ if (result.length === 0) {
213
+ result.push('javascript');
214
+ }
215
+
216
+ return result;
217
+ }
218
+
219
+ /**
220
+ * Get tools relevant for specific languages
221
+ *
222
+ * @param {string[]} languages - Array of language names
223
+ * @returns {Object} Filtered CLI_TOOLS for the specified languages
224
+ */
225
+ function getToolsForLanguages(languages) {
226
+ const relevant = {};
227
+
228
+ for (const [toolName, tool] of Object.entries(CLI_TOOLS)) {
229
+ if (tool.languages.some(lang => languages.includes(lang))) {
230
+ relevant[toolName] = tool;
231
+ }
232
+ }
233
+
234
+ return relevant;
235
+ }
236
+
237
+ /**
238
+ * Detect which CLI tools are available on the system
239
+ * Uses cache when available
240
+ *
241
+ * @param {string[]} [languages] - Optional languages to filter tools for
242
+ * @param {string} [repoPath] - Optional repo path for caching
243
+ * @returns {Object} Object with tool names as keys and availability as boolean values
244
+ */
245
+ function detectAvailableTools(languages = null, repoPath = null) {
246
+ // Check cache if repoPath provided
247
+ if (repoPath) {
248
+ const cacheKey = getCacheKey(repoPath);
249
+ const cached = toolCache.get(cacheKey);
250
+ if (isCacheValid(cached)) {
251
+ // Return cached tools filtered by languages if specified
252
+ if (languages) {
253
+ const relevantTools = getToolsForLanguages(languages);
254
+ const filtered = {};
255
+ for (const name of Object.keys(relevantTools)) {
256
+ filtered[name] = cached.tools[name] || false;
257
+ }
258
+ return filtered;
259
+ }
260
+ return { ...cached.tools };
261
+ }
262
+ }
263
+
264
+ // Get tools to check
265
+ const toolsToCheck = languages ? getToolsForLanguages(languages) : CLI_TOOLS;
266
+ const result = {};
267
+
268
+ for (const [toolName, tool] of Object.entries(toolsToCheck)) {
269
+ result[toolName] = isToolAvailable(tool.checkCommand);
270
+ }
271
+
272
+ // Update cache if repoPath provided
273
+ if (repoPath) {
274
+ const cacheKey = getCacheKey(repoPath);
275
+ const existing = toolCache.get(cacheKey) || {};
276
+ toolCache.set(cacheKey, {
277
+ tools: { ...existing.tools, ...result },
278
+ languages: languages || existing.languages || [],
279
+ timestamp: Date.now()
280
+ });
281
+ }
282
+
283
+ return result;
284
+ }
285
+
286
+ /**
287
+ * Get tool availability for a specific repo (with caching)
288
+ *
289
+ * @param {string} repoPath - Repository root path
290
+ * @param {Object} [options] - Options
291
+ * @param {boolean} [options.forceRefresh=false] - Force cache refresh
292
+ * @returns {{ available: Object, missing: string[], languages: string[] }} Tool availability info
293
+ */
294
+ function getToolAvailabilityForRepo(repoPath, options = {}) {
295
+ const cacheKey = getCacheKey(repoPath);
296
+
297
+ // Check cache unless force refresh
298
+ if (!options.forceRefresh) {
299
+ const cached = toolCache.get(cacheKey);
300
+ if (isCacheValid(cached) && cached.languages && cached.languages.length > 0) {
301
+ const relevantTools = getToolsForLanguages(cached.languages);
302
+ const missing = Object.keys(relevantTools).filter(t => !cached.tools[t]);
303
+ return {
304
+ available: { ...cached.tools },
305
+ missing,
306
+ languages: [...cached.languages]
307
+ };
308
+ }
309
+ }
310
+
311
+ // Detect languages
312
+ const languages = detectProjectLanguages(repoPath);
313
+
314
+ // Detect tools for those languages
315
+ const available = detectAvailableTools(languages, repoPath);
316
+
317
+ // Find missing tools
318
+ const relevantTools = getToolsForLanguages(languages);
319
+ const missing = Object.keys(relevantTools).filter(t => !available[t]);
320
+
321
+ // Update cache
322
+ toolCache.set(cacheKey, {
323
+ tools: available,
324
+ languages,
325
+ timestamp: Date.now()
326
+ });
327
+
328
+ return { available, missing, languages };
329
+ }
330
+
331
+ /**
332
+ * Run duplicate code detection using jscpd
333
+ *
334
+ * @param {string} repoPath - Repository root path
335
+ * @param {Object} options - Options
336
+ * @param {number} [options.minLines=5] - Minimum lines for duplicate detection
337
+ * @param {number} [options.minTokens=50] - Minimum tokens for duplicate detection
338
+ * @returns {Array|null} Duplicates found, or null if tool not available
339
+ */
340
+ function runDuplicateDetection(repoPath, options = {}) {
341
+ if (!isToolAvailable(CLI_TOOLS.jscpd.checkCommand)) {
342
+ return null;
343
+ }
344
+
345
+ const minLines = options.minLines || 5;
346
+ const minTokens = options.minTokens || 50;
347
+
348
+ try {
349
+ // Run jscpd with JSON output
350
+ // Use execFileSync with arg array to prevent command injection (no shell interpretation)
351
+ const outputPath = process.platform === 'win32' ? 'NUL' : '/dev/null';
352
+ const args = [
353
+ repoPath,
354
+ '--min-lines', String(minLines),
355
+ '--min-tokens', String(minTokens),
356
+ '--reporters', 'json',
357
+ '--output', outputPath,
358
+ '--silent'
359
+ ];
360
+
361
+ const result = execFileSync('jscpd', args, {
362
+ stdio: ['pipe', 'pipe', 'pipe'], // stdin, stdout, stderr all piped
363
+ timeout: 60000,
364
+ windowsHide: true,
365
+ cwd: repoPath,
366
+ encoding: 'utf8'
367
+ });
368
+
369
+ // Parse JSON output
370
+ try {
371
+ const report = JSON.parse(result);
372
+ const duplicates = [];
373
+
374
+ if (report.duplicates) {
375
+ for (const dup of report.duplicates) {
376
+ duplicates.push({
377
+ firstFile: dup.firstFile?.name || 'unknown',
378
+ firstLine: dup.firstFile?.start || 0,
379
+ secondFile: dup.secondFile?.name || 'unknown',
380
+ secondLine: dup.secondFile?.start || 0,
381
+ lines: dup.lines || 0,
382
+ tokens: dup.tokens || 0,
383
+ fragment: dup.fragment?.substring(0, 100) || ''
384
+ });
385
+ }
386
+ }
387
+
388
+ return duplicates;
389
+ } catch {
390
+ // JSON parsing failed, return empty array
391
+ return [];
392
+ }
393
+ } catch {
394
+ // Tool execution failed
395
+ return null;
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Run circular dependency detection using madge
401
+ *
402
+ * @param {string} repoPath - Repository root path
403
+ * @param {Object} options - Options
404
+ * @param {string} [options.entry] - Entry file (defaults to src/index.js or index.js)
405
+ * @returns {Array|null} Circular dependency cycles, or null if tool not available
406
+ */
407
+ function runDependencyAnalysis(repoPath, options = {}) {
408
+ if (!isToolAvailable(CLI_TOOLS.madge.checkCommand)) {
409
+ return null;
410
+ }
411
+
412
+ // Determine entry point
413
+ let entry = options.entry;
414
+ if (!entry) {
415
+ const possibleEntries = [
416
+ 'src/index.js',
417
+ 'src/index.ts',
418
+ 'index.js',
419
+ 'index.ts',
420
+ 'lib/index.js',
421
+ 'main.js'
422
+ ];
423
+
424
+ for (const e of possibleEntries) {
425
+ if (fs.existsSync(path.join(repoPath, e))) {
426
+ entry = e;
427
+ break;
428
+ }
429
+ }
430
+ }
431
+
432
+ if (!entry) {
433
+ // No entry point found, scan entire directory
434
+ entry = '.';
435
+ }
436
+
437
+ try {
438
+ // Run madge with circular flag and JSON output
439
+ // Use execFileSync with arg array to prevent command injection (no shell interpretation)
440
+ const args = ['--circular', '--json', entry];
441
+
442
+ const result = execFileSync('madge', args, {
443
+ stdio: ['pipe', 'pipe', 'pipe'],
444
+ timeout: 60000,
445
+ windowsHide: true,
446
+ cwd: repoPath,
447
+ encoding: 'utf8'
448
+ });
449
+
450
+ // Parse JSON output
451
+ try {
452
+ const cycles = JSON.parse(result);
453
+ // madge returns array of arrays (each cycle is an array of file paths)
454
+ return Array.isArray(cycles) ? cycles : [];
455
+ } catch {
456
+ return [];
457
+ }
458
+ } catch {
459
+ // Tool execution failed
460
+ return null;
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Run complexity analysis using escomplex
466
+ *
467
+ * @param {string} repoPath - Repository root path
468
+ * @param {string[]} targetFiles - Files to analyze
469
+ * @param {Object} options - Options
470
+ * @returns {Array|null} Complexity results, or null if tool not available
471
+ */
472
+ function runComplexityAnalysis(repoPath, targetFiles, options = {}) {
473
+ if (!isToolAvailable(CLI_TOOLS.escomplex.checkCommand)) {
474
+ return null;
475
+ }
476
+
477
+ const results = [];
478
+
479
+ // escomplex works on individual files
480
+ for (const file of targetFiles) {
481
+ // Only analyze JS/TS files
482
+ if (!file.match(/\.[jt]sx?$/)) continue;
483
+
484
+ const filePath = path.isAbsolute(file) ? file : path.join(repoPath, file);
485
+
486
+ try {
487
+ // Use execFileSync with arg array to prevent command injection (no shell interpretation)
488
+ const args = [filePath, '--format', 'json'];
489
+
490
+ const result = execFileSync('escomplex', args, {
491
+ stdio: ['pipe', 'pipe', 'pipe'],
492
+ timeout: 30000,
493
+ windowsHide: true,
494
+ cwd: repoPath,
495
+ encoding: 'utf8'
496
+ });
497
+
498
+ try {
499
+ const report = JSON.parse(result);
500
+
501
+ // Extract function-level complexity
502
+ if (report.functions) {
503
+ for (const fn of report.functions) {
504
+ results.push({
505
+ file,
506
+ name: fn.name || 'anonymous',
507
+ line: fn.line || 0,
508
+ complexity: fn.cyclomatic || 0,
509
+ halstead: fn.halstead?.difficulty || 0,
510
+ sloc: fn.sloc?.logical || 0
511
+ });
512
+ }
513
+ }
514
+
515
+ // Also include module-level metrics
516
+ if (report.aggregate) {
517
+ results.push({
518
+ file,
519
+ name: 'module',
520
+ line: 0,
521
+ complexity: report.aggregate.cyclomatic || 0,
522
+ halstead: report.aggregate.halstead?.difficulty || 0,
523
+ sloc: report.aggregate.sloc?.logical || 0,
524
+ maintainability: report.maintainability || 0
525
+ });
526
+ }
527
+ } catch {
528
+ // JSON parsing failed for this file
529
+ }
530
+ } catch {
531
+ // Tool execution failed for this file
532
+ }
533
+ }
534
+
535
+ return results.length > 0 ? results : null;
536
+ }
537
+
538
+ /**
539
+ * Get user-friendly message about missing tools (language-aware)
540
+ *
541
+ * @param {string[]} missingTools - Array of missing tool names
542
+ * @param {string[]} [languages] - Detected languages (for context in message)
543
+ * @returns {string} Formatted message
544
+ */
545
+ function getMissingToolsMessage(missingTools, languages = null) {
546
+ if (!missingTools || missingTools.length === 0) {
547
+ return '';
548
+ }
549
+
550
+ // Filter to only known tools
551
+ const validTools = missingTools.filter(t => CLI_TOOLS[t]);
552
+ if (validTools.length === 0) {
553
+ return '';
554
+ }
555
+
556
+ let message = '\n## Enhanced Analysis Available\n\n';
557
+
558
+ if (languages && languages.length > 0) {
559
+ message += `Detected project languages: ${languages.join(', ')}\n\n`;
560
+ }
561
+
562
+ message += 'For deeper analysis, consider installing:\n\n';
563
+
564
+ for (const toolName of validTools) {
565
+ const tool = CLI_TOOLS[toolName];
566
+ if (tool) {
567
+ message += `- **${tool.name}**: ${tool.description}\n`;
568
+ message += ` Install: \`${tool.installHint}\`\n`;
569
+ }
570
+ }
571
+
572
+ message += '\nThese tools are optional and enhance detection capabilities.\n';
573
+
574
+ return message;
575
+ }
576
+
577
+ /**
578
+ * Get all CLI tool definitions
579
+ *
580
+ * @returns {Object} CLI tool definitions
581
+ */
582
+ function getToolDefinitions() {
583
+ return { ...CLI_TOOLS };
584
+ }
585
+
586
+ /**
587
+ * Get supported languages list
588
+ *
589
+ * @returns {string[]} Array of supported language names
590
+ */
591
+ function getSupportedLanguages() {
592
+ return [...SUPPORTED_LANGUAGES];
593
+ }
594
+
595
+ module.exports = {
596
+ detectAvailableTools,
597
+ detectProjectLanguages,
598
+ getToolsForLanguages,
599
+ getToolAvailabilityForRepo,
600
+ runDuplicateDetection,
601
+ runDependencyAnalysis,
602
+ runComplexityAnalysis,
603
+ getMissingToolsMessage,
604
+ getToolDefinitions,
605
+ getSupportedLanguages,
606
+ clearCache,
607
+ // Exported for testing
608
+ isToolAvailable,
609
+ CLI_TOOLS,
610
+ SUPPORTED_LANGUAGES
611
+ };