opencastle 0.6.0 → 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.
- package/README.md +5 -4
- package/dist/cli/adapters/claude-code.d.ts.map +1 -1
- package/dist/cli/adapters/claude-code.js +30 -3
- package/dist/cli/adapters/claude-code.js.map +1 -1
- package/dist/cli/adapters/cursor.d.ts.map +1 -1
- package/dist/cli/adapters/cursor.js +27 -3
- package/dist/cli/adapters/cursor.js.map +1 -1
- package/dist/cli/adapters/opencode.d.ts +20 -0
- package/dist/cli/adapters/opencode.d.ts.map +1 -0
- package/dist/cli/adapters/opencode.js +265 -0
- package/dist/cli/adapters/opencode.js.map +1 -0
- package/dist/cli/adapters/vscode.d.ts.map +1 -1
- package/dist/cli/adapters/vscode.js +37 -6
- package/dist/cli/adapters/vscode.js.map +1 -1
- package/dist/cli/copy.d.ts +12 -0
- package/dist/cli/copy.d.ts.map +1 -1
- package/dist/cli/copy.js +27 -0
- package/dist/cli/copy.js.map +1 -1
- package/dist/cli/detect.d.ts +1 -1
- package/dist/cli/detect.js +21 -15
- package/dist/cli/detect.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +143 -94
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/manifest.d.ts +1 -1
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +2 -1
- package/dist/cli/manifest.js.map +1 -1
- package/dist/cli/mcp.d.ts +6 -6
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +104 -33
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/prompt.d.ts +19 -0
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +143 -0
- package/dist/cli/prompt.js.map +1 -1
- package/dist/cli/stack-config.d.ts +23 -0
- package/dist/cli/stack-config.d.ts.map +1 -1
- package/dist/cli/stack-config.js +128 -124
- package/dist/cli/stack-config.js.map +1 -1
- package/dist/cli/types.d.ts +26 -9
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js +26 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +60 -19
- package/dist/cli/update.js.map +1 -1
- package/dist/orchestrator/plugins/chrome-devtools/config.d.ts +3 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.js +28 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -0
- package/dist/orchestrator/plugins/contentful/config.d.ts +3 -0
- package/dist/orchestrator/plugins/contentful/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/contentful/config.js +48 -0
- package/dist/orchestrator/plugins/contentful/config.js.map +1 -0
- package/dist/orchestrator/plugins/convex/config.d.ts +3 -0
- package/dist/orchestrator/plugins/convex/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/convex/config.js +32 -0
- package/dist/orchestrator/plugins/convex/config.js.map +1 -0
- package/dist/orchestrator/plugins/index.d.ts +28 -0
- package/dist/orchestrator/plugins/index.d.ts.map +1 -0
- package/dist/orchestrator/plugins/index.js +63 -0
- package/dist/orchestrator/plugins/index.js.map +1 -0
- package/dist/orchestrator/plugins/jira/config.d.ts +3 -0
- package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/jira/config.js +29 -0
- package/dist/orchestrator/plugins/jira/config.js.map +1 -0
- package/dist/orchestrator/plugins/linear/config.d.ts +3 -0
- package/dist/orchestrator/plugins/linear/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/linear/config.js +33 -0
- package/dist/orchestrator/plugins/linear/config.js.map +1 -0
- package/dist/orchestrator/plugins/nx/config.d.ts +3 -0
- package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/nx/config.js +28 -0
- package/dist/orchestrator/plugins/nx/config.js.map +1 -0
- package/dist/orchestrator/plugins/sanity/config.d.ts +3 -0
- package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/sanity/config.js +43 -0
- package/dist/orchestrator/plugins/sanity/config.js.map +1 -0
- package/dist/orchestrator/plugins/slack/config.d.ts +3 -0
- package/dist/orchestrator/plugins/slack/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/slack/config.js +34 -0
- package/dist/orchestrator/plugins/slack/config.js.map +1 -0
- package/dist/orchestrator/plugins/strapi/config.d.ts +3 -0
- package/dist/orchestrator/plugins/strapi/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/strapi/config.js +40 -0
- package/dist/orchestrator/plugins/strapi/config.js.map +1 -0
- package/dist/orchestrator/plugins/supabase/config.d.ts +3 -0
- package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/supabase/config.js +33 -0
- package/dist/orchestrator/plugins/supabase/config.js.map +1 -0
- package/dist/orchestrator/plugins/teams/config.d.ts +3 -0
- package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/teams/config.js +43 -0
- package/dist/orchestrator/plugins/teams/config.js.map +1 -0
- package/dist/orchestrator/plugins/types.d.ts +61 -0
- package/dist/orchestrator/plugins/types.d.ts.map +1 -0
- package/dist/orchestrator/plugins/types.js +2 -0
- package/dist/orchestrator/plugins/types.js.map +1 -0
- package/dist/orchestrator/plugins/vercel/config.d.ts +3 -0
- package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/vercel/config.js +32 -0
- package/dist/orchestrator/plugins/vercel/config.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/adapters/claude-code.ts +36 -4
- package/src/cli/adapters/cursor.ts +42 -4
- package/src/cli/adapters/opencode.ts +320 -0
- package/src/cli/adapters/vscode.ts +40 -8
- package/src/cli/copy.ts +32 -0
- package/src/cli/detect.ts +17 -17
- package/src/cli/init.ts +157 -99
- package/src/cli/manifest.ts +2 -1
- package/src/cli/mcp.ts +129 -50
- package/src/cli/prompt.ts +176 -0
- package/src/cli/stack-config.ts +174 -145
- package/src/cli/types.ts +39 -8
- package/src/cli/update.ts +71 -20
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/orchestrator/agent-workflows/README.md +1 -1
- package/src/orchestrator/agent-workflows/bug-fix.md +12 -12
- package/src/orchestrator/agent-workflows/data-pipeline.md +21 -20
- package/src/orchestrator/agent-workflows/database-migration.md +11 -11
- package/src/orchestrator/agent-workflows/feature-implementation.md +10 -10
- package/src/orchestrator/agent-workflows/performance-optimization.md +6 -6
- package/src/orchestrator/agent-workflows/refactoring.md +10 -10
- package/src/orchestrator/agent-workflows/schema-changes.md +8 -8
- package/src/orchestrator/agent-workflows/security-audit.md +12 -12
- package/src/orchestrator/agent-workflows/shared-delivery-phase.md +5 -5
- package/src/orchestrator/agents/api-designer.agent.md +1 -1
- package/src/orchestrator/agents/architect.agent.md +2 -2
- package/src/orchestrator/agents/content-engineer.agent.md +3 -3
- package/src/orchestrator/agents/copywriter.agent.md +2 -2
- package/src/orchestrator/agents/data-expert.agent.md +6 -6
- package/src/orchestrator/agents/database-engineer.agent.md +3 -3
- package/src/orchestrator/agents/developer.agent.md +4 -4
- package/src/orchestrator/agents/devops-expert.agent.md +5 -5
- package/src/orchestrator/agents/documentation-writer.agent.md +1 -1
- package/src/orchestrator/agents/performance-expert.agent.md +2 -2
- package/src/orchestrator/agents/release-manager.agent.md +4 -4
- package/src/orchestrator/agents/researcher.agent.md +3 -3
- package/src/orchestrator/agents/reviewer.agent.md +1 -1
- package/src/orchestrator/agents/security-expert.agent.md +4 -4
- package/src/orchestrator/agents/seo-specialist.agent.md +2 -2
- package/src/orchestrator/agents/team-lead.agent.md +56 -38
- package/src/orchestrator/agents/testing-expert.agent.md +5 -5
- package/src/orchestrator/agents/ui-ux-expert.agent.md +6 -6
- package/src/orchestrator/copilot-instructions.md +1 -1
- package/src/orchestrator/customizations/AGENT-FAILURES.md +1 -1
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +12 -12
- package/src/orchestrator/customizations/DISPUTES.md +5 -5
- package/src/orchestrator/customizations/KNOWN-ISSUES.md +30 -0
- package/src/orchestrator/customizations/LESSONS-LEARNED.md +7 -7
- package/src/orchestrator/customizations/README.md +5 -2
- package/src/orchestrator/customizations/agents/agent-registry.md +1 -1
- package/src/orchestrator/customizations/agents/skill-matrix.md +12 -7
- package/src/orchestrator/customizations/logs/README.md +1 -1
- package/src/orchestrator/customizations/project/decisions.md +31 -0
- package/src/orchestrator/customizations/project/docs-structure.md +16 -5
- package/src/orchestrator/customizations/project/roadmap.md +24 -0
- package/src/orchestrator/customizations/project/tracker-config.md +1 -1
- package/src/orchestrator/customizations/stack/cms-config.md +1 -1
- package/src/orchestrator/customizations/stack/notifications-config.md +1 -1
- package/src/orchestrator/instructions/ai-optimization.instructions.md +2 -2
- package/src/orchestrator/instructions/general.instructions.md +102 -40
- package/src/orchestrator/{skills/browser-testing → plugins/chrome-devtools}/SKILL.md +1 -1
- package/src/orchestrator/plugins/chrome-devtools/config.ts +29 -0
- package/src/orchestrator/{skills/contentful-cms → plugins/contentful}/SKILL.md +1 -1
- package/src/orchestrator/plugins/contentful/config.ts +49 -0
- package/src/orchestrator/{skills/convex-database → plugins/convex}/SKILL.md +1 -1
- package/src/orchestrator/plugins/convex/config.ts +33 -0
- package/src/orchestrator/plugins/index.ts +85 -0
- package/src/orchestrator/{skills/jira-management → plugins/jira}/SKILL.md +3 -3
- package/src/orchestrator/plugins/jira/config.ts +30 -0
- package/src/orchestrator/{skills/task-management → plugins/linear}/SKILL.md +3 -3
- package/src/orchestrator/plugins/linear/config.ts +34 -0
- package/src/orchestrator/{skills/nx-workspace → plugins/nx}/SKILL.md +1 -1
- package/src/orchestrator/plugins/nx/config.ts +29 -0
- package/src/orchestrator/{skills/sanity-cms → plugins/sanity}/SKILL.md +1 -1
- package/src/orchestrator/plugins/sanity/config.ts +44 -0
- package/src/orchestrator/{skills/slack-notifications → plugins/slack}/SKILL.md +2 -2
- package/src/orchestrator/plugins/slack/config.ts +35 -0
- package/src/orchestrator/{skills/strapi-cms → plugins/strapi}/SKILL.md +1 -1
- package/src/orchestrator/plugins/strapi/config.ts +41 -0
- package/src/orchestrator/{skills/supabase-database → plugins/supabase}/SKILL.md +1 -1
- package/src/orchestrator/plugins/supabase/config.ts +34 -0
- package/src/orchestrator/{skills/teams-notifications → plugins/teams}/SKILL.md +2 -2
- package/src/orchestrator/plugins/teams/config.ts +44 -0
- package/src/orchestrator/plugins/types.ts +79 -0
- package/src/orchestrator/plugins/vercel/config.ts +33 -0
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +8 -8
- package/src/orchestrator/prompts/brainstorm.prompt.md +3 -3
- package/src/orchestrator/prompts/bug-fix.prompt.md +18 -18
- package/src/orchestrator/prompts/create-skill.prompt.md +50 -32
- package/src/orchestrator/prompts/generate-task-spec.prompt.md +3 -3
- package/src/orchestrator/prompts/implement-feature.prompt.md +26 -26
- package/src/orchestrator/prompts/metrics-report.prompt.md +11 -11
- package/src/orchestrator/prompts/quick-refinement.prompt.md +16 -16
- package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +2 -2
- package/src/orchestrator/skills/accessibility-standards/SKILL.md +1 -1
- package/src/orchestrator/skills/agent-hooks/SKILL.md +27 -18
- package/src/orchestrator/skills/agent-memory/SKILL.md +7 -7
- package/src/orchestrator/skills/api-patterns/SKILL.md +6 -6
- package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
- package/src/orchestrator/skills/context-map/SKILL.md +4 -4
- package/src/orchestrator/skills/data-engineering/SKILL.md +7 -4
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +2 -2
- package/src/orchestrator/skills/documentation-standards/SKILL.md +1 -1
- package/src/orchestrator/skills/fast-review/SKILL.md +3 -3
- package/src/orchestrator/skills/frontend-design/SKILL.md +1 -1
- package/src/orchestrator/skills/memory-merger/SKILL.md +8 -8
- package/src/orchestrator/skills/nextjs-patterns/SKILL.md +1 -1
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +2 -2
- package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +1 -1
- package/src/orchestrator/skills/performance-optimization/SKILL.md +1 -1
- package/src/orchestrator/skills/react-development/SKILL.md +3 -3
- package/src/orchestrator/skills/security-hardening/SKILL.md +27 -27
- package/src/orchestrator/skills/self-improvement/SKILL.md +14 -13
- package/src/orchestrator/skills/seo-patterns/SKILL.md +1 -1
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +19 -19
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +9 -9
- package/src/orchestrator/skills/testing-workflow/SKILL.md +13 -13
- package/src/orchestrator/skills/validation-gates/SKILL.md +8 -15
- package/src/orchestrator/mcp.json +0 -69
|
@@ -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, RepoInfo, 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.
|
|
@@ -69,18 +69,36 @@ export async function install(
|
|
|
69
69
|
|
|
70
70
|
// Build filter based on directory type
|
|
71
71
|
let filter: ((_name: string, _srcPath: string) => boolean) | undefined
|
|
72
|
+
let transform: CopyDirOptions['transform'] | undefined
|
|
72
73
|
if (dir === 'skills') {
|
|
73
74
|
filter = (name) => !excludedSkills.has(name)
|
|
74
75
|
} else if (dir === 'agents') {
|
|
75
76
|
filter = (name) => !excludedAgents.has(name)
|
|
77
|
+
transform = stack ? getAgentTransform(stack) : undefined
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
const sub = await copyDir(srcDir, destDir, { filter })
|
|
80
|
+
const sub = await copyDir(srcDir, destDir, { filter, transform })
|
|
79
81
|
results.copied.push(...sub.copied)
|
|
80
82
|
results.skipped.push(...sub.skipped)
|
|
81
83
|
results.created.push(...sub.created)
|
|
82
84
|
}
|
|
83
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
|
+
|
|
84
102
|
// Customization templates (scaffold once)
|
|
85
103
|
const custTransform = stack ? getCustomizationsTransform(stack) : undefined
|
|
86
104
|
for (const dir of CUSTOMIZABLE_DIRS) {
|
|
@@ -95,11 +113,11 @@ export async function install(
|
|
|
95
113
|
|
|
96
114
|
// MCP server config → .vscode/mcp.json (scaffold once)
|
|
97
115
|
const mcpResult = await scaffoldMcpConfig(
|
|
98
|
-
pkgRoot,
|
|
99
116
|
projectRoot,
|
|
100
117
|
'.vscode/mcp.json',
|
|
101
118
|
stack,
|
|
102
|
-
repoInfo
|
|
119
|
+
repoInfo,
|
|
120
|
+
'vscode'
|
|
103
121
|
)
|
|
104
122
|
results[mcpResult.action].push(mcpResult.path)
|
|
105
123
|
|
|
@@ -139,18 +157,32 @@ export async function update(
|
|
|
139
157
|
const destDir = resolve(destRoot, dir)
|
|
140
158
|
|
|
141
159
|
let filter: ((_name: string, _srcPath: string) => boolean) | undefined
|
|
160
|
+
let transform: CopyDirOptions['transform'] | undefined
|
|
142
161
|
if (dir === 'skills') {
|
|
143
162
|
filter = (name) => !excludedSkills.has(name)
|
|
144
163
|
} else if (dir === 'agents') {
|
|
145
164
|
filter = (name) => !excludedAgents.has(name)
|
|
165
|
+
transform = stack ? getAgentTransform(stack) : undefined
|
|
146
166
|
}
|
|
147
167
|
|
|
148
|
-
const sub = await copyDir(srcDir, destDir, { overwrite: true, filter })
|
|
168
|
+
const sub = await copyDir(srcDir, destDir, { overwrite: true, filter, transform })
|
|
149
169
|
// All re-installed framework files count as "updated" (copied), not "created"
|
|
150
170
|
results.copied.push(...sub.copied, ...sub.created)
|
|
151
171
|
results.skipped.push(...sub.skipped)
|
|
152
172
|
}
|
|
153
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
|
+
|
|
154
186
|
// Customizations are NEVER overwritten during update.
|
|
155
187
|
|
|
156
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
|
+
}
|
package/src/cli/detect.ts
CHANGED
|
@@ -423,30 +423,30 @@ function cleanEmpty(info: RepoInfoInternal): RepoInfo {
|
|
|
423
423
|
|
|
424
424
|
/**
|
|
425
425
|
* Merge user-declared stack choices into the auto-detected repoInfo.
|
|
426
|
-
* Adds
|
|
426
|
+
* Adds tech tools and team tools from the questionnaire so
|
|
427
427
|
* repoInfo becomes the single combined source of truth.
|
|
428
428
|
*/
|
|
429
429
|
export function mergeStackIntoRepoInfo(info: RepoInfo, stack: StackConfig): RepoInfo {
|
|
430
430
|
const merged = { ...info };
|
|
431
431
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// Project management
|
|
443
|
-
if (stack.pm !== 'none') {
|
|
444
|
-
merged.pm = addUniqueToArray(merged.pm, stack.pm);
|
|
432
|
+
for (const tool of stack.techTools) {
|
|
433
|
+
if (['sanity', 'contentful', 'strapi'].includes(tool)) {
|
|
434
|
+
merged.cms = addUniqueToArray(merged.cms, tool);
|
|
435
|
+
} else if (['supabase', 'convex'].includes(tool)) {
|
|
436
|
+
merged.databases = addUniqueToArray(merged.databases, tool);
|
|
437
|
+
} else if (tool === 'vercel') {
|
|
438
|
+
merged.deployment = addUniqueToArray(merged.deployment, tool);
|
|
439
|
+
} else if (tool === 'nx') {
|
|
440
|
+
merged.monorepo = merged.monorepo ?? 'nx';
|
|
441
|
+
}
|
|
445
442
|
}
|
|
446
443
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
444
|
+
for (const tool of stack.teamTools) {
|
|
445
|
+
if (['linear', 'jira'].includes(tool)) {
|
|
446
|
+
merged.pm = addUniqueToArray(merged.pm, tool);
|
|
447
|
+
} else if (['slack', 'teams'].includes(tool)) {
|
|
448
|
+
merged.notifications = addUniqueToArray(merged.notifications, tool);
|
|
449
|
+
}
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
return merged;
|