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,298 @@
1
+ /**
2
+ * Adapter Transform Functions
3
+ *
4
+ * Shared transforms for converting Claude Code plugin content into
5
+ * OpenCode and Codex adapter formats. Used by:
6
+ * - bin/cli.js (npm installer)
7
+ * - scripts/dev-install.js (development installer)
8
+ * - scripts/gen-adapters.js (static adapter generation)
9
+ *
10
+ * @module adapter-transforms
11
+ * @author Avi Fenesh
12
+ * @license MIT
13
+ */
14
+
15
+ const discovery = require('./discovery');
16
+
17
+ function transformBodyForOpenCode(content, repoRoot) {
18
+ content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, '${PLUGIN_ROOT}');
19
+ content = content.replace(/\$CLAUDE_PLUGIN_ROOT/g, '$PLUGIN_ROOT');
20
+
21
+ // Replace .claude/ paths with .opencode/ but preserve platform documentation lists
22
+ // that enumerate all three platforms (Claude Code: .claude/, OpenCode: .opencode/, Codex: .codex/)
23
+ // Also preserve {AI_STATE_DIR} references which are platform-agnostic
24
+ content = content.replace(/\.claude\//g, (match, offset) => {
25
+ const context = content.substring(Math.max(0, offset - 60), offset + match.length + 10);
26
+ // Skip if inside a platform enumeration (e.g., "Claude Code: `.claude/`")
27
+ if (/Claude Code:/.test(context)) return match;
28
+ return '.opencode/';
29
+ });
30
+ content = content.replace(/\.claude'/g, (match, offset) => {
31
+ const context = content.substring(Math.max(0, offset - 60), offset + match.length + 10);
32
+ if (/Claude Code:/.test(context)) return match;
33
+ return ".opencode'";
34
+ });
35
+ content = content.replace(/\.claude"/g, (match, offset) => {
36
+ const context = content.substring(Math.max(0, offset - 60), offset + match.length + 10);
37
+ if (/Claude Code:/.test(context)) return match;
38
+ return '.opencode"';
39
+ });
40
+ content = content.replace(/\.claude`/g, (match, offset) => {
41
+ const context = content.substring(Math.max(0, offset - 60), offset + match.length + 10);
42
+ if (/Claude Code:/.test(context)) return match;
43
+ return '.opencode`';
44
+ });
45
+
46
+ const plugins = discovery.discoverPlugins(repoRoot);
47
+ if (plugins.length > 0) {
48
+ const pluginNames = plugins.join('|');
49
+ content = content.replace(new RegExp('`(' + pluginNames + '):([a-z-]+)`', 'g'), '`$2`');
50
+ content = content.replace(new RegExp('(' + pluginNames + '):([a-z-]+)', 'g'), '$2');
51
+ }
52
+
53
+ content = content.replace(
54
+ /```(\w*)\n([\s\S]*?)```/g,
55
+ (match, lang, code) => {
56
+ const langLower = (lang || '').toLowerCase();
57
+
58
+ if (langLower === 'bash' || langLower === 'shell' || langLower === 'sh') {
59
+ if (code.includes('node -e') && code.includes('require(')) {
60
+ return '*(Bash command with Node.js require - adapt for OpenCode)*';
61
+ }
62
+ return match;
63
+ }
64
+
65
+ if (!lang && (code.trim().startsWith('gh ') || code.trim().startsWith('glab ') ||
66
+ code.trim().startsWith('git ') || code.trim().startsWith('#!'))) {
67
+ return match;
68
+ }
69
+
70
+ if (code.includes('require(') || code.includes('Task(') ||
71
+ code.includes('const ') || code.includes('let ') ||
72
+ code.includes('function ') || code.includes('=>') ||
73
+ code.includes('async ') || code.includes('await ')) {
74
+
75
+ let instructions = '';
76
+
77
+ const taskMatches = [...code.matchAll(/(?:await\s+)?Task\s*\(\s*\{[^}]*subagent_type:\s*["'](?:[^"':]+:)?([^"']+)["'][^}]*\}\s*\)/gs)];
78
+ for (const taskMatch of taskMatches) {
79
+ const agent = taskMatch[1];
80
+ instructions += `- Invoke \`@${agent}\` agent\n`;
81
+ }
82
+
83
+ const phaseMatches = code.match(/startPhase\s*\(\s*['"]([^'"]+)['"]\s*\)/g);
84
+ if (phaseMatches) {
85
+ for (const pm of phaseMatches) {
86
+ const phase = pm.match(/['"]([^'"]+)['"]/)[1];
87
+ instructions += `- Phase: ${phase}\n`;
88
+ }
89
+ }
90
+
91
+ if (code.includes('AskUserQuestion')) {
92
+ instructions += '- Use AskUserQuestion tool for user input\n';
93
+ }
94
+
95
+ if (code.includes('EnterPlanMode')) {
96
+ instructions += '- Use EnterPlanMode for user approval\n';
97
+ }
98
+
99
+ if (instructions) {
100
+ return instructions;
101
+ }
102
+
103
+ return '*(JavaScript reference - not executable in OpenCode)*';
104
+ }
105
+
106
+ return match;
107
+ }
108
+ );
109
+
110
+ content = content.replace(/\*\(Reference - adapt for OpenCode\)\*/g, '');
111
+
112
+ content = content.replace(/await\s+Task\s*\(\s*\{[\s\S]*?\}\s*\);?/g, (match) => {
113
+ const agentMatch = match.match(/subagent_type:\s*["'](?:[^"':]+:)?([^"']+)["']/);
114
+ if (agentMatch) {
115
+ return `Invoke \`@${agentMatch[1]}\` agent`;
116
+ }
117
+ return '*(Task call - use @agent-name syntax)*';
118
+ });
119
+
120
+ content = content.replace(/(?:const|let|var)\s+\{?[^}=\n]+\}?\s*=\s*require\s*\([^)]+\);?/g, '');
121
+ content = content.replace(/require\s*\(['"][^'"]+['"]\)/g, '');
122
+
123
+ if (content.includes('agent')) {
124
+ const note = `
125
+ > **OpenCode Note**: Invoke agents using \`@agent-name\` syntax.
126
+ > Available agents: task-discoverer, exploration-agent, planning-agent,
127
+ > implementation-agent, deslop-agent, delivery-validator, sync-docs-agent, consult-agent
128
+ > Example: \`@exploration-agent analyze the codebase\`
129
+
130
+ `;
131
+ content = content.replace(/^(---\n[\s\S]*?---\n)/, `$1${note}`);
132
+ }
133
+
134
+ if (content.includes('Master Workflow Orchestrator') && content.includes('No Shortcuts Policy')) {
135
+ const policySection = `
136
+ ## Phase 1: Policy Selection (Built-in Options)
137
+
138
+ Ask the user these questions using AskUserQuestion:
139
+
140
+ **Question 1 - Source**: "Where should I look for tasks?"
141
+ - GitHub Issues - Use \`gh issue list\` to find issues
142
+ - GitLab Issues - Use \`glab issue list\` to find issues
143
+ - Local tasks.md - Read from PLAN.md, tasks.md, or TODO.md in the repo
144
+ - Custom - User specifies their own source
145
+ - Other - User describes source, you figure it out
146
+
147
+ **Question 2 - Priority**: "What type of tasks to prioritize?"
148
+ - All - Consider all tasks, pick by score
149
+ - Bugs - Focus on bug fixes
150
+ - Security - Security issues first
151
+ - Features - New feature development
152
+
153
+ **Question 3 - Stop Point**: "How far should I take this task?"
154
+ - Merged - Until PR is merged to main
155
+ - PR Created - Stop after creating PR
156
+ - Implemented - Stop after local implementation
157
+ - Deployed - Deploy to staging
158
+ - Production - Full production deployment
159
+
160
+ After user answers, proceed to Phase 2 with the selected policy.
161
+
162
+ `;
163
+ if (content.includes('OpenCode Note')) {
164
+ content = content.replace(/(Example:.*analyze the codebase\`\n\n)/, `$1${policySection}`);
165
+ }
166
+ }
167
+
168
+ return content;
169
+ }
170
+
171
+ function transformCommandFrontmatterForOpenCode(content) {
172
+ return content.replace(
173
+ /^---\n([\s\S]*?)^---/m,
174
+ (match, frontmatter) => {
175
+ // Parse existing frontmatter
176
+ const lines = frontmatter.trim().split('\n');
177
+ const parsed = {};
178
+ for (const line of lines) {
179
+ const colonIdx = line.indexOf(':');
180
+ if (colonIdx > 0) {
181
+ const key = line.substring(0, colonIdx).trim();
182
+ const value = line.substring(colonIdx + 1).trim();
183
+ parsed[key] = value;
184
+ }
185
+ }
186
+
187
+ // Build OpenCode command frontmatter
188
+ let opencodeFrontmatter = '---\n';
189
+ if (parsed.description) opencodeFrontmatter += `description: ${parsed.description}\n`;
190
+ opencodeFrontmatter += 'agent: general\n';
191
+ // Don't include argument-hint or allowed-tools (not supported)
192
+ opencodeFrontmatter += '---';
193
+ return opencodeFrontmatter;
194
+ }
195
+ );
196
+ }
197
+
198
+ function transformAgentFrontmatterForOpenCode(content, options) {
199
+ const { stripModels = true } = options || {};
200
+
201
+ return content.replace(
202
+ /^---\n([\s\S]*?)^---/m,
203
+ (match, frontmatter) => {
204
+ // Parse existing frontmatter
205
+ const lines = frontmatter.trim().split('\n');
206
+ const parsed = {};
207
+ for (const line of lines) {
208
+ const colonIdx = line.indexOf(':');
209
+ if (colonIdx > 0) {
210
+ const key = line.substring(0, colonIdx).trim();
211
+ const value = line.substring(colonIdx + 1).trim();
212
+ parsed[key] = value;
213
+ }
214
+ }
215
+
216
+ // Build OpenCode frontmatter
217
+ let opencodeFrontmatter = '---\n';
218
+ if (parsed.name) opencodeFrontmatter += `name: ${parsed.name}\n`;
219
+ if (parsed.description) opencodeFrontmatter += `description: ${parsed.description}\n`;
220
+ opencodeFrontmatter += 'mode: subagent\n';
221
+
222
+ // Map model names - only include if NOT stripping
223
+ if (parsed.model && !stripModels) {
224
+ const modelMap = {
225
+ 'sonnet': 'anthropic/claude-sonnet-4',
226
+ 'opus': 'anthropic/claude-opus-4',
227
+ 'haiku': 'anthropic/claude-haiku-3-5'
228
+ };
229
+ opencodeFrontmatter += `model: ${modelMap[parsed.model] || parsed.model}\n`;
230
+ }
231
+
232
+ // Convert tools to permissions
233
+ if (parsed.tools) {
234
+ opencodeFrontmatter += 'permission:\n';
235
+ const tools = parsed.tools.toLowerCase();
236
+ opencodeFrontmatter += ` read: ${tools.includes('read') ? 'allow' : 'deny'}\n`;
237
+ opencodeFrontmatter += ` edit: ${tools.includes('edit') || tools.includes('write') ? 'allow' : 'deny'}\n`;
238
+ opencodeFrontmatter += ` bash: ${tools.includes('bash') ? 'allow' : 'ask'}\n`;
239
+ opencodeFrontmatter += ` glob: ${tools.includes('glob') ? 'allow' : 'deny'}\n`;
240
+ opencodeFrontmatter += ` grep: ${tools.includes('grep') ? 'allow' : 'deny'}\n`;
241
+ }
242
+
243
+ opencodeFrontmatter += '---';
244
+ return opencodeFrontmatter;
245
+ }
246
+ );
247
+ }
248
+
249
+ function transformSkillBodyForOpenCode(content, repoRoot) {
250
+ return transformBodyForOpenCode(content, repoRoot);
251
+ }
252
+
253
+ function transformForCodex(content, options) {
254
+ const { skillName, description, pluginInstallPath } = options;
255
+
256
+ // Escape description for YAML: wrap in double quotes, escape backslashes and internal quotes
257
+ const escapedDescription = description.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
258
+ const yamlDescription = `"${escapedDescription}"`;
259
+
260
+ if (content.startsWith('---')) {
261
+ // Replace existing frontmatter with Codex-compatible format
262
+ content = content.replace(
263
+ /^---\n[\s\S]*?\n---\n/,
264
+ `---\nname: ${skillName}\ndescription: ${yamlDescription}\n---\n`
265
+ );
266
+ } else {
267
+ // Add new frontmatter
268
+ content = `---\nname: ${skillName}\ndescription: ${yamlDescription}\n---\n\n${content}`;
269
+ }
270
+
271
+ // Transform PLUGIN_ROOT to actual installed path (or placeholder) for Codex
272
+ content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginInstallPath);
273
+ content = content.replace(/\$CLAUDE_PLUGIN_ROOT/g, pluginInstallPath);
274
+ content = content.replace(/\$\{PLUGIN_ROOT\}/g, pluginInstallPath);
275
+ content = content.replace(/\$PLUGIN_ROOT/g, pluginInstallPath);
276
+
277
+ // Transform AskUserQuestion → request_user_input for Codex native tool
278
+ content = content.replace(/AskUserQuestion/g, 'request_user_input');
279
+
280
+ // Remove multiSelect lines (not supported in Codex)
281
+ content = content.replace(/^[ \t]*multiSelect:.*\n?/gm, '');
282
+
283
+ // Inject Codex note about required id field after request_user_input blocks
284
+ content = content.replace(
285
+ /^([ \t]*request_user_input:\s*)$/gm,
286
+ '$1\n> **Codex**: Each question MUST include a unique `id` field (e.g., `id: "q1"`).'
287
+ );
288
+
289
+ return content;
290
+ }
291
+
292
+ module.exports = {
293
+ transformBodyForOpenCode,
294
+ transformCommandFrontmatterForOpenCode,
295
+ transformAgentFrontmatterForOpenCode,
296
+ transformSkillBodyForOpenCode,
297
+ transformForCodex
298
+ };
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Codebase Collector
3
+ *
4
+ * Scans codebase structure, frameworks, and implemented features.
5
+ * Extracted from drift-detect/collectors.js for shared use.
6
+ *
7
+ * @module lib/collectors/codebase
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const DEFAULT_OPTIONS = {
16
+ depth: 'thorough',
17
+ cwd: process.cwd()
18
+ };
19
+
20
+ /**
21
+ * Maximum file size to analyze (50KB)
22
+ * Larger files are skipped to avoid memory issues
23
+ */
24
+ const MAX_FILE_SIZE = 50000;
25
+
26
+ /**
27
+ * Directories to exclude from analysis
28
+ */
29
+ const EXCLUDE_DIRS = [
30
+ 'node_modules', 'vendor', 'dist', 'build', 'out', 'target',
31
+ '.git', '.svn', '.hg', '__pycache__', '.pytest_cache',
32
+ 'coverage', '.nyc_output', '.next', '.nuxt', '.cache'
33
+ ];
34
+
35
+ /**
36
+ * Source file extensions per language
37
+ */
38
+ const SOURCE_EXTENSIONS = {
39
+ js: ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'],
40
+ rust: ['.rs'],
41
+ go: ['.go'],
42
+ python: ['.py'],
43
+ java: ['.java']
44
+ };
45
+
46
+ /**
47
+ * Safe file read
48
+ */
49
+ function safeReadFile(filePath, basePath) {
50
+ const fullPath = path.resolve(basePath, filePath);
51
+ const resolvedBase = path.resolve(basePath);
52
+ if (!fullPath.startsWith(resolvedBase)) {
53
+ return null;
54
+ }
55
+ try {
56
+ return fs.readFileSync(fullPath, 'utf8');
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Check if path should be excluded
64
+ */
65
+ function shouldExclude(filePath, excludeDirs = EXCLUDE_DIRS) {
66
+ const parts = filePath.split(/[\\/]/);
67
+ return parts.some(part => excludeDirs.includes(part));
68
+ }
69
+
70
+ /**
71
+ * Detect frameworks from package.json
72
+ */
73
+ function detectFrameworks(result, pkgJson) {
74
+ const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
75
+ const frameworkMap = {
76
+ react: 'React',
77
+ 'react-dom': 'React',
78
+ next: 'Next.js',
79
+ vue: 'Vue.js',
80
+ nuxt: 'Nuxt',
81
+ angular: 'Angular',
82
+ express: 'Express',
83
+ fastify: 'Fastify',
84
+ koa: 'Koa',
85
+ nestjs: 'NestJS'
86
+ };
87
+
88
+ for (const [pkgName, framework] of Object.entries(frameworkMap)) {
89
+ if (deps[pkgName]) {
90
+ result.frameworks.push(framework);
91
+ }
92
+ }
93
+
94
+ result.frameworks = [...new Set(result.frameworks)];
95
+ }
96
+
97
+ /**
98
+ * Detect test framework
99
+ */
100
+ function detectTestFramework(result, pkgJson) {
101
+ const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
102
+ const testFrameworks = ['jest', 'mocha', 'vitest', 'ava', 'tap', 'jasmine'];
103
+
104
+ for (const framework of testFrameworks) {
105
+ if (deps[framework]) {
106
+ result.testFramework = framework;
107
+ result.health.hasTests = true;
108
+ break;
109
+ }
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Extract symbols (functions, classes, exports) from a JS/TS file
115
+ */
116
+ function extractSymbols(content) {
117
+ const symbols = {
118
+ functions: [],
119
+ classes: [],
120
+ exports: []
121
+ };
122
+
123
+ const funcPattern = /(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
124
+ let match;
125
+ while ((match = funcPattern.exec(content)) !== null) {
126
+ symbols.functions.push(match[1]);
127
+ }
128
+
129
+ const arrowPattern = /(?:const|let)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g;
130
+ while ((match = arrowPattern.exec(content)) !== null) {
131
+ symbols.functions.push(match[1]);
132
+ }
133
+
134
+ const classPattern = /class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
135
+ while ((match = classPattern.exec(content)) !== null) {
136
+ symbols.classes.push(match[1]);
137
+ }
138
+
139
+ const namedExportPattern = /export\s+(?:(?:async\s+)?function|class|const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
140
+ while ((match = namedExportPattern.exec(content)) !== null) {
141
+ symbols.exports.push(match[1]);
142
+ }
143
+
144
+ const moduleExportsPattern = /module\.exports\s*=\s*\{([^}]+)\}/;
145
+ const moduleMatch = content.match(moduleExportsPattern);
146
+ if (moduleMatch) {
147
+ const keys = moduleMatch[1].split(',').map(k => k.trim().split(':')[0].trim());
148
+ symbols.exports.push(...keys.filter(k => k && /^[a-zA-Z_$]/.test(k)));
149
+ }
150
+
151
+ symbols.functions = [...new Set(symbols.functions)];
152
+ symbols.classes = [...new Set(symbols.classes)];
153
+ symbols.exports = [...new Set(symbols.exports)];
154
+
155
+ return symbols;
156
+ }
157
+
158
+ /**
159
+ * Scan key source files for symbols
160
+ */
161
+ function scanFileSymbols(basePath, topLevelDirs) {
162
+ const sourceSymbols = {};
163
+ const sourceDirs = ['lib', 'src', 'app', 'pages', 'components', 'utils', 'services', 'api'];
164
+ const dirsToScan = topLevelDirs.filter(d => sourceDirs.includes(d));
165
+ const allExts = Object.values(SOURCE_EXTENSIONS).flat();
166
+
167
+ let filesScanned = 0;
168
+ const maxFiles = 40;
169
+
170
+ function scanDir(dirPath, relativePath, depth = 0) {
171
+ if (filesScanned >= maxFiles || depth > 2) return;
172
+ if (!fs.existsSync(dirPath)) return;
173
+
174
+ try {
175
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
176
+
177
+ for (const entry of entries) {
178
+ if (filesScanned >= maxFiles) break;
179
+
180
+ const fullPath = path.join(dirPath, entry.name);
181
+ const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
182
+
183
+ if (entry.isDirectory()) {
184
+ if (['node_modules', '__tests__', 'test', 'tests', 'dist', 'build'].includes(entry.name)) continue;
185
+ scanDir(fullPath, relPath, depth + 1);
186
+ } else if (entry.isFile()) {
187
+ const ext = path.extname(entry.name);
188
+ if (!allExts.includes(ext)) continue;
189
+ if (entry.name.includes('.test.') || entry.name.includes('.spec.')) continue;
190
+
191
+ try {
192
+ const stat = fs.statSync(fullPath);
193
+ if (stat.size > MAX_FILE_SIZE) continue;
194
+
195
+ const content = fs.readFileSync(fullPath, 'utf8');
196
+ const symbols = extractSymbols(content);
197
+
198
+ if (symbols.functions.length || symbols.classes.length || symbols.exports.length) {
199
+ sourceSymbols[relPath] = symbols;
200
+ filesScanned++;
201
+ }
202
+ } catch {
203
+ // Skip unreadable files
204
+ }
205
+ }
206
+ }
207
+ } catch {
208
+ // Skip unreadable dirs
209
+ }
210
+ }
211
+
212
+ for (const dir of dirsToScan) {
213
+ if (filesScanned >= maxFiles) break;
214
+ scanDir(path.join(basePath, dir), dir);
215
+ }
216
+
217
+ return sourceSymbols;
218
+ }
219
+
220
+ /**
221
+ * Scan directory structure recursively
222
+ */
223
+ function scanDirectory(result, basePath, relativePath, maxDepth, depth = 0) {
224
+ if (depth >= maxDepth) return;
225
+
226
+ const fullPath = path.join(basePath, relativePath);
227
+ if (!fs.existsSync(fullPath)) return;
228
+
229
+ try {
230
+ const entries = fs.readdirSync(fullPath, { withFileTypes: true });
231
+ const dirs = [];
232
+ const files = [];
233
+
234
+ for (const entry of entries) {
235
+ if (entry.isDirectory()) {
236
+ if (!EXCLUDE_DIRS.includes(entry.name)) {
237
+ dirs.push(entry.name);
238
+ }
239
+ } else {
240
+ files.push(entry.name);
241
+ }
242
+ }
243
+
244
+ const key = relativePath || '.';
245
+ result.structure[key] = { dirs, fileCount: files.length };
246
+
247
+ for (const file of files) {
248
+ const ext = path.extname(file).toLowerCase() || 'no-ext';
249
+ result.fileStats[ext] = (result.fileStats[ext] || 0) + 1;
250
+ }
251
+
252
+ for (const dir of dirs) {
253
+ scanDirectory(result, basePath, path.join(relativePath, dir), maxDepth, depth + 1);
254
+ }
255
+ } catch {
256
+ // Permission or read errors
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Detect project health indicators
262
+ */
263
+ function detectHealth(result, basePath) {
264
+ result.health.hasReadme = fs.existsSync(path.join(basePath, 'README.md'));
265
+
266
+ const lintConfigs = ['.eslintrc', '.eslintrc.js', '.eslintrc.json', 'eslint.config.js', 'biome.json'];
267
+ result.health.hasLinting = lintConfigs.some(f => fs.existsSync(path.join(basePath, f)));
268
+
269
+ const ciConfigs = [
270
+ '.github/workflows',
271
+ '.gitlab-ci.yml',
272
+ '.circleci',
273
+ 'Jenkinsfile',
274
+ '.travis.yml'
275
+ ];
276
+ result.health.hasCi = ciConfigs.some(f => fs.existsSync(path.join(basePath, f)));
277
+
278
+ const testDirs = ['tests', '__tests__', 'test', 'spec'];
279
+ result.health.hasTests = result.health.hasTests || testDirs.some(d => fs.existsSync(path.join(basePath, d)));
280
+ }
281
+
282
+ /**
283
+ * Find implemented features from code patterns
284
+ */
285
+ function findImplementedFeatures(result, basePath) {
286
+ const featurePatterns = {
287
+ authentication: ['auth', 'login', 'session', 'jwt', 'oauth'],
288
+ api: ['routes', 'controllers', 'handlers', 'endpoints'],
289
+ database: ['models', 'schemas', 'migrations', 'seeds'],
290
+ ui: ['components', 'views', 'pages', 'layouts'],
291
+ testing: ['__tests__', 'test', 'spec', '.test.', '.spec.'],
292
+ docs: ['docs', 'documentation', 'wiki']
293
+ };
294
+
295
+ for (const [feature, patterns] of Object.entries(featurePatterns)) {
296
+ const found = patterns.some(pattern => {
297
+ for (const dir of Object.keys(result.structure)) {
298
+ if (dir.toLowerCase().includes(pattern)) {
299
+ return true;
300
+ }
301
+ }
302
+ return false;
303
+ });
304
+
305
+ if (found) {
306
+ result.implementedFeatures.push(feature);
307
+ }
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Scan codebase structure and features
313
+ * @param {Object} options - Collection options
314
+ * @returns {Object} Codebase analysis
315
+ */
316
+ function scanCodebase(options = {}) {
317
+ const opts = { ...DEFAULT_OPTIONS, ...options };
318
+ const basePath = opts.cwd;
319
+
320
+ const result = {
321
+ summary: { totalDirs: 0, totalFiles: 0 },
322
+ topLevelDirs: [],
323
+ frameworks: [],
324
+ testFramework: null,
325
+ hasTypeScript: false,
326
+ implementedFeatures: [],
327
+ symbols: {},
328
+ health: {
329
+ hasTests: false,
330
+ hasLinting: false,
331
+ hasCi: false,
332
+ hasReadme: false
333
+ },
334
+ fileStats: {}
335
+ };
336
+
337
+ const internalStructure = {};
338
+
339
+ // Detect package.json dependencies
340
+ const pkgContent = safeReadFile('package.json', basePath);
341
+ if (pkgContent) {
342
+ try {
343
+ const pkg = JSON.parse(pkgContent);
344
+ detectFrameworks(result, pkg);
345
+ detectTestFramework(result, pkg);
346
+ } catch {
347
+ // Invalid JSON
348
+ }
349
+ }
350
+
351
+ result.hasTypeScript = fs.existsSync(path.join(basePath, 'tsconfig.json'));
352
+
353
+ scanDirectory({ structure: internalStructure, fileStats: result.fileStats }, basePath, '', opts.depth === 'thorough' ? 3 : 2);
354
+
355
+ result.summary.totalDirs = Object.keys(internalStructure).length;
356
+ result.summary.totalFiles = Object.values(internalStructure).reduce((sum, d) => sum + (d.fileCount || 0), 0);
357
+
358
+ const rootEntry = internalStructure['.'];
359
+ if (rootEntry) {
360
+ result.topLevelDirs = rootEntry.dirs || [];
361
+ }
362
+
363
+ detectHealth(result, basePath);
364
+
365
+ if (opts.depth === 'thorough') {
366
+ findImplementedFeatures({ ...result, structure: internalStructure }, basePath);
367
+ result.symbols = scanFileSymbols(basePath, result.topLevelDirs);
368
+ }
369
+
370
+ const sortedStats = Object.entries(result.fileStats)
371
+ .sort((a, b) => b[1] - a[1])
372
+ .slice(0, 10);
373
+ result.fileStats = Object.fromEntries(sortedStats);
374
+
375
+ return result;
376
+ }
377
+
378
+ module.exports = {
379
+ DEFAULT_OPTIONS,
380
+ EXCLUDE_DIRS,
381
+ SOURCE_EXTENSIONS,
382
+ scanCodebase,
383
+ detectFrameworks,
384
+ detectTestFramework,
385
+ detectHealth,
386
+ findImplementedFeatures,
387
+ extractSymbols,
388
+ scanFileSymbols,
389
+ scanDirectory,
390
+ shouldExclude,
391
+ safeReadFile
392
+ };