Cursor Rules Best Practices: Stop Your AI Agent from Breaking Your Architecture

Cursor rules are the first line of defense against AI-generated chaos. Here's how to write rules that actually work, the patterns that scale, and why static rules eventually hit a ceiling.

Cover Image for Cursor Rules Best Practices: Stop Your AI Agent from Breaking Your Architecture

Cursor Rules Best Practices: Stop Your AI Agent from Breaking Your Architecture

You've told the AI agent to use your auth pattern three times today. There's a better way.


Cursor rules are persistent, project-level markdown files (.mdc) in your .cursor/rules/ directory that get injected into the AI agent's context when relevant. They replace the need to repeat architectural instructions in every prompt โ€” defining error handling patterns, module boundaries, technology constraints, and security guardrails once so the agent follows them automatically across all conversations.

If you're vibecoding with Cursor, you've probably discovered rules โ€” the .cursor/rules/ directory where you can define persistent instructions that shape how the AI agent behaves across your project.

Rules are powerful. They're also widely misunderstood, underused, and eventually insufficient. This guide covers all three: how to write rules that work, the patterns that scale, and where even the best static rules hit a ceiling.

What Cursor Rules Actually Are

Cursor rules are markdown files (.mdc) in your project's .cursor/rules/ directory. Each rule contains instructions that get injected into the AI agent's context when relevant. They're not prompts โ€” they're persistent, project-level constraints that apply across all conversations.

Think of them as your codebase's operating manual for the AI. Instead of repeating "use our error handling pattern" in every prompt, you write it once as a rule and the agent follows it automatically.

Rule Types

Cursor supports several rule application modes:

  • Always applied โ€” Injected into every conversation. Use for global conventions.
  • Auto-attached โ€” Triggered when specific file patterns are matched. Use for file-type or directory-specific rules.
  • Agent-requested โ€” Available for the agent to pull in when it determines relevance. Use for reference material.
  • Manual โ€” Only included when explicitly referenced with @ruleName. Use for specialized workflows.

The choice of rule type matters as much as the rule content. An always-applied rule that's only relevant to database files wastes context tokens on every frontend task.

Anatomy of an Effective Rule

Bad rules are vague instructions. Good rules are specific, actionable, and scoped.

Bad rule:

Use our standard error handling pattern.

Good rule:

# API Error Handling

All HTTP endpoint handlers in `src/api/` must follow this pattern:

- Wrap handler logic in try-catch
- Use `AppError` class from `src/shared/errors.ts` for all expected errors
- Return errors as `{ error: { code: string, message: string } }`
- Log unexpected errors with `console.error` including request context
- Never expose stack traces in production responses
- Return 400 for validation errors, 401 for auth, 403 for authz, 500 for unexpected

Example:
\`\`\`typescript
try {
  const result = await handler(req);
  res.status(200).json(result);
} catch (err) {
  if (err instanceof AppError) {
    res.status(err.statusCode).json({ error: { code: err.code, message: err.message } });
  } else {
    console.error('Unexpected error:', { path: req.path, error: err });
    res.status(500).json({ error: { code: 'INTERNAL', message: 'Internal server error' } });
  }
}
\`\`\`

The difference: specificity. The good rule leaves no room for interpretation. It names the class, the file path, the response shape, and the status codes. The LLM doesn't have to guess.

Six Rule Patterns That Work

1. The Architecture Boundary Rule

Purpose: Prevent the agent from violating module boundaries.

# Module Boundaries

- `src/api/` handlers must NOT import directly from `src/database/`. 
  Use service layer in `src/services/` as intermediary.
- `src/shared/` must have zero imports from `src/api/` or `src/services/`.
- Database models in `src/models/` must not contain business logic.

Why it works: LLMs frequently take shortcuts across module boundaries because the shortest path between two files is a direct import. This rule enforces the layered architecture you designed.

2. The Pattern Consistency Rule

Purpose: Ensure new code matches existing patterns.

# Service Layer Pattern

All service files in `src/services/` must follow this structure:

- Export a single class named `{Entity}Service`
- Constructor accepts `db: DatabaseClient` parameter
- All public methods are async and return typed results
- Validation happens in the service, not the handler
- Use `ServiceError` for domain errors, not raw throws

Reference: `src/services/UserService.ts` is the canonical example.

Why it works: Pointing to a canonical example gives the LLM a concrete reference rather than an abstract description. "Make it look like UserService" is more effective than a paragraph of instructions.

3. The Technology Constraint Rule

Purpose: Lock down which libraries and approaches to use.

# Technology Constraints

- HTTP client: Use `node-fetch` (already installed). Do NOT use axios, got, or undici.
- Validation: Use Zod schemas. Do NOT use joi, yup, or manual validation.
- Dates: Use native Date. Do NOT add moment, dayjs, or luxon.
- State management: Zustand only. No Redux, MobX, or Jotai.
- CSS: Tailwind utility classes. No CSS modules, styled-components, or inline styles.

Why it works: LLMs default to whatever library is most common in their training data. Without this rule, you'll end up with three different HTTP clients across three features.

4. The File-Scoped Convention Rule

Purpose: Auto-attach rules for specific file types or directories.

Set the rule to auto-attach on *.test.ts:

# Test Conventions

- Use Vitest (not Jest)
- Test file goes next to source file: `foo.ts` โ†’ `foo.test.ts`
- Use `describe` for the module, `it` for each behavior
- Mock external services using `vi.mock()`, never real network calls
- Use factory functions for test data, not inline objects
- Assert on behavior, not implementation details

Why it works: Test conventions only matter when writing tests. Auto-attaching on test files keeps this context out of unrelated conversations.

5. The Security Guardrail Rule

Purpose: Prevent common security mistakes in AI-generated code.

# Security Requirements

- NEVER hardcode API keys, secrets, or credentials. Use environment variables.
- ALL user input must be validated with Zod before processing.
- SQL queries must use parameterized queries. No string concatenation.
- Authentication middleware must run on ALL routes except /health and /public/*.
- File uploads must validate MIME type and enforce size limits.
- CORS must be restricted to allowed origins. Never use `*` in production.

Why it works: LLMs frequently generate insecure-but-functional code. Explicit security rules catch the most common AI-generated security vulnerabilities before they reach your codebase.

6. The Domain Context Rule

Purpose: Give the agent understanding of your business domain.

# Domain Context

This is a B2B SaaS for product teams. Key domain concepts:

- "Deep dive" = AI-powered product analysis (our core feature)
- "Pre-mortem" = Risk analysis run before building
- "Constraint graph" = Dependency map of product requirements and NFRs
- Organizations have many Products. Products have many Premortems.
- Users belong to Organizations. Multi-tenancy is enforced at the query level.
- All pricing is per-organization, not per-user.

Why it works: Without domain context, the LLM names things generically. With it, the generated code uses your actual terminology and reflects your data model accurately.

Writing Rules That Scale

Keep Rules Atomic

One rule per concern. Don't put your error handling pattern, auth strategy, and naming conventions in the same file. Atomic rules are easier to maintain and let Cursor apply only what's relevant.

Use Concrete Examples Over Abstract Descriptions

"Return errors as JSON with a code and message" is ambiguous. A code example showing the exact response shape is not. Always include at least one concrete example per rule.

Reference Canonical Files

Instead of documenting every pattern in the rule, point to a well-written file as the reference implementation. The LLM is better at pattern-matching against real code than interpreting prose descriptions.

Version Your Rules

Your rules will evolve as your codebase does. Keep them in version control (they're already in .cursor/rules/). Review them in PRs. Treat rule changes like architecture decisions.

Don't Over-Rule

More rules means more tokens competing for attention in the context window. If you have 30 always-applied rules, the LLM can't prioritize them all. Keep always-applied rules under 10. Use auto-attach and agent-requested for everything else.

Where Static Rules Hit a Ceiling

Cursor rules are a significant improvement over raw prompting. But they have fundamental limitations:

Rules Don't Know Your Dependency Graph

A rule can say "use the auth middleware on all endpoints." It can't say "this specific endpoint inherits a PII-handling constraint from the user service, which requires data encryption at rest and audit logging." Inherited constraints through dependency chains are invisible to static rules.

Rules Are All-or-Nothing

A rule either applies or it doesn't. There's no mechanism for "this rule applies with high priority when building payment endpoints and low priority otherwise." Real architectural constraints have varying severity and contextual relevance.

Rules Don't Detect Conflicts

If Rule A says "all endpoints must be authenticated" and Rule B says "the webhook handler must accept unauthenticated requests from Stripe," you have a conflict. Cursor won't flag it. The LLM will silently pick one and ignore the other.

Rules Don't Evolve With Your Codebase

When you add a new service, refactor a module, or change an API pattern, your rules need manual updating. They drift from reality at the pace of your development โ€” which, if you're vibecoding, is fast.

Rules Can't Measure Coverage

You have no way to know: "Do my rules cover all the important patterns?" There's no coverage metric. There's no report saying "17 of your 23 features have error handling rules, but 6 don't." You're flying blind on whether your rules are comprehensive.

From Static Rules to Dynamic Constraints

Static rules are the right starting point. They're easy to write, easy to understand, and immediately effective. Use them.

But as your codebase grows past 10-15 features, consider upgrading to a dynamic constraint system that:

  • Injects contextually โ€” Only surfaces the constraints relevant to the current task, not a static wall of rules
  • Propagates through dependencies โ€” Inherited constraints flow automatically when you depend on a constrained component
  • Detects conflicts โ€” Contradictory constraints are flagged before code is generated
  • Tracks coverage โ€” You can see which features have constraint coverage and where the gaps are
  • Evolves with your codebase โ€” Constraints update as your architecture changes

This is what a constraint graph provides. It's not a replacement for Cursor rules โ€” it's the layer that sits underneath them, providing the structured context that static rules can't.

Cutline's MCP integration connects directly to your Cursor environment. Your AI agent can query the product constraint graph in real time, getting precisely the right constraints for every task without you maintaining a growing pile of .mdc files.

The Practical Path

  1. Start with rules. Write the six patterns above for your project. This alone will dramatically improve your AI agent's output consistency.
  2. Review and prune monthly. Rules that contradict your current codebase are worse than no rules. Keep them current.
  3. When rules aren't enough โ€” when you're maintaining 20+ rules, when the agent keeps violating constraints you can't express statically, when your codebase has too many interconnected patterns for flat files โ€” upgrade to a constraint graph.

The goal isn't perfection. It's giving your AI agent enough structural context that its default behavior matches your architecture, so you spend your time building features instead of correcting the agent.


FAQ

Q: What are Cursor rules?

Cursor rules are markdown files (.mdc) in your project's .cursor/rules/ directory that define persistent, project-level instructions for the AI agent. They get injected into the agent's context when relevant, ensuring consistent behavior across all conversations without repeating instructions in every prompt.

Q: How many Cursor rules should I have?

Keep always-applied rules under 10 to avoid context window competition. Use auto-attached rules for file-type or directory-specific conventions, and agent-requested rules for reference material. More rules means more tokens competing for attention, so prioritize quality and specificity over quantity.

Q: What is the difference between always-applied and auto-attached Cursor rules?

Always-applied rules are injected into every conversation regardless of context โ€” use these for global conventions like error handling or naming patterns. Auto-attached rules trigger only when specific file patterns are matched โ€” use these for file-type or directory-specific conventions like test patterns or database migration rules.

Q: Why do Cursor rules stop working as codebases grow?

Static rules hit a ceiling because they don't know your dependency graph (inherited constraints are invisible), they're all-or-nothing (no priority by context), they can't detect conflicts between rules, they don't evolve with your codebase, and they can't measure coverage. A constraint graph addresses these limitations by propagating constraints through dependencies and injecting them contextually.

Q: What are the best Cursor rule patterns?

The six most effective patterns are: (1) architecture boundary rules to prevent module violations, (2) pattern consistency rules with canonical examples, (3) technology constraint rules to lock down libraries, (4) file-scoped convention rules auto-attached to specific file types, (5) security guardrail rules to prevent common vulnerabilities, and (6) domain context rules to give the agent business understanding.


Cutline generates constraint graphs from your product requirements and injects them into Cursor via MCP. Your rules, but dynamic. Try it free โ†’


Read more about

ยท7 min readยท๐Ÿ“Posts

SlopBurn reframes agentic software quality as a depth-first roguelike dungeon crawl. Bugs become monsters, tests become weakpoints, and software quality becomes the main loop instead of an afterthought.

ยท9 min readยท๐Ÿ“Posts

We're evolving from a technical product manager to a research company focused on safe vibecoding. Our mission remains the same: help developers build secure, scalable, and reliable software with AI coding agents โ€” from the first line of code.

ยท9 min readยท๐Ÿ“Posts

A new category of freelance work is exploding: fixing apps that AI built and humans shipped. Full disclosure: I'm a former Upwork employee (2022โ€“2024). All observations below are based on publicly available data. Here's what the numbers say about the vibecoding cleanup economy โ€” and why the hardest 20% is where all the money is.

ยท11 min readยท๐Ÿ“Posts

Whether you just shipped an MVP or are still prompting your first feature, your vibecoded app has security gaps. They're not bugs โ€” they're structural omissions baked into how LLMs generate code. Here's how to find them, fix them, and prevent them at every stage of the software engineering lifecycle.

ยท14 min readยท๐Ÿ“Posts

In 2015, Google warned that ML systems were the 'high-interest credit card of technical debt.' A decade later, vibecoding tech debt makes that metaphor quaint. AI-generated code doesn't carry credit card rates โ€” it carries payday lender rates, with terms designed to look cheap until the first payment is due.

ยท15 min readยท๐Ÿ“Posts

Traditional TDD asks developers to write tests before code. Cutline's Red-Green Refactoring mode flips the script โ€” the constraint graph writes the tests for you, turning every feature into a gauntlet of security, performance, and stability checks that the AI must pass.