Skip to content

Feature Request: Merge Global + Local Configurations (--merge-global) #319

@austinm911

Description

@austinm911

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:

  1. Global .ruler/AGENTS.md
  2. Global .ruler/*.md (sorted, excluding same-name as local)
  3. Local root AGENTS.md (outside .ruler, if exists and not generated)
  4. Local .ruler/AGENTS.md
  5. Local .ruler/*.md (sorted)

Skills:

  1. Discover global skills from ~/.config/ruler/skills/
  2. Discover local skills from .ruler/skills/
  3. Merge: local skill directories override global on name conflict
  4. Copy merged result to .claude/skills/ and .skillz/

MCP Servers:

  1. Load global ruler.toml MCP servers
  2. Load local ruler.toml MCP servers
  3. 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

  1. src/cli/commands.ts

    • Add --merge-global boolean flag (default: false)
  2. src/core/FileSystemUtils.ts

    • New: readMarkdownFilesWithGlobal(localRulerDir: string, globalRulerDir: string)
      • Reads both directories
      • Deduplicates by filename (local wins)
      • Returns combined list in correct order
  3. 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
  4. src/core/ConfigLoader.ts

    • New: mergeLoadedConfigs(global: LoadedConfig, local: LoadedConfig): LoadedConfig
  5. 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
  6. src/core/UnifiedConfigLoader.ts

    • Update to support merging MCP servers from two sources
  7. src/lib.ts

    • Add mergeGlobal parameter to applyAllAgentConfigs()
    • Pass through to relevant functions

Test Cases

  • --merge-global with only global config (same as fallback)
  • --merge-global with only local config (no change)
  • --merge-global with both: rules concatenated correctly
  • Same-name .md file: local wins, global excluded
  • Same-name skill directory: local wins
  • MCP server name conflict: local wins
  • Config value conflict: local wins
  • --merge-global --nested: only root gets global merge
  • Without --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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions