From c563cef0c218d3f833617faccb9c506011cb2f97 Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Sun, 8 Feb 2026 19:21:48 -0600 Subject: [PATCH 1/4] refactor: replace module installer scripts with declarative directories config Removes the security-risky _module-installer pattern (code execution at install time) in favor of a declarative `directories` key in module.yaml. The main installer now handles directory creation centrally based on this config, eliminating per-module installer.js scripts and their CJS/ESM issues. Changes: - Delete src/bmm/_module-installer/installer.js - Delete src/core/_module-installer/installer.js - Add `directories` key to src/bmm/module.yaml - Rename runModuleInstaller() -> createModuleDirectories() - Remove _module-installer from ESLint overrides - Remove _module-installer from file-ref validator skip dirs --- eslint.config.mjs | 11 -- src/bmm/_module-installer/installer.js | 48 ------ src/bmm/module.yaml | 6 + src/core/_module-installer/installer.js | 60 ------- tools/cli/external-official-modules.yaml | 19 ++- .../installers/lib/core/config-collector.js | 19 +-- tools/cli/installers/lib/core/installer.js | 22 ++- tools/cli/installers/lib/custom/handler.js | 2 +- tools/cli/installers/lib/modules/manager.js | 151 +++++++++--------- tools/validate-file-refs.js | 2 +- 10 files changed, 107 insertions(+), 233 deletions(-) delete mode 100644 src/bmm/_module-installer/installer.js delete mode 100644 src/core/_module-installer/installer.js diff --git a/eslint.config.mjs b/eslint.config.mjs index d6c20f329..23bf73aa5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -114,17 +114,6 @@ export default [ }, }, - // Module installer scripts use CommonJS for compatibility - { - files: ['**/_module-installer/**/*.js'], - rules: { - // Allow CommonJS patterns for installer scripts - 'unicorn/prefer-module': 'off', - 'n/no-missing-require': 'off', - 'n/no-unpublished-require': 'off', - }, - }, - // ESLint config file should not be checked for publish-related Node rules { files: ['eslint.config.mjs'], diff --git a/src/bmm/_module-installer/installer.js b/src/bmm/_module-installer/installer.js deleted file mode 100644 index 7b844a15d..000000000 --- a/src/bmm/_module-installer/installer.js +++ /dev/null @@ -1,48 +0,0 @@ -const fs = require('fs-extra'); -const path = require('node:path'); -const chalk = require('chalk'); - -// Directories to create from config -const DIRECTORIES = ['output_folder', 'planning_artifacts', 'implementation_artifacts']; - -/** - * BMM Module Installer - * Creates output directories configured in module config - * - * @param {Object} options - Installation options - * @param {string} options.projectRoot - The root directory of the target project - * @param {Object} options.config - Module configuration from module.yaml - * @param {Array} options.installedIDEs - Array of IDE codes that were installed - * @param {Object} options.logger - Logger instance for output - * @returns {Promise} - Success status - */ -async function install(options) { - const { projectRoot, config, logger } = options; - - try { - logger.log(chalk.blue('🚀 Installing BMM Module...')); - - // Create configured directories - for (const configKey of DIRECTORIES) { - const configValue = config[configKey]; - if (!configValue) continue; - - const dirPath = configValue.replace('{project-root}/', ''); - const fullPath = path.join(projectRoot, dirPath); - - if (!(await fs.pathExists(fullPath))) { - const dirName = configKey.replace('_', ' '); - logger.log(chalk.yellow(`Creating ${dirName} directory: ${dirPath}`)); - await fs.ensureDir(fullPath); - } - } - - logger.log(chalk.green('✓ BMM Module installation complete')); - return true; - } catch (error) { - logger.error(chalk.red(`Error installing BMM module: ${error.message}`)); - return false; - } -} - -module.exports = { install }; diff --git a/src/bmm/module.yaml b/src/bmm/module.yaml index a9884e586..76f6b7433 100644 --- a/src/bmm/module.yaml +++ b/src/bmm/module.yaml @@ -42,3 +42,9 @@ project_knowledge: # Artifacts from research, document-project output, other lon prompt: "Where should long-term project knowledge be stored? (docs, research, references)" default: "docs" result: "{project-root}/{value}" + +# Directories to create during installation (declarative, no code execution) +directories: + - "{planning_artifacts}" + - "{implementation_artifacts}" + - "{project_knowledge}" diff --git a/src/core/_module-installer/installer.js b/src/core/_module-installer/installer.js deleted file mode 100644 index d77bc62fa..000000000 --- a/src/core/_module-installer/installer.js +++ /dev/null @@ -1,60 +0,0 @@ -const chalk = require('chalk'); - -/** - * Core Module Installer - * Standard module installer function that executes after IDE installations - * - * @param {Object} options - Installation options - * @param {string} options.projectRoot - The root directory of the target project - * @param {Object} options.config - Module configuration from module.yaml - * @param {Array} options.installedIDEs - Array of IDE codes that were installed - * @param {Object} options.logger - Logger instance for output - * @returns {Promise} - Success status - */ -async function install(options) { - const { projectRoot, config, installedIDEs, logger } = options; - - try { - logger.log(chalk.blue('🏗️ Installing Core Module...')); - - // Core agent configs are created by the main installer's createAgentConfigs method - // No need to create them here - they'll be handled along with all other agents - - // Handle IDE-specific configurations if needed - if (installedIDEs && installedIDEs.length > 0) { - logger.log(chalk.cyan(`Configuring Core for IDEs: ${installedIDEs.join(', ')}`)); - - // Add any IDE-specific Core configurations here - for (const ide of installedIDEs) { - await configureForIDE(ide, projectRoot, config, logger); - } - } - - logger.log(chalk.green('✓ Core Module installation complete')); - return true; - } catch (error) { - logger.error(chalk.red(`Error installing Core module: ${error.message}`)); - return false; - } -} - -/** - * Configure Core module for specific IDE - * @private - */ -async function configureForIDE(ide) { - // Add IDE-specific configurations here - switch (ide) { - case 'claude-code': { - // Claude Code specific Core configurations - break; - } - // Add more IDEs as needed - default: { - // No specific configuration needed - break; - } - } -} - -module.exports = { install }; diff --git a/tools/cli/external-official-modules.yaml b/tools/cli/external-official-modules.yaml index 431ded4a3..d6ae06ee6 100644 --- a/tools/cli/external-official-modules.yaml +++ b/tools/cli/external-official-modules.yaml @@ -42,13 +42,12 @@ modules: type: bmad-org npmPackage: bmad-method-test-architecture-enterprise -# TODO: Enable once fixes applied: - -# whiteport-design-system: -# url: https://github.com/bmad-code-org/bmad-method-wds-expansion -# module-definition: src/module.yaml -# code: WDS -# name: "Whiteport UX Design System" -# description: "UX design framework with Figma integration" -# defaultSelected: false -# type: community + # whiteport-design-system: + # url: https://github.com/bmad-code-org/bmad-method-wds-expansion + # module-definition: src/module.yaml + # code: wds + # name: "Whiteport UX Design System" + # description: "UX design framework with Figma integration" + # defaultSelected: false + # type: community + # npmPackage: bmad-method-wds-expansion diff --git a/tools/cli/installers/lib/core/config-collector.js b/tools/cli/installers/lib/core/config-collector.js index 1a0f50d29..1fd410fa3 100644 --- a/tools/cli/installers/lib/core/config-collector.js +++ b/tools/cli/installers/lib/core/config-collector.js @@ -188,20 +188,18 @@ class ConfigCollector { this.allAnswers = {}; } - // Load module's install config schema + // Load module's config schema from module.yaml // First, try the standard src/modules location - let installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'module.yaml'); let moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml'); // If not found in src/modules, we need to find it by searching the project - if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(moduleConfigPath))) { + if (!(await fs.pathExists(moduleConfigPath))) { // Use the module manager to find the module source const { ModuleManager } = require('../modules/manager'); const moduleManager = new ModuleManager(); const moduleSourcePath = await moduleManager.findModuleSource(moduleName); if (moduleSourcePath) { - installerConfigPath = path.join(moduleSourcePath, '_module-installer', 'module.yaml'); moduleConfigPath = path.join(moduleSourcePath, 'module.yaml'); } } @@ -211,8 +209,6 @@ class ConfigCollector { if (await fs.pathExists(moduleConfigPath)) { configPath = moduleConfigPath; - } else if (await fs.pathExists(installerConfigPath)) { - configPath = installerConfigPath; } else { // Check if this is a custom module with custom.yaml const { ModuleManager } = require('../modules/manager'); @@ -221,9 +217,8 @@ class ConfigCollector { if (moduleSourcePath) { const rootCustomConfigPath = path.join(moduleSourcePath, 'custom.yaml'); - const moduleInstallerCustomPath = path.join(moduleSourcePath, '_module-installer', 'custom.yaml'); - if ((await fs.pathExists(rootCustomConfigPath)) || (await fs.pathExists(moduleInstallerCustomPath))) { + if (await fs.pathExists(rootCustomConfigPath)) { isCustomModule = true; // For custom modules, we don't have an install-config schema, so just use existing values // The custom.yaml values will be loaded and merged during installation @@ -500,28 +495,24 @@ class ConfigCollector { } // Load module's config // First, check if we have a custom module path for this module - let installerConfigPath = null; let moduleConfigPath = null; if (this.customModulePaths && this.customModulePaths.has(moduleName)) { const customPath = this.customModulePaths.get(moduleName); - installerConfigPath = path.join(customPath, '_module-installer', 'module.yaml'); moduleConfigPath = path.join(customPath, 'module.yaml'); } else { // Try the standard src/modules location - installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'module.yaml'); moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml'); } // If not found in src/modules or custom paths, search the project - if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(moduleConfigPath))) { + if (!(await fs.pathExists(moduleConfigPath))) { // Use the module manager to find the module source const { ModuleManager } = require('../modules/manager'); const moduleManager = new ModuleManager(); const moduleSourcePath = await moduleManager.findModuleSource(moduleName); if (moduleSourcePath) { - installerConfigPath = path.join(moduleSourcePath, '_module-installer', 'module.yaml'); moduleConfigPath = path.join(moduleSourcePath, 'module.yaml'); } } @@ -529,8 +520,6 @@ class ConfigCollector { let configPath = null; if (await fs.pathExists(moduleConfigPath)) { configPath = moduleConfigPath; - } else if (await fs.pathExists(installerConfigPath)) { - configPath = installerConfigPath; } else { // No config for this module return; diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index 44f31090d..c25b76748 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -1070,11 +1070,11 @@ class Installer { warn: (msg) => console.warn(msg), // Always show warnings }; - // Run core module installer if core was installed + // Create directories for core module if core was installed if (config.installCore || resolution.byModule.core) { - spinner.message('Running core module installer...'); + spinner.message('Creating core module directories...'); - await this.moduleManager.runModuleInstaller('core', bmadDir, { + await this.moduleManager.createModuleDirectories('core', bmadDir, { installedIDEs: config.ides || [], moduleConfig: moduleConfigs.core || {}, coreConfig: moduleConfigs.core || {}, @@ -1083,13 +1083,13 @@ class Installer { }); } - // Run installers for user-selected modules + // Create directories for user-selected modules if (config.modules && config.modules.length > 0) { for (const moduleName of config.modules) { - spinner.message(`Running ${moduleName} module installer...`); + spinner.message(`Creating ${moduleName} module directories...`); - // Pass installed IDEs and module config to module installer - await this.moduleManager.runModuleInstaller(moduleName, bmadDir, { + // Pass installed IDEs and module config to directory creator + await this.moduleManager.createModuleDirectories(moduleName, bmadDir, { installedIDEs: config.ides || [], moduleConfig: moduleConfigs[moduleName] || {}, coreConfig: moduleConfigs.core || {}, @@ -1904,8 +1904,8 @@ class Installer { continue; } - // Skip _module-installer directory - it's only needed at install time - if (file.startsWith('_module-installer/') || file === 'module.yaml') { + // Skip module.yaml at root - it's only needed at install time + if (file === 'module.yaml') { continue; } @@ -1958,10 +1958,6 @@ class Installer { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { - // Skip _module-installer directories - if (entry.name === '_module-installer') { - continue; - } const subFiles = await this.getFileList(fullPath, baseDir); files.push(...subFiles); } else { diff --git a/tools/cli/installers/lib/custom/handler.js b/tools/cli/installers/lib/custom/handler.js index 6256e3cd2..52595e4ff 100644 --- a/tools/cli/installers/lib/custom/handler.js +++ b/tools/cli/installers/lib/custom/handler.js @@ -55,7 +55,7 @@ class CustomHandler { // Found a custom.yaml file customPaths.push(fullPath); } else if ( - entry.name === 'module.yaml' && // Check if this is a custom module (either in _module-installer or in root directory) + entry.name === 'module.yaml' && // Check if this is a custom module (in root directory) // Skip if it's in src/modules (those are standard modules) !fullPath.includes(path.join('src', 'modules')) ) { diff --git a/tools/cli/installers/lib/modules/manager.js b/tools/cli/installers/lib/modules/manager.js index a0b50048c..bc199a53d 100644 --- a/tools/cli/installers/lib/modules/manager.js +++ b/tools/cli/installers/lib/modules/manager.js @@ -236,17 +236,11 @@ class ModuleManager { async getModuleInfo(modulePath, defaultName, sourceDescription) { // Check for module structure (module.yaml OR custom.yaml) const moduleConfigPath = path.join(modulePath, 'module.yaml'); - const installerConfigPath = path.join(modulePath, '_module-installer', 'module.yaml'); - const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml'); const rootCustomConfigPath = path.join(modulePath, 'custom.yaml'); let configPath = null; if (await fs.pathExists(moduleConfigPath)) { configPath = moduleConfigPath; - } else if (await fs.pathExists(installerConfigPath)) { - configPath = installerConfigPath; - } else if (await fs.pathExists(customConfigPath)) { - configPath = customConfigPath; } else if (await fs.pathExists(rootCustomConfigPath)) { configPath = rootCustomConfigPath; } @@ -268,7 +262,7 @@ class ModuleManager { description: 'BMAD Module', version: '5.0.0', source: sourceDescription, - isCustom: configPath === customConfigPath || configPath === rootCustomConfigPath || isCustomSource, + isCustom: configPath === rootCustomConfigPath || isCustomSource, }; // Read module config for metadata @@ -541,7 +535,6 @@ class ModuleManager { // Check if this is a custom module and read its custom.yaml values let customConfig = null; const rootCustomConfigPath = path.join(sourcePath, 'custom.yaml'); - const moduleInstallerCustomPath = path.join(sourcePath, '_module-installer', 'custom.yaml'); if (await fs.pathExists(rootCustomConfigPath)) { try { @@ -550,13 +543,6 @@ class ModuleManager { } catch (error) { await prompts.log.warn(`Warning: Failed to read custom.yaml for ${moduleName}: ${error.message}`); } - } else if (await fs.pathExists(moduleInstallerCustomPath)) { - try { - const customContent = await fs.readFile(moduleInstallerCustomPath, 'utf8'); - customConfig = yaml.parse(customContent); - } catch (error) { - await prompts.log.warn(`Warning: Failed to read custom.yaml for ${moduleName}: ${error.message}`); - } } // If this is a custom module, merge its values into the module config @@ -585,9 +571,9 @@ class ModuleManager { // Process agent files to inject activation block await this.processAgentFiles(targetPath, moduleName); - // Call module-specific installer if it exists (unless explicitly skipped) + // Create directories declared in module.yaml (unless explicitly skipped) if (!options.skipModuleInstaller) { - await this.runModuleInstaller(moduleName, bmadDir, options); + await this.createModuleDirectories(moduleName, bmadDir, options); } // Capture version info for manifest @@ -743,8 +729,8 @@ class ModuleManager { continue; } - // Skip _module-installer directory - it's only needed at install time - if (file.startsWith('_module-installer/') || file === 'module.yaml') { + // Skip module.yaml at root - it's only needed at install time + if (file === 'module.yaml') { continue; } @@ -1259,80 +1245,101 @@ class ModuleManager { } /** - * Run module-specific installer if it exists + * Create directories declared in module.yaml's `directories` key + * This replaces the security-risky module installer pattern with declarative config * @param {string} moduleName - Name of the module * @param {string} bmadDir - Target bmad directory * @param {Object} options - Installation options + * @param {Object} options.moduleConfig - Module configuration from config collector + * @param {Object} options.coreConfig - Core configuration */ - async runModuleInstaller(moduleName, bmadDir, options = {}) { + async createModuleDirectories(moduleName, bmadDir, options = {}) { + const moduleConfig = options.moduleConfig || {}; + const projectRoot = path.dirname(bmadDir); + // Special handling for core module - it's in src/core not src/modules let sourcePath; if (moduleName === 'core') { sourcePath = getSourcePath('core'); } else { - sourcePath = await this.findModuleSource(moduleName, { silent: options.silent }); + sourcePath = await this.findModuleSource(moduleName, { silent: true }); if (!sourcePath) { - // No source found, skip module installer - return; + return; // No source found, skip } } - const installerDir = path.join(sourcePath, '_module-installer'); - // Prefer .cjs (always CommonJS) then fall back to .js - const cjsPath = path.join(installerDir, 'installer.cjs'); - const jsPath = path.join(installerDir, 'installer.js'); - const hasCjs = await fs.pathExists(cjsPath); - const installerPath = hasCjs ? cjsPath : jsPath; - - // Check if module has a custom installer - if (!hasCjs && !(await fs.pathExists(jsPath))) { - return; // No custom installer + // Read module.yaml to find the `directories` key + const moduleYamlPath = path.join(sourcePath, 'module.yaml'); + if (!(await fs.pathExists(moduleYamlPath))) { + return; // No module.yaml, skip } + let moduleYaml; try { - // .cjs files are always CommonJS and safe to require(). - // .js files may be ESM (when the package sets "type":"module"), - // so use dynamic import() which handles both CJS and ESM. - let moduleInstaller; - if (hasCjs) { - moduleInstaller = require(installerPath); - } else { - const { pathToFileURL } = require('node:url'); - const imported = await import(pathToFileURL(installerPath).href); - // CJS module.exports lands on .default; ESM default can be object, function, or class - moduleInstaller = imported.default == null ? imported : imported.default; + const yamlContent = await fs.readFile(moduleYamlPath, 'utf8'); + moduleYaml = yaml.parse(yamlContent); + } catch { + return; // Invalid YAML, skip + } + + if (!moduleYaml || !moduleYaml.directories) { + return; // No directories declared, skip + } + + // Get color utility for styled output + const color = await prompts.getColor(); + const directories = moduleYaml.directories; + const wdsFolders = moduleYaml.wds_folders || []; + + for (const dirRef of directories) { + // Parse variable reference like "{design_artifacts}" + const varMatch = dirRef.match(/^\{([^}]+)\}$/); + if (!varMatch) { + // Not a variable reference, skip + continue; } - if (typeof moduleInstaller.install === 'function') { - // Get project root (parent of bmad directory) - const projectRoot = path.dirname(bmadDir); + const configKey = varMatch[1]; + const dirValue = moduleConfig[configKey]; + if (!dirValue || typeof dirValue !== 'string') { + continue; // No value or not a string, skip + } - // Prepare logger (use console if not provided) - const logger = options.logger || { - log: console.log, - error: console.error, - warn: console.warn, - }; + // Strip {project-root}/ prefix if present + let dirPath = dirValue.replace(/^\{project-root\}\/?/, ''); - // Call the module installer - const result = await moduleInstaller.install({ - projectRoot, - config: options.moduleConfig || {}, - coreConfig: options.coreConfig || {}, - installedIDEs: options.installedIDEs || [], - logger, - }); + // Handle remaining {project-root} anywhere in the path + dirPath = dirPath.replaceAll('{project-root}', ''); - if (!result) { - await prompts.log.warn(`Module installer for ${moduleName} returned false`); + // Resolve to absolute path + const fullPath = path.join(projectRoot, dirPath); + + // Validate path is within project root (prevent directory traversal) + const normalizedPath = path.normalize(fullPath); + const normalizedRoot = path.normalize(projectRoot); + if (!normalizedPath.startsWith(normalizedRoot + path.sep) && normalizedPath !== normalizedRoot) { + await prompts.log.warn(color.yellow(`Warning: ${configKey} path escapes project root, skipping: ${dirPath}`)); + continue; + } + + // Create directory if it doesn't exist + if (!(await fs.pathExists(fullPath))) { + const dirName = configKey.replaceAll('_', ' '); + await prompts.log.message(color.yellow(`Creating ${dirName} directory: ${dirPath}`)); + await fs.ensureDir(fullPath); + } + + // Create WDS subfolders if this is the design_artifacts directory + if (configKey === 'design_artifacts' && wdsFolders.length > 0) { + await prompts.log.message(color.cyan('Creating WDS folder structure...')); + for (const subfolder of wdsFolders) { + const subPath = path.join(fullPath, subfolder); + if (!(await fs.pathExists(subPath))) { + await fs.ensureDir(subPath); + await prompts.log.message(color.dim(` ✓ ${subfolder}/`)); + } } } - } catch { - // Post-install scripts are optional; module files are already installed. - // TODO: Eliminate post-install scripts entirely by adding a `directories` key - // to module.yaml that declares which config keys are paths to auto-create. - // The main installer can then handle directory creation centrally, removing - // the need for per-module installer.js scripts and their CJS/ESM issues. } } @@ -1402,10 +1409,6 @@ class ModuleManager { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { - // Skip _module-installer directories - if (entry.name === '_module-installer') { - continue; - } const subFiles = await this.getFileList(fullPath, baseDir); files.push(...subFiles); } else { diff --git a/tools/validate-file-refs.js b/tools/validate-file-refs.js index 22b02da7f..bf92f31f8 100644 --- a/tools/validate-file-refs.js +++ b/tools/validate-file-refs.js @@ -42,7 +42,7 @@ const STRICT = process.argv.includes('--strict'); const SCAN_EXTENSIONS = new Set(['.yaml', '.yml', '.md', '.xml', '.csv']); // Skip directories -const SKIP_DIRS = new Set(['node_modules', '_module-installer', '.git']); +const SKIP_DIRS = new Set(['node_modules', '.git']); // Pattern: {project-root}/_bmad/ references const PROJECT_ROOT_REF = /\{project-root\}\/_bmad\/([^\s'"<>})\]`]+)/g; From 622b62743085987579075f26c3296f18aad8225e Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Sun, 8 Feb 2026 20:05:47 -0600 Subject: [PATCH 2/4] docs: update CHANGELOG for v6.0.0-Beta.8 --- CHANGELOG.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7911a7d9e..0574f9363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,58 @@ # Changelog +## [6.0.0-Beta.8] + +**Release: February 8, 2026** + +### 🌟 Key Highlights + +1. **Non-Interactive Installation** — Full CI/CD support with 10 new CLI flags for automated deployments +2. **Complete @clack/prompts Migration** — Unified CLI experience with consolidated installer output +3. **CSV File Reference Validation** — Extended Layer 1 validator to catch broken workflow references in CSV files +4. **Kiro IDE Support** — Standardized config-driven installation, replacing custom installer + +### 🎁 Features + +* **Non-Interactive Installation** — Added `--directory`, `--modules`, `--tools`, `--custom-content`, `--user-name`, `--communication-language`, `--document-output-language`, `--output-folder`, and `-y/--yes` flags for CI/CD automation (#1520) +* **CSV File Reference Validation** — Extended validator to scan `.csv` files for broken workflow references, checking 501 references across 212 files (#1573) +* **Kiro IDE Support** — Replaced broken custom installer with config-driven templates using `#[[file:...]]` syntax and `inclusion: manual` frontmatter (#1589) +* **OpenCode Template Consolidation** — Combined split templates with `mode: primary` frontmatter for Tab-switching support, fixing agent discovery (#1556) +* **Modules Reference Page** — Added official external modules reference documentation (#1540) + +### 🐛 Bug Fixes + +* **Installer Streamlining** — Removed "None - Skip module installation" option, eliminated ~100 lines of dead code, and added ESM/.cjs support for module installers (#1590) +* **CodeRabbit Workflow** — Changed `pull_request` to `pull_request_target` to fix 403 errors and enable reviews on fork PRs (#1583) +* **Party Mode Return Protocol** — Added RETURN PROTOCOL to prevent lost-in-the-middle failures after Party Mode completes (#1569) +* **Spacebar Toggle** — Fixed SPACE key not working in autocomplete multiselect prompts for tool/IDE selection (#1557) +* **OpenCode Agent Routing** — Fixed agents installing to wrong directory by adding `targets` array for routing `.opencode/agent/` vs `.opencode/command/` (#1549) +* **Technical Research Workflow** — Fixed step-05 routing to step-06 and corrected `stepsCompleted` values (#1547) +* **Forbidden Variable Removal** — Removed `workflow_path` variable from 16 workflow step files (#1546) +* **Kilo Installer** — Fixed YAML formatting issues by trimming activation header and converting to yaml.parse/stringify (#1537) +* **bmad-help** — Now reads project-specific docs and respects `communication_language` setting (#1535) +* **Cache Errors** — Removed `--prefer-offline` npm flag to prevent stale cache errors during installation (#1531) + +### ♻️ Refactoring + +* **Complete @clack/prompts Migration** — Migrated 24 files from legacy libraries (ora, chalk, boxen, figlet, etc.), replaced ~100 console.log+chalk calls, consolidated installer output to single spinner, and removed 5 dependencies (#1586) +* **Downloads Page Removal** — Removed downloads page, bundle generation, and archiver dependency in favor of GitHub's native archives (#1577) +* **Workflow Verb Standardization** — Replaced "invoke/run" with "load and follow/load" in review workflow prompts (#1570) +* **Documentation Language** — Renamed "brownfield" to "established projects" and flattened directory structure for accessibility (#1539) + +### 📚 Documentation + +* **Comprehensive Site Review** — Fixed broken directory tree diagram, corrected grammar/capitalization, added SEO descriptions, and reordered how-to guides (#1578) +* **SEO Metadata** — Added description front matter to 9 documentation pages for search engine optimization (#1566) +* **PR Template** — Added pull request template for consistent PR descriptions (#1554) +* **Manual Release Cleanup** — Removed broken manual-release workflow and related scripts (#1576) + +### 🔧 Maintenance + +* **Dual-Mode AI Code Review** — Configured Augment Code (audit mode) and CodeRabbit (adversarial mode) for improved code quality (#1511) +* **Package-Lock Sync** — Cleaned up 471 lines of orphaned dependencies after archiver removal (#1580) + +--- + ## [6.0.0-Beta.7] **Release: February 4, 2026** From 60c3477a3a0fd9388cc6f66fe3bd83c12259c935 Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Sun, 8 Feb 2026 20:06:17 -0600 Subject: [PATCH 3/4] chore: remove unused changelog-social skill --- .claude/skills/changelog-social.skill | Bin 7296 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .claude/skills/changelog-social.skill diff --git a/.claude/skills/changelog-social.skill b/.claude/skills/changelog-social.skill deleted file mode 100644 index 8ef04097da76ee4d69a0daa5febd6b5fae13d178..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7296 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 Date: Sun, 8 Feb 2026 20:06:40 -0600 Subject: [PATCH 4/4] 6.0.0-Beta.8 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f0ce7e21..da039ecc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bmad-method", - "version": "6.0.0-Beta.7", + "version": "6.0.0-Beta.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bmad-method", - "version": "6.0.0-Beta.7", + "version": "6.0.0-Beta.8", "license": "MIT", "dependencies": { "@clack/core": "^1.0.0", diff --git a/package.json b/package.json index 404548897..9cd7e90ad 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "bmad-method", - "version": "6.0.0-Beta.7", + "version": "6.0.0-Beta.8", "description": "Breakthrough Method of Agile AI-driven Development", "keywords": [ "agile",