@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 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 (core, bmm)
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
 
@@ -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(`📦 Installation Target: ${toolInfo.name} (${toolInfo.path})`);
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(process.cwd(), toolInfo.path, skillName),
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
- !['init', '--bootstrap', '--force', '--tool'].some((x) => a.includes(x))
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
- await install([
158
+ const installArgs = [
149
159
  'install',
150
160
  `--from=${tempDir}`,
151
- `--tool=${toolInfo.name.toLowerCase()}`, // Ensure generic name match
152
- '--force', // Always force in bootstrap mode? Or respect args? Let's use force for bootstrap convenience
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(process.cwd(), toolInfo.path, skill),
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.resolve(process.cwd(), toolInfo.path, skillName);
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
- * Helpher to copy skill with checks
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
- * Helper to detect AI tool
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
- const tools = [
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
- selectedTool = tools.find((t) =>
318
+ selected = TOOLS.find((t) =>
280
319
  t.name.toLowerCase().includes(toolArg.toLowerCase())
281
320
  );
282
321
  }
283
-
284
- if (!selectedTool) {
285
- if (force) {
286
- // Default to antigravity if forced and not found
287
- return tools[0];
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
- console.log('❌ No AI tool directory detected (.agent, .cursor, .claude).');
291
- console.log(
292
- ' Use --tool=<name> to force installation or ensure you are in the project root.'
293
- );
294
- console.log(' Available tools: antigravity, cursor, claude');
295
- return null;
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
- return selectedTool;
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
- --force Overwrite existing skills / Force installation
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.yaml for each module in the output directory
100
- * Checks BMAD repo for config templates, falls back to defaults
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(outputDir, bmadRoot = null) {
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
- const modules = await fs.readdir(outputDir);
108
-
109
- for (const moduleName of modules) {
110
- const modulePath = path.join(outputDir, moduleName);
111
-
112
- // Skip if not a directory
113
- if (!(await fs.stat(modulePath)).isDirectory()) {
114
- continue;
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 configPath = path.join(modulePath, 'config.yaml');
118
-
119
- // Don't overwrite existing config
120
- if (await fs.pathExists(configPath)) {
121
- console.log(` ⏭ ${moduleName}/config.yaml (exists)`);
122
- continue;
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
- if (config.pathPatterns && config.pathPatterns.length > 0) {
353
- for (const item of config.pathPatterns) {
354
- try {
355
- item.regex = new RegExp(item.pattern, 'g');
356
- } catch (e) {
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
- for (const agent of agents) {
403
- let relPath = path.relative(bmadRoot, agent.path);
404
- // Normalize path to match BMAD content conventions
405
- if (relPath.startsWith('src/modules/')) {
406
- relPath = relPath.replace('src/modules/', '');
407
- } else if (relPath.startsWith('src/')) {
408
- relPath = relPath.replace('src/', '');
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
- let relPath = path.relative(bmadRoot, workflow.path);
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: config.pathPatterns,
441
- skillMapOptions: config.skillMap || {},
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: config.pathPatterns,
486
- skillMapOptions: config.skillMap || {},
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
- config.pathPatterns
548
+ pathPatternsEffective,
549
+ outputStructure
512
550
  );
513
551
 
514
552
  // Step 7: Generate module configs (checks BMAD repo for templates)
515
- await generateModuleConfigs(outputDir, bmadRoot);
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);
@@ -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 at:
33
- - `{skills-dir}/core/config.yaml` - User preferences
34
- - `{skills-dir}/bmm/config.yaml` - Project settings
35
- - `{skills-dir}/{module}/config.yaml` - Module specific configuration
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 generates skills organized by module in the `skills/` directory at the project root:
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/ # excalidraw-templates.yaml, excalidraw-library.json
15
- │ ├── config.yaml # Project configuration
16
- │ └── (skills...) # BMM methodology skills (includes create-excalidraw-*)
37
+ │ │ └── _shared/
38
+ │ ├── config.yaml
39
+ │ └── (skills...)
17
40
  ├── core/
18
- │ ├── config.yaml # Core user settings
41
+ │ ├── config.yaml
19
42
  │ ├── resources/
20
- │ │ └── excalidraw/ # excalidraw-helpers.md, validate-json-instructions.md, etc.
21
- │ └── (skills...) # Core system 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**: All path-rewrite rules are defined in `config.json` under `pathPatterns`. Each entry has `pattern` (regex string), `replacement`, and optional `description`. Order in the array matters—specific rules (e.g. init, document-project) must come before generic ones.
103
- - **skillMap options**: The dynamic skillMap block (exact file and directory rewrites from discovered agents/workflows) uses `config.skillMap`: `sourcePrefix` (regex fragment for `{project-root}/_bmad/`), `dirLookahead` (lookahead so directory matches don’t over-match, e.g. space/quote/backtick or end), and `replacementPrefix` (e.g. `{skill-root}`). Omit `skillMap` to use built-in defaults.
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**: A `skillMap` of discovered agents/workflows is applied after `pathPatterns` to resolve exact source paths to destination skills.
106
- - **Skill root variable**: Output uses `{skill-root}` instead of fragile relative paths.
107
- - **Standardized paths** (from `pathPatterns`): Cross-skill `{skill-root}/{module}/{skill}/SKILL.md`; resources under `data/`; Excalidraw under `core/resources/excalidraw/` and `bmm/excalidraw-diagrams/_shared/`.
108
- - **Migrated resources**: Paths inside files migrated via `auxiliaryResources` are rewritten using the same `pathPatterns`.
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 `config.yaml` files for each module. The generation logic prioritizes templates from the BMAD repository:
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
- Skills reference these configs using `{skill-root}/{module}/config.yaml`. Users should customize these files for their project settings.
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.2.3",
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
- ## Quick Start
14
+ ## When You Run BS (Guided Flow)
15
15
 
16
- Run the one-liner to install everything automatically:
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
- Then proceed to **Configure Installation** below.
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 this when you need more control or want a guided experience.
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/config.yaml`)
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/config.yaml`)
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 (core, bmm)
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**: If the user starts with `BS`, prioritize asking the questions in Steps 1, 2, and the Configuration section before running commands.
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 - don't force user to answer every question if they are happy with the suggested values.
123
+ - **Defaults**: Offer defaultsdont 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, bmadRoot, bmadRepo, bmadBranch } = {
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
- // Create link to instructions instead of embedding
127
- if (bmadRoot && bmadRepo) {
128
- const relativePath = path.relative(bmadRoot, instructionsPath);
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
  }
@@ -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(relativePath);
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
- // Support legacy structure: src/modules/bmm/agents/...
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 } for the dynamic skillMap block
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 ?? '\\{project-root\\}/_bmad/';
24
- const dirLookahead = opts.dirLookahead ?? "(?=['\\s`]|$)";
25
- const replacementPrefix = opts.replacementPrefix ?? '{skill-root}';
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
- if (skillMap) {
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
- result = result.replace(
57
- regex,
58
- `${replacementPrefix}/${module}/${name}/SKILL.md`
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
- result = result.replace(regex, `${replacementPrefix}/${module}/${name}`);
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, or empty array if none
20
- const migrations = auxiliaryResources.map((res) => ({
21
- src: res.src,
22
- dest: res.dest,
23
- name: res.name,
24
- isDirectory: res.isDirectory || false,
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 skillDir = path.join(outputDir, module, sanitizedName);
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