opencastle 0.8.0 → 0.8.1
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 +27 -0
- package/bin/cli.mjs +2 -0
- package/dist/cli/adapters/claude-code.d.ts +2 -5
- package/dist/cli/adapters/claude-code.d.ts.map +1 -1
- package/dist/cli/adapters/claude-code.js +12 -251
- 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 +3 -17
- package/dist/cli/adapters/cursor.js.map +1 -1
- package/dist/cli/adapters/frontmatter.d.ts +26 -0
- package/dist/cli/adapters/frontmatter.d.ts.map +1 -0
- package/dist/cli/adapters/frontmatter.js +40 -0
- package/dist/cli/adapters/frontmatter.js.map +1 -0
- package/dist/cli/adapters/index.d.ts +5 -0
- package/dist/cli/adapters/index.d.ts.map +1 -0
- package/dist/cli/adapters/index.js +9 -0
- package/dist/cli/adapters/index.js.map +1 -0
- package/dist/cli/adapters/opencode.d.ts +2 -5
- package/dist/cli/adapters/opencode.d.ts.map +1 -1
- package/dist/cli/adapters/opencode.js +12 -250
- package/dist/cli/adapters/opencode.js.map +1 -1
- package/dist/cli/adapters/single-file-base.d.ts +40 -0
- package/dist/cli/adapters/single-file-base.d.ts.map +1 -0
- package/dist/cli/adapters/single-file-base.js +246 -0
- package/dist/cli/adapters/single-file-base.js.map +1 -0
- package/dist/cli/dashboard.d.ts.map +1 -1
- package/dist/cli/dashboard.js +3 -2
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/detect.d.ts.map +1 -1
- package/dist/cli/detect.js +13 -11
- package/dist/cli/detect.js.map +1 -1
- package/dist/cli/doctor.d.ts +3 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +205 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +31 -19
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/run/schema.d.ts +1 -5
- package/dist/cli/run/schema.d.ts.map +1 -1
- package/dist/cli/run/schema.js +6 -330
- package/dist/cli/run/schema.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +14 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/types.d.ts +0 -5
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +4 -17
- package/dist/cli/update.js.map +1 -1
- package/package.json +7 -2
- package/src/cli/adapters/claude-code.ts +13 -304
- package/src/cli/adapters/cursor.ts +3 -23
- package/src/cli/adapters/frontmatter.ts +47 -0
- package/src/cli/adapters/index.ts +13 -0
- package/src/cli/adapters/opencode.ts +12 -301
- package/src/cli/adapters/single-file-base.ts +320 -0
- package/src/cli/dashboard.ts +3 -2
- package/src/cli/detect.ts +19 -15
- package/src/cli/doctor.ts +235 -0
- package/src/cli/init.ts +31 -24
- package/src/cli/run/schema.ts +7 -365
- package/src/cli/run.ts +17 -1
- package/src/cli/types.ts +0 -6
- package/src/cli/update.ts +5 -23
- package/src/dashboard/dist/_astro/{index.CWVzbF4T.css → index.Bnq19_1M.css} +1 -1
- package/src/dashboard/dist/index.html +170 -11
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/seed-data/reviews.ndjson +6 -0
- package/src/dashboard/src/pages/index.astro +213 -10
- package/src/dashboard/src/styles/dashboard.css +196 -0
- package/src/orchestrator/agent-workflows/bug-fix.md +2 -2
- package/src/orchestrator/agent-workflows/data-pipeline.md +8 -8
- package/src/orchestrator/agent-workflows/database-migration.md +2 -2
- package/src/orchestrator/agent-workflows/feature-implementation.md +2 -2
- package/src/orchestrator/agent-workflows/performance-optimization.md +2 -2
- package/src/orchestrator/agent-workflows/refactoring.md +2 -2
- package/src/orchestrator/agent-workflows/schema-changes.md +2 -2
- package/src/orchestrator/agent-workflows/security-audit.md +2 -2
- package/src/orchestrator/agents/data-expert.agent.md +2 -2
- package/src/orchestrator/agents/researcher.agent.md +0 -16
- package/src/orchestrator/agents/team-lead.agent.md +17 -6
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +1 -3
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +1 -1
- package/src/orchestrator/skills/agent-hooks/SKILL.md +4 -2
- package/src/orchestrator/skills/self-improvement/SKILL.md +1 -1
- package/src/orchestrator/prompts/metrics-report.prompt.md +0 -144
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
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'
|
|
1
|
+
import { createSingleFileAdapter } from './single-file-base.js'
|
|
8
2
|
|
|
9
3
|
/**
|
|
10
4
|
* Claude Code adapter.
|
|
@@ -23,301 +17,16 @@ import type { CopyResults, ManagedPaths, RepoInfo, StackConfig } from '../types.
|
|
|
23
17
|
*/
|
|
24
18
|
|
|
25
19
|
export const IDE_ID = 'claude-code'
|
|
26
|
-
export const IDE_LABEL = 'Claude Code'
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
repoInfo?: RepoInfo
|
|
53
|
-
): Promise<CopyResults> {
|
|
54
|
-
const srcRoot = getOrchestratorRoot(pkgRoot)
|
|
55
|
-
const results: CopyResults = { copied: [], skipped: [], created: [] }
|
|
56
|
-
|
|
57
|
-
const excludedSkills = stack ? getExcludedSkills(stack) : new Set<string>()
|
|
58
|
-
const excludedAgents = stack ? getExcludedAgents(stack) : new Set<string>()
|
|
59
|
-
|
|
60
|
-
// 1. Build CLAUDE.md ← copilot-instructions + instructions/* + agent index + skill index
|
|
61
|
-
const claudeMd = resolve(projectRoot, 'CLAUDE.md')
|
|
62
|
-
if (!existsSync(claudeMd)) {
|
|
63
|
-
const sections: string[] = []
|
|
64
|
-
|
|
65
|
-
// Claude-specific header (not copilot-instructions.md verbatim)
|
|
66
|
-
sections.push(
|
|
67
|
-
'# Project Instructions\n\n' +
|
|
68
|
-
'All conventions, architecture, and project context are embedded below. ' +
|
|
69
|
-
'Skills are in `.claude/skills/` — read them when a task matches. ' +
|
|
70
|
-
'Agent definitions are in `.claude/agents/` — read the relevant file when adopting a persona.'
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
// Always-loaded instruction files
|
|
74
|
-
const instDir = resolve(srcRoot, 'instructions')
|
|
75
|
-
if (existsSync(instDir)) {
|
|
76
|
-
for (const file of (await readdir(instDir)).sort()) {
|
|
77
|
-
if (!file.endsWith('.md')) continue
|
|
78
|
-
const content = await readFile(resolve(instDir, file), 'utf8')
|
|
79
|
-
sections.push(
|
|
80
|
-
`\n---\n\n<!-- Source: instructions/${file} -->\n\n${stripFrontmatter(content)}`
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Agent reference (so Claude can adopt personas)
|
|
86
|
-
const agentsDir = resolve(srcRoot, 'agents')
|
|
87
|
-
if (existsSync(agentsDir)) {
|
|
88
|
-
const agentLines: string[] = ['\n---\n\n## Agent Definitions\n']
|
|
89
|
-
agentLines.push(
|
|
90
|
-
'The following agent personas are available. Adopt the appropriate persona when asked.\n'
|
|
91
|
-
)
|
|
92
|
-
for (const file of (await readdir(agentsDir)).sort()) {
|
|
93
|
-
if (!file.endsWith('.md')) continue
|
|
94
|
-
if (excludedAgents.has(file)) continue
|
|
95
|
-
const meta = parseFrontmatterMeta(
|
|
96
|
-
await readFile(resolve(agentsDir, file), 'utf8')
|
|
97
|
-
)
|
|
98
|
-
const name = meta['name'] ?? basename(file, '.agent.md')
|
|
99
|
-
const desc = meta['description'] ?? ''
|
|
100
|
-
agentLines.push(`- **${name}**: ${desc}`)
|
|
101
|
-
}
|
|
102
|
-
agentLines.push(
|
|
103
|
-
'\nFull agent definitions are in `.claude/agents/`. Read the relevant file when adopting a persona.'
|
|
104
|
-
)
|
|
105
|
-
sections.push(agentLines.join('\n'))
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Skill index
|
|
109
|
-
const skillsDir = resolve(srcRoot, 'skills')
|
|
110
|
-
if (existsSync(skillsDir)) {
|
|
111
|
-
const skillLines: string[] = ['\n---\n\n## Available Skills\n']
|
|
112
|
-
skillLines.push(
|
|
113
|
-
'Skills are on-demand knowledge files. Read the file when the task matches.\n'
|
|
114
|
-
)
|
|
115
|
-
const subdirs = (
|
|
116
|
-
await readdir(skillsDir, { withFileTypes: true })
|
|
117
|
-
).filter((e) => e.isDirectory())
|
|
118
|
-
for (const entry of subdirs.sort((a, b) =>
|
|
119
|
-
a.name.localeCompare(b.name)
|
|
120
|
-
)) {
|
|
121
|
-
if (excludedSkills.has(entry.name)) continue
|
|
122
|
-
const skillFile = resolve(skillsDir, entry.name, 'SKILL.md')
|
|
123
|
-
if (!existsSync(skillFile)) continue
|
|
124
|
-
const meta = parseFrontmatterMeta(await readFile(skillFile, 'utf8'))
|
|
125
|
-
const desc = meta['description'] ?? ''
|
|
126
|
-
skillLines.push(
|
|
127
|
-
`- **${entry.name}** (\`.claude/skills/${entry.name}.md\`): ${desc}`
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Plugin skills
|
|
132
|
-
const pluginsRoot = getPluginsRoot(pkgRoot)
|
|
133
|
-
const includedPlugins = stack ? getIncludedPluginIds(stack) : undefined
|
|
134
|
-
const pluginEntries = await getPluginSkillEntries(pluginsRoot, includedPlugins)
|
|
135
|
-
for (const { id, skillPath } of pluginEntries.sort((a, b) => a.id.localeCompare(b.id))) {
|
|
136
|
-
const pluginMeta = parseFrontmatterMeta(await readFile(skillPath, 'utf8'))
|
|
137
|
-
const pluginDesc = pluginMeta['description'] ?? ''
|
|
138
|
-
skillLines.push(
|
|
139
|
-
`- **${id}** (\`.claude/skills/${id}.md\`): ${pluginDesc}`
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
sections.push(skillLines.join('\n'))
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
await writeFile(claudeMd, sections.join('\n') + '\n')
|
|
147
|
-
results.created.push(claudeMd)
|
|
148
|
-
} else {
|
|
149
|
-
results.skipped.push(claudeMd)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const claudeDir = resolve(projectRoot, '.claude')
|
|
153
|
-
|
|
154
|
-
// 2. Agent definitions → .claude/agents/
|
|
155
|
-
const agentsDir = resolve(srcRoot, 'agents')
|
|
156
|
-
if (existsSync(agentsDir)) {
|
|
157
|
-
const destAgents = resolve(claudeDir, 'agents')
|
|
158
|
-
await mkdir(destAgents, { recursive: true })
|
|
159
|
-
for (const file of await readdir(agentsDir)) {
|
|
160
|
-
if (!file.endsWith('.md')) continue
|
|
161
|
-
if (excludedAgents.has(file)) continue
|
|
162
|
-
const destPath = resolve(destAgents, file)
|
|
163
|
-
if (existsSync(destPath)) {
|
|
164
|
-
results.skipped.push(destPath)
|
|
165
|
-
continue
|
|
166
|
-
}
|
|
167
|
-
const content = await readFile(resolve(agentsDir, file), 'utf8')
|
|
168
|
-
await writeFile(destPath, stripFrontmatter(content) + '\n')
|
|
169
|
-
results.created.push(destPath)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// 3. Skills → .claude/skills/<name>.md
|
|
174
|
-
const skillsDir = resolve(srcRoot, 'skills')
|
|
175
|
-
if (existsSync(skillsDir)) {
|
|
176
|
-
const destSkills = resolve(claudeDir, 'skills')
|
|
177
|
-
await mkdir(destSkills, { recursive: true })
|
|
178
|
-
const subdirs = (
|
|
179
|
-
await readdir(skillsDir, { withFileTypes: true })
|
|
180
|
-
).filter((e) => e.isDirectory())
|
|
181
|
-
for (const entry of subdirs) {
|
|
182
|
-
if (excludedSkills.has(entry.name)) continue
|
|
183
|
-
const skillFile = resolve(skillsDir, entry.name, 'SKILL.md')
|
|
184
|
-
if (!existsSync(skillFile)) continue
|
|
185
|
-
const destPath = resolve(destSkills, `${entry.name}.md`)
|
|
186
|
-
if (existsSync(destPath)) {
|
|
187
|
-
results.skipped.push(destPath)
|
|
188
|
-
continue
|
|
189
|
-
}
|
|
190
|
-
const content = await readFile(skillFile, 'utf8')
|
|
191
|
-
await writeFile(destPath, stripFrontmatter(content) + '\n')
|
|
192
|
-
results.created.push(destPath)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// 3b. Plugin skills → .claude/skills/<plugin-id>.md
|
|
197
|
-
{
|
|
198
|
-
const pluginsRoot = getPluginsRoot(pkgRoot)
|
|
199
|
-
const includedPlugins = stack ? getIncludedPluginIds(stack) : undefined
|
|
200
|
-
const pluginEntries = await getPluginSkillEntries(pluginsRoot, includedPlugins)
|
|
201
|
-
const destSkills = resolve(claudeDir, 'skills')
|
|
202
|
-
await mkdir(destSkills, { recursive: true })
|
|
203
|
-
for (const { id, skillPath } of pluginEntries) {
|
|
204
|
-
const destPath = resolve(destSkills, `${id}.md`)
|
|
205
|
-
if (existsSync(destPath)) {
|
|
206
|
-
results.skipped.push(destPath)
|
|
207
|
-
continue
|
|
208
|
-
}
|
|
209
|
-
const content = await readFile(skillPath, 'utf8')
|
|
210
|
-
await writeFile(destPath, stripFrontmatter(content) + '\n')
|
|
211
|
-
results.created.push(destPath)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// 4. Prompts → .claude/commands/<name>.md
|
|
216
|
-
const promptDir = resolve(srcRoot, 'prompts')
|
|
217
|
-
if (existsSync(promptDir)) {
|
|
218
|
-
const destCmds = resolve(claudeDir, 'commands')
|
|
219
|
-
await mkdir(destCmds, { recursive: true })
|
|
220
|
-
for (const file of await readdir(promptDir)) {
|
|
221
|
-
if (!file.endsWith('.md')) continue
|
|
222
|
-
const name = basename(file, '.prompt.md') || basename(file, '.md')
|
|
223
|
-
const destPath = resolve(destCmds, `${name}.md`)
|
|
224
|
-
if (existsSync(destPath)) {
|
|
225
|
-
results.skipped.push(destPath)
|
|
226
|
-
continue
|
|
227
|
-
}
|
|
228
|
-
const content = await readFile(resolve(promptDir, file), 'utf8')
|
|
229
|
-
await writeFile(destPath, stripFrontmatter(content) + '\n')
|
|
230
|
-
results.created.push(destPath)
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// 5. Agent Workflows → .claude/commands/workflow-<name>.md
|
|
235
|
-
const wfDir = resolve(srcRoot, 'agent-workflows')
|
|
236
|
-
if (existsSync(wfDir)) {
|
|
237
|
-
const destCmds = resolve(claudeDir, 'commands')
|
|
238
|
-
await mkdir(destCmds, { recursive: true })
|
|
239
|
-
for (const file of await readdir(wfDir)) {
|
|
240
|
-
if (!file.endsWith('.md')) continue
|
|
241
|
-
if (file === 'README.md') continue // Skip README — not a workflow
|
|
242
|
-
const name = basename(file, '.md')
|
|
243
|
-
const destPath = resolve(destCmds, `workflow-${name}.md`)
|
|
244
|
-
if (existsSync(destPath)) {
|
|
245
|
-
results.skipped.push(destPath)
|
|
246
|
-
continue
|
|
247
|
-
}
|
|
248
|
-
const content = await readFile(resolve(wfDir, file), 'utf8')
|
|
249
|
-
await writeFile(destPath, stripFrontmatter(content) + '\n')
|
|
250
|
-
results.created.push(destPath)
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// 6. Customizations (scaffold once, pre-populated with stack choices)
|
|
255
|
-
const custDir = resolve(srcRoot, 'customizations')
|
|
256
|
-
if (existsSync(custDir)) {
|
|
257
|
-
const destCust = resolve(claudeDir, 'customizations')
|
|
258
|
-
const custTransform = stack ? getCustomizationsTransform(stack) : undefined
|
|
259
|
-
const sub = await copyDir(custDir, destCust, { transform: custTransform })
|
|
260
|
-
results.created.push(...sub.created)
|
|
261
|
-
results.skipped.push(...sub.skipped)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// 7. MCP server config → .claude/mcp.json (scaffold once)
|
|
265
|
-
const mcpResult = await scaffoldMcpConfig(
|
|
266
|
-
projectRoot,
|
|
267
|
-
'.claude/mcp.json',
|
|
268
|
-
stack,
|
|
269
|
-
repoInfo,
|
|
270
|
-
'claude-code'
|
|
271
|
-
)
|
|
272
|
-
results[mcpResult.action].push(mcpResult.path)
|
|
273
|
-
|
|
274
|
-
return results
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ─── Update ───────────────────────────────────────────────────────
|
|
278
|
-
|
|
279
|
-
export async function update(
|
|
280
|
-
pkgRoot: string,
|
|
281
|
-
projectRoot: string,
|
|
282
|
-
stack?: StackConfig
|
|
283
|
-
): Promise<CopyResults> {
|
|
284
|
-
const results: CopyResults = { copied: [], skipped: [], created: [] }
|
|
285
|
-
const claudeDir = resolve(projectRoot, '.claude')
|
|
286
|
-
|
|
287
|
-
// 1. Regenerate CLAUDE.md (overwrite)
|
|
288
|
-
const claudeMd = resolve(projectRoot, 'CLAUDE.md')
|
|
289
|
-
if (existsSync(claudeMd)) {
|
|
290
|
-
await unlink(claudeMd)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// 2. Remove existing framework files so install() recreates them
|
|
294
|
-
const frameworkDirs = ['agents', 'skills', 'commands']
|
|
295
|
-
for (const dir of frameworkDirs) {
|
|
296
|
-
const dirPath = resolve(claudeDir, dir)
|
|
297
|
-
if (existsSync(dirPath)) {
|
|
298
|
-
await rm(dirPath, { recursive: true })
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// 3. Re-run full install (CLAUDE.md + agents + skills + commands)
|
|
303
|
-
const installResult = await install(pkgRoot, projectRoot, stack)
|
|
304
|
-
// Everything install created is an "update" copy
|
|
305
|
-
results.copied.push(...installResult.created)
|
|
306
|
-
results.skipped.push(...installResult.skipped)
|
|
307
|
-
|
|
308
|
-
return results
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// ─── Managed paths ────────────────────────────────────────────────
|
|
312
|
-
|
|
313
|
-
export function getManagedPaths(): ManagedPaths {
|
|
314
|
-
return {
|
|
315
|
-
framework: [
|
|
316
|
-
'CLAUDE.md',
|
|
317
|
-
'.claude/agents/',
|
|
318
|
-
'.claude/skills/',
|
|
319
|
-
'.claude/commands/',
|
|
320
|
-
],
|
|
321
|
-
customizable: ['.claude/customizations/', '.claude/mcp.json'],
|
|
322
|
-
}
|
|
323
|
-
}
|
|
21
|
+
const { install, update, getManagedPaths } = createSingleFileAdapter({
|
|
22
|
+
rootFile: 'CLAUDE.md',
|
|
23
|
+
dotDir: '.claude',
|
|
24
|
+
mcpConfigPath: '.claude/mcp.json',
|
|
25
|
+
mcpFormat: 'claude-code',
|
|
26
|
+
promptsDir: 'commands',
|
|
27
|
+
workflowsDir: 'commands',
|
|
28
|
+
workflowPrefix: 'workflow-',
|
|
29
|
+
frameworkDirs: ['agents', 'skills', 'commands'],
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export { install, update, getManagedPaths }
|
|
@@ -5,6 +5,7 @@ import { copyDir, getOrchestratorRoot, removeDirIfExists, getPluginsRoot, getPlu
|
|
|
5
5
|
import { scaffoldMcpConfig } from '../mcp.js'
|
|
6
6
|
import { getExcludedSkills, getExcludedAgents, getCustomizationsTransform, getIncludedPluginIds } from '../stack-config.js'
|
|
7
7
|
import type { CopyResults, ManagedPaths, RepoInfo, StackConfig } from '../types.js'
|
|
8
|
+
import { splitFrontmatter, parseFrontmatterString } from './frontmatter.js'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Cursor adapter.
|
|
@@ -25,27 +26,6 @@ export const IDE_LABEL = 'Cursor'
|
|
|
25
26
|
|
|
26
27
|
// ─── Helpers ──────────────────────────────────────────────────────
|
|
27
28
|
|
|
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
29
|
interface MdcOptions {
|
|
50
30
|
description?: string
|
|
51
31
|
globs?: string[]
|
|
@@ -82,8 +62,8 @@ async function convertFile(
|
|
|
82
62
|
{ alwaysApply = false, descriptionFallback = '' }: ConvertFileOptions = {}
|
|
83
63
|
): Promise<string> {
|
|
84
64
|
const content = await readFile(srcPath, 'utf8')
|
|
85
|
-
const { frontmatter, body } =
|
|
86
|
-
const meta =
|
|
65
|
+
const { frontmatter, body } = splitFrontmatter(content)
|
|
66
|
+
const meta = parseFrontmatterString(frontmatter)
|
|
87
67
|
|
|
88
68
|
// Description: frontmatter > fallback > first heading
|
|
89
69
|
let description = meta['description'] ?? descriptionFallback
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared frontmatter utilities for IDE adapters.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface FrontmatterResult {
|
|
6
|
+
frontmatter: string
|
|
7
|
+
body: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Split content into frontmatter and body.
|
|
12
|
+
* Frontmatter is the YAML block between `---` delimiters.
|
|
13
|
+
*/
|
|
14
|
+
export function splitFrontmatter(content: string): FrontmatterResult {
|
|
15
|
+
const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
|
|
16
|
+
return m
|
|
17
|
+
? { frontmatter: m[1], body: m[2] }
|
|
18
|
+
: { frontmatter: '', body: content }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Strip frontmatter and return only the body, trimmed.
|
|
23
|
+
*/
|
|
24
|
+
export function stripFrontmatter(content: string): string {
|
|
25
|
+
return splitFrontmatter(content).body.trim()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse frontmatter YAML into a key-value record.
|
|
30
|
+
* Handles simple `key: value` and `key: 'value'` / `key: "value"` patterns.
|
|
31
|
+
*/
|
|
32
|
+
export function parseFrontmatterMeta(content: string): Record<string, string> {
|
|
33
|
+
const { frontmatter } = splitFrontmatter(content)
|
|
34
|
+
return parseFrontmatterString(frontmatter)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Parse a raw frontmatter string (without delimiters) into key-value pairs.
|
|
39
|
+
*/
|
|
40
|
+
export function parseFrontmatterString(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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IdeAdapter } from '../types.js'
|
|
2
|
+
|
|
3
|
+
/** Lazy-loaded IDE adapters for init/update commands. */
|
|
4
|
+
export const IDE_ADAPTERS: Record<string, () => Promise<IdeAdapter>> = {
|
|
5
|
+
vscode: () => import('./vscode.js') as Promise<IdeAdapter>,
|
|
6
|
+
cursor: () => import('./cursor.js') as Promise<IdeAdapter>,
|
|
7
|
+
'claude-code': () =>
|
|
8
|
+
import('./claude-code.js') as Promise<IdeAdapter>,
|
|
9
|
+
opencode: () =>
|
|
10
|
+
import('./opencode.js') as Promise<IdeAdapter>,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const VALID_IDES = Object.keys(IDE_ADAPTERS)
|