@clfhhc/bmad-methods-skills 0.0.2 → 0.0.5

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
@@ -1,5 +1,8 @@
1
1
  # BMAD-Methods-Skills
2
2
 
3
+ [![Release](https://github.com/clfhhc/BMAD-Methods-Skills/actions/workflows/release.yml/badge.svg)](https://github.com/clfhhc/BMAD-Methods-Skills/actions/workflows/release.yml)
4
+ [![npm](https://img.shields.io/npm/v/@clfhhc/bmad-methods-skills.svg)](https://www.npmjs.com/package/@clfhhc/bmad-methods-skills)
5
+
3
6
  Automatically convert BMAD-METHOD agents and workflows to Claude Skills format.
4
7
 
5
8
  ## Distribution (New Projects)
@@ -7,13 +10,14 @@ Automatically convert BMAD-METHOD agents and workflows to Claude Skills format.
7
10
  To install the BMAD bootstrap skill into a new project, you can use `npx`:
8
11
 
9
12
  ```bash
10
- npx @clfhhc/bmad-methods-skills init
13
+ npx @clfhhc/bmad-methods-skills init --bootstrap
11
14
  ```
12
15
 
13
16
  This will:
14
17
  1. Detect your AI tool (.agent, .cursor, or .claude)
15
- 2. Install the `bootstrap-bmad-skills` and `enhance-bmad-skills` into your project
16
- 3. Enable the `BS` command to fetch and install all other BMAD skills via `npx`
18
+ 2. Automatically fetch, convert, and install the complete BMAD method suite
19
+ 3. Install `bootstrap-bmad-skills` and `enhance-bmad-skills` for future maintenance
20
+ 4. Enable the `BS` command as a manual backup workflow
17
21
 
18
22
  ## Documentation
19
23
 
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from 'fs-extra';
4
3
  import path from 'node:path';
5
4
  import { fileURLToPath } from 'node:url';
6
- import readline from 'node:readline';
5
+ import fs from 'fs-extra';
7
6
 
8
7
  const __filename = fileURLToPath(import.meta.url);
9
8
  const __dirname = path.dirname(__filename);
@@ -17,7 +16,12 @@ async function run() {
17
16
  await init(args);
18
17
  } else if (command === 'install') {
19
18
  await install(args);
20
- } else if (command === '--help' || command === '-h' || args.includes('--help') || args.includes('-h')) {
19
+ } else if (
20
+ command === '--help' ||
21
+ command === '-h' ||
22
+ args.includes('--help') ||
23
+ args.includes('-h')
24
+ ) {
21
25
  printHelp();
22
26
  } else {
23
27
  // Proxy to convert.js logic
@@ -25,13 +29,11 @@ async function run() {
25
29
  }
26
30
  }
27
31
 
28
-
29
-
30
32
  /**
31
33
  * Install specific skills from a source directory to the project's skill directory
32
34
  */
33
35
  async function install(args) {
34
- const sourceArg = args.find(a => a.startsWith('--from='))?.split('=')[1];
36
+ const sourceArg = args.find((a) => a.startsWith('--from='))?.split('=')[1];
35
37
  const force = args.includes('--force');
36
38
 
37
39
  if (!sourceArg) {
@@ -55,7 +57,7 @@ async function install(args) {
55
57
  // Get skills from source
56
58
  const skills = await fs.readdir(sourcePath);
57
59
  const validSkills = [];
58
-
60
+
59
61
  for (const skill of skills) {
60
62
  if ((await fs.stat(path.join(sourcePath, skill))).isDirectory()) {
61
63
  validSkills.push(skill);
@@ -68,18 +70,18 @@ async function install(args) {
68
70
  }
69
71
 
70
72
  console.log(`\nFound ${validSkills.length} skills to install.`);
71
-
73
+
72
74
  // Install
73
75
  for (const skillName of validSkills) {
74
76
  await installSkill(
75
- skillName,
76
- path.join(sourcePath, skillName),
77
+ skillName,
78
+ path.join(sourcePath, skillName),
77
79
  path.join(process.cwd(), toolInfo.path, skillName),
78
80
  force
79
81
  );
80
82
  }
81
83
 
82
- console.log(`\n✅ Installation complete.`);
84
+ console.log('\n✅ Installation complete.');
83
85
  }
84
86
 
85
87
  async function init(args) {
@@ -88,44 +90,140 @@ async function init(args) {
88
90
  const toolInfo = await detectTool(args);
89
91
  if (!toolInfo) return;
90
92
 
93
+ // Check for bootstrap flag
94
+ const isBootstrap = args.includes('--bootstrap');
95
+
96
+ if (isBootstrap) {
97
+ console.log('🔄 Bootstrapping full BMAD suite...');
98
+
99
+ // 1. Run conversion
100
+ const tempDir = '.temp/converted-skills-bootstrap';
101
+ console.log(`\n--- Step 1: Fetching & Converting (${tempDir}) ---`);
102
+ process.env.BMAD_OUTPUT_DIR = tempDir; // Pass specific output dir to convert.js
103
+
104
+ // Create synthetic args for convert.js
105
+ // We filter out init-specific args to avoid confusion, but keep repo/branch overrides
106
+ const convertArgs = args.filter(
107
+ (a) =>
108
+ !['init', '--bootstrap', '--force', '--tool'].some((x) => a.includes(x))
109
+ );
110
+
111
+ // Temporarily override argv for the imported module
112
+ const originalArgv = process.argv;
113
+ process.argv = [
114
+ process.argv[0],
115
+ process.argv[1],
116
+ ...convertArgs,
117
+ '--output-dir',
118
+ tempDir,
119
+ ];
120
+
121
+ try {
122
+ const { spawn } = await import('node:child_process');
123
+ const binPath = fileURLToPath(import.meta.url); // this file
124
+
125
+ await new Promise((resolve, reject) => {
126
+ // Run self without command -> trigger convert logic
127
+ const child = spawn(
128
+ process.execPath,
129
+ [binPath, ...convertArgs, '--output-dir', tempDir],
130
+ {
131
+ stdio: 'inherit',
132
+ }
133
+ );
134
+ child.on('close', (code) => {
135
+ if (code === 0) resolve();
136
+ else reject(new Error(`Conversion failed with code ${code}`));
137
+ });
138
+ });
139
+ } catch (error) {
140
+ console.error(`❌ Bootstrap conversion failed: ${error.message}`);
141
+ process.argv = originalArgv; // Restore
142
+ return;
143
+ }
144
+ process.argv = originalArgv; // Restore
145
+
146
+ // 2. Install
147
+ console.log(`\n--- Step 2: Installing to ${toolInfo.name} ---`);
148
+ await install([
149
+ 'install',
150
+ `--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
+ ]);
154
+
155
+ // 3. Install bundled skills (bootstrap-bmad-skills, enhance-bmad-skills)
156
+ // We reuse the logic from the standard init, but silence it slightly or just run it
157
+ const skillsDir = path.join(pkgRoot, 'skills');
158
+ if (await fs.pathExists(skillsDir)) {
159
+ const skills = await fs.readdir(skillsDir);
160
+ for (const skill of skills) {
161
+ if ((await fs.stat(path.join(skillsDir, skill))).isDirectory()) {
162
+ await installSkill(
163
+ skill,
164
+ path.join(skillsDir, skill),
165
+ path.join(process.cwd(), toolInfo.path, skill),
166
+ true
167
+ );
168
+ }
169
+ }
170
+ }
171
+
172
+ // Cleanup
173
+ try {
174
+ await fs.remove('.temp'); // Remove generic temp if used
175
+ // Note: we used specific temp dir, removing that
176
+ if (tempDir.startsWith('.temp')) await fs.remove('.temp');
177
+ } catch (_e) {
178
+ /* ignore */
179
+ }
180
+
181
+ console.log('\n✨ Bootstrap functionality complete!');
182
+ return;
183
+ }
184
+
185
+ // STANDARD INIT (Bootstrap skills only)
91
186
  console.log(`📦 Installing Bootstrap Skills for ${toolInfo.name}...`);
92
187
 
93
188
  // Dynamically find skills in package
94
189
  const skillsDir = path.join(pkgRoot, 'skills');
95
190
  if (!(await fs.pathExists(skillsDir))) {
96
- console.error('❌ Critical Error: Package skills directory not found.');
97
- return;
191
+ console.error('❌ Critical Error: Package skills directory not found.');
192
+ return;
98
193
  }
99
194
 
100
195
  const skills = await fs.readdir(skillsDir);
101
196
  const skillsToInstall = [];
102
-
197
+
103
198
  for (const skill of skills) {
104
199
  // Only install directories as skills
105
200
  if ((await fs.stat(path.join(skillsDir, skill))).isDirectory()) {
106
- skillsToInstall.push(skill);
201
+ skillsToInstall.push(skill);
107
202
  }
108
203
  }
109
204
 
110
205
  if (skillsToInstall.length === 0) {
111
- console.warn('⚠️ No skills found in package to install.');
112
- return;
206
+ console.warn('⚠️ No skills found in package to install.');
207
+ return;
113
208
  }
114
-
209
+
115
210
  const force = args.includes('--force');
116
211
 
117
212
  try {
118
213
  for (const skillName of skillsToInstall) {
119
214
  const sourceDir = path.join(skillsDir, skillName);
120
215
  const targetDir = path.resolve(process.cwd(), toolInfo.path, skillName);
121
-
216
+
122
217
  await installSkill(skillName, sourceDir, targetDir, force);
123
218
  }
124
-
219
+
125
220
  console.log(`\n✅ Successfully initialized in: ${toolInfo.path}/`);
126
221
  console.log('\nNext steps:');
127
222
  console.log(`1. Open your AI chat (${toolInfo.name}).`);
128
- console.log('2. Type "BS" or "bootstrap-skills" to fetch and install the full BMAD method suite.');
223
+ console.log(
224
+ '2. Run "npx @clfhhc/bmad-methods-skills init --bootstrap" to auto-install everything.'
225
+ );
226
+ console.log(' OR Type "BS" in chat for the guided workflow.');
129
227
  } catch (error) {
130
228
  console.error(`\n❌ Installation failed: ${error.message}`);
131
229
  }
@@ -138,14 +236,14 @@ async function installSkill(name, source, target, force) {
138
236
  if (await fs.pathExists(target)) {
139
237
  if (!force) {
140
238
  console.warn(` ⚠ Skill '${name}' already exists. Skipping.`);
141
- console.warn(' (Use --force to overwrite)');
239
+ console.warn(' (Use --force to overwrite)');
142
240
  return;
143
241
  }
144
242
  console.log(` ↻ Updating ${name}...`);
145
243
  } else {
146
244
  console.log(` + Installing ${name}...`);
147
245
  }
148
-
246
+
149
247
  await fs.ensureDir(path.dirname(target));
150
248
  await fs.copy(source, target, { overwrite: true });
151
249
  }
@@ -154,29 +252,45 @@ async function installSkill(name, source, target, force) {
154
252
  * Helper to detect AI tool
155
253
  */
156
254
  async function detectTool(args) {
157
- const toolArg = args.find(a => a.startsWith('--tool='))?.split('=')[1];
255
+ const toolArg = args.find((a) => a.startsWith('--tool='))?.split('=')[1];
158
256
  const force = args.includes('--force');
159
257
 
160
258
  const tools = [
161
- { name: 'Antigravity', path: '.agent/skills', active: await fs.pathExists('.agent') },
162
- { name: 'Cursor', path: '.cursor/skills', active: await fs.pathExists('.cursor') },
163
- { name: 'Claude Code (Local)', path: '.claude/skills', active: await fs.pathExists('.claude') },
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
+ },
164
274
  ];
165
275
 
166
- let selectedTool = tools.find(t => t.active);
167
-
276
+ let selectedTool = tools.find((t) => t.active);
277
+
168
278
  if (toolArg) {
169
- selectedTool = tools.find(t => t.name.toLowerCase().includes(toolArg.toLowerCase()));
279
+ selectedTool = tools.find((t) =>
280
+ t.name.toLowerCase().includes(toolArg.toLowerCase())
281
+ );
170
282
  }
171
283
 
172
284
  if (!selectedTool) {
173
285
  if (force) {
174
- // Default to antigravity if forced and not found
175
- return tools[0];
286
+ // Default to antigravity if forced and not found
287
+ return tools[0];
176
288
  }
177
-
289
+
178
290
  console.log('❌ No AI tool directory detected (.agent, .cursor, .claude).');
179
- console.log(' Use --tool=<name> to force installation or ensure you are in the project root.');
291
+ console.log(
292
+ ' Use --tool=<name> to force installation or ensure you are in the project root.'
293
+ );
180
294
  console.log(' Available tools: antigravity, cursor, claude');
181
295
  return null;
182
296
  }
@@ -196,6 +310,7 @@ Commands:
196
310
  Options (for init/install):
197
311
  --tool=<name> Specify tool (antigravity, cursor, claude)
198
312
  --force Overwrite existing skills / Force installation
313
+ --bootstrap Automatically fetch, convert, and install full suite
199
314
  --from=<path> (install only) Source directory containing skills
200
315
 
201
316
  Options (for conversion):
@@ -211,7 +326,7 @@ Options (for conversion):
211
326
  `);
212
327
  }
213
328
 
214
- run().catch(err => {
329
+ run().catch((err) => {
215
330
  console.error(err);
216
331
  process.exit(1);
217
332
  });
package/config.json CHANGED
@@ -4,14 +4,8 @@
4
4
  "outputDir": "./skills",
5
5
  "tempDir": "./.temp/bmad-method",
6
6
  "modules": ["bmm", "bmb", "cis", "core"],
7
- "agentPaths": [
8
- "src/core/agents",
9
- "src/modules/*/agents"
10
- ],
11
- "workflowPaths": [
12
- "src/core/workflows",
13
- "src/modules/*/workflows"
14
- ],
7
+ "agentPaths": ["src/core/agents", "src/modules/*/agents"],
8
+ "workflowPaths": ["src/core/workflows", "src/modules/*/workflows"],
15
9
  "enhancements": {
16
10
  "optional": {
17
11
  "addExamples": true,
package/convert.js CHANGED
@@ -1,10 +1,11 @@
1
- import fs from 'fs-extra';
2
1
  import path from 'node:path';
3
2
  import { fileURLToPath } from 'node:url';
4
- import { fetchBmadRepo } from './src/utils/bmad-fetcher.js';
5
- import { findAgentsAndWorkflows } from './src/utils/file-finder.js';
3
+ import fs from 'fs-extra';
6
4
  import { convertAgentToSkill } from './src/converters/agent-converter.js';
7
5
  import { convertWorkflowToSkill } from './src/converters/workflow-converter.js';
6
+ import { fetchBmadRepo } from './src/utils/bmad-fetcher.js';
7
+ import { findAgentsAndWorkflows } from './src/utils/file-finder.js';
8
+ import { migrateResources } from './src/utils/resource-migrator.js';
8
9
  import { writeSkill } from './src/utils/skill-writer.js';
9
10
 
10
11
  const __filename = fileURLToPath(import.meta.url);
@@ -29,7 +30,7 @@ function parseArgs() {
29
30
 
30
31
  for (let i = 0; i < args.length; i++) {
31
32
  const arg = args[i];
32
-
33
+
33
34
  if (arg === '--output-dir' && i + 1 < args.length) {
34
35
  options.outputDir = args[++i];
35
36
  } else if (arg === '--repo' && i + 1 < args.length) {
@@ -167,7 +168,8 @@ try {
167
168
  config.enhancements.optional.addBestPractices = cliOptions.addBestPractices;
168
169
  }
169
170
  if (cliOptions.addTroubleshooting !== null) {
170
- config.enhancements.optional.addTroubleshooting = cliOptions.addTroubleshooting;
171
+ config.enhancements.optional.addTroubleshooting =
172
+ cliOptions.addTroubleshooting;
171
173
  }
172
174
  if (cliOptions.addRelatedSkills !== null) {
173
175
  config.enhancements.optional.addRelatedSkills = cliOptions.addRelatedSkills;
@@ -199,7 +201,7 @@ async function main() {
199
201
  const bmadRoot = await fetchBmadRepo(
200
202
  config.bmadRepo,
201
203
  config.bmadBranch,
202
- path.resolve(process.cwd(), config.tempDir),
204
+ path.resolve(process.cwd(), config.tempDir)
203
205
  );
204
206
  console.log(`✓ Repository ready at: ${bmadRoot}\n`);
205
207
 
@@ -208,14 +210,14 @@ async function main() {
208
210
  const { agents, workflows } = await findAgentsAndWorkflows(
209
211
  bmadRoot,
210
212
  config.agentPaths,
211
- config.workflowPaths,
213
+ config.workflowPaths
212
214
  );
213
215
 
214
216
  stats.agents.total = agents.length;
215
217
  stats.workflows.total = workflows.length;
216
218
 
217
219
  console.log(
218
- `✓ Found ${agents.length} agents and ${workflows.length} workflows\n`,
220
+ `✓ Found ${agents.length} agents and ${workflows.length} workflows\n`
219
221
  );
220
222
 
221
223
  // Step 3: Prepare output directory
@@ -237,12 +239,7 @@ async function main() {
237
239
  ...agentOptions,
238
240
  currentModule: agent.module,
239
241
  });
240
- await writeSkill(
241
- outputDir,
242
- agent.module,
243
- agent.name,
244
- skillContent,
245
- );
242
+ await writeSkill(outputDir, agent.module, agent.name, skillContent);
246
243
  stats.agents.converted++;
247
244
  console.log(` ✓ ${agent.module}/${agent.name}`);
248
245
  } catch (error) {
@@ -252,9 +249,7 @@ async function main() {
252
249
  path: agent.path,
253
250
  error: error.message,
254
251
  });
255
- console.error(
256
- ` ✗ ${agent.module}/${agent.name}: ${error.message}`,
257
- );
252
+ console.error(` ✗ ${agent.module}/${agent.name}: ${error.message}`);
258
253
  }
259
254
  }
260
255
  console.log();
@@ -274,14 +269,14 @@ async function main() {
274
269
  {
275
270
  ...workflowOptions,
276
271
  isMarkdown: workflow.isMarkdown || false,
277
- },
272
+ }
278
273
  );
279
274
  await writeSkill(
280
275
  outputDir,
281
276
  workflow.module,
282
277
  workflow.name,
283
278
  skillContent,
284
- { workflowDir: workflow.workflowDir },
279
+ { workflowDir: workflow.workflowDir }
285
280
  );
286
281
  stats.workflows.converted++;
287
282
  console.log(` ✓ ${workflow.module}/${workflow.name}`);
@@ -293,14 +288,17 @@ async function main() {
293
288
  error: error.message,
294
289
  });
295
290
  console.error(
296
- ` ✗ ${workflow.module}/${workflow.name}: ${error.message}`,
291
+ ` ✗ ${workflow.module}/${workflow.name}: ${error.message}`
297
292
  );
298
293
  }
299
294
  }
300
295
  console.log();
301
296
  }
302
297
 
303
- // Step 6: Generate summary
298
+ // Step 6: Migrate auxiliary resources
299
+ await migrateResources(bmadRoot, outputDir);
300
+
301
+ // Step 7: Generate summary
304
302
  await printSummary();
305
303
  } catch (error) {
306
304
  console.error(`\n❌ Fatal error: ${error.message}`);
@@ -325,8 +323,7 @@ async function printSummary() {
325
323
  console.log(` Errors: ${stats.workflows.errors}`);
326
324
  console.log();
327
325
 
328
- const totalConverted =
329
- stats.agents.converted + stats.workflows.converted;
326
+ const totalConverted = stats.agents.converted + stats.workflows.converted;
330
327
  const totalErrors = stats.agents.errors + stats.workflows.errors;
331
328
 
332
329
  if (totalErrors > 0) {
@@ -340,19 +337,23 @@ async function printSummary() {
340
337
 
341
338
  console.log(`✅ Successfully converted ${totalConverted} skills`);
342
339
  console.log(
343
- `📁 Output directory: ${path.resolve(process.cwd(), config.outputDir)}`,
340
+ `📁 Output directory: ${path.resolve(process.cwd(), config.outputDir)}`
344
341
  );
345
-
342
+
346
343
  // Show configuration info
347
344
  if (config.outputDir !== './skills') {
348
345
  console.log('\n💡 Note: Output directory is not the default (./skills)');
349
346
  console.log(' This directory is not version controlled.');
350
347
  console.log(' To use default settings, run without --output-dir flag.');
351
348
  }
352
-
349
+
353
350
  if (config.enhancements.identityCharLimit !== null) {
354
- console.log(`\n💡 Note: Identity character limit is set to ${config.enhancements.identityCharLimit}`);
355
- console.log(' Default behavior (no limit) is recommended for better content.');
351
+ console.log(
352
+ `\n💡 Note: Identity character limit is set to ${config.enhancements.identityCharLimit}`
353
+ );
354
+ console.log(
355
+ ' Default behavior (no limit) is recommended for better content.'
356
+ );
356
357
  }
357
358
 
358
359
  // Print per-module breakdown