Files
BMAD-METHOD/tools/cli/installers/lib/ide/workflow-command-generator.js

163 lines
5.2 KiB
JavaScript

const path = require('node:path');
const fs = require('fs-extra');
const csv = require('csv-parse/sync');
const chalk = require('chalk');
/**
* Generates Claude Code command files for each workflow in the manifest
*/
class WorkflowCommandGenerator {
constructor() {
this.templatePath = path.join(__dirname, 'workflow-command-template.md');
}
/**
* Generate workflow commands from the manifest CSV
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
*/
async generateWorkflowCommands(projectDir, bmadDir) {
const manifestPath = path.join(bmadDir, '_cfg', 'workflow-manifest.csv');
if (!(await fs.pathExists(manifestPath))) {
console.log(chalk.yellow('Workflow manifest not found. Skipping command generation.'));
return { generated: 0 };
}
// Read and parse the CSV manifest
const csvContent = await fs.readFile(manifestPath, 'utf8');
const workflows = csv.parse(csvContent, {
columns: true,
skip_empty_lines: true,
});
// Base commands directory
const baseCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
let generatedCount = 0;
// Generate a command file for each workflow, organized by module
for (const workflow of workflows) {
// Create module directory structure: commands/bmad/{module}/workflows/
const moduleWorkflowsDir = path.join(baseCommandsDir, workflow.module, 'workflows');
await fs.ensureDir(moduleWorkflowsDir);
// Use just the workflow name as filename (no prefix)
const commandContent = await this.generateCommandContent(workflow, bmadDir);
const commandPath = path.join(moduleWorkflowsDir, `${workflow.name}.md`);
await fs.writeFile(commandPath, commandContent);
generatedCount++;
}
// Also create a workflow launcher README in each module
await this.createModuleWorkflowLaunchers(baseCommandsDir, workflows, bmadDir);
return { generated: generatedCount };
}
/**
* Generate command content for a workflow
*/
async generateCommandContent(workflow, bmadDir) {
// Load the template
const template = await fs.readFile(this.templatePath, 'utf8');
// Convert source path to installed path
// From: /Users/.../src/modules/bmm/workflows/.../workflow.yaml
// To: {project-root}/bmad/bmm/workflows/.../workflow.yaml
let workflowPath = workflow.path;
// Extract the relative path from source
if (workflowPath.includes('/src/modules/')) {
const match = workflowPath.match(/\/src\/modules\/(.+)/);
if (match) {
workflowPath = `{project-root}/bmad/${match[1]}`;
}
} else if (workflowPath.includes('/src/core/')) {
const match = workflowPath.match(/\/src\/core\/(.+)/);
if (match) {
workflowPath = `{project-root}/bmad/core/${match[1]}`;
}
}
// Replace template variables
return template
.replaceAll('{{name}}', workflow.name)
.replaceAll('{{module}}', workflow.module)
.replaceAll('{{description}}', workflow.description)
.replaceAll('{{workflow_path}}', workflowPath)
.replaceAll('{{interactive}}', workflow.interactive)
.replaceAll('{{author}}', workflow.author || 'BMAD');
}
/**
* Create workflow launcher files for each module
*/
async createModuleWorkflowLaunchers(baseCommandsDir, workflows, bmadDir) {
// Group workflows by module
const workflowsByModule = {};
for (const workflow of workflows) {
if (!workflowsByModule[workflow.module]) {
workflowsByModule[workflow.module] = [];
}
// Convert path for display
let workflowPath = workflow.path;
if (workflowPath.includes('/src/modules/')) {
const match = workflowPath.match(/\/src\/modules\/(.+)/);
if (match) {
workflowPath = `{project-root}/bmad/${match[1]}`;
}
} else if (workflowPath.includes('/src/core/')) {
const match = workflowPath.match(/\/src\/core\/(.+)/);
if (match) {
workflowPath = `{project-root}/bmad/core/${match[1]}`;
}
}
workflowsByModule[workflow.module].push({
...workflow,
displayPath: workflowPath,
});
}
// Create a launcher file for each module
for (const [module, moduleWorkflows] of Object.entries(workflowsByModule)) {
let content = `# ${module.toUpperCase()} Workflows
## Available Workflows in ${module}
`;
for (const workflow of moduleWorkflows) {
content += `**${workflow.name}**\n`;
content += `- Path: \`${workflow.displayPath}\`\n`;
content += `- ${workflow.description}\n\n`;
}
content += `
## Execution
When running any workflow:
1. LOAD {project-root}/bmad/core/tasks/workflow.xml
2. Pass the workflow path as 'workflow-config' parameter
3. Follow workflow.xml instructions EXACTLY
4. Save outputs after EACH section
## Modes
- Normal: Full interaction
- #yolo: Skip optional steps
`;
// Write module-specific launcher
const moduleWorkflowsDir = path.join(baseCommandsDir, module, 'workflows');
await fs.ensureDir(moduleWorkflowsDir);
const launcherPath = path.join(moduleWorkflowsDir, 'README.md');
await fs.writeFile(launcherPath, content);
}
}
}
module.exports = { WorkflowCommandGenerator };