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
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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'
|
|
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
|
|