@clfhhc/bmad-methods-skills 0.2.3 → 0.3.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 +3 -1
- package/bin/bmad-skills.js +106 -47
- package/config.json +249 -1
- package/convert.js +101 -60
- package/docs/getting-started.md +6 -4
- package/docs/technical-reference.md +41 -15
- package/package.json +2 -2
- package/skills/bootstrap-bmad-skills/SKILL.md +28 -10
- package/src/converters/workflow-converter.js +4 -14
- package/src/utils/file-finder.js +22 -6
- package/src/utils/path-rewriter.js +22 -10
- package/src/utils/resource-migrator.js +17 -8
- package/src/utils/skill-writer.js +6 -1
package/README.md
CHANGED
|
@@ -24,7 +24,9 @@ This installs `bootstrap-bmad-skills` and `enhance-bmad-skills`. Then open your
|
|
|
24
24
|
1. **Fetches** the latest BMAD-METHOD from GitHub
|
|
25
25
|
2. **Converts** agents and workflows to Claude Skills format
|
|
26
26
|
3. **Installs** skills to your AI tool's directory
|
|
27
|
-
4. **Generates** module config files (
|
|
27
|
+
4. **Generates** module config files (default flat: `_config/bmm.yaml`, `_config/core.yaml`)
|
|
28
|
+
|
|
29
|
+
Output uses a **flat** layout by default (`bmm-analyst/`, `core-bmad-master/`, etc.) so AI tools can discover all skills. See [Technical Reference](docs/technical-reference.md) for nested layout and options.
|
|
28
30
|
|
|
29
31
|
## Supported Tools
|
|
30
32
|
|
package/bin/bmad-skills.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import fs from 'fs-extra';
|
|
@@ -51,7 +52,9 @@ async function install(args) {
|
|
|
51
52
|
const toolInfo = await detectTool(args);
|
|
52
53
|
if (!toolInfo) return;
|
|
53
54
|
|
|
54
|
-
console.log(
|
|
55
|
+
console.log(
|
|
56
|
+
`📦 Installation Target: ${toolInfo.name} (${toolInfo.path})${toolInfo.scope === 'global' ? ' [global]' : ''}`
|
|
57
|
+
);
|
|
55
58
|
console.log(`📂 Source: ${sourcePath}`);
|
|
56
59
|
|
|
57
60
|
// Get skills from source
|
|
@@ -71,12 +74,12 @@ async function install(args) {
|
|
|
71
74
|
|
|
72
75
|
console.log(`\nFound ${validSkills.length} modules to install.`);
|
|
73
76
|
|
|
74
|
-
// Install
|
|
77
|
+
// Install (toolInfo.path is absolute)
|
|
75
78
|
for (const skillName of validSkills) {
|
|
76
79
|
await installSkill(
|
|
77
80
|
skillName,
|
|
78
81
|
path.join(sourcePath, skillName),
|
|
79
|
-
path.join(
|
|
82
|
+
path.join(toolInfo.path, skillName),
|
|
80
83
|
force
|
|
81
84
|
);
|
|
82
85
|
}
|
|
@@ -105,7 +108,14 @@ async function init(args) {
|
|
|
105
108
|
// We filter out init-specific args to avoid confusion, but keep repo/branch overrides
|
|
106
109
|
const convertArgs = args.filter(
|
|
107
110
|
(a) =>
|
|
108
|
-
![
|
|
111
|
+
![
|
|
112
|
+
'init',
|
|
113
|
+
'--bootstrap',
|
|
114
|
+
'--force',
|
|
115
|
+
'--tool',
|
|
116
|
+
'--scope',
|
|
117
|
+
'--global',
|
|
118
|
+
].some((x) => a === x || a.startsWith(`${x}=`))
|
|
109
119
|
);
|
|
110
120
|
|
|
111
121
|
// Temporarily override argv for the imported module
|
|
@@ -145,12 +155,16 @@ async function init(args) {
|
|
|
145
155
|
|
|
146
156
|
// 2. Install
|
|
147
157
|
console.log(`\n--- Step 2: Installing to ${toolInfo.name} ---`);
|
|
148
|
-
|
|
158
|
+
const installArgs = [
|
|
149
159
|
'install',
|
|
150
160
|
`--from=${tempDir}`,
|
|
151
|
-
`--tool=${toolInfo.name.toLowerCase()}`,
|
|
152
|
-
'--force',
|
|
153
|
-
]
|
|
161
|
+
`--tool=${toolInfo.name.toLowerCase()}`,
|
|
162
|
+
'--force',
|
|
163
|
+
];
|
|
164
|
+
const scopeArg = args.find((a) => a.startsWith('--scope='));
|
|
165
|
+
if (scopeArg) installArgs.push(scopeArg);
|
|
166
|
+
else if (args.includes('--global')) installArgs.push('--global');
|
|
167
|
+
await install(installArgs);
|
|
154
168
|
|
|
155
169
|
// 3. Install bundled skills (bootstrap-bmad-skills, enhance-bmad-skills)
|
|
156
170
|
// We reuse the logic from the standard init, but silence it slightly or just run it
|
|
@@ -162,7 +176,7 @@ async function init(args) {
|
|
|
162
176
|
await installSkill(
|
|
163
177
|
skill,
|
|
164
178
|
path.join(skillsDir, skill),
|
|
165
|
-
path.join(
|
|
179
|
+
path.join(toolInfo.path, skill),
|
|
166
180
|
true
|
|
167
181
|
);
|
|
168
182
|
}
|
|
@@ -212,7 +226,7 @@ async function init(args) {
|
|
|
212
226
|
try {
|
|
213
227
|
for (const skillName of skillsToInstall) {
|
|
214
228
|
const sourceDir = path.join(skillsDir, skillName);
|
|
215
|
-
const targetDir = path.
|
|
229
|
+
const targetDir = path.join(toolInfo.path, skillName);
|
|
216
230
|
|
|
217
231
|
await installSkill(skillName, sourceDir, targetDir, force);
|
|
218
232
|
}
|
|
@@ -230,7 +244,7 @@ async function init(args) {
|
|
|
230
244
|
}
|
|
231
245
|
|
|
232
246
|
/**
|
|
233
|
-
*
|
|
247
|
+
* Helper to copy skill with checks
|
|
234
248
|
*/
|
|
235
249
|
async function installSkill(name, source, target, force) {
|
|
236
250
|
if (await fs.pathExists(target)) {
|
|
@@ -249,53 +263,96 @@ async function installSkill(name, source, target, force) {
|
|
|
249
263
|
}
|
|
250
264
|
|
|
251
265
|
/**
|
|
252
|
-
*
|
|
266
|
+
* Tool definitions: project path (relative to cwd) and global path (relative to home).
|
|
267
|
+
* Paths match bootstrap-bmad-skills SKILL: .cursor/skills, .agent/skills, .claude/skills (project);
|
|
268
|
+
* ~/.cursor/skills, ~/.gemini/antigravity/skills, ~/.claude/skills (global).
|
|
269
|
+
*/
|
|
270
|
+
const TOOLS = [
|
|
271
|
+
{
|
|
272
|
+
name: 'Antigravity',
|
|
273
|
+
projectPath: '.agent/skills',
|
|
274
|
+
globalPath: '.gemini/antigravity/skills',
|
|
275
|
+
active: () => fs.pathExists('.agent'),
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: 'Cursor',
|
|
279
|
+
projectPath: '.cursor/skills',
|
|
280
|
+
globalPath: '.cursor/skills',
|
|
281
|
+
active: () => fs.pathExists('.cursor'),
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'Claude Code (Local)',
|
|
285
|
+
projectPath: '.claude/skills',
|
|
286
|
+
globalPath: '.claude/skills',
|
|
287
|
+
active: () => fs.pathExists('.claude'),
|
|
288
|
+
},
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Resolve scope: --scope=global|project or --global (shorthand for --scope=global).
|
|
293
|
+
* Default is project.
|
|
294
|
+
*/
|
|
295
|
+
function parseScope(args) {
|
|
296
|
+
const scopeArg = args.find((a) => a.startsWith('--scope='))?.split('=')[1];
|
|
297
|
+
if (scopeArg && scopeArg !== 'global' && scopeArg !== 'project') {
|
|
298
|
+
console.warn(`⚠ Unknown --scope=${scopeArg}, using 'project'.`);
|
|
299
|
+
}
|
|
300
|
+
if (scopeArg === 'global' || scopeArg === 'project') return scopeArg;
|
|
301
|
+
if (args.includes('--global')) return 'global';
|
|
302
|
+
return 'project';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Detect AI tool and return { name, path, scope }.
|
|
307
|
+
* @param {string[]} args - CLI args (e.g. from process.argv.slice(2))
|
|
308
|
+
* @returns {Promise<{ name: string, path: string, scope: 'project'|'global' }|null>}
|
|
309
|
+
* path is the absolute directory to install skills into (project or global).
|
|
253
310
|
*/
|
|
254
311
|
async function detectTool(args) {
|
|
255
312
|
const toolArg = args.find((a) => a.startsWith('--tool='))?.split('=')[1];
|
|
256
313
|
const force = args.includes('--force');
|
|
314
|
+
const scope = parseScope(args);
|
|
257
315
|
|
|
258
|
-
|
|
259
|
-
{
|
|
260
|
-
name: 'Antigravity',
|
|
261
|
-
path: '.agent/skills',
|
|
262
|
-
active: await fs.pathExists('.agent'),
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
name: 'Cursor',
|
|
266
|
-
path: '.cursor/skills',
|
|
267
|
-
active: await fs.pathExists('.cursor'),
|
|
268
|
-
},
|
|
269
|
-
{
|
|
270
|
-
name: 'Claude Code (Local)',
|
|
271
|
-
path: '.claude/skills',
|
|
272
|
-
active: await fs.pathExists('.claude'),
|
|
273
|
-
},
|
|
274
|
-
];
|
|
275
|
-
|
|
276
|
-
let selectedTool = tools.find((t) => t.active);
|
|
277
|
-
|
|
316
|
+
let selected = null;
|
|
278
317
|
if (toolArg) {
|
|
279
|
-
|
|
318
|
+
selected = TOOLS.find((t) =>
|
|
280
319
|
t.name.toLowerCase().includes(toolArg.toLowerCase())
|
|
281
320
|
);
|
|
282
321
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
322
|
+
if (!selected && scope === 'project') {
|
|
323
|
+
for (const t of TOOLS) {
|
|
324
|
+
if (await t.active()) {
|
|
325
|
+
selected = t;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
288
328
|
}
|
|
329
|
+
}
|
|
289
330
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
'
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
331
|
+
if (!selected) {
|
|
332
|
+
if (scope === 'global') {
|
|
333
|
+
console.log('❌ For --scope=global, --tool=<name> is required.');
|
|
334
|
+
console.log(' Example: --tool=cursor');
|
|
335
|
+
console.log(' Available tools: antigravity, cursor, claude');
|
|
336
|
+
} else if (force) {
|
|
337
|
+
selected = TOOLS.find((t) => t.name === 'Cursor') || TOOLS[0];
|
|
338
|
+
} else {
|
|
339
|
+
console.log(
|
|
340
|
+
'❌ No AI tool directory detected (.agent, .cursor, .claude).'
|
|
341
|
+
);
|
|
342
|
+
console.log(
|
|
343
|
+
' Use --tool=<name> to force installation or ensure you are in the project root.'
|
|
344
|
+
);
|
|
345
|
+
console.log(' Available tools: antigravity, cursor, claude');
|
|
346
|
+
}
|
|
347
|
+
if (!selected) return null;
|
|
296
348
|
}
|
|
297
349
|
|
|
298
|
-
|
|
350
|
+
const absPath =
|
|
351
|
+
scope === 'global'
|
|
352
|
+
? path.join(os.homedir(), ...selected.globalPath.split('/'))
|
|
353
|
+
: path.resolve(process.cwd(), selected.projectPath);
|
|
354
|
+
|
|
355
|
+
return { name: selected.name, path: absPath, scope };
|
|
299
356
|
}
|
|
300
357
|
|
|
301
358
|
function printHelp() {
|
|
@@ -308,8 +365,10 @@ Commands:
|
|
|
308
365
|
[no command] Run the BMAD-to-Skills converter (proxy to convert.js)
|
|
309
366
|
|
|
310
367
|
Options (for init/install):
|
|
311
|
-
--tool=<name> Specify tool (antigravity, cursor, claude)
|
|
312
|
-
--
|
|
368
|
+
--tool=<name> Specify tool (antigravity, cursor, claude). Required for --scope=global.
|
|
369
|
+
--scope=<s> Install destination: project (default) or global
|
|
370
|
+
--global Shorthand for --scope=global (installs to ~/.cursor/skills, etc.)
|
|
371
|
+
--force Overwrite existing skills. With no tool dir, defaults to Cursor.
|
|
313
372
|
--bootstrap Automatically fetch, convert, and install full suite
|
|
314
373
|
--from=<path> (install only) Source directory containing skills
|
|
315
374
|
|
package/config.json
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
"outputDir": "./skills",
|
|
5
5
|
"tempDir": "./.temp/bmad-method",
|
|
6
6
|
"modules": ["bmm", "core"],
|
|
7
|
+
"outputStructure": "flat",
|
|
8
|
+
"sourcePathPrefixesToStrip": ["src/modules/", "src/"],
|
|
9
|
+
"moduleExtractionPatterns": [
|
|
10
|
+
{ "pattern": "^src\\/modules\\/([^/]+)\\/", "group": 1 },
|
|
11
|
+
{ "pattern": "^src\\/([^/]+)\\/", "group": 1 }
|
|
12
|
+
],
|
|
7
13
|
"agentPaths": ["src/*/agents"],
|
|
8
14
|
"workflowPaths": ["src/*/workflows"],
|
|
9
15
|
"enhancements": {
|
|
@@ -20,28 +26,33 @@
|
|
|
20
26
|
{
|
|
21
27
|
"src": "src/bmm/agents/tech-writer/tech-writer-sidecar/documentation-standards.md",
|
|
22
28
|
"dest": "bmm/tech-writer/data/documentation-standards.md",
|
|
29
|
+
"destFlat": "bmm-tech-writer/data/documentation-standards.md",
|
|
23
30
|
"name": "Documentation Standards"
|
|
24
31
|
},
|
|
25
32
|
{
|
|
26
33
|
"src": "src/bmm/testarch/tea-index.csv",
|
|
27
34
|
"dest": "bmm/tea/tea-index.csv",
|
|
35
|
+
"destFlat": "bmm-tea/tea-index.csv",
|
|
28
36
|
"name": "TEA Index"
|
|
29
37
|
},
|
|
30
38
|
{
|
|
31
39
|
"src": "src/bmm/testarch/knowledge",
|
|
32
40
|
"dest": "bmm/tea/knowledge",
|
|
41
|
+
"destFlat": "bmm-tea/knowledge",
|
|
33
42
|
"name": "TEA Knowledge Base",
|
|
34
43
|
"isDirectory": true
|
|
35
44
|
},
|
|
36
45
|
{
|
|
37
46
|
"src": "src/core/resources/excalidraw",
|
|
38
47
|
"dest": "core/resources/excalidraw",
|
|
48
|
+
"destFlat": "_resources/excalidraw",
|
|
39
49
|
"name": "Excalidraw Resources",
|
|
40
50
|
"isDirectory": true
|
|
41
51
|
},
|
|
42
52
|
{
|
|
43
53
|
"src": "src/bmm/workflows/excalidraw-diagrams/_shared",
|
|
44
54
|
"dest": "bmm/excalidraw-diagrams/_shared",
|
|
55
|
+
"destFlat": "_resources/bmm-excalidraw-shared",
|
|
45
56
|
"name": "Excalidraw Diagrams Shared (templates, library)",
|
|
46
57
|
"isDirectory": true
|
|
47
58
|
}
|
|
@@ -54,9 +65,24 @@
|
|
|
54
65
|
"pathPatterns": [
|
|
55
66
|
{
|
|
56
67
|
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/workflow-status/init/workflow\\.(yaml|md)",
|
|
57
|
-
"replacement": "{skill-root}/bmm/init/SKILL.md",
|
|
68
|
+
"replacement": "{skill-root}/bmm/workflow-init/SKILL.md",
|
|
58
69
|
"description": "Init nested workflow (must run before workflow-status dir)"
|
|
59
70
|
},
|
|
71
|
+
{
|
|
72
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/workflow-status/init",
|
|
73
|
+
"replacement": "{skill-root}/bmm/workflow-init",
|
|
74
|
+
"description": "Init nested workflow dir (source path; must run before workflow-status pattern)"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"pattern": "\\{skill-root\\}/bmm/workflow-status/init",
|
|
78
|
+
"replacement": "{skill-root}/bmm/workflow-init",
|
|
79
|
+
"description": "Init nested workflow dir (nested layout; fix workflow-status/init)"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"pattern": "\\{skill-root\\}/bmm/init",
|
|
83
|
+
"replacement": "{skill-root}/bmm/workflow-init",
|
|
84
|
+
"description": "Init nested workflow (nested; bmm/init -> bmm/workflow-init)"
|
|
85
|
+
},
|
|
60
86
|
{
|
|
61
87
|
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/document-project/workflows",
|
|
62
88
|
"replacement": "{skill-root}/bmm/document-project",
|
|
@@ -252,5 +278,227 @@
|
|
|
252
278
|
"replacement": "{skill-root}/core/{workflow-name}/",
|
|
253
279
|
"description": "Template {workflow-name}"
|
|
254
280
|
}
|
|
281
|
+
],
|
|
282
|
+
"pathPatternsFlat": [
|
|
283
|
+
{
|
|
284
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/workflow-status/init/workflow\\.(yaml|md)",
|
|
285
|
+
"replacement": "{skill-root}/bmm-workflow-init/SKILL.md",
|
|
286
|
+
"description": "Init nested workflow (must run before workflow-status dir)"
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/workflow-status/init",
|
|
290
|
+
"replacement": "{skill-root}/bmm-workflow-init",
|
|
291
|
+
"description": "Init nested workflow dir (source path; must run before workflow-status pattern)"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"pattern": "\\{skill-root\\}/bmm-workflow-status/init",
|
|
295
|
+
"replacement": "{skill-root}/bmm-workflow-init",
|
|
296
|
+
"description": "Init nested workflow dir (flat; fix workflow-status/init)"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"pattern": "\\{skill-root\\}/bmm/workflow-status/init",
|
|
300
|
+
"replacement": "{skill-root}/bmm-workflow-init",
|
|
301
|
+
"description": "Init nested workflow dir (flat; fix nested-form when using pathPatternsFlat)"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"pattern": "\\{skill-root\\}/bmm-init",
|
|
305
|
+
"replacement": "{skill-root}/bmm-workflow-init",
|
|
306
|
+
"description": "Init nested workflow (flat; legacy bmm-init -> bmm-workflow-init)"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/document-project/workflows",
|
|
310
|
+
"replacement": "{skill-root}/bmm-document-project",
|
|
311
|
+
"description": "document-project workflows dir"
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"pattern": "\\{project-root\\}/_bmad/core/workflows/([^/\\s'\"]+)/workflow\\.(yaml|md|xml)",
|
|
315
|
+
"replacement": "{skill-root}/core-$1/SKILL.md",
|
|
316
|
+
"description": "Core workflow files"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/(?:\\d-[^/]+/)?([^/\\s'\"]+)/workflow\\.(yaml|md|xml)",
|
|
320
|
+
"replacement": "{skill-root}/bmm-$1/SKILL.md",
|
|
321
|
+
"description": "BMM workflow files (incl. category folders)"
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/(?:workflows/)?testarch/([^/\\s'\"]+)/workflow\\.(yaml|md|xml)",
|
|
325
|
+
"replacement": "{skill-root}/bmm-testarch-$1/SKILL.md",
|
|
326
|
+
"description": "Testarch workflow files"
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/excalidraw-diagrams/([^/\\s'\"]+)/workflow\\.(yaml|md|xml)",
|
|
330
|
+
"replacement": "{skill-root}/bmm-$1/SKILL.md",
|
|
331
|
+
"description": "Excalidraw-diagrams workflow files"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/bmad-quick-flow/([^/\\s'\"]+)/workflow\\.(yaml|md)",
|
|
335
|
+
"replacement": "{skill-root}/bmm-$1/SKILL.md",
|
|
336
|
+
"description": "bmad-quick-flow workflow files"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"pattern": "\\{project-root\\}/_bmad/core/tasks/workflow\\.xml",
|
|
340
|
+
"replacement": "{skill-root}/core-tasks/SKILL.md",
|
|
341
|
+
"description": "Core tasks workflow.xml"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"pattern": "\\{project-root\\}/_bmad/core/resources/excalidraw/([^/\\s'\"]+)",
|
|
345
|
+
"replacement": "{skill-root}/_resources/excalidraw/$1",
|
|
346
|
+
"description": "Excalidraw helper resources"
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/excalidraw-diagrams/_shared/([^/\\s'\"]+)",
|
|
350
|
+
"replacement": "{skill-root}/_resources/bmm-excalidraw-shared/$1",
|
|
351
|
+
"description": "Excalidraw-diagrams _shared files"
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/excalidraw-diagrams/_shared(?!/)",
|
|
355
|
+
"replacement": "{skill-root}/_resources/bmm-excalidraw-shared",
|
|
356
|
+
"description": "Excalidraw-diagrams _shared directory"
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"pattern": "\\{project-root\\}/_bmad/(core|bmm)/workflows/(?:\\d-[^/]+/)?([^/\\s'\"]+)/([^/\\s'\"]+\\.(md|csv|yaml|xml))",
|
|
360
|
+
"replacement": "{skill-root}/$1-$2/$3",
|
|
361
|
+
"description": "Files within workflow dirs"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"pattern": "\\{project-root\\}/_bmad/(core|bmm)/workflows/(?:\\d-[^/]+/)?([^/\\s'\"]+)/steps/([^/\\s'\"]+)",
|
|
365
|
+
"replacement": "{skill-root}/$1-$2/steps/$3",
|
|
366
|
+
"description": "Step files within workflows"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/\\d-[^/]+/([^/\\s'\"]+)(?=['\\s`])",
|
|
370
|
+
"replacement": "{skill-root}/bmm-$1",
|
|
371
|
+
"description": "BMM workflows dir with category"
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/([a-z][-a-z0-9]+)(?=['\\s`])",
|
|
375
|
+
"replacement": "{skill-root}/bmm-$1",
|
|
376
|
+
"description": "BMM workflows dir (no category)"
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/bmad-quick-flow/([^/\\s'\"]+)(?=['\\s`])",
|
|
380
|
+
"replacement": "{skill-root}/bmm-$1",
|
|
381
|
+
"description": "bmad-quick-flow directory refs"
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/testarch/([^/\\s'\"]+)(?=['\\s`])",
|
|
385
|
+
"replacement": "{skill-root}/bmm-testarch-$1",
|
|
386
|
+
"description": "Testarch directory refs"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/workflows/workflow-status(?=['\\s`/])",
|
|
390
|
+
"replacement": "{skill-root}/bmm-workflow-status",
|
|
391
|
+
"description": "workflow-status directory"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"pattern": "\\{project-root\\}/_bmad/(bmm|core)/config\\.yaml",
|
|
395
|
+
"replacement": "{skill-root}/_config/$1.yaml",
|
|
396
|
+
"description": "Module config files"
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
"pattern": "\\{project-root\\}/_bmad/_config/agent-manifest\\.csv",
|
|
400
|
+
"replacement": "{skill-root}/agent-manifest.csv",
|
|
401
|
+
"description": "Agent manifest"
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/testarch/tea-index\\.csv",
|
|
405
|
+
"replacement": "{skill-root}/bmm-tea/tea-index.csv",
|
|
406
|
+
"description": "TEA tea-index.csv"
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/testarch/knowledge/",
|
|
410
|
+
"replacement": "{skill-root}/bmm-tea/knowledge/",
|
|
411
|
+
"description": "TEA Knowledge Base (migrated)"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"pattern": "\\{project-root\\}/_bmad/bmm/data/documentation-standards\\.md",
|
|
415
|
+
"replacement": "{skill-root}/bmm-tech-writer/data/documentation-standards.md",
|
|
416
|
+
"description": "documentation-standards (migrated to tech-writer)"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"pattern": "\\{project-root\\}/_bmad/_memory/([^/\\s'\"]+)",
|
|
420
|
+
"replacement": "{runtime-memory}/$1",
|
|
421
|
+
"description": "Runtime memory"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"pattern": "\\{project-root\\}/_bmad/core/tasks/([^/\\s'\"]+\\.xml)",
|
|
425
|
+
"replacement": "{skill-root}/core-tasks/$1",
|
|
426
|
+
"description": "Core tasks XML files"
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"pattern": "\\{project-root\\}/_bmad/(bmm|core)/tasks/([^/\\s'\"]+)",
|
|
430
|
+
"replacement": "{skill-root}/$1-tasks/$2",
|
|
431
|
+
"description": "Module tasks"
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
"pattern": "\\{project-root\\}/_data/([^/\\s'\"]+)",
|
|
435
|
+
"replacement": "{project-data}/$1",
|
|
436
|
+
"description": "Project data files"
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
"pattern": "\\{project-root\\}/src/modules/(bmm|core)/workflows/(?:\\d-[^/]+/)?([^/\\s'\"]+)",
|
|
440
|
+
"replacement": "{skill-root}/$1-$2",
|
|
441
|
+
"description": "src/modules workflow refs"
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
"pattern": "\\{project-root\\}/_bmad/cis/workflows/([^/\\s'\"]+)/workflow\\.(yaml|md|xml)",
|
|
445
|
+
"replacement": "{skill-root}/cis-$1/SKILL.md",
|
|
446
|
+
"description": "CIS workflow files"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"pattern": "\\{project-root\\}/_bmad/(bmm|core|cis)/workflows/([a-z][-a-z0-9]+)(?=['\\s`]|$)",
|
|
450
|
+
"replacement": "{skill-root}/$1-$2",
|
|
451
|
+
"description": "Generic workflow fallback (only when no extra path segment)"
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"pattern": "\\{project-root\\}/_bmad/\\{module-code\\}/workflows/",
|
|
455
|
+
"replacement": "{skill-root}/{module-code}/",
|
|
456
|
+
"description": "Template {module-code}"
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
"pattern": "\\{project-root\\}/_bmad/\\{module-id\\}/workflows/",
|
|
460
|
+
"replacement": "{skill-root}/{module-id}/",
|
|
461
|
+
"description": "Template {module-id}"
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
"pattern": "\\{project-root\\}/_bmad/\\{module\\}/",
|
|
465
|
+
"replacement": "{skill-root}/{module}/",
|
|
466
|
+
"description": "Template {module}"
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"pattern": "\\{project-root\\}/_bmad/\\{module_code\\}/workflows/",
|
|
470
|
+
"replacement": "{skill-root}/{module_code}/",
|
|
471
|
+
"description": "Template {module_code}"
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"pattern": "\\{project-root\\}/_bmad/\\[module\\]/",
|
|
475
|
+
"replacement": "{skill-root}/[module]/",
|
|
476
|
+
"description": "Template [module]"
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"pattern": "\\{project-root\\}/_bmad/\\[module-path\\]/workflows/",
|
|
480
|
+
"replacement": "{skill-root}/[module-path]/",
|
|
481
|
+
"description": "Template [module-path]"
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
"pattern": "\\{project-root\\}/_bmad/\\[MODULE FOLDER\\]/",
|
|
485
|
+
"replacement": "{skill-root}/[MODULE FOLDER]/",
|
|
486
|
+
"description": "Template [MODULE FOLDER]"
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"pattern": "\\{project-root\\}/\\.\\.\\./([^/\\s'\"]+)/workflow\\.(yaml|md|xml)",
|
|
490
|
+
"replacement": "{skill-root}/.../$1/SKILL.md",
|
|
491
|
+
"description": "Ellipsis workflow in docs"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
"pattern": "\\{project-root\\}/_bmad/\\.\\.\\./",
|
|
495
|
+
"replacement": "{skill-root}/.../",
|
|
496
|
+
"description": "Ellipsis in documentation"
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
"pattern": "\\{project-root\\}/_bmad/core/workflows/\\{workflow-name\\}/",
|
|
500
|
+
"replacement": "{skill-root}/core-{workflow-name}/",
|
|
501
|
+
"description": "Template {workflow-name}"
|
|
502
|
+
}
|
|
255
503
|
]
|
|
256
504
|
}
|
package/convert.js
CHANGED
|
@@ -96,35 +96,52 @@ async function getModuleConfigContent(moduleName, bmadRoot = null) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
* Generate config
|
|
100
|
-
*
|
|
99
|
+
* Generate config for each module in the output directory
|
|
100
|
+
* Nested: outputDir/{module}/config.yaml. Flat: outputDir/_config/{module}.yaml
|
|
101
101
|
* @param {string} outputDir - Output directory containing skills
|
|
102
102
|
* @param {string|null} bmadRoot - Optional path to BMAD repo for template lookup
|
|
103
|
+
* @param {string[]} modules - Module names (e.g. config.modules)
|
|
104
|
+
* @param {string} outputStructure - 'flat' | 'nested'
|
|
103
105
|
*/
|
|
104
|
-
async function generateModuleConfigs(
|
|
106
|
+
async function generateModuleConfigs(
|
|
107
|
+
outputDir,
|
|
108
|
+
bmadRoot = null,
|
|
109
|
+
modules = ['bmm', 'core'],
|
|
110
|
+
outputStructure = 'flat'
|
|
111
|
+
) {
|
|
105
112
|
console.log('📝 Generating module configs...');
|
|
106
113
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
if (outputStructure === 'flat') {
|
|
115
|
+
const configDir = path.join(outputDir, '_config');
|
|
116
|
+
await fs.ensureDir(configDir);
|
|
117
|
+
for (const moduleName of modules) {
|
|
118
|
+
const configPath = path.join(configDir, `${moduleName}.yaml`);
|
|
119
|
+
if (await fs.pathExists(configPath)) {
|
|
120
|
+
console.log(` ⏭ _config/${moduleName}.yaml (exists)`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const configContent = await getModuleConfigContent(moduleName, bmadRoot);
|
|
124
|
+
await fs.writeFile(configPath, configContent, 'utf8');
|
|
125
|
+
console.log(` ✓ _config/${moduleName}.yaml`);
|
|
115
126
|
}
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
} else {
|
|
128
|
+
for (const moduleName of modules) {
|
|
129
|
+
const modulePath = path.join(outputDir, moduleName);
|
|
130
|
+
if (
|
|
131
|
+
!(await fs.pathExists(modulePath)) ||
|
|
132
|
+
!(await fs.stat(modulePath)).isDirectory()
|
|
133
|
+
) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const configPath = path.join(modulePath, 'config.yaml');
|
|
137
|
+
if (await fs.pathExists(configPath)) {
|
|
138
|
+
console.log(` ⏭ ${moduleName}/config.yaml (exists)`);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const configContent = await getModuleConfigContent(moduleName, bmadRoot);
|
|
142
|
+
await fs.writeFile(configPath, configContent, 'utf8');
|
|
143
|
+
console.log(` ✓ ${moduleName}/config.yaml`);
|
|
123
144
|
}
|
|
124
|
-
|
|
125
|
-
const configContent = await getModuleConfigContent(moduleName, bmadRoot);
|
|
126
|
-
await fs.writeFile(configPath, configContent, 'utf8');
|
|
127
|
-
console.log(` ✓ ${moduleName}/config.yaml`);
|
|
128
145
|
}
|
|
129
146
|
|
|
130
147
|
console.log();
|
|
@@ -161,6 +178,7 @@ function parseArgs() {
|
|
|
161
178
|
const args = process.argv.slice(2);
|
|
162
179
|
const options = {
|
|
163
180
|
outputDir: null,
|
|
181
|
+
outputStructure: null,
|
|
164
182
|
repoUrl: null,
|
|
165
183
|
branch: null,
|
|
166
184
|
identityCharLimit: null,
|
|
@@ -176,6 +194,8 @@ function parseArgs() {
|
|
|
176
194
|
|
|
177
195
|
if (arg === '--output-dir' && i + 1 < args.length) {
|
|
178
196
|
options.outputDir = args[++i];
|
|
197
|
+
} else if (arg.startsWith('--output-structure=')) {
|
|
198
|
+
options.outputStructure = arg.split('=')[1];
|
|
179
199
|
} else if (arg === '--repo' && i + 1 < args.length) {
|
|
180
200
|
options.repoUrl = args[++i];
|
|
181
201
|
} else if (arg === '--branch' && i + 1 < args.length) {
|
|
@@ -224,6 +244,7 @@ Usage: pnpm convert [options]
|
|
|
224
244
|
Options:
|
|
225
245
|
--output-dir <path> Custom output directory (default: ./skills)
|
|
226
246
|
Use a non-version-controlled folder for custom configs
|
|
247
|
+
--output-structure=flat|nested Override config (default: flat)
|
|
227
248
|
|
|
228
249
|
--repo <url> Override BMAD repository URL
|
|
229
250
|
--branch <name> Override BMAD branch (default: main)
|
|
@@ -285,6 +306,10 @@ try {
|
|
|
285
306
|
config.bmadBranch = cliOptions.branch;
|
|
286
307
|
console.log(`ℹ️ Overriding BMAD Branch: ${config.bmadBranch}`);
|
|
287
308
|
}
|
|
309
|
+
if (cliOptions.outputStructure) {
|
|
310
|
+
config.outputStructure = cliOptions.outputStructure;
|
|
311
|
+
console.log(`ℹ️ Overriding output structure: ${config.outputStructure}`);
|
|
312
|
+
}
|
|
288
313
|
|
|
289
314
|
// Initialize enhancements config if not present
|
|
290
315
|
if (!config.enhancements) {
|
|
@@ -348,14 +373,23 @@ async function main() {
|
|
|
348
373
|
);
|
|
349
374
|
console.log(`✓ Repository ready at: ${bmadRoot}\n`);
|
|
350
375
|
|
|
376
|
+
const outputStructure = config.outputStructure ?? 'flat';
|
|
377
|
+
const pathPatternsEffective =
|
|
378
|
+
outputStructure === 'flat'
|
|
379
|
+
? config.pathPatternsFlat || []
|
|
380
|
+
: config.pathPatterns || [];
|
|
381
|
+
if (outputStructure === 'flat' && !pathPatternsEffective.length) {
|
|
382
|
+
console.warn(
|
|
383
|
+
'⚠️ outputStructure is "flat" but pathPatternsFlat is missing or empty. Path rewriting may be incorrect.'
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
351
387
|
// Pre-compile path pattern regexes for performance
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
console.warn(`⚠️ Invalid path pattern in config: ${e.message}`);
|
|
358
|
-
}
|
|
388
|
+
for (const item of pathPatternsEffective) {
|
|
389
|
+
try {
|
|
390
|
+
item.regex = new RegExp(item.pattern, 'g');
|
|
391
|
+
} catch (e) {
|
|
392
|
+
console.warn(`⚠️ Invalid path pattern in config: ${e.message}`);
|
|
359
393
|
}
|
|
360
394
|
}
|
|
361
395
|
|
|
@@ -367,7 +401,8 @@ async function main() {
|
|
|
367
401
|
const { agents, workflows } = await findAgentsAndWorkflows(
|
|
368
402
|
bmadRoot,
|
|
369
403
|
config.agentPaths,
|
|
370
|
-
config.workflowPaths
|
|
404
|
+
config.workflowPaths,
|
|
405
|
+
{ moduleExtractionPatterns: config.moduleExtractionPatterns }
|
|
371
406
|
);
|
|
372
407
|
|
|
373
408
|
stats.agents.total = agents.length;
|
|
@@ -399,28 +434,31 @@ async function main() {
|
|
|
399
434
|
// Maps source file paths (relative to bmadRoot) to destination skill paths
|
|
400
435
|
const skillMap = new Map();
|
|
401
436
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
437
|
+
const stripPrefixes = config.sourcePathPrefixesToStrip;
|
|
438
|
+
const normalizeRelPath = (p) => {
|
|
439
|
+
if (Array.isArray(stripPrefixes)) {
|
|
440
|
+
for (const prefix of stripPrefixes) {
|
|
441
|
+
if (p.startsWith(prefix)) return p.slice(prefix.length);
|
|
442
|
+
}
|
|
443
|
+
return p;
|
|
409
444
|
}
|
|
445
|
+
if (p.startsWith('src/modules/')) return p.replace('src/modules/', '');
|
|
446
|
+
if (p.startsWith('src/')) return p.replace('src/', '');
|
|
447
|
+
return p;
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
for (const agent of agents) {
|
|
451
|
+
const relPath = normalizeRelPath(path.relative(bmadRoot, agent.path));
|
|
410
452
|
skillMap.set(relPath, { module: agent.module, name: agent.name });
|
|
411
453
|
}
|
|
412
454
|
|
|
413
455
|
for (const workflow of workflows) {
|
|
414
|
-
|
|
415
|
-
// Normalize path to match BMAD content conventions
|
|
416
|
-
if (relPath.startsWith('src/modules/')) {
|
|
417
|
-
relPath = relPath.replace('src/modules/', '');
|
|
418
|
-
} else if (relPath.startsWith('src/')) {
|
|
419
|
-
relPath = relPath.replace('src/', '');
|
|
420
|
-
}
|
|
456
|
+
const relPath = normalizeRelPath(path.relative(bmadRoot, workflow.path));
|
|
421
457
|
skillMap.set(relPath, { module: workflow.module, name: workflow.name });
|
|
422
458
|
}
|
|
423
459
|
|
|
460
|
+
const skillMapOptions = { ...(config.skillMap || {}), outputStructure };
|
|
461
|
+
|
|
424
462
|
// Step 4: Convert agents
|
|
425
463
|
if (agents.length > 0) {
|
|
426
464
|
console.log('🤖 Converting agents...');
|
|
@@ -437,8 +475,9 @@ async function main() {
|
|
|
437
475
|
});
|
|
438
476
|
await writeSkill(outputDir, agent.module, agent.name, skillContent, {
|
|
439
477
|
skillMap,
|
|
440
|
-
pathPatterns:
|
|
441
|
-
skillMapOptions
|
|
478
|
+
pathPatterns: pathPatternsEffective,
|
|
479
|
+
skillMapOptions,
|
|
480
|
+
outputStructure,
|
|
442
481
|
});
|
|
443
482
|
stats.agents.converted++;
|
|
444
483
|
console.log(` ✓ ${agent.module}/${agent.name}`);
|
|
@@ -469,9 +508,6 @@ async function main() {
|
|
|
469
508
|
{
|
|
470
509
|
...workflowOptions,
|
|
471
510
|
isMarkdown: workflow.isMarkdown || false,
|
|
472
|
-
bmadRoot,
|
|
473
|
-
bmadRepo: config.bmadRepo,
|
|
474
|
-
bmadBranch: config.bmadBranch,
|
|
475
511
|
}
|
|
476
512
|
);
|
|
477
513
|
await writeSkill(
|
|
@@ -482,8 +518,9 @@ async function main() {
|
|
|
482
518
|
{
|
|
483
519
|
workflowDir: workflow.workflowDir,
|
|
484
520
|
skillMap,
|
|
485
|
-
pathPatterns:
|
|
486
|
-
skillMapOptions
|
|
521
|
+
pathPatterns: pathPatternsEffective,
|
|
522
|
+
skillMapOptions,
|
|
523
|
+
outputStructure,
|
|
487
524
|
}
|
|
488
525
|
);
|
|
489
526
|
stats.workflows.converted++;
|
|
@@ -508,14 +545,20 @@ async function main() {
|
|
|
508
545
|
bmadRoot,
|
|
509
546
|
outputDir,
|
|
510
547
|
config.auxiliaryResources || [],
|
|
511
|
-
|
|
548
|
+
pathPatternsEffective,
|
|
549
|
+
outputStructure
|
|
512
550
|
);
|
|
513
551
|
|
|
514
552
|
// Step 7: Generate module configs (checks BMAD repo for templates)
|
|
515
|
-
await generateModuleConfigs(
|
|
553
|
+
await generateModuleConfigs(
|
|
554
|
+
outputDir,
|
|
555
|
+
bmadRoot,
|
|
556
|
+
config.modules || ['bmm', 'core'],
|
|
557
|
+
outputStructure
|
|
558
|
+
);
|
|
516
559
|
|
|
517
560
|
// Step 8: Generate summary
|
|
518
|
-
await printSummary();
|
|
561
|
+
await printSummary(outputStructure);
|
|
519
562
|
} catch (error) {
|
|
520
563
|
console.error(`\n❌ Fatal error: ${error.message}`);
|
|
521
564
|
console.error(error.stack);
|
|
@@ -525,8 +568,9 @@ async function main() {
|
|
|
525
568
|
|
|
526
569
|
/**
|
|
527
570
|
* Prints conversion summary
|
|
571
|
+
* @param {string} [outputStructure] - 'flat' | 'nested'. For flat, per-module breakdown is omitted.
|
|
528
572
|
*/
|
|
529
|
-
async function printSummary() {
|
|
573
|
+
async function printSummary(outputStructure = 'flat') {
|
|
530
574
|
console.log('📊 Conversion Summary\n');
|
|
531
575
|
console.log('Agents:');
|
|
532
576
|
console.log(` Total: ${stats.agents.total}`);
|
|
@@ -572,11 +616,9 @@ async function printSummary() {
|
|
|
572
616
|
);
|
|
573
617
|
}
|
|
574
618
|
|
|
575
|
-
// Print per-module breakdown
|
|
576
|
-
if (totalConverted > 0) {
|
|
619
|
+
// Print per-module breakdown (nested only; flat uses top-level dirs)
|
|
620
|
+
if (totalConverted > 0 && outputStructure === 'nested') {
|
|
577
621
|
console.log('\n📦 Per-module breakdown:');
|
|
578
|
-
|
|
579
|
-
// Count by module from output structure
|
|
580
622
|
const outputDir = path.resolve(process.cwd(), config.outputDir);
|
|
581
623
|
if (await fs.pathExists(outputDir)) {
|
|
582
624
|
const modules = await fs.readdir(outputDir);
|
|
@@ -584,7 +626,6 @@ async function printSummary() {
|
|
|
584
626
|
const modulePath = path.join(outputDir, module);
|
|
585
627
|
if ((await fs.stat(modulePath)).isDirectory()) {
|
|
586
628
|
const items = await fs.readdir(modulePath);
|
|
587
|
-
// Only count directories (skills), not files like config.yaml
|
|
588
629
|
let skillCount = 0;
|
|
589
630
|
for (const item of items) {
|
|
590
631
|
const itemPath = path.join(modulePath, item);
|
package/docs/getting-started.md
CHANGED
|
@@ -29,10 +29,11 @@ This single command:
|
|
|
29
29
|
4. ✅ Generates `config.yaml` for each module (core, bmm)
|
|
30
30
|
5. ✅ Cleans up temporary files
|
|
31
31
|
|
|
32
|
-
**After installation**, customize the generated config files
|
|
33
|
-
- `{skills-dir}/core
|
|
34
|
-
- `{skills-dir}/bmm
|
|
35
|
-
|
|
32
|
+
**After installation**, customize the generated config files. With the default **flat** layout:
|
|
33
|
+
- `{skills-dir}/_config/core.yaml` - User preferences
|
|
34
|
+
- `{skills-dir}/_config/bmm.yaml` - Project settings
|
|
35
|
+
|
|
36
|
+
With `outputStructure: "nested"`: `{skills-dir}/bmm/config.yaml`, `{skills-dir}/core/config.yaml`.
|
|
36
37
|
|
|
37
38
|
### Option B: AI-Guided Workflow
|
|
38
39
|
|
|
@@ -121,6 +122,7 @@ pnpm convert --no-examples --no-best-practices --no-related-skills
|
|
|
121
122
|
- `--output-dir <path>` - Custom output directory (default: `./skills`)
|
|
122
123
|
- Use a non-version-controlled folder for custom configs
|
|
123
124
|
- Default `./skills` directory is version controlled with default settings
|
|
125
|
+
- `--output-structure=flat|nested` - Override `config.json` (default: flat)
|
|
124
126
|
|
|
125
127
|
- `--identity-limit <num>` - Character limit for identity in description
|
|
126
128
|
- Default: no limit (recommended)
|
|
@@ -2,7 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## Output Structure
|
|
4
4
|
|
|
5
|
-
The converter
|
|
5
|
+
The converter supports two output layouts controlled by `config.json` → `outputStructure` (default: **`"flat"`**). Flat layout is required for AI tools (Claude Code, Cursor) that discover skills only in immediate subfolders of `skills/`.
|
|
6
|
+
|
|
7
|
+
### Flat (default)
|
|
8
|
+
|
|
9
|
+
skills/
|
|
10
|
+
├── bmm-analyst/
|
|
11
|
+
│ └── SKILL.md
|
|
12
|
+
├── bmm-architect/
|
|
13
|
+
│ └── SKILL.md
|
|
14
|
+
├── core-bmad-master/
|
|
15
|
+
│ └── SKILL.md
|
|
16
|
+
├── core-brainstorming/
|
|
17
|
+
│ └── SKILL.md
|
|
18
|
+
├── _config/
|
|
19
|
+
│ ├── bmm.yaml # Module config (was bmm/config.yaml)
|
|
20
|
+
│ └── core.yaml # Module config (was core/config.yaml)
|
|
21
|
+
├── _resources/
|
|
22
|
+
│ ├── excalidraw/ # excalidraw-helpers.md, validate-json-instructions.md, etc.
|
|
23
|
+
│ └── bmm-excalidraw-shared/ # excalidraw-templates.yaml, excalidraw-library.json
|
|
24
|
+
├── bootstrap-bmad-skills/
|
|
25
|
+
└── enhance-bmad-skills/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Nested (`outputStructure: "nested"`)
|
|
6
29
|
|
|
7
30
|
skills/
|
|
8
31
|
├── bmm/
|
|
@@ -11,14 +34,14 @@ skills/
|
|
|
11
34
|
│ ├── pm/
|
|
12
35
|
│ │ └── SKILL.md
|
|
13
36
|
│ ├── excalidraw-diagrams/
|
|
14
|
-
│ │ └── _shared/
|
|
15
|
-
│ ├── config.yaml
|
|
16
|
-
│ └── (skills...)
|
|
37
|
+
│ │ └── _shared/
|
|
38
|
+
│ ├── config.yaml
|
|
39
|
+
│ └── (skills...)
|
|
17
40
|
├── core/
|
|
18
|
-
│ ├── config.yaml
|
|
41
|
+
│ ├── config.yaml
|
|
19
42
|
│ ├── resources/
|
|
20
|
-
│ │ └── excalidraw/
|
|
21
|
-
│ └── (skills...)
|
|
43
|
+
│ │ └── excalidraw/
|
|
44
|
+
│ └── (skills...)
|
|
22
45
|
```
|
|
23
46
|
|
|
24
47
|
Each skill folder contains:
|
|
@@ -99,22 +122,25 @@ Migrated resources are processed recursively. Any text-based files within these
|
|
|
99
122
|
|
|
100
123
|
To make skills portable, path rewriting uses **config-driven regex patterns** plus a dynamic map of discovered skills:
|
|
101
124
|
|
|
102
|
-
- **Source of truth**:
|
|
103
|
-
- **skillMap options**: The dynamic skillMap
|
|
125
|
+
- **Source of truth**: Path-rewrite rules are in `config.json` under `pathPatterns` (nested) and `pathPatternsFlat` (flat). Each entry has `pattern` (regex), `replacement`, and optional `description`. **`outputStructure`** chooses which set is used: `"flat"` (default) uses `pathPatternsFlat`; `"nested"` uses `pathPatterns`. Only `replacement` differs (e.g. flat: `{skill-root}/bmm-analyst/`, `{skill-root}/_config/{module}.yaml`, `{skill-root}/_resources/excalidraw/`).
|
|
126
|
+
- **skillMap options**: The dynamic skillMap (exact rewrites for discovered agents/workflows) uses `config.skillMap`: `sourcePrefix`, `dirLookahead`, `replacementPrefix`. `outputStructure` in `skillMapOptions` controls `/{module}/{name}/` (nested) vs `/{module}-{name}/` (flat).
|
|
104
127
|
- **Pattern optimization**: Regexes are pre-compiled at startup for performance.
|
|
105
|
-
- **Exact skill resolution**:
|
|
106
|
-
- **Skill root variable**: Output uses `{skill-root}
|
|
107
|
-
- **Standardized paths
|
|
108
|
-
- **Migrated resources**:
|
|
128
|
+
- **Exact skill resolution**: `skillMap` is applied with `pathPatterns`/`pathPatternsFlat` to resolve source paths to destination skills.
|
|
129
|
+
- **Skill root variable**: Output uses `{skill-root}`.
|
|
130
|
+
- **Standardized paths**: Flat: `{skill-root}/{module}-{skill}/SKILL.md`, `{skill-root}/_config/{module}.yaml`, `{skill-root}/_resources/...`. Nested: `{skill-root}/{module}/{skill}/SKILL.md`, `{skill-root}/{module}/config.yaml`, etc.
|
|
131
|
+
- **Migrated resources**: `auxiliaryResources` supports optional `destFlat`; when `outputStructure === "flat"` and `destFlat` is set, it is used instead of `dest`. Migrated content is rewritten with the active path patterns.
|
|
109
132
|
|
|
110
133
|
This ensures skills work correctly regardless of where the root `skills` directory is installed and that cross-skill references are robust.
|
|
111
134
|
|
|
112
|
-
The converter creates
|
|
135
|
+
The converter creates module config files. The generation logic prioritizes templates from the BMAD repository:
|
|
113
136
|
|
|
114
137
|
1. **BMAD Template**: Checks for `config-template.yaml` within the module directory in the BMAD repo.
|
|
115
138
|
2. **Fallback Defaults**: If no template exists in the repo, hardcoded defaults from `convert.js` are used.
|
|
116
139
|
|
|
117
|
-
|
|
140
|
+
- **Flat**: configs are `{skill-root}/_config/bmm.yaml` and `{skill-root}/_config/core.yaml`.
|
|
141
|
+
- **Nested**: configs are `{skill-root}/bmm/config.yaml` and `{skill-root}/core/config.yaml`.
|
|
142
|
+
|
|
143
|
+
Users should customize these files for their project.
|
|
118
144
|
|
|
119
145
|
## Placeholder Variables
|
|
120
146
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clfhhc/bmad-methods-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Convert BMAD-METHOD agents and workflows to Claude Skills format",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"LICENSE"
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
|
-
"test": "node tests/path-rewriter.test.js",
|
|
26
|
+
"test": "node --test tests/path-rewriter.test.js tests/workflow-converter.test.js",
|
|
27
27
|
"convert": "node convert.js",
|
|
28
28
|
"clean": "node -e \"import('fs-extra').then(fs => { try { fs.removeSync('./.temp'); console.log('Cleaned temp directory'); } catch(e) { console.error('Error:', e.message); } })\"",
|
|
29
29
|
"clean:all": "node -e \"import('fs-extra').then(fs => { try { fs.removeSync('./skills'); fs.removeSync('./.temp'); console.log('Cleaned skills and temp directories'); } catch(e) { console.error('Error:', e.message); } })\"",
|
|
@@ -11,9 +11,15 @@ description: Bootstrap and install BMAD-METHOD skills for Claude Code, Cursor, A
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## When You Run BS (Guided Flow)
|
|
15
15
|
|
|
16
|
-
Run the one-liner to
|
|
16
|
+
For **`BS`** or **`bootstrap-skills`**: (1) Ask Step 1 (Tool), Step 2 (Scope), and **Configure Installation**—offer defaults. (2) Summarize and **confirm** before running commands. (3) Run the one-liner or Manual (Steps 3–5) as appropriate; write the user’s config to `{skill-root}/_config/core.yaml` and `{skill-root}/_config/bmm.yaml` after install.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Quick Start (Unattended)
|
|
21
|
+
|
|
22
|
+
Use only when the user explicitly wants to **skip prompts** (e.g. “just run it” or “use defaults”):
|
|
17
23
|
|
|
18
24
|
```bash
|
|
19
25
|
npx @clfhhc/bmad-methods-skills init --tool=[TOOL] --bootstrap
|
|
@@ -21,13 +27,13 @@ npx @clfhhc/bmad-methods-skills init --tool=[TOOL] --bootstrap
|
|
|
21
27
|
|
|
22
28
|
Replace `[TOOL]` with `antigravity`, `cursor`, or `claude`.
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
Afterward, the user can edit `{skill-root}/_config/core.yaml` and `{skill-root}/_config/bmm.yaml`, or you can run the **Configure Installation** questions and apply the answers to those files.
|
|
25
31
|
|
|
26
32
|
---
|
|
27
33
|
|
|
28
34
|
## Manual Workflow
|
|
29
35
|
|
|
30
|
-
Use
|
|
36
|
+
Use when the user wants a guided experience, needs **global** install, or when you must run Fetch & Convert and Install as separate steps.
|
|
31
37
|
|
|
32
38
|
### Step 1: Tool Selection
|
|
33
39
|
|
|
@@ -47,6 +53,8 @@ Ask whether to install skills **globally** or **project-specific**:
|
|
|
47
53
|
| **Global** | Skills available across all projects | Cursor: `~/.cursor/skills/`; Antigravity: `~/.gemini/antigravity/skills/`; Claude Code: `~/.claude/skills/` |
|
|
48
54
|
| **Project-Specific** | Skills limited to current repo | Cursor: `.cursor/skills/`; Antigravity: `.agent/skills/`; Claude Code: `.claude/skills/` |
|
|
49
55
|
|
|
56
|
+
**Note:** On **install**, use `--scope=project` (default) or `--scope=global` / `--global`. Project: `.{tool}/skills/` under cwd (run from project root). Global: `~/.cursor/skills` etc.; `--tool` required.
|
|
57
|
+
|
|
50
58
|
### Step 3: Fetch & Convert
|
|
51
59
|
|
|
52
60
|
```bash
|
|
@@ -55,10 +63,20 @@ npx @clfhhc/bmad-methods-skills --output-dir .temp/converted-skills
|
|
|
55
63
|
|
|
56
64
|
### Step 4: Install
|
|
57
65
|
|
|
66
|
+
**Project-specific** (default; run from project root):
|
|
67
|
+
|
|
58
68
|
```bash
|
|
59
69
|
npx @clfhhc/bmad-methods-skills install --from=.temp/converted-skills --tool=[TOOL] --force
|
|
60
70
|
```
|
|
61
71
|
|
|
72
|
+
**Global** (`--tool` required; works from any directory):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx @clfhhc/bmad-methods-skills install --from=.temp/converted-skills --tool=[TOOL] --force --scope=global
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
(`--global` = `--scope=global`.)
|
|
79
|
+
|
|
62
80
|
### Step 5: Clean up
|
|
63
81
|
|
|
64
82
|
```bash
|
|
@@ -71,7 +89,7 @@ rm -rf .temp
|
|
|
71
89
|
|
|
72
90
|
Prompt the user for each configuration setting. Offer the defaults shown:
|
|
73
91
|
|
|
74
|
-
### Core Configuration (`{skill-root}/core
|
|
92
|
+
### Core Configuration (`{skill-root}/_config/core.yaml`)
|
|
75
93
|
|
|
76
94
|
| Setting | Question | Default |
|
|
77
95
|
|---------|----------|---------|
|
|
@@ -80,7 +98,7 @@ Prompt the user for each configuration setting. Offer the defaults shown:
|
|
|
80
98
|
| `document_output_language` | Preferred document output language? | English |
|
|
81
99
|
| `output_folder` | Where should output files be saved? | `_bmad-output` |
|
|
82
100
|
|
|
83
|
-
### BMM Configuration (`{skill-root}/bmm
|
|
101
|
+
### BMM Configuration (`{skill-root}/_config/bmm.yaml`)
|
|
84
102
|
|
|
85
103
|
| Setting | Question | Default |
|
|
86
104
|
|---------|----------|---------|
|
|
@@ -95,12 +113,12 @@ Prompt the user for each configuration setting. Offer the defaults shown:
|
|
|
95
113
|
## Verify
|
|
96
114
|
|
|
97
115
|
1. Skills installed at the correct destination
|
|
98
|
-
2. Config files exist
|
|
99
|
-
3. Paths use `{skill-root}` variable
|
|
116
|
+
2. Config files exist: `_config/core.yaml`, `_config/bmm.yaml`
|
|
117
|
+
3. Paths under `_config` use the `{skill-root}` variable
|
|
100
118
|
|
|
101
119
|
## Guidelines
|
|
102
120
|
|
|
103
|
-
- **Guided Flow**:
|
|
121
|
+
- **Guided Flow**: For `BS`, follow **When You Run BS (Guided Flow)** above.
|
|
104
122
|
- **Confirmation**: Always summarize the plan and ask for confirmation before executing automated installation commands.
|
|
105
|
-
- **Defaults**: Offer defaults
|
|
123
|
+
- **Defaults**: Offer defaults—don’t force the user to answer every question if they accept the suggested values.
|
|
106
124
|
- **Overwrite**: Ask before overwriting existing skills unless `--force` is used.
|
|
@@ -19,11 +19,8 @@ export async function convertWorkflowToSkill(
|
|
|
19
19
|
_instructionsType = null,
|
|
20
20
|
options = {}
|
|
21
21
|
) {
|
|
22
|
-
const { isMarkdown
|
|
22
|
+
const { isMarkdown } = {
|
|
23
23
|
isMarkdown: false,
|
|
24
|
-
bmadRoot: null,
|
|
25
|
-
bmadRepo: null,
|
|
26
|
-
bmadBranch: 'main',
|
|
27
24
|
...options,
|
|
28
25
|
};
|
|
29
26
|
|
|
@@ -123,16 +120,9 @@ export async function convertWorkflowToSkill(
|
|
|
123
120
|
|
|
124
121
|
// Read instructions if available
|
|
125
122
|
if (instructionsPath && (await fs.pathExists(instructionsPath))) {
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const repoBase = bmadRepo.replace(/\.git$/, '');
|
|
130
|
-
const link = `${repoBase}/blob/${bmadBranch}/${relativePath}`;
|
|
131
|
-
instructionsContent = `See instructions at: [${path.basename(instructionsPath)}](${link})`;
|
|
132
|
-
} else {
|
|
133
|
-
// Fallback if no repo info
|
|
134
|
-
instructionsContent = `See instructions in: ${path.basename(instructionsPath)}`;
|
|
135
|
-
}
|
|
123
|
+
// Link to local auxiliary file (instructions are copied into the skill dir by writeSkill)
|
|
124
|
+
const basename = path.basename(instructionsPath);
|
|
125
|
+
instructionsContent = `See instructions at: [${basename}](${basename})`;
|
|
136
126
|
} else {
|
|
137
127
|
instructionsContent = 'No instructions available.';
|
|
138
128
|
}
|
package/src/utils/file-finder.js
CHANGED
|
@@ -8,12 +8,14 @@ import yaml from 'js-yaml';
|
|
|
8
8
|
* @param {string} bmadRoot - Root path of BMAD-METHOD repository
|
|
9
9
|
* @param {string[]} agentPaths - Glob patterns for agent paths
|
|
10
10
|
* @param {string[]} workflowPaths - Glob patterns for workflow paths
|
|
11
|
+
* @param {{ moduleExtractionPatterns?: Array<{ pattern: string, group: number }> }} [options] - Optional. moduleExtractionPatterns from config for extractModule.
|
|
11
12
|
* @returns {Promise<{agents: Array, workflows: Array}>}
|
|
12
13
|
*/
|
|
13
14
|
export async function findAgentsAndWorkflows(
|
|
14
15
|
bmadRoot,
|
|
15
16
|
agentPaths,
|
|
16
|
-
workflowPaths
|
|
17
|
+
workflowPaths,
|
|
18
|
+
options = {}
|
|
17
19
|
) {
|
|
18
20
|
if (!bmadRoot || !(await fs.pathExists(bmadRoot))) {
|
|
19
21
|
throw new Error(`BMAD root directory does not exist: ${bmadRoot}`);
|
|
@@ -23,6 +25,7 @@ export async function findAgentsAndWorkflows(
|
|
|
23
25
|
throw new Error('agentPaths and workflowPaths must be arrays');
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
const { moduleExtractionPatterns } = options;
|
|
26
29
|
const agents = [];
|
|
27
30
|
const workflows = [];
|
|
28
31
|
|
|
@@ -43,7 +46,7 @@ export async function findAgentsAndWorkflows(
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
const relativePath = path.relative(bmadRoot, filePath);
|
|
46
|
-
const module = extractModule(relativePath);
|
|
49
|
+
const module = extractModule(relativePath, moduleExtractionPatterns);
|
|
47
50
|
const name = path.basename(filePath, '.agent.yaml');
|
|
48
51
|
|
|
49
52
|
if (!name || name.trim() === '') {
|
|
@@ -120,7 +123,10 @@ export async function findAgentsAndWorkflows(
|
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
const relativePath = path.relative(bmadRoot, absolutePath);
|
|
123
|
-
const module = extractModule(
|
|
126
|
+
const module = extractModule(
|
|
127
|
+
relativePath,
|
|
128
|
+
moduleExtractionPatterns
|
|
129
|
+
);
|
|
124
130
|
|
|
125
131
|
const isMarkdown = absolutePath.endsWith('.md');
|
|
126
132
|
const isXml = absolutePath.endsWith('.xml');
|
|
@@ -241,12 +247,22 @@ export async function findAgentsAndWorkflows(
|
|
|
241
247
|
return { agents, workflows };
|
|
242
248
|
}
|
|
243
249
|
|
|
244
|
-
function extractModule(relativePath) {
|
|
245
|
-
|
|
250
|
+
function extractModule(relativePath, moduleExtractionPatterns) {
|
|
251
|
+
if (
|
|
252
|
+
Array.isArray(moduleExtractionPatterns) &&
|
|
253
|
+
moduleExtractionPatterns.length > 0
|
|
254
|
+
) {
|
|
255
|
+
for (const { pattern, group } of moduleExtractionPatterns) {
|
|
256
|
+
const m = relativePath.match(new RegExp(pattern));
|
|
257
|
+
if (m && m[group] != null) return m[group];
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Fallback when moduleExtractionPatterns not provided (backward compatibility)
|
|
246
263
|
const modulesMatch = relativePath.match(/^src\/modules\/([^/]+)\//);
|
|
247
264
|
if (modulesMatch) return modulesMatch[1];
|
|
248
265
|
|
|
249
|
-
// Support new structure (and core): src/bmm/agents/... or src/core/agents/...
|
|
250
266
|
const srcMatch = relativePath.match(/^src\/([^/]+)\//);
|
|
251
267
|
if (srcMatch) return srcMatch[1];
|
|
252
268
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @param {string} content - File content to process
|
|
9
9
|
* @param {Map|null} skillMap - Map of source paths to destination skill info
|
|
10
10
|
* @param {Array|null} pathPatterns - Array of {pattern, replacement, description?} from config.json pathPatterns (source of truth for all regex rewrites)
|
|
11
|
-
* @param {Object} [skillMapOptions] - From config.json skillMap: { sourcePrefix, dirLookahead, replacementPrefix }
|
|
11
|
+
* @param {Object} [skillMapOptions] - From config.json skillMap: { sourcePrefix, dirLookahead, replacementPrefix, outputStructure? }. outputStructure defaults to 'flat'.
|
|
12
12
|
* @returns {string} Content with rewritten paths
|
|
13
13
|
*/
|
|
14
14
|
export function rewriteBmadPaths(
|
|
@@ -20,9 +20,10 @@ export function rewriteBmadPaths(
|
|
|
20
20
|
let result = content;
|
|
21
21
|
|
|
22
22
|
const opts = skillMapOptions || {};
|
|
23
|
-
const sourcePrefix = opts.sourcePrefix
|
|
24
|
-
const dirLookahead = opts.dirLookahead
|
|
25
|
-
const replacementPrefix = opts.replacementPrefix
|
|
23
|
+
const sourcePrefix = opts.sourcePrefix;
|
|
24
|
+
const dirLookahead = opts.dirLookahead;
|
|
25
|
+
const replacementPrefix = opts.replacementPrefix;
|
|
26
|
+
const outputStructure = opts.outputStructure ?? 'flat';
|
|
26
27
|
|
|
27
28
|
// === CONFIG-DRIVEN PATTERNS (Applied First) ===
|
|
28
29
|
if (pathPatterns && pathPatterns.length > 0) {
|
|
@@ -38,7 +39,13 @@ export function rewriteBmadPaths(
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
// === MAP-BASED EXACT REPLACEMENTS (Priority) ===
|
|
41
|
-
|
|
42
|
+
// Requires sourcePrefix, dirLookahead, replacementPrefix from config.skillMap
|
|
43
|
+
if (
|
|
44
|
+
skillMap &&
|
|
45
|
+
sourcePrefix != null &&
|
|
46
|
+
dirLookahead != null &&
|
|
47
|
+
replacementPrefix != null
|
|
48
|
+
) {
|
|
42
49
|
const dirMap = new Map();
|
|
43
50
|
|
|
44
51
|
// 1. Rewrite Workflow Files
|
|
@@ -53,10 +60,11 @@ export function rewriteBmadPaths(
|
|
|
53
60
|
// srcPath is now normalized by convert.js
|
|
54
61
|
const escapedPath = srcPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
55
62
|
const regex = new RegExp(`${sourcePrefix}${escapedPath}`, 'g');
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
const fileReplacement =
|
|
64
|
+
outputStructure === 'flat'
|
|
65
|
+
? `${replacementPrefix}/${module}-${name}/SKILL.md`
|
|
66
|
+
: `${replacementPrefix}/${module}/${name}/SKILL.md`;
|
|
67
|
+
result = result.replace(regex, fileReplacement);
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
// 2. Rewrite Workflow Directories
|
|
@@ -72,7 +80,11 @@ export function rewriteBmadPaths(
|
|
|
72
80
|
`${sourcePrefix}${escapedDir}${dirLookahead}`,
|
|
73
81
|
'g'
|
|
74
82
|
);
|
|
75
|
-
|
|
83
|
+
const dirReplacement =
|
|
84
|
+
outputStructure === 'flat'
|
|
85
|
+
? `${replacementPrefix}/${module}-${name}`
|
|
86
|
+
: `${replacementPrefix}/${module}/${name}`;
|
|
87
|
+
result = result.replace(regex, dirReplacement);
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
90
|
|
|
@@ -7,22 +7,31 @@ import { rewriteBmadPaths, shouldRewritePaths } from './path-rewriter.js';
|
|
|
7
7
|
* @param {string} bmadRoot - Root of the fetched BMAD repo
|
|
8
8
|
* @param {string} outputDir - Root of the skills output directory
|
|
9
9
|
* @param {Array} auxiliaryResources - Array of migration definitions from config
|
|
10
|
+
* @param {Array|null} pathPatterns - Path patterns for rewriting (pathPatterns or pathPatternsFlat)
|
|
11
|
+
* @param {string} [outputStructure] - 'flat' | 'nested'. When 'flat' and res.destFlat exists, use destFlat.
|
|
10
12
|
*/
|
|
11
13
|
export async function migrateResources(
|
|
12
14
|
bmadRoot,
|
|
13
15
|
outputDir,
|
|
14
16
|
auxiliaryResources = [],
|
|
15
|
-
pathPatterns = null
|
|
17
|
+
pathPatterns = null,
|
|
18
|
+
outputStructure = 'flat'
|
|
16
19
|
) {
|
|
17
20
|
console.log('📦 Migrating auxiliary resources...');
|
|
18
21
|
|
|
19
|
-
// Use config-provided resources
|
|
20
|
-
const migrations = auxiliaryResources.map((res) =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
// Use config-provided resources; resolve dest vs destFlat when outputStructure is 'flat'
|
|
23
|
+
const migrations = auxiliaryResources.map((res) => {
|
|
24
|
+
const dest =
|
|
25
|
+
outputStructure === 'flat' && res.destFlat != null
|
|
26
|
+
? res.destFlat
|
|
27
|
+
: res.dest;
|
|
28
|
+
return {
|
|
29
|
+
src: res.src,
|
|
30
|
+
dest,
|
|
31
|
+
name: res.name,
|
|
32
|
+
isDirectory: res.isDirectory || false,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
26
35
|
|
|
27
36
|
if (migrations.length === 0) {
|
|
28
37
|
console.log(' ⚠️ No auxiliary resources configured in config.json');
|
|
@@ -10,6 +10,7 @@ import { rewriteBmadPaths, shouldRewritePaths } from './path-rewriter.js';
|
|
|
10
10
|
* @param {string} skillContent - SKILL.md content
|
|
11
11
|
* @param {Object} options - Additional options
|
|
12
12
|
* @param {string} options.workflowDir - Workflow directory (for copying templates/checklists)
|
|
13
|
+
* @param {string} [options.outputStructure] - 'flat' | 'nested'. Default 'flat'.
|
|
13
14
|
* @returns {Promise<string>} Path to written SKILL.md file
|
|
14
15
|
*/
|
|
15
16
|
export async function writeSkill(
|
|
@@ -40,7 +41,11 @@ export async function writeSkill(
|
|
|
40
41
|
);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
const
|
|
44
|
+
const outputStructure = options.outputStructure ?? 'flat';
|
|
45
|
+
const skillDir =
|
|
46
|
+
outputStructure === 'flat'
|
|
47
|
+
? path.join(outputDir, `${module}-${sanitizedName}`)
|
|
48
|
+
: path.join(outputDir, module, sanitizedName);
|
|
44
49
|
|
|
45
50
|
try {
|
|
46
51
|
// Create directory structure
|