agentleFS
Sign inSign up

prpm / rules

pr-pm/prpm/.cursor/rules/creating-cursor-rules.mdc

Meta-rule for creating effective Cursor IDE rules with best practices, patterns, and examples

Cursor rule120 starsChanged 7 months ago
---
description: Meta-rule for creating effective Cursor IDE rules with best practices, patterns, and examples
tags: [meta, cursor, documentation, best-practices]
---

# Creating Cursor Rules - Meta Rule

## Overview

This is a meta-rule for creating effective `.cursor/rules` files. Apply these principles when writing or improving Cursor IDE rules for your project.

## When to Use

**Use when:**
- Starting a new project and setting up `.cursor/rules`
- Improving existing project rules
- Converting skills or guidelines to Cursor format
- Team needs consistent coding standards

**Don't use for:**
- Claude Code skills (those go in `.claude/skills/`)
- One-time instructions (just ask directly)
- User-specific preferences (those go in global settings)

## Core Principles

### 1. Be Specific and Actionable

```markdown
# ❌ BAD - Vague
Write clean code with good practices.

# ✅ GOOD - Specific
Use functional components with TypeScript.
Define prop types with interfaces, not inline types.
Extract hooks when logic exceeds 10 lines.
```

### 2. Focus on Decisions, Not Basics

```markdown
# ❌ BAD - Obvious
Use semicolons in JavaScript.
Indent with 2 spaces.

# ✅ GOOD - Decision guidance
Choose Zustand for global state, React Context for component trees.
Use Zod for runtime validation at API boundaries only.
Prefer server components except for: forms, client-only APIs, animations.
```

### 3. Organize by Concern

```markdown
# ✅ GOOD Structure

## Tech Stack
- Next.js 14 with App Router
- TypeScript strict mode
- Tailwind CSS for styling

## Code Style
- Functional components only
- Named exports (no default exports)
- Co-locate tests with source files

## Patterns
- Use React Server Components by default
- Client components: mark with "use client" directive
- Error handling: try/catch + toast notification

## Project Conventions
- API routes in app/api/
- Components in components/ (flat structure)
- Types in types/ (shared), components/*/types.ts (local)
```

## Rule Anatomy

### MDC Format and Metadata

Cursor rules are written in **MDC (.mdc)** format, which supports YAML frontmatter metadata and markdown content. The metadata controls how and when rules are applied.

### Required YAML Frontmatter

Every Cursor rule MUST start with YAML frontmatter between `---` markers:

```yaml
---
description: Brief description of when and how to use this rule
globs: ["**/*.ts", "**/*.tsx"]
alwaysApply: false
---
```

### Frontmatter Properties

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `description` | string | **Yes** | Brief description of the rule's purpose. Used by AI to decide relevance. Never use placeholders like `---` or empty strings. |
| `globs` | array | No | File patterns that trigger auto-attachment (e.g., `["**/*.ts"]`). Leave empty or omit if not using Auto Attached type. |
| `alwaysApply` | boolean | No | If `true`, rule is always included in context. If `false` or omitted, behavior depends on Rule Type. |

### Rule Types

Control how rules are applied using the **type dropdown** in Cursor:

| Rule Type | Description | When to Use |
|-----------|-------------|-------------|
| **Always** | Always included in model context | Core project conventions, tech stack, universal patterns that apply everywhere |
| **Auto Attached** | Included when files matching `globs` pattern are referenced | File-type specific rules (e.g., React components, API routes, test files) |
| **Agent Requested** | Available to AI, which decides whether to include it based on `description` | Contextual patterns, specialized workflows, optional conventions |
| **Manual** | Only included when explicitly mentioned using `@ruleName` | Rarely-used patterns, experimental conventions, legacy documentation |

### Examples by Rule Type

**Always Rule** (Core conventions):
```yaml
---
description: TypeScript and code style conventions for the entire project
alwaysApply: true
---
```

**Auto Attached Rule** (File pattern-specific):
```yaml
---
description: React component patterns and conventions
globs: ["**/components/**/*.tsx", "**/app/**/*.tsx"]
alwaysApply: false
---
```

**Agent Requested Rule** (Contextual):
```yaml
---
description: RPC service boilerplate and patterns for creating new RPC endpoints
globs: []
alwaysApply: false
---
```

**Manual Rule** (Explicit invocation):
```yaml
---
description: Legacy API migration patterns (deprecated, use for reference only)
globs: []
alwaysApply: false
---
```

### Best Practices for Frontmatter

1. **Description is mandatory** - AI uses this to determine relevance. Be specific:
   - ❌ Bad: `Backend code`
   - ✅ Good: `Fastify API route patterns, error handling, and validation using Zod`

2. **Use globs strategically** - Auto-attach to relevant file types:
   - React components: `["**/*.tsx", "**/*.jsx"]`
   - API routes: `["**/api/**/*.ts", "**/routes/**/*.ts"]`
   - Tests: `["**/*.test.ts", "**/*.spec.ts"]`

3. **Avoid always applying everything** - Use `alwaysApply: true` sparingly:
   - ✅ Good for: Tech stack, core conventions, project structure
   - ❌ Bad for: Framework-specific patterns, specialized workflows

4. **Make Agent Requested rules discoverable** - Write descriptions that help AI understand when to use:
   - Include keywords: "boilerplate", "template", "pattern for X"
   - Mention specific use cases: "when creating new API routes"

### Additional Optional Fields

While the core properties above control rule behavior, you may also include:

**title:** (Optional)
- Clear, concise name for the rule
- Example: `Creating Cursor Rules`, `TypeScript Type Safety`

**tags:** (Optional)
- Array of relevant tags for organization
- Use lowercase, kebab-case
- Example: `[meta, cursor, documentation, best-practices]`

**source:** (Optional)
- Where the rule originated from
- Example: `claude-code-skill`, `custom`, `community`

**IMPORTANT:** The `description` field is MANDATORY for all cursor rules. When converting skills to cursor rules or creating new rules, always include a valid description. Never use placeholders like `---` or empty strings.

## Required Sections

### Tech Stack Declaration

```markdown
## Tech Stack
- Framework: Next.js 14
- Language: TypeScript 5.x (strict mode)
- Styling: Tailwind CSS 3.x
- State: Zustand
- Database: PostgreSQL + Prisma
- Testing: Vitest + Playwright
```

**Why:** Prevents AI from suggesting wrong tools/patterns.

### Code Style Guidelines

```markdown
## Code Style
- **Components**: Functional with TypeScript
- **Props**: Interface definitions, destructure in params
- **Hooks**: Extract when logic > 10 lines
- **Exports**: Named exports only (no default)
- **File naming**: kebab-case.tsx
```

### Common Patterns

```markdown
## Patterns

### Error Handling
```typescript
try {
  const result = await operation();
  toast.success('Operation completed');
  return result;
} catch (error) {
  const message = error instanceof Error ? error.message : 'Unknown error';
  toast.error(message);
  throw error; // Re-throw for caller to handle
}
```

### API Route Structure
```typescript
// app/api/users/route.ts
export async function GET(request: Request) {
  try {
    // 1. Parse/validate input
    // 2. Check auth/permissions
    // 3. Perform operation
    // 4. Return Response
  } catch (error) {
    return new Response(JSON.stringify({ error: 'Message' }), {
      status: 500
    });
  }
}
```
```

### What NOT to Include

```markdown
# ❌ AVOID - Too obvious
- Write readable code
- Use meaningful variable names
- Add comments when necessary
- Follow best practices

# ❌ AVOID - Too restrictive
- Never use any third-party libraries
- Always write everything from scratch
- Every function must be under 5 lines

# ❌ AVOID - Language-agnostic advice
- Use design patterns
- Think before you code
- Test your code
```

## Structure Template

```markdown
# Project Name - Cursor Rules

## Tech Stack
[List all major technologies]

## Code Style
[Specific style decisions]

## Project Structure
[Directory organization]

## Patterns
[Common patterns with code examples]

### Pattern Name
[Description]
```code example```

## Conventions
[Project-specific conventions]

## Common Tasks
[Frequent operations with snippets]

### Task Name
```
step 1
step 2
```

## Anti-Patterns
[What to avoid and why]

## Testing
[Testing approach and patterns]
```

## Example Sections

### Tech Stack Section

```markdown
## Tech Stack

**Framework:** Next.js 14 (App Router)
**Language:** TypeScript 5.x (strict mode enabled)
**Styling:** Tailwind CSS 3.x with custom design system
**State:** Zustand for global, React Context for component trees
**Forms:** React Hook Form + Zod validation
**Database:** PostgreSQL with Prisma ORM
**Testing:** Vitest (unit), Playwright (E2E)
**Deployment:** Vercel

**Key Dependencies:**
- `@tanstack/react-query` for server state
- `date-fns` for date manipulation (not moment.js)
- `clsx` + `tailwind-merge` for conditional classes
```

### Patterns Section with Code

```markdown
## Patterns

### Server Component Data Fetching

```typescript
// app/users/page.tsx
import { prisma } from '@/lib/prisma';

export default async function UsersPage() {
  // Fetch directly in server component
  const users = await prisma.user.findMany({
    select: {
      id: true,
      name: true,
      email: true
    }
  });

  return <UserList users={users} />;
}
```

### Client Component with State

```typescript
'use client';

import { useState } from 'react';
import { toast } from 'sonner';

interface FormProps {
  onSubmit: (data: FormData) => Promise<void>;
}

export function Form({ onSubmit }: FormProps) {
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setLoading(true);

    try {
      await onSubmit(new FormData(e.target as HTMLFormElement));
      toast.success('Saved successfully');
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Failed to save';
      toast.error(message);
    } finally {
      setLoading(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
      <button disabled={loading}>
        {loading ? 'Saving...' : 'Save'}
      </button>
    </form>
  );
}
```
```

### Anti-Patterns Section

```markdown
## Anti-Patterns

### ❌ Don't: Default Exports
```typescript
// ❌ BAD
export default function Button() { }

// ✅ GOOD
export function Button() { }
```

**Why:** Named exports are more refactor-friendly and enable better tree-shaking.

### ❌ Don't: Inline Type Definitions
```typescript
// ❌ BAD
function UserCard({ user }: { user: { name: string; email: string } }) { }

// ✅ GOOD
interface User {
  name: string;
  email: string;
}

function UserCard({ user }: { user: User }) { }
```

**Why:** Reusability and discoverability.

### ❌ Don't: Client Components for Static Content
```typescript
// ❌ BAD
'use client';
export function StaticContent() {
  return <div>Static text</div>;
}

// ✅ GOOD - Server component by default
export function StaticContent() {
  return <div>Static text</div>;
}
```

**Why:** Server components are faster and reduce bundle size.
```

## Common Tasks

Include shortcuts for frequent operations:

```markdown
## Common Tasks

### Adding a New API Route

1. Create `app/api/[route]/route.ts`
2. Define HTTP method exports (GET, POST, etc.)
3. Validate input with Zod schema
4. Use try/catch for error handling
5. Return `Response` object

```typescript
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(1)
});

export async function POST(request: Request) {
  try {
    const body = await request.json();
    const data = schema.parse(body);

    // Process...

    return Response.json({ success: true });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return Response.json(
        { error: error.errors },
        { status: 400 }
      );
    }
    return Response.json(
      { error: 'Internal error' },
      { status: 500 }
    );
  }
}
```

### Adding a New Component

1. Create `components/component-name.tsx`
2. Define props interface
3. Export as named export
4. Co-locate test if complex logic

```typescript
// components/user-card.tsx
interface UserCardProps {
  name: string;
  email: string;
  onEdit?: () => void;
}

export function UserCard({ name, email, onEdit }: UserCardProps) {
  return (
    <div className="rounded-lg border p-4">
      <h3 className="font-semibold">{name}</h3>
      <p className="text-sm text-gray-600">{email}</p>
      {onEdit && (
        <button onClick={onEdit}>Edit</button>
      )}
    </div>
  );
}
```
```

## Best Practices

### Keep Rules Under 500 Lines

- Split large rules into multiple, composable files
- Each rule file should focus on one domain or concern
- Reference other rule files when needed (e.g., "See `backend-api.mdc` for API patterns")
- **Why:** Large files become unmanageable and harder for AI to process effectively

### Split Into Composable Rules

Break down by concern rather than creating one monolithic file:

```
.cursor/rules/
  ├── tech-stack.mdc          # Core technologies
  ├── typescript-patterns.mdc # Language-specific patterns
  ├── api-conventions.mdc     # API route standards
  ├── component-patterns.mdc  # React/UI patterns
  └── testing-standards.mdc   # Testing approaches
```

**Why:** Easier to maintain, update, and reuse across similar projects.

### Provide Concrete Examples or Referenced Files

Instead of vague guidance, always include:
- Complete, runnable code examples
- References to actual project files: `See components/auth/LoginForm.tsx for example`
- Links to internal docs or design system
- Specific file paths and line numbers when relevant

**❌ BAD - Vague:**
```markdown
Use proper error handling in API routes.
```

**✅ GOOD - Concrete:**
```markdown
API routes must use try/catch with typed errors. Example:
```typescript
// app/api/users/route.ts (lines 10-25)
export async function POST(request: Request) {
  try {
    const data = await request.json();
    return Response.json({ success: true });
  } catch (error) {
    return handleApiError(error); // See lib/errors.ts
  }
}
```
See `app/api/products/route.ts` for complete implementation.
```

### Avoid Vague Guidance - Write Rules Like Clear Internal Docs

Rules should read like technical documentation, not casual advice:
- Be precise and unambiguous
- Include the "why" behind decisions
- Document exceptions to rules
- Reference architecture decisions
- Link to related rules or documentation

**Think:** "Could a new engineer understand this without asking questions?"

### Reuse Rules When Repeating Prompts in Chat

If you find yourself giving the same instructions repeatedly in chat:
1. Document that pattern in `.cursor/rules/`
2. Include the specific guidance you keep repeating
3. Add examples of correct implementation
4. Update existing rule files rather than creating new ones

**Common scenarios to capture:**
- "Always use X pattern for Y"
- "Don't forget to Z when doing W"
- Corrections you make frequently
- Patterns specific to your team/codebase

### Keep It Scannable

- Use headers and sections
- Bold important terms
- Code examples for clarity
- Tables for comparisons
- Add table of contents for files over 200 lines

### Update Regularly

- Review monthly or after major changes
- Remove outdated patterns
- Add new patterns as they emerge
- Keep examples current
- Archive deprecated rules rather than deleting (for reference)

### Test with AI

Ask AI to:
1. "Create a new API route following our conventions"
2. "Add error handling to this component"
3. "Refactor this to match our patterns"

Verify it follows your rules correctly.

## Real-World Example

See the PRPM registry `.cursor/rules` for a complete example:
- Clear tech stack declaration
- Specific TypeScript patterns
- Fastify-specific conventions
- Error handling standards
- API route patterns

## Checklist for New Cursor Rules

**YAML Frontmatter:**
- [ ] Title field present and descriptive
- [ ] Description field present (MANDATORY - never empty or `---`)
- [ ] Tags array includes relevant categories
- [ ] Optional fields (ruleType, alwaysApply, source) added if applicable

**Project Context:**
- [ ] Tech stack clearly defined
- [ ] Version numbers specified
- [ ] Key dependencies listed

**Code Style:**
- [ ] Component style specified (functional/class)
- [ ] Export style (named/default)
- [ ] File naming convention
- [ ] Specific to project (not generic)

**Patterns:**
- [ ] At least 3 code examples
- [ ] Cover most common tasks
- [ ] Include error handling pattern
- [ ] Show project-specific conventions

**Organization:**
- [ ] Logical section headers
- [ ] Scannable (not wall of text)
- [ ] Examples are complete and runnable
- [ ] Anti-patterns included

**Testing:**
- [ ] Tested with AI assistant
- [ ] AI follows conventions correctly
- [ ] Updated after catching mistakes

---

**Remember:** Cursor rules are living documents. Update them as your project evolves and patterns emerge.

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.