diff --git a/LICENSE b/LICENSE index 69bf31e1..d0a0c83d 100644 --- a/LICENSE +++ b/LICENSE @@ -21,6 +21,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. TRADEMARK NOTICE: -BMAD™, BMAD-CORE™ and BMAD-METHOD™ are trademarks of BMad Code, LLC. The use of these +BMad™ , BMAD-CORE™ and BMAD-METHOD™ are trademarks of BMad Code, LLC. The use of these trademarks in this software does not grant any rights to use the trademarks for any other purpose. diff --git a/README.md b/README.md index 1c0975e8..e4ba806d 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for full development guidelines. MIT License - See [LICENSE](LICENSE) for details. -**Trademarks:** BMAD™ and BMAD-METHOD™ are trademarks of BMad Code, LLC. +**Trademarks:** BMad™ and BMAD-METHOD™ are trademarks of BMad Code, LLC. --- diff --git a/src/core/module.yaml b/src/core/module.yaml index 0c0f43c0..cf5b9ad5 100644 --- a/src/core/module.yaml +++ b/src/core/module.yaml @@ -1,8 +1,8 @@ code: core -name: "BMAD™ Core Module" +name: "BMad™ Core Module" -header: "BMAD™ Core Configuration" -subheader: "Configure the core settings for your BMAD™ installation.\nThese settings will be used across all modules and agents." +header: "BMad™ Core Configuration" +subheader: "Configure the core settings for your BMad™ installation.\nThese settings will be used across all modules and agents." user_name: prompt: "What shall the agents call you (TIP: Use a team name if using with a group)?" diff --git a/src/modules/bmb/module.yaml b/src/modules/bmb/module.yaml index d2546f2e..bac4622b 100644 --- a/src/modules/bmb/module.yaml +++ b/src/modules/bmb/module.yaml @@ -1,7 +1,7 @@ code: bmb name: "BMB: BMad Builder - Agent, Workflow and Module Builder" header: "BMad Optimized Builder (BoMB) Module Configuration" -subheader: "Configure the settings for the BoMB Factory!\nThe agent, workflow and module builder for BMAD™" +subheader: "Configure the settings for the BoMB Factory!\nThe agent, workflow and module builder for BMad™ " default_selected: false # This module will not be selected by default for new installations # Variables from Core Config inserted: diff --git a/src/modules/bmm/sub-modules/claude-code/config.yaml b/src/modules/bmm/sub-modules/claude-code/config.yaml index 6908fe96..26d26bf9 100644 --- a/src/modules/bmm/sub-modules/claude-code/config.yaml +++ b/src/modules/bmm/sub-modules/claude-code/config.yaml @@ -1,4 +1,3 @@ -# Powered by BMAD™ Core name: bmmcc short-title: BMM Claude Code Sub Module author: Brian (BMad) Madison diff --git a/tools/cli/installers/lib/core/config-collector.js b/tools/cli/installers/lib/core/config-collector.js index d31af833..5898ace5 100644 --- a/tools/cli/installers/lib/core/config-collector.js +++ b/tools/cli/installers/lib/core/config-collector.js @@ -127,10 +127,6 @@ class ConfigCollector { } } - if (foundAny) { - console.log(chalk.cyan('\n📋 Found existing BMAD module configurations')); - } - return foundAny; } diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index aa5ec034..5d126ad7 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -406,7 +406,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: CLIUtils.displayLogo(); // Display welcome message - CLIUtils.displaySection('BMAD™ Installation', 'Version ' + require(path.join(getProjectRoot(), 'package.json')).version); + CLIUtils.displaySection('BMad™ Installation', 'Version ' + require(path.join(getProjectRoot(), 'package.json')).version); } // Note: Legacy V4 detection now happens earlier in UI.promptInstall() @@ -834,13 +834,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: moduleManager: tempModuleManager, }); - if (config.verbose) { - spinner.succeed('Dependencies resolved'); - } else { - spinner.succeed('Dependencies resolved'); - } - - // Core is already installed above, skip if included in resolution + spinner.succeed('Dependencies resolved'); // Install modules with their dependencies if (allModules && allModules.length > 0) { @@ -1217,14 +1211,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: console.log(chalk.green(`✓ Configured: ${validIdes.join(', ')}`)); } } - - // Copy IDE-specific documentation (only for valid IDEs) - const validIdesForDocs = (config.ides || []).filter((ide) => ide && typeof ide === 'string'); - if (validIdesForDocs.length > 0) { - spinner.start('Copying IDE documentation...'); - await this.copyIdeDocumentation(validIdesForDocs, bmadDir); - spinner.succeed('IDE documentation copied'); - } } // Run module-specific installers after IDE setup @@ -1328,20 +1314,20 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: if (customFiles.length > 0) { console.log(chalk.cyan(`\n📁 Custom files preserved: ${customFiles.length}`)); console.log(chalk.dim('The following custom files were found and restored:\n')); - for (const file of customFiles) { - console.log(chalk.dim(` - ${path.relative(bmadDir, file)}`)); + for (const customFile of customFiles) { + const relativePath = path.relative(projectDir, customFile); + console.log(chalk.dim(` • ${relativePath}`)); } - console.log(''); } if (modifiedFiles.length > 0) { - console.log(chalk.yellow(`\n⚠️ Modified files detected: ${modifiedFiles.length}`)); - console.log(chalk.dim('The following files were modified and backed up with .bak extension:\n')); - for (const file of modifiedFiles) { - console.log(chalk.dim(` - ${file.relativePath} → ${file.relativePath}.bak`)); - } - console.log(chalk.dim('\nThese files have been updated with the new version.')); - console.log(chalk.dim('Review the .bak files to see your changes and merge if needed.\n')); + console.log(chalk.yellow(`\n⚠️ User modified files detected: ${modifiedFiles.length}`)); + console.log( + chalk.dim( + '\nThese user modified files have been updated with the new version, search the project for .bak files that had your customizations.', + ), + ); + console.log(chalk.dim('Remove these .bak files it no longer needed\n')); } // Display completion message @@ -1906,7 +1892,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml'); if (await fs.pathExists(genericTemplatePath)) { await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad'); - // Only show customize creation in verbose mode if (process.env.BMAD_VERBOSE_INSTALL === 'true') { console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`)); } @@ -3056,25 +3041,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: return nodes; } - /** - * Copy IDE-specific documentation to BMAD docs - * @param {Array} ides - List of selected IDEs - * @param {string} bmadDir - BMAD installation directory - */ - async copyIdeDocumentation(ides, bmadDir) { - const docsDir = path.join(bmadDir, 'docs'); - await fs.ensureDir(docsDir); - - for (const ide of ides) { - const sourceDocPath = path.join(getProjectRoot(), 'docs', 'ide-info', `${ide}.md`); - const targetDocPath = path.join(docsDir, `${ide}-instructions.md`); - - if (await fs.pathExists(sourceDocPath)) { - await this.copyFileWithPlaceholderReplacement(sourceDocPath, targetDocPath, this.bmadFolderName || 'bmad'); - } - } - } - /** * Handle missing custom module sources interactively * @param {Map} customModuleSources - Map of custom module ID to info diff --git a/tools/cli/lib/ui.js b/tools/cli/lib/ui.js index 6d9d5c4b..4b7f1ae3 100644 --- a/tools/cli/lib/ui.js +++ b/tools/cli/lib/ui.js @@ -282,101 +282,133 @@ class UI { }; } - // If actionType === 'update', continue with normal flow below - } - - // For new installations, ask about content types first - if (!hasExistingInstall) { - // Ask about official modules first - const { wantsOfficialModules } = await inquirer.prompt([ - { - type: 'confirm', - name: 'wantsOfficialModules', - message: 'Will you be installing any official modules (BMad Method, BMad Builder, Creative Innovation Suite)?', - default: true, - }, - ]); - - let selectedOfficialModules = []; - if (wantsOfficialModules) { + // If actionType === 'update', handle it with the new flow + // Return early with modify configuration + if (actionType === 'update') { + // Get existing installation info const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory); - const moduleChoices = await this.getModuleChoices(installedModuleIds, { hasCustomContent: false }); - selectedOfficialModules = await this.selectModules(moduleChoices); + + console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`)); + const { changeModuleSelection } = await inquirer.prompt([ + { + type: 'confirm', + name: 'changeModuleSelection', + message: 'Change which modules are installed?', + default: false, + }, + ]); + + let selectedModules = []; + if (changeModuleSelection) { + // Show module selection with existing modules pre-selected + const moduleChoices = await this.getModuleChoices(new Set(installedModuleIds), { hasCustomContent: false }); + selectedModules = await this.selectModules(moduleChoices, [...installedModuleIds]); + } else { + selectedModules = [...installedModuleIds]; + } + + // Get tool selection + const toolSelection = await this.promptToolSelection(confirmedDirectory, selectedModules); + + // TTS configuration - ask right after tool selection (matches new install flow) + const hasClaudeCode = toolSelection.ides && toolSelection.ides.includes('claude-code'); + let enableTts = false; + + if (hasClaudeCode) { + const { enableTts: enable } = await inquirer.prompt([ + { + type: 'confirm', + name: 'enableTts', + message: 'Claude Code supports TTS (Text-to-Speech). Would you like to enable it?', + default: false, + }, + ]); + enableTts = enable; + } + + // Core config with existing defaults (ask after TTS) + const coreConfig = await this.collectCoreConfig(confirmedDirectory); + + return { + actionType: 'update', + directory: confirmedDirectory, + installCore: true, + modules: selectedModules, + ides: toolSelection.ides, + skipIde: toolSelection.skipIde, + coreConfig: coreConfig, + customContent: { hasCustomContent: false }, + enableAgentVibes: enableTts, + agentVibesInstalled: false, + }; } - - // Then ask about custom content - const { wantsCustomContent } = await inquirer.prompt([ - { - type: 'confirm', - name: 'wantsCustomContent', - message: 'Will you be installing any locally stored custom content?', - default: false, - }, - ]); - - if (wantsCustomContent) { - customContentConfig = await this.promptCustomContentSource(); - } - - // Store the selected modules for later - customContentConfig._selectedOfficialModules = selectedOfficialModules; } + // This section is only for new installations (update returns early above) const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory); - // Collect core configuration first + // Ask about official modules for new installations + const { wantsOfficialModules } = await inquirer.prompt([ + { + type: 'confirm', + name: 'wantsOfficialModules', + message: 'Will you be installing any official modules (BMad Method, BMad Builder, Creative Innovation Suite)?', + default: true, + }, + ]); + + let selectedOfficialModules = []; + if (wantsOfficialModules) { + const moduleChoices = await this.getModuleChoices(installedModuleIds, { hasCustomContent: false }); + selectedOfficialModules = await this.selectModules(moduleChoices); + } + + // Ask about custom content + const { wantsCustomContent } = await inquirer.prompt([ + { + type: 'confirm', + name: 'wantsCustomContent', + message: 'Will you be installing any locally stored custom content?', + default: false, + }, + ]); + + if (wantsCustomContent) { + customContentConfig = await this.promptCustomContentSource(); + } + + // Store the selected modules for later + customContentConfig._selectedOfficialModules = selectedOfficialModules; + + // Build the final list of selected modules + let selectedModules = customContentConfig._selectedOfficialModules || []; + + // Add custom content modules if any were selected + if (customContentConfig && customContentConfig.selectedModuleIds) { + selectedModules = [...selectedModules, ...customContentConfig.selectedModuleIds]; + } + + // Remove core if it's in the list (it's always installed) + selectedModules = selectedModules.filter((m) => m !== 'core'); + + // Tool selection (already done for new installs at the beginning) + if (!toolSelection) { + toolSelection = await this.promptToolSelection(confirmedDirectory, selectedModules); + } + + // Collect configurations for new installations const coreConfig = await this.collectCoreConfig(confirmedDirectory); - // Custom content will be handled during installation phase - // Store the custom content config for later use - if (customContentConfig._shouldAsk) { - delete customContentConfig._shouldAsk; - } - - // Handle module selection - let selectedModules = []; - if (actionType === 'update' || actionType === 'reinstall') { - // Keep all existing installed modules during update/reinstall - selectedModules = [...installedModuleIds]; - console.log(chalk.cyan('\n📦 Keeping existing modules: ') + selectedModules.join(', ')); - } else if (!hasExistingInstall) { - // For new installs, we've already selected official modules - selectedModules = customContentConfig._selectedOfficialModules || []; - - // Add custom content modules if any were selected - if (customContentConfig && customContentConfig.selectedModuleIds) { - selectedModules = [...selectedModules, ...customContentConfig.selectedModuleIds]; - } - - // Custom modules are already added via selectedModuleIds from customContentConfig - // No need for additional processing here - } - - // AgentVibes TTS configuration already collected earlier for new installations - // For existing installations, keep the old behavior - if (hasExistingInstall && !agentVibesConfig.enabled) { - agentVibesConfig = await this.promptAgentVibes(confirmedDirectory); - } - - // Tool selection already collected for new installations - // For existing installations, we need to collect it now - if (hasExistingInstall && !toolSelection) { - const modulesForToolSelection = selectedModules; - toolSelection = await this.promptToolSelection(confirmedDirectory, modulesForToolSelection); - } - - // No more screen clearing - keep output flowing + // TTS already handled at the beginning for new installs return { - actionType: actionType || 'update', // Preserve reinstall or update action + actionType: 'install', directory: confirmedDirectory, - installCore: true, // Always install core + installCore: true, modules: selectedModules, - // IDE selection collected after config, will be configured later ides: toolSelection.ides, skipIde: toolSelection.skipIde, - coreConfig: coreConfig, // Pass collected core config to installer - // Custom content configuration + coreConfig: coreConfig, customContent: customContentConfig, enableAgentVibes: agentVibesConfig.enabled, agentVibesInstalled: agentVibesConfig.alreadyInstalled, @@ -760,13 +792,14 @@ class UI { * @param {Array} moduleChoices - Available module choices * @returns {Array} Selected module IDs */ - async selectModules(moduleChoices) { + async selectModules(moduleChoices, defaultSelections = []) { const moduleAnswer = await inquirer.prompt([ { type: 'checkbox', name: 'modules', message: 'Select modules to install:', choices: moduleChoices, + default: defaultSelections, }, ]); @@ -1114,6 +1147,57 @@ class UI { return (await fs.pathExists(hookPath)) && (await fs.pathExists(playTtsPath)); } + /** + * Load existing configurations to use as defaults + * @param {string} directory - Installation directory + * @returns {Object} Existing configurations + */ + async loadExistingConfigurations(directory) { + const configs = { + hasCustomContent: false, + coreConfig: {}, + ideConfig: { ides: [], skipIde: false }, + agentVibesConfig: { enabled: false, alreadyInstalled: false }, + }; + + try { + // Load core config + configs.coreConfig = await this.collectCoreConfig(directory); + + // Load IDE configuration + const configuredIdes = await this.getConfiguredIdes(directory); + if (configuredIdes.length > 0) { + configs.ideConfig.ides = configuredIdes; + configs.ideConfig.skipIde = false; + } + + // Load AgentVibes configuration + const agentVibesInstalled = await this.checkAgentVibesInstalled(directory); + configs.agentVibesConfig = { enabled: agentVibesInstalled, alreadyInstalled: agentVibesInstalled }; + + return configs; + } catch { + // If loading fails, return empty configs + console.warn('Warning: Could not load existing configurations'); + return configs; + } + } + + /** + * Get configured IDEs from existing installation + * @param {string} directory - Installation directory + * @returns {Array} List of configured IDEs + */ + async getConfiguredIdes(directory) { + const { Detector } = require('../installers/lib/core/detector'); + const { Installer } = require('../installers/lib/core/installer'); + const detector = new Detector(); + const installer = new Installer(); + const bmadResult = await installer.findBmadDir(directory); + const existingInstall = await detector.detect(bmadResult.bmadDir); + return existingInstall.ides || []; + } + /** * Prompt for custom content for existing installations * @returns {Object} Custom content configuration