ZYVOPMulti-Platform Sync
SeriesAI NewsWhy ZyVOPJoin Discord
LoginGet Started
ZYVOPMulti-Platform Sync

The Developer Publishing Hub. Write once, publish everywhere, and make your work citation-ready with built-in SEO, AEO, and GEO discovery support. Zero reader paywalls.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • Why ZyVOP
  • Developer API & CLI
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

© 2026 ZyVOP. Developer Publishing Hub.

Zero paywalls · Full content ownership
All systems operational
HomePrompt & Rules Quality for Cursor, Windsurf, and Claude Code: The Complete Developer Guide

Prompt & Rules Quality for Cursor, Windsurf, and Claude Code: The Complete Developer Guide

How .cursor/rules, Windsurf's Rules & Memories, and Claude Code's CLAUDE.md + hooks actually work — with real config, code, and a cross-tool sync setup.

Pushpum Vats
Pushpum Vats
Senior Developer
September 3, 2026
13 min read
Prompt & Rules Quality for Cursor, Windsurf, and Claude Code: The Complete Developer Guide
#cursor#Windsurf#Claude Code#AI Coding Agents#developer tools
👍2

AI coding agents forget everything the moment your session ends. Every new chat, every fresh terminal, every reset context window starts from zero — no memory of your linting rules, your folder conventions, or the fact that you told it three times yesterday not to use default exports.

Rules files and hooks are how you fix that. They're not a bolt-on "prompt quality" product — they're native, built-in mechanisms in Cursor, Windsurf, and Claude Code that inject persistent context before the model ever sees your prompt, and (in Claude Code's case) can deterministically gate what the agent is allowed to do.

This guide covers the real, current mechanism in each tool, with working code, plus how to keep them in sync so you're not maintaining three divergent config files by hand.


Why this matters

LLMs are stateless between completions. Without a rules mechanism, "use functional components," "no default exports," "TypeScript strict mode," and "never touch .env" all have to be retyped, every session, and the model still might not follow them consistently.

Rules files fix the first problem: they inject standing context automatically. Hooks (Claude Code only, as of this writing) fix the second problem: they give you deterministic enforcement that doesn't depend on the model remembering anything at all.

Keep that distinction in mind through the rest of this guide — rules shape what the agent knows; hooks control what the agent is allowed to do.


Cursor: Project Rules (.mdc)

The old single-file .cursorrules is deprecated. The current system is Project Rules — multiple .mdc (Markdown + Config) files living in .cursor/rules/, each with its own activation logic.

Rule types

Type

Location

Scope

Shared via

Project Rules

.cursor/rules/*.mdc

Current repo

Git

User Rules

Cursor Settings → Rules

All your projects

Not shared

Team Rules

Cursor dashboard

All team members

Dashboard (paid plans)

AGENTS.md

Project root/subdirs

Current repo

Git

The .mdc format

Each file is YAML frontmatter + Markdown body:

---
description: "TypeScript conventions for this project"
globs: "src/**/*.ts,src/**/*.tsx"
alwaysApply: false
---

# TypeScript Standards

- Use strict mode (`tsconfig` → `strict: true`)
- Prefer interfaces over type aliases for object shapes
- Use const objects instead of enums
- No default exports except for Next.js page components
- All function parameters must have explicit types
- Use early returns for error conditions

Frontmatter fields and the four activation modes

Field

Type

Effect

description

string

Summary the agent uses to decide relevance in "Agent Requested" mode

globs

string | string[]

File patterns that auto-attach this rule when a matching file is open

alwaysApply

boolean

If true, injected into every chat regardless of context

Combining these gives you four practical modes:

<!-- 1. Always — injected into every single conversation -->
---
description: "Core project context"
alwaysApply: true
---

<!-- 2. Auto-Attached — fires only when matching files are in context -->
---
description: "Django ORM and DRF patterns"
globs: "**/backend/**/*.py"
alwaysApply: false
---

<!-- 3. Agent Requested — model reads the description and decides -->
---
description: "How to write and run integration tests in this repo"
alwaysApply: false
---

<!-- 4. Manual — only injected when explicitly @-mentioned in chat -->
---
description: "One-off migration playbook, rarely needed"
alwaysApply: false
---

A realistic rule set

.cursor/rules/
├── project-context.mdc      # alwaysApply: true
├── typescript.mdc           # globs: src/**/*.{ts,tsx}
├── testing.mdc              # globs: **/*.test.ts
├── api-error-format.mdc     # description-based, agent decides
└── db-migration-local.mdc   # manual, gitignored, personal only

.cursor/rules/testing.mdc:

---
description: "Testing conventions using Vitest"
globs: "**/*.test.ts,**/*.spec.ts"
alwaysApply: false
---

# Testing Standards

- Use Vitest, not Jest
- One `describe` block per exported function
- Mock external HTTP calls with `msw`, never with manual `fetch` stubs
- Every new API route needs at least one happy-path and one error-path test

Practical rules

  • Keep each .mdc file under ~500 lines — a bloated file burns context budget on every request.

  • Name files in kebab-case by concern (error-responses.mdc, not rules2.mdc).

  • Disable a rule without deleting it: set alwaysApply: false and strip the globs/description.

  • For personal, non-shared overrides, use a *-local.mdc naming convention and add .cursor/rules/*-local.mdc to .gitignore.

  • AGENTS.md is the simpler fallback for small projects that don't need glob-level scoping — and it doubles as a cross-tool format (see "Keeping rules in sync across tools" below).


Windsurf: Rules & Memories (Cascade)

Windsurf's agent, Cascade, splits persistent context into two separate systems that people often conflate:

  • Memories — auto-generated by Cascade during a session, or created on request ("remember this"). Workspace-scoped, free (no credit cost), not shared across projects.

  • Rules — explicitly authored by you, always active, applied globally or per-workspace.

Rule storage locations

Location

Scope

~/.codeium/windsurf/global_rules.md (or via Settings)

Every workspace

.windsurf/rules/*.md in the project root

Current workspace

.windsurf/rules/ in a subdirectory

That subdirectory only

.windsurfrules (legacy single file)

Current workspace, still supported

Activation modes

Mode

Behavior

Always On

Injected into every Cascade interaction

Manual

Only applied when @mentioned in the Cascade input

Model Decision

Cascade reads a natural-language description and decides relevance itself

Glob

Applied when files matching a pattern (e.g. src/**/*.ts) are in context

Example: global_rules.md

# Global Rules

- Server Components by default in Next.js; Client Components only when
  the file needs interactivity, state, or browser-only APIs
- Never suggest `any` in TypeScript — use `unknown` and narrow it
- Prefer named exports over default exports

Example: workspace rule, glob-scoped

---
trigger: glob
globs: "*.py"
---

# Python Backend Rules

- Follow PEP 8; format with `ruff format`
- All public functions require type hints and a docstring
- Use `pydantic` models for request/response validation, not raw dicts

XML-tag style (Windsurf's alternate, model-friendly format)

Windsurf documentation also supports wrapping rules in XML-style tags, which some teams find easier for the model to parse and cite back:

<coding_guidelines>
- My project's programming language is Python
- Use early returns when possible
- Always add docstrings when creating new functions and classes
</coding_guidelines>

Practical rules

  • Each rule file has a hard character cap (in the low five figures — Windsurf truncates content beyond it), so split large standards into multiple focused files rather than one giant global_rules.md.

  • Add .windsurfrules (the legacy single-file format) to .gitignore if it holds personal preferences rather than team conventions.

  • Don't put project-specific facts ("we migrated the auth service last sprint") in Rules — that's what auto-generated Memories are for. Rules should hold durable policy, not session trivia.

  • Review Cascade's auto-generated memories periodically via the Memories panel; stale ones actively mislead the agent.


Claude Code: CLAUDE.md + Hooks

Claude Code splits the same problem into two genuinely different tools, and this is the part most guides gloss over: CLAUDE.md is context, hooks are control. CLAUDE.md can be ignored by a distracted model. A hook cannot.

CLAUDE.md hierarchy

Claude Code walks up the directory tree and merges multiple CLAUDE.md files, broadest to most specific:

Level

Location

Scope

Managed policy

/Library/Application Support/ClaudeCode/CLAUDE.md (macOS) / /etc/claude-code/CLAUDE.md (Linux)

Org-wide, enterprise-managed

User

~/.claude/CLAUDE.md

Every project, every session

Project

CLAUDE.md at repo root

This repo, committed to git

Subdirectory

<dir>/CLAUDE.md

Loaded on demand when Claude works in that folder

More specific files layer on top of (don't fully replace) broader ones — a frontend/CLAUDE.md and a backend/CLAUDE.md can carry completely different conventions in a monorepo without bloating the root file.

Example CLAUDE.md

# Project: Acme Billing Service

## Stack
- Node 20, TypeScript, Fastify, Postgres via Prisma
- Tests: Vitest. Run with `npm test`, not `npm run test:watch` in CI contexts.

## Conventions
- All monetary values are integer cents, never floats
- Every new endpoint needs a corresponding OpenAPI entry in `openapi.yaml`
- Do not add new npm dependencies without calling it out explicitly in your response

## Commands
- `npm run dev` — start local server on :3000
- `npm run db:migrate` — apply pending Prisma migrations
- `npm run lint:fix` — run before considering any task done

@docs/architecture.md
@~/.claude/personal-style.md

The @import syntax

CLAUDE.md files can pull in other files with @path/to/file:

  • Supports relative paths, absolute paths, and home-directory paths (@~/.claude/...)

  • Imports can themselves import other files, up to 5 levels deep

  • @ references are not evaluated inside code spans or fenced code blocks — so documenting the syntax (as above) doesn't accidentally trigger a real import

Use this to split a sprawling root file into composable pieces (@docs/architecture.md, @docs/testing-strategy.md) instead of one 2,000-line wall of text.

Hooks: deterministic quality gates

CLAUDE.md is a request, not a guarantee — the model can still skip a step under time pressure inside a long agentic run. Hooks are shell commands Claude Code fires automatically at fixed points in its execution loop, regardless of what the model "remembers."

Where hooks live:

~/.claude/settings.json          # global, all projects
.claude/settings.json            # project-level, committable
.claude/settings.local.json      # project-level, gitignored
<managed policy path>            # org-wide, admin-controlled

Key lifecycle events:

Event

Fires

Can block the action?

PreToolUse

Before a tool call executes

✅ Yes (exit code 2)

PostToolUse

After a tool call succeeds

❌ No — but can trigger follow-up (format, test, notify)

UserPromptSubmit

When you submit a prompt, before Claude processes it

✅ Yes

Stop

When Claude finishes responding

—

SessionStart

New session begins

—

PreCompact

Before context gets compacted

—

Notification

Claude sends a notification

—

Basic shape, in .claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Every hook receives structured JSON on stdin describing the event (tool_name, tool_input, etc.), and controls what happens next via its exit code:

  • exit 0 → continue normally

  • exit 2 (on PreToolUse) → block the tool call; stderr is shown to the model as the reason

  • Non-zero on PostToolUse → surfaced as feedback, but the action already happened

Example: block edits to .env and force-pushes

#!/bin/bash
# .claude/hooks/block-dangerous.sh
INPUT=$(cat)
TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input | tojson')

if echo "$TOOL_INPUT" | jq -r '.file_path // empty' | grep -q '\.env'; then
  echo "Blocked: .env files must be edited manually, not by the agent" >&2
  exit 2
fi

if echo "$TOOL_INPUT" | jq -r '.command // empty' | grep -q 'push.*--force'; then
  echo "Blocked: force push requires human review" >&2
  exit 2
fi

exit 0
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Edit|Write",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/block-dangerous.sh" }
        ]
      }
    ]
  }
}

Example: auto-format and auto-test after every edit

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
        ]
      }
    ]
  }
}

This is the real difference from a rules file: writing "always run Prettier after editing" in CLAUDE.md is a hope. A PostToolUse hook is a guarantee — it runs every time, independent of the model's attention.

Set an explicit timeout (in seconds) on any hook that shells out to something slow, like a full test suite — default timeouts are short and have changed across releases, so don't rely on the default for long-running commands.


Side-by-side comparison

Cursor

Windsurf

Claude Code

Context file(s)

.cursor/rules/*.mdc

.windsurf/rules/*.md, global_rules.md

CLAUDE.md (hierarchical)

Legacy single-file format

.cursorrules (deprecated)

.windsurfrules (still supported)

—

Granular scoping

Per-file, via globs

Per-file, via glob trigger

Per-directory, via nested files

Model decides relevance?

Yes (description, no alwaysApply)

Yes ("Model Decision" mode)

No — loaded by directory/hierarchy

Auto-generated memory

No

Yes (Cascade Memories)

No

Team-shared rules

Yes (paid dashboard)

Via committed .windsurf/rules/

Via committed CLAUDE.md

Can block an action outright

No

No

Yes — PreToolUse hooks

Auto-run commands after edits

No

No

Yes — PostToolUse hooks

Cross-file import

No native syntax

No native syntax

@path imports, depth 5

The takeaway: Cursor and Windsurf give you rich, granular context injection. Claude Code gives you that too (via CLAUDE.md), plus a second, independent layer of enforcement that doesn't exist in the other two as of this writing.


One rule set, three formats — a worked example

Say you want one policy — "REST handlers must validate input with Zod, and every mutation needs a test" — enforced consistently. Here's the same rule expressed natively in each tool.

Cursor — .cursor/rules/api-validation.mdc:

---
description: "Input validation and test coverage for REST handlers"
globs: "src/routes/**/*.ts"
alwaysApply: false
---
# API Validation

- Every route handler must validate `req.body` with a Zod schema before use
- Every handler that mutates state needs a corresponding test in `*.test.ts`

Windsurf — .windsurf/rules/api-validation.md:

---
trigger: glob
globs: "src/routes/**/*.ts"
---
# API Validation

- Every route handler must validate `req.body` with a Zod schema before use
- Every handler that mutates state needs a corresponding test in `*.test.ts`

Claude Code — a section inside src/routes/CLAUDE.md:

## API Validation

- Every route handler must validate `req.body` with a Zod schema before use
- Every handler that mutates state needs a corresponding test in `*.test.ts`

...and optionally backed by a hook that actually checks it, rather than trusting the model read the section:

#!/bin/bash
# .claude/hooks/check-zod-validation.sh — PostToolUse on route file edits
FILE=$(cat | jq -r '.tool_input.file_path // empty')
if [[ "$FILE" == src/routes/* ]] && ! grep -q "z\.object\|zodSchema" "$FILE"; then
  echo "Warning: $FILE has no visible Zod validation call" >&2
fi
exit 0

Notice the content is identical — only the wrapper (frontmatter keys, file location) differs. That's exactly the problem the next section solves.


Keeping rules in sync across tools

Maintaining near-duplicate content in .cursor/rules/, .windsurf/rules/, and CLAUDE.md by hand drifts fast. A small but real open-source ecosystem exists specifically to solve this — write once, generate the rest.

Ruler (@intellectronica/ruler)

The most established of these. One .ruler/ directory is the source of truth; ruler apply distributes it to every supported agent's native format (Claude Code, Cursor, Windsurf, Copilot, Codex CLI, Cline, Aider, and others).

npm install -g @intellectronica/ruler
cd your-project
ruler init

ruler init scaffolds:

.ruler/
├── AGENTS.md      # your canonical rules, in plain Markdown
├── ruler.toml     # which agents to target, output paths
└── mcp.json       # optional shared MCP server config

Split large rule sets into focused files — they're concatenated alphabetically:

.ruler/
├── AGENTS.md
├── coding_style.md
├── api_conventions.md
└── security_guidelines.md

Apply to every configured agent, or target specific ones:

ruler apply                          # all configured agents
ruler apply --agents cursor,claude   # only these two
ruler revert                         # undo, restores from .bak files
ruler revert --dry-run               # preview the undo first

ruler.toml controls which agents get generated and where — check ruler --help for the exact keys in the version you install, since the CLI evolves, but the shape is roughly:

default_agents = ["claude", "cursor", "windsurf", "copilot"]

[agents.cursor]
enabled = true

[agents.windsurf]
enabled = true

Each generated file gets a source marker for traceability, e.g.:

<!-- Source: .ruler/api_conventions.md -->

...so if Cursor's rule looks wrong, you know exactly which canonical file to fix.

Lighter alternatives

If you don't want a full tool, a pre-commit hook that regenerates target files from one canonical AGENTS.md accomplishes the same thing with a few lines:

# .husky/pre-commit
npx agentsync sync   # example: regenerates CLAUDE.md, .cursorrules, etc. from AGENTS.md
git add -A

The specific tool matters less than the principle: pick one canonical file, generate the rest, and never hand-edit a generated file.


Hooks as a real quality gate

Since Claude Code is the only one of the three with an enforcement layer today, it's worth showing a fuller pattern: a "quality gate" that runs lint, type-check, and tests after every batch of edits, and reports failures back to the model so it can self-correct — without you doing anything.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/quality-gate.sh",
            "timeout": 120
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/quality-gate.sh
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

[[ "$FILE" != *.ts && "$FILE" != *.tsx ]] && exit 0

npx eslint "$FILE" --quiet 2>&1 | tee /tmp/lint-out.txt
if [ -s /tmp/lint-out.txt ]; then
  echo "ESLint found issues in $FILE — fix before continuing:" >&2
  cat /tmp/lint-out.txt >&2
  exit 1   # PostToolUse can't block, but this surfaces feedback to the model
fi

npx tsc --noEmit -p . 2>&1 | tee /tmp/tsc-out.txt
if [ -s /tmp/tsc-out.txt ]; then
  echo "Type errors present:" >&2
  cat /tmp/tsc-out.txt >&2
  exit 1
fi

exit 0

Because this is a PostToolUse hook it can't undo the edit — but a non-zero exit surfaces the lint/type output directly to the model as feedback, so in an agentic loop it typically self-corrects on the next turn instead of you catching it in review three files later.

For anything that should be prevented outright rather than corrected after the fact (touching production config, running rm -rf, editing CI secrets), use PreToolUse with exit 2 instead — that's the only exit code across either event that actually stops the action.


Common mistakes

  • Treating CLAUDE.md as an enforcement mechanism. It's context. If something must never happen, it needs a PreToolUse hook, not a bullet point.

  • One giant rules file. All three tools reward focused, single-topic files over a 2,000-line monolith — it wastes context budget and the model deprioritizes buried instructions.

  • Forgetting alwaysApply/trigger semantics. A Cursor rule with no globs and alwaysApply: false and a vague description may simply never fire. Test that your rule actually attaches.

  • Committing personal preferences. "I prefer terse commit messages" belongs in Cursor's User Rules or a gitignored local file — not in a team's shared .cursor/rules/.

  • No timeout on slow hooks. A hook that shells out to a full test suite without an explicit timeout can stall the agent loop.

  • Hand-editing generated files. If you adopt Ruler or a similar sync tool, edit the canonical source only — hand edits get silently overwritten on the next apply.


Cheat sheet

Cursor
  .cursor/rules/*.mdc        → frontmatter: description, globs, alwaysApply
  .cursor/rules/*-local.mdc  → gitignore for personal overrides

Windsurf
  global_rules.md            → all workspaces
  .windsurf/rules/*.md       → this workspace (or subdirectory)
  .windsurfrules              → legacy single-file, still works

Claude Code
  ~/.claude/CLAUDE.md         → user, all projects
  ./CLAUDE.md                 → project root
  ./**/CLAUDE.md              → subdirectory, loaded on demand
  @path/to/file.md            → import (max depth 5)
  ~/.claude/settings.json     → global hooks
  .claude/settings.json       → project hooks (commit this)
  .claude/settings.local.json → project hooks (gitignore this)

Sync tool
  npm install -g @intellectronica/ruler
  ruler init && ruler apply

FAQ

Is .cursorrules still supported? Yes, but it's deprecated — Cursor still reads it, but new projects should use .cursor/rules/*.mdc for glob scoping and per-rule activation modes.

Does Windsurf have anything like Claude Code's hooks? Not as of this writing. Windsurf's Rules and Memories shape context; they don't gate or block tool calls the way a Claude Code PreToolUse hook can.

Can a Cursor or Windsurf rule stop the agent from running a command? No — both are context-injection systems. Neither has a deterministic blocking mechanism equivalent to Claude Code's PreToolUse + exit 2.

What's the difference between Windsurf Memories and Rules? Memories are auto-generated (or on-request) facts scoped to a workspace, free to create, meant for session-specific context. Rules are explicitly authored, always-active policy. Don't put durable team conventions in Memories — they're not guaranteed to persist the way a committed rules file is.

Do I need a separate tool to keep these in sync? Not strictly — you can hand-maintain three files for a small project. Past a handful of rules, drift becomes real; a sync tool like Ruler (or a simple pre-commit script) pays for itself quickly.

Where do MCP server configs fit into this? Outside the scope of this guide, but the same tools generally handle it: Cursor uses .cursor/mcp.json, Claude Code uses .mcp.json, and Ruler can distribute a shared mcp.json alongside your rules.

Comments (0)

Login to post a comment.

Pushpum Vats
Pushpum Vats

Passionate developer sharing knowledge about modern web technologies and best practices.

Subscribe to Pushpum Vats's Newsletter

More from Pushpum Vats

View profile

Qwen3.8-27B Hits Cerebras at 1,500 Tokens a Second: The Full Picture

Qwen3.8-27B is live on Cerebras at ~1,500 tokens/sec, confirmed on its dedicated model page rather than the general catalog the HN post linked. It's a real 10x speedup over typical GPUs, but not a new record for Cerebras, and the vendor benchmarks come with notable caveats.

6 minSep 4

Three Surveys. Three №1 Tools. All of Them Correct.

Copilot leads adoption. Claude Code wins loyalty. Cursor dominates editor workflows. Three surveys, three winners—and together they reveal why the future isn't one AI coding assistant, but an AI coding stack.

8 minJul 10

Securing a VPS in 2026: A No-Nonsense Hardening Guide

A freshly provisioned VPS gets scanned within minutes of going online. This guide skips the theory and gives you the exact commands and config files to lock it down — ordered by what actually matters most first, from SSH and firewall rules to post-quantum key exchange, database hardening, and systemd sandboxing. Updated for 2026 standards.

9 minJul 1

I Built a Tiny AI Agent From Scratch — Every Line Tested Before It Touched a Real API

AI agents are often overcomplicated. At their core, they’re just models that can call tools, use the results, and continue working. This tutorial builds that loop from scratch in about 80 lines of Python and includes a complete test run that proves the agent works before making a single real API call.

8 minJun 14

Apple Just Confirmed Claude Is Coming to Your iPhone — Here's What WWDC 2026 Actually Changes

Last week’s WWDC was Apple’s most AI-focused event in years: a rebuilt Siri, a standalone chat interface, deeper on-screen awareness, and smarter app actions were officially announced. But claims that iOS 27 will let Siri hand requests to Claude, ChatGPT, or Gemini remain reports and rumors, not Apple-confirmed features.

5 minJun 15