Skip to content

feat(config): per-user global config across worktrees#2339

Open
jrevillard wants to merge 5 commits intobmad-code-org:mainfrom
jrevillard:feat/global-user-config
Open

feat(config): per-user global config across worktrees#2339
jrevillard wants to merge 5 commits intobmad-code-org:mainfrom
jrevillard:feat/global-user-config

Conversation

@jrevillard
Copy link
Copy Markdown
Contributor

Summary

  • Add a global user config layer at ~/.bmad/config/ that applies as lowest-priority fallback across all projects and worktrees
  • resolve_config.py now merges 5 layers (was 4): global user → base team → base user → custom team → custom user
  • resolve_customization.py now merges 4 layers (was 3): global user → skill defaults → project team → project user
  • 14 new tests covering priority ordering, backward compat, --key flag, and cross-layer value preservation

Problem

Users working across multiple git worktrees or cloned repos must re-enter personal settings (user_name, communication_language, user_skill_level) in every project's _bmad/custom/config.user.toml. This is tedious and error-prone.

Solution

A global user layer at ~/.bmad/config/config.user.toml (and ~/.bmad/config/{skill}.user.toml for skill customization) is merged first as the lowest-priority fallback. Any project-level override always wins. Missing global dir is fully backward-compatible — existing installs work unchanged.

Test plan

  • All 313 existing tests pass
  • 14 new tests for global layer merge behavior
  • Backward compat: missing ~/.bmad/config/ doesn't break anything
  • Priority chain verified: global < project layers
  • Manual test: create ~/.bmad/config/config.user.toml, verify it's resolved
  • Manual test: project override wins over global value

Closes #2338

@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Apr 27, 2026

🤖 Augment PR Summary

Summary: Introduces a per-user global configuration layer under ~/.bmad/config/ so personal defaults apply across multiple repos/worktrees.

Changes:

  • resolve_config.py: adds a new lowest-priority global user layer (config.user.toml) and updates the merge chain to 5 layers.
  • resolve_customization.py: adds a new lowest-priority global per-skill user layer ({skill}.user.toml) and updates the merge chain to 4 layers.
  • Adds a Node-based integration test suite that runs the Python scripts against temporary TOML fixtures.
  • Tests cover priority ordering, backward compatibility when the global directory is missing, --key behavior, and preservation of values introduced in lower layers.

Technical Notes: The global directory is derived from Path.home(), so users can redirect it via HOME when needed; project/worktree-level config continues to override global defaults.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/scripts/resolve_config.py Outdated
project_root = Path(args.project_root).resolve()
bmad_dir = project_root / "_bmad"

global_user = load_toml(GLOBAL_DIR / "config.user.toml")
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/scripts/resolve_config.py: This PR changes the resolver to a five-layer merge, but the argparse.ArgumentParser(description=...) string still says “four-layer TOML merge”, which makes --help misleading.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

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")
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/scripts/resolve_customization.py: The CLI argparse description still says “three-layer TOML merge” even though the script is now documented/implemented as four layers, so --help output is now incorrect.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

`Expected "TestProject", got "${result.project_name}"`,
);
} finally {
process.env.HOME = origHome;
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test/test-config-resolution.js: Restoring process.env.HOME via process.env.HOME = origHome will set it to the literal string "undefined" when origHome was unset, which can leak state into subsequent tests in the same process.

Severity: medium

Other Locations
  • test/test-config-resolution.js:130
  • test/test-config-resolution.js:161
  • test/test-config-resolution.js:206
  • test/test-config-resolution.js:241
  • test/test-config-resolution.js:271
  • test/test-config-resolution.js:290
  • test/test-config-resolution.js:318

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread test/test-config-resolution.js Outdated

assert(
result.user_name === 'ProjectAlice',
'project config.user overrides global user_name',
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test/test-config-resolution.js: This assertion label says “project config.user overrides global user_name”, but the override in this fixture comes from _bmad/config.toml (team layer), which could be confusing when diagnosing failures.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

The changes introduce a global user configuration layer at ~/.bmad/config/ that applies across all worktrees and projects. The resolve_config.py and resolve_customization.py scripts now load global user defaults with lowest priority, ensuring project-level configs always take precedence. A comprehensive integration test suite validates the new merge order and global layer behavior.

Changes

Cohort / File(s) Summary
Config Resolution Layer
src/scripts/resolve_config.py, src/scripts/resolve_customization.py
Both scripts now define GLOBAL_DIR = Path.home() / ".bmad" / "config" and load corresponding user TOML files (config.user.toml and {skill_name}.user.toml). Merge order updated to place global user defaults at lowest priority, merged before existing project and custom layers. Docstrings updated to reflect new layer count (5 layers for config, 4 for customization).
Integration Tests
test/test-config-resolution.js
New test file with 338 lines of integration tests validating: config/customization resolution with global user overrides, priority chain correctness, handling of missing global directory, and --key option functionality. Tests exercise both scripts via execSync with temporary TOML fixtures.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(config): per-user global config across worktrees' clearly and concisely summarizes the main change: adding a global user configuration layer to be shared across multiple worktrees.
Description check ✅ Passed The description is well-structured, clearly explaining the problem, solution, and test plan with specific details about the new configuration layers and backward compatibility.
Linked Issues check ✅ Passed The PR successfully implements all objectives from issue #2338: adds global user config at ~/.bmad/config/, integrates as lowest-priority layer in both resolve_config.py (5 layers) and resolve_customization.py (4 layers), includes 14 new tests, and maintains backward compatibility.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing global user config across worktrees as specified in issue #2338; modifications to resolve_config.py, resolve_customization.py, and the new test file are all in-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/scripts/resolve_customization.py (1)

183-186: Update the argparse description to match the four-layer merge.

The docstring correctly describes the four-layer merge, but the argparse description still references "three-layer TOML merge."

♻️ Proposed fix
     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,
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scripts/resolve_customization.py` around lines 183 - 186, The
argparse.ArgumentParser description is outdated (it says "three-layer TOML
merge"); update the description string passed to argparse.ArgumentParser in the
parser initialization (the call to argparse.ArgumentParser) to reference the
correct "four-layer merge" wording to match the module docstring and behavior.
src/scripts/resolve_config.py (1)

140-142: Update the argparse description to match the five-layer merge.

The docstring correctly describes the five-layer merge, but the argparse description still references "four-layer TOML merge."

♻️ Proposed fix
     parser = argparse.ArgumentParser(
-        description="Resolve BMad central config using four-layer TOML merge.",
+        description="Resolve BMad central config using five-layer TOML merge.",
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scripts/resolve_config.py` around lines 140 - 142, Update the argparse
description passed to parser = argparse.ArgumentParser in resolve_config.py to
reflect the five-layer merge (change the string from "four-layer TOML merge." to
mention "five-layer TOML merge.") so the CLI help matches the module docstring.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/scripts/resolve_config.py`:
- Around line 140-142: Update the argparse description passed to parser =
argparse.ArgumentParser in resolve_config.py to reflect the five-layer merge
(change the string from "four-layer TOML merge." to mention "five-layer TOML
merge.") so the CLI help matches the module docstring.

In `@src/scripts/resolve_customization.py`:
- Around line 183-186: The argparse.ArgumentParser description is outdated (it
says "three-layer TOML merge"); update the description string passed to
argparse.ArgumentParser in the parser initialization (the call to
argparse.ArgumentParser) to reference the correct "four-layer merge" wording to
match the module docstring and behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 827fb1e7-9f8b-42cf-b206-e99cbf3e731f

📥 Commits

Reviewing files that changed from the base of the PR and between 6ff74ba and d4ee147.

📒 Files selected for processing (3)
  • src/scripts/resolve_config.py
  • src/scripts/resolve_customization.py
  • test/test-config-resolution.js

@bmadcode
Copy link
Copy Markdown
Collaborator

all configs have not yet converted to toml, which they need to - and the config script which is currently unused in the workflows (it should be) also needs to convert soon with the change to toml for the next version roll out

can you carve out the change to the config loader and lean this PR just to the customization change to support the ~/.bmad/custom - and a change is coming soon to the other script and I will include a similar ~/.bmad/configs

Thanks @jrevillard

@jrevillard
Copy link
Copy Markdown
Contributor Author

jrevillard commented Apr 28, 2026

Thanks for the feedback! Two things I'd like to clarify before splitting:

1. Directory paths

My implementation uses ~/.bmad/config/ for both scripts (e.g. ~/.bmad/config/config.user.toml and ~/.bmad/config/{skill}.user.toml). You mentioned ~/.bmad/custom and ~/.bmad/configs — should we use different paths?

2. Skills still read config.yaml directly

The reason I updated the skill files to call resolve_config.py is that skills currently read _bmad/bmm/config.yaml directly, completely bypassing the TOML merge mechanism. Without that change, the global user layer is never resolved by agents.

If I strip the skill updates from this PR, the global customization layer in resolve_customization.py will work, but the global config layer in resolve_config.py won't be consumed until skills are updated separately.

Should I:

  • (A) Keep only resolve_customization.py changes in this PR (the global config script change + skill updates come later)?
  • (B) Keep both resolver changes but defer the skill updates to a follow-up PR?

@jrevillard jrevillard force-pushed the feat/global-user-config branch from 23aa389 to 5416e12 Compare April 28, 2026 06:06
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 bmad-code-org#2338
…ults

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
…ectly

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.
… workflow.md

The 5 skills whose workflow.md was absorbed into SKILL.md by PR bmad-code-org#2308
still had the old config.yaml loading instruction. Updated them to use
resolve_config.py like all other skills.
@jrevillard jrevillard force-pushed the feat/global-user-config branch from 5416e12 to 4a0c59f Compare April 29, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-user global config not shared across git worktrees

2 participants