Skip to content

Monorepos and large projects

A large repository needs each agent to see the instructions for the code it is working on, and not much more. ai-rulez gives you three tools, which you can combine:

Tool Use it when What you get
Domains and profiles Teams or services share one repository and one configuration One set of root files, with the content of the selected domains
[[scopes]] Packages need their own instructions, loaded when an agent works there Root files plus <package>/AGENTS.md, <package>/CLAUDE.md, … and path-scoped rule files
Nested configurations Packages are owned separately and keep their own .ai-rulez/ One independent project per directory, generated with --recursive

ai-rulez generate --recursive over two nested roots, recursive validation, and a token budget check

A repository with shared rules, an api and a web domain, and one scope per package:

.ai-rulez/
config.toml
rules/security.md # every package
domains/api/rules/errors.md # packages/api only
domains/api/rules/go-style.md # paths: ["**/*.go"]
domains/web/rules/components.md # packages/web only
packages/api/
packages/web/
.ai-rulez/config.toml
version = "5.0"
name = "acme"
presets = ["claude", "cursor", "codex"]
default = "root" # the root files carry no domain
[profiles]
root = []
api = ["api"]
web = ["web"]
[[scopes]]
path = "packages/api"
profile = "api" # presets default to codex and claude
[[scopes]]
path = "packages/web"
profile = "web"
presets = ["claude", "cursor"]

A scope renders only the domains the root output does not already contain, so the root profile selects none: a root profile that included api would leave packages/api with nothing to write. ai-rulez generate writes:

AGENTS.md # root: security
CLAUDE.md # @AGENTS.md
packages/api/AGENTS.md # errors, go-style
packages/api/CLAUDE.md # @AGENTS.md
packages/web/AGENTS.md # components
packages/web/CLAUDE.md
.claude/rules/packages-api/go-style.md # paths: [packages/api/**/*.go]
...

Each scope file holds only the scope’s own content: tools load a nested AGENTS.md or CLAUDE.md on top of the root file, so repeating the root rules would load them twice.

Put cross-cutting content (architecture, security, git workflow) at the root of .ai-rulez/, and team- or service-specific content in domains/<name>/. A profile selects domains:

default = "full"
[profiles]
full = ["backend", "frontend", "qa"]
backend = ["backend", "qa"]
frontend = ["frontend", "qa"]
Terminal window
ai-rulez generate --profile backend # root content plus backend and qa

See Domains and profiles for the layout and Roles to select content per kind of task rather than per team.

A [[scopes]] entry writes a second set of root files in a subdirectory, from its own profile:

Key Default Meaning
name Label used in messages
path required Directory relative to the project root; no glob characters
profile the active profile Profile whose domains the scope renders
presets ["codex", "claude"] Presets that write files in the scope

A domain the root output already contains is not repeated in a scope, and a scope left with nothing writes no files. See scopes for the full reference.

Root files (CLAUDE.md, AGENTS.md, GEMINI.md, …) are written in the scope directory, but rule files of a scope go to the root rules folder, with the scope path as a qualifier and glob prefix. Not every tool reads a nested rules folder (Copilot reads only the repository root), so this is the one placement that every preset with a rules folder reads.

Preset Scoped rule file for packages/api, rule style
Claude .claude/rules/packages-api/style.md
Cursor .cursor/rules/packages-api/style.mdc
Copilot .github/instructions/packages-api/style.instructions.md
Devin, Cline, Junie <rules dir>/packages-api--style.md
Antigravity .agents/rules/packages-api--style.md
  • Globs are relative to the scope: paths: ["**/*.go"] becomes packages/api/**/*.go, and a rule without globs (or with only negated globs) gets packages/api/**.
  • A glob that leaves the scope with .. skips that rule for the scope, with a warning.
  • auto and manual rules keep their mode, so they are not limited to the scope; generate warns once per scope.
  • With [rules] mode = "inline", only path-scoped items move to the root folder; the rest stays in the scope’s root file.
  • Two rules that map to the same file get a -<6 hex> suffix with a warning (see File names). Two scopes that produce the same qualifier (packages/api and packages-api) fail; rename one.

Limitations:

  • Copilot (.github/copilot-instructions.md) and Junie read their root instruction file at the repository root only, so content left inline in a scope’s copy never reaches them, and generate warns. Use path-scoped rules or [rules] mode = "split" for those presets.
  • Custom presets whose rules output is not split keep a scope’s rules in the scope directory, with a warning.

When a package keeps its own .ai-rulez/ (or .agents/ai-rulez.toml), it is an independent project. Process every configuration below the current directory with --recursive:

$ ai-rulez generate --recursive
...
✅ Total: Generated 18 file(s) from 2 config(s)
$ ai-rulez generate --check --recursive
[1/2] .ai-rulez/config.toml (47ms)
[2/2] services/billing/.ai-rulez/config.toml (42ms)
INFO Generated files are up to date roots=2
$ ai-rulez validate --recursive
[2/2] services/billing/.ai-rulez/config.toml (79ms)
validation: no findings in 2 root(s)
services/billing/.ai-rulez/config.toml
version = "5.0"
name = "billing"
presets = ["claude", "gemini"]
  • Roots are processed concurrently, one worker per CPU. generate --check --recursive and validate --recursive report in path order, so output and exit code are the same on every run; --quiet and --format json silence the [n/N] progress lines.
  • A root that authors a plugin or marketplace and configures no preset is built as a bundle, as generate --plugin would, and skipped by generate --check --recursive; check it with verify --plugin. See Authoring plugins.
  • Use --debug to see where time goes on a large repository.

With gitignore = true, a recursive run writes one managed block in the .gitignore at the top of each git repository, holding the patterns of every root inside it (svc/CLAUDE.md, svc/.claude/skills/, …). generate --recursive --check verifies each block once.

  • A nested directory with its own .git gets its own block; a repository whose root sets gitignore = false is never written for other roots.
  • The root’s own entries are unanchored (AGENTS.md, CLAUDE.md), so they also match nested roots’ files.
  • To commit generated files, list them in each root’s gitignore_exclude, for example gitignore_exclude = ["**/AGENTS.md", "**/CLAUDE.md", ".claude/"]. Each root applies its list to its own outputs only.

Packages often already hold AGENTS.md, CLAUDE.md, .claude/, .agents/ or skills/, and a harness loads them only once it works in that directory. An agent planning at the root, or changing several packages, can ask the MCP server what applies to a path:

Terminal window
ai-rulez mcp --serve-skills --discover-skills

find_skill, find_context and load_context then take a path or scope; the nearest scope wins a skill name. The index reads the files that exist, so it needs no generate run. It is cached between runs ([mcp] index_cache), refreshed while the server runs, and an agent can add a skill to a scope with create_skill when allow_write is on. See: