agent updates

This commit is contained in:
Brian Madison
2025-10-02 21:45:59 -05:00
parent c6704b4b6e
commit 3f40ef4756
69 changed files with 2596 additions and 55160 deletions

View File

@@ -327,18 +327,8 @@ class Installer {
spinner.succeed('Module configurations generated');
// Create agent configuration files
spinner.start('Creating agent configurations...');
// Get user info from collected config if available
const userInfo = {
userName: moduleConfigs.core?.['user_name'] || null,
responseLanguage: moduleConfigs.core?.['communication_language'] || null,
};
const agentConfigResult = await this.createAgentConfigs(bmadDir, userInfo);
if (agentConfigResult.skipped > 0) {
spinner.succeed(`Agent configurations: ${agentConfigResult.created} created, ${agentConfigResult.skipped} preserved`);
} else {
spinner.succeed(`Agent configurations created: ${agentConfigResult.created}`);
}
// Note: Legacy createAgentConfigs removed - using YAML customize system instead
// Customize templates are now created in processAgentFiles when building YAML agents
// Pre-register manifest files that will be created (except files-manifest.csv to avoid recursion)
const cfgDir = path.join(bmadDir, '_cfg');
@@ -770,6 +760,10 @@ class Installer {
},
);
// Process agent files to build YAML agents and create customize templates
const modulePath = path.join(bmadDir, moduleName);
await this.processAgentFiles(modulePath, moduleName);
// Dependencies are already included in full module install
}
@@ -939,8 +933,8 @@ class Installer {
}
/**
* Process agent files to inject activation block
* @param {string} modulePath - Path to module
* Process agent files to build YAML agents and inject activation blocks
* @param {string} modulePath - Path to module in bmad/ installation
* @param {string} moduleName - Module name
*/
async processAgentFiles(modulePath, moduleName) {
@@ -951,21 +945,137 @@ class Installer {
return; // No agents to process
}
// Determine project directory (parent of bmad/ directory)
const bmadDir = path.dirname(modulePath);
const projectDir = path.dirname(bmadDir);
const cfgAgentsDir = path.join(bmadDir, '_cfg', 'agents');
// Ensure _cfg/agents directory exists
await fs.ensureDir(cfgAgentsDir);
// Get all agent files
const agentFiles = await fs.readdir(agentsPath);
for (const agentFile of agentFiles) {
if (!agentFile.endsWith('.md')) continue;
// Handle YAML agents - build them to .md
if (agentFile.endsWith('.agent.yaml')) {
const agentName = agentFile.replace('.agent.yaml', '');
const yamlPath = path.join(agentsPath, agentFile);
const mdPath = path.join(agentsPath, `${agentName}.md`);
const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
const agentPath = path.join(agentsPath, agentFile);
let content = await fs.readFile(agentPath, 'utf8');
// Create customize template if it doesn't exist
if (!(await fs.pathExists(customizePath))) {
const genericTemplatePath = getSourcePath('utility', 'templates', 'agent.customize.template.yaml');
if (await fs.pathExists(genericTemplatePath)) {
await fs.copy(genericTemplatePath, customizePath);
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
}
}
// Check if content has agent XML and no activation block
if (content.includes('<agent') && !content.includes('<activation')) {
// Inject the activation block using XML handler
content = this.xmlHandler.injectActivationSimple(content);
await fs.writeFile(agentPath, content, 'utf8');
// Build YAML + customize to .md
const customizeExists = await fs.pathExists(customizePath);
const xmlContent = await this.xmlHandler.buildFromYaml(yamlPath, customizeExists ? customizePath : null, {
includeMetadata: true,
});
// Replace {project-root} placeholder
const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
// Write the built .md file
await fs.writeFile(mdPath, processedContent, 'utf8');
this.installedFiles.push(mdPath);
console.log(chalk.dim(` Built agent: ${agentName}.md`));
}
// Handle legacy .md agents - inject activation if needed
else if (agentFile.endsWith('.md')) {
const agentPath = path.join(agentsPath, agentFile);
let content = await fs.readFile(agentPath, 'utf8');
// Check if content has agent XML and no activation block
if (content.includes('<agent') && !content.includes('<activation')) {
// Inject the activation block using XML handler
content = this.xmlHandler.injectActivationSimple(content);
await fs.writeFile(agentPath, content, 'utf8');
}
}
}
}
/**
* Compile/rebuild all agents and tasks for quick updates
* @param {Object} config - Compilation configuration
* @returns {Object} Compilation results
*/
async compileAgents(config) {
const ora = require('ora');
const spinner = ora('Starting agent compilation...').start();
try {
const projectDir = path.resolve(config.directory);
const bmadDir = path.join(projectDir, 'bmad');
// Check if bmad directory exists
if (!(await fs.pathExists(bmadDir))) {
spinner.fail('No BMAD installation found');
throw new Error(`BMAD not installed at ${bmadDir}`);
}
let agentCount = 0;
let taskCount = 0;
// Process all modules in bmad directory
spinner.text = 'Rebuilding agent files...';
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && entry.name !== '_cfg') {
const modulePath = path.join(bmadDir, entry.name);
// Process agents
const agentsPath = path.join(modulePath, 'agents');
if (await fs.pathExists(agentsPath)) {
await this.processAgentFiles(modulePath, entry.name);
const agentFiles = await fs.readdir(agentsPath);
agentCount += agentFiles.filter((f) => f.endsWith('.md')).length;
}
// Count tasks (already built)
const tasksPath = path.join(modulePath, 'tasks');
if (await fs.pathExists(tasksPath)) {
const taskFiles = await fs.readdir(tasksPath);
taskCount += taskFiles.filter((f) => f.endsWith('.md')).length;
}
}
}
// Ask for IDE to update
spinner.stop();
// Note: UI lives in tools/cli/lib/ui.js; from installers/lib/core use '../../../lib/ui'
const { UI } = require('../../../lib/ui');
const ui = new UI();
const toolConfig = await ui.promptToolSelection(projectDir, []);
if (!toolConfig.skipIde && toolConfig.ides && toolConfig.ides.length > 0) {
spinner.start('Updating IDE configurations...');
for (const ide of toolConfig.ides) {
spinner.text = `Updating ${ide}...`;
await this.ideManager.setup(ide, projectDir, bmadDir, {
selectedModules: entries.filter((e) => e.isDirectory() && e.name !== '_cfg').map((e) => e.name),
skipModuleInstall: true, // Skip module installation, just update IDE files
verbose: config.verbose,
});
}
spinner.succeed('IDE configurations updated');
}
return { agentCount, taskCount };
} catch (error) {
spinner.fail('Compilation failed');
throw error;
}
}

View File

@@ -177,8 +177,9 @@ class BaseIdeSetup {
processed = this.xmlHandler.injectActivationSimple(processed, metadata);
}
// Use the actual project directory path if provided, otherwise default to 'bmad/'
const projectRoot = projectDir ? projectDir + '/' : 'bmad/';
// Use the actual project directory path if provided, otherwise default to 'bmad'
// Note: Don't add trailing slash - paths in source include leading slash
const projectRoot = projectDir || 'bmad';
// Common replacements (including in the activation block)
processed = processed.replaceAll('{project-root}', projectRoot);

View File

@@ -92,11 +92,10 @@ class ClaudeCodeSetup extends BaseIdeSetup {
await this.ensureDir(bmadCommandsDir);
// Get agents and tasks from SOURCE, not installed location
// This ensures we process files with {project-root} placeholders intact
const sourceDir = getSourcePath('modules');
const agents = await this.getAgentsFromSource(sourceDir, options.selectedModules || []);
const tasks = await this.getTasksFromSource(sourceDir, options.selectedModules || []);
// Get agents and tasks from INSTALLED bmad/ directory
// Base installer has already built .md files from .agent.yaml sources
const agents = await this.getAgentsFromBmad(bmadDir, options.selectedModules || []);
const tasks = await this.getTasksFromBmad(bmadDir, options.selectedModules || []);
// Create directories for each module
const modules = new Set();
@@ -108,30 +107,32 @@ class ClaudeCodeSetup extends BaseIdeSetup {
await this.ensureDir(path.join(bmadCommandsDir, module, 'tasks'));
}
// Process and copy agents
// Copy agents from bmad/ to .claude/commands/
let agentCount = 0;
for (const agent of agents) {
const content = await this.readAndProcess(agent.path, {
const sourcePath = agent.path;
const targetPath = path.join(bmadCommandsDir, agent.module, 'agents', `${agent.name}.md`);
const content = await this.readAndProcess(sourcePath, {
module: agent.module,
name: agent.name,
});
const targetPath = path.join(bmadCommandsDir, agent.module, 'agents', `${agent.name}.md`);
await this.writeFile(targetPath, content);
agentCount++;
}
// Process and copy tasks
// Copy tasks from bmad/ to .claude/commands/
let taskCount = 0;
for (const task of tasks) {
const content = await this.readAndProcess(task.path, {
const sourcePath = task.path;
const targetPath = path.join(bmadCommandsDir, task.module, 'tasks', `${task.name}.md`);
const content = await this.readAndProcess(sourcePath, {
module: task.module,
name: task.name,
});
const targetPath = path.join(bmadCommandsDir, task.module, 'tasks', `${task.name}.md`);
await this.writeFile(targetPath, content);
taskCount++;
}
@@ -185,6 +186,58 @@ class ClaudeCodeSetup extends BaseIdeSetup {
return super.processContent(content, metadata, this.projectDir);
}
/**
* Get agents from installed bmad/ directory
*/
async getAgentsFromBmad(bmadDir, selectedModules) {
const fs = require('fs-extra');
const agents = [];
// Add core agents
if (await fs.pathExists(path.join(bmadDir, 'core', 'agents'))) {
const coreAgents = await this.getAgentsFromDir(path.join(bmadDir, 'core', 'agents'), 'core');
agents.push(...coreAgents);
}
// Add module agents
for (const moduleName of selectedModules) {
const agentsPath = path.join(bmadDir, moduleName, 'agents');
if (await fs.pathExists(agentsPath)) {
const moduleAgents = await this.getAgentsFromDir(agentsPath, moduleName);
agents.push(...moduleAgents);
}
}
return agents;
}
/**
* Get tasks from installed bmad/ directory
*/
async getTasksFromBmad(bmadDir, selectedModules) {
const fs = require('fs-extra');
const tasks = [];
// Add core tasks
if (await fs.pathExists(path.join(bmadDir, 'core', 'tasks'))) {
const coreTasks = await this.getTasksFromDir(path.join(bmadDir, 'core', 'tasks'), 'core');
tasks.push(...coreTasks);
}
// Add module tasks
for (const moduleName of selectedModules) {
const tasksPath = path.join(bmadDir, moduleName, 'tasks');
if (await fs.pathExists(tasksPath)) {
const moduleTasks = await this.getTasksFromDir(tasksPath, moduleName);
tasks.push(...moduleTasks);
}
}
return tasks;
}
/**
* Get agents from source modules (not installed location)
*/
@@ -243,14 +296,23 @@ class ClaudeCodeSetup extends BaseIdeSetup {
/**
* Get agents from a specific directory
* When reading from bmad/, this returns built .md files
*/
async getAgentsFromDir(dirPath, moduleName) {
const fs = require('fs-extra');
const agents = [];
const files = await fs.readdir(dirPath);
for (const file of files) {
// Only process .md files (base installer has already built .agent.yaml to .md)
if (file.endsWith('.md')) {
// Skip customize templates
if (file.includes('.customize.')) {
continue;
}
const baseName = file.replace('.md', '');
const filePath = path.join(dirPath, file);
const content = await fs.readFile(filePath, 'utf8');
@@ -261,7 +323,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
agents.push({
path: filePath,
name: file.replace('.md', ''),
name: baseName,
module: moduleName,
});
}