{"schemaVersion":"1.0","type":"Article","slug":"prompt-rules-quality-for-cursor-windsurf-and-claude-code-the-complete-developer-guide-rk7u5","url":"https://api.zyvop.com/prompt-rules-quality-for-cursor-windsurf-and-claude-code-the-complete-developer-guide-rk7u5","title":"Prompt & Rules Quality for Cursor, Windsurf, and Claude Code: The Complete Developer Guide","subtitle":"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.","tldr":"Cursor, Windsurf, and Claude Code each inject persistent rules into every prompt — but only Claude Code can enforce them with hooks. This guide covers the real .mdc, rules, and CLAUDE.md formats with working code, a side-by-side comparison, and how to sync them all with Ruler.","keywords":["cursor","Windsurf","Claude Code","AI Coding Agents","developer tools"],"entities":["Pushpum Vats","cursor","Windsurf","Claude Code","AI Coding Agents","developer tools","ZyVOP"],"keyTakeaways":["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."],"headings":["Why this matters","Cursor: Project Rules (.mdc)","Rule types","The .mdc format","Frontmatter fields and the four activation modes","A realistic rule set","Practical rules","Windsurf: Rules &amp; Memories (Cascade)","Rule storage locations","Activation modes","Example: global_rules.md","Example: workspace rule, glob-scoped","XML-tag style (Windsurf's alternate, model-friendly format)","Claude Code: CLAUDE.md + Hooks","CLAUDE.md hierarchy","Example CLAUDE.md","The @import syntax","Hooks: deterministic quality gates","Example: block edits to .env and force-pushes","Example: auto-format and auto-test after every edit","Side-by-side comparison","One rule set, three formats — a worked example","Keeping rules in sync across tools"],"outboundLinks":[],"contentText":"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 conditionsFrontmatter 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: &lt;!-- 1. Always — injected into every single conversation --&gt; --- description: \"Core project context\" alwaysApply: true --- &lt;!-- 2. Auto-Attached — fires only when matching files are in context --&gt; --- description: \"Django ORM and DRF patterns\" globs: \"**/backend/**/*.py\" alwaysApply: false --- &lt;!-- 3. Agent Requested — model reads the description and decides --&gt; --- description: \"How to write and run integration tests in this repo\" alwaysApply: false --- &lt;!-- 4. Manual — only injected when explicitly @-mentioned in chat --&gt; --- 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 testPractical 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 &amp; 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 exportsExample: 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 dictsXML-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: &lt;coding_guidelines&gt; - My project's programming language is Python - Use early returns when possible - Always add docstrings when creating new functions and classes &lt;/coding_guidelines&gt;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 &lt;dir&gt;/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.mdThe @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 &lt;managed policy path&gt; # org-wide, admin-controlledKey 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\" &gt;&amp;2 exit 2 fi if echo \"$TOOL_INPUT\" | jq -r '.command // empty' | grep -q 'push.*--force'; then echo \"Blocked: force push requires human review\" &gt;&amp;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/* ]] &amp;&amp; ! grep -q \"z\\.object\\|zodSchema\" \"$FILE\"; then echo \"Warning: $FILE has no visible Zod validation call\" &gt;&amp;2 fi exit 0Notice 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 initruler 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 configSplit large rule sets into focused files — they're concatenated alphabetically: .ruler/ ├── AGENTS.md ├── coding_style.md ├── api_conventions.md └── security_guidelines.mdApply 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 firstruler.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 = trueEach generated file gets a source marker for traceability, e.g.: &lt;!-- Source: .ruler/api_conventions.md --&gt;...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 -AThe 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 &amp;&amp; \"$FILE\" != *.tsx ]] &amp;&amp; exit 0 npx eslint \"$FILE\" --quiet 2&gt;&amp;1 | tee /tmp/lint-out.txt if [ -s /tmp/lint-out.txt ]; then echo \"ESLint found issues in $FILE — fix before continuing:\" &gt;&amp;2 cat /tmp/lint-out.txt &gt;&amp;2 exit 1 # PostToolUse can't block, but this surfaces feedback to the model fi npx tsc --noEmit -p . 2&gt;&amp;1 | tee /tmp/tsc-out.txt if [ -s /tmp/tsc-out.txt ]; then echo \"Type errors present:\" &gt;&amp;2 cat /tmp/tsc-out.txt &gt;&amp;2 exit 1 fi exit 0Because 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 &amp;&amp; ruler applyFAQ 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.","contentHash":"sha256:a665f3d5621b556c88b8f70493acb1318983996da5a37df66da0e4a70ee3cb1a","authorName":"Pushpum Vats","authorUrl":"https://api.zyvop.com/author/pushpum","authorSameAs":[],"category":null,"tags":["cursor","Windsurf","Claude Code","AI Coding Agents","developer tools"],"audience":"Readers researching cursor","tone":"Practical and evidence-based","readingTimeMinutes":14,"wordCount":3185,"faqs":null,"primaryTopic":"cursor","publishedAt":"2026-09-03T08:13:29.809Z","updatedAt":"2026-09-03T08:13:29.809Z","canonicalUrl":"https://api.zyvop.com/prompt-rules-quality-for-cursor-windsurf-and-claude-code-the-complete-developer-guide-rk7u5"}