opencastle 0.5.1 → 0.7.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 (231) hide show
  1. package/README.md +5 -4
  2. package/dist/cli/adapters/claude-code.d.ts +2 -2
  3. package/dist/cli/adapters/claude-code.d.ts.map +1 -1
  4. package/dist/cli/adapters/claude-code.js +31 -4
  5. package/dist/cli/adapters/claude-code.js.map +1 -1
  6. package/dist/cli/adapters/cursor.d.ts +2 -2
  7. package/dist/cli/adapters/cursor.d.ts.map +1 -1
  8. package/dist/cli/adapters/cursor.js +28 -4
  9. package/dist/cli/adapters/cursor.js.map +1 -1
  10. package/dist/cli/adapters/opencode.d.ts +20 -0
  11. package/dist/cli/adapters/opencode.d.ts.map +1 -0
  12. package/dist/cli/adapters/opencode.js +265 -0
  13. package/dist/cli/adapters/opencode.js.map +1 -0
  14. package/dist/cli/adapters/vscode.d.ts +2 -2
  15. package/dist/cli/adapters/vscode.d.ts.map +1 -1
  16. package/dist/cli/adapters/vscode.js +38 -7
  17. package/dist/cli/adapters/vscode.js.map +1 -1
  18. package/dist/cli/copy.d.ts +12 -0
  19. package/dist/cli/copy.d.ts.map +1 -1
  20. package/dist/cli/copy.js +27 -0
  21. package/dist/cli/copy.js.map +1 -1
  22. package/dist/cli/detect.d.ts +18 -0
  23. package/dist/cli/detect.d.ts.map +1 -0
  24. package/dist/cli/detect.js +434 -0
  25. package/dist/cli/detect.js.map +1 -0
  26. package/dist/cli/gitignore.d.ts.map +1 -1
  27. package/dist/cli/gitignore.js +0 -2
  28. package/dist/cli/gitignore.js.map +1 -1
  29. package/dist/cli/init.d.ts.map +1 -1
  30. package/dist/cli/init.js +154 -91
  31. package/dist/cli/init.js.map +1 -1
  32. package/dist/cli/manifest.d.ts +1 -1
  33. package/dist/cli/manifest.d.ts.map +1 -1
  34. package/dist/cli/manifest.js +2 -1
  35. package/dist/cli/manifest.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -6
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +105 -34
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/prompt.d.ts +22 -0
  41. package/dist/cli/prompt.d.ts.map +1 -1
  42. package/dist/cli/prompt.js +239 -0
  43. package/dist/cli/prompt.js.map +1 -1
  44. package/dist/cli/stack-config.d.ts +26 -3
  45. package/dist/cli/stack-config.d.ts.map +1 -1
  46. package/dist/cli/stack-config.js +140 -125
  47. package/dist/cli/stack-config.js.map +1 -1
  48. package/dist/cli/types.d.ts +46 -10
  49. package/dist/cli/types.d.ts.map +1 -1
  50. package/dist/cli/types.js +26 -1
  51. package/dist/cli/types.js.map +1 -1
  52. package/dist/cli/update.d.ts.map +1 -1
  53. package/dist/cli/update.js +66 -19
  54. package/dist/cli/update.js.map +1 -1
  55. package/dist/orchestrator/plugins/chrome-devtools/config.d.ts +3 -0
  56. package/dist/orchestrator/plugins/chrome-devtools/config.d.ts.map +1 -0
  57. package/dist/orchestrator/plugins/chrome-devtools/config.js +28 -0
  58. package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -0
  59. package/dist/orchestrator/plugins/contentful/config.d.ts +3 -0
  60. package/dist/orchestrator/plugins/contentful/config.d.ts.map +1 -0
  61. package/dist/orchestrator/plugins/contentful/config.js +48 -0
  62. package/dist/orchestrator/plugins/contentful/config.js.map +1 -0
  63. package/dist/orchestrator/plugins/convex/config.d.ts +3 -0
  64. package/dist/orchestrator/plugins/convex/config.d.ts.map +1 -0
  65. package/dist/orchestrator/plugins/convex/config.js +32 -0
  66. package/dist/orchestrator/plugins/convex/config.js.map +1 -0
  67. package/dist/orchestrator/plugins/index.d.ts +28 -0
  68. package/dist/orchestrator/plugins/index.d.ts.map +1 -0
  69. package/dist/orchestrator/plugins/index.js +63 -0
  70. package/dist/orchestrator/plugins/index.js.map +1 -0
  71. package/dist/orchestrator/plugins/jira/config.d.ts +3 -0
  72. package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -0
  73. package/dist/orchestrator/plugins/jira/config.js +29 -0
  74. package/dist/orchestrator/plugins/jira/config.js.map +1 -0
  75. package/dist/orchestrator/plugins/linear/config.d.ts +3 -0
  76. package/dist/orchestrator/plugins/linear/config.d.ts.map +1 -0
  77. package/dist/orchestrator/plugins/linear/config.js +33 -0
  78. package/dist/orchestrator/plugins/linear/config.js.map +1 -0
  79. package/dist/orchestrator/plugins/nx/config.d.ts +3 -0
  80. package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -0
  81. package/dist/orchestrator/plugins/nx/config.js +28 -0
  82. package/dist/orchestrator/plugins/nx/config.js.map +1 -0
  83. package/dist/orchestrator/plugins/sanity/config.d.ts +3 -0
  84. package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -0
  85. package/dist/orchestrator/plugins/sanity/config.js +43 -0
  86. package/dist/orchestrator/plugins/sanity/config.js.map +1 -0
  87. package/dist/orchestrator/plugins/slack/config.d.ts +3 -0
  88. package/dist/orchestrator/plugins/slack/config.d.ts.map +1 -0
  89. package/dist/orchestrator/plugins/slack/config.js +34 -0
  90. package/dist/orchestrator/plugins/slack/config.js.map +1 -0
  91. package/dist/orchestrator/plugins/strapi/config.d.ts +3 -0
  92. package/dist/orchestrator/plugins/strapi/config.d.ts.map +1 -0
  93. package/dist/orchestrator/plugins/strapi/config.js +40 -0
  94. package/dist/orchestrator/plugins/strapi/config.js.map +1 -0
  95. package/dist/orchestrator/plugins/supabase/config.d.ts +3 -0
  96. package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -0
  97. package/dist/orchestrator/plugins/supabase/config.js +33 -0
  98. package/dist/orchestrator/plugins/supabase/config.js.map +1 -0
  99. package/dist/orchestrator/plugins/teams/config.d.ts +3 -0
  100. package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -0
  101. package/dist/orchestrator/plugins/teams/config.js +43 -0
  102. package/dist/orchestrator/plugins/teams/config.js.map +1 -0
  103. package/dist/orchestrator/plugins/types.d.ts +61 -0
  104. package/dist/orchestrator/plugins/types.d.ts.map +1 -0
  105. package/dist/orchestrator/plugins/types.js +2 -0
  106. package/dist/orchestrator/plugins/types.js.map +1 -0
  107. package/dist/orchestrator/plugins/vercel/config.d.ts +3 -0
  108. package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -0
  109. package/dist/orchestrator/plugins/vercel/config.js +32 -0
  110. package/dist/orchestrator/plugins/vercel/config.js.map +1 -0
  111. package/package.json +1 -1
  112. package/src/cli/adapters/claude-code.ts +40 -6
  113. package/src/cli/adapters/cursor.ts +46 -6
  114. package/src/cli/adapters/opencode.ts +320 -0
  115. package/src/cli/adapters/vscode.ts +43 -9
  116. package/src/cli/copy.ts +32 -0
  117. package/src/cli/detect.ts +483 -0
  118. package/src/cli/gitignore.ts +0 -3
  119. package/src/cli/init.ts +169 -96
  120. package/src/cli/manifest.ts +2 -1
  121. package/src/cli/mcp.ts +131 -51
  122. package/src/cli/prompt.ts +299 -0
  123. package/src/cli/stack-config.ts +187 -145
  124. package/src/cli/types.ts +60 -9
  125. package/src/cli/update.ts +78 -20
  126. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  127. package/src/orchestrator/agent-workflows/README.md +1 -1
  128. package/src/orchestrator/agent-workflows/bug-fix.md +12 -12
  129. package/src/orchestrator/agent-workflows/data-pipeline.md +21 -20
  130. package/src/orchestrator/agent-workflows/database-migration.md +11 -11
  131. package/src/orchestrator/agent-workflows/feature-implementation.md +10 -10
  132. package/src/orchestrator/agent-workflows/performance-optimization.md +6 -6
  133. package/src/orchestrator/agent-workflows/refactoring.md +10 -10
  134. package/src/orchestrator/agent-workflows/schema-changes.md +8 -8
  135. package/src/orchestrator/agent-workflows/security-audit.md +12 -12
  136. package/src/orchestrator/agent-workflows/shared-delivery-phase.md +5 -5
  137. package/src/orchestrator/agents/api-designer.agent.md +2 -2
  138. package/src/orchestrator/agents/architect.agent.md +2 -2
  139. package/src/orchestrator/agents/content-engineer.agent.md +4 -4
  140. package/src/orchestrator/agents/copywriter.agent.md +2 -2
  141. package/src/orchestrator/agents/data-expert.agent.md +6 -6
  142. package/src/orchestrator/agents/database-engineer.agent.md +4 -4
  143. package/src/orchestrator/agents/developer.agent.md +5 -5
  144. package/src/orchestrator/agents/devops-expert.agent.md +5 -5
  145. package/src/orchestrator/agents/documentation-writer.agent.md +1 -1
  146. package/src/orchestrator/agents/performance-expert.agent.md +3 -3
  147. package/src/orchestrator/agents/release-manager.agent.md +4 -4
  148. package/src/orchestrator/agents/researcher.agent.md +19 -3
  149. package/src/orchestrator/agents/reviewer.agent.md +2 -4
  150. package/src/orchestrator/agents/security-expert.agent.md +4 -4
  151. package/src/orchestrator/agents/seo-specialist.agent.md +2 -2
  152. package/src/orchestrator/agents/team-lead.agent.md +97 -101
  153. package/src/orchestrator/agents/testing-expert.agent.md +5 -5
  154. package/src/orchestrator/agents/ui-ux-expert.agent.md +7 -7
  155. package/src/orchestrator/copilot-instructions.md +1 -1
  156. package/src/orchestrator/customizations/AGENT-FAILURES.md +1 -1
  157. package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +12 -12
  158. package/src/orchestrator/customizations/DISPUTES.md +5 -5
  159. package/src/orchestrator/customizations/KNOWN-ISSUES.md +30 -0
  160. package/src/orchestrator/customizations/LESSONS-LEARNED.md +7 -7
  161. package/src/orchestrator/customizations/README.md +5 -2
  162. package/src/orchestrator/customizations/agents/agent-registry.md +1 -1
  163. package/src/orchestrator/customizations/agents/skill-matrix.md +12 -7
  164. package/src/orchestrator/customizations/logs/README.md +1 -1
  165. package/src/orchestrator/customizations/project/decisions.md +31 -0
  166. package/src/orchestrator/customizations/project/docs-structure.md +16 -5
  167. package/src/orchestrator/customizations/project/roadmap.md +24 -0
  168. package/src/orchestrator/customizations/project/tracker-config.md +1 -1
  169. package/src/orchestrator/customizations/stack/cms-config.md +1 -1
  170. package/src/orchestrator/customizations/stack/notifications-config.md +1 -1
  171. package/src/orchestrator/instructions/ai-optimization.instructions.md +2 -2
  172. package/src/orchestrator/instructions/general.instructions.md +102 -40
  173. package/src/orchestrator/{skills/browser-testing → plugins/chrome-devtools}/SKILL.md +1 -1
  174. package/src/orchestrator/plugins/chrome-devtools/config.ts +29 -0
  175. package/src/orchestrator/{skills/contentful-cms → plugins/contentful}/SKILL.md +1 -1
  176. package/src/orchestrator/plugins/contentful/config.ts +49 -0
  177. package/src/orchestrator/{skills/convex-database → plugins/convex}/SKILL.md +1 -1
  178. package/src/orchestrator/plugins/convex/config.ts +33 -0
  179. package/src/orchestrator/plugins/index.ts +85 -0
  180. package/src/orchestrator/{skills/jira-management → plugins/jira}/SKILL.md +3 -3
  181. package/src/orchestrator/plugins/jira/config.ts +30 -0
  182. package/src/orchestrator/{skills/task-management → plugins/linear}/SKILL.md +3 -3
  183. package/src/orchestrator/plugins/linear/config.ts +34 -0
  184. package/src/orchestrator/{skills/nx-workspace → plugins/nx}/SKILL.md +1 -1
  185. package/src/orchestrator/plugins/nx/config.ts +29 -0
  186. package/src/orchestrator/{skills/sanity-cms → plugins/sanity}/SKILL.md +1 -1
  187. package/src/orchestrator/plugins/sanity/config.ts +44 -0
  188. package/src/orchestrator/{skills/slack-notifications → plugins/slack}/SKILL.md +2 -2
  189. package/src/orchestrator/plugins/slack/config.ts +35 -0
  190. package/src/orchestrator/{skills/strapi-cms → plugins/strapi}/SKILL.md +1 -1
  191. package/src/orchestrator/plugins/strapi/config.ts +41 -0
  192. package/src/orchestrator/{skills/supabase-database → plugins/supabase}/SKILL.md +1 -1
  193. package/src/orchestrator/plugins/supabase/config.ts +34 -0
  194. package/src/orchestrator/{skills/teams-notifications → plugins/teams}/SKILL.md +2 -2
  195. package/src/orchestrator/plugins/teams/config.ts +44 -0
  196. package/src/orchestrator/plugins/types.ts +79 -0
  197. package/src/orchestrator/plugins/vercel/config.ts +33 -0
  198. package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +59 -12
  199. package/src/orchestrator/prompts/brainstorm.prompt.md +3 -3
  200. package/src/orchestrator/prompts/bug-fix.prompt.md +18 -18
  201. package/src/orchestrator/prompts/create-skill.prompt.md +50 -32
  202. package/src/orchestrator/prompts/generate-task-spec.prompt.md +3 -3
  203. package/src/orchestrator/prompts/implement-feature.prompt.md +26 -26
  204. package/src/orchestrator/prompts/metrics-report.prompt.md +11 -11
  205. package/src/orchestrator/prompts/quick-refinement.prompt.md +16 -16
  206. package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +2 -2
  207. package/src/orchestrator/skills/accessibility-standards/SKILL.md +1 -1
  208. package/src/orchestrator/skills/agent-hooks/SKILL.md +27 -18
  209. package/src/orchestrator/skills/agent-memory/SKILL.md +7 -7
  210. package/src/orchestrator/skills/api-patterns/SKILL.md +6 -6
  211. package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
  212. package/src/orchestrator/skills/context-map/SKILL.md +4 -4
  213. package/src/orchestrator/skills/data-engineering/SKILL.md +7 -4
  214. package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +2 -2
  215. package/src/orchestrator/skills/documentation-standards/SKILL.md +1 -1
  216. package/src/orchestrator/skills/fast-review/SKILL.md +3 -3
  217. package/src/orchestrator/skills/frontend-design/SKILL.md +1 -1
  218. package/src/orchestrator/skills/memory-merger/SKILL.md +8 -8
  219. package/src/orchestrator/skills/nextjs-patterns/SKILL.md +1 -1
  220. package/src/orchestrator/skills/panel-majority-vote/SKILL.md +2 -2
  221. package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +1 -1
  222. package/src/orchestrator/skills/performance-optimization/SKILL.md +1 -1
  223. package/src/orchestrator/skills/react-development/SKILL.md +3 -3
  224. package/src/orchestrator/skills/security-hardening/SKILL.md +27 -27
  225. package/src/orchestrator/skills/self-improvement/SKILL.md +14 -13
  226. package/src/orchestrator/skills/seo-patterns/SKILL.md +1 -1
  227. package/src/orchestrator/skills/session-checkpoints/SKILL.md +19 -19
  228. package/src/orchestrator/skills/team-lead-reference/SKILL.md +9 -9
  229. package/src/orchestrator/skills/testing-workflow/SKILL.md +13 -13
  230. package/src/orchestrator/skills/validation-gates/SKILL.md +8 -15
  231. package/src/orchestrator/mcp.json +0 -61
@@ -0,0 +1,320 @@
1
+ import { resolve, basename } from 'node:path'
2
+ import { mkdir, writeFile, readdir, readFile, unlink, rm } from 'node:fs/promises'
3
+ import { existsSync } from 'node:fs'
4
+ import { copyDir, getOrchestratorRoot, getPluginsRoot, getPluginSkillEntries } from '../copy.js'
5
+ import { scaffoldMcpConfig } from '../mcp.js'
6
+ import { getExcludedSkills, getExcludedAgents, getCustomizationsTransform, getIncludedPluginIds } from '../stack-config.js'
7
+ import type { CopyResults, ManagedPaths, RepoInfo, StackConfig } from '../types.js'
8
+
9
+ /**
10
+ * OpenCode adapter.
11
+ *
12
+ * Generates AGENTS.md (root instructions) and .opencode/ structure.
13
+ *
14
+ * copilot-instructions.md → AGENTS.md (combined with instructions/)
15
+ * skills/*\/SKILL.md → .opencode/skills/<name>.md
16
+ * agents/*.agent.md → .opencode/agents/<name>.md
17
+ * agent-workflows/*.md → .opencode/workflows/<name>.md
18
+ * prompts/*.prompt.md → .opencode/prompts/<name>.md
19
+ * customizations/ → .opencode/customizations/ (scaffolded once)
20
+ * mcp.json → opencode.json (OpenCode format: type local/remote)
21
+ */
22
+
23
+ export const IDE_ID = 'opencode'
24
+ export const IDE_LABEL = 'OpenCode'
25
+
26
+ // ─── Helpers ──────────────────────────────────────────────────────
27
+
28
+ function stripFrontmatter(content: string): string {
29
+ const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
30
+ return m ? m[2].trim() : content.trim()
31
+ }
32
+
33
+ function parseFrontmatterMeta(content: string): Record<string, string> {
34
+ const m = content.match(/^---\n([\s\S]*?)\n---/)
35
+ if (!m) return {}
36
+ const meta: Record<string, string> = {}
37
+ for (const line of m[1].split('\n')) {
38
+ const kv = line.match(/^(\w[\w-]*):\s*['"]?(.+?)['"]?\s*$/)
39
+ if (kv) meta[kv[1]] = kv[2]
40
+ }
41
+ return meta
42
+ }
43
+
44
+ // ─── Install ──────────────────────────────────────────────────────
45
+
46
+ export async function install(
47
+ pkgRoot: string,
48
+ projectRoot: string,
49
+ stack?: StackConfig,
50
+ repoInfo?: RepoInfo
51
+ ): Promise<CopyResults> {
52
+ const srcRoot = getOrchestratorRoot(pkgRoot)
53
+ const results: CopyResults = { copied: [], skipped: [], created: [] }
54
+
55
+ const excludedSkills = stack ? getExcludedSkills(stack) : new Set<string>()
56
+ const excludedAgents = stack ? getExcludedAgents(stack) : new Set<string>()
57
+
58
+ // 1. Build AGENTS.md ← instructions/* + agent index + skill index
59
+ const agentsMd = resolve(projectRoot, 'AGENTS.md')
60
+ if (!existsSync(agentsMd)) {
61
+ const sections: string[] = []
62
+
63
+ sections.push(
64
+ '# Project Instructions\n\n' +
65
+ 'All conventions, architecture, and project context are embedded below. ' +
66
+ 'Skills are in `.opencode/skills/` — read them when a task matches. ' +
67
+ 'Agent definitions are in `.opencode/agents/` — read the relevant file when adopting a persona.'
68
+ )
69
+
70
+ // Always-loaded instruction files
71
+ const instDir = resolve(srcRoot, 'instructions')
72
+ if (existsSync(instDir)) {
73
+ for (const file of (await readdir(instDir)).sort()) {
74
+ if (!file.endsWith('.md')) continue
75
+ const content = await readFile(resolve(instDir, file), 'utf8')
76
+ sections.push(
77
+ `\n---\n\n<!-- Source: instructions/${file} -->\n\n${stripFrontmatter(content)}`
78
+ )
79
+ }
80
+ }
81
+
82
+ // Agent reference
83
+ const agentsDir = resolve(srcRoot, 'agents')
84
+ if (existsSync(agentsDir)) {
85
+ const agentLines: string[] = ['\n---\n\n## Agent Definitions\n']
86
+ agentLines.push(
87
+ 'The following agent personas are available. Adopt the appropriate persona when asked.\n'
88
+ )
89
+ for (const file of (await readdir(agentsDir)).sort()) {
90
+ if (!file.endsWith('.md')) continue
91
+ if (excludedAgents.has(file)) continue
92
+ const meta = parseFrontmatterMeta(
93
+ await readFile(resolve(agentsDir, file), 'utf8')
94
+ )
95
+ const name = meta['name'] ?? basename(file, '.agent.md')
96
+ const desc = meta['description'] ?? ''
97
+ agentLines.push(`- **${name}**: ${desc}`)
98
+ }
99
+ agentLines.push(
100
+ '\nFull agent definitions are in `.opencode/agents/`. Read the relevant file when adopting a persona.'
101
+ )
102
+ sections.push(agentLines.join('\n'))
103
+ }
104
+
105
+ // Skill index
106
+ const skillsDir = resolve(srcRoot, 'skills')
107
+ if (existsSync(skillsDir)) {
108
+ const skillLines: string[] = ['\n---\n\n## Available Skills\n']
109
+ skillLines.push(
110
+ 'Skills are on-demand knowledge files. Read the file when the task matches.\n'
111
+ )
112
+ const subdirs = (
113
+ await readdir(skillsDir, { withFileTypes: true })
114
+ ).filter((e) => e.isDirectory())
115
+ for (const entry of subdirs.sort((a, b) =>
116
+ a.name.localeCompare(b.name)
117
+ )) {
118
+ if (excludedSkills.has(entry.name)) continue
119
+ const skillFile = resolve(skillsDir, entry.name, 'SKILL.md')
120
+ if (!existsSync(skillFile)) continue
121
+ const meta = parseFrontmatterMeta(await readFile(skillFile, 'utf8'))
122
+ const desc = meta['description'] ?? ''
123
+ skillLines.push(
124
+ `- **${entry.name}** (\`.opencode/skills/${entry.name}.md\`): ${desc}`
125
+ )
126
+ }
127
+
128
+ // Plugin skills
129
+ const pluginsRoot = getPluginsRoot(pkgRoot)
130
+ const includedPlugins = stack ? getIncludedPluginIds(stack) : undefined
131
+ const pluginEntries = await getPluginSkillEntries(pluginsRoot, includedPlugins)
132
+ for (const { id, skillPath } of pluginEntries.sort((a, b) => a.id.localeCompare(b.id))) {
133
+ const pluginMeta = parseFrontmatterMeta(await readFile(skillPath, 'utf8'))
134
+ const pluginDesc = pluginMeta['description'] ?? ''
135
+ skillLines.push(
136
+ `- **${id}** (\`.opencode/skills/${id}.md\`): ${pluginDesc}`
137
+ )
138
+ }
139
+
140
+ sections.push(skillLines.join('\n'))
141
+ }
142
+
143
+ await writeFile(agentsMd, sections.join('\n') + '\n')
144
+ results.created.push(agentsMd)
145
+ } else {
146
+ results.skipped.push(agentsMd)
147
+ }
148
+
149
+ const openDir = resolve(projectRoot, '.opencode')
150
+
151
+ // 2. Agent definitions → .opencode/agents/
152
+ const agentsDir = resolve(srcRoot, 'agents')
153
+ if (existsSync(agentsDir)) {
154
+ const destAgents = resolve(openDir, 'agents')
155
+ await mkdir(destAgents, { recursive: true })
156
+ for (const file of await readdir(agentsDir)) {
157
+ if (!file.endsWith('.md')) continue
158
+ if (excludedAgents.has(file)) continue
159
+ const destPath = resolve(destAgents, file)
160
+ if (existsSync(destPath)) {
161
+ results.skipped.push(destPath)
162
+ continue
163
+ }
164
+ const content = await readFile(resolve(agentsDir, file), 'utf8')
165
+ await writeFile(destPath, stripFrontmatter(content) + '\n')
166
+ results.created.push(destPath)
167
+ }
168
+ }
169
+
170
+ // 3. Skills → .opencode/skills/<name>.md
171
+ const skillsDir = resolve(srcRoot, 'skills')
172
+ if (existsSync(skillsDir)) {
173
+ const destSkills = resolve(openDir, 'skills')
174
+ await mkdir(destSkills, { recursive: true })
175
+ const subdirs = (
176
+ await readdir(skillsDir, { withFileTypes: true })
177
+ ).filter((e) => e.isDirectory())
178
+ for (const entry of subdirs) {
179
+ if (excludedSkills.has(entry.name)) continue
180
+ const skillFile = resolve(skillsDir, entry.name, 'SKILL.md')
181
+ if (!existsSync(skillFile)) continue
182
+ const destPath = resolve(destSkills, `${entry.name}.md`)
183
+ if (existsSync(destPath)) {
184
+ results.skipped.push(destPath)
185
+ continue
186
+ }
187
+ const content = await readFile(skillFile, 'utf8')
188
+ await writeFile(destPath, stripFrontmatter(content) + '\n')
189
+ results.created.push(destPath)
190
+ }
191
+ }
192
+
193
+ // 3b. Plugin skills → .opencode/skills/<plugin-id>.md
194
+ {
195
+ const pluginsRoot = getPluginsRoot(pkgRoot)
196
+ const includedPlugins = stack ? getIncludedPluginIds(stack) : undefined
197
+ const pluginEntries = await getPluginSkillEntries(pluginsRoot, includedPlugins)
198
+ const destSkills = resolve(openDir, 'skills')
199
+ await mkdir(destSkills, { recursive: true })
200
+ for (const { id, skillPath } of pluginEntries) {
201
+ const destPath = resolve(destSkills, `${id}.md`)
202
+ if (existsSync(destPath)) {
203
+ results.skipped.push(destPath)
204
+ continue
205
+ }
206
+ const content = await readFile(skillPath, 'utf8')
207
+ await writeFile(destPath, stripFrontmatter(content) + '\n')
208
+ results.created.push(destPath)
209
+ }
210
+ }
211
+
212
+ // 4. Prompts → .opencode/prompts/<name>.md
213
+ const promptDir = resolve(srcRoot, 'prompts')
214
+ if (existsSync(promptDir)) {
215
+ const destPrompts = resolve(openDir, 'prompts')
216
+ await mkdir(destPrompts, { recursive: true })
217
+ for (const file of await readdir(promptDir)) {
218
+ if (!file.endsWith('.md')) continue
219
+ const name = basename(file, '.prompt.md') || basename(file, '.md')
220
+ const destPath = resolve(destPrompts, `${name}.md`)
221
+ if (existsSync(destPath)) {
222
+ results.skipped.push(destPath)
223
+ continue
224
+ }
225
+ const content = await readFile(resolve(promptDir, file), 'utf8')
226
+ await writeFile(destPath, stripFrontmatter(content) + '\n')
227
+ results.created.push(destPath)
228
+ }
229
+ }
230
+
231
+ // 5. Agent Workflows → .opencode/workflows/<name>.md
232
+ const wfDir = resolve(srcRoot, 'agent-workflows')
233
+ if (existsSync(wfDir)) {
234
+ const destWf = resolve(openDir, 'workflows')
235
+ await mkdir(destWf, { recursive: true })
236
+ for (const file of await readdir(wfDir)) {
237
+ if (!file.endsWith('.md')) continue
238
+ if (file === 'README.md') continue
239
+ const name = basename(file, '.md')
240
+ const destPath = resolve(destWf, `${name}.md`)
241
+ if (existsSync(destPath)) {
242
+ results.skipped.push(destPath)
243
+ continue
244
+ }
245
+ const content = await readFile(resolve(wfDir, file), 'utf8')
246
+ await writeFile(destPath, stripFrontmatter(content) + '\n')
247
+ results.created.push(destPath)
248
+ }
249
+ }
250
+
251
+ // 6. Customizations (scaffold once, pre-populated with stack choices)
252
+ const custDir = resolve(srcRoot, 'customizations')
253
+ if (existsSync(custDir)) {
254
+ const destCust = resolve(openDir, 'customizations')
255
+ const custTransform = stack ? getCustomizationsTransform(stack) : undefined
256
+ const sub = await copyDir(custDir, destCust, { transform: custTransform })
257
+ results.created.push(...sub.created)
258
+ results.skipped.push(...sub.skipped)
259
+ }
260
+
261
+ // 7. MCP server config → opencode.json (OpenCode format)
262
+ const mcpResult = await scaffoldMcpConfig(
263
+ projectRoot,
264
+ 'opencode.json',
265
+ stack,
266
+ repoInfo,
267
+ 'opencode'
268
+ )
269
+ results[mcpResult.action].push(mcpResult.path)
270
+
271
+ return results
272
+ }
273
+
274
+ // ─── Update ───────────────────────────────────────────────────────
275
+
276
+ export async function update(
277
+ pkgRoot: string,
278
+ projectRoot: string,
279
+ stack?: StackConfig
280
+ ): Promise<CopyResults> {
281
+ const results: CopyResults = { copied: [], skipped: [], created: [] }
282
+ const openDir = resolve(projectRoot, '.opencode')
283
+
284
+ // 1. Regenerate AGENTS.md (overwrite)
285
+ const agentsMd = resolve(projectRoot, 'AGENTS.md')
286
+ if (existsSync(agentsMd)) {
287
+ await unlink(agentsMd)
288
+ }
289
+
290
+ // 2. Remove existing framework files so install() recreates them
291
+ const frameworkDirs = ['agents', 'skills', 'prompts', 'workflows']
292
+ for (const dir of frameworkDirs) {
293
+ const dirPath = resolve(openDir, dir)
294
+ if (existsSync(dirPath)) {
295
+ await rm(dirPath, { recursive: true })
296
+ }
297
+ }
298
+
299
+ // 3. Re-run full install
300
+ const installResult = await install(pkgRoot, projectRoot, stack)
301
+ results.copied.push(...installResult.created)
302
+ results.skipped.push(...installResult.skipped)
303
+
304
+ return results
305
+ }
306
+
307
+ // ─── Managed paths ────────────────────────────────────────────────
308
+
309
+ export function getManagedPaths(): ManagedPaths {
310
+ return {
311
+ framework: [
312
+ 'AGENTS.md',
313
+ '.opencode/agents/',
314
+ '.opencode/skills/',
315
+ '.opencode/prompts/',
316
+ '.opencode/workflows/',
317
+ ],
318
+ customizable: ['.opencode/customizations/', 'opencode.json'],
319
+ }
320
+ }
@@ -1,10 +1,10 @@
1
1
  import { resolve } from 'node:path'
2
- import { mkdir, readFile, writeFile } from 'node:fs/promises'
2
+ import { mkdir, readFile, writeFile, copyFile } from 'node:fs/promises'
3
3
  import { existsSync } from 'node:fs'
4
- import { copyDir, getOrchestratorRoot, removeDirIfExists } from '../copy.js'
4
+ import { copyDir, getOrchestratorRoot, removeDirIfExists, getPluginsRoot, getPluginSkillEntries } from '../copy.js'
5
5
  import { scaffoldMcpConfig } from '../mcp.js'
6
- import { getExcludedSkills, getExcludedAgents, getCustomizationsTransform } from '../stack-config.js'
7
- import type { CopyResults, ManagedPaths, StackConfig } from '../types.js'
6
+ import { getExcludedSkills, getExcludedAgents, getCustomizationsTransform, getIncludedPluginIds, getAgentTransform } from '../stack-config.js'
7
+ import type { CopyResults, CopyDirOptions, ManagedPaths, RepoInfo, StackConfig } from '../types.js'
8
8
 
9
9
  /**
10
10
  * VS Code / GitHub Copilot adapter.
@@ -38,7 +38,8 @@ const CUSTOMIZABLE_DIRS = ['customizations']
38
38
  export async function install(
39
39
  pkgRoot: string,
40
40
  projectRoot: string,
41
- stack?: StackConfig
41
+ stack?: StackConfig,
42
+ repoInfo?: RepoInfo
42
43
  ): Promise<CopyResults> {
43
44
  const srcRoot = getOrchestratorRoot(pkgRoot)
44
45
  const destRoot = resolve(projectRoot, '.github')
@@ -68,18 +69,36 @@ export async function install(
68
69
 
69
70
  // Build filter based on directory type
70
71
  let filter: ((_name: string, _srcPath: string) => boolean) | undefined
72
+ let transform: CopyDirOptions['transform'] | undefined
71
73
  if (dir === 'skills') {
72
74
  filter = (name) => !excludedSkills.has(name)
73
75
  } else if (dir === 'agents') {
74
76
  filter = (name) => !excludedAgents.has(name)
77
+ transform = stack ? getAgentTransform(stack) : undefined
75
78
  }
76
79
 
77
- const sub = await copyDir(srcDir, destDir, { filter })
80
+ const sub = await copyDir(srcDir, destDir, { filter, transform })
78
81
  results.copied.push(...sub.copied)
79
82
  results.skipped.push(...sub.skipped)
80
83
  results.created.push(...sub.created)
81
84
  }
82
85
 
86
+ // Plugin skills → .github/skills/<plugin-id>/
87
+ const pluginsRoot = getPluginsRoot(pkgRoot)
88
+ const includedPlugins = stack ? getIncludedPluginIds(stack) : undefined
89
+ const pluginSkills = await getPluginSkillEntries(pluginsRoot, includedPlugins)
90
+ for (const { id, skillPath } of pluginSkills) {
91
+ const pluginDestDir = resolve(destRoot, 'skills', id)
92
+ await mkdir(pluginDestDir, { recursive: true })
93
+ const destPath = resolve(pluginDestDir, 'SKILL.md')
94
+ if (existsSync(destPath)) {
95
+ results.skipped.push(destPath)
96
+ } else {
97
+ await copyFile(skillPath, destPath)
98
+ results.created.push(destPath)
99
+ }
100
+ }
101
+
83
102
  // Customization templates (scaffold once)
84
103
  const custTransform = stack ? getCustomizationsTransform(stack) : undefined
85
104
  for (const dir of CUSTOMIZABLE_DIRS) {
@@ -94,10 +113,11 @@ export async function install(
94
113
 
95
114
  // MCP server config → .vscode/mcp.json (scaffold once)
96
115
  const mcpResult = await scaffoldMcpConfig(
97
- pkgRoot,
98
116
  projectRoot,
99
117
  '.vscode/mcp.json',
100
- stack
118
+ stack,
119
+ repoInfo,
120
+ 'vscode'
101
121
  )
102
122
  results[mcpResult.action].push(mcpResult.path)
103
123
 
@@ -137,18 +157,32 @@ export async function update(
137
157
  const destDir = resolve(destRoot, dir)
138
158
 
139
159
  let filter: ((_name: string, _srcPath: string) => boolean) | undefined
160
+ let transform: CopyDirOptions['transform'] | undefined
140
161
  if (dir === 'skills') {
141
162
  filter = (name) => !excludedSkills.has(name)
142
163
  } else if (dir === 'agents') {
143
164
  filter = (name) => !excludedAgents.has(name)
165
+ transform = stack ? getAgentTransform(stack) : undefined
144
166
  }
145
167
 
146
- const sub = await copyDir(srcDir, destDir, { overwrite: true, filter })
168
+ const sub = await copyDir(srcDir, destDir, { overwrite: true, filter, transform })
147
169
  // All re-installed framework files count as "updated" (copied), not "created"
148
170
  results.copied.push(...sub.copied, ...sub.created)
149
171
  results.skipped.push(...sub.skipped)
150
172
  }
151
173
 
174
+ // Plugin skills → .github/skills/<plugin-id>/ (overwrite)
175
+ const pluginsRoot = getPluginsRoot(pkgRoot)
176
+ const includedPlugins = stack ? getIncludedPluginIds(stack) : undefined
177
+ const pluginSkills = await getPluginSkillEntries(pluginsRoot, includedPlugins)
178
+ for (const { id, skillPath } of pluginSkills) {
179
+ const pluginDestDir = resolve(destRoot, 'skills', id)
180
+ await mkdir(pluginDestDir, { recursive: true })
181
+ const destPath = resolve(pluginDestDir, 'SKILL.md')
182
+ await copyFile(skillPath, destPath)
183
+ results.copied.push(destPath)
184
+ }
185
+
152
186
  // Customizations are NEVER overwritten during update.
153
187
 
154
188
  return results
package/src/cli/copy.ts CHANGED
@@ -77,3 +77,35 @@ export async function removeDirIfExists(dirPath: string): Promise<void> {
77
77
  await rm(dirPath, { recursive: true });
78
78
  }
79
79
  }
80
+
81
+ /**
82
+ * Resolve the plugins source directory from the CLI package root.
83
+ */
84
+ export function getPluginsRoot(pkgRoot: string): string {
85
+ return resolve(pkgRoot, 'src', 'orchestrator', 'plugins');
86
+ }
87
+
88
+ /**
89
+ * Scan plugin directories for SKILL.md files.
90
+ * Returns entries with plugin ID and skill file path.
91
+ */
92
+ export async function getPluginSkillEntries(
93
+ pluginsRoot: string,
94
+ includedPluginIds?: Set<string>
95
+ ): Promise<Array<{ id: string; skillPath: string }>> {
96
+ if (!existsSync(pluginsRoot)) return [];
97
+
98
+ const entries = await readdir(pluginsRoot, { withFileTypes: true });
99
+ const results: Array<{ id: string; skillPath: string }> = [];
100
+
101
+ for (const entry of entries) {
102
+ if (!entry.isDirectory()) continue;
103
+ if (includedPluginIds && !includedPluginIds.has(entry.name)) continue;
104
+ const skillPath = resolve(pluginsRoot, entry.name, 'SKILL.md');
105
+ if (existsSync(skillPath)) {
106
+ results.push({ id: entry.name, skillPath });
107
+ }
108
+ }
109
+
110
+ return results;
111
+ }