feat: Add workflow vendoring to web bundler
The web bundler now performs workflow vendoring before bundling agents, similar to the module installer. This ensures that workflows referenced via workflow-install attributes are copied from their source locations to their destination locations before the bundler attempts to resolve and bundle them. Changes: - Added vendorCrossModuleWorkflows() method to WebBundler class - Added updateWorkflowConfigSource() helper method - Integrated vendoring into bundleAll(), bundleModule(), and bundleAgent() - Workflows are vendored before agent discovery and bundling - Config_source is updated in vendored workflows to reference target module This fixes missing dependency warnings for BMGD agents that vendor BMM workflows for Phase 4 (Production) workflows.
This commit is contained in:
parent
f84e18760f
commit
281eac3373
|
|
@ -51,8 +51,13 @@ class WebBundler {
|
||||||
console.log(chalk.cyan.bold('═══════════════════════════════════════════════\n'));
|
console.log(chalk.cyan.bold('═══════════════════════════════════════════════\n'));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Pre-discover all modules to generate complete manifests
|
// Vendor cross-module workflows FIRST
|
||||||
const modules = await this.discoverModules();
|
const modules = await this.discoverModules();
|
||||||
|
for (const module of modules) {
|
||||||
|
await this.vendorCrossModuleWorkflows(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-discover all modules to generate complete manifests
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
await this.preDiscoverModule(module);
|
await this.preDiscoverModule(module);
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +97,9 @@ class WebBundler {
|
||||||
teams: [],
|
teams: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Vendor cross-module workflows first (if not already done by bundleAll)
|
||||||
|
await this.vendorCrossModuleWorkflows(moduleName);
|
||||||
|
|
||||||
// Pre-discover all agents and teams for manifest generation
|
// Pre-discover all agents and teams for manifest generation
|
||||||
await this.preDiscoverModule(moduleName);
|
await this.preDiscoverModule(moduleName);
|
||||||
|
|
||||||
|
|
@ -134,6 +142,9 @@ class WebBundler {
|
||||||
|
|
||||||
console.log(chalk.dim(` → Processing: ${agentName}`));
|
console.log(chalk.dim(` → Processing: ${agentName}`));
|
||||||
|
|
||||||
|
// Vendor cross-module workflows first (if not already done)
|
||||||
|
await this.vendorCrossModuleWorkflows(moduleName);
|
||||||
|
|
||||||
const agentPath = path.join(this.modulesPath, moduleName, 'agents', agentFile);
|
const agentPath = path.join(this.modulesPath, moduleName, 'agents', agentFile);
|
||||||
|
|
||||||
// Check if agent file exists
|
// Check if agent file exists
|
||||||
|
|
@ -433,6 +444,97 @@ class WebBundler {
|
||||||
return parts.join('\n');
|
return parts.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor cross-module workflows for a module
|
||||||
|
* Scans source agent YAML files for workflow-install attributes and copies workflows
|
||||||
|
*/
|
||||||
|
async vendorCrossModuleWorkflows(moduleName) {
|
||||||
|
const modulePath = path.join(this.modulesPath, moduleName);
|
||||||
|
const agentsPath = path.join(modulePath, 'agents');
|
||||||
|
|
||||||
|
if (!(await fs.pathExists(agentsPath))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all agent YAML files
|
||||||
|
const files = await fs.readdir(agentsPath);
|
||||||
|
const yamlFiles = files.filter((f) => f.endsWith('.agent.yaml'));
|
||||||
|
|
||||||
|
for (const agentFile of yamlFiles) {
|
||||||
|
const agentPath = path.join(agentsPath, agentFile);
|
||||||
|
const agentYaml = yaml.load(await fs.readFile(agentPath, 'utf8'));
|
||||||
|
|
||||||
|
const menuItems = agentYaml?.agent?.menu || [];
|
||||||
|
const workflowInstallItems = menuItems.filter((item) => item['workflow-install']);
|
||||||
|
|
||||||
|
for (const item of workflowInstallItems) {
|
||||||
|
const sourceWorkflowPath = item.workflow;
|
||||||
|
const installWorkflowPath = item['workflow-install'];
|
||||||
|
|
||||||
|
if (!sourceWorkflowPath || !installWorkflowPath) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse paths to extract module and workflow location
|
||||||
|
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/bmad\/([^/]+)\/workflows\/(.+)/);
|
||||||
|
const installMatch = installWorkflowPath.match(/\{project-root\}\/bmad\/([^/]+)\/workflows\/(.+)/);
|
||||||
|
|
||||||
|
if (!sourceMatch || !installMatch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceModule = sourceMatch[1];
|
||||||
|
const sourceWorkflowRelPath = sourceMatch[2];
|
||||||
|
const installModule = installMatch[1];
|
||||||
|
const installWorkflowRelPath = installMatch[2];
|
||||||
|
|
||||||
|
// Build actual filesystem paths
|
||||||
|
const actualSourceWorkflowPath = path.join(this.modulesPath, sourceModule, 'workflows', sourceWorkflowRelPath);
|
||||||
|
const actualDestWorkflowPath = path.join(this.modulesPath, installModule, 'workflows', installWorkflowRelPath);
|
||||||
|
|
||||||
|
// Check if source workflow exists
|
||||||
|
if (!(await fs.pathExists(actualSourceWorkflowPath))) {
|
||||||
|
console.log(chalk.yellow(` ⚠ Source workflow not found for vendoring: ${sourceWorkflowPath}`));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if destination already exists (skip if already vendored)
|
||||||
|
if (await fs.pathExists(actualDestWorkflowPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get workflow directory (workflow.yaml is in a directory with other files)
|
||||||
|
const sourceWorkflowDir = path.dirname(actualSourceWorkflowPath);
|
||||||
|
const destWorkflowDir = path.dirname(actualDestWorkflowPath);
|
||||||
|
|
||||||
|
// Copy entire workflow directory
|
||||||
|
await fs.copy(sourceWorkflowDir, destWorkflowDir, { overwrite: false });
|
||||||
|
|
||||||
|
// Update config_source in the vendored workflow.yaml
|
||||||
|
const workflowYamlPath = actualDestWorkflowPath;
|
||||||
|
if (await fs.pathExists(workflowYamlPath)) {
|
||||||
|
await this.updateWorkflowConfigSource(workflowYamlPath, installModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.dim(` → Vendored workflow: ${sourceWorkflowRelPath} → ${installModule}/workflows/${installWorkflowRelPath}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update config_source in a vendored workflow YAML file
|
||||||
|
*/
|
||||||
|
async updateWorkflowConfigSource(workflowYamlPath, newModuleName) {
|
||||||
|
let yamlContent = await fs.readFile(workflowYamlPath, 'utf8');
|
||||||
|
|
||||||
|
// Replace config_source with new module reference
|
||||||
|
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);
|
||||||
|
await fs.writeFile(workflowYamlPath, updatedYaml, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pre-discover all agents and teams in a module for manifest generation
|
* Pre-discover all agents and teams in a module for manifest generation
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue