mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-29 16:14:59 +00:00
feat: Extract BMGD module and implement workflow vendoring
This commit extracts game development functionality from BMM into a standalone BMGD (BMad Game Development) module and implements workflow vendoring to enable module independence. BMGD Module Creation: - Moved agents: game-designer, game-dev, game-architect from BMM to BMGD - Moved team config: team-gamedev - Created new Game Dev Scrum Master agent using workflow vendoring pattern - Reorganized workflows into industry-standard game dev phases: * Phase 1 (Preproduction): brainstorm-game, game-brief * Phase 2 (Design): gdd, narrative * Phase 3 (Technical): game-architecture * Phase 4 (Production): vendored from BMM workflows - Updated all module metadata and config_source references Workflow Vendoring Feature: - Enables modules to copy workflows from other modules during installation - Build-time process that updates config_source in vendored workflows - New agent YAML attribute: workflow-install (build-time metadata) - Final compiled agents use workflow-install value for workflow attribute - Implementation in module manager: vendorCrossModuleWorkflows() - Allows standalone module installation without forced dependencies Technical Changes: - tools/cli/lib/yaml-xml-builder.js: Use workflow-install for workflow attribute - tools/cli/installers/lib/modules/manager.js: Add vendoring functions - tools/schema/agent.js: Add workflow-install to menu item schema - Updated 3 documentation files with workflow vendoring details BMM Workflow Updates: - workflow-status/init: Added game detection checkpoint - workflow-status/paths/game-design.yaml: Redirect to BMGD module - prd/instructions.md: Route game projects to BMGD - research/instructions-market.md: Reference BMGD for game development Documentation: - Created comprehensive BMGD module README - Added workflow vendoring documentation - Updated BMB agent creation and module creation guides
This commit is contained in:
@@ -112,6 +112,10 @@ class ModuleManager {
|
||||
await fs.remove(targetPath);
|
||||
}
|
||||
|
||||
// Vendor cross-module workflows BEFORE copying
|
||||
// This reads source agent.yaml files and copies referenced workflows
|
||||
await this.vendorCrossModuleWorkflows(sourcePath, targetPath, moduleName);
|
||||
|
||||
// Copy module files with filtering
|
||||
await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig);
|
||||
|
||||
@@ -435,6 +439,130 @@ class ModuleManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor cross-module workflows referenced in agent files
|
||||
* Scans SOURCE agent.yaml files for workflow-install and copies workflows to destination
|
||||
* @param {string} sourcePath - Source module path
|
||||
* @param {string} targetPath - Target module path (destination)
|
||||
* @param {string} moduleName - Module name being installed
|
||||
*/
|
||||
async vendorCrossModuleWorkflows(sourcePath, targetPath, moduleName) {
|
||||
const sourceAgentsPath = path.join(sourcePath, 'agents');
|
||||
|
||||
// Check if source agents directory exists
|
||||
if (!(await fs.pathExists(sourceAgentsPath))) {
|
||||
return; // No agents to process
|
||||
}
|
||||
|
||||
// Get all agent YAML files from source
|
||||
const agentFiles = await fs.readdir(sourceAgentsPath);
|
||||
const yamlFiles = agentFiles.filter((f) => f.endsWith('.agent.yaml') || f.endsWith('.yaml'));
|
||||
|
||||
if (yamlFiles.length === 0) {
|
||||
return; // No YAML agent files
|
||||
}
|
||||
|
||||
let workflowsVendored = false;
|
||||
|
||||
for (const agentFile of yamlFiles) {
|
||||
const agentPath = path.join(sourceAgentsPath, agentFile);
|
||||
const agentYaml = yaml.load(await fs.readFile(agentPath, 'utf8'));
|
||||
|
||||
// Check if agent has menu items with workflow-install
|
||||
const menuItems = agentYaml?.agent?.menu || [];
|
||||
const workflowInstallItems = menuItems.filter((item) => item['workflow-install']);
|
||||
|
||||
if (workflowInstallItems.length === 0) {
|
||||
continue; // No workflow-install in this agent
|
||||
}
|
||||
|
||||
if (!workflowsVendored) {
|
||||
console.log(chalk.cyan(`\n Vendoring cross-module workflows for ${moduleName}...`));
|
||||
workflowsVendored = true;
|
||||
}
|
||||
|
||||
console.log(chalk.dim(` Processing: ${agentFile}`));
|
||||
|
||||
for (const item of workflowInstallItems) {
|
||||
const sourceWorkflowPath = item.workflow; // Where to copy FROM
|
||||
const installWorkflowPath = item['workflow-install']; // Where to copy TO
|
||||
|
||||
// Parse SOURCE workflow path
|
||||
// Example: {project-root}/bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
|
||||
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/bmad\/([^/]+)\/workflows\/(.+)/);
|
||||
if (!sourceMatch) {
|
||||
console.warn(chalk.yellow(` Could not parse workflow path: ${sourceWorkflowPath}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
const [, sourceModule, sourceWorkflowSubPath] = sourceMatch;
|
||||
|
||||
// Parse INSTALL workflow path
|
||||
// Example: {project-root}/bmad/bmgd/workflows/4-production/create-story/workflow.yaml
|
||||
const installMatch = installWorkflowPath.match(/\{project-root\}\/bmad\/([^/]+)\/workflows\/(.+)/);
|
||||
if (!installMatch) {
|
||||
console.warn(chalk.yellow(` Could not parse workflow-install path: ${installWorkflowPath}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
const installWorkflowSubPath = installMatch[2];
|
||||
|
||||
// Determine actual filesystem paths
|
||||
const sourceModulePath = path.join(this.modulesSourcePath, sourceModule);
|
||||
const actualSourceWorkflowPath = path.join(sourceModulePath, 'workflows', sourceWorkflowSubPath.replace(/\/workflow\.yaml$/, ''));
|
||||
|
||||
const actualDestWorkflowPath = path.join(targetPath, 'workflows', installWorkflowSubPath.replace(/\/workflow\.yaml$/, ''));
|
||||
|
||||
// Check if source workflow exists
|
||||
if (!(await fs.pathExists(actualSourceWorkflowPath))) {
|
||||
console.warn(chalk.yellow(` Source workflow not found: ${actualSourceWorkflowPath}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Copy the entire workflow folder
|
||||
console.log(
|
||||
chalk.dim(
|
||||
` Vendoring: ${sourceModule}/workflows/${sourceWorkflowSubPath.replace(/\/workflow\.yaml$/, '')} → ${moduleName}/workflows/${installWorkflowSubPath.replace(/\/workflow\.yaml$/, '')}`,
|
||||
),
|
||||
);
|
||||
|
||||
await fs.ensureDir(path.dirname(actualDestWorkflowPath));
|
||||
await fs.copy(actualSourceWorkflowPath, actualDestWorkflowPath, { overwrite: true });
|
||||
|
||||
// Update the workflow.yaml config_source reference
|
||||
const workflowYamlPath = path.join(actualDestWorkflowPath, 'workflow.yaml');
|
||||
if (await fs.pathExists(workflowYamlPath)) {
|
||||
await this.updateWorkflowConfigSource(workflowYamlPath, moduleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (workflowsVendored) {
|
||||
console.log(chalk.green(` ✓ Workflow vendoring complete\n`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update workflow.yaml config_source to point to new module
|
||||
* @param {string} workflowYamlPath - Path to workflow.yaml file
|
||||
* @param {string} newModuleName - New module name to reference
|
||||
*/
|
||||
async updateWorkflowConfigSource(workflowYamlPath, newModuleName) {
|
||||
let yamlContent = await fs.readFile(workflowYamlPath, 'utf8');
|
||||
|
||||
// Replace config_source: "{project-root}/bmad/OLD_MODULE/config.yaml"
|
||||
// with config_source: "{project-root}/bmad/NEW_MODULE/config.yaml"
|
||||
const configSourcePattern = /config_source:\s*["']?\{project-root\}\/bmad\/[^/]+\/config\.yaml["']?/g;
|
||||
const newConfigSource = `config_source: "{project-root}/bmad/${newModuleName}/config.yaml"`;
|
||||
|
||||
const updatedYaml = yamlContent.replaceAll(configSourcePattern, newConfigSource);
|
||||
|
||||
if (updatedYaml !== yamlContent) {
|
||||
await fs.writeFile(workflowYamlPath, updatedYaml, 'utf8');
|
||||
console.log(chalk.dim(` Updated config_source to: bmad/${newModuleName}/config.yaml`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run module-specific installer if it exists
|
||||
* @param {string} moduleName - Name of the module
|
||||
|
||||
@@ -311,7 +311,15 @@ class YamlXmlBuilder {
|
||||
const attrs = [`cmd="${trigger}"`];
|
||||
|
||||
// Add handler attributes
|
||||
if (item.workflow) attrs.push(`workflow="${item.workflow}"`);
|
||||
// If workflow-install exists, use its value for workflow attribute (vendoring)
|
||||
// workflow-install is build-time metadata - tells installer where to copy workflows
|
||||
// The final XML should only have workflow pointing to the install location
|
||||
if (item['workflow-install']) {
|
||||
attrs.push(`workflow="${item['workflow-install']}"`);
|
||||
} else if (item.workflow) {
|
||||
attrs.push(`workflow="${item.workflow}"`);
|
||||
}
|
||||
|
||||
if (item['validate-workflow']) attrs.push(`validate-workflow="${item['validate-workflow']}"`);
|
||||
if (item.exec) attrs.push(`exec="${item.exec}"`);
|
||||
if (item.tmpl) attrs.push(`tmpl="${item.tmpl}"`);
|
||||
|
||||
@@ -170,6 +170,7 @@ function buildMenuItemSchema() {
|
||||
trigger: createNonEmptyString('agent.menu[].trigger'),
|
||||
description: createNonEmptyString('agent.menu[].description'),
|
||||
workflow: createNonEmptyString('agent.menu[].workflow').optional(),
|
||||
'workflow-install': createNonEmptyString('agent.menu[].workflow-install').optional(),
|
||||
'validate-workflow': createNonEmptyString('agent.menu[].validate-workflow').optional(),
|
||||
exec: createNonEmptyString('agent.menu[].exec').optional(),
|
||||
action: createNonEmptyString('agent.menu[].action').optional(),
|
||||
|
||||
Reference in New Issue
Block a user