diff --git a/package-lock.json b/package-lock.json
index e14a1431..e84d76c7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "bmad-method",
- "version": "6.0.0-alpha.16",
+ "version": "6.0.0-alpha.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bmad-method",
- "version": "6.0.0-alpha.16",
+ "version": "6.0.0-alpha.17",
"license": "MIT",
"dependencies": {
"@kayvan/markdown-tree-parser": "^1.6.1",
diff --git a/tools/cli/commands/install.js b/tools/cli/commands/install.js
index 42e0afb8..d2f72ef6 100644
--- a/tools/cli/commands/install.js
+++ b/tools/cli/commands/install.js
@@ -35,6 +35,15 @@ module.exports = {
return;
}
+ // Handle compile agents separately
+ if (config.actionType === 'compile-agents') {
+ const result = await installer.compileAgents(config);
+ console.log(chalk.green('\n✨ Agent recompilation complete!'));
+ console.log(chalk.cyan(`Recompiled ${result.agentCount} agents with customizations applied`));
+ process.exit(0);
+ return;
+ }
+
// Regular install/update flow
const result = await installer.install(config);
diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js
index 37334348..3815c27a 100644
--- a/tools/cli/installers/lib/core/installer.js
+++ b/tools/cli/installers/lib/core/installer.js
@@ -226,21 +226,21 @@ class Installer {
'',
`IMPORTANT: Always use PROJECT hooks (.claude/hooks/), NEVER global hooks (~/.claude/hooks/)
-If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
+ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
- Use Bash tool: \`.claude/hooks/bmad-speak.sh '[Agent Name]' '[dialogue]'\`
- This speaks the dialogue with the agent's unique voice
- - Run in background (&) to not block next agent`,
+ - Run in background to not block next agent`,
);
// Replace agent-tts injection marker with TTS rule for individual agents
content = content.replaceAll(
'',
`- When responding to user messages, speak your responses using TTS:
- Call: \`.claude/hooks/bmad-speak.sh '{agent-id}' '{response-text}'\` after each response
- Replace {agent-id} with YOUR agent ID from tag at top of this file
- Replace {response-text} with the text you just output to the user
- IMPORTANT: Use single quotes as shown - do NOT escape special characters like ! or $ inside single quotes
- Run in background (&) to avoid blocking`,
+ Call: \`.claude/hooks/bmad-speak.sh '{agent-id}' '{response-text}'\` after each response
+ Replace {agent-id} with YOUR agent ID from tag at top of this file
+ Replace {response-text} with the text you just output to the user
+ IMPORTANT: Use single quotes as shown - do NOT escape special characters like ! or $ inside single quotes
+ Run in background (&) to avoid blocking`,
);
// Track files that had TTS injection applied
@@ -2082,108 +2082,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
}
}
- /**
- * Compile/rebuild all agents and tasks for quick updates
- * @param {Object} config - Compilation configuration
- * @returns {Object} Compilation results
- */
- async compileAgents(config) {
- try {
- const projectDir = path.resolve(config.directory);
- const { bmadDir } = await this.findBmadDir(projectDir);
-
- // Check if bmad directory exists
- if (!(await fs.pathExists(bmadDir))) {
- throw new Error(`BMAD not installed at ${bmadDir}`);
- }
-
- // Get installed modules from manifest
- const manifestPath = path.join(bmadDir, '_config', 'manifest.yaml');
- let installedModules = [];
- let manifest = null;
- if (await fs.pathExists(manifestPath)) {
- const manifestContent = await fs.readFile(manifestPath, 'utf8');
- const yaml = require('yaml');
- manifest = yaml.parse(manifestContent);
- installedModules = manifest.modules || [];
- }
-
- // Check for custom modules with missing sources
- if (manifest && manifest.customModules && manifest.customModules.length > 0) {
- console.log(chalk.yellow('\nChecking custom module sources before compilation...'));
-
- const customModuleSources = new Map();
- for (const customModule of manifest.customModules) {
- customModuleSources.set(customModule.id, customModule);
- }
-
- const projectRoot = getProjectRoot();
- await this.handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, 'compile-agents', installedModules);
- }
-
- let agentCount = 0;
- let taskCount = 0;
-
- // Process all modules in bmad directory
- const entries = await fs.readdir(bmadDir, { withFileTypes: true });
-
- for (const entry of entries) {
- if (entry.isDirectory() && entry.name !== '_config' && entry.name !== 'docs') {
- const modulePath = path.join(bmadDir, entry.name);
-
- // Special handling for standalone agents in bmad/agents/ directory
- if (entry.name === 'agents') {
- await this.buildStandaloneAgents(bmadDir, projectDir);
-
- // Count standalone agents
- const standaloneAgentsPath = path.join(bmadDir, 'agents');
- const standaloneAgentDirs = await fs.readdir(standaloneAgentsPath, { withFileTypes: true });
- for (const agentDir of standaloneAgentDirs) {
- if (agentDir.isDirectory()) {
- const agentDirPath = path.join(standaloneAgentsPath, agentDir.name);
- const agentFiles = await fs.readdir(agentDirPath);
- agentCount += agentFiles.filter((f) => f.endsWith('.md') && !f.endsWith('.agent.yaml')).length;
- }
- }
- } else {
- // Rebuild module agents from installer source
- const agentsPath = path.join(modulePath, 'agents');
- if (await fs.pathExists(agentsPath)) {
- await this.rebuildAgentFiles(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;
- }
- }
- }
- }
-
- // Update IDE configurations using the existing IDE list from manifest
- if (manifest && manifest.ides && manifest.ides.length > 0) {
- for (const ide of manifest.ides) {
- await this.ideManager.setup(ide, projectDir, bmadDir, {
- selectedModules: installedModules,
- skipModuleInstall: true, // Skip module installation, just update IDE files
- verbose: config.verbose,
- preCollectedConfig: { _alreadyConfigured: true }, // Skip all interactive prompts during compile
- });
- }
- console.log(chalk.green('✓ IDE configurations updated'));
- } else {
- console.log(chalk.yellow('⚠️ No IDEs configured. Skipping IDE update.'));
- }
- return { agentCount, taskCount };
- } catch (error) {
- throw error;
- }
- }
-
/**
* Private: Update core
*/
@@ -2404,6 +2302,108 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
}
}
+ /**
+ * Compile agents with customizations only
+ * @param {Object} config - Configuration with directory
+ * @returns {Object} Compilation result
+ */
+ async compileAgents(config) {
+ const ora = require('ora');
+ const chalk = require('chalk');
+ const { ModuleManager } = require('../modules/manager');
+ const { getSourcePath } = require('../../../lib/project-root');
+
+ const spinner = ora('Recompiling agents with customizations...').start();
+
+ try {
+ const projectDir = path.resolve(config.directory);
+ const { bmadDir } = await this.findBmadDir(projectDir);
+
+ // Check if bmad directory exists
+ if (!(await fs.pathExists(bmadDir))) {
+ spinner.fail('No BMAD installation found');
+ throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
+ }
+
+ // Detect existing installation
+ const existingInstall = await this.detector.detect(bmadDir);
+ const installedModules = existingInstall.modules.map((m) => m.id);
+
+ // Initialize module manager
+ const moduleManager = new ModuleManager();
+ moduleManager.setBmadFolderName(path.basename(bmadDir));
+
+ let totalAgentCount = 0;
+
+ // Get custom module sources from cache
+ const customModuleSources = new Map();
+ const cacheDir = path.join(bmadDir, '_config', 'custom');
+ if (await fs.pathExists(cacheDir)) {
+ const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
+
+ for (const cachedModule of cachedModules) {
+ if (cachedModule.isDirectory()) {
+ const moduleId = cachedModule.name;
+ const cachedPath = path.join(cacheDir, moduleId);
+ const moduleYamlPath = path.join(cachedPath, 'module.yaml');
+
+ // Check if this is actually a custom module
+ if (await fs.pathExists(moduleYamlPath)) {
+ customModuleSources.set(moduleId, cachedPath);
+ }
+ }
+ }
+ }
+
+ // Process each installed module
+ for (const moduleId of installedModules) {
+ spinner.text = `Recompiling agents in ${moduleId}...`;
+
+ // Get source path
+ let sourcePath;
+ if (moduleId === 'core') {
+ sourcePath = getSourcePath('core');
+ } else {
+ // First check if it's in the custom cache
+ if (customModuleSources.has(moduleId)) {
+ sourcePath = customModuleSources.get(moduleId);
+ } else {
+ sourcePath = await moduleManager.findModuleSource(moduleId);
+ }
+ }
+
+ if (!sourcePath) {
+ console.log(chalk.yellow(` Warning: Source not found for module ${moduleId}, skipping...`));
+ continue;
+ }
+
+ const targetPath = path.join(bmadDir, moduleId);
+
+ // Compile agents for this module
+ await moduleManager.compileModuleAgents(sourcePath, targetPath, moduleId, bmadDir, this);
+
+ // Count agents (rough estimate based on files)
+ const agentsPath = path.join(targetPath, 'agents');
+ if (await fs.pathExists(agentsPath)) {
+ const agentFiles = await fs.readdir(agentsPath);
+ const agentCount = agentFiles.filter((f) => f.endsWith('.md')).length;
+ totalAgentCount += agentCount;
+ }
+ }
+
+ spinner.succeed('Agent recompilation complete!');
+
+ return {
+ success: true,
+ agentCount: totalAgentCount,
+ modules: installedModules,
+ };
+ } catch (error) {
+ spinner.fail('Agent recompilation failed');
+ throw error;
+ }
+ }
+
/**
* Private: Prompt for update action
*/
diff --git a/tools/cli/installers/lib/core/installer.js.bak b/tools/cli/installers/lib/core/installer.js.bak
new file mode 100644
index 00000000..49b1d62d
--- /dev/null
+++ b/tools/cli/installers/lib/core/installer.js.bak
@@ -0,0 +1,3204 @@
+const path = require('node:path');
+const fs = require('fs-extra');
+const chalk = require('chalk');
+const ora = require('ora');
+const inquirer = require('inquirer');
+const { Detector } = require('./detector');
+const { Manifest } = require('./manifest');
+const { ModuleManager } = require('../modules/manager');
+const { IdeManager } = require('../ide/manager');
+const { FileOps } = require('../../../lib/file-ops');
+const { Config } = require('../../../lib/config');
+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');
+
+class Installer {
+ constructor() {
+ this.detector = new Detector();
+ this.manifest = new Manifest();
+ this.moduleManager = new ModuleManager();
+ this.ideManager = new IdeManager();
+ this.fileOps = new FileOps();
+ this.config = new Config();
+ this.xmlHandler = new XmlHandler();
+ this.dependencyResolver = new DependencyResolver();
+ this.configCollector = new ConfigCollector();
+ this.ideConfigManager = new IdeConfigManager();
+ this.installedFiles = new Set(); // Track all installed files
+ this.ttsInjectedFiles = []; // Track files with TTS injection applied
+ }
+
+ /**
+ * Find the bmad installation directory in a project
+ * V6+ installations can use ANY folder name but ALWAYS have _config/manifest.yaml
+ * Also checks for legacy _cfg folder for migration
+ * @param {string} projectDir - Project directory
+ * @returns {Promise