diff --git a/docs/A-Product-Brief/.gitkeep b/docs/A-Product-Brief/.gitkeep new file mode 100644 index 00000000..379ad9b7 --- /dev/null +++ b/docs/A-Product-Brief/.gitkeep @@ -0,0 +1 @@ +# This file ensures the directory is tracked by git diff --git a/docs/B-Trigger-Map/.gitkeep b/docs/B-Trigger-Map/.gitkeep new file mode 100644 index 00000000..379ad9b7 --- /dev/null +++ b/docs/B-Trigger-Map/.gitkeep @@ -0,0 +1 @@ +# This file ensures the directory is tracked by git diff --git a/docs/C-Platform-Requirements/.gitkeep b/docs/C-Platform-Requirements/.gitkeep new file mode 100644 index 00000000..379ad9b7 --- /dev/null +++ b/docs/C-Platform-Requirements/.gitkeep @@ -0,0 +1 @@ +# This file ensures the directory is tracked by git diff --git a/docs/C-Scenarios/.gitkeep b/docs/C-Scenarios/.gitkeep new file mode 100644 index 00000000..379ad9b7 --- /dev/null +++ b/docs/C-Scenarios/.gitkeep @@ -0,0 +1 @@ +# This file ensures the directory is tracked by git diff --git a/docs/D-Design-System/.gitkeep b/docs/D-Design-System/.gitkeep new file mode 100644 index 00000000..379ad9b7 --- /dev/null +++ b/docs/D-Design-System/.gitkeep @@ -0,0 +1 @@ +# This file ensures the directory is tracked by git diff --git a/docs/E-PRD/.gitkeep b/docs/E-PRD/.gitkeep new file mode 100644 index 00000000..379ad9b7 --- /dev/null +++ b/docs/E-PRD/.gitkeep @@ -0,0 +1 @@ +# This file ensures the directory is tracked by git diff --git a/docs/E-PRD/Design-Deliveries/.gitkeep b/docs/E-PRD/Design-Deliveries/.gitkeep new file mode 100644 index 00000000..379ad9b7 --- /dev/null +++ b/docs/E-PRD/Design-Deliveries/.gitkeep @@ -0,0 +1 @@ +# This file ensures the directory is tracked by git diff --git a/docs/F-Testing/.gitkeep b/docs/F-Testing/.gitkeep new file mode 100644 index 00000000..379ad9b7 --- /dev/null +++ b/docs/F-Testing/.gitkeep @@ -0,0 +1 @@ +# This file ensures the directory is tracked by git diff --git a/docs/G-Product-Development/.gitkeep b/docs/G-Product-Development/.gitkeep new file mode 100644 index 00000000..379ad9b7 --- /dev/null +++ b/docs/G-Product-Development/.gitkeep @@ -0,0 +1 @@ +# This file ensures the directory is tracked by git diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index 8ecd869e..2780679b 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -444,6 +444,60 @@ class Installer { config._isUpdate = true; config._existingInstall = existingInstall; + // Detect modules that were previously installed but are NOT in the new selection (to be removed) + const previouslyInstalledModules = new Set(existingInstall.modules.map((m) => m.id)); + const newlySelectedModules = new Set(config.modules || []); + + // Find modules to remove (installed but not in new selection) + // Exclude 'core' from being removable + const modulesToRemove = [...previouslyInstalledModules].filter((m) => !newlySelectedModules.has(m) && m !== 'core'); + + // If there are modules to remove, ask for confirmation + if (modulesToRemove.length > 0) { + const prompts = require('../../../lib/prompts'); + spinner.stop(); + + console.log(''); + console.log(chalk.yellow.bold('⚠️ Modules to be removed:')); + for (const moduleId of modulesToRemove) { + const moduleInfo = existingInstall.modules.find((m) => m.id === moduleId); + const displayName = moduleInfo?.name || moduleId; + const modulePath = path.join(bmadDir, moduleId); + console.log(chalk.red(` - ${displayName} (${modulePath})`)); + } + console.log(''); + + const confirmRemoval = await prompts.confirm({ + message: `Remove ${modulesToRemove.length} module(s) from BMAD installation?`, + default: false, + }); + + if (confirmRemoval) { + // Remove module folders + for (const moduleId of modulesToRemove) { + const modulePath = path.join(bmadDir, moduleId); + try { + if (await fs.pathExists(modulePath)) { + await fs.remove(modulePath); + console.log(chalk.dim(` ✓ Removed: ${moduleId}`)); + } + } catch (error) { + console.warn(chalk.yellow(` Warning: Failed to remove ${moduleId}: ${error.message}`)); + } + } + console.log(chalk.green(` ✓ Removed ${modulesToRemove.length} module(s)`)); + } else { + console.log(chalk.dim(' → Module removal cancelled')); + // Add the modules back to the selection since user cancelled removal + for (const moduleId of modulesToRemove) { + if (!config.modules) config.modules = []; + config.modules.push(moduleId); + } + } + + spinner.start('Preparing update...'); + } + // Detect custom and modified files BEFORE updating (compare current files vs files-manifest.csv) const existingFilesManifest = await this.readFilesManifest(bmadDir); const { customFiles, modifiedFiles } = await this.detectCustomFiles(bmadDir, existingFilesManifest); diff --git a/tools/cli/lib/ui.js b/tools/cli/lib/ui.js index 2e710866..0728ccf5 100644 --- a/tools/cli/lib/ui.js +++ b/tools/cli/lib/ui.js @@ -252,19 +252,52 @@ class UI { const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory); console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`)); - const changeModuleSelection = await prompts.confirm({ - message: 'Modify official module selection (BMad Method, BMad Builder)?', - default: false, + + // Ask about BMad Method Module (bmm) + const wantsBmm = await prompts.confirm({ + message: + 'Select the BMad Method Module for installation?\n ---> This is the Full BMad Method Agile AI Driven Development Framework Including BMad Quick Flow', + default: installedModuleIds.has('bmm'), }); - 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]; + // Ask about BMad Builder Module (bmb) + const wantsBmb = await prompts.confirm({ + message: 'Select the BMad Builder Module for installation?\n ---> Create Your Own Custom BMad Agents, Workflows and Modules', + default: installedModuleIds.has('bmb'), + }); + + let selectedOfficialModules = []; + if (wantsBmm) { + selectedOfficialModules.push('bmm'); } + if (wantsBmb) { + selectedOfficialModules.push('bmb'); + } + + // Ask about other external modules + // Check if any external modules are already installed (not bmm, bmb, or core) + const installedExternalModules = [...installedModuleIds].filter((id) => !['bmm', 'bmb', 'core'].includes(id)); + + let selectedExternalModules = []; + // If external modules are already installed, skip confirm and go straight to selection + // Otherwise ask if they want to choose external modules + if (installedExternalModules.length > 0) { + const externalModuleChoices = await this.getExternalModuleChoices(); + selectedExternalModules = await this.selectExternalModules(externalModuleChoices, installedExternalModules); + } else { + const wantsExternalModules = await prompts.confirm({ + message: 'Would you like to choose any other Recommended BMad Core Modules for installation?', + default: false, + }); + + if (wantsExternalModules) { + const externalModuleChoices = await this.getExternalModuleChoices(); + selectedExternalModules = await this.selectExternalModules(externalModuleChoices, []); + } + } + + // Combine official and external modules + let selectedModules = [...selectedOfficialModules, ...selectedExternalModules]; // After module selection, ask about custom modules console.log(''); @@ -744,16 +777,23 @@ class UI { /** * Prompt for external module selection * @param {Array} externalModuleChoices - Available external module choices + * @param {Array} defaultSelections - Module codes to pre-select * @returns {Array} Selected external module codes */ - async selectExternalModules(externalModuleChoices) { + async selectExternalModules(externalModuleChoices, defaultSelections = []) { // Build a message showing available modules const availableNames = externalModuleChoices.map((c) => c.name).join(', '); const message = `Select official BMad modules to install ${availableNames ? chalk.dim(`(${availableNames})`) : ''} ${chalk.dim('(↑/↓ navigates multiselect, SPACE toggles, A to toggles All, ENTER confirm)')}:`; + // Mark choices as checked based on defaultSelections + const choicesWithDefaults = externalModuleChoices.map((choice) => ({ + ...choice, + checked: defaultSelections.includes(choice.value), + })); + // Add a "None" option at the end for users who changed their mind const choicesWithSkipOption = [ - ...externalModuleChoices, + ...choicesWithDefaults, { name: '⚠ None / I changed my mind - skip external module installation', value: '__NONE__',