bmad-method 6.2.1-next.25 → 6.2.1-next.27

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.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/tools/cli/installers/lib/core/installer.js +1 -3
  3. package/tools/cli/installers/lib/core/manifest-generator.js +58 -734
  4. package/src/bmm-skills/1-analysis/bmad-document-project/bmad-skill-manifest.yaml +0 -1
  5. package/src/bmm-skills/1-analysis/bmad-product-brief/bmad-skill-manifest.yaml +0 -1
  6. package/src/bmm-skills/1-analysis/research/bmad-domain-research/bmad-skill-manifest.yaml +0 -1
  7. package/src/bmm-skills/1-analysis/research/bmad-market-research/bmad-skill-manifest.yaml +0 -1
  8. package/src/bmm-skills/1-analysis/research/bmad-technical-research/bmad-skill-manifest.yaml +0 -1
  9. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/bmad-skill-manifest.yaml +0 -1
  10. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/bmad-skill-manifest.yaml +0 -1
  11. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/bmad-skill-manifest.yaml +0 -1
  12. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/bmad-skill-manifest.yaml +0 -1
  13. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/bmad-skill-manifest.yaml +0 -1
  14. package/src/bmm-skills/3-solutioning/bmad-create-architecture/bmad-skill-manifest.yaml +0 -1
  15. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/bmad-skill-manifest.yaml +0 -1
  16. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/bmad-skill-manifest.yaml +0 -1
  17. package/src/bmm-skills/4-implementation/bmad-code-review/bmad-skill-manifest.yaml +0 -1
  18. package/src/bmm-skills/4-implementation/bmad-correct-course/bmad-skill-manifest.yaml +0 -1
  19. package/src/bmm-skills/4-implementation/bmad-create-story/bmad-skill-manifest.yaml +0 -1
  20. package/src/bmm-skills/4-implementation/bmad-dev-story/bmad-skill-manifest.yaml +0 -1
  21. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/bmad-skill-manifest.yaml +0 -1
  22. package/src/bmm-skills/4-implementation/bmad-quick-dev/bmad-skill-manifest.yaml +0 -1
  23. package/src/bmm-skills/4-implementation/bmad-retrospective/bmad-skill-manifest.yaml +0 -1
  24. package/src/bmm-skills/4-implementation/bmad-sprint-planning/bmad-skill-manifest.yaml +0 -1
  25. package/src/bmm-skills/4-implementation/bmad-sprint-status/bmad-skill-manifest.yaml +0 -1
  26. package/src/core-skills/bmad-advanced-elicitation/bmad-skill-manifest.yaml +0 -1
  27. package/src/core-skills/bmad-brainstorming/bmad-skill-manifest.yaml +0 -1
  28. package/src/core-skills/bmad-distillator/bmad-skill-manifest.yaml +0 -15
  29. package/src/core-skills/bmad-editorial-review-prose/bmad-skill-manifest.yaml +0 -1
  30. package/src/core-skills/bmad-editorial-review-structure/bmad-skill-manifest.yaml +0 -1
  31. package/src/core-skills/bmad-help/bmad-skill-manifest.yaml +0 -1
  32. package/src/core-skills/bmad-index-docs/bmad-skill-manifest.yaml +0 -1
  33. package/src/core-skills/bmad-init/bmad-skill-manifest.yaml +0 -1
  34. package/src/core-skills/bmad-party-mode/bmad-skill-manifest.yaml +0 -1
  35. package/src/core-skills/bmad-review-adversarial-general/bmad-skill-manifest.yaml +0 -1
  36. package/src/core-skills/bmad-review-edge-case-hunter/bmad-skill-manifest.yaml +0 -1
  37. package/src/core-skills/bmad-shard-doc/bmad-skill-manifest.yaml +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "bmad-method",
4
- "version": "6.2.1-next.25",
4
+ "version": "6.2.1-next.27",
5
5
  "description": "Breakthrough Method of Agile AI-driven Development",
6
6
  "keywords": [
7
7
  "agile",
@@ -1124,11 +1124,9 @@ class Installer {
1124
1124
  // Pre-register manifest files
1125
1125
  const cfgDir = path.join(bmadDir, '_config');
1126
1126
  this.installedFiles.add(path.join(cfgDir, 'manifest.yaml'));
1127
- this.installedFiles.add(path.join(cfgDir, 'workflow-manifest.csv'));
1128
1127
  this.installedFiles.add(path.join(cfgDir, 'agent-manifest.csv'));
1129
- this.installedFiles.add(path.join(cfgDir, 'task-manifest.csv'));
1130
1128
 
1131
- // Generate CSV manifests for workflows, agents, tasks AND ALL FILES with hashes
1129
+ // Generate CSV manifests for agents, skills AND ALL FILES with hashes
1132
1130
  // This must happen BEFORE mergeModuleHelpCatalogs because it depends on agent-manifest.csv
1133
1131
  message('Generating manifests...');
1134
1132
  const manifestGen = new ManifestGenerator();
@@ -16,15 +16,12 @@ const {
16
16
  const packageJson = require('../../../../../package.json');
17
17
 
18
18
  /**
19
- * Generates manifest files for installed workflows, agents, and tasks
19
+ * Generates manifest files for installed skills and agents
20
20
  */
21
21
  class ManifestGenerator {
22
22
  constructor() {
23
- this.workflows = [];
24
23
  this.skills = [];
25
24
  this.agents = [];
26
- this.tasks = [];
27
- this.tools = [];
28
25
  this.modules = [];
29
26
  this.files = [];
30
27
  this.selectedIdes = [];
@@ -50,29 +47,6 @@ class ManifestGenerator {
50
47
  return getInstallToBmadShared(manifest, filename);
51
48
  }
52
49
 
53
- /**
54
- * Native SKILL.md entrypoints can be packaged as either skills or agents.
55
- * Both need verbatim installation for skill-format IDEs.
56
- * @param {string|null} artifactType - Manifest type resolved for SKILL.md
57
- * @returns {boolean} True when the directory should be installed verbatim
58
- */
59
- isNativeSkillDirType(artifactType) {
60
- return artifactType === 'skill' || artifactType === 'agent';
61
- }
62
-
63
- /**
64
- * Check whether a loaded bmad-skill-manifest.yaml declares a native
65
- * SKILL.md entrypoint, either as a single-entry manifest or a multi-entry map.
66
- * @param {Object|null} manifest - Loaded manifest
67
- * @returns {boolean} True when the manifest contains a native skill/agent entrypoint
68
- */
69
- hasNativeSkillManifest(manifest) {
70
- if (!manifest) return false;
71
- if (manifest.__single) return this.isNativeSkillDirType(manifest.__single.type);
72
-
73
- return Object.values(manifest).some((entry) => this.isNativeSkillDirType(entry?.type));
74
- }
75
-
76
50
  /**
77
51
  * Clean text for CSV output by normalizing whitespace.
78
52
  * Note: Quote escaping is handled by escapeCsv() at write time.
@@ -108,10 +82,6 @@ class ManifestGenerator {
108
82
  this.modules = allModules;
109
83
  this.updatedModules = allModules; // Include ALL modules (including custom) for scanning
110
84
 
111
- // For CSV manifests, we need to include ALL modules that are installed
112
- // preservedModules controls which modules stay as-is in the CSV (don't get rescanned)
113
- // But all modules should be included in the final manifest
114
- this.preservedModules = allModules; // Include ALL modules (including custom)
115
85
  this.bmadDir = bmadDir;
116
86
  this.bmadFolderName = path.basename(bmadDir); // Get the actual folder name (e.g., '_bmad' or 'bmad')
117
87
  this.allInstalledFiles = installedFiles;
@@ -134,35 +104,20 @@ class ManifestGenerator {
134
104
  // Collect skills first (populates skillClaimedDirs before legacy collectors run)
135
105
  await this.collectSkills();
136
106
 
137
- // Collect workflow data
138
- await this.collectWorkflows(selectedModules);
139
-
140
107
  // Collect agent data - use updatedModules which includes all installed modules
141
108
  await this.collectAgents(this.updatedModules);
142
109
 
143
- // Collect task data
144
- await this.collectTasks(this.updatedModules);
145
-
146
- // Collect tool data
147
- await this.collectTools(this.updatedModules);
148
-
149
110
  // Write manifest files and collect their paths
150
111
  const manifestFiles = [
151
112
  await this.writeMainManifest(cfgDir),
152
- await this.writeWorkflowManifest(cfgDir),
153
113
  await this.writeSkillManifest(cfgDir),
154
114
  await this.writeAgentManifest(cfgDir),
155
- await this.writeTaskManifest(cfgDir),
156
- await this.writeToolManifest(cfgDir),
157
115
  await this.writeFilesManifest(cfgDir),
158
116
  ];
159
117
 
160
118
  return {
161
119
  skills: this.skills.length,
162
- workflows: this.workflows.length,
163
120
  agents: this.agents.length,
164
- tasks: this.tasks.length,
165
- tools: this.tools.length,
166
121
  files: this.files.length,
167
122
  manifestFiles: manifestFiles,
168
123
  };
@@ -170,9 +125,9 @@ class ManifestGenerator {
170
125
 
171
126
  /**
172
127
  * Recursively walk a module directory tree, collecting native SKILL.md entrypoints.
173
- * A native entrypoint directory is one that contains both a
174
- * bmad-skill-manifest.yaml with type: skill or type: agent AND a SKILL.md file
175
- * with name/description frontmatter.
128
+ * A directory is discovered as a skill when it contains a SKILL.md file with
129
+ * valid name/description frontmatter (name must match directory name).
130
+ * Manifest YAML is loaded only when present — for install_to_bmad and agent metadata.
176
131
  * Populates this.skills[] and this.skillClaimedDirs (Set of absolute paths).
177
132
  */
178
133
  async collectSkills() {
@@ -193,77 +148,55 @@ class ManifestGenerator {
193
148
  return;
194
149
  }
195
150
 
196
- // Check this directory for skill manifest
197
- const manifest = await this.loadSkillManifest(dir);
198
-
199
- // Determine if this directory is a native SKILL.md entrypoint
151
+ // SKILL.md with valid frontmatter is the primary discovery gate
200
152
  const skillFile = 'SKILL.md';
201
- const artifactType = this.getArtifactType(manifest, skillFile);
202
-
203
- if (this.isNativeSkillDirType(artifactType)) {
204
- const skillMdPath = path.join(dir, 'SKILL.md');
205
- const dirName = path.basename(dir);
206
-
207
- // Validate and parse SKILL.md
208
- const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
209
-
210
- if (skillMeta) {
211
- // Build path relative from module root (points to SKILL.md — the permanent entrypoint)
212
- const relativePath = path.relative(modulePath, dir).split(path.sep).join('/');
213
- const installPath = relativePath
214
- ? `${this.bmadFolderName}/${moduleName}/${relativePath}/${skillFile}`
215
- : `${this.bmadFolderName}/${moduleName}/${skillFile}`;
216
-
217
- // Native SKILL.md entrypoints derive canonicalId from directory name.
218
- // Agent entrypoints may keep canonicalId metadata for compatibility, so
219
- // only warn for non-agent SKILL.md directories.
220
- if (manifest && manifest.__single && manifest.__single.canonicalId && artifactType !== 'agent') {
221
- console.warn(
222
- `Warning: Native entrypoint manifest at ${dir}/bmad-skill-manifest.yaml contains canonicalId — this field is ignored for SKILL.md directories (directory name is the canonical ID)`,
223
- );
224
- }
225
- const canonicalId = dirName;
226
-
227
- this.skills.push({
228
- name: skillMeta.name,
229
- description: this.cleanForCSV(skillMeta.description),
230
- module: moduleName,
231
- path: installPath,
232
- canonicalId,
233
- install_to_bmad: this.getInstallToBmad(manifest, skillFile),
234
- });
235
-
236
- // Add to files list
237
- this.files.push({
238
- type: 'skill',
239
- name: skillMeta.name,
240
- module: moduleName,
241
- path: installPath,
242
- });
243
-
244
- this.skillClaimedDirs.add(dir);
245
-
246
- if (debug) {
247
- console.log(`[DEBUG] collectSkills: claimed skill "${skillMeta.name}" as ${canonicalId} at ${dir}`);
248
- }
153
+ const skillMdPath = path.join(dir, skillFile);
154
+ const dirName = path.basename(dir);
155
+
156
+ const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
157
+
158
+ if (skillMeta) {
159
+ // Load manifest when present (for install_to_bmad and agent metadata)
160
+ const manifest = await this.loadSkillManifest(dir);
161
+ const artifactType = this.getArtifactType(manifest, skillFile);
162
+
163
+ // Build path relative from module root (points to SKILL.md — the permanent entrypoint)
164
+ const relativePath = path.relative(modulePath, dir).split(path.sep).join('/');
165
+ const installPath = relativePath
166
+ ? `${this.bmadFolderName}/${moduleName}/${relativePath}/${skillFile}`
167
+ : `${this.bmadFolderName}/${moduleName}/${skillFile}`;
168
+
169
+ // Native SKILL.md entrypoints derive canonicalId from directory name.
170
+ // Agent entrypoints may keep canonicalId metadata for compatibility, so
171
+ // only warn for non-agent SKILL.md directories.
172
+ if (manifest && manifest.__single && manifest.__single.canonicalId && artifactType !== 'agent') {
173
+ console.warn(
174
+ `Warning: Native entrypoint manifest at ${dir}/bmad-skill-manifest.yaml contains canonicalId — this field is ignored for SKILL.md directories (directory name is the canonical ID)`,
175
+ );
249
176
  }
250
- }
177
+ const canonicalId = dirName;
251
178
 
252
- // Warn if manifest says this is a native entrypoint but the directory was not claimed
253
- if (manifest && !this.skillClaimedDirs.has(dir)) {
254
- let hasNativeSkillType = false;
255
- if (manifest.__single) {
256
- hasNativeSkillType = this.isNativeSkillDirType(manifest.__single.type);
257
- } else {
258
- for (const key of Object.keys(manifest)) {
259
- if (this.isNativeSkillDirType(manifest[key]?.type)) {
260
- hasNativeSkillType = true;
261
- break;
262
- }
263
- }
264
- }
265
- if (hasNativeSkillType && debug) {
266
- console.log(`[DEBUG] collectSkills: dir has native SKILL.md manifest but failed validation: ${dir}`);
179
+ this.skills.push({
180
+ name: skillMeta.name,
181
+ description: this.cleanForCSV(skillMeta.description),
182
+ module: moduleName,
183
+ path: installPath,
184
+ canonicalId,
185
+ install_to_bmad: this.getInstallToBmad(manifest, skillFile),
186
+ });
187
+
188
+ // Add to files list
189
+ this.files.push({
190
+ type: 'skill',
191
+ name: skillMeta.name,
192
+ module: moduleName,
193
+ path: installPath,
194
+ });
195
+
196
+ this.skillClaimedDirs.add(dir);
197
+
198
+ if (debug) {
199
+ console.log(`[DEBUG] collectSkills: claimed skill "${skillMeta.name}" as ${canonicalId} at ${dir}`);
267
200
  }
268
201
  }
269
202
 
@@ -334,153 +267,6 @@ class ManifestGenerator {
334
267
  }
335
268
  }
336
269
 
337
- /**
338
- * Collect all workflows from core and selected modules
339
- * Scans the INSTALLED bmad directory, not the source
340
- */
341
- async collectWorkflows(selectedModules) {
342
- this.workflows = [];
343
-
344
- // Use updatedModules which already includes deduplicated 'core' + selectedModules
345
- for (const moduleName of this.updatedModules) {
346
- const modulePath = path.join(this.bmadDir, moduleName);
347
-
348
- if (await fs.pathExists(modulePath)) {
349
- const moduleWorkflows = await this.getWorkflowsFromPath(modulePath, moduleName);
350
- this.workflows.push(...moduleWorkflows);
351
-
352
- // Also scan tasks/ for type:skill entries (skills can live anywhere)
353
- const tasksSkills = await this.getWorkflowsFromPath(modulePath, moduleName, 'tasks');
354
- this.workflows.push(...tasksSkills);
355
- }
356
- }
357
- }
358
-
359
- /**
360
- * Recursively find and parse workflow.md files
361
- */
362
- async getWorkflowsFromPath(basePath, moduleName, subDir = 'workflows') {
363
- const workflows = [];
364
- const workflowsPath = path.join(basePath, subDir);
365
- const debug = process.env.BMAD_DEBUG_MANIFEST === 'true';
366
-
367
- if (debug) {
368
- console.log(`[DEBUG] Scanning workflows in: ${workflowsPath}`);
369
- }
370
-
371
- if (!(await fs.pathExists(workflowsPath))) {
372
- if (debug) {
373
- console.log(`[DEBUG] Workflows path does not exist: ${workflowsPath}`);
374
- }
375
- return workflows;
376
- }
377
-
378
- // Recursively find workflow.md files
379
- const findWorkflows = async (dir, relativePath = '') => {
380
- // Skip directories already claimed as skills
381
- if (this.skillClaimedDirs && this.skillClaimedDirs.has(dir)) return;
382
-
383
- const entries = await fs.readdir(dir, { withFileTypes: true });
384
- // Load skill manifest for this directory (if present)
385
- const skillManifest = await this.loadSkillManifest(dir);
386
-
387
- for (const entry of entries) {
388
- const fullPath = path.join(dir, entry.name);
389
-
390
- if (entry.isDirectory()) {
391
- // Skip directories claimed by collectSkills
392
- if (this.skillClaimedDirs && this.skillClaimedDirs.has(fullPath)) continue;
393
- // Recurse into subdirectories
394
- const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
395
- await findWorkflows(fullPath, newRelativePath);
396
- } else if (entry.name === 'workflow.md' || (entry.name.startsWith('workflow-') && entry.name.endsWith('.md'))) {
397
- // Parse workflow file (both YAML and MD formats)
398
- if (debug) {
399
- console.log(`[DEBUG] Found workflow file: ${fullPath}`);
400
- }
401
- try {
402
- // Read and normalize line endings (fix Windows CRLF issues)
403
- const rawContent = await fs.readFile(fullPath, 'utf8');
404
- const content = rawContent.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
405
-
406
- // Parse MD workflow with YAML frontmatter
407
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
408
- if (!frontmatterMatch) {
409
- if (debug) {
410
- console.log(`[DEBUG] Skipped (no frontmatter): ${fullPath}`);
411
- }
412
- continue; // Skip MD files without frontmatter
413
- }
414
- const workflow = yaml.parse(frontmatterMatch[1]);
415
-
416
- if (debug) {
417
- console.log(`[DEBUG] Parsed: name="${workflow.name}", description=${workflow.description ? 'OK' : 'MISSING'}`);
418
- }
419
-
420
- // Skip template workflows (those with placeholder values)
421
- if (workflow.name && workflow.name.includes('{') && workflow.name.includes('}')) {
422
- if (debug) {
423
- console.log(`[DEBUG] Skipped (template placeholder): ${workflow.name}`);
424
- }
425
- continue;
426
- }
427
-
428
- // Skip workflows marked as non-standalone (reference/example workflows)
429
- if (workflow.standalone === false) {
430
- if (debug) {
431
- console.log(`[DEBUG] Skipped (standalone=false): ${workflow.name}`);
432
- }
433
- continue;
434
- }
435
-
436
- if (workflow.name && workflow.description) {
437
- // Build relative path for installation
438
- const installPath =
439
- moduleName === 'core'
440
- ? `${this.bmadFolderName}/core/${subDir}/${relativePath}/${entry.name}`
441
- : `${this.bmadFolderName}/${moduleName}/${subDir}/${relativePath}/${entry.name}`;
442
-
443
- // Workflows with standalone: false are filtered out above
444
- workflows.push({
445
- name: workflow.name,
446
- description: this.cleanForCSV(workflow.description),
447
- module: moduleName,
448
- path: installPath,
449
- canonicalId: this.getCanonicalId(skillManifest, entry.name),
450
- });
451
-
452
- // Add to files list
453
- this.files.push({
454
- type: 'workflow',
455
- name: workflow.name,
456
- module: moduleName,
457
- path: installPath,
458
- });
459
-
460
- if (debug) {
461
- console.log(`[DEBUG] ✓ Added workflow: ${workflow.name} (${moduleName})`);
462
- }
463
- } else {
464
- if (debug) {
465
- console.log(`[DEBUG] Skipped (missing name or description): ${fullPath}`);
466
- }
467
- }
468
- } catch (error) {
469
- await prompts.log.warn(`Failed to parse workflow at ${fullPath}: ${error.message}`);
470
- }
471
- }
472
- }
473
- };
474
-
475
- await findWorkflows(workflowsPath);
476
-
477
- if (debug) {
478
- console.log(`[DEBUG] Total workflows found in ${moduleName}: ${workflows.length}`);
479
- }
480
-
481
- return workflows;
482
- }
483
-
484
270
  /**
485
271
  * Collect all agents from core and selected modules
486
272
  * Scans the INSTALLED bmad directory, not the source
@@ -634,212 +420,6 @@ class ManifestGenerator {
634
420
  return agents;
635
421
  }
636
422
 
637
- /**
638
- * Collect all tasks from core and selected modules
639
- * Scans the INSTALLED bmad directory, not the source
640
- */
641
- async collectTasks(selectedModules) {
642
- this.tasks = [];
643
-
644
- // Use updatedModules which already includes deduplicated 'core' + selectedModules
645
- for (const moduleName of this.updatedModules) {
646
- const tasksPath = path.join(this.bmadDir, moduleName, 'tasks');
647
-
648
- if (await fs.pathExists(tasksPath)) {
649
- const moduleTasks = await this.getTasksFromDir(tasksPath, moduleName);
650
- this.tasks.push(...moduleTasks);
651
- }
652
- }
653
- }
654
-
655
- /**
656
- * Get tasks from a directory
657
- */
658
- async getTasksFromDir(dirPath, moduleName) {
659
- // Skip directories claimed by collectSkills
660
- if (this.skillClaimedDirs && this.skillClaimedDirs.has(dirPath)) return [];
661
- const tasks = [];
662
- const files = await fs.readdir(dirPath);
663
- // Load skill manifest for this directory (if present)
664
- const skillManifest = await this.loadSkillManifest(dirPath);
665
-
666
- for (const file of files) {
667
- // Check for both .xml and .md files
668
- if (file.endsWith('.xml') || file.endsWith('.md')) {
669
- const filePath = path.join(dirPath, file);
670
- const content = await fs.readFile(filePath, 'utf8');
671
-
672
- // Skip internal/engine files (not user-facing tasks)
673
- if (content.includes('internal="true"')) {
674
- continue;
675
- }
676
-
677
- let name = file.replace(/\.(xml|md)$/, '');
678
- let displayName = name;
679
- let description = '';
680
- let standalone = false;
681
-
682
- if (file.endsWith('.md')) {
683
- // Parse YAML frontmatter for .md tasks
684
- const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
685
- if (frontmatterMatch) {
686
- try {
687
- const frontmatter = yaml.parse(frontmatterMatch[1]);
688
- name = frontmatter.name || name;
689
- displayName = frontmatter.displayName || frontmatter.name || name;
690
- description = this.cleanForCSV(frontmatter.description || '');
691
- // Tasks are standalone by default unless explicitly false (internal=true is already filtered above)
692
- standalone = frontmatter.standalone !== false && frontmatter.standalone !== 'false';
693
- } catch {
694
- // If YAML parsing fails, use defaults
695
- standalone = true; // Default to standalone
696
- }
697
- } else {
698
- standalone = true; // No frontmatter means standalone
699
- }
700
- } else {
701
- // For .xml tasks, extract from tag attributes
702
- const nameMatch = content.match(/name="([^"]+)"/);
703
- displayName = nameMatch ? nameMatch[1] : name;
704
-
705
- const descMatch = content.match(/description="([^"]+)"/);
706
- const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
707
- description = this.cleanForCSV(descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '');
708
-
709
- const standaloneFalseMatch = content.match(/<task[^>]+standalone="false"/);
710
- standalone = !standaloneFalseMatch;
711
- }
712
-
713
- // Build relative path for installation
714
- const installPath =
715
- moduleName === 'core' ? `${this.bmadFolderName}/core/tasks/${file}` : `${this.bmadFolderName}/${moduleName}/tasks/${file}`;
716
-
717
- tasks.push({
718
- name: name,
719
- displayName: displayName,
720
- description: description,
721
- module: moduleName,
722
- path: installPath,
723
- standalone: standalone,
724
- canonicalId: this.getCanonicalId(skillManifest, file),
725
- });
726
-
727
- // Add to files list
728
- this.files.push({
729
- type: 'task',
730
- name: name,
731
- module: moduleName,
732
- path: installPath,
733
- });
734
- }
735
- }
736
-
737
- return tasks;
738
- }
739
-
740
- /**
741
- * Collect all tools from core and selected modules
742
- * Scans the INSTALLED bmad directory, not the source
743
- */
744
- async collectTools(selectedModules) {
745
- this.tools = [];
746
-
747
- // Use updatedModules which already includes deduplicated 'core' + selectedModules
748
- for (const moduleName of this.updatedModules) {
749
- const toolsPath = path.join(this.bmadDir, moduleName, 'tools');
750
-
751
- if (await fs.pathExists(toolsPath)) {
752
- const moduleTools = await this.getToolsFromDir(toolsPath, moduleName);
753
- this.tools.push(...moduleTools);
754
- }
755
- }
756
- }
757
-
758
- /**
759
- * Get tools from a directory
760
- */
761
- async getToolsFromDir(dirPath, moduleName) {
762
- // Skip directories claimed by collectSkills
763
- if (this.skillClaimedDirs && this.skillClaimedDirs.has(dirPath)) return [];
764
- const tools = [];
765
- const files = await fs.readdir(dirPath);
766
- // Load skill manifest for this directory (if present)
767
- const skillManifest = await this.loadSkillManifest(dirPath);
768
-
769
- for (const file of files) {
770
- // Check for both .xml and .md files
771
- if (file.endsWith('.xml') || file.endsWith('.md')) {
772
- const filePath = path.join(dirPath, file);
773
- const content = await fs.readFile(filePath, 'utf8');
774
-
775
- // Skip internal tools (same as tasks)
776
- if (content.includes('internal="true"')) {
777
- continue;
778
- }
779
-
780
- let name = file.replace(/\.(xml|md)$/, '');
781
- let displayName = name;
782
- let description = '';
783
- let standalone = false;
784
-
785
- if (file.endsWith('.md')) {
786
- // Parse YAML frontmatter for .md tools
787
- const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
788
- if (frontmatterMatch) {
789
- try {
790
- const frontmatter = yaml.parse(frontmatterMatch[1]);
791
- name = frontmatter.name || name;
792
- displayName = frontmatter.displayName || frontmatter.name || name;
793
- description = this.cleanForCSV(frontmatter.description || '');
794
- // Tools are standalone by default unless explicitly false (internal=true is already filtered above)
795
- standalone = frontmatter.standalone !== false && frontmatter.standalone !== 'false';
796
- } catch {
797
- // If YAML parsing fails, use defaults
798
- standalone = true; // Default to standalone
799
- }
800
- } else {
801
- standalone = true; // No frontmatter means standalone
802
- }
803
- } else {
804
- // For .xml tools, extract from tag attributes
805
- const nameMatch = content.match(/name="([^"]+)"/);
806
- displayName = nameMatch ? nameMatch[1] : name;
807
-
808
- const descMatch = content.match(/description="([^"]+)"/);
809
- const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
810
- description = this.cleanForCSV(descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '');
811
-
812
- const standaloneFalseMatch = content.match(/<tool[^>]+standalone="false"/);
813
- standalone = !standaloneFalseMatch;
814
- }
815
-
816
- // Build relative path for installation
817
- const installPath =
818
- moduleName === 'core' ? `${this.bmadFolderName}/core/tools/${file}` : `${this.bmadFolderName}/${moduleName}/tools/${file}`;
819
-
820
- tools.push({
821
- name: name,
822
- displayName: displayName,
823
- description: description,
824
- module: moduleName,
825
- path: installPath,
826
- standalone: standalone,
827
- canonicalId: this.getCanonicalId(skillManifest, file),
828
- });
829
-
830
- // Add to files list
831
- this.files.push({
832
- type: 'tool',
833
- name: name,
834
- module: moduleName,
835
- path: installPath,
836
- });
837
- }
838
- }
839
-
840
- return tools;
841
- }
842
-
843
423
  /**
844
424
  * Write main manifest as YAML with installation info only
845
425
  * Fetches fresh version info for all modules
@@ -925,131 +505,6 @@ class ManifestGenerator {
925
505
  return manifestPath;
926
506
  }
927
507
 
928
- /**
929
- * Read existing CSV and preserve rows for modules NOT being updated
930
- * @param {string} csvPath - Path to existing CSV file
931
- * @param {number} moduleColumnIndex - Which column contains the module name (0-indexed)
932
- * @param {Array<string>} expectedColumns - Expected column names in order
933
- * @param {Object} defaultValues - Default values for missing columns
934
- * @returns {Array} Preserved CSV rows (without header), upgraded to match expected columns
935
- */
936
- async getPreservedCsvRows(csvPath, moduleColumnIndex, expectedColumns, defaultValues = {}) {
937
- if (!(await fs.pathExists(csvPath)) || this.preservedModules.length === 0) {
938
- return [];
939
- }
940
-
941
- try {
942
- const content = await fs.readFile(csvPath, 'utf8');
943
- const lines = content.trim().split('\n');
944
-
945
- if (lines.length < 2) {
946
- return []; // No data rows
947
- }
948
-
949
- // Parse header to understand old schema
950
- const header = lines[0];
951
- const headerColumns = header.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
952
- const oldColumns = headerColumns.map((c) => c.replaceAll(/^"|"$/g, ''));
953
-
954
- // Skip header row for data
955
- const dataRows = lines.slice(1);
956
- const preservedRows = [];
957
-
958
- for (const row of dataRows) {
959
- // Simple CSV parsing (handles quoted values)
960
- const columns = row.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
961
- const cleanColumns = columns.map((c) => c.replaceAll(/^"|"$/g, ''));
962
-
963
- const moduleValue = cleanColumns[moduleColumnIndex];
964
-
965
- // Keep this row if it belongs to a preserved module
966
- if (this.preservedModules.includes(moduleValue)) {
967
- // Upgrade row to match expected schema
968
- const upgradedRow = this.upgradeRowToSchema(cleanColumns, oldColumns, expectedColumns, defaultValues);
969
- preservedRows.push(upgradedRow);
970
- }
971
- }
972
-
973
- return preservedRows;
974
- } catch (error) {
975
- await prompts.log.warn(`Failed to read existing CSV ${csvPath}: ${error.message}`);
976
- return [];
977
- }
978
- }
979
-
980
- /**
981
- * Upgrade a CSV row from old schema to new schema
982
- * @param {Array<string>} rowValues - Values from old row
983
- * @param {Array<string>} oldColumns - Old column names
984
- * @param {Array<string>} newColumns - New column names
985
- * @param {Object} defaultValues - Default values for missing columns
986
- * @returns {string} Upgraded CSV row
987
- */
988
- upgradeRowToSchema(rowValues, oldColumns, newColumns, defaultValues) {
989
- const upgradedValues = [];
990
-
991
- for (const newCol of newColumns) {
992
- const oldIndex = oldColumns.indexOf(newCol);
993
-
994
- if (oldIndex !== -1 && oldIndex < rowValues.length) {
995
- // Column exists in old schema, use its value
996
- upgradedValues.push(rowValues[oldIndex]);
997
- } else if (defaultValues[newCol] === undefined) {
998
- // Column missing, no default provided
999
- upgradedValues.push('');
1000
- } else {
1001
- // Column missing, use default value
1002
- upgradedValues.push(defaultValues[newCol]);
1003
- }
1004
- }
1005
-
1006
- // Properly quote values and join
1007
- return upgradedValues.map((v) => `"${v}"`).join(',');
1008
- }
1009
-
1010
- /**
1011
- * Write workflow manifest CSV
1012
- * @returns {string} Path to the manifest file
1013
- */
1014
- async writeWorkflowManifest(cfgDir) {
1015
- const csvPath = path.join(cfgDir, 'workflow-manifest.csv');
1016
- const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
1017
-
1018
- // Create CSV header - standalone column removed, canonicalId added as optional column
1019
- let csv = 'name,description,module,path,canonicalId\n';
1020
-
1021
- // Build workflows map from discovered workflows only
1022
- // Old entries are NOT preserved - the manifest reflects what actually exists on disk
1023
- const allWorkflows = new Map();
1024
-
1025
- // Only add workflows that were actually discovered in this scan
1026
- for (const workflow of this.workflows) {
1027
- const key = `${workflow.module}:${workflow.name}`;
1028
- allWorkflows.set(key, {
1029
- name: workflow.name,
1030
- description: workflow.description,
1031
- module: workflow.module,
1032
- path: workflow.path,
1033
- canonicalId: workflow.canonicalId || '',
1034
- });
1035
- }
1036
-
1037
- // Write all workflows
1038
- for (const [, value] of allWorkflows) {
1039
- const row = [
1040
- escapeCsv(value.name),
1041
- escapeCsv(value.description),
1042
- escapeCsv(value.module),
1043
- escapeCsv(value.path),
1044
- escapeCsv(value.canonicalId),
1045
- ].join(',');
1046
- csv += row + '\n';
1047
- }
1048
-
1049
- await fs.writeFile(csvPath, csv);
1050
- return csvPath;
1051
- }
1052
-
1053
508
  /**
1054
509
  * Write skill manifest CSV
1055
510
  * @returns {string} Path to the manifest file
@@ -1150,134 +605,6 @@ class ManifestGenerator {
1150
605
  return csvPath;
1151
606
  }
1152
607
 
1153
- /**
1154
- * Write task manifest CSV
1155
- * @returns {string} Path to the manifest file
1156
- */
1157
- async writeTaskManifest(cfgDir) {
1158
- const csvPath = path.join(cfgDir, 'task-manifest.csv');
1159
- const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
1160
-
1161
- // Read existing manifest to preserve entries
1162
- const existingEntries = new Map();
1163
- if (await fs.pathExists(csvPath)) {
1164
- const content = await fs.readFile(csvPath, 'utf8');
1165
- const records = csv.parse(content, {
1166
- columns: true,
1167
- skip_empty_lines: true,
1168
- });
1169
- for (const record of records) {
1170
- existingEntries.set(`${record.module}:${record.name}`, record);
1171
- }
1172
- }
1173
-
1174
- // Create CSV header with standalone and canonicalId columns
1175
- let csvContent = 'name,displayName,description,module,path,standalone,canonicalId\n';
1176
-
1177
- // Combine existing and new tasks
1178
- const allTasks = new Map();
1179
-
1180
- // Add existing entries
1181
- for (const [key, value] of existingEntries) {
1182
- allTasks.set(key, value);
1183
- }
1184
-
1185
- // Add/update new tasks
1186
- for (const task of this.tasks) {
1187
- const key = `${task.module}:${task.name}`;
1188
- allTasks.set(key, {
1189
- name: task.name,
1190
- displayName: task.displayName,
1191
- description: task.description,
1192
- module: task.module,
1193
- path: task.path,
1194
- standalone: task.standalone,
1195
- canonicalId: task.canonicalId || '',
1196
- });
1197
- }
1198
-
1199
- // Write all tasks
1200
- for (const [, record] of allTasks) {
1201
- const row = [
1202
- escapeCsv(record.name),
1203
- escapeCsv(record.displayName),
1204
- escapeCsv(record.description),
1205
- escapeCsv(record.module),
1206
- escapeCsv(record.path),
1207
- escapeCsv(record.standalone),
1208
- escapeCsv(record.canonicalId),
1209
- ].join(',');
1210
- csvContent += row + '\n';
1211
- }
1212
-
1213
- await fs.writeFile(csvPath, csvContent);
1214
- return csvPath;
1215
- }
1216
-
1217
- /**
1218
- * Write tool manifest CSV
1219
- * @returns {string} Path to the manifest file
1220
- */
1221
- async writeToolManifest(cfgDir) {
1222
- const csvPath = path.join(cfgDir, 'tool-manifest.csv');
1223
- const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
1224
-
1225
- // Read existing manifest to preserve entries
1226
- const existingEntries = new Map();
1227
- if (await fs.pathExists(csvPath)) {
1228
- const content = await fs.readFile(csvPath, 'utf8');
1229
- const records = csv.parse(content, {
1230
- columns: true,
1231
- skip_empty_lines: true,
1232
- });
1233
- for (const record of records) {
1234
- existingEntries.set(`${record.module}:${record.name}`, record);
1235
- }
1236
- }
1237
-
1238
- // Create CSV header with standalone and canonicalId columns
1239
- let csvContent = 'name,displayName,description,module,path,standalone,canonicalId\n';
1240
-
1241
- // Combine existing and new tools
1242
- const allTools = new Map();
1243
-
1244
- // Add existing entries
1245
- for (const [key, value] of existingEntries) {
1246
- allTools.set(key, value);
1247
- }
1248
-
1249
- // Add/update new tools
1250
- for (const tool of this.tools) {
1251
- const key = `${tool.module}:${tool.name}`;
1252
- allTools.set(key, {
1253
- name: tool.name,
1254
- displayName: tool.displayName,
1255
- description: tool.description,
1256
- module: tool.module,
1257
- path: tool.path,
1258
- standalone: tool.standalone,
1259
- canonicalId: tool.canonicalId || '',
1260
- });
1261
- }
1262
-
1263
- // Write all tools
1264
- for (const [, record] of allTools) {
1265
- const row = [
1266
- escapeCsv(record.name),
1267
- escapeCsv(record.displayName),
1268
- escapeCsv(record.description),
1269
- escapeCsv(record.module),
1270
- escapeCsv(record.path),
1271
- escapeCsv(record.standalone),
1272
- escapeCsv(record.canonicalId),
1273
- ].join(',');
1274
- csvContent += row + '\n';
1275
- }
1276
-
1277
- await fs.writeFile(csvPath, csvContent);
1278
- return csvPath;
1279
- }
1280
-
1281
608
  /**
1282
609
  * Write files manifest CSV
1283
610
  */
@@ -1384,11 +711,10 @@ class ManifestGenerator {
1384
711
  const hasTasks = await fs.pathExists(path.join(modulePath, 'tasks'));
1385
712
  const hasTools = await fs.pathExists(path.join(modulePath, 'tools'));
1386
713
 
1387
- // Check for native-entrypoint-only modules: recursive scan for
1388
- // bmad-skill-manifest.yaml with type: skill or type: agent
714
+ // Check for native-entrypoint-only modules: recursive scan for SKILL.md
1389
715
  let hasSkills = false;
1390
716
  if (!hasAgents && !hasWorkflows && !hasTasks && !hasTools) {
1391
- hasSkills = await this._hasSkillManifestRecursive(modulePath);
717
+ hasSkills = await this._hasSkillMdRecursive(modulePath);
1392
718
  }
1393
719
 
1394
720
  // If it has any of these directories or skill manifests, it's likely a module
@@ -1404,13 +730,12 @@ class ManifestGenerator {
1404
730
  }
1405
731
 
1406
732
  /**
1407
- * Recursively check if a directory tree contains a bmad-skill-manifest.yaml that
1408
- * declares a native SKILL.md entrypoint (type: skill or type: agent).
733
+ * Recursively check if a directory tree contains a SKILL.md file.
1409
734
  * Skips directories starting with . or _.
1410
735
  * @param {string} dir - Directory to search
1411
- * @returns {boolean} True if a skill manifest is found
736
+ * @returns {boolean} True if a SKILL.md is found
1412
737
  */
1413
- async _hasSkillManifestRecursive(dir) {
738
+ async _hasSkillMdRecursive(dir) {
1414
739
  let entries;
1415
740
  try {
1416
741
  entries = await fs.readdir(dir, { withFileTypes: true });
@@ -1418,15 +743,14 @@ class ManifestGenerator {
1418
743
  return false;
1419
744
  }
1420
745
 
1421
- // Check for manifest in this directory
1422
- const manifest = await this.loadSkillManifest(dir);
1423
- if (this.hasNativeSkillManifest(manifest)) return true;
746
+ // Check for SKILL.md in this directory
747
+ if (entries.some((e) => !e.isDirectory() && e.name === 'SKILL.md')) return true;
1424
748
 
1425
749
  // Recurse into subdirectories
1426
750
  for (const entry of entries) {
1427
751
  if (!entry.isDirectory()) continue;
1428
752
  if (entry.name.startsWith('.') || entry.name.startsWith('_')) continue;
1429
- if (await this._hasSkillManifestRecursive(path.join(dir, entry.name))) return true;
753
+ if (await this._hasSkillMdRecursive(path.join(dir, entry.name))) return true;
1430
754
  }
1431
755
 
1432
756
  return false;
@@ -1,15 +0,0 @@
1
- type: skill
2
- module: core
3
- capabilities:
4
- - name: bmad-distillator
5
- menu-code: DSTL
6
- description: "Produces lossless LLM-optimized distillate from source documents. Use after producing large human presentable documents that will be consumed later by LLMs"
7
- supports-headless: true
8
- input: source documents
9
- args: output, validate
10
- output: single distillate or folder of distillates next to source input
11
- config-vars-used: null
12
- phase: anytime
13
- before: []
14
- after: []
15
- is-required: false
@@ -1 +0,0 @@
1
- type: skill
@@ -1 +0,0 @@
1
- type: skill
@@ -1 +0,0 @@
1
- type: skill