bmad-method 6.2.1-next.26 → 6.2.1-next.28

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/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.26",
4
+ "version": "6.2.1-next.28",
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 = [];
@@ -85,10 +82,6 @@ class ManifestGenerator {
85
82
  this.modules = allModules;
86
83
  this.updatedModules = allModules; // Include ALL modules (including custom) for scanning
87
84
 
88
- // For CSV manifests, we need to include ALL modules that are installed
89
- // preservedModules controls which modules stay as-is in the CSV (don't get rescanned)
90
- // But all modules should be included in the final manifest
91
- this.preservedModules = allModules; // Include ALL modules (including custom)
92
85
  this.bmadDir = bmadDir;
93
86
  this.bmadFolderName = path.basename(bmadDir); // Get the actual folder name (e.g., '_bmad' or 'bmad')
94
87
  this.allInstalledFiles = installedFiles;
@@ -111,35 +104,20 @@ class ManifestGenerator {
111
104
  // Collect skills first (populates skillClaimedDirs before legacy collectors run)
112
105
  await this.collectSkills();
113
106
 
114
- // Collect workflow data
115
- await this.collectWorkflows(selectedModules);
116
-
117
107
  // Collect agent data - use updatedModules which includes all installed modules
118
108
  await this.collectAgents(this.updatedModules);
119
109
 
120
- // Collect task data
121
- await this.collectTasks(this.updatedModules);
122
-
123
- // Collect tool data
124
- await this.collectTools(this.updatedModules);
125
-
126
110
  // Write manifest files and collect their paths
127
111
  const manifestFiles = [
128
112
  await this.writeMainManifest(cfgDir),
129
- await this.writeWorkflowManifest(cfgDir),
130
113
  await this.writeSkillManifest(cfgDir),
131
114
  await this.writeAgentManifest(cfgDir),
132
- await this.writeTaskManifest(cfgDir),
133
- await this.writeToolManifest(cfgDir),
134
115
  await this.writeFilesManifest(cfgDir),
135
116
  ];
136
117
 
137
118
  return {
138
119
  skills: this.skills.length,
139
- workflows: this.workflows.length,
140
120
  agents: this.agents.length,
141
- tasks: this.tasks.length,
142
- tools: this.tools.length,
143
121
  files: this.files.length,
144
122
  manifestFiles: manifestFiles,
145
123
  };
@@ -289,153 +267,6 @@ class ManifestGenerator {
289
267
  }
290
268
  }
291
269
 
292
- /**
293
- * Collect all workflows from core and selected modules
294
- * Scans the INSTALLED bmad directory, not the source
295
- */
296
- async collectWorkflows(selectedModules) {
297
- this.workflows = [];
298
-
299
- // Use updatedModules which already includes deduplicated 'core' + selectedModules
300
- for (const moduleName of this.updatedModules) {
301
- const modulePath = path.join(this.bmadDir, moduleName);
302
-
303
- if (await fs.pathExists(modulePath)) {
304
- const moduleWorkflows = await this.getWorkflowsFromPath(modulePath, moduleName);
305
- this.workflows.push(...moduleWorkflows);
306
-
307
- // Also scan tasks/ for type:skill entries (skills can live anywhere)
308
- const tasksSkills = await this.getWorkflowsFromPath(modulePath, moduleName, 'tasks');
309
- this.workflows.push(...tasksSkills);
310
- }
311
- }
312
- }
313
-
314
- /**
315
- * Recursively find and parse workflow.md files
316
- */
317
- async getWorkflowsFromPath(basePath, moduleName, subDir = 'workflows') {
318
- const workflows = [];
319
- const workflowsPath = path.join(basePath, subDir);
320
- const debug = process.env.BMAD_DEBUG_MANIFEST === 'true';
321
-
322
- if (debug) {
323
- console.log(`[DEBUG] Scanning workflows in: ${workflowsPath}`);
324
- }
325
-
326
- if (!(await fs.pathExists(workflowsPath))) {
327
- if (debug) {
328
- console.log(`[DEBUG] Workflows path does not exist: ${workflowsPath}`);
329
- }
330
- return workflows;
331
- }
332
-
333
- // Recursively find workflow.md files
334
- const findWorkflows = async (dir, relativePath = '') => {
335
- // Skip directories already claimed as skills
336
- if (this.skillClaimedDirs && this.skillClaimedDirs.has(dir)) return;
337
-
338
- const entries = await fs.readdir(dir, { withFileTypes: true });
339
- // Load skill manifest for this directory (if present)
340
- const skillManifest = await this.loadSkillManifest(dir);
341
-
342
- for (const entry of entries) {
343
- const fullPath = path.join(dir, entry.name);
344
-
345
- if (entry.isDirectory()) {
346
- // Skip directories claimed by collectSkills
347
- if (this.skillClaimedDirs && this.skillClaimedDirs.has(fullPath)) continue;
348
- // Recurse into subdirectories
349
- const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
350
- await findWorkflows(fullPath, newRelativePath);
351
- } else if (entry.name === 'workflow.md' || (entry.name.startsWith('workflow-') && entry.name.endsWith('.md'))) {
352
- // Parse workflow file (both YAML and MD formats)
353
- if (debug) {
354
- console.log(`[DEBUG] Found workflow file: ${fullPath}`);
355
- }
356
- try {
357
- // Read and normalize line endings (fix Windows CRLF issues)
358
- const rawContent = await fs.readFile(fullPath, 'utf8');
359
- const content = rawContent.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
360
-
361
- // Parse MD workflow with YAML frontmatter
362
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
363
- if (!frontmatterMatch) {
364
- if (debug) {
365
- console.log(`[DEBUG] Skipped (no frontmatter): ${fullPath}`);
366
- }
367
- continue; // Skip MD files without frontmatter
368
- }
369
- const workflow = yaml.parse(frontmatterMatch[1]);
370
-
371
- if (debug) {
372
- console.log(`[DEBUG] Parsed: name="${workflow.name}", description=${workflow.description ? 'OK' : 'MISSING'}`);
373
- }
374
-
375
- // Skip template workflows (those with placeholder values)
376
- if (workflow.name && workflow.name.includes('{') && workflow.name.includes('}')) {
377
- if (debug) {
378
- console.log(`[DEBUG] Skipped (template placeholder): ${workflow.name}`);
379
- }
380
- continue;
381
- }
382
-
383
- // Skip workflows marked as non-standalone (reference/example workflows)
384
- if (workflow.standalone === false) {
385
- if (debug) {
386
- console.log(`[DEBUG] Skipped (standalone=false): ${workflow.name}`);
387
- }
388
- continue;
389
- }
390
-
391
- if (workflow.name && workflow.description) {
392
- // Build relative path for installation
393
- const installPath =
394
- moduleName === 'core'
395
- ? `${this.bmadFolderName}/core/${subDir}/${relativePath}/${entry.name}`
396
- : `${this.bmadFolderName}/${moduleName}/${subDir}/${relativePath}/${entry.name}`;
397
-
398
- // Workflows with standalone: false are filtered out above
399
- workflows.push({
400
- name: workflow.name,
401
- description: this.cleanForCSV(workflow.description),
402
- module: moduleName,
403
- path: installPath,
404
- canonicalId: this.getCanonicalId(skillManifest, entry.name),
405
- });
406
-
407
- // Add to files list
408
- this.files.push({
409
- type: 'workflow',
410
- name: workflow.name,
411
- module: moduleName,
412
- path: installPath,
413
- });
414
-
415
- if (debug) {
416
- console.log(`[DEBUG] ✓ Added workflow: ${workflow.name} (${moduleName})`);
417
- }
418
- } else {
419
- if (debug) {
420
- console.log(`[DEBUG] Skipped (missing name or description): ${fullPath}`);
421
- }
422
- }
423
- } catch (error) {
424
- await prompts.log.warn(`Failed to parse workflow at ${fullPath}: ${error.message}`);
425
- }
426
- }
427
- }
428
- };
429
-
430
- await findWorkflows(workflowsPath);
431
-
432
- if (debug) {
433
- console.log(`[DEBUG] Total workflows found in ${moduleName}: ${workflows.length}`);
434
- }
435
-
436
- return workflows;
437
- }
438
-
439
270
  /**
440
271
  * Collect all agents from core and selected modules
441
272
  * Scans the INSTALLED bmad directory, not the source
@@ -589,212 +420,6 @@ class ManifestGenerator {
589
420
  return agents;
590
421
  }
591
422
 
592
- /**
593
- * Collect all tasks from core and selected modules
594
- * Scans the INSTALLED bmad directory, not the source
595
- */
596
- async collectTasks(selectedModules) {
597
- this.tasks = [];
598
-
599
- // Use updatedModules which already includes deduplicated 'core' + selectedModules
600
- for (const moduleName of this.updatedModules) {
601
- const tasksPath = path.join(this.bmadDir, moduleName, 'tasks');
602
-
603
- if (await fs.pathExists(tasksPath)) {
604
- const moduleTasks = await this.getTasksFromDir(tasksPath, moduleName);
605
- this.tasks.push(...moduleTasks);
606
- }
607
- }
608
- }
609
-
610
- /**
611
- * Get tasks from a directory
612
- */
613
- async getTasksFromDir(dirPath, moduleName) {
614
- // Skip directories claimed by collectSkills
615
- if (this.skillClaimedDirs && this.skillClaimedDirs.has(dirPath)) return [];
616
- const tasks = [];
617
- const files = await fs.readdir(dirPath);
618
- // Load skill manifest for this directory (if present)
619
- const skillManifest = await this.loadSkillManifest(dirPath);
620
-
621
- for (const file of files) {
622
- // Check for both .xml and .md files
623
- if (file.endsWith('.xml') || file.endsWith('.md')) {
624
- const filePath = path.join(dirPath, file);
625
- const content = await fs.readFile(filePath, 'utf8');
626
-
627
- // Skip internal/engine files (not user-facing tasks)
628
- if (content.includes('internal="true"')) {
629
- continue;
630
- }
631
-
632
- let name = file.replace(/\.(xml|md)$/, '');
633
- let displayName = name;
634
- let description = '';
635
- let standalone = false;
636
-
637
- if (file.endsWith('.md')) {
638
- // Parse YAML frontmatter for .md tasks
639
- const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
640
- if (frontmatterMatch) {
641
- try {
642
- const frontmatter = yaml.parse(frontmatterMatch[1]);
643
- name = frontmatter.name || name;
644
- displayName = frontmatter.displayName || frontmatter.name || name;
645
- description = this.cleanForCSV(frontmatter.description || '');
646
- // Tasks are standalone by default unless explicitly false (internal=true is already filtered above)
647
- standalone = frontmatter.standalone !== false && frontmatter.standalone !== 'false';
648
- } catch {
649
- // If YAML parsing fails, use defaults
650
- standalone = true; // Default to standalone
651
- }
652
- } else {
653
- standalone = true; // No frontmatter means standalone
654
- }
655
- } else {
656
- // For .xml tasks, extract from tag attributes
657
- const nameMatch = content.match(/name="([^"]+)"/);
658
- displayName = nameMatch ? nameMatch[1] : name;
659
-
660
- const descMatch = content.match(/description="([^"]+)"/);
661
- const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
662
- description = this.cleanForCSV(descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '');
663
-
664
- const standaloneFalseMatch = content.match(/<task[^>]+standalone="false"/);
665
- standalone = !standaloneFalseMatch;
666
- }
667
-
668
- // Build relative path for installation
669
- const installPath =
670
- moduleName === 'core' ? `${this.bmadFolderName}/core/tasks/${file}` : `${this.bmadFolderName}/${moduleName}/tasks/${file}`;
671
-
672
- tasks.push({
673
- name: name,
674
- displayName: displayName,
675
- description: description,
676
- module: moduleName,
677
- path: installPath,
678
- standalone: standalone,
679
- canonicalId: this.getCanonicalId(skillManifest, file),
680
- });
681
-
682
- // Add to files list
683
- this.files.push({
684
- type: 'task',
685
- name: name,
686
- module: moduleName,
687
- path: installPath,
688
- });
689
- }
690
- }
691
-
692
- return tasks;
693
- }
694
-
695
- /**
696
- * Collect all tools from core and selected modules
697
- * Scans the INSTALLED bmad directory, not the source
698
- */
699
- async collectTools(selectedModules) {
700
- this.tools = [];
701
-
702
- // Use updatedModules which already includes deduplicated 'core' + selectedModules
703
- for (const moduleName of this.updatedModules) {
704
- const toolsPath = path.join(this.bmadDir, moduleName, 'tools');
705
-
706
- if (await fs.pathExists(toolsPath)) {
707
- const moduleTools = await this.getToolsFromDir(toolsPath, moduleName);
708
- this.tools.push(...moduleTools);
709
- }
710
- }
711
- }
712
-
713
- /**
714
- * Get tools from a directory
715
- */
716
- async getToolsFromDir(dirPath, moduleName) {
717
- // Skip directories claimed by collectSkills
718
- if (this.skillClaimedDirs && this.skillClaimedDirs.has(dirPath)) return [];
719
- const tools = [];
720
- const files = await fs.readdir(dirPath);
721
- // Load skill manifest for this directory (if present)
722
- const skillManifest = await this.loadSkillManifest(dirPath);
723
-
724
- for (const file of files) {
725
- // Check for both .xml and .md files
726
- if (file.endsWith('.xml') || file.endsWith('.md')) {
727
- const filePath = path.join(dirPath, file);
728
- const content = await fs.readFile(filePath, 'utf8');
729
-
730
- // Skip internal tools (same as tasks)
731
- if (content.includes('internal="true"')) {
732
- continue;
733
- }
734
-
735
- let name = file.replace(/\.(xml|md)$/, '');
736
- let displayName = name;
737
- let description = '';
738
- let standalone = false;
739
-
740
- if (file.endsWith('.md')) {
741
- // Parse YAML frontmatter for .md tools
742
- const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
743
- if (frontmatterMatch) {
744
- try {
745
- const frontmatter = yaml.parse(frontmatterMatch[1]);
746
- name = frontmatter.name || name;
747
- displayName = frontmatter.displayName || frontmatter.name || name;
748
- description = this.cleanForCSV(frontmatter.description || '');
749
- // Tools are standalone by default unless explicitly false (internal=true is already filtered above)
750
- standalone = frontmatter.standalone !== false && frontmatter.standalone !== 'false';
751
- } catch {
752
- // If YAML parsing fails, use defaults
753
- standalone = true; // Default to standalone
754
- }
755
- } else {
756
- standalone = true; // No frontmatter means standalone
757
- }
758
- } else {
759
- // For .xml tools, extract from tag attributes
760
- const nameMatch = content.match(/name="([^"]+)"/);
761
- displayName = nameMatch ? nameMatch[1] : name;
762
-
763
- const descMatch = content.match(/description="([^"]+)"/);
764
- const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
765
- description = this.cleanForCSV(descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '');
766
-
767
- const standaloneFalseMatch = content.match(/<tool[^>]+standalone="false"/);
768
- standalone = !standaloneFalseMatch;
769
- }
770
-
771
- // Build relative path for installation
772
- const installPath =
773
- moduleName === 'core' ? `${this.bmadFolderName}/core/tools/${file}` : `${this.bmadFolderName}/${moduleName}/tools/${file}`;
774
-
775
- tools.push({
776
- name: name,
777
- displayName: displayName,
778
- description: description,
779
- module: moduleName,
780
- path: installPath,
781
- standalone: standalone,
782
- canonicalId: this.getCanonicalId(skillManifest, file),
783
- });
784
-
785
- // Add to files list
786
- this.files.push({
787
- type: 'tool',
788
- name: name,
789
- module: moduleName,
790
- path: installPath,
791
- });
792
- }
793
- }
794
-
795
- return tools;
796
- }
797
-
798
423
  /**
799
424
  * Write main manifest as YAML with installation info only
800
425
  * Fetches fresh version info for all modules
@@ -880,131 +505,6 @@ class ManifestGenerator {
880
505
  return manifestPath;
881
506
  }
882
507
 
883
- /**
884
- * Read existing CSV and preserve rows for modules NOT being updated
885
- * @param {string} csvPath - Path to existing CSV file
886
- * @param {number} moduleColumnIndex - Which column contains the module name (0-indexed)
887
- * @param {Array<string>} expectedColumns - Expected column names in order
888
- * @param {Object} defaultValues - Default values for missing columns
889
- * @returns {Array} Preserved CSV rows (without header), upgraded to match expected columns
890
- */
891
- async getPreservedCsvRows(csvPath, moduleColumnIndex, expectedColumns, defaultValues = {}) {
892
- if (!(await fs.pathExists(csvPath)) || this.preservedModules.length === 0) {
893
- return [];
894
- }
895
-
896
- try {
897
- const content = await fs.readFile(csvPath, 'utf8');
898
- const lines = content.trim().split('\n');
899
-
900
- if (lines.length < 2) {
901
- return []; // No data rows
902
- }
903
-
904
- // Parse header to understand old schema
905
- const header = lines[0];
906
- const headerColumns = header.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
907
- const oldColumns = headerColumns.map((c) => c.replaceAll(/^"|"$/g, ''));
908
-
909
- // Skip header row for data
910
- const dataRows = lines.slice(1);
911
- const preservedRows = [];
912
-
913
- for (const row of dataRows) {
914
- // Simple CSV parsing (handles quoted values)
915
- const columns = row.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
916
- const cleanColumns = columns.map((c) => c.replaceAll(/^"|"$/g, ''));
917
-
918
- const moduleValue = cleanColumns[moduleColumnIndex];
919
-
920
- // Keep this row if it belongs to a preserved module
921
- if (this.preservedModules.includes(moduleValue)) {
922
- // Upgrade row to match expected schema
923
- const upgradedRow = this.upgradeRowToSchema(cleanColumns, oldColumns, expectedColumns, defaultValues);
924
- preservedRows.push(upgradedRow);
925
- }
926
- }
927
-
928
- return preservedRows;
929
- } catch (error) {
930
- await prompts.log.warn(`Failed to read existing CSV ${csvPath}: ${error.message}`);
931
- return [];
932
- }
933
- }
934
-
935
- /**
936
- * Upgrade a CSV row from old schema to new schema
937
- * @param {Array<string>} rowValues - Values from old row
938
- * @param {Array<string>} oldColumns - Old column names
939
- * @param {Array<string>} newColumns - New column names
940
- * @param {Object} defaultValues - Default values for missing columns
941
- * @returns {string} Upgraded CSV row
942
- */
943
- upgradeRowToSchema(rowValues, oldColumns, newColumns, defaultValues) {
944
- const upgradedValues = [];
945
-
946
- for (const newCol of newColumns) {
947
- const oldIndex = oldColumns.indexOf(newCol);
948
-
949
- if (oldIndex !== -1 && oldIndex < rowValues.length) {
950
- // Column exists in old schema, use its value
951
- upgradedValues.push(rowValues[oldIndex]);
952
- } else if (defaultValues[newCol] === undefined) {
953
- // Column missing, no default provided
954
- upgradedValues.push('');
955
- } else {
956
- // Column missing, use default value
957
- upgradedValues.push(defaultValues[newCol]);
958
- }
959
- }
960
-
961
- // Properly quote values and join
962
- return upgradedValues.map((v) => `"${v}"`).join(',');
963
- }
964
-
965
- /**
966
- * Write workflow manifest CSV
967
- * @returns {string} Path to the manifest file
968
- */
969
- async writeWorkflowManifest(cfgDir) {
970
- const csvPath = path.join(cfgDir, 'workflow-manifest.csv');
971
- const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
972
-
973
- // Create CSV header - standalone column removed, canonicalId added as optional column
974
- let csv = 'name,description,module,path,canonicalId\n';
975
-
976
- // Build workflows map from discovered workflows only
977
- // Old entries are NOT preserved - the manifest reflects what actually exists on disk
978
- const allWorkflows = new Map();
979
-
980
- // Only add workflows that were actually discovered in this scan
981
- for (const workflow of this.workflows) {
982
- const key = `${workflow.module}:${workflow.name}`;
983
- allWorkflows.set(key, {
984
- name: workflow.name,
985
- description: workflow.description,
986
- module: workflow.module,
987
- path: workflow.path,
988
- canonicalId: workflow.canonicalId || '',
989
- });
990
- }
991
-
992
- // Write all workflows
993
- for (const [, value] of allWorkflows) {
994
- const row = [
995
- escapeCsv(value.name),
996
- escapeCsv(value.description),
997
- escapeCsv(value.module),
998
- escapeCsv(value.path),
999
- escapeCsv(value.canonicalId),
1000
- ].join(',');
1001
- csv += row + '\n';
1002
- }
1003
-
1004
- await fs.writeFile(csvPath, csv);
1005
- return csvPath;
1006
- }
1007
-
1008
508
  /**
1009
509
  * Write skill manifest CSV
1010
510
  * @returns {string} Path to the manifest file
@@ -1105,134 +605,6 @@ class ManifestGenerator {
1105
605
  return csvPath;
1106
606
  }
1107
607
 
1108
- /**
1109
- * Write task manifest CSV
1110
- * @returns {string} Path to the manifest file
1111
- */
1112
- async writeTaskManifest(cfgDir) {
1113
- const csvPath = path.join(cfgDir, 'task-manifest.csv');
1114
- const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
1115
-
1116
- // Read existing manifest to preserve entries
1117
- const existingEntries = new Map();
1118
- if (await fs.pathExists(csvPath)) {
1119
- const content = await fs.readFile(csvPath, 'utf8');
1120
- const records = csv.parse(content, {
1121
- columns: true,
1122
- skip_empty_lines: true,
1123
- });
1124
- for (const record of records) {
1125
- existingEntries.set(`${record.module}:${record.name}`, record);
1126
- }
1127
- }
1128
-
1129
- // Create CSV header with standalone and canonicalId columns
1130
- let csvContent = 'name,displayName,description,module,path,standalone,canonicalId\n';
1131
-
1132
- // Combine existing and new tasks
1133
- const allTasks = new Map();
1134
-
1135
- // Add existing entries
1136
- for (const [key, value] of existingEntries) {
1137
- allTasks.set(key, value);
1138
- }
1139
-
1140
- // Add/update new tasks
1141
- for (const task of this.tasks) {
1142
- const key = `${task.module}:${task.name}`;
1143
- allTasks.set(key, {
1144
- name: task.name,
1145
- displayName: task.displayName,
1146
- description: task.description,
1147
- module: task.module,
1148
- path: task.path,
1149
- standalone: task.standalone,
1150
- canonicalId: task.canonicalId || '',
1151
- });
1152
- }
1153
-
1154
- // Write all tasks
1155
- for (const [, record] of allTasks) {
1156
- const row = [
1157
- escapeCsv(record.name),
1158
- escapeCsv(record.displayName),
1159
- escapeCsv(record.description),
1160
- escapeCsv(record.module),
1161
- escapeCsv(record.path),
1162
- escapeCsv(record.standalone),
1163
- escapeCsv(record.canonicalId),
1164
- ].join(',');
1165
- csvContent += row + '\n';
1166
- }
1167
-
1168
- await fs.writeFile(csvPath, csvContent);
1169
- return csvPath;
1170
- }
1171
-
1172
- /**
1173
- * Write tool manifest CSV
1174
- * @returns {string} Path to the manifest file
1175
- */
1176
- async writeToolManifest(cfgDir) {
1177
- const csvPath = path.join(cfgDir, 'tool-manifest.csv');
1178
- const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
1179
-
1180
- // Read existing manifest to preserve entries
1181
- const existingEntries = new Map();
1182
- if (await fs.pathExists(csvPath)) {
1183
- const content = await fs.readFile(csvPath, 'utf8');
1184
- const records = csv.parse(content, {
1185
- columns: true,
1186
- skip_empty_lines: true,
1187
- });
1188
- for (const record of records) {
1189
- existingEntries.set(`${record.module}:${record.name}`, record);
1190
- }
1191
- }
1192
-
1193
- // Create CSV header with standalone and canonicalId columns
1194
- let csvContent = 'name,displayName,description,module,path,standalone,canonicalId\n';
1195
-
1196
- // Combine existing and new tools
1197
- const allTools = new Map();
1198
-
1199
- // Add existing entries
1200
- for (const [key, value] of existingEntries) {
1201
- allTools.set(key, value);
1202
- }
1203
-
1204
- // Add/update new tools
1205
- for (const tool of this.tools) {
1206
- const key = `${tool.module}:${tool.name}`;
1207
- allTools.set(key, {
1208
- name: tool.name,
1209
- displayName: tool.displayName,
1210
- description: tool.description,
1211
- module: tool.module,
1212
- path: tool.path,
1213
- standalone: tool.standalone,
1214
- canonicalId: tool.canonicalId || '',
1215
- });
1216
- }
1217
-
1218
- // Write all tools
1219
- for (const [, record] of allTools) {
1220
- const row = [
1221
- escapeCsv(record.name),
1222
- escapeCsv(record.displayName),
1223
- escapeCsv(record.description),
1224
- escapeCsv(record.module),
1225
- escapeCsv(record.path),
1226
- escapeCsv(record.standalone),
1227
- escapeCsv(record.canonicalId),
1228
- ].join(',');
1229
- csvContent += row + '\n';
1230
- }
1231
-
1232
- await fs.writeFile(csvPath, csvContent);
1233
- return csvPath;
1234
- }
1235
-
1236
608
  /**
1237
609
  * Write files manifest CSV
1238
610
  */
@@ -1332,21 +704,12 @@ class ManifestGenerator {
1332
704
  continue;
1333
705
  }
1334
706
 
1335
- // Check if this looks like a module (has agents, workflows, or tasks directory)
707
+ // Check if this looks like a module (has agents directory or skill manifests)
1336
708
  const modulePath = path.join(bmadDir, entry.name);
1337
709
  const hasAgents = await fs.pathExists(path.join(modulePath, 'agents'));
1338
- const hasWorkflows = await fs.pathExists(path.join(modulePath, 'workflows'));
1339
- const hasTasks = await fs.pathExists(path.join(modulePath, 'tasks'));
1340
- const hasTools = await fs.pathExists(path.join(modulePath, 'tools'));
1341
-
1342
- // Check for native-entrypoint-only modules: recursive scan for SKILL.md
1343
- let hasSkills = false;
1344
- if (!hasAgents && !hasWorkflows && !hasTasks && !hasTools) {
1345
- hasSkills = await this._hasSkillMdRecursive(modulePath);
1346
- }
710
+ const hasSkills = await this._hasSkillMdRecursive(modulePath);
1347
711
 
1348
- // If it has any of these directories or skill manifests, it's likely a module
1349
- if (hasAgents || hasWorkflows || hasTasks || hasTools || hasSkills) {
712
+ if (hasAgents || hasSkills) {
1350
713
  modules.push(entry.name);
1351
714
  }
1352
715
  }
@@ -27,7 +27,7 @@ async function loadSkillManifest(dirPath) {
27
27
  /**
28
28
  * Get the canonicalId for a specific file from a loaded skill manifest.
29
29
  * @param {Object|null} manifest - Loaded manifest (from loadSkillManifest)
30
- * @param {string} filename - Source filename to look up (e.g., 'pm.md', 'help.md', 'pm.agent.yaml')
30
+ * @param {string} filename - Source filename to look up (e.g., 'pm.md', 'help.md')
31
31
  * @returns {string} canonicalId or empty string
32
32
  */
33
33
  function getCanonicalId(manifest, filename) {
@@ -36,12 +36,6 @@ function getCanonicalId(manifest, filename) {
36
36
  if (manifest.__single) return manifest.__single.canonicalId || '';
37
37
  // Multi-entry: look up by filename directly
38
38
  if (manifest[filename]) return manifest[filename].canonicalId || '';
39
- // Fallback: try alternate extensions for compiled files
40
- const baseName = filename.replace(/\.(md|xml)$/i, '');
41
- const agentKey = `${baseName}.agent.yaml`;
42
- if (manifest[agentKey]) return manifest[agentKey].canonicalId || '';
43
- const xmlKey = `${baseName}.xml`;
44
- if (manifest[xmlKey]) return manifest[xmlKey].canonicalId || '';
45
39
  return '';
46
40
  }
47
41
 
@@ -57,12 +51,6 @@ function getArtifactType(manifest, filename) {
57
51
  if (manifest.__single) return manifest.__single.type || null;
58
52
  // Multi-entry: look up by filename directly
59
53
  if (manifest[filename]) return manifest[filename].type || null;
60
- // Fallback: try alternate extensions for compiled files
61
- const baseName = filename.replace(/\.(md|xml)$/i, '');
62
- const agentKey = `${baseName}.agent.yaml`;
63
- if (manifest[agentKey]) return manifest[agentKey].type || null;
64
- const xmlKey = `${baseName}.xml`;
65
- if (manifest[xmlKey]) return manifest[xmlKey].type || null;
66
54
  return null;
67
55
  }
68
56
 
@@ -78,12 +66,6 @@ function getInstallToBmad(manifest, filename) {
78
66
  if (manifest.__single) return manifest.__single.install_to_bmad !== false;
79
67
  // Multi-entry: look up by filename directly
80
68
  if (manifest[filename]) return manifest[filename].install_to_bmad !== false;
81
- // Fallback: try alternate extensions for compiled files
82
- const baseName = filename.replace(/\.(md|xml)$/i, '');
83
- const agentKey = `${baseName}.agent.yaml`;
84
- if (manifest[agentKey]) return manifest[agentKey].install_to_bmad !== false;
85
- const xmlKey = `${baseName}.xml`;
86
- if (manifest[xmlKey]) return manifest[xmlKey].install_to_bmad !== false;
87
69
  return true;
88
70
  }
89
71