opencastle 0.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.
- package/LICENSE +21 -0
- package/README.md +215 -0
- package/bin/cli.mjs +69 -0
- package/dist/cli/adapters/claude-code.d.ts +22 -0
- package/dist/cli/adapters/claude-code.d.ts.map +1 -0
- package/dist/cli/adapters/claude-code.js +237 -0
- package/dist/cli/adapters/claude-code.js.map +1 -0
- package/dist/cli/adapters/cursor.d.ts +20 -0
- package/dist/cli/adapters/cursor.d.ts.map +1 -0
- package/dist/cli/adapters/cursor.js +231 -0
- package/dist/cli/adapters/cursor.js.map +1 -0
- package/dist/cli/adapters/vscode.d.ts +20 -0
- package/dist/cli/adapters/vscode.d.ts.map +1 -0
- package/dist/cli/adapters/vscode.js +132 -0
- package/dist/cli/adapters/vscode.js.map +1 -0
- package/dist/cli/copy.d.ts +14 -0
- package/dist/cli/copy.d.ts.map +1 -0
- package/dist/cli/copy.js +62 -0
- package/dist/cli/copy.js.map +1 -0
- package/dist/cli/dashboard.d.ts +3 -0
- package/dist/cli/dashboard.d.ts.map +1 -0
- package/dist/cli/dashboard.js +183 -0
- package/dist/cli/dashboard.js.map +1 -0
- package/dist/cli/diff.d.ts +3 -0
- package/dist/cli/diff.d.ts.map +1 -0
- package/dist/cli/diff.js +27 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/eject.d.ts +3 -0
- package/dist/cli/eject.d.ts.map +1 -0
- package/dist/cli/eject.js +27 -0
- package/dist/cli/eject.js.map +1 -0
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +92 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/manifest.d.ts +14 -0
- package/dist/cli/manifest.d.ts.map +1 -0
- package/dist/cli/manifest.js +34 -0
- package/dist/cli/manifest.js.map +1 -0
- package/dist/cli/mcp.d.ts +14 -0
- package/dist/cli/mcp.d.ts.map +1 -0
- package/dist/cli/mcp.js +35 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/prompt.d.ts +12 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +104 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/run/adapters/claude-code.d.ts +16 -0
- package/dist/cli/run/adapters/claude-code.d.ts.map +1 -0
- package/dist/cli/run/adapters/claude-code.js +82 -0
- package/dist/cli/run/adapters/claude-code.js.map +1 -0
- package/dist/cli/run/adapters/copilot.d.ts +16 -0
- package/dist/cli/run/adapters/copilot.d.ts.map +1 -0
- package/dist/cli/run/adapters/copilot.js +84 -0
- package/dist/cli/run/adapters/copilot.js.map +1 -0
- package/dist/cli/run/adapters/cursor.d.ts +16 -0
- package/dist/cli/run/adapters/cursor.d.ts.map +1 -0
- package/dist/cli/run/adapters/cursor.js +81 -0
- package/dist/cli/run/adapters/cursor.js.map +1 -0
- package/dist/cli/run/adapters/index.d.ts +14 -0
- package/dist/cli/run/adapters/index.d.ts.map +1 -0
- package/dist/cli/run/adapters/index.js +35 -0
- package/dist/cli/run/adapters/index.js.map +1 -0
- package/dist/cli/run/executor.d.ts +15 -0
- package/dist/cli/run/executor.d.ts.map +1 -0
- package/dist/cli/run/executor.js +249 -0
- package/dist/cli/run/executor.js.map +1 -0
- package/dist/cli/run/reporter.d.ts +10 -0
- package/dist/cli/run/reporter.d.ts.map +1 -0
- package/dist/cli/run/reporter.js +112 -0
- package/dist/cli/run/reporter.js.map +1 -0
- package/dist/cli/run/schema.d.ts +28 -0
- package/dist/cli/run/schema.d.ts.map +1 -0
- package/dist/cli/run/schema.js +511 -0
- package/dist/cli/run/schema.js.map +1 -0
- package/dist/cli/run.d.ts +6 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +123 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/stack-config.d.ts +12 -0
- package/dist/cli/stack-config.d.ts.map +1 -0
- package/dist/cli/stack-config.js +146 -0
- package/dist/cli/stack-config.js.map +1 -0
- package/dist/cli/types.d.ts +169 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/update.d.ts +3 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/update.js +50 -0
- package/dist/cli/update.js.map +1 -0
- package/package.json +48 -0
- package/src/cli/adapters/claude-code.ts +287 -0
- package/src/cli/adapters/cursor.ts +377 -0
- package/src/cli/adapters/vscode.ts +168 -0
- package/src/cli/copy.ts +79 -0
- package/src/cli/dashboard.ts +225 -0
- package/src/cli/diff.ts +44 -0
- package/src/cli/eject.ts +39 -0
- package/src/cli/init.ts +120 -0
- package/src/cli/manifest.ts +45 -0
- package/src/cli/mcp.ts +49 -0
- package/src/cli/prompt.ts +115 -0
- package/src/cli/run/adapters/claude-code.ts +95 -0
- package/src/cli/run/adapters/copilot.ts +97 -0
- package/src/cli/run/adapters/cursor.ts +94 -0
- package/src/cli/run/adapters/index.ts +40 -0
- package/src/cli/run/executor.ts +292 -0
- package/src/cli/run/reporter.ts +129 -0
- package/src/cli/run/schema.ts +595 -0
- package/src/cli/run.ts +137 -0
- package/src/cli/stack-config.ts +180 -0
- package/src/cli/types.ts +207 -0
- package/src/cli/update.ts +75 -0
- package/src/dashboard/astro.config.mjs +6 -0
- package/src/dashboard/package-lock.json +5455 -0
- package/src/dashboard/package.json +14 -0
- package/src/dashboard/public/data/delegations.ndjson +35 -0
- package/src/dashboard/public/data/panels.ndjson +13 -0
- package/src/dashboard/public/data/sessions.ndjson +50 -0
- package/src/dashboard/public/icon-192.png +0 -0
- package/src/dashboard/scripts/generate-seed-data.ts +355 -0
- package/src/dashboard/src/layouts/Layout.astro +25 -0
- package/src/dashboard/src/pages/index.astro +1070 -0
- package/src/dashboard/src/styles/dashboard.css +1078 -0
- package/src/dashboard/tsconfig.json +6 -0
- package/src/orchestrator/agent-workflows/README.md +22 -0
- package/src/orchestrator/agent-workflows/bug-fix.md +128 -0
- package/src/orchestrator/agent-workflows/data-pipeline.md +145 -0
- package/src/orchestrator/agent-workflows/database-migration.md +159 -0
- package/src/orchestrator/agent-workflows/feature-implementation.md +223 -0
- package/src/orchestrator/agent-workflows/performance-optimization.md +125 -0
- package/src/orchestrator/agent-workflows/refactoring.md +142 -0
- package/src/orchestrator/agent-workflows/schema-changes.md +164 -0
- package/src/orchestrator/agent-workflows/security-audit.md +148 -0
- package/src/orchestrator/agent-workflows/shared-delivery-phase.md +33 -0
- package/src/orchestrator/agents/api-designer.agent.md +68 -0
- package/src/orchestrator/agents/architect.agent.md +129 -0
- package/src/orchestrator/agents/content-engineer.agent.md +57 -0
- package/src/orchestrator/agents/copywriter.agent.md +95 -0
- package/src/orchestrator/agents/data-expert.agent.md +63 -0
- package/src/orchestrator/agents/database-engineer.agent.md +62 -0
- package/src/orchestrator/agents/developer.agent.md +66 -0
- package/src/orchestrator/agents/devops-expert.agent.md +57 -0
- package/src/orchestrator/agents/documentation-writer.agent.md +60 -0
- package/src/orchestrator/agents/performance-expert.agent.md +58 -0
- package/src/orchestrator/agents/release-manager.agent.md +72 -0
- package/src/orchestrator/agents/researcher.agent.md +145 -0
- package/src/orchestrator/agents/reviewer.agent.md +62 -0
- package/src/orchestrator/agents/security-expert.agent.md +64 -0
- package/src/orchestrator/agents/seo-specialist.agent.md +67 -0
- package/src/orchestrator/agents/team-lead.agent.md +644 -0
- package/src/orchestrator/agents/testing-expert.agent.md +85 -0
- package/src/orchestrator/agents/ui-ux-expert.agent.md +63 -0
- package/src/orchestrator/copilot-instructions.md +3 -0
- package/src/orchestrator/customizations/AGENT-EXPERTISE.md +325 -0
- package/src/orchestrator/customizations/AGENT-FAILURES.md +69 -0
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +58 -0
- package/src/orchestrator/customizations/DISPUTES.md +162 -0
- package/src/orchestrator/customizations/KNOWLEDGE-GRAPH.md +10 -0
- package/src/orchestrator/customizations/LESSONS-LEARNED.md +70 -0
- package/src/orchestrator/customizations/README.md +59 -0
- package/src/orchestrator/customizations/agents/agent-registry.md +46 -0
- package/src/orchestrator/customizations/agents/skill-matrix.md +142 -0
- package/src/orchestrator/customizations/logs/README.md +181 -0
- package/src/orchestrator/customizations/logs/delegations.ndjson +1 -0
- package/src/orchestrator/customizations/logs/panels.ndjson +1 -0
- package/src/orchestrator/customizations/logs/sessions.ndjson +1 -0
- package/src/orchestrator/customizations/project/docs-structure.md +23 -0
- package/src/orchestrator/customizations/project/tracker-config.md +45 -0
- package/src/orchestrator/customizations/project.instructions.md +64 -0
- package/src/orchestrator/customizations/stack/api-config.md +37 -0
- package/src/orchestrator/customizations/stack/cms-config.md +26 -0
- package/src/orchestrator/customizations/stack/data-pipeline-config.md +41 -0
- package/src/orchestrator/customizations/stack/database-config.md +44 -0
- package/src/orchestrator/customizations/stack/deployment-config.md +45 -0
- package/src/orchestrator/customizations/stack/testing-config.md +56 -0
- package/src/orchestrator/instructions/ai-optimization.instructions.md +143 -0
- package/src/orchestrator/instructions/general.instructions.md +194 -0
- package/src/orchestrator/mcp.json +55 -0
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +235 -0
- package/src/orchestrator/prompts/brainstorm.prompt.md +115 -0
- package/src/orchestrator/prompts/bug-fix.prompt.md +141 -0
- package/src/orchestrator/prompts/create-skill.prompt.md +103 -0
- package/src/orchestrator/prompts/generate-task-spec.prompt.md +154 -0
- package/src/orchestrator/prompts/implement-feature.prompt.md +124 -0
- package/src/orchestrator/prompts/metrics-report.prompt.md +142 -0
- package/src/orchestrator/prompts/quick-refinement.prompt.md +137 -0
- package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +100 -0
- package/src/orchestrator/skills/accessibility-standards/SKILL.md +164 -0
- package/src/orchestrator/skills/agent-hooks/SKILL.md +147 -0
- package/src/orchestrator/skills/agent-memory/SKILL.md +144 -0
- package/src/orchestrator/skills/api-patterns/SKILL.md +106 -0
- package/src/orchestrator/skills/browser-testing/SKILL.md +203 -0
- package/src/orchestrator/skills/code-commenting/SKILL.md +133 -0
- package/src/orchestrator/skills/contentful-cms/SKILL.md +43 -0
- package/src/orchestrator/skills/context-map/SKILL.md +135 -0
- package/src/orchestrator/skills/convex-database/SKILL.md +80 -0
- package/src/orchestrator/skills/data-engineering/SKILL.md +99 -0
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +49 -0
- package/src/orchestrator/skills/documentation-standards/SKILL.md +85 -0
- package/src/orchestrator/skills/fast-review/SKILL.md +327 -0
- package/src/orchestrator/skills/frontend-design/SKILL.md +42 -0
- package/src/orchestrator/skills/jira-management/SKILL.md +168 -0
- package/src/orchestrator/skills/memory-merger/SKILL.md +123 -0
- package/src/orchestrator/skills/nextjs-patterns/SKILL.md +75 -0
- package/src/orchestrator/skills/nx-workspace/SKILL.md +192 -0
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +184 -0
- package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +38 -0
- package/src/orchestrator/skills/performance-optimization/SKILL.md +101 -0
- package/src/orchestrator/skills/react-development/SKILL.md +117 -0
- package/src/orchestrator/skills/sanity-cms/SKILL.md +18 -0
- package/src/orchestrator/skills/security-hardening/SKILL.md +118 -0
- package/src/orchestrator/skills/self-improvement/SKILL.md +137 -0
- package/src/orchestrator/skills/seo-patterns/SKILL.md +40 -0
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +205 -0
- package/src/orchestrator/skills/slack-notifications/SKILL.md +211 -0
- package/src/orchestrator/skills/strapi-cms/SKILL.md +43 -0
- package/src/orchestrator/skills/supabase-database/SKILL.md +24 -0
- package/src/orchestrator/skills/task-management/SKILL.md +143 -0
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +317 -0
- package/src/orchestrator/skills/teams-notifications/SKILL.md +249 -0
- package/src/orchestrator/skills/testing-workflow/SKILL.md +134 -0
- package/src/orchestrator/skills/validation-gates/SKILL.md +100 -0
|
@@ -0,0 +1,287 @@
|
|
|
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 } from '../copy.js'
|
|
5
|
+
import { scaffoldMcpConfig } from '../mcp.js'
|
|
6
|
+
import { getExcludedSkills, getExcludedAgents, getCustomizationsTransform } from '../stack-config.js'
|
|
7
|
+
import type { CopyResults, ManagedPaths, StackConfig } from '../types.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Claude Code adapter.
|
|
11
|
+
*
|
|
12
|
+
* Generates CLAUDE.md (root instructions) and .claude/ structure.
|
|
13
|
+
*
|
|
14
|
+
* copilot-instructions.md → CLAUDE.md (combined with instructions/)
|
|
15
|
+
* skills/\*\/SKILL.md → .claude/skills/<name>.md
|
|
16
|
+
* agent-workflows/*.md → .claude/commands/workflow-<name>.md
|
|
17
|
+
* prompts/*.prompt.md → .claude/commands/<name>.md
|
|
18
|
+
* customizations/ → .claude/customizations/ (scaffolded once)
|
|
19
|
+
*
|
|
20
|
+
* Note: Claude Code has no "agents" concept. Agent definitions are embedded
|
|
21
|
+
* as reference sections within CLAUDE.md so Claude can adopt personas
|
|
22
|
+
* when asked.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export const IDE_ID = 'claude-code'
|
|
26
|
+
export const IDE_LABEL = 'Claude Code'
|
|
27
|
+
|
|
28
|
+
// ─── Helpers ──────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
function stripFrontmatter(content: string): string {
|
|
31
|
+
const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
|
|
32
|
+
return m ? m[2].trim() : content.trim()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseFrontmatterMeta(content: string): Record<string, string> {
|
|
36
|
+
const m = content.match(/^---\n([\s\S]*?)\n---/)
|
|
37
|
+
if (!m) return {}
|
|
38
|
+
const meta: Record<string, string> = {}
|
|
39
|
+
for (const line of m[1].split('\n')) {
|
|
40
|
+
const kv = line.match(/^(\w[\w-]*):\s*['"]?(.+?)['"]?\s*$/)
|
|
41
|
+
if (kv) meta[kv[1]] = kv[2]
|
|
42
|
+
}
|
|
43
|
+
return meta
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Install ──────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export async function install(
|
|
49
|
+
pkgRoot: string,
|
|
50
|
+
projectRoot: string,
|
|
51
|
+
stack?: StackConfig
|
|
52
|
+
): Promise<CopyResults> {
|
|
53
|
+
const srcRoot = getOrchestratorRoot(pkgRoot)
|
|
54
|
+
const results: CopyResults = { copied: [], skipped: [], created: [] }
|
|
55
|
+
|
|
56
|
+
const excludedSkills = stack ? getExcludedSkills(stack) : new Set<string>()
|
|
57
|
+
const excludedAgents = stack ? getExcludedAgents(stack) : new Set<string>()
|
|
58
|
+
|
|
59
|
+
// 1. Build CLAUDE.md ← copilot-instructions + instructions/* + agent index + skill index
|
|
60
|
+
const claudeMd = resolve(projectRoot, 'CLAUDE.md')
|
|
61
|
+
if (!existsSync(claudeMd)) {
|
|
62
|
+
const sections: string[] = []
|
|
63
|
+
|
|
64
|
+
// Main instructions
|
|
65
|
+
const main = await readFile(
|
|
66
|
+
resolve(srcRoot, 'copilot-instructions.md'),
|
|
67
|
+
'utf8'
|
|
68
|
+
)
|
|
69
|
+
sections.push(stripFrontmatter(main))
|
|
70
|
+
|
|
71
|
+
// Always-loaded instruction files
|
|
72
|
+
const instDir = resolve(srcRoot, 'instructions')
|
|
73
|
+
if (existsSync(instDir)) {
|
|
74
|
+
for (const file of (await readdir(instDir)).sort()) {
|
|
75
|
+
if (!file.endsWith('.md')) continue
|
|
76
|
+
const content = await readFile(resolve(instDir, file), 'utf8')
|
|
77
|
+
sections.push(
|
|
78
|
+
`\n---\n\n<!-- Source: instructions/${file} -->\n\n${stripFrontmatter(content)}`
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Agent reference (so Claude can adopt personas)
|
|
84
|
+
const agentsDir = resolve(srcRoot, 'agents')
|
|
85
|
+
if (existsSync(agentsDir)) {
|
|
86
|
+
const agentLines: string[] = ['\n---\n\n## Agent Definitions\n']
|
|
87
|
+
agentLines.push(
|
|
88
|
+
'The following agent personas are available. Adopt the appropriate persona when asked.\n'
|
|
89
|
+
)
|
|
90
|
+
for (const file of (await readdir(agentsDir)).sort()) {
|
|
91
|
+
if (!file.endsWith('.md')) continue
|
|
92
|
+
if (excludedAgents.has(file)) continue
|
|
93
|
+
const meta = parseFrontmatterMeta(
|
|
94
|
+
await readFile(resolve(agentsDir, file), 'utf8')
|
|
95
|
+
)
|
|
96
|
+
const name = meta['name'] ?? basename(file, '.agent.md')
|
|
97
|
+
const desc = meta['description'] ?? ''
|
|
98
|
+
agentLines.push(`- **${name}**: ${desc}`)
|
|
99
|
+
}
|
|
100
|
+
agentLines.push(
|
|
101
|
+
'\nFull agent definitions are in `.claude/agents/`. Read the relevant file when adopting a persona.'
|
|
102
|
+
)
|
|
103
|
+
sections.push(agentLines.join('\n'))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Skill index
|
|
107
|
+
const skillsDir = resolve(srcRoot, 'skills')
|
|
108
|
+
if (existsSync(skillsDir)) {
|
|
109
|
+
const skillLines: string[] = ['\n---\n\n## Available Skills\n']
|
|
110
|
+
skillLines.push(
|
|
111
|
+
'Skills are on-demand knowledge files. Read the file when the task matches.\n'
|
|
112
|
+
)
|
|
113
|
+
const subdirs = (
|
|
114
|
+
await readdir(skillsDir, { withFileTypes: true })
|
|
115
|
+
).filter((e) => e.isDirectory())
|
|
116
|
+
for (const entry of subdirs.sort((a, b) =>
|
|
117
|
+
a.name.localeCompare(b.name)
|
|
118
|
+
)) {
|
|
119
|
+
if (excludedSkills.has(entry.name)) continue
|
|
120
|
+
const skillFile = resolve(skillsDir, entry.name, 'SKILL.md')
|
|
121
|
+
if (!existsSync(skillFile)) continue
|
|
122
|
+
const meta = parseFrontmatterMeta(await readFile(skillFile, 'utf8'))
|
|
123
|
+
const desc = meta['description'] ?? ''
|
|
124
|
+
skillLines.push(
|
|
125
|
+
`- **${entry.name}** (\`.claude/skills/${entry.name}.md\`): ${desc}`
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
sections.push(skillLines.join('\n'))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await writeFile(claudeMd, sections.join('\n') + '\n')
|
|
132
|
+
results.created.push(claudeMd)
|
|
133
|
+
} else {
|
|
134
|
+
results.skipped.push(claudeMd)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const claudeDir = resolve(projectRoot, '.claude')
|
|
138
|
+
|
|
139
|
+
// 2. Agent definitions → .claude/agents/
|
|
140
|
+
const agentsDir = resolve(srcRoot, 'agents')
|
|
141
|
+
if (existsSync(agentsDir)) {
|
|
142
|
+
const destAgents = resolve(claudeDir, 'agents')
|
|
143
|
+
await mkdir(destAgents, { recursive: true })
|
|
144
|
+
for (const file of await readdir(agentsDir)) {
|
|
145
|
+
if (!file.endsWith('.md')) continue
|
|
146
|
+
if (excludedAgents.has(file)) continue
|
|
147
|
+
const destPath = resolve(destAgents, file)
|
|
148
|
+
if (existsSync(destPath)) {
|
|
149
|
+
results.skipped.push(destPath)
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
const content = await readFile(resolve(agentsDir, file), 'utf8')
|
|
153
|
+
await writeFile(destPath, stripFrontmatter(content) + '\n')
|
|
154
|
+
results.created.push(destPath)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 3. Skills → .claude/skills/<name>.md
|
|
159
|
+
const skillsDir = resolve(srcRoot, 'skills')
|
|
160
|
+
if (existsSync(skillsDir)) {
|
|
161
|
+
const destSkills = resolve(claudeDir, 'skills')
|
|
162
|
+
await mkdir(destSkills, { recursive: true })
|
|
163
|
+
const subdirs = (
|
|
164
|
+
await readdir(skillsDir, { withFileTypes: true })
|
|
165
|
+
).filter((e) => e.isDirectory())
|
|
166
|
+
for (const entry of subdirs) {
|
|
167
|
+
if (excludedSkills.has(entry.name)) continue
|
|
168
|
+
const skillFile = resolve(skillsDir, entry.name, 'SKILL.md')
|
|
169
|
+
if (!existsSync(skillFile)) continue
|
|
170
|
+
const destPath = resolve(destSkills, `${entry.name}.md`)
|
|
171
|
+
if (existsSync(destPath)) {
|
|
172
|
+
results.skipped.push(destPath)
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
const content = await readFile(skillFile, 'utf8')
|
|
176
|
+
await writeFile(destPath, stripFrontmatter(content) + '\n')
|
|
177
|
+
results.created.push(destPath)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 4. Prompts → .claude/commands/<name>.md
|
|
182
|
+
const promptDir = resolve(srcRoot, 'prompts')
|
|
183
|
+
if (existsSync(promptDir)) {
|
|
184
|
+
const destCmds = resolve(claudeDir, 'commands')
|
|
185
|
+
await mkdir(destCmds, { recursive: true })
|
|
186
|
+
for (const file of await readdir(promptDir)) {
|
|
187
|
+
if (!file.endsWith('.md')) continue
|
|
188
|
+
const name = basename(file, '.prompt.md') || basename(file, '.md')
|
|
189
|
+
const destPath = resolve(destCmds, `${name}.md`)
|
|
190
|
+
if (existsSync(destPath)) {
|
|
191
|
+
results.skipped.push(destPath)
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
const content = await readFile(resolve(promptDir, file), 'utf8')
|
|
195
|
+
await writeFile(destPath, stripFrontmatter(content) + '\n')
|
|
196
|
+
results.created.push(destPath)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 5. Agent Workflows → .claude/commands/workflow-<name>.md
|
|
201
|
+
const wfDir = resolve(srcRoot, 'agent-workflows')
|
|
202
|
+
if (existsSync(wfDir)) {
|
|
203
|
+
const destCmds = resolve(claudeDir, 'commands')
|
|
204
|
+
await mkdir(destCmds, { recursive: true })
|
|
205
|
+
for (const file of await readdir(wfDir)) {
|
|
206
|
+
if (!file.endsWith('.md')) continue
|
|
207
|
+
const name = basename(file, '.md')
|
|
208
|
+
const destPath = resolve(destCmds, `workflow-${name}.md`)
|
|
209
|
+
if (existsSync(destPath)) {
|
|
210
|
+
results.skipped.push(destPath)
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
const content = await readFile(resolve(wfDir, file), 'utf8')
|
|
214
|
+
await writeFile(destPath, stripFrontmatter(content) + '\n')
|
|
215
|
+
results.created.push(destPath)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 6. Customizations (scaffold once, pre-populated with stack choices)
|
|
220
|
+
const custDir = resolve(srcRoot, 'customizations')
|
|
221
|
+
if (existsSync(custDir)) {
|
|
222
|
+
const destCust = resolve(claudeDir, 'customizations')
|
|
223
|
+
const custTransform = stack ? getCustomizationsTransform(stack) : undefined
|
|
224
|
+
const sub = await copyDir(custDir, destCust, { transform: custTransform })
|
|
225
|
+
results.created.push(...sub.created)
|
|
226
|
+
results.skipped.push(...sub.skipped)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 7. MCP server config → .claude/mcp.json (scaffold once)
|
|
230
|
+
const mcpResult = await scaffoldMcpConfig(
|
|
231
|
+
pkgRoot,
|
|
232
|
+
projectRoot,
|
|
233
|
+
'.claude/mcp.json',
|
|
234
|
+
stack
|
|
235
|
+
)
|
|
236
|
+
results[mcpResult.action].push(mcpResult.path)
|
|
237
|
+
|
|
238
|
+
return results
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── Update ───────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
export async function update(
|
|
244
|
+
pkgRoot: string,
|
|
245
|
+
projectRoot: string,
|
|
246
|
+
stack?: StackConfig
|
|
247
|
+
): Promise<CopyResults> {
|
|
248
|
+
const results: CopyResults = { copied: [], skipped: [], created: [] }
|
|
249
|
+
const claudeDir = resolve(projectRoot, '.claude')
|
|
250
|
+
|
|
251
|
+
// 1. Regenerate CLAUDE.md (overwrite)
|
|
252
|
+
const claudeMd = resolve(projectRoot, 'CLAUDE.md')
|
|
253
|
+
if (existsSync(claudeMd)) {
|
|
254
|
+
await unlink(claudeMd)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 2. Remove existing framework files so install() recreates them
|
|
258
|
+
const frameworkDirs = ['agents', 'skills', 'commands']
|
|
259
|
+
for (const dir of frameworkDirs) {
|
|
260
|
+
const dirPath = resolve(claudeDir, dir)
|
|
261
|
+
if (existsSync(dirPath)) {
|
|
262
|
+
await rm(dirPath, { recursive: true })
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 3. Re-run full install (CLAUDE.md + agents + skills + commands)
|
|
267
|
+
const installResult = await install(pkgRoot, projectRoot, stack)
|
|
268
|
+
// Everything install created is an "update" copy
|
|
269
|
+
results.copied.push(...installResult.created)
|
|
270
|
+
results.skipped.push(...installResult.skipped)
|
|
271
|
+
|
|
272
|
+
return results
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ─── Managed paths ────────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
export function getManagedPaths(): ManagedPaths {
|
|
278
|
+
return {
|
|
279
|
+
framework: [
|
|
280
|
+
'CLAUDE.md',
|
|
281
|
+
'.claude/agents/',
|
|
282
|
+
'.claude/skills/',
|
|
283
|
+
'.claude/commands/',
|
|
284
|
+
],
|
|
285
|
+
customizable: ['.claude/customizations/', '.claude/mcp.json'],
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { resolve, basename } from 'node:path'
|
|
2
|
+
import { mkdir, writeFile, readdir, readFile } from 'node:fs/promises'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import { copyDir, getOrchestratorRoot, removeDirIfExists } from '../copy.js'
|
|
5
|
+
import { scaffoldMcpConfig } from '../mcp.js'
|
|
6
|
+
import { getExcludedSkills, getExcludedAgents, getCustomizationsTransform } from '../stack-config.js'
|
|
7
|
+
import type { CopyResults, ManagedPaths, StackConfig } from '../types.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Cursor adapter.
|
|
11
|
+
*
|
|
12
|
+
* Transforms Copilot-format orchestrator files into Cursor's .mdc rule format.
|
|
13
|
+
*
|
|
14
|
+
* copilot-instructions.md → .cursorrules
|
|
15
|
+
* instructions/*.md → .cursor/rules/*.mdc (alwaysApply: true)
|
|
16
|
+
* agents/*.agent.md → .cursor/rules/agents/*.mdc (description-triggered)
|
|
17
|
+
* skills/\*\/SKILL.md → .cursor/rules/skills/*.mdc (alwaysApply: false)
|
|
18
|
+
* agent-workflows/*.md → .cursor/rules/agent-workflows/*.mdc
|
|
19
|
+
* prompts/*.prompt.md → .cursor/rules/prompts/*.mdc
|
|
20
|
+
* customizations/ → .cursor/rules/customizations/ (scaffolded once)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export const IDE_ID = 'cursor'
|
|
24
|
+
export const IDE_LABEL = 'Cursor'
|
|
25
|
+
|
|
26
|
+
// ─── Helpers ──────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
interface FrontmatterResult {
|
|
29
|
+
frontmatter: string
|
|
30
|
+
body: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function stripFrontmatter(content: string): FrontmatterResult {
|
|
34
|
+
const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
|
|
35
|
+
return m
|
|
36
|
+
? { frontmatter: m[1], body: m[2] }
|
|
37
|
+
: { frontmatter: '', body: content }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseFrontmatter(fm: string): Record<string, string> {
|
|
41
|
+
const result: Record<string, string> = {}
|
|
42
|
+
for (const line of fm.split('\n')) {
|
|
43
|
+
const m = line.match(/^(\w[\w-]*):\s*['"]?(.+?)['"]?\s*$/)
|
|
44
|
+
if (m) result[m[1]] = m[2]
|
|
45
|
+
}
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface MdcOptions {
|
|
50
|
+
description?: string
|
|
51
|
+
globs?: string[]
|
|
52
|
+
alwaysApply: boolean
|
|
53
|
+
body: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toMdc({ description, globs, alwaysApply, body }: MdcOptions): string {
|
|
57
|
+
const lines = ['---']
|
|
58
|
+
if (description) lines.push(`description: "${description}"`)
|
|
59
|
+
if (globs) lines.push(`globs: ${JSON.stringify(globs)}`)
|
|
60
|
+
lines.push(`alwaysApply: ${alwaysApply ? 'true' : 'false'}`)
|
|
61
|
+
lines.push('---', '', body.trim(), '')
|
|
62
|
+
return lines.join('\n')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Convert a source filename to .mdc, stripping intermediate extensions. */
|
|
66
|
+
function mdcName(name: string): string {
|
|
67
|
+
// Handle compound extensions like .agent.md, .instructions.md, .prompt.md
|
|
68
|
+
const compound = name.replace(/\.(agent|instructions|prompt)\.md$/, '.mdc')
|
|
69
|
+
if (compound !== name) return compound
|
|
70
|
+
// Handle plain .md files
|
|
71
|
+
return name.replace(/\.md$/, '.mdc')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface ConvertFileOptions {
|
|
75
|
+
alwaysApply?: boolean
|
|
76
|
+
descriptionFallback?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Read a source .md and produce an .mdc string. */
|
|
80
|
+
async function convertFile(
|
|
81
|
+
srcPath: string,
|
|
82
|
+
{ alwaysApply = false, descriptionFallback = '' }: ConvertFileOptions = {}
|
|
83
|
+
): Promise<string> {
|
|
84
|
+
const content = await readFile(srcPath, 'utf8')
|
|
85
|
+
const { frontmatter, body } = stripFrontmatter(content)
|
|
86
|
+
const meta = parseFrontmatter(frontmatter)
|
|
87
|
+
|
|
88
|
+
// Description: frontmatter > fallback > first heading
|
|
89
|
+
let description = meta['description'] ?? descriptionFallback
|
|
90
|
+
if (!description) {
|
|
91
|
+
const heading = body.match(/^#\s+(.+)/m)
|
|
92
|
+
if (heading) description = heading[1]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// If applyTo is '**' the rule should always apply
|
|
96
|
+
const globs = meta['applyTo'] ? [meta['applyTo']] : undefined
|
|
97
|
+
const apply = meta['applyTo'] === '**' ? true : alwaysApply
|
|
98
|
+
|
|
99
|
+
return toMdc({ description, globs, alwaysApply: apply, body: body.trim() })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Write a converted file; skip if it already exists (unless overwrite). */
|
|
103
|
+
async function writeConverted(
|
|
104
|
+
srcPath: string,
|
|
105
|
+
destPath: string,
|
|
106
|
+
opts: ConvertFileOptions,
|
|
107
|
+
results: CopyResults,
|
|
108
|
+
overwrite = false
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
if (!overwrite && existsSync(destPath)) {
|
|
111
|
+
results.skipped.push(destPath)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
const existed = existsSync(destPath)
|
|
115
|
+
const mdc = await convertFile(srcPath, opts)
|
|
116
|
+
await writeFile(destPath, mdc)
|
|
117
|
+
results[existed ? 'copied' : 'created'].push(destPath)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Install ──────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
export async function install(
|
|
123
|
+
pkgRoot: string,
|
|
124
|
+
projectRoot: string,
|
|
125
|
+
stack?: StackConfig
|
|
126
|
+
): Promise<CopyResults> {
|
|
127
|
+
const srcRoot = getOrchestratorRoot(pkgRoot)
|
|
128
|
+
const results: CopyResults = { copied: [], skipped: [], created: [] }
|
|
129
|
+
|
|
130
|
+
const excludedSkills = stack ? getExcludedSkills(stack) : new Set<string>()
|
|
131
|
+
const excludedAgents = stack ? getExcludedAgents(stack) : new Set<string>()
|
|
132
|
+
|
|
133
|
+
// 1. .cursorrules ← copilot-instructions.md (body only)
|
|
134
|
+
const cursorrules = resolve(projectRoot, '.cursorrules')
|
|
135
|
+
if (!existsSync(cursorrules)) {
|
|
136
|
+
const { body } = stripFrontmatter(
|
|
137
|
+
await readFile(resolve(srcRoot, 'copilot-instructions.md'), 'utf8')
|
|
138
|
+
)
|
|
139
|
+
await writeFile(cursorrules, body.trim() + '\n')
|
|
140
|
+
results.created.push(cursorrules)
|
|
141
|
+
} else {
|
|
142
|
+
results.skipped.push(cursorrules)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const rulesRoot = resolve(projectRoot, '.cursor', 'rules')
|
|
146
|
+
await mkdir(rulesRoot, { recursive: true })
|
|
147
|
+
|
|
148
|
+
// 2. Instructions → .cursor/rules/*.mdc (alwaysApply: true)
|
|
149
|
+
await convertDir(srcRoot, 'instructions', rulesRoot, results, {
|
|
150
|
+
alwaysApply: true,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// 3. Agents → .cursor/rules/agents/*.mdc
|
|
154
|
+
await convertDir(srcRoot, 'agents', resolve(rulesRoot, 'agents'), results, {
|
|
155
|
+
descriptionPrefix: 'Agent: ',
|
|
156
|
+
removeExt: '.agent.md',
|
|
157
|
+
excludeFiles: excludedAgents,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// 4. Skills → .cursor/rules/skills/*.mdc
|
|
161
|
+
await convertSkills(srcRoot, resolve(rulesRoot, 'skills'), results, false, excludedSkills)
|
|
162
|
+
|
|
163
|
+
// 5. Agent Workflows → .cursor/rules/agent-workflows/*.mdc
|
|
164
|
+
await convertDir(
|
|
165
|
+
srcRoot,
|
|
166
|
+
'agent-workflows',
|
|
167
|
+
resolve(rulesRoot, 'agent-workflows'),
|
|
168
|
+
results,
|
|
169
|
+
{ descriptionPrefix: 'Workflow: ' }
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
// 6. Prompts → .cursor/rules/prompts/*.mdc
|
|
173
|
+
await convertDir(srcRoot, 'prompts', resolve(rulesRoot, 'prompts'), results, {
|
|
174
|
+
descriptionPrefix: 'Prompt: ',
|
|
175
|
+
removeExt: '.prompt.md',
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// 7. Customizations (scaffold once, pre-populated with stack choices)
|
|
179
|
+
const custSrc = resolve(srcRoot, 'customizations')
|
|
180
|
+
if (existsSync(custSrc)) {
|
|
181
|
+
const custDest = resolve(rulesRoot, 'customizations')
|
|
182
|
+
const custTransform = stack ? getCustomizationsTransform(stack) : undefined
|
|
183
|
+
const sub = await copyDir(custSrc, custDest, { transform: custTransform })
|
|
184
|
+
results.created.push(...sub.created)
|
|
185
|
+
results.skipped.push(...sub.skipped)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 8. MCP server config → .cursor/mcp.json (scaffold once)
|
|
189
|
+
const mcpResult = await scaffoldMcpConfig(
|
|
190
|
+
pkgRoot,
|
|
191
|
+
projectRoot,
|
|
192
|
+
'.cursor/mcp.json',
|
|
193
|
+
stack
|
|
194
|
+
)
|
|
195
|
+
results[mcpResult.action].push(mcpResult.path)
|
|
196
|
+
|
|
197
|
+
return results
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── Update ───────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
export async function update(
|
|
203
|
+
pkgRoot: string,
|
|
204
|
+
projectRoot: string,
|
|
205
|
+
stack?: StackConfig
|
|
206
|
+
): Promise<CopyResults> {
|
|
207
|
+
const srcRoot = getOrchestratorRoot(pkgRoot)
|
|
208
|
+
const results: CopyResults = { copied: [], skipped: [], created: [] }
|
|
209
|
+
|
|
210
|
+
const excludedSkills = stack ? getExcludedSkills(stack) : new Set<string>()
|
|
211
|
+
const excludedAgents = stack ? getExcludedAgents(stack) : new Set<string>()
|
|
212
|
+
|
|
213
|
+
// Overwrite .cursorrules
|
|
214
|
+
const { body } = stripFrontmatter(
|
|
215
|
+
await readFile(resolve(srcRoot, 'copilot-instructions.md'), 'utf8')
|
|
216
|
+
)
|
|
217
|
+
await writeFile(resolve(projectRoot, '.cursorrules'), body.trim() + '\n')
|
|
218
|
+
results.copied.push('.cursorrules')
|
|
219
|
+
|
|
220
|
+
const rulesRoot = resolve(projectRoot, '.cursor', 'rules')
|
|
221
|
+
|
|
222
|
+
// Remove existing framework rule directories to clear stale files
|
|
223
|
+
const FRAMEWORK_RULE_DIRS = ['agents', 'skills', 'agent-workflows', 'prompts']
|
|
224
|
+
for (const dir of FRAMEWORK_RULE_DIRS) {
|
|
225
|
+
await removeDirIfExists(resolve(rulesRoot, dir))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Overwrite framework rules
|
|
229
|
+
await convertDir(srcRoot, 'instructions', rulesRoot, results, {
|
|
230
|
+
alwaysApply: true,
|
|
231
|
+
overwrite: true,
|
|
232
|
+
})
|
|
233
|
+
await convertDir(
|
|
234
|
+
srcRoot,
|
|
235
|
+
'agents',
|
|
236
|
+
resolve(rulesRoot, 'agents'),
|
|
237
|
+
results,
|
|
238
|
+
{ descriptionPrefix: 'Agent: ', removeExt: '.agent.md', overwrite: true, excludeFiles: excludedAgents }
|
|
239
|
+
)
|
|
240
|
+
await convertSkills(srcRoot, resolve(rulesRoot, 'skills'), results, true, excludedSkills)
|
|
241
|
+
await convertDir(
|
|
242
|
+
srcRoot,
|
|
243
|
+
'agent-workflows',
|
|
244
|
+
resolve(rulesRoot, 'agent-workflows'),
|
|
245
|
+
results,
|
|
246
|
+
{ descriptionPrefix: 'Workflow: ', overwrite: true }
|
|
247
|
+
)
|
|
248
|
+
await convertDir(
|
|
249
|
+
srcRoot,
|
|
250
|
+
'prompts',
|
|
251
|
+
resolve(rulesRoot, 'prompts'),
|
|
252
|
+
results,
|
|
253
|
+
{ descriptionPrefix: 'Prompt: ', removeExt: '.prompt.md', overwrite: true }
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
// Customizations are NEVER overwritten.
|
|
257
|
+
|
|
258
|
+
// All re-installed framework files count as "updated" (copied), not "created"
|
|
259
|
+
results.copied.push(...results.created)
|
|
260
|
+
results.created = []
|
|
261
|
+
|
|
262
|
+
return results
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ─── Managed paths ────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
export function getManagedPaths(): ManagedPaths {
|
|
268
|
+
return {
|
|
269
|
+
framework: [
|
|
270
|
+
'.cursorrules',
|
|
271
|
+
'.cursor/rules/agents/',
|
|
272
|
+
'.cursor/rules/skills/',
|
|
273
|
+
'.cursor/rules/agent-workflows/',
|
|
274
|
+
'.cursor/rules/prompts/',
|
|
275
|
+
'.cursor/rules/general.mdc',
|
|
276
|
+
'.cursor/rules/ai-optimization.mdc',
|
|
277
|
+
],
|
|
278
|
+
customizable: [
|
|
279
|
+
'.cursor/rules/customizations/',
|
|
280
|
+
'.cursor/mcp.json',
|
|
281
|
+
],
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ─── Internal helpers ─────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
interface ConvertDirOptions {
|
|
288
|
+
alwaysApply?: boolean
|
|
289
|
+
descriptionPrefix?: string
|
|
290
|
+
removeExt?: string
|
|
291
|
+
overwrite?: boolean
|
|
292
|
+
excludeFiles?: Set<string>
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function convertDir(
|
|
296
|
+
srcRoot: string,
|
|
297
|
+
dirName: string,
|
|
298
|
+
destDir: string,
|
|
299
|
+
results: CopyResults,
|
|
300
|
+
{
|
|
301
|
+
alwaysApply,
|
|
302
|
+
descriptionPrefix,
|
|
303
|
+
removeExt,
|
|
304
|
+
overwrite,
|
|
305
|
+
excludeFiles,
|
|
306
|
+
}: ConvertDirOptions = {}
|
|
307
|
+
): Promise<void> {
|
|
308
|
+
const srcDir = resolve(srcRoot, dirName)
|
|
309
|
+
if (!existsSync(srcDir)) return
|
|
310
|
+
|
|
311
|
+
await mkdir(destDir, { recursive: true })
|
|
312
|
+
|
|
313
|
+
for (const file of await readdir(srcDir)) {
|
|
314
|
+
if (!file.endsWith('.md')) continue
|
|
315
|
+
if (excludeFiles?.has(file)) continue
|
|
316
|
+
const fallback = descriptionPrefix
|
|
317
|
+
? `${descriptionPrefix}${basename(file, removeExt ?? '.md')}`
|
|
318
|
+
: ''
|
|
319
|
+
const destPath = resolve(destDir, mdcName(file))
|
|
320
|
+
await writeConverted(
|
|
321
|
+
resolve(srcDir, file),
|
|
322
|
+
destPath,
|
|
323
|
+
{ alwaysApply: alwaysApply ?? false, descriptionFallback: fallback },
|
|
324
|
+
results,
|
|
325
|
+
overwrite
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function convertSkills(
|
|
331
|
+
srcRoot: string,
|
|
332
|
+
destDir: string,
|
|
333
|
+
results: CopyResults,
|
|
334
|
+
overwrite = false,
|
|
335
|
+
excludedSkills?: Set<string>
|
|
336
|
+
): Promise<void> {
|
|
337
|
+
const skillsDir = resolve(srcRoot, 'skills')
|
|
338
|
+
if (!existsSync(skillsDir)) return
|
|
339
|
+
|
|
340
|
+
await mkdir(destDir, { recursive: true })
|
|
341
|
+
|
|
342
|
+
const subdirs = await readdir(skillsDir, { withFileTypes: true })
|
|
343
|
+
for (const entry of subdirs) {
|
|
344
|
+
if (!entry.isDirectory()) continue
|
|
345
|
+
if (excludedSkills?.has(entry.name)) continue
|
|
346
|
+
const skillFile = resolve(skillsDir, entry.name, 'SKILL.md')
|
|
347
|
+
if (!existsSync(skillFile)) continue
|
|
348
|
+
|
|
349
|
+
// Main skill → skills/<name>.mdc
|
|
350
|
+
const destPath = resolve(destDir, `${entry.name}.mdc`)
|
|
351
|
+
await writeConverted(
|
|
352
|
+
skillFile,
|
|
353
|
+
destPath,
|
|
354
|
+
{ descriptionFallback: `Skill: ${entry.name}` },
|
|
355
|
+
results,
|
|
356
|
+
overwrite
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
// Extra files in the skill directory (e.g. templates)
|
|
360
|
+
const files = await readdir(resolve(skillsDir, entry.name))
|
|
361
|
+
const extras = files.filter((f) => f !== 'SKILL.md' && f.endsWith('.md'))
|
|
362
|
+
if (extras.length > 0) {
|
|
363
|
+
const subDest = resolve(destDir, entry.name)
|
|
364
|
+
await mkdir(subDest, { recursive: true })
|
|
365
|
+
for (const file of extras) {
|
|
366
|
+
const extraDest = resolve(subDest, mdcName(file))
|
|
367
|
+
await writeConverted(
|
|
368
|
+
resolve(skillsDir, entry.name, file),
|
|
369
|
+
extraDest,
|
|
370
|
+
{ descriptionFallback: `${entry.name}: ${basename(file, '.md')}` },
|
|
371
|
+
results,
|
|
372
|
+
overwrite
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|