How to use AI rules in a monorepo

May 5, 2026 · 6 min read

Monorepos make AI rules files harder. One repo can have a Next.js app, a Go API, a Python pipeline, and three internal packages — each with different stacks, conventions, and constraints. The AI tool needs the right rules at the right time, not all of them at once.

Different tools handle this differently. Here's how to set it up for each.

Claude Code: nested CLAUDE.md files

Claude Code merges CLAUDE.md files as it walks up from the current working directory. Strategy:

  • Root CLAUDE.md — shared monorepo conventions: package manager, workspace structure, branching policy, Turborepo conventions
  • apps/web/CLAUDE.md — Next.js app specific rules
  • apps/api/CLAUDE.md — backend service specific rules
  • packages/ui/CLAUDE.md — shared component package specific rules

When Claude is editing files in apps/web/, it loads the root file and the apps/web/CLAUDE.md. When working in packages/ui/, it loads root + packages/ui/CLAUDE.md. Each file stays scoped to one concern.

Keep nested files short. The whole point is per-directory specificity, not duplication.

Cursor (.mdc): scope rules with globs

Cursor's modern .cursor/rules/*.mdc format scopes rules with file globs. Drop multiple .mdc files in .cursor/rules/ and let globs do the routing:

.cursor/rules/
├── monorepo.mdc        — globs: ["**/*"]
├── nextjs.mdc          — globs: ["apps/web/**/*"]
├── go-api.mdc          — globs: ["apps/api/**/*"]
├── ui-package.mdc      — globs: ["packages/ui/**/*"]
└── tests.mdc           — globs: ["**/*.test.ts", "**/*.spec.ts"]

Each file's frontmatter scopes it. Cursor only loads the rules whose globs match the files being edited.

Set alwaysApply: true for monorepo-wide rules and alwaysApply: false for scoped rules — that way the global ones load every turn, scoped ones only when relevant.

Cursor legacy (.cursorrules): single file, embedded conditionals

.cursorrules is a single file — no globs. Two options:

  1. Migrate to .cursor/rules/*.mdc — recommended; Cursor still reads the legacy file but the new format handles monorepos better.
  2. Conditional sections in the single file — write each package's rules in its own H2 section and explicitly cue the model: "When working in apps/web/, follow the Next.js section. When working in apps/api/, follow the Go section."

The conditional approach is brittle in practice — the model often picks the wrong section. Migrate to .mdc if you can.

GitHub Copilot: single file, generic conventions

.github/copilot-instructions.md is one file at one path. Copilot reads it for the whole repo. There's no nesting, no globs.

For monorepos, this means:

  • Keep the root file generic — describe shared conventions only (package manager, monorepo structure, common toolchain)
  • Rely on per-package README.md files for stack-specific conventions; Copilot will pick those up when its context window includes them
  • Or accept that Copilot is less context-aware than Cursor or Claude in monorepo scenarios — it's a known limitation

Aider: explicit per-package CONVENTIONS.md

Aider doesn't auto-load rules — you specify with --read. So per-package conventions are easy:

# When working on the web app:
aider --read apps/web/CONVENTIONS.md

# When working on the API:
aider --read apps/api/CONVENTIONS.md

Or use .aider.conf.yml to specify defaults per directory. Aider's "explicit loading" model fits monorepos naturally.

Cline: directory of files

Cline's .clinerules can be a directory of files (not just a single file). Drop one per concern:

.clinerules/
├── monorepo.md
├── nextjs.md
├── go-api.md
└── ui-package.md

Cline loads them all. Less precise than Cursor's globs but better than Copilot's single-file constraint.

What about the others?

Windsurf, Continue, and Cody are essentially single-file tools. Same constraint as Copilot — keep the rules file generic, rely on README conventions per package.

A practical setup

Most monorepos I've seen settle on one of two patterns:

Pattern A: Claude-first, supplemental files.

  • Primary: nested CLAUDE.md files (root + per-package)
  • Secondary: a single .cursorrules and .github/copilot-instructions.md at the root with monorepo-wide rules only, for teammates on those tools

Pattern B: Cursor .mdc-first, derived files.

  • Primary: .cursor/rules/*.mdc with glob-scoped rules
  • Secondary: a root CLAUDE.md and .github/copilot-instructions.md regenerated when the .mdc rules change

Either works. The key is picking one canonical source of truth per package and treating the others as derived. The converter on this site handles the regeneration in one click.

Don't write the same rule four times

If you find yourself maintaining the same rule across apps/web/CLAUDE.md and apps/web/.cursorrules and apps/web/.github/copilot-instructions.md, that's a smell. Pick one as the source, regenerate the rest. The cost of staleness across four files is much higher than the cost of one regeneration step.