How to Manage AI Coding Rules in a Monorepo
Learn how to organize AI coding rules across a monorepo using layered CLAUDE.md files and Cursor globs - root conventions plus package-specific overrides.
The monorepo challenge for AI rules
Monorepos are great for sharing code, enforcing consistency, and managing multiple packages from one repository. But they introduce a thorny problem for AI coding assistants: where do the rules live?
A typical Turborepo or Nx workspace might look like this:
my-monorepo/
apps/
web/ # Next.js frontend
api/ # Express or Hono backend
mobile/ # React Native app
packages/
ui/ # Shared component library
utils/ # Shared utilities
config/ # ESLint, TypeScript configs
Each of these has different conventions. The web app uses Tailwind and App Router patterns. The api uses a specific error-handling format and database query style. The ui package enforces strict component export patterns. The mobile app has React Native-specific rules about styling and navigation.
If you put one flat rules file at the root, it becomes a sprawling document that the AI struggles to apply selectively. If you put rules only in each package, you lose shared conventions and end up with duplication. Neither approach works well on its own.
This post covers the strategies that actually work - and how to make them scale.
Root-level rules: shared conventions
The root of your monorepo is the right place for rules that apply everywhere:
- Language and TypeScript settings (strict mode, target version)
- Naming conventions (camelCase functions, PascalCase components, kebab-case files)
- Import style (named exports over default exports, absolute imports via
@/) - Git and commit conventions
- Documentation standards
For Cursor, this means a .cursor/rules/general.mdc file at the root (the older .cursorrules file still works but is deprecated). For Claude Code, a CLAUDE.md at the root. These get picked up when the AI is working anywhere in the repo.
# Root CLAUDE.md
## Language
- TypeScript throughout. Strict mode enabled.
- Use named exports — no default exports except for Next.js pages/layouts.
- Absolute imports via `@/` configured in each package's tsconfig.
## Naming
- Files: kebab-case (e.g., `user-profile.ts`, not `UserProfile.ts`)
- React components: PascalCase
- Functions and variables: camelCase
- Constants: SCREAMING_SNAKE_CASE for top-level module constants
## Error handling
- Always handle errors explicitly. Never swallow errors silently.
- Use Result types or throw errors — do not return null to indicate failure.
Keep root rules tight. If you stuff package-specific rules here, the AI gets confused about what applies where.
Package-level rules: targeted overrides
Each package or app should have its own rules file that adds context specific to that package. These rules layer on top of the root rules - they don't replace them.
Here is how you might structure apps/web/CLAUDE.md:
# Web App (Next.js 15)
This is a Next.js 15 app using the App Router. Rules here extend the root CLAUDE.md.
## Stack
- Framework: Next.js 15, App Router only (no Pages Router)
- Styling: Tailwind CSS v4
- Data fetching: React Server Components for reads, Server Actions for mutations
## Component patterns
- Server Components by default. Add `"use client"` only when needed.
- Co-locate components with their routes unless used across multiple routes.
- Styles: Tailwind utility classes only — no CSS modules, no inline styles.
## API routes
Export async functions named GET, POST, PUT, DELETE.
Return `NextResponse.json()` for all responses.
Always include error handling with try/catch.
And apps/api/CLAUDE.md:
# API Service (Hono)
REST API built with Hono on Cloudflare Workers.
## Route patterns
- Group routes by resource in separate files.
- Use Zod for request validation — validate before any business logic.
- Return `{ data: T }` for success, `{ error: string, code: string }` for errors.
## Database
- Use Drizzle ORM. Never write raw SQL unless there is no Drizzle equivalent.
- Always use transactions for multi-table writes.
- Query functions live in `src/db/queries/` — do not inline complex queries in routes.
This layered approach mirrors how most monorepos already handle configuration - tsconfig.json extends a base, ESLint configs extend a shared config, and so on.
Tool-specific strategies
Different AI tools handle directory-scoped rules differently. Here is how the major tools work in a monorepo context.
Claude Code: CLAUDE.md hierarchy
Claude Code natively supports a hierarchy of CLAUDE.md files. When you open a file in apps/web/src/components/, Claude Code reads:
CLAUDE.mdat the git rootapps/CLAUDE.md(if it exists)apps/web/CLAUDE.mdapps/web/src/CLAUDE.mdapps/web/src/components/CLAUDE.md
Each file adds context. This is exactly what you want - the closer the CLAUDE.md is to the file being edited, the more specific it can be. You do not need any special configuration. Just place CLAUDE.md files where they make sense and Claude Code handles the rest.
Read more about this in the complete Claude Code CLAUDE.md guide.
Cursor: globs for targeted rules
Cursor's .cursor/rules/ folder supports a globs field in each rule file's frontmatter. This is how you apply rules selectively in a monorepo:
---
description: Next.js web app conventions
globs: ["apps/web/**/*.ts", "apps/web/**/*.tsx"]
alwaysApply: false
---
Use App Router patterns only.
Server Components by default — add "use client" only when needed.
---
description: API service conventions
globs: ["apps/api/**/*.ts"]
alwaysApply: false
---
Use Hono route handler patterns.
Validate all inputs with Zod before processing.
---
description: Shared packages — strict exports
globs: ["packages/**/*.ts", "packages/**/*.tsx"]
alwaysApply: false
---
Every public export must have a JSDoc comment.
No side effects at module level.
Barrel exports via index.ts only.
Cursor auto-attaches the right rules when you reference or mention matching files in the chat. When you're working with files in apps/web, only web rules fire. When you reference files in packages/ui, the shared package rules apply.
See the full Cursor rules guide for more on the .mdc format.
Windsurf and other tools
Windsurf uses a .windsurf/rules/ folder at the project root with .md files. Windsurf supports four activation modes for rules: Always On, Manual (via @mention), Model Decision (the AI decides based on a natural language description), and Glob (applied to files matching a pattern like apps/web/**/*.ts). For monorepos, the Glob activation mode lets you scope rules to specific packages, similar to Cursor's approach.
If you prefer a simpler setup, you can also prefix rules with the scope they apply to and use the Model Decision mode:
# [apps/web] Next.js conventions
When working in apps/web:
- Use App Router patterns only
- Server Components by default
Both approaches work. The glob mode gives you precise file-pattern targeting, while scope prefixes with Model Decision mode let the AI apply rules conditionally based on context.
Practical setup with Turborepo
Here is a complete file structure for a Turborepo monorepo with full AI rules coverage:
my-monorepo/
CLAUDE.md # Root: shared conventions
.cursor/rules/
general.mdc # Always active: shared conventions
web-app.mdc # globs: apps/web/**
api-service.mdc # globs: apps/api/**
shared-packages.mdc # globs: packages/**
.windsurf/rules/
general.md # Always On: shared conventions
web-app.md # Glob: apps/web/**
api-service.md # Glob: apps/api/**
apps/
web/
CLAUDE.md # Claude Code: web-specific rules
api/
CLAUDE.md # Claude Code: API-specific rules
packages/
ui/
CLAUDE.md # Claude Code: component library rules
The root CLAUDE.md and general.mdc cover shared conventions. Package-level files add specifics. Each tool reads what it understands.
What to put in shared vs package rules
A common mistake is duplicating content between root and package rules. Here is a clear division:
| Topic | Root rules | Package rules |
|---|---|---|
| TypeScript version and compiler options | Yes | No |
| Naming conventions | Yes | Only exceptions |
| Import style | Yes | Package-specific aliases |
| Framework/library patterns | No | Yes |
| Error handling philosophy | Yes | Tool-specific format |
| Testing approach | Yes (pattern) | Package-specific setup |
| Dependency restrictions | No | Yes (what's available) |
When in doubt: if the rule would apply identically in every package, it belongs at the root. If it assumes a specific framework or package context, it belongs in that package.
Keeping rules in sync across packages
The real maintenance challenge with monorepo rules is drift. Package rules get updated by different developers, root rules fall out of date, and after six months you have rules that contradict each other.
A few practices that help:
Review rules alongside code changes. When a PR changes a convention (say, migrating from React Query to server-side data fetching), the rules update should be in the same PR. Make it part of your definition of done.
Lint your rules. You can write simple CI checks that scan your rules files for banned patterns, check that each package has the required rule files, or verify that glob patterns match existing directories.
Own shared rules in a central package. For conventions that span packages (like TypeScript settings or naming rules), consider writing them once in packages/config/ and generating the per-tool formats from a single source of truth.
Sharing rules across multiple repositories
Monorepos keep things together, but not everyone uses a monorepo. Many teams have a frontend repo, a backend repo, and a mobile repo, all with the same shared conventions. Even within a monorepo, you might want to share rules with contractors or open-source contributors.
This is where localskills.sh fits in. You publish your rules as a versioned package, and anyone installs them with one command:
localskills install your-org/shared-conventions --target cursor claude windsurf
The CLI handles format differences automatically. One published skill installs as .cursor/rules/, CLAUDE.md entries, and .windsurf/rules/ depending on which --target flags you pass.
For monorepos specifically, you can publish scoped skills for each package type:
# Install conventions for the web app
localskills install your-org/nextjs-app-router --target cursor claude --path apps/web
# Install conventions for the API
localskills install your-org/hono-api --target cursor claude --path apps/api
The --path flag scopes the installation to a subdirectory, so Cursor's globs and Claude Code's CLAUDE.md hierarchy work exactly as described above.
Read the publish your first skill guide to get started, or see best practices for AI coding rules for more on what to include.
A note on rule file size
AI context windows are large but not infinite. Cursor includes matching rules in every request - alwaysApply rules are always attached, and auto-attached rules are included when referenced files match their globs. If your active rules total 20,000 tokens, that is 20,000 tokens consumed before the AI has read any code.
Keep each rule file focused. A good target is under 500 words per file. If a file is growing, split it: api-routes.mdc and api-validation.mdc are better than one giant api.mdc.
With Cursor's globs system, only matching rule files are included in context. With Claude Code's CLAUDE.md hierarchy, parent directory rules load at launch and subdirectory rules load on demand as files are accessed. Both systems help keep context lean, but they only help if your files are actually scoped correctly.
Summary
Managing AI rules in a monorepo is a solved problem once you follow a layered approach:
- Root rules for shared conventions: TypeScript settings, naming, import style, error handling philosophy
- Package rules for tool-specific patterns: framework conventions, available libraries, data access patterns
- Tool-specific targeting: Cursor globs, Claude Code CLAUDE.md hierarchy, Windsurf glob activation mode
- Version control as the source of truth: rules live in git, change through PRs, reviewed alongside code
The result is an AI that understands your entire codebase, not just the file open in the editor.
Ready to share your monorepo rules across your team and across tools? Create a free account on localskills.sh and publish your first skill today.
npm install -g @localskills/cli
localskills login
localskills publish