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,413 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Platform Detection Infrastructure
4
+ * Auto-detects project configuration for zero-config slash commands
5
+ *
6
+ * Usage: node lib/platform/detect-platform.js
7
+ * Output: JSON with detected platform information
8
+ *
9
+ * @author Avi Fenesh
10
+ * @license MIT
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { exec } = require('child_process');
16
+ const { promisify } = require('util');
17
+
18
+ const execAsync = promisify(exec);
19
+ const fsPromises = fs.promises;
20
+
21
+ // Import shared utilities
22
+ const { CacheManager } = require('../utils/cache-manager');
23
+ const {
24
+ CI_CONFIGS,
25
+ DEPLOYMENT_CONFIGS,
26
+ PACKAGE_MANAGER_CONFIGS
27
+ } = require('./detection-configs');
28
+
29
+ /**
30
+ * Default timeout for async operations (5 seconds)
31
+ */
32
+ const DEFAULT_ASYNC_TIMEOUT_MS = 5000;
33
+
34
+ /**
35
+ * Maximum JSON file size to parse (1MB) - prevents DoS via large files
36
+ */
37
+ const MAX_JSON_SIZE_BYTES = 1024 * 1024;
38
+
39
+ /**
40
+ * Safely parse JSON content with size limit
41
+ * @param {string} content - JSON string to parse
42
+ * @param {string} filename - Filename for error messages
43
+ * @returns {Object|null} Parsed object or null if invalid/too large
44
+ */
45
+ function safeJSONParse(content, filename = 'unknown') {
46
+ if (!content || typeof content !== 'string') {
47
+ return null;
48
+ }
49
+ if (content.length > MAX_JSON_SIZE_BYTES) {
50
+ return null;
51
+ }
52
+ try {
53
+ return JSON.parse(content);
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Wrap a promise with a timeout
61
+ * @param {Promise} promise - Promise to wrap
62
+ * @param {number} timeoutMs - Timeout in milliseconds
63
+ * @param {string} operation - Operation name for error message
64
+ * @returns {Promise} Promise that rejects on timeout
65
+ */
66
+ function withTimeout(promise, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS, operation = 'operation') {
67
+ let timeoutId;
68
+ const timeoutPromise = new Promise((_, reject) => {
69
+ timeoutId = setTimeout(() => {
70
+ reject(new Error(`${operation} timed out after ${timeoutMs}ms`));
71
+ }, timeoutMs);
72
+ });
73
+
74
+ return Promise.race([promise, timeoutPromise]).finally(() => {
75
+ clearTimeout(timeoutId);
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Execute a command with timeout protection
81
+ * @param {string} cmd - Command to execute
82
+ * @param {Object} options - exec options
83
+ * @param {number} timeoutMs - Timeout in milliseconds
84
+ * @returns {Promise<{stdout: string, stderr: string}>}
85
+ */
86
+ async function execWithTimeout(cmd, options = {}, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS) {
87
+ return withTimeout(execAsync(cmd, options), timeoutMs, `exec: ${cmd.substring(0, 50)}`);
88
+ }
89
+
90
+ // Maximum cached file size constant
91
+ const MAX_CACHED_FILE_SIZE = 64 * 1024; // 64KB max per cached file
92
+
93
+ // Cache instances using CacheManager abstraction
94
+ const _detectionCache = new CacheManager({ maxSize: 1, ttl: 60000 });
95
+ const _fileCache = new CacheManager({ maxSize: 100, ttl: 60000, maxValueSize: MAX_CACHED_FILE_SIZE });
96
+ const _existsCache = new CacheManager({ maxSize: 100, ttl: 60000 });
97
+
98
+ /**
99
+ * Generic file-based detector
100
+ * @param {Array} configs - Array of {file, platform} objects
101
+ * @param {Function} existsChecker - Async function to check file existence
102
+ * @returns {Promise<string|null>} Detected platform or null
103
+ */
104
+ async function detectFromFiles(configs, existsChecker) {
105
+ try {
106
+ const checks = await Promise.all(
107
+ configs.map(({ file }) => existsChecker(file).catch(() => false))
108
+ );
109
+
110
+ for (let i = 0; i < checks.length; i++) {
111
+ if (checks[i]) {
112
+ return configs[i].platform;
113
+ }
114
+ }
115
+ return null;
116
+ } catch (error) {
117
+ // Log unexpected errors but return null to allow detection to continue
118
+ if (process.env.DEBUG) {
119
+ console.error(`[detect-platform] detectFromFiles error: ${error.message}`);
120
+ }
121
+ return null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Check if a file exists (cached)
127
+ * @param {string} filepath - Path to check
128
+ * @returns {Promise<boolean>}
129
+ */
130
+ async function existsCached(filepath) {
131
+ const cached = _existsCache.get(filepath);
132
+ if (cached !== undefined) {
133
+ return cached;
134
+ }
135
+ try {
136
+ await fsPromises.access(filepath);
137
+ _existsCache.set(filepath, true);
138
+ return true;
139
+ } catch {
140
+ _existsCache.set(filepath, false);
141
+ return false;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Read file contents (cached)
147
+ * Only caches files smaller than MAX_CACHED_FILE_SIZE to prevent memory bloat
148
+ * Optimized: normalizes filepath to prevent cache pollution from variant paths
149
+ * @param {string} filepath - Path to read
150
+ * @returns {Promise<string|null>}
151
+ */
152
+ async function readFileCached(filepath) {
153
+ const normalizedPath = path.resolve(filepath);
154
+
155
+ const cached = _fileCache.get(normalizedPath);
156
+ if (cached !== undefined) {
157
+ return cached;
158
+ }
159
+ try {
160
+ const content = await fsPromises.readFile(normalizedPath, 'utf8');
161
+ _fileCache.set(normalizedPath, content);
162
+ return content;
163
+ } catch {
164
+ _fileCache.set(normalizedPath, null);
165
+ return null;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Detects CI platform by scanning for configuration files
171
+ * @returns {Promise<string|null>} CI platform name or null if not detected
172
+ */
173
+ async function detectCI() {
174
+ return detectFromFiles(CI_CONFIGS, existsCached);
175
+ }
176
+
177
+ /**
178
+ * Detects deployment platform by scanning for platform-specific files
179
+ * @returns {Promise<string|null>} Deployment platform name or null if not detected
180
+ */
181
+ async function detectDeployment() {
182
+ return detectFromFiles(DEPLOYMENT_CONFIGS, existsCached);
183
+ }
184
+
185
+ /**
186
+ * Detects project type by scanning for language-specific files
187
+ * @returns {Promise<string>} Project type identifier
188
+ */
189
+ async function detectProjectType() {
190
+ try {
191
+ const checks = await Promise.all([
192
+ existsCached('package.json').catch(() => false),
193
+ existsCached('requirements.txt').catch(() => false),
194
+ existsCached('pyproject.toml').catch(() => false),
195
+ existsCached('setup.py').catch(() => false),
196
+ existsCached('Cargo.toml').catch(() => false),
197
+ existsCached('go.mod').catch(() => false),
198
+ existsCached('pom.xml').catch(() => false),
199
+ existsCached('build.gradle').catch(() => false)
200
+ ]);
201
+
202
+ if (checks[0]) return 'nodejs';
203
+ if (checks[1] || checks[2] || checks[3]) return 'python';
204
+ if (checks[4]) return 'rust';
205
+ if (checks[5]) return 'go';
206
+ if (checks[6] || checks[7]) return 'java';
207
+ return 'unknown';
208
+ } catch (error) {
209
+ if (process.env.DEBUG) {
210
+ console.error(`[detect-platform] detectProjectType error: ${error.message}`);
211
+ }
212
+ return 'unknown';
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Detects package manager by scanning for lockfiles
218
+ * @returns {Promise<string|null>} Package manager name or null if not detected
219
+ */
220
+ async function detectPackageManager() {
221
+ return detectFromFiles(
222
+ PACKAGE_MANAGER_CONFIGS.map(({ file, manager }) => ({ file, platform: manager })),
223
+ existsCached
224
+ );
225
+ }
226
+
227
+ /**
228
+ * Detects branch strategy (single-branch vs multi-branch with dev+prod)
229
+ * @returns {Promise<string>} 'single-branch' or 'multi-branch'
230
+ */
231
+ async function detectBranchStrategy() {
232
+ try {
233
+ const [localResult, remoteResult] = await Promise.all([
234
+ execWithTimeout('git branch', { encoding: 'utf8' }).catch(() => ({ stdout: '' })),
235
+ execWithTimeout('git branch -r', { encoding: 'utf8' }).catch(() => ({ stdout: '' }))
236
+ ]);
237
+
238
+ const allBranches = (localResult.stdout || '') + (remoteResult.stdout || '');
239
+
240
+ const hasStable = allBranches.includes('stable');
241
+ const hasProduction = allBranches.includes('production') || allBranches.includes('prod');
242
+
243
+ if (hasStable || hasProduction) {
244
+ return 'multi-branch';
245
+ }
246
+
247
+ if (await existsCached('railway.json')) {
248
+ try {
249
+ const content = await readFileCached('railway.json');
250
+ if (content) {
251
+ const config = safeJSONParse(content, 'railway.json');
252
+ if (config &&
253
+ typeof config === 'object' &&
254
+ typeof config.environments === 'object' &&
255
+ config.environments !== null &&
256
+ Object.keys(config.environments).length > 1) {
257
+ return 'multi-branch';
258
+ }
259
+ }
260
+ } catch { /* config parse error - ignore */ }
261
+ }
262
+
263
+ return 'single-branch';
264
+ } catch {
265
+ return 'single-branch';
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Detects the main branch name
271
+ * @returns {Promise<string>} Main branch name ('main' or 'master')
272
+ */
273
+ async function detectMainBranch() {
274
+ try {
275
+ const { stdout } = await execWithTimeout('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf8' });
276
+ return stdout.trim().replace('refs/remotes/origin/', '');
277
+ } catch {
278
+ try {
279
+ await execWithTimeout('git rev-parse --verify main', { encoding: 'utf8' });
280
+ return 'main';
281
+ } catch {
282
+ return 'master';
283
+ }
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Main detection function - aggregates all platform information
289
+ * Uses Promise.all for parallel execution and caching
290
+ * @param {boolean} forceRefresh - Force cache refresh
291
+ * @returns {Promise<Object>} Platform configuration object
292
+ */
293
+ async function detect(forceRefresh = false) {
294
+ if (!forceRefresh) {
295
+ const cached = _detectionCache.get('detection');
296
+ if (cached !== undefined) {
297
+ return cached;
298
+ }
299
+ }
300
+
301
+ try {
302
+ const [
303
+ ci,
304
+ deployment,
305
+ projectType,
306
+ packageManager,
307
+ branchStrategy,
308
+ mainBranch,
309
+ hasPlanFile,
310
+ hasTechDebtFile
311
+ ] = await Promise.all([
312
+ detectCI().catch((err) => {
313
+ if (process.env.DEBUG) console.error(`[detect-platform] detectCI failed: ${err.message}`);
314
+ return null;
315
+ }),
316
+ detectDeployment().catch((err) => {
317
+ if (process.env.DEBUG) console.error(`[detect-platform] detectDeployment failed: ${err.message}`);
318
+ return null;
319
+ }),
320
+ detectProjectType().catch((err) => {
321
+ if (process.env.DEBUG) console.error(`[detect-platform] detectProjectType failed: ${err.message}`);
322
+ return 'unknown';
323
+ }),
324
+ detectPackageManager().catch((err) => {
325
+ if (process.env.DEBUG) console.error(`[detect-platform] detectPackageManager failed: ${err.message}`);
326
+ return null;
327
+ }),
328
+ detectBranchStrategy().catch((err) => {
329
+ if (process.env.DEBUG) console.error(`[detect-platform] detectBranchStrategy failed: ${err.message}`);
330
+ return 'single-branch';
331
+ }),
332
+ detectMainBranch().catch((err) => {
333
+ if (process.env.DEBUG) console.error(`[detect-platform] detectMainBranch failed: ${err.message}`);
334
+ return 'main';
335
+ }),
336
+ existsCached('PLAN.md').catch(() => false),
337
+ existsCached('TECHNICAL_DEBT.md').catch(() => false)
338
+ ]);
339
+
340
+ const detection = {
341
+ ci,
342
+ deployment,
343
+ projectType,
344
+ packageManager,
345
+ branchStrategy,
346
+ mainBranch,
347
+ hasPlanFile,
348
+ hasTechDebtFile,
349
+ timestamp: new Date().toISOString()
350
+ };
351
+
352
+ _detectionCache.set('detection', detection);
353
+ return detection;
354
+ } catch (error) {
355
+ // Return sensible defaults if detection fails entirely
356
+ const fallback = {
357
+ ci: null,
358
+ deployment: null,
359
+ projectType: 'unknown',
360
+ packageManager: null,
361
+ branchStrategy: 'single-branch',
362
+ mainBranch: 'main',
363
+ hasPlanFile: false,
364
+ hasTechDebtFile: false,
365
+ timestamp: new Date().toISOString(),
366
+ error: error.message
367
+ };
368
+ if (process.env.DEBUG) {
369
+ console.error(`[detect-platform] detect() failed, returning fallback: ${error.message}`);
370
+ }
371
+ return fallback;
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Invalidate all detection caches
377
+ * Call this after making changes that affect platform detection
378
+ */
379
+ function invalidateCache() {
380
+ _detectionCache.clear();
381
+ _fileCache.clear();
382
+ _existsCache.clear();
383
+ }
384
+
385
+ // When run directly, output JSON
386
+ if (require.main === module) {
387
+ (async () => {
388
+ try {
389
+ const result = await detect();
390
+ const indent = process.stdout.isTTY ? 2 : 0;
391
+ console.log(JSON.stringify(result, null, indent));
392
+ } catch (error) {
393
+ const indent = process.stderr.isTTY ? 2 : 0;
394
+ console.error(JSON.stringify({
395
+ error: error.message,
396
+ timestamp: new Date().toISOString()
397
+ }, null, indent));
398
+ process.exit(1);
399
+ }
400
+ })();
401
+ }
402
+
403
+ // Export for use as module
404
+ module.exports = {
405
+ detect,
406
+ invalidateCache,
407
+ detectCI,
408
+ detectDeployment,
409
+ detectProjectType,
410
+ detectPackageManager,
411
+ detectBranchStrategy,
412
+ detectMainBranch
413
+ };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Detection Configurations
3
+ * Centralized config for platform detection logic
4
+ *
5
+ * @module lib/platform/detection-configs
6
+ * @author Avi Fenesh
7
+ * @license MIT
8
+ */
9
+
10
+ /**
11
+ * CI platform detection configuration
12
+ * Order matters - first match wins
13
+ */
14
+ const CI_CONFIGS = [
15
+ { file: '.github/workflows', platform: 'github-actions' },
16
+ { file: '.gitlab-ci.yml', platform: 'gitlab-ci' },
17
+ { file: '.circleci/config.yml', platform: 'circleci' },
18
+ { file: 'Jenkinsfile', platform: 'jenkins' },
19
+ { file: '.travis.yml', platform: 'travis' }
20
+ ];
21
+
22
+ /**
23
+ * Deployment platform detection configuration
24
+ * Order matters - first match wins
25
+ */
26
+ const DEPLOYMENT_CONFIGS = [
27
+ { file: 'railway.json', platform: 'railway' },
28
+ { file: 'railway.toml', platform: 'railway' }, // Legacy Railway marker
29
+ { file: 'vercel.json', platform: 'vercel' },
30
+ { file: 'netlify.toml', platform: 'netlify' },
31
+ { file: '.netlify', platform: 'netlify' }, // Legacy Netlify marker
32
+ { file: 'fly.toml', platform: 'fly' },
33
+ { file: '.platform.app.yaml', platform: 'platformsh' },
34
+ { file: 'render.yaml', platform: 'render' }
35
+ ];
36
+
37
+ /**
38
+ * Project type detection configuration
39
+ * Checks package.json for framework indicators
40
+ */
41
+ const PROJECT_TYPE_CONFIGS = {
42
+ dependencies: [
43
+ { name: 'next', type: 'nextjs' },
44
+ { name: 'react', type: 'react' },
45
+ { name: 'vue', type: 'vue' },
46
+ { name: '@angular/core', type: 'angular' },
47
+ { name: 'svelte', type: 'svelte' },
48
+ { name: 'express', type: 'express' },
49
+ { name: '@nestjs/core', type: 'nestjs' },
50
+ { name: 'gatsby', type: 'gatsby' },
51
+ { name: '@remix-run/react', type: 'remix' },
52
+ { name: 'astro', type: 'astro' }
53
+ ]
54
+ };
55
+
56
+ /**
57
+ * Package manager detection configuration
58
+ * Lock files indicate package manager used
59
+ * Order matters - first match wins (prioritize pnpm > yarn > bun > npm for Node.js)
60
+ */
61
+ const PACKAGE_MANAGER_CONFIGS = [
62
+ { file: 'pnpm-lock.yaml', manager: 'pnpm' },
63
+ { file: 'yarn.lock', manager: 'yarn' },
64
+ { file: 'bun.lockb', manager: 'bun' },
65
+ { file: 'package-lock.json', manager: 'npm' },
66
+ { file: 'poetry.lock', manager: 'poetry' },
67
+ { file: 'Pipfile.lock', manager: 'pipenv' },
68
+ { file: 'Cargo.lock', manager: 'cargo' },
69
+ { file: 'go.sum', manager: 'go' }
70
+ ];
71
+
72
+ /**
73
+ * Branch strategy patterns
74
+ */
75
+ const BRANCH_STRATEGIES = {
76
+ gitflow: ['develop', 'main', 'master'],
77
+ githubflow: ['main'],
78
+ trunkbased: ['main', 'trunk']
79
+ };
80
+
81
+ /**
82
+ * Main branch candidates (in priority order)
83
+ */
84
+ const MAIN_BRANCH_CANDIDATES = ['main', 'master', 'trunk', 'develop'];
85
+
86
+ module.exports = {
87
+ CI_CONFIGS,
88
+ DEPLOYMENT_CONFIGS,
89
+ PROJECT_TYPE_CONFIGS,
90
+ PACKAGE_MANAGER_CONFIGS,
91
+ BRANCH_STRATEGIES,
92
+ MAIN_BRANCH_CANDIDATES
93
+ };
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Platform-aware state directory detection
3
+ *
4
+ * Determines the appropriate state directory based on the AI coding assistant
5
+ * being used (Claude Code, OpenCode, or Codex CLI).
6
+ *
7
+ * @module lib/platform/state-dir
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ /**
14
+ * Cached state directory name (relative, without leading dot handling),
15
+ * scoped by resolved base path.
16
+ * @type {Map<string, string>}
17
+ */
18
+ const _cachedStateDirs = new Map();
19
+
20
+ function isDirectory(targetPath) {
21
+ try {
22
+ return fs.statSync(targetPath).isDirectory();
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Detect which AI coding assistant is running and return appropriate state directory
30
+ *
31
+ * Detection order:
32
+ * 1. AI_STATE_DIR env var (user override)
33
+ * 2. OpenCode detection (OPENCODE_CONFIG env or .opencode/ exists)
34
+ * 3. Codex detection (CODEX_HOME env or .codex/ exists)
35
+ * 4. Default to .claude (Claude Code or unknown)
36
+ *
37
+ * @param {string} [basePath=process.cwd()] - Base path to check for project directories
38
+ * @returns {string} State directory name (e.g., '.claude', '.opencode', '.codex')
39
+ */
40
+ function getStateDir(basePath = process.cwd()) {
41
+ // Check user override first
42
+ if (process.env.AI_STATE_DIR) {
43
+ return process.env.AI_STATE_DIR;
44
+ }
45
+
46
+ const cacheKey = path.resolve(basePath);
47
+ const cached = _cachedStateDirs.get(cacheKey);
48
+ if (cached) {
49
+ return cached;
50
+ }
51
+
52
+ // OpenCode detection
53
+ if (process.env.OPENCODE_CONFIG || process.env.OPENCODE_CONFIG_DIR) {
54
+ _cachedStateDirs.set(cacheKey, '.opencode');
55
+ return '.opencode';
56
+ }
57
+
58
+ // Check for .opencode directory in project
59
+ try {
60
+ const opencodePath = path.join(basePath, '.opencode');
61
+ if (isDirectory(opencodePath)) {
62
+ _cachedStateDirs.set(cacheKey, '.opencode');
63
+ return '.opencode';
64
+ }
65
+ } catch {
66
+ // Ignore errors, continue detection
67
+ }
68
+
69
+ // Codex detection
70
+ if (process.env.CODEX_HOME) {
71
+ _cachedStateDirs.set(cacheKey, '.codex');
72
+ return '.codex';
73
+ }
74
+
75
+ // Check for .codex directory in project
76
+ try {
77
+ const codexPath = path.join(basePath, '.codex');
78
+ if (isDirectory(codexPath)) {
79
+ _cachedStateDirs.set(cacheKey, '.codex');
80
+ return '.codex';
81
+ }
82
+ } catch {
83
+ // Ignore errors, continue detection
84
+ }
85
+
86
+ // Default to Claude Code
87
+ _cachedStateDirs.set(cacheKey, '.claude');
88
+ return '.claude';
89
+ }
90
+
91
+ /**
92
+ * Get the full path to the state directory
93
+ * @param {string} [basePath=process.cwd()] - Base path
94
+ * @returns {string} Full path to state directory
95
+ */
96
+ function getStateDirPath(basePath = process.cwd()) {
97
+ return path.join(basePath, getStateDir(basePath));
98
+ }
99
+
100
+ /**
101
+ * Get the detected platform name
102
+ * @param {string} [basePath=process.cwd()] - Base path
103
+ * @returns {string} Platform name ('claude', 'opencode', 'codex', or 'custom')
104
+ */
105
+ function getPlatformName(basePath = process.cwd()) {
106
+ const stateDir = getStateDir(basePath);
107
+
108
+ if (process.env.AI_STATE_DIR) {
109
+ return 'custom';
110
+ }
111
+
112
+ switch (stateDir) {
113
+ case '.opencode': return 'opencode';
114
+ case '.codex': return 'codex';
115
+ case '.claude': return 'claude';
116
+ default: return 'unknown';
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Clear the cached state directory (useful for testing)
122
+ */
123
+ function clearCache() {
124
+ _cachedStateDirs.clear();
125
+ }
126
+
127
+ module.exports = {
128
+ getStateDir,
129
+ getStateDirPath,
130
+ getPlatformName,
131
+ clearCache
132
+ };