From ffcdaf752948b7cb9bce19994d5632356778186b Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Sun, 19 Apr 2026 19:38:52 -0500 Subject: [PATCH] fix(installer): strip core-key pollution from [modules.*]; soften config headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - writeCentralConfig now always strips core-module keys from every [modules.] bucket, even when the module's schema is not available in src/ (external / marketplace modules like cis, bmb). Core values belong in [core] only; workflows read them directly. - When the module's own schema IS available (built-in modules), also drop any key it does not declare as a prompt — same spread-pollution filter as before, now layered on top. - Section-aware headers on both _bmad/config.toml and _bmad/config.user.toml: [core] / [modules.*] values are editable (installer reads them as defaults on next install); [agents.*] is regenerated from module.yaml and will be wiped — overrides for agents go in _bmad/custom/config*.toml instead. --- tools/installer/core/manifest-generator.js | 51 +++++++++++++++++----- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/tools/installer/core/manifest-generator.js b/tools/installer/core/manifest-generator.js index 32f400895..28864d3b7 100644 --- a/tools/installer/core/manifest-generator.js +++ b/tools/installer/core/manifest-generator.js @@ -423,11 +423,26 @@ class ManifestGenerator { } } - const partition = (moduleName, cfg) => { + // Core keys are always known (core module.yaml is built-in). These are + // the only keys allowed in [core]; they must be stripped from every + // non-core module bucket because legacy _bmad/{mod}/config.yaml files + // spread core values into each module. Core belongs in [core] only — + // workflows that need user_name/language/etc. read [core] directly. + const coreKeys = new Set(Object.keys(scopeByModuleKey.core || {})); + + // Partition a module's answered config into team vs user buckets. + // For non-core modules: strip core keys always; when we know the module's + // own schema, also drop keys it doesn't declare. Unknown-schema modules + // (external / marketplace) fall through with their remaining answers as + // team so they don't vanish from the config. + const partition = (moduleName, cfg, onlyDeclaredKeys = false) => { const team = {}; const user = {}; const scopes = scopeByModuleKey[moduleName] || {}; + const isCore = moduleName === 'core'; for (const [key, value] of Object.entries(cfg || {})) { + if (!isCore && coreKeys.has(key)) continue; + if (onlyDeclaredKeys && !(key in scopes)) continue; if (scopes[key] === 'user') { user[key] = value; } else { @@ -439,22 +454,33 @@ class ManifestGenerator { const teamHeader = [ '# ─────────────────────────────────────────────────────────────────', - '# DO NOT EDIT — regenerated on every install.', + '# Installer-managed. Regenerated on every install.', '#', - '# To override any value, add it to one of:', - '# _bmad/custom/config.toml (team, committed to version control)', - '# _bmad/custom/config.user.toml (personal, gitignored)', + '# [core] and [modules.] values: you CAN edit these directly.', + '# The installer reads current values as defaults on next install,', + '# so your edits persist.', + '#', + '# [agents.] values: regenerated from each module.yaml on every', + '# install. DO NOT edit here — your changes will be wiped. To override', + '# an agent descriptor or add custom agents, use:', + '# _bmad/custom/config.toml (team, committed)', + '# _bmad/custom/config.user.toml (personal, gitignored)', + '# Those files are never touched by the installer.', '# ─────────────────────────────────────────────────────────────────', '', ]; const userHeader = [ '# ─────────────────────────────────────────────────────────────────', - '# DO NOT EDIT — regenerated on every install.', - '# This file holds install answers scoped to YOU personally.', + '# Installer-managed. Regenerated on every install.', + '# Holds install answers scoped to YOU personally.', '#', - '# To override any value, add it to:', - '# _bmad/custom/config.user.toml (personal, gitignored)', + '# You CAN edit values here directly. The installer reads current', + '# values as defaults on next install, so your edits persist.', + '#', + '# For custom agents or sections the installer does not know about,', + '# use _bmad/custom/config.user.toml — it is never touched by the', + '# installer.', '# ─────────────────────────────────────────────────────────────────', '', ]; @@ -485,7 +511,12 @@ class ManifestGenerator { if (moduleName === 'core') continue; const cfg = moduleConfigs[moduleName]; if (!cfg || Object.keys(cfg).length === 0) continue; - const { team: modTeam, user: modUser } = partition(moduleName, cfg); + // Only filter out spread-from-core pollution when we actually know + // this module's prompt schema. For external/marketplace modules whose + // module.yaml isn't in the src tree, fall through as all-team so we + // don't drop their real answers. + const haveSchema = Object.keys(scopeByModuleKey[moduleName] || {}).length > 0; + const { team: modTeam, user: modUser } = partition(moduleName, cfg, haveSchema); if (Object.keys(modTeam).length > 0) { teamLines.push(`[modules.${moduleName}]`); for (const [key, value] of Object.entries(modTeam)) {