From 26d2974bcc33262d43dfa1ac0d357b0a1ec7391e Mon Sep 17 00:00:00 2001 From: Jerome Revillard Date: Mon, 27 Apr 2026 15:53:10 +0200 Subject: [PATCH 1/6] feat(config): add per-user global config layer at ~/.bmad/config/ Users working across multiple worktrees or repos no longer need to re-enter personal settings (user_name, communication_language, user_skill_level) in every project. A global user layer at ~/.bmad/config/config.user.toml is merged as the lowest-priority fallback in both resolve_config.py (5-layer) and resolve_customization.py (4-layer). Project-level overrides always win. Missing global dir is fully backward-compatible. Closes #2338 --- src/scripts/resolve_config.py | 18 +- src/scripts/resolve_customization.py | 11 +- test/test-config-resolution.js | 338 +++++++++++++++++++++++++++ 3 files changed, 357 insertions(+), 10 deletions(-) create mode 100644 test/test-config-resolution.js diff --git a/src/scripts/resolve_config.py b/src/scripts/resolve_config.py index eb9e20288..ee6d509fe 100644 --- a/src/scripts/resolve_config.py +++ b/src/scripts/resolve_config.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 """ -Resolve BMad's central config using four-layer TOML merge. +Resolve BMad's central config using five-layer TOML merge. -Reads from four layers (highest priority last): - 1. {project-root}/_bmad/config.toml (installer-owned team) - 2. {project-root}/_bmad/config.user.toml (installer-owned user) - 3. {project-root}/_bmad/custom/config.toml (human-authored team, committed) - 4. {project-root}/_bmad/custom/config.user.toml (human-authored user, gitignored) +Reads from five layers (highest priority last): + 1. ~/.bmad/config/config.user.toml (global user defaults) + 2. {project-root}/_bmad/config.toml (installer-owned team) + 3. {project-root}/_bmad/config.user.toml (installer-owned user) + 4. {project-root}/_bmad/custom/config.toml (human-authored team, committed) + 5. {project-root}/_bmad/custom/config.user.toml (human-authored user, gitignored) Outputs merged JSON to stdout. Errors go to stderr. @@ -40,6 +41,7 @@ except ImportError: _MISSING = object() _KEYED_MERGE_FIELDS = ("code", "id") +GLOBAL_DIR = Path.home() / ".bmad" / "config" def load_toml(file_path: Path, required: bool = False) -> dict: @@ -151,12 +153,14 @@ def main(): project_root = Path(args.project_root).resolve() bmad_dir = project_root / "_bmad" + global_user = load_toml(GLOBAL_DIR / "config.user.toml") base_team = load_toml(bmad_dir / "config.toml", required=True) base_user = load_toml(bmad_dir / "config.user.toml") custom_team = load_toml(bmad_dir / "custom" / "config.toml") custom_user = load_toml(bmad_dir / "custom" / "config.user.toml") - merged = deep_merge(base_team, base_user) + merged = deep_merge(global_user, base_team) + merged = deep_merge(merged, base_user) merged = deep_merge(merged, custom_team) merged = deep_merge(merged, custom_user) diff --git a/src/scripts/resolve_customization.py b/src/scripts/resolve_customization.py index 28901ed0f..d2d6b3326 100755 --- a/src/scripts/resolve_customization.py +++ b/src/scripts/resolve_customization.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 """ -Resolve customization for a BMad skill using three-layer TOML merge. +Resolve customization for a BMad skill using four-layer TOML merge. -Reads customization from three layers (highest priority first): +Reads customization from four layers (highest priority first): 1. {project-root}/_bmad/custom/{name}.user.toml (personal, gitignored) 2. {project-root}/_bmad/custom/{name}.toml (team/org, committed) 3. {skill-root}/customize.toml (skill defaults) + 4. ~/.bmad/config/{name}.user.toml (global user defaults) Skill name is derived from the basename of the skill directory. @@ -51,6 +52,7 @@ except ImportError: _MISSING = object() _KEYED_MERGE_FIELDS = ("code", "id") +GLOBAL_DIR = Path.home() / ".bmad" / "config" def find_project_root(start: Path): @@ -211,7 +213,10 @@ def main(): team = load_toml(custom_dir / f"{skill_name}.toml") user = load_toml(custom_dir / f"{skill_name}.user.toml") - merged = deep_merge(defaults, team) + global_user = load_toml(GLOBAL_DIR / f"{skill_name}.user.toml") + + merged = deep_merge(global_user, defaults) + merged = deep_merge(merged, team) merged = deep_merge(merged, user) if args.key: diff --git a/test/test-config-resolution.js b/test/test-config-resolution.js new file mode 100644 index 000000000..5db486a46 --- /dev/null +++ b/test/test-config-resolution.js @@ -0,0 +1,338 @@ +/** + * Config Resolution Tests + * + * Tests the Python config resolution scripts by invoking them as subprocesses + * with temporary TOML fixtures. Validates: + * - Global user layer is loaded and merged with correct priority + * - Project layers override global layers + * - Missing global dir doesn't break anything (backward compat) + * - resolve_customization.py global skill layer + * + * Usage: node test/test-config-resolution.js + */ + +const path = require('node:path'); +const os = require('node:os'); +const fs = require('node:fs/promises'); +const { execSync } = require('node:child_process'); + +const SCRIPTS_DIR = path.resolve(__dirname, '..', 'src', 'scripts'); + +const colors = { + reset: '', + green: '', + red: '', + dim: '', +}; + +let passed = 0; +let failed = 0; + +function assert(condition, testName, errorMessage = '') { + if (condition) { + console.log(`${colors.green}✓${colors.reset} ${testName}`); + passed++; + } else { + console.log(`${colors.red}✗${colors.reset} ${testName}`); + if (errorMessage) { + console.log(` ${colors.dim}${errorMessage}${colors.reset}`); + } + failed++; + } +} + +function writeToml(filePath, data) { + const lines = []; + for (const [key, val] of Object.entries(data)) { + if (typeof val === 'string') { + lines.push(`${key} = "${val}"`); + } else if (typeof val === 'number' || typeof val === 'boolean') { + lines.push(`${key} = ${val}`); + } + } + return fs.writeFile(filePath, lines.join('\n') + '\n'); +} + +async function withTempDir(fn) { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-config-test-')); + try { + return await fn(dir); + } finally { + await fs.rm(dir, { recursive: true, force: true }); + } +} + +async function testResolveConfig() { + console.log('\n--- resolve_config.py ---\n'); + + // Test 1: global user layer is loaded + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'config.user.toml'), { + user_name: 'GlobalAlice', + communication_language: 'en', + }); + + const projectDir = path.join(tmpDir, 'project'); + const bmadDir = path.join(projectDir, '_bmad'); + await fs.mkdir(path.join(bmadDir, 'custom'), { recursive: true }); + await writeToml(path.join(bmadDir, 'config.toml'), { + project_name: 'TestProject', + user_name: 'ProjectAlice', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_config.py --project-root ${projectDir}`, { encoding: 'utf-8' })); + + assert( + result.user_name === 'ProjectAlice', + 'project config.user overrides global user_name', + `Expected "ProjectAlice", got "${result.user_name}"`, + ); + assert( + result.communication_language === 'en', + 'global communication_language preserved when project has no override', + `Expected "en", got "${result.communication_language}"`, + ); + assert( + result.project_name === 'TestProject', + 'project config values preserved', + `Expected "TestProject", got "${result.project_name}"`, + ); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 2: missing global dir — backward compat + await withTempDir(async (tmpDir) => { + const projectDir = path.join(tmpDir, 'project'); + const bmadDir = path.join(projectDir, '_bmad'); + await fs.mkdir(path.join(bmadDir, 'custom'), { recursive: true }); + await writeToml(path.join(bmadDir, 'config.toml'), { + project_name: 'NoGlobalProject', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_config.py --project-root ${projectDir}`, { encoding: 'utf-8' })); + + assert( + result.project_name === 'NoGlobalProject', + 'works fine without global config dir', + `Expected "NoGlobalProject", got "${result.project_name}"`, + ); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 3: full priority chain — global < base_team < base_user < custom_team < custom_user + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'config.user.toml'), { + user_name: 'L0-Global', + }); + + const projectDir = path.join(tmpDir, 'project'); + const bmadDir = path.join(projectDir, '_bmad'); + await fs.mkdir(path.join(bmadDir, 'custom'), { recursive: true }); + await writeToml(path.join(bmadDir, 'config.toml'), { user_name: 'L1-BaseTeam' }); + await writeToml(path.join(bmadDir, 'config.user.toml'), { user_name: 'L2-BaseUser' }); + await writeToml(path.join(bmadDir, 'custom', 'config.toml'), { user_name: 'L3-CustomTeam' }); + await writeToml(path.join(bmadDir, 'custom', 'config.user.toml'), { user_name: 'L4-CustomUser' }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_config.py --project-root ${projectDir}`, { encoding: 'utf-8' })); + + assert( + result.user_name === 'L4-CustomUser', + 'highest priority layer (custom user) wins', + `Expected "L4-CustomUser", got "${result.user_name}"`, + ); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 4: --key flag works with global layer + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'config.user.toml'), { + user_name: 'KeyTestGlobal', + communication_language: 'fr', + }); + + const projectDir = path.join(tmpDir, 'project'); + const bmadDir = path.join(projectDir, '_bmad'); + await fs.mkdir(path.join(bmadDir, 'custom'), { recursive: true }); + await writeToml(path.join(bmadDir, 'config.toml'), { + project_name: 'KeyTestProject', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse( + execSync(`python3 ${SCRIPTS_DIR}/resolve_config.py --project-root ${projectDir} --key user_name --key communication_language`, { + encoding: 'utf-8', + }), + ); + + assert( + Object.keys(result).length === 2, + '--key returns only requested keys', + `Expected 2 keys, got ${Object.keys(result).length}: ${JSON.stringify(Object.keys(result))}`, + ); + assert( + result.user_name === 'KeyTestGlobal', + '--key user_name returns global value', + `Expected "KeyTestGlobal", got "${result.user_name}"`, + ); + assert( + result.communication_language === 'fr', + '--key communication_language returns global value', + `Expected "fr", got "${result.communication_language}"`, + ); + } finally { + process.env.HOME = origHome; + } + }); +} + +async function testResolveCustomization() { + console.log('\n--- resolve_customization.py ---\n'); + + // Test 1: global skill user layer is loaded + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'test-skill.user.toml'), { + agent: 'global-agent-prompt', + }); + + const skillDir = path.join(tmpDir, 'skill', 'test-skill'); + await fs.mkdir(skillDir, { recursive: true }); + await writeToml(path.join(skillDir, 'customize.toml'), { + agent: 'default-agent-prompt', + version: '1.0.0', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_customization.py --skill ${skillDir}`, { encoding: 'utf-8' })); + + assert( + result.agent === 'default-agent-prompt', + 'skill defaults override global user layer', + `Expected "default-agent-prompt", got "${result.agent}"`, + ); + assert(result.version === '1.0.0', 'skill default values preserved', `Expected "1.0.0", got "${result.version}"`); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 2: global skill layer provides value not in defaults + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'test-skill.user.toml'), { + extra_global_key: 'from-global', + }); + + const skillDir = path.join(tmpDir, 'skill', 'test-skill'); + await fs.mkdir(skillDir, { recursive: true }); + await writeToml(path.join(skillDir, 'customize.toml'), { + version: '2.0.0', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_customization.py --skill ${skillDir}`, { encoding: 'utf-8' })); + + assert( + result.extra_global_key === 'from-global', + 'global key not in defaults is preserved', + `Expected "from-global", got "${result.extra_global_key}"`, + ); + assert(result.version === '2.0.0', 'skill defaults still present', `Expected "2.0.0", got "${result.version}"`); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 3: missing global dir — backward compat + await withTempDir(async (tmpDir) => { + const skillDir = path.join(tmpDir, 'skill', 'test-skill'); + await fs.mkdir(skillDir, { recursive: true }); + await writeToml(path.join(skillDir, 'customize.toml'), { + version: '3.0.0', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_customization.py --skill ${skillDir}`, { encoding: 'utf-8' })); + + assert(result.version === '3.0.0', 'works without global config dir', `Expected "3.0.0", got "${result.version}"`); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 4: full priority chain — global < defaults < team < user + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'test-skill.user.toml'), { + agent: 'L0-Global', + }); + + const skillDir = path.join(tmpDir, 'project', '_bmad', 'skills', 'test-skill'); + await fs.mkdir(skillDir, { recursive: true }); + await writeToml(path.join(skillDir, 'customize.toml'), { agent: 'L1-Defaults' }); + + const customDir = path.join(tmpDir, 'project', '_bmad', 'custom'); + await fs.mkdir(customDir, { recursive: true }); + await writeToml(path.join(customDir, 'test-skill.toml'), { agent: 'L2-Team' }); + await writeToml(path.join(customDir, 'test-skill.user.toml'), { agent: 'L3-User' }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_customization.py --skill ${skillDir}`, { encoding: 'utf-8' })); + + assert(result.agent === 'L3-User', 'highest priority layer (project user) wins', `Expected "L3-User", got "${result.agent}"`); + } finally { + process.env.HOME = origHome; + } + }); +} + +async function main() { + console.log('Config Resolution Tests\n'); + + try { + await testResolveConfig(); + await testResolveCustomization(); + } catch (error) { + console.error(`${colors.red}Fatal error: ${error.message}${colors.reset}`); + process.exit(1); + } + + console.log(`\n${colors.green}${passed} passed${colors.reset}, ${colors.red}${failed} failed${colors.reset}`); + process.exit(failed > 0 ? 1 : 0); +} + +main(); From e37aab98f66f1b831df237cc7107cbf29e5a7e7b Mon Sep 17 00:00:00 2001 From: Jerome Revillard Date: Mon, 27 Apr 2026 16:23:46 +0200 Subject: [PATCH 2/6] =?UTF-8?q?fix(config):=20correct=20global=20layer=20p?= =?UTF-8?q?riority=20=E2=80=94=20overrides=20installer=20defaults?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global user layer was lowest priority, meaning installer-generated defaults in _bmad/config.user.toml always shadowed it. Reorder so global user preferences override installer defaults but are still overridden by hand-authored project customizations. New order for resolve_config.py: base_team → base_user → global_user → custom_team → custom_user New order for resolve_customization.py: defaults → global_user → team → user --- src/scripts/resolve_config.py | 12 ++++----- src/scripts/resolve_customization.py | 6 ++--- test/test-config-resolution.js | 39 +++++++++++++++------------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/scripts/resolve_config.py b/src/scripts/resolve_config.py index ee6d509fe..6ddd20e02 100644 --- a/src/scripts/resolve_config.py +++ b/src/scripts/resolve_config.py @@ -3,9 +3,9 @@ Resolve BMad's central config using five-layer TOML merge. Reads from five layers (highest priority last): - 1. ~/.bmad/config/config.user.toml (global user defaults) - 2. {project-root}/_bmad/config.toml (installer-owned team) - 3. {project-root}/_bmad/config.user.toml (installer-owned user) + 1. {project-root}/_bmad/config.toml (installer-owned team) + 2. {project-root}/_bmad/config.user.toml (installer-owned user) + 3. ~/.bmad/config/config.user.toml (global user preferences) 4. {project-root}/_bmad/custom/config.toml (human-authored team, committed) 5. {project-root}/_bmad/custom/config.user.toml (human-authored user, gitignored) @@ -153,14 +153,14 @@ def main(): project_root = Path(args.project_root).resolve() bmad_dir = project_root / "_bmad" - global_user = load_toml(GLOBAL_DIR / "config.user.toml") base_team = load_toml(bmad_dir / "config.toml", required=True) base_user = load_toml(bmad_dir / "config.user.toml") + global_user = load_toml(GLOBAL_DIR / "config.user.toml") custom_team = load_toml(bmad_dir / "custom" / "config.toml") custom_user = load_toml(bmad_dir / "custom" / "config.user.toml") - merged = deep_merge(global_user, base_team) - merged = deep_merge(merged, base_user) + merged = deep_merge(base_team, base_user) + merged = deep_merge(merged, global_user) merged = deep_merge(merged, custom_team) merged = deep_merge(merged, custom_user) diff --git a/src/scripts/resolve_customization.py b/src/scripts/resolve_customization.py index d2d6b3326..634c5baab 100755 --- a/src/scripts/resolve_customization.py +++ b/src/scripts/resolve_customization.py @@ -5,8 +5,8 @@ Resolve customization for a BMad skill using four-layer TOML merge. Reads customization from four layers (highest priority first): 1. {project-root}/_bmad/custom/{name}.user.toml (personal, gitignored) 2. {project-root}/_bmad/custom/{name}.toml (team/org, committed) - 3. {skill-root}/customize.toml (skill defaults) - 4. ~/.bmad/config/{name}.user.toml (global user defaults) + 3. ~/.bmad/config/{name}.user.toml (global user preferences) + 4. {skill-root}/customize.toml (skill defaults) Skill name is derived from the basename of the skill directory. @@ -215,7 +215,7 @@ def main(): global_user = load_toml(GLOBAL_DIR / f"{skill_name}.user.toml") - merged = deep_merge(global_user, defaults) + merged = deep_merge(defaults, global_user) merged = deep_merge(merged, team) merged = deep_merge(merged, user) diff --git a/test/test-config-resolution.js b/test/test-config-resolution.js index 5db486a46..e19baf103 100644 --- a/test/test-config-resolution.js +++ b/test/test-config-resolution.js @@ -65,7 +65,7 @@ async function withTempDir(fn) { async function testResolveConfig() { console.log('\n--- resolve_config.py ---\n'); - // Test 1: global user layer is loaded + // Test 1: global user overrides installer defaults await withTempDir(async (tmpDir) => { const globalDir = path.join(tmpDir, '.bmad', 'config'); await fs.mkdir(globalDir, { recursive: true }); @@ -79,7 +79,10 @@ async function testResolveConfig() { await fs.mkdir(path.join(bmadDir, 'custom'), { recursive: true }); await writeToml(path.join(bmadDir, 'config.toml'), { project_name: 'TestProject', - user_name: 'ProjectAlice', + user_name: 'InstallerDefault', + }); + await writeToml(path.join(bmadDir, 'config.user.toml'), { + user_name: 'InstallerUserDefault', }); const origHome = process.env.HOME; @@ -88,18 +91,18 @@ async function testResolveConfig() { const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_config.py --project-root ${projectDir}`, { encoding: 'utf-8' })); assert( - result.user_name === 'ProjectAlice', - 'project config.user overrides global user_name', - `Expected "ProjectAlice", got "${result.user_name}"`, + result.user_name === 'GlobalAlice', + 'global user overrides installer defaults', + `Expected "GlobalAlice", got "${result.user_name}"`, ); assert( result.communication_language === 'en', - 'global communication_language preserved when project has no override', + 'global communication_language preserved when installer has no override', `Expected "en", got "${result.communication_language}"`, ); assert( result.project_name === 'TestProject', - 'project config values preserved', + 'installer team config values preserved', `Expected "TestProject", got "${result.project_name}"`, ); } finally { @@ -131,19 +134,19 @@ async function testResolveConfig() { } }); - // Test 3: full priority chain — global < base_team < base_user < custom_team < custom_user + // Test 3: full priority chain — base_team < base_user < global < custom_team < custom_user await withTempDir(async (tmpDir) => { const globalDir = path.join(tmpDir, '.bmad', 'config'); await fs.mkdir(globalDir, { recursive: true }); await writeToml(path.join(globalDir, 'config.user.toml'), { - user_name: 'L0-Global', + user_name: 'L2-Global', }); const projectDir = path.join(tmpDir, 'project'); const bmadDir = path.join(projectDir, '_bmad'); await fs.mkdir(path.join(bmadDir, 'custom'), { recursive: true }); - await writeToml(path.join(bmadDir, 'config.toml'), { user_name: 'L1-BaseTeam' }); - await writeToml(path.join(bmadDir, 'config.user.toml'), { user_name: 'L2-BaseUser' }); + await writeToml(path.join(bmadDir, 'config.toml'), { user_name: 'L0-BaseTeam' }); + await writeToml(path.join(bmadDir, 'config.user.toml'), { user_name: 'L1-BaseUser' }); await writeToml(path.join(bmadDir, 'custom', 'config.toml'), { user_name: 'L3-CustomTeam' }); await writeToml(path.join(bmadDir, 'custom', 'config.user.toml'), { user_name: 'L4-CustomUser' }); @@ -211,7 +214,7 @@ async function testResolveConfig() { async function testResolveCustomization() { console.log('\n--- resolve_customization.py ---\n'); - // Test 1: global skill user layer is loaded + // Test 1: global skill user overrides skill defaults await withTempDir(async (tmpDir) => { const globalDir = path.join(tmpDir, '.bmad', 'config'); await fs.mkdir(globalDir, { recursive: true }); @@ -232,9 +235,9 @@ async function testResolveCustomization() { const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_customization.py --skill ${skillDir}`, { encoding: 'utf-8' })); assert( - result.agent === 'default-agent-prompt', - 'skill defaults override global user layer', - `Expected "default-agent-prompt", got "${result.agent}"`, + result.agent === 'global-agent-prompt', + 'global user overrides skill defaults', + `Expected "global-agent-prompt", got "${result.agent}"`, ); assert(result.version === '1.0.0', 'skill default values preserved', `Expected "1.0.0", got "${result.version}"`); } finally { @@ -291,17 +294,17 @@ async function testResolveCustomization() { } }); - // Test 4: full priority chain — global < defaults < team < user + // Test 4: full priority chain — defaults < global < team < user await withTempDir(async (tmpDir) => { const globalDir = path.join(tmpDir, '.bmad', 'config'); await fs.mkdir(globalDir, { recursive: true }); await writeToml(path.join(globalDir, 'test-skill.user.toml'), { - agent: 'L0-Global', + agent: 'L1-Global', }); const skillDir = path.join(tmpDir, 'project', '_bmad', 'skills', 'test-skill'); await fs.mkdir(skillDir, { recursive: true }); - await writeToml(path.join(skillDir, 'customize.toml'), { agent: 'L1-Defaults' }); + await writeToml(path.join(skillDir, 'customize.toml'), { agent: 'L0-Defaults' }); const customDir = path.join(tmpDir, 'project', '_bmad', 'custom'); await fs.mkdir(customDir, { recursive: true }); From cb4f85223c676bb9cf2cad0381825b8be1138b39 Mon Sep 17 00:00:00 2001 From: Jerome Revillard Date: Mon, 27 Apr 2026 16:29:14 +0200 Subject: [PATCH 3/6] fix: update argparse descriptions to match actual layer count --- src/scripts/resolve_config.py | 2 +- src/scripts/resolve_customization.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/resolve_config.py b/src/scripts/resolve_config.py index 6ddd20e02..5f6c8d7bd 100644 --- a/src/scripts/resolve_config.py +++ b/src/scripts/resolve_config.py @@ -138,7 +138,7 @@ def extract_key(data, dotted_key: str): def main(): parser = argparse.ArgumentParser( - description="Resolve BMad central config using four-layer TOML merge.", + description="Resolve BMad central config using five-layer TOML merge.", ) parser.add_argument( "--project-root", "-p", required=True, diff --git a/src/scripts/resolve_customization.py b/src/scripts/resolve_customization.py index 634c5baab..d8af2d26f 100755 --- a/src/scripts/resolve_customization.py +++ b/src/scripts/resolve_customization.py @@ -181,7 +181,7 @@ def extract_key(data, dotted_key: str): def main(): parser = argparse.ArgumentParser( - description="Resolve customization for a BMad skill using three-layer TOML merge.", + description="Resolve customization for a BMad skill using four-layer TOML merge.", add_help=True, ) parser.add_argument( From 8e7d5b4beab23230769db28d2d5caa3d5ce544a6 Mon Sep 17 00:00:00 2001 From: Jerome Revillard Date: Mon, 27 Apr 2026 18:16:19 +0200 Subject: [PATCH 4/6] fix(skills): use resolve_config.py instead of reading config.yaml directly Skills were reading _bmad/bmm|core/config.yaml directly, bypassing the TOML merge mechanism. Now they call resolve_config.py first, with a fallback to read the merge logic and apply it manually. --- .../1-analysis/bmad-agent-analyst/SKILL.md | 2 +- .../1-analysis/bmad-agent-tech-writer/SKILL.md | 2 +- .../1-analysis/bmad-document-project/SKILL.md | 2 +- .../workflows/deep-dive-workflow.md | 2 +- .../workflows/full-scan-workflow.md | 2 +- src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md | 2 +- .../1-analysis/bmad-product-brief/SKILL.md | 9 ++++++++- .../research/bmad-domain-research/SKILL.md | 2 +- .../research/bmad-market-research/SKILL.md | 2 +- .../research/bmad-technical-research/SKILL.md | 2 +- .../2-plan-workflows/bmad-agent-pm/SKILL.md | 2 +- .../bmad-agent-ux-designer/SKILL.md | 2 +- .../2-plan-workflows/bmad-create-prd/SKILL.md | 2 +- .../bmad-create-ux-design/SKILL.md | 2 +- .../2-plan-workflows/bmad-edit-prd/SKILL.md | 2 +- .../2-plan-workflows/bmad-validate-prd/SKILL.md | 2 +- .../3-solutioning/bmad-agent-architect/SKILL.md | 2 +- .../bmad-check-implementation-readiness/SKILL.md | 2 +- .../bmad-create-architecture/SKILL.md | 2 +- .../bmad-create-epics-and-stories/SKILL.md | 2 +- .../bmad-generate-project-context/SKILL.md | 2 +- .../4-implementation/bmad-agent-dev/SKILL.md | 2 +- .../bmad-checkpoint-preview/SKILL.md | 14 +++++++++++++- .../4-implementation/bmad-correct-course/SKILL.md | 2 +- .../4-implementation/bmad-create-story/SKILL.md | 2 +- .../bmad-qa-generate-e2e-tests/SKILL.md | 2 +- .../4-implementation/bmad-retrospective/SKILL.md | 2 +- src/core-skills/bmad-brainstorming/workflow.md | 2 +- src/core-skills/bmad-help/SKILL.md | 2 +- src/core-skills/bmad-party-mode/SKILL.md | 2 +- 30 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md b/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md index 4653171df..daa69616f 100644 --- a/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md b/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md index ff6430d93..8b5749163 100644 --- a/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md b/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md index 112732031..0a628e291 100644 --- a/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-workflow.md b/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-workflow.md index c55f036a7..1796ea8d8 100644 --- a/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-workflow.md +++ b/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-workflow.md @@ -11,7 +11,7 @@ ### Configuration Loading -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_knowledge` - `user_name` diff --git a/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-workflow.md b/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-workflow.md index 5aaf4a580..367179ec3 100644 --- a/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-workflow.md +++ b/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-workflow.md @@ -10,7 +10,7 @@ ### Configuration Loading -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_knowledge` - `user_name` diff --git a/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md b/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md index 6ce2d33ed..55338d6f9 100644 --- a/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md @@ -50,7 +50,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md b/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md index 0d26145af..1b5614e6f 100644 --- a/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md @@ -21,7 +21,7 @@ Briefs produced here are honest, right-sized to purpose, and built for what come 1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, surface the diagnostic and halt. 2. Execute each entry in `{workflow.activation_steps_prepend}` in order. 3. Treat every entry in `{workflow.persistent_facts}` as foundational context for the rest of the run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim. -4. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. +4. Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. 5. Greet `{user_name}` in `{communication_language}`. Detect intent (create / update / validate). If interactive and intent is unclear, ask; for headless behavior see `## Headless Mode`. 6. Execute each entry in `{workflow.activation_steps_append}` in order. @@ -59,6 +59,13 @@ When invoked headless, do not ask. Complete the intent using what is provided, w Omit keys for artifacts that were not produced. +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: +- Use `{user_name}` for greeting +- Use `{communication_language}` for all communications +- Use `{document_output_language}` for output documents +- Use `{planning_artifacts}` for output location and artifact scanning +- Use `{project_knowledge}` for additional context scanning + ## Discovery Conversationally surface what the user brings, why this brief exists, and the domain — echo back how each shapes your approach. Open with space for the full picture: invite a brain dump and ask up front for any source material they already have (memo, deck, transcript, prior brief, slack thread). Read what exists first; ask only what is missing. After the dump, a simple "anything else?" often surfaces what they almost forgot. Drill into specifics only after the broad shape is on the table; premature granular questions interrupt the dump and miss the room. Get a read on stakes early (passion project, internal pitch, investor input, public launch), and let that calibrate how hard you push. Suggest research (web, competitive, market) only when the stakes warrant it. diff --git a/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md b/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md index be364aa2f..d4fa24e05 100644 --- a/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +++ b/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md @@ -44,7 +44,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md b/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md index 964049085..eb96b59f8 100644 --- a/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +++ b/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md @@ -44,7 +44,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md b/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md index 582a05c60..e46a82239 100644 --- a/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +++ b/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md @@ -44,7 +44,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md index 693072603..9ab429a27 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md index cb261c3fb..2e666a9b8 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md index 1ad02d01d..c32eff2fe 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md @@ -73,7 +73,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md index 496473b1e..76f865681 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md @@ -47,7 +47,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md index e209df340..3b5c7526c 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md @@ -73,7 +73,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md index 90ec68f17..28c2797ea 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md @@ -73,7 +73,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md b/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md index 1650aee09..52e9829b4 100644 --- a/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md b/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md index 1d5133f90..725ce3e31 100644 --- a/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md @@ -69,7 +69,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md index ca89a71cf..ebe478f73 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md @@ -50,7 +50,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md index a3f0f61c8..c535de75e 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md @@ -71,7 +71,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md b/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md index 42fd2e8fc..13e712fcd 100644 --- a/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md @@ -50,7 +50,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md b/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md index 95a3b9594..4d2ec3711 100644 --- a/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md b/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md index 101dcf2bc..8439e4d6c 100644 --- a/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `implementation_artifacts` - `planning_artifacts` @@ -63,6 +63,18 @@ Activation is complete. Begin the workflow below. - **Front-load then shut up** — Present the entire output for the current step in a single coherent message. Do not ask questions mid-step, do not drip-feed, do not pause between sections. - **Language** — Speak in `{communication_language}`. Write any file output in `{document_output_language}`. +<<<<<<< HEAD +======= +## INITIALIZATION + +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: + +- `implementation_artifacts` +- `planning_artifacts` +- `communication_language` +- `document_output_language` + +>>>>>>> 3846e184 (fix(skills): use resolve_config.py instead of reading config.yaml directly) ## FIRST STEP Read fully and follow `./step-01-orientation.md` to begin. diff --git a/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md b/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md index adea0bda0..0456d63dd 100644 --- a/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md b/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md index cf14039c1..516095eee 100644 --- a/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md @@ -47,7 +47,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md b/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md index ef9d7e87a..655130c71 100644 --- a/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md b/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md index b6d0c96c6..e1cdb2764 100644 --- a/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md @@ -56,7 +56,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/core-skills/bmad-brainstorming/workflow.md b/src/core-skills/bmad-brainstorming/workflow.md index 168dab93e..7eccef9b7 100644 --- a/src/core-skills/bmad-brainstorming/workflow.md +++ b/src/core-skills/bmad-brainstorming/workflow.md @@ -32,7 +32,7 @@ This uses **micro-file architecture** for disciplined execution: ### Configuration Loading -Load config from `{project-root}/_bmad/core/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `output_folder`, `user_name` - `communication_language`, `document_output_language`, `user_skill_level` diff --git a/src/core-skills/bmad-help/SKILL.md b/src/core-skills/bmad-help/SKILL.md index 62b06a747..85e3e6e16 100644 --- a/src/core-skills/bmad-help/SKILL.md +++ b/src/core-skills/bmad-help/SKILL.md @@ -23,7 +23,7 @@ When this skill completes, the user should: ## Data Sources - **Catalog**: `{project-root}/_bmad/_config/bmad-help.csv` — assembled manifest of all installed module skills -- **Config**: `config.yaml` and `user-config.yaml` files in `{project-root}/_bmad/` and its subfolders — resolve `output-location` variables, provide `communication_language` and `project_knowledge` +- **Config**: Run `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` to get merged config. If that fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself — resolve `output-location` variables, provide `communication_language` and `project_knowledge` - **Artifacts**: Files matching `outputs` patterns at resolved `output-location` paths reveal which steps are possibly completed; their content may also provide grounding context for recommendations - **Project knowledge**: If `project_knowledge` resolves to an existing path, read it for grounding context. Never fabricate project-specific details. - **Module docs**: Rows with `_meta` in the `skill` column carry a URL or path in `output-location` pointing to the module's documentation (e.g., llms.txt). Fetch and use these to answer general questions about that module. diff --git a/src/core-skills/bmad-party-mode/SKILL.md b/src/core-skills/bmad-party-mode/SKILL.md index 6f4ee3e63..6f3906da8 100644 --- a/src/core-skills/bmad-party-mode/SKILL.md +++ b/src/core-skills/bmad-party-mode/SKILL.md @@ -22,7 +22,7 @@ Party mode accepts optional arguments when invoked: 1. **Parse arguments** — check for `--model` and `--solo` flags from the user's invocation. -2. Load config from `{project-root}/_bmad/core/config.yaml` and resolve: +2. Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications From 33d6f7a5188b9aa1d2dc09f284648a390f46ddce Mon Sep 17 00:00:00 2001 From: Jerome Revillard Date: Mon, 27 Apr 2026 18:25:23 +0200 Subject: [PATCH 5/6] fix(skills): update config resolution in SKILL.md files migrated from workflow.md The 5 skills whose workflow.md was absorbed into SKILL.md by PR #2308 still had the old config.yaml loading instruction. Updated them to use resolve_config.py like all other skills. --- src/bmm-skills/4-implementation/bmad-code-review/SKILL.md | 2 +- src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md | 2 +- src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md | 2 +- src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md | 2 +- src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md b/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md index 44223f11a..909a86477 100644 --- a/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `planning_artifacts`, `implementation_artifacts`, `user_name` - `communication_language`, `document_output_language`, `user_skill_level` diff --git a/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md b/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md index 218b234ab..3db2e9954 100644 --- a/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md @@ -47,7 +47,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md b/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md index f5326fc3f..c0ba1b8a1 100644 --- a/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md @@ -59,7 +59,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `planning_artifacts`, `implementation_artifacts`, `user_name` - `communication_language`, `document_output_language`, `user_skill_level` diff --git a/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md b/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md index 25266d716..887bd45bf 100644 --- a/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md b/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md index c52a84947..57273ba05 100644 --- a/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` From 4ef741a2731fda5128668971f5c05ac47346ce02 Mon Sep 17 00:00:00 2001 From: Jerome Revillard Date: Wed, 13 May 2026 08:48:50 +0200 Subject: [PATCH 6/6] fix: remove leftover conflict markers in bmad-checkpoint-preview SKILL.md The ## INITIALIZATION section was redundant with Step 4: Load Config in the On Activation steps above. --- .../bmad-checkpoint-preview/SKILL.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md b/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md index 8439e4d6c..db1d7fba2 100644 --- a/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md @@ -63,18 +63,6 @@ Activation is complete. Begin the workflow below. - **Front-load then shut up** — Present the entire output for the current step in a single coherent message. Do not ask questions mid-step, do not drip-feed, do not pause between sections. - **Language** — Speak in `{communication_language}`. Write any file output in `{document_output_language}`. -<<<<<<< HEAD -======= -## INITIALIZATION - -Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - -- `implementation_artifacts` -- `planning_artifacts` -- `communication_language` -- `document_output_language` - ->>>>>>> 3846e184 (fix(skills): use resolve_config.py instead of reading config.yaml directly) ## FIRST STEP Read fully and follow `./step-01-orientation.md` to begin.