fixed _bmad folder stutter with agent custom files

This commit is contained in:
Brian Madison 2025-12-18 03:22:46 +08:00
parent 363915b0c6
commit 23f650ff4d
3 changed files with 32 additions and 3688 deletions

View File

@ -13,12 +13,13 @@ const { XmlHandler } = require('../../../lib/xml-handler');
const { DependencyResolver } = require('./dependency-resolver');
const { ConfigCollector } = require('./config-collector');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { AgentPartyGenerator } = require('../../../lib/agent-party-generator');
const { CLIUtils } = require('../../../lib/cli-utils');
const { ManifestGenerator } = require('./manifest-generator');
const { IdeConfigManager } = require('./ide-config-manager');
const { CustomHandler } = require('../custom/handler');
const { filterCustomizationData } = require('../../../lib/agent/compiler');
// BMAD installation folder name - this is constant and should never change
const BMAD_FOLDER_NAME = '_bmad';
class Installer {
constructor() {
@ -34,60 +35,37 @@ class Installer {
this.ideConfigManager = new IdeConfigManager();
this.installedFiles = new Set(); // Track all installed files
this.ttsInjectedFiles = []; // Track files with TTS injection applied
this.bmadFolderName = BMAD_FOLDER_NAME;
}
/**
* Find the bmad installation directory in a project
* V6+ installations can use ANY folder name but ALWAYS have _config/manifest.yaml
* Always uses the standard _bmad folder name
* Also checks for legacy _cfg folder for migration
* @param {string} projectDir - Project directory
* @returns {Promise<Object>} { bmadDir: string, hasLegacyCfg: boolean }
*/
async findBmadDir(projectDir) {
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
// Check if project directory exists
if (!(await fs.pathExists(projectDir))) {
// Project doesn't exist yet, return default
return { bmadDir: path.join(projectDir, '_bmad'), hasLegacyCfg: false };
return { bmadDir, hasLegacyCfg: false };
}
let bmadDir = null;
// Check for legacy _cfg folder if bmad directory exists
let hasLegacyCfg = false;
try {
const entries = await fs.readdir(projectDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const bmadPath = path.join(projectDir, entry.name);
// Check for current _config folder
const manifestPath = path.join(bmadPath, '_config', 'manifest.yaml');
if (await fs.pathExists(manifestPath)) {
// Found a V6+ installation with current _config folder
return { bmadDir: bmadPath, hasLegacyCfg: false };
}
// Check for legacy _cfg folder
const legacyManifestPath = path.join(bmadPath, '_cfg', 'manifest.yaml');
if (await fs.pathExists(legacyManifestPath)) {
bmadDir = bmadPath;
if (await fs.pathExists(bmadDir)) {
const legacyCfgPath = path.join(bmadDir, '_cfg');
if (await fs.pathExists(legacyCfgPath)) {
hasLegacyCfg = true;
}
}
}
} catch {
console.log(chalk.red('Error reading project directory for BMAD installation detection'));
}
// If we found a bmad directory (with or without legacy _cfg)
if (bmadDir) {
return { bmadDir, hasLegacyCfg };
}
// No V6+ installation found, return default
// This will be used for new installations
return { bmadDir: path.join(projectDir, '_bmad'), hasLegacyCfg: false };
}
/**
* @function copyFileWithPlaceholderReplacement
* @intent Copy files from BMAD source to installation directory with dynamic content transformation
@ -120,7 +98,7 @@ class Installer {
*
* 3. Document marker in instructions.md (if applicable)
*/
async copyFileWithPlaceholderReplacement(sourcePath, targetPath, bmadFolderName) {
async copyFileWithPlaceholderReplacement(sourcePath, targetPath) {
// List of text file extensions that should have placeholder replacement
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv', '.xml'];
const ext = path.extname(sourcePath).toLowerCase();
@ -285,7 +263,7 @@ class Installer {
// Check for already configured IDEs
const { Detector } = require('./detector');
const detector = new Detector();
const bmadDir = path.join(projectDir, this.bmadFolderName || 'bmad');
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
// During full reinstall, use the saved previous IDEs since bmad dir was deleted
// Otherwise detect from existing installation
@ -532,18 +510,14 @@ class Installer {
}
}
// Always use _bmad as the folder name
const bmadFolderName = '_bmad';
this.bmadFolderName = bmadFolderName; // Store for use in other methods
// Store AgentVibes configuration for injection point processing
this.enableAgentVibes = config.enableAgentVibes || false;
// Set bmad folder name on module manager and IDE manager for placeholder replacement
this.moduleManager.setBmadFolderName(bmadFolderName);
this.moduleManager.setBmadFolderName(BMAD_FOLDER_NAME);
this.moduleManager.setCoreConfig(moduleConfigs.core || {});
this.moduleManager.setCustomModulePaths(customModulePaths);
this.ideManager.setBmadFolderName(bmadFolderName);
this.ideManager.setBmadFolderName(BMAD_FOLDER_NAME);
// Tool selection will be collected after we determine if it's a reinstall/update/new install
@ -553,14 +527,8 @@ class Installer {
// Resolve target directory (path.resolve handles platform differences)
const projectDir = path.resolve(config.directory);
let existingBmadDir = null;
let existingBmadFolderName = null;
if (await fs.pathExists(projectDir)) {
const result = await this.findBmadDir(projectDir);
existingBmadDir = result.bmadDir;
existingBmadFolderName = path.basename(existingBmadDir);
}
// Always use the standard _bmad folder name
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
// Create a project directory if it doesn't exist (user already confirmed)
if (!(await fs.pathExists(projectDir))) {
@ -582,8 +550,6 @@ class Installer {
}
}
const bmadDir = path.join(projectDir, bmadFolderName);
// Check existing installation
spinner.text = 'Checking for existing installation...';
const existingInstall = await this.detector.detect(bmadDir);
@ -1606,7 +1572,7 @@ class Installer {
const targetPath = path.join(agentsDir, fileName);
if (await fs.pathExists(sourcePath)) {
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
this.installedFiles.add(targetPath);
}
}
@ -1622,7 +1588,7 @@ class Installer {
const targetPath = path.join(tasksDir, fileName);
if (await fs.pathExists(sourcePath)) {
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
this.installedFiles.add(targetPath);
}
}
@ -1638,7 +1604,7 @@ class Installer {
const targetPath = path.join(toolsDir, fileName);
if (await fs.pathExists(sourcePath)) {
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
this.installedFiles.add(targetPath);
}
}
@ -1654,7 +1620,7 @@ class Installer {
const targetPath = path.join(templatesDir, fileName);
if (await fs.pathExists(sourcePath)) {
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
this.installedFiles.add(targetPath);
}
}
@ -1669,7 +1635,7 @@ class Installer {
await fs.ensureDir(path.dirname(targetPath));
if (await fs.pathExists(dataPath)) {
await this.copyFileWithPlaceholderReplacement(dataPath, targetPath, this.bmadFolderName || 'bmad');
await this.copyFileWithPlaceholderReplacement(dataPath, targetPath);
this.installedFiles.add(targetPath);
}
}
@ -1759,14 +1725,9 @@ class Installer {
}
}
// Check if this is a workflow.yaml file
if (file.endsWith('workflow.yaml')) {
await fs.ensureDir(path.dirname(targetFile));
await this.copyWorkflowYamlStripped(sourceFile, targetFile);
} else {
// Copy the file with placeholder replacement
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile, this.bmadFolderName || 'bmad');
}
await fs.ensureDir(path.dirname(targetFile));
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile);
// Track the installed file
this.installedFiles.add(targetFile);
@ -1844,7 +1805,7 @@ class Installer {
if (!(await fs.pathExists(customizePath))) {
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
if (await fs.pathExists(genericTemplatePath)) {
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad');
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
}
@ -1853,235 +1814,6 @@ class Installer {
}
}
/**
* Build standalone agents in bmad/agents/ directory
* @param {string} bmadDir - Path to bmad directory
* @param {string} projectDir - Path to project directory
*/
async buildStandaloneAgents(bmadDir, projectDir) {
const standaloneAgentsPath = path.join(bmadDir, 'agents');
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
// Check if standalone agents directory exists
if (!(await fs.pathExists(standaloneAgentsPath))) {
return;
}
// Get all subdirectories in agents/
const agentDirs = await fs.readdir(standaloneAgentsPath, { withFileTypes: true });
for (const agentDir of agentDirs) {
if (!agentDir.isDirectory()) continue;
const agentDirPath = path.join(standaloneAgentsPath, agentDir.name);
// Find any .agent.yaml file in the directory
const files = await fs.readdir(agentDirPath);
const yamlFile = files.find((f) => f.endsWith('.agent.yaml'));
if (!yamlFile) continue;
const agentName = path.basename(yamlFile, '.agent.yaml');
const sourceYamlPath = path.join(agentDirPath, yamlFile);
const targetMdPath = path.join(agentDirPath, `${agentName}.md`);
const customizePath = path.join(cfgAgentsDir, `${agentName}.customize.yaml`);
// Check for customizations
const customizeExists = await fs.pathExists(customizePath);
let customizedFields = [];
if (customizeExists) {
const customizeContent = await fs.readFile(customizePath, 'utf8');
const yaml = require('yaml');
const customizeYaml = yaml.parse(customizeContent);
// Detect what fields are customized (similar to rebuildAgentFiles)
if (customizeYaml) {
if (customizeYaml.persona) {
for (const [key, value] of Object.entries(customizeYaml.persona)) {
if (value !== '' && value !== null && !(Array.isArray(value) && value.length === 0)) {
customizedFields.push(`persona.${key}`);
}
}
}
if (customizeYaml.agent?.metadata) {
for (const [key, value] of Object.entries(customizeYaml.agent.metadata)) {
if (value !== '' && value !== null) {
customizedFields.push(`metadata.${key}`);
}
}
}
if (customizeYaml.critical_actions && customizeYaml.critical_actions.length > 0) {
customizedFields.push('critical_actions');
}
if (customizeYaml.menu && customizeYaml.menu.length > 0) {
customizedFields.push('menu');
}
}
}
// Build YAML to XML .md
let xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
includeMetadata: true,
});
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
// Process TTS injection points (pass targetPath for tracking)
xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath);
// Write the built .md file with POSIX-compliant final newline
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
await fs.writeFile(targetMdPath, content, 'utf8');
// Display result
if (customizedFields.length > 0) {
console.log(chalk.dim(` Built standalone agent: ${agentName}.md `) + chalk.yellow(`(customized: ${customizedFields.join(', ')})`));
} else {
console.log(chalk.dim(` Built standalone agent: ${agentName}.md`));
}
}
}
/**
* Rebuild agent files from installer source (for compile command)
* @param {string} modulePath - Path to module in bmad/ installation
* @param {string} moduleName - Module name
*/
async rebuildAgentFiles(modulePath, moduleName) {
// Get source agents directory from installer
const sourceAgentsPath =
moduleName === 'core' ? path.join(getModulePath('core'), 'agents') : path.join(getSourcePath(`modules/${moduleName}`), 'agents');
if (!(await fs.pathExists(sourceAgentsPath))) {
return; // No source agents to rebuild
}
// Determine project directory (parent of bmad/ directory)
const bmadDir = path.dirname(modulePath);
const projectDir = path.dirname(bmadDir);
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
const targetAgentsPath = path.join(modulePath, 'agents');
// Ensure target directory exists
await fs.ensureDir(targetAgentsPath);
// Get all YAML agent files from source
const sourceFiles = await fs.readdir(sourceAgentsPath);
for (const file of sourceFiles) {
if (file.endsWith('.agent.yaml')) {
const agentName = file.replace('.agent.yaml', '');
const sourceYamlPath = path.join(sourceAgentsPath, file);
const targetMdPath = path.join(targetAgentsPath, `${agentName}.md`);
const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
// Check for customizations
const customizeExists = await fs.pathExists(customizePath);
let customizedFields = [];
if (customizeExists) {
const customizeContent = await fs.readFile(customizePath, 'utf8');
const yaml = require('yaml');
const customizeYaml = yaml.parse(customizeContent);
// Detect what fields are customized
if (customizeYaml) {
if (customizeYaml.persona) {
for (const [key, value] of Object.entries(customizeYaml.persona)) {
if (value !== '' && value !== null && !(Array.isArray(value) && value.length === 0)) {
customizedFields.push(`persona.${key}`);
}
}
}
if (customizeYaml.agent?.metadata) {
for (const [key, value] of Object.entries(customizeYaml.agent.metadata)) {
if (value !== '' && value !== null) {
customizedFields.push(`metadata.${key}`);
}
}
}
if (customizeYaml.critical_actions && customizeYaml.critical_actions.length > 0) {
customizedFields.push('critical_actions');
}
if (customizeYaml.memories && customizeYaml.memories.length > 0) {
customizedFields.push('memories');
}
if (customizeYaml.menu && customizeYaml.menu.length > 0) {
customizedFields.push('menu');
}
if (customizeYaml.prompts && customizeYaml.prompts.length > 0) {
customizedFields.push('prompts');
}
}
}
// Read the YAML content
const yamlContent = await fs.readFile(sourceYamlPath, 'utf8');
// Read customize content if exists
let customizeData = {};
if (customizeExists) {
const customizeContent = await fs.readFile(customizePath, 'utf8');
const yaml = require('yaml');
customizeData = yaml.parse(customizeContent);
}
// Build agent answers from customize data (filter empty values)
const answers = {};
if (customizeData.persona) {
Object.assign(answers, filterCustomizationData(customizeData.persona));
}
if (customizeData.agent?.metadata) {
const filteredMetadata = filterCustomizationData(customizeData.agent.metadata);
if (Object.keys(filteredMetadata).length > 0) {
Object.assign(answers, { metadata: filteredMetadata });
}
}
if (customizeData.critical_actions && customizeData.critical_actions.length > 0) {
answers.critical_actions = customizeData.critical_actions;
}
if (customizeData.memories && customizeData.memories.length > 0) {
answers.memories = customizeData.memories;
}
const coreConfigPath = path.join(bmadDir, 'core', 'config.yaml');
let coreConfig = {};
if (await fs.pathExists(coreConfigPath)) {
const yaml = require('yaml');
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
coreConfig = yaml.parse(coreConfigContent);
}
// Compile using the same compiler as initial installation
const { compileAgent } = require('../../../lib/agent/compiler');
const result = await compileAgent(yamlContent, answers, agentName, path.relative(bmadDir, targetMdPath), {
config: coreConfig,
});
// Check if compilation succeeded
if (!result || !result.xml) {
throw new Error(`Failed to compile agent ${agentName}: No XML returned from compiler`);
}
// Replace _bmad with actual folder name if needed
const finalXml = result.xml.replaceAll('_bmad', path.basename(bmadDir));
// Write the rebuilt .md file with POSIX-compliant final newline
const content = finalXml.endsWith('\n') ? finalXml : finalXml + '\n';
await fs.writeFile(targetMdPath, content, 'utf8');
// Display result with customizations if any
if (customizedFields.length > 0) {
console.log(chalk.dim(` Rebuilt agent: ${agentName}.md `) + chalk.yellow(`(customized: ${customizedFields.join(', ')})`));
} else {
console.log(chalk.dim(` Rebuilt agent: ${agentName}.md`));
}
}
}
}
/**
* Private: Update core
*/
@ -2677,190 +2409,6 @@ class Installer {
return { customFiles, modifiedFiles };
}
/**
* Private: Create agent configuration files
* @param {string} bmadDir - BMAD installation directory
* @param {Object} userInfo - User information including name and language
*/
async createAgentConfigs(bmadDir, userInfo = null) {
const agentConfigDir = path.join(bmadDir, '_config', 'agents');
await fs.ensureDir(agentConfigDir);
// Get all agents from all modules
const agents = [];
const agentDetails = []; // For manifest generation
// Check modules for agents (including core)
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && entry.name !== '_config') {
const moduleAgentsPath = path.join(bmadDir, entry.name, 'agents');
if (await fs.pathExists(moduleAgentsPath)) {
const agentFiles = await fs.readdir(moduleAgentsPath);
for (const agentFile of agentFiles) {
if (agentFile.endsWith('.md')) {
const agentPath = path.join(moduleAgentsPath, agentFile);
const agentContent = await fs.readFile(agentPath, 'utf8');
// Skip agents with localskip="true"
const hasLocalSkip = agentContent.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
if (hasLocalSkip) {
continue; // Skip this agent - it should not have been installed
}
const agentName = path.basename(agentFile, '.md');
// Extract any nodes with agentConfig="true"
const agentConfigNodes = this.extractAgentConfigNodes(agentContent);
agents.push({
name: agentName,
module: entry.name,
agentConfigNodes: agentConfigNodes,
});
// Use shared AgentPartyGenerator to extract details
let details = AgentPartyGenerator.extractAgentDetails(agentContent, entry.name, agentName);
// Apply config overrides if they exist
if (details) {
const configPath = path.join(agentConfigDir, `${entry.name}-${agentName}.md`);
if (await fs.pathExists(configPath)) {
const configContent = await fs.readFile(configPath, 'utf8');
details = AgentPartyGenerator.applyConfigOverrides(details, configContent);
}
agentDetails.push(details);
}
}
}
}
}
}
// Create config file for each agent
let createdCount = 0;
let skippedCount = 0;
// Load agent config template
const templatePath = getSourcePath('utility', 'models', 'agent-config-template.md');
const templateContent = await fs.readFile(templatePath, 'utf8');
for (const agent of agents) {
const configPath = path.join(agentConfigDir, `${agent.module}-${agent.name}.md`);
// Skip if config file already exists (preserve custom configurations)
if (await fs.pathExists(configPath)) {
skippedCount++;
continue;
}
// Build config content header
let configContent = `# Agent Config: ${agent.name}\n\n`;
// Process template and add agent-specific config nodes
let processedTemplate = templateContent;
// Replace {core:user_name} placeholder with actual user name if available
if (userInfo && userInfo.userName) {
processedTemplate = processedTemplate.replaceAll('{core:user_name}', userInfo.userName);
}
// Replace {core:communication_language} placeholder with actual language if available
if (userInfo && userInfo.responseLanguage) {
processedTemplate = processedTemplate.replaceAll('{core:communication_language}', userInfo.responseLanguage);
}
// If this agent has agentConfig nodes, add them after the existing comment
if (agent.agentConfigNodes && agent.agentConfigNodes.length > 0) {
// Find the agent-specific configuration nodes comment
const commentPattern = /(\s*<!-- Agent-specific configuration nodes -->)/;
const commentMatch = processedTemplate.match(commentPattern);
if (commentMatch) {
// Add nodes right after the comment
let agentSpecificNodes = '';
for (const node of agent.agentConfigNodes) {
agentSpecificNodes += `\n ${node}`;
}
processedTemplate = processedTemplate.replace(commentPattern, `$1${agentSpecificNodes}`);
}
}
configContent += processedTemplate;
// Ensure POSIX-compliant final newline
if (!configContent.endsWith('\n')) {
configContent += '\n';
}
await fs.writeFile(configPath, configContent, 'utf8');
this.installedFiles.add(configPath); // Track agent config files
createdCount++;
}
// Generate agent manifest with overrides applied
await this.generateAgentManifest(bmadDir, agentDetails);
return { total: agents.length, created: createdCount, skipped: skippedCount };
}
/**
* Generate agent manifest XML file
* @param {string} bmadDir - BMAD installation directory
* @param {Array} agentDetails - Array of agent details
*/
async generateAgentManifest(bmadDir, agentDetails) {
const manifestPath = path.join(bmadDir, '_config', 'agent-manifest.csv');
await AgentPartyGenerator.writeAgentParty(manifestPath, agentDetails, { forWeb: false });
}
/**
* Extract nodes with agentConfig="true" from agent content
* @param {string} content - Agent file content
* @returns {Array} Array of XML nodes that should be added to agent config
*/
extractAgentConfigNodes(content) {
const nodes = [];
try {
// Find all XML nodes with agentConfig="true"
// Match self-closing tags and tags with content
const selfClosingPattern = /<([a-zA-Z][a-zA-Z0-9_-]*)\s+[^>]*agentConfig="true"[^>]*\/>/g;
const withContentPattern = /<([a-zA-Z][a-zA-Z0-9_-]*)\s+[^>]*agentConfig="true"[^>]*>([\s\S]*?)<\/\1>/g;
// Extract self-closing tags
let match;
while ((match = selfClosingPattern.exec(content)) !== null) {
// Extract just the tag without children (structure only)
const tagMatch = match[0].match(/<([a-zA-Z][a-zA-Z0-9_-]*)([^>]*)\/>/);
if (tagMatch) {
const tagName = tagMatch[1];
const attributes = tagMatch[2].replace(/\s*agentConfig="true"/, ''); // Remove agentConfig attribute
nodes.push(`<${tagName}${attributes}></${tagName}>`);
}
}
// Extract tags with content
while ((match = withContentPattern.exec(content)) !== null) {
const fullMatch = match[0];
const tagName = match[1];
// Extract opening tag with attributes (removing agentConfig="true")
const openingTagMatch = fullMatch.match(new RegExp(`<${tagName}([^>]*)>`));
if (openingTagMatch) {
const attributes = openingTagMatch[1].replace(/\s*agentConfig="true"/, '');
// Add empty node structure (no children)
nodes.push(`<${tagName}${attributes}></${tagName}>`);
}
}
} catch (error) {
console.error('Error extracting agentConfig nodes:', error);
}
return nodes;
}
/**
* Handle missing custom module sources interactively
* @param {Map} customModuleSources - Map of custom module ID to info
@ -2999,7 +2547,7 @@ class Installer {
await this.manifest.addCustomModule(bmadDir, missing.info);
validCustomModules.push({
id: moduleId,
id: missing.id,
name: missing.name,
path: resolvedPath,
info: missing.info,
@ -3013,7 +2561,7 @@ class Installer {
case 'remove': {
// Extra confirmation for destructive remove
console.log(chalk.red.bold(`\n⚠️ WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!`));
console.log(chalk.red(` Module location: ${path.join(bmadDir, moduleId)}`));
console.log(chalk.red(` Module location: ${path.join(bmadDir, missing.id)}`));
const { confirm } = await inquirer.prompt([
{

File diff suppressed because it is too large Load Diff

View File

@ -731,7 +731,7 @@ class ModuleManager {
async compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir, installer = null) {
const sourceAgentsPath = path.join(sourcePath, 'agents');
const targetAgentsPath = path.join(targetPath, 'agents');
const cfgAgentsDir = path.join(bmadDir, '_bmad', '_config', 'agents');
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
// Check if agents directory exists in source
if (!(await fs.pathExists(sourceAgentsPath))) {