Feature Request: Merge Global + Local Configurations (--merge-global)
Current Behavior
Global configuration at $XDG_CONFIG_HOME/ruler (default: ~/.config/ruler) is only used as a fallback when no local .ruler/ directory exists. If a local .ruler/ exists, global config is completely ignored.
Desired Behavior
New opt-in flag --merge-global that merges global and local configurations at the project root level only:
| Resource |
Merge Behavior |
Rules (.md files) |
Global prepended before local; same-name files → local wins |
ruler.toml config |
Deep merge; local overrides global on conflicts |
| MCP servers |
Merged; local wins on name conflicts |
| Skills |
Merged; local wins on same-name skill directories |
Use Case
Maintain global coding standards, personal preferences, tool configurations, and skills that apply to ALL projects, while allowing project-specific overrides.
Example global setup:
~/.config/ruler/
├── AGENTS.md # "Always use TypeScript, follow TDD..."
├── coding-style.md # Personal code style preferences
├── ruler.toml # Default MCP servers, agent settings
└── skills/
├── my-linter/ # Personal linting skill
│ └── SKILL.md
└── doc-generator/ # Documentation skill
└── SKILL.md
Example local project:
myproject/.ruler/
├── AGENTS.md # Project-specific context
├── api-guidelines.md # API conventions for this project
├── coding-style.md # Override global coding-style (local wins)
├── ruler.toml # Project MCP servers (merged with global)
└── skills/
└── my-linter/ # Override global my-linter (local wins)
└── SKILL.md
Result with --merge-global:
- Rules concatenated: global
AGENTS.md → global others → local AGENTS.md → local others
coding-style.md: only local version used (same-name conflict)
- Skills:
my-linter/ from local, doc-generator/ from global
- MCP: servers from both, local wins on conflicts
Nested Mode Behavior
When --merge-global is combined with --nested:
- Global merge applies only to project root
.ruler/
- Nested
.ruler/ directories are not affected by global merge
- Each nested directory continues to generate its own independent output
project/
├── .ruler/ ← Global merged HERE only
│ └── AGENTS.md
├── packages/
│ └── api/
│ └── .ruler/ ← NOT merged with global (independent)
│ └── AGENTS.md
└── apps/
└── web/
└── .ruler/ ← NOT merged with global (independent)
└── AGENTS.md
CLI Interface
ruler apply --merge-global # Merge global + local at project root
ruler apply --merge-global --nested # Global at root only, nested dirs independent
Concatenation Order (with --merge-global)
Rules:
- Global
.ruler/AGENTS.md
- Global
.ruler/*.md (sorted, excluding same-name as local)
- Local root
AGENTS.md (outside .ruler, if exists and not generated)
- Local
.ruler/AGENTS.md
- Local
.ruler/*.md (sorted)
Skills:
- Discover global skills from
~/.config/ruler/skills/
- Discover local skills from
.ruler/skills/
- Merge: local skill directories override global on name conflict
- Copy merged result to
.claude/skills/ and .skillz/
MCP Servers:
- Load global
ruler.toml MCP servers
- Load local
ruler.toml MCP servers
- Merge:
{ ...globalServers, ...localServers } (local wins)
Config (ruler.toml):
function mergeLoadedConfigs(global: LoadedConfig, local: LoadedConfig): LoadedConfig {
return {
defaultAgents: local.defaultAgents ?? global.defaultAgents,
agentConfigs: { ...global.agentConfigs, ...local.agentConfigs },
mcp: {
enabled: local.mcp?.enabled ?? global.mcp?.enabled,
strategy: local.mcp?.strategy ?? global.mcp?.strategy,
// servers merged separately
},
gitignore: { enabled: local.gitignore?.enabled ?? global.gitignore?.enabled },
skills: { enabled: local.skills?.enabled ?? global.skills?.enabled },
nested: local.nested ?? global.nested,
};
}
Files to Modify
-
src/cli/commands.ts
- Add
--merge-global boolean flag (default: false)
-
src/core/FileSystemUtils.ts
- New:
readMarkdownFilesWithGlobal(localRulerDir: string, globalRulerDir: string)
- Reads both directories
- Deduplicates by filename (local wins)
- Returns combined list in correct order
-
src/core/apply-engine.ts
loadSingleConfiguration(): When mergeGlobal=true:
- Find both local and global
.ruler/ dirs
- Call new merged file reader
- Merge configs
loadNestedConfigurations(): Only apply global merge to root config entry
- New:
mergeRulerConfigurations(global, local) helper
-
src/core/ConfigLoader.ts
- New:
mergeLoadedConfigs(global: LoadedConfig, local: LoadedConfig): LoadedConfig
-
src/core/SkillsProcessor.ts
discoverSkills(): Accept optional global skills dir parameter
- New:
mergeSkills(globalSkills: SkillInfo[], localSkills: SkillInfo[]): SkillInfo[]
- Merge by skill name, local wins on conflict
propagateSkills(): When mergeGlobal=true, discover from both dirs and merge
-
src/core/UnifiedConfigLoader.ts
- Update to support merging MCP servers from two sources
-
src/lib.ts
- Add
mergeGlobal parameter to applyAllAgentConfigs()
- Pass through to relevant functions
Test Cases
Future Enhancement
A separate issue could address propagating global ~/.config/ruler/skills/ directly to user-global ~/.claude/skills/ (e.g., ruler apply --global), aligning with Claude Code's native global skills support. This would enable true user-level skills without any project setup. Out of scope for this feature.
Feature Request: Merge Global + Local Configurations (
--merge-global)Current Behavior
Global configuration at
$XDG_CONFIG_HOME/ruler(default:~/.config/ruler) is only used as a fallback when no local.ruler/directory exists. If a local.ruler/exists, global config is completely ignored.Desired Behavior
New opt-in flag
--merge-globalthat merges global and local configurations at the project root level only:.mdfiles)ruler.tomlconfigUse Case
Maintain global coding standards, personal preferences, tool configurations, and skills that apply to ALL projects, while allowing project-specific overrides.
Example global setup:
Example local project:
Result with
--merge-global:AGENTS.md→ global others → localAGENTS.md→ local otherscoding-style.md: only local version used (same-name conflict)my-linter/from local,doc-generator/from globalNested Mode Behavior
When
--merge-globalis combined with--nested:.ruler/.ruler/directories are not affected by global mergeCLI Interface
Concatenation Order (with --merge-global)
Rules:
.ruler/AGENTS.md.ruler/*.md(sorted, excluding same-name as local)AGENTS.md(outside .ruler, if exists and not generated).ruler/AGENTS.md.ruler/*.md(sorted)Skills:
~/.config/ruler/skills/.ruler/skills/.claude/skills/and.skillz/MCP Servers:
ruler.tomlMCP serversruler.tomlMCP servers{ ...globalServers, ...localServers }(local wins)Config (
ruler.toml):Files to Modify
src/cli/commands.ts--merge-globalboolean flag (default: false)src/core/FileSystemUtils.tsreadMarkdownFilesWithGlobal(localRulerDir: string, globalRulerDir: string)src/core/apply-engine.tsloadSingleConfiguration(): WhenmergeGlobal=true:.ruler/dirsloadNestedConfigurations(): Only apply global merge to root config entrymergeRulerConfigurations(global, local)helpersrc/core/ConfigLoader.tsmergeLoadedConfigs(global: LoadedConfig, local: LoadedConfig): LoadedConfigsrc/core/SkillsProcessor.tsdiscoverSkills(): Accept optional global skills dir parametermergeSkills(globalSkills: SkillInfo[], localSkills: SkillInfo[]): SkillInfo[]propagateSkills(): WhenmergeGlobal=true, discover from both dirs and mergesrc/core/UnifiedConfigLoader.tssrc/lib.tsmergeGlobalparameter toapplyAllAgentConfigs()Test Cases
--merge-globalwith only global config (same as fallback)--merge-globalwith only local config (no change)--merge-globalwith both: rules concatenated correctly.mdfile: local wins, global excluded--merge-global --nested: only root gets global merge--merge-global: existing behavior unchanged (backward compat)Future Enhancement
A separate issue could address propagating global
~/.config/ruler/skills/directly to user-global~/.claude/skills/(e.g.,ruler apply --global), aligning with Claude Code's native global skills support. This would enable true user-level skills without any project setup. Out of scope for this feature.