Mastering Cursor Rules: A Practical Guide for AI-Native Development
Most teams set up Cursor Rules once and never touch them again, then wonder why the AI keeps repeating a mistake. Here are the four rule modes and how to keep a directory from going stale.

Cursor Rules are project-scoped instructions, stored as Markdown files in a .cursor/rules directory, that steer how the AI reads, generates, and edits code in your repository. They replace the old single .cursorrules file with a more granular system: multiple files, each scoped by glob pattern, description, or manual invocation, so different rules apply to different parts of a codebase.
If you have used Cursor for more than a few weeks, you have probably felt the gap between "the AI understood my codebase" and "the AI keeps doing the thing I told it not to do three days ago." Rules exist to close that gap, but most teams set them up once, never revisit them, and end up with a rules directory that is either ignored by the model or actively fighting itself. This guide covers how the system actually works, where it breaks down, and how to structure rules so they earn their place in your workflow instead of becoming another stale config file.
How Cursor Rules actually work under the hood
Each rule file lives in .cursor/rules/ and carries frontmatter that determines when it gets pulled into context. The four practical modes are:
- Always: injected into every request regardless of what you're doing.
- Auto Attached: injected when a file matching a glob pattern (like src/api/**/*.ts) is part of the conversation.
- Agent Requested: the model decides whether to pull the rule in, based on a description you write.
- Manual: only included when you explicitly reference it with @ruleName in chat.
A minimal rule file looks like this:
1---2description: Enforce error handling conventions for API route handlers3globs: src/api/**/*.ts4alwaysApply: false5---67- Wrap all async route handlers in the `withErrorBoundary` helper from `src/lib/errors.ts`.8- Never throw raw `Error` objects; use `ApiError` subclasses with an explicit status code.9- Log errors with the request ID before returning a response.This is documented in Cursor's own reference material on project rules, and the behavior matters: an "Always" rule loaded on a monorepo with unrelated frontend and backend code wastes context tokens on every single request, whether or not the current task touches the backend at all. Glob-scoped auto-attach rules are almost always the better default for anything beyond a handful of universal conventions.
Why global rules are the wrong default for most teams
Every team's first instinct is to write one long rules file covering formatting, architecture, testing, and style, then set it to always apply. This works for repos under a few thousand lines. Past that, it becomes a tax on every request: more prompt tokens spent on instructions irrelevant to the current file, more surface area for the model to selectively ignore parts of a long list (which happens more than people admit; instruction-following degrades as instruction count grows), and a single point of failure when the rule contradicts something the code actually needs.
The better pattern is a small always-on rule (project identity, non-negotiable constraints, maybe 10 to 15 lines) plus a set of narrowly scoped rules attached by path. A Next.js repo, for example, might have:
1.cursor/rules/2 core.mdc (alwaysApply: true, ~12 lines)3 api-conventions.mdc (globs: src/api/**)4 react-components.mdc (globs: src/components/**/*.tsx)5 migrations.mdc (manual, invoked with @migrations)6 testing.mdc (globs: **/*.test.ts)This keeps each request's context lean and makes rules easier to audit, since you can grep for the glob pattern that governs a given file instead of scrolling a 400-line manifesto.
What should actually go in a Cursor Rule?
Rules work best as constraints and conventions the model cannot infer from the code alone, not as a restatement of things already visible in context. Concrete categories that pay off:
- Naming and structural conventions that aren't self-evident from a few files: "New database migrations go in db/migrations/ with a timestamp prefix and must include a down() function."
- Team-specific anti-patterns: "Do not use useEffect for data fetching; use the useQuery hook from src/hooks/data.ts."
- Tooling context: which package manager, which test runner, which lint command to run after generating code.
- Boundaries the model tends to overstep: "Never modify files under generated/; regenerate them by running pnpm codegen instead."
What does not belong in a rule: things easily inferred from open files (the model can see your indentation style), generic best-practice advice ("write clean code"), or anything that changes weekly. Volatile instructions belong in chat, not in a persisted rule, because a stale rule that contradicts current practice is worse than no rule, since the model will follow it confidently and produce code that is wrong in a way that looks intentional.
How do I know if my rules are actually being used?
This is the question most engineers cannot answer, and it is the biggest failure mode in practice. Cursor does not surface, by default, a clear log of exactly which rule content was injected into a given request. You get the model's behavior as circumstantial evidence, which is a weak signal.
Two practical checks help:
- Ask the model directly: "List the rules currently active for this file." Agent mode will typically enumerate the auto-attached and always-on rules it has loaded, though this is a self-report from the model, not a guaranteed accurate trace, so treat it as a sanity check rather than ground truth.
- Deliberately put an unmistakable, unnatural instruction in a scoped rule (e.g., "prefix every generated comment with // RULE-CHECK") and confirm it shows up when you edit a matching file. Remove it once verified. This is a crude but reliable way to confirm glob scoping is actually working, especially after refactors that move files across directory boundaries the globs depend on.
Rules that silently stop matching after a directory rename are extremely common. If you restructure src/api into src/server/api, every rule with a src/api/** glob goes dark with no warning, and the model reverts to whatever conventions it infers from the code, which is usually a subtly different style than the one you intended to enforce.
Do Cursor Rules cost extra tokens and latency?
Yes, and it is worth being precise about this instead of hand-waving it. Every active rule's content is prepended to the model context on each relevant request, which means it counts against the context window and against your token consumption on metered plans. A 200-line always-on rule file applied to every single chat turn adds up over a long session, both in raw dollar cost on usage-based pricing and in the seconds of added latency for the model to process a larger prompt before it starts responding. See Cursor's pricing and model documentation for how requests are metered against context size (Cursor pricing, Cursor docs).
The practical implication: keep always-on rules short by discipline, not by accident. If a rule file is pushing past 50 lines and applies to everything, that's a signal to split it into path-scoped pieces that only load when relevant. This isn't a theoretical optimization; on a large session with dozens of turns, the cumulative token overhead from an oversized always-on rule is comparable to attaching an extra mid-sized source file to every message you send.
Rules versus .cursorignore versus inline comments
Rules are not the only lever for shaping model behavior, and using the wrong one is a common mistake:
- .cursorignore stops files from being indexed or pulled into context at all (secrets, generated bundles, vendored dependencies). Use this for exclusion, not for behavioral steering. It follows the same gitignore-style syntax you already know (Cursor ignore files docs).
- Inline comments (// cursor: don't touch this function) work for one-off, file-local instructions but don't persist across sessions or apply to other files with the same pattern.
- Rules are for durable, repeatable, scoped conventions that should survive across sessions and across engineers on the team.
A common anti-pattern is trying to use .cursorignore to "hide" a file from being edited while still wanting it referenced for context. Ignored files are invisible to the model entirely; if you need it visible but off-limits for edits, that's a rule ("never modify config/legacy.json, only read it for reference"), not an ignore entry.
When Cursor Rules are the wrong tool entirely
Rules assume a codebase stable enough that conventions are worth codifying. On a greenfield prototype where the architecture is still being decided week to week, writing detailed rules is premature; you'll spend more time updating the rules than they save you, and a lightweight README.md or a short paragraph in chat covers the same ground with less maintenance overhead. Similarly, for one-off scripts, exploratory notebooks, or throwaway spikes, skip rules entirely and just describe what you want in the prompt.
Rules also don't substitute for actual code review or CI enforcement. A rule saying "always add tests for new functions" is a nudge, not a guarantee; the model can and will skip it under time pressure in the conversation or when a task description conflicts with it. If a convention is non-negotiable, enforce it with a linter, a pre-commit hook, or a CI check, and use the rule as a secondary reinforcement that reduces how often the check fails in the first place. Anthropic's and OpenAI's own guidance on prompting and system instructions makes a similar point in a different context: persistent instructions bias behavior, they don't guarantee compliance (Anthropic prompt engineering docs).
A workflow for maintaining rules as the codebase evolves
Treat the rules directory like code, because it is code that shapes code. Put it under version control (it already is, if it's in the repo), and review changes to it in pull requests the same way you'd review a lint config change. When you notice the AI making the same mistake twice across different sessions, that's your signal to write or fix a rule rather than repeating the correction in chat every time.
Once a quarter, or after any significant refactor that moves directories around, audit the glob patterns against the actual file tree. A five-minute check (grep -r "globs:" .cursor/rules/) against your current directory structure catches the silent breakage described earlier before it costs you a week of subtly-off generated code. Delete rules for patterns and conventions that no longer exist in the codebase; a rule referencing a deleted helper function is actively misleading, not neutral. The goal is a rules directory that stays small enough for a new team member to read start to finish in ten minutes, because a ruleset nobody can hold in their head is one that quietly stops being followed by the humans on the team, long before the model has any trouble with it.
Related Articles

How to configure .cursorignore to prevent context pollution and protect secrets
Cursor indexes your working directory, not just what Git tracks, so an untracked .env file can still leak straight into a prompt. Here's how to lock that down properly.

How to configure Model Context Protocol servers in Cursor
An agent that can run SELECT * FROM orders can usually also run DELETE FROM orders unless you stop it. Here's the exact MCP config, and where to draw the read-only line.

How to fix Cursor codebase indexing issues on large repositories
Reinstalling Cursor rarely fixes a broken index. On monorepos above roughly 50,000 files, an aggressive .cursorignore plus a manual reindex almost always does instead.