Cursor Rules Tutorial: 12 Steps, 70 Min [2026] Cursor's project rules in the `.cursor/rules` folder are now applied by both the desktop editor and the command-line agent, and reportedly by local Agent SDK runs, according to AgentVera's CLI guide cited in a 12-step, roughly 70-minute tutorial targeting Composer 2.5, Cursor's newest Composer model as of November 11, 2026. The tutorial builds a TypeScript service called ledger-api with seven rule files plus an AGENTS.md and validates the rules with a Python harness that calls the CLI agent and checks replies with regular expressions. The guide also reports that the older root `.cursorrules` file is deprecated in an early-November documentation snapshot, that plain `.md` files dropped into the rules folder do not behave like `.mdc` rules, and cites Stack Overflow's 2025 Developer Survey finding that 84% of developers use or plan to use AI tools and 31% currently use AI agents. Cursor loads project rules from the .cursor/rules folder, and the command-line agent applies the same rules system as the editor, according to AgentVera’s CLI guide https://agentvera.dev/en/blog/how-to-use-cursor-cli/ . That one fact changes how you should write them. A rule file is no longer a note for a chat sidebar. It is a behavior contract that the desktop app and the terminal agent both read, and reportedly local Agent SDK runs read it too. Build a small TypeScript service called ledger-api, give it seven rule files plus an AGENTS.md, then prove the rules work with a Python harness that calls the CLI agent on Composer 2.5 and checks every reply with regular expressions. Plan on about 70 minutes. Teams moving Copilot instructions into Cursor can start with our walkthrough on migrating rules from GitHub Copilot https://shattered.io/cursor-rules-migrate-github-copilot-2026/ , then come back here to test what they migrated. The stakes keep rising. Stack Overflow’s 2025 Developer Survey https://stackoverflow.co/company/press/archive/stack-overflow-2025-developer-survey/ reported that 84% of developers use or plan to use AI tools, that 31% currently use AI agents, and that 69% of developers who have used agents at work agreed the agents raised their productivity. When agents write that much code, unwritten team conventions stop working. Composer 2.5, Cursor’s own agentic model, is the newest Composer model as of November 11, 2026, so the demo targets it. Don't miss new tech stories on Google Add Shattered once in the Google app and our stories show up in your news suggestions. Add now https://www.google.com/preferences/source?hl=en&q=https%3A%2F%2Fshattered.io%2F What Cursor Rules Are and What Changed in 2026 Cursor rules are plain-text instructions that Cursor injects into the model’s context, so the agent follows your conventions without a reminder in every prompt. Project rules live in .cursor/rules as .mdc files. Each one is markdown with a short YAML frontmatter block, and that block decides when the rule loads. The loading mode matters more than the wording, because a rule that never loads cannot help and a rule that always loads costs tokens on every request. Four modes cover almost every case. The table maps each mode to its frontmatter, its trigger and the kind of guidance it suits. | Rule type | Frontmatter | When it loads | Best for | |---|---|---|---| | Always apply | alwaysApply: true | Every chat and agent run in scope | Language level conventions, logging, error types | | Apply to specific files | globs: src/routes/ / .ts | When a matching file is in context | Framework patterns, test style | | Apply intelligently | description: ... with alwaysApply: false | When the agent judges the description relevant | Migrations, security reviews, rare workflows | | Apply manually | alwaysApply: false , no globs, no description | When you mention the rule by name or point the agent at the file | Release checklists, one-off playbooks | Several things shifted during 2026. First, the terminal caught up with the editor. Tutorials that describe Cursor only as a graphical editor, or that leave out CLI support, are outdated as of October and November 2026, according to the Agenten-Kompass overview of Cursor https://ki-agentenkompass.de/en/agents/cursor/ . Second, the CLI reportedly reads AGENTS.md and CLAUDE.md alongside .cursor/rules , so a repo that already carries one of those files gets partial coverage for free. Third, a documentation snapshot from early November describes the older root .cursorrules file as deprecated, which makes the folder the only format worth writing today. Fourth, ordinary .md files dropped into the rules folder do not behave like .mdc rules, so guides that tell you to do that are wrong as of November 2026. Finally, rules written in Cursor Settings user rules are a different layer. A Cursor forum thread on the Agent SDK says Settings rules are not loaded by the local process, so anything a CI job or script needs must live in the repository. Treat the modes as layers that add up. On a typical request the agent can see the always-on baseline, any glob rules whose files are in context, any description rules it judges relevant and whatever you mention by name. Assume that two rules which disagree both reach the model, and write each rule so a conflict cannot arise or is settled in the text itself. That design pressure favors a tiny always-on set. A useful rule of thumb: if a convention applies to fewer than half your files, scope it with a glob instead of promoting it to the baseline. The model underneath also moves fast. Cursor’s changelog shows Claude Sonnet 5.5 arriving on September 28, Claude Opus 5.5 and Grok 4.7 in the same stretch, and Claude Haiku 5.5 as an opt-in model on October 5. A rule that a model follows today may slip on tomorrow’s default, which is why this build ends in a test harness rather than a prompt-writing contest. If you are still choosing a tool, compare how Claude Code, Cursor and Copilot compare on pricing and limits https://shattered.io/claude-code-vs-cursor-vs-github-copilot-pricing-limits-2026/ before you invest in rule files, and see Windsurf versus Cursor https://shattered.io/windsurf-vs-cursor-devin-desktop-rename-40-seat-2026/ if seat price drives the decision. Prerequisites and Versions for This Cursor Rules Tutorial Gather the tools below before you start. Versions follow what the sources confirm, and anything unpinned means the current release is fine. For other editor setups and agent tools, browse the coding and developer tools coverage https://shattered.io/category/software/ . | Requirement | Version | Why it is needed | |---|---|---| | Cursor desktop | Current stable build check Help, About | Authoring rules and testing them in the editor | | Cursor CLI | Current build, the agent command | Headless runs for the test harness | | Composer 2.5 | Available on your plan | The model the harness targets | | Node.js | 22 or newer | Runs the demo API and Vitest | | TypeScript | 5.x | Strict mode demo code | | Python | 3.10 or higher | Runs the harness | | Git | Any recent release | Rules live in version control | A Cursor account with access to Composer 2.5 is required, since the harness calls the CLI under your login. Cursor reworked its individual plans in October 2026, and forum reports describe weekly included usage with third-party models billed separately at API list prices, so check the pricing page for your own limits before you run a large suite. The cost section later puts numbers on a typical run. Budget the time honestly. Scaffolding and the CLI check take about 15 minutes, the seven rule files about 25 minutes, the harness about 15 minutes and the first full run with fixes about 15 minutes. The first run takes longest because it exposes the weak rules, and every later change takes minutes. Skipping the harness saves the 15 minutes and costs far more in unreviewed guesswork later. Two habits help. Work in a fresh Git repository so you can diff what each rule changes. And open the repo root in Cursor rather than a parent folder, because rule discovery starts from the project directory. The CLI main command is agent as of October 2026, and cursor-agent still works as an alias, per AgentVera, so older scripts keep running. Confirm the install with agent --version . Steps 1 and 2: Create the Demo Repo and Check the Cursor CLI Step 1: scaffold ledger-api. The project is deliberately tiny. It needs just enough structure for different rules to have different targets: a routes folder, a test file, a migrations folder and a frozen legacy folder. mkdir ledger-api && cd ledger-api git init npm init -y npm install express zod npm install -D typescript vitest tsx @types/node @types/express mkdir -p src/routes src/lib src/services src/legacy migrations docs mkdir -p .cursor/rules src/legacy/.cursor/rules scripts/rule-tests Create the stub files the rules will refer to. The harness never runs this code, since every test prompt tells the agent not to edit files. The files exist so the agent sees a realistic project when it builds context. js cat src/lib/logger.ts <<'EOF' export const logger = { info: msg: string = process.stdout.write ${msg}\n , warn: msg: string = process.stderr.write WARN ${msg}\n , error: msg: string = process.stderr.write ERROR ${msg}\n , }; EOF cat src/lib/errors.ts <<'EOF' export class AppError extends Error { constructor public code: string, message: string { super message ; } } EOF cat src/legacy/fees.ts <<'EOF' export function calcFee cents: number : number { return Math.floor cents 0.029 ; } EOF Step 2: verify the CLI. The harness shells out to agent , so a broken install fails every case. Sign in, check the version and run a no-edit smoke prompt. If your build names the sign-in command differently, agent --help lists it. agent --version agent login agent -p "Reply with the single word ready." --model composer-2.5 A healthy smoke test prints one short line and exits with code 0. If it hangs, the CLI is almost always waiting on authentication, which the troubleshooting section covers. If you also want the agent running unattended on pull requests later, our guide to running the Cursor CLI headless in GitHub Actions https://shattered.io/cursor-cli-github-actions-headless-agent-2026/ picks up where this step leaves off. The -p flag runs the agent in print mode, which writes the reply to standard output and exits. That makes the agent scriptable from Python, shell or CI, and it explains why the harness needs no editor window. Flag names can shift between CLI releases, so confirm the exact spelling with agent --help on your build before you copy commands into automation. Commit the scaffold before you add any rules. A clean first commit gives you a baseline, and every later step becomes a small diff you can read. If a rule behaves strangely, git diff on the rules folder is your fastest debugging tool. Steps 3 and 4: Write the Always-On Baseline Rule and AGENTS.md Step 3: write the baseline rule. An always-on rule should hold the few conventions that apply to every file and that the model gets wrong without a reminder. Keep it short. Every line costs input tokens on every request, and a long baseline crowds out the scoped rules that follow. --- description: Baseline TypeScript conventions for every file in the repo alwaysApply: true --- Baseline - Use TypeScript strict mode. Never write any . Use unknown and narrow it. - Use named exports only. Never use export default . - Log through logger from src/lib/logger.ts logger.info , logger.warn , logger.error . Never call console. . - Throw AppError from src/lib/errors.ts for expected failures. - Money is integer cents. Never use floating point for currency. - For code requests, answer with code first and at most one sentence of explanation. Save that as .cursor/rules/00-baseline.mdc . The numeric prefix is a habit, not a requirement. It keeps the baseline first in directory listings and signals to teammates that it is the foundation. Each line follows a pattern worth copying. It states the rule, names the replacement and points at a real file. Never use console. becomes a rule the model can obey because it also says which logger to call. A bare prohibition leaves the model guessing, and guesses drift. Step 4: write AGENTS.md. Cursor’s CLI reportedly reads AGENTS.md too, and other agent tools read it as well, so use it for facts about the repo rather than style. Commands, layout and boundaries belong here. Style belongs in the rules. ledger-api Commands - Install: npm install - Test: npm test - Typecheck: npm run typecheck Layout - src/routes/ HTTP handlers, thin - src/services/ business logic - src/lib/ logger, errors, money helpers - src/legacy/ frozen code, bug fixes only - migrations/ forward-only SQL Boundaries - Never edit files under migrations/ that have already been applied. - Never commit secrets. Read configuration from environment variables. The split avoids duplication. If a convention appears in both files, they will eventually disagree, and the agent will follow whichever it reads last. Put each fact in exactly one place and link by file name when needed. A useful test is to ask whether a fact would still be true if you switched languages. If yes, it belongs in AGENTS.md. If it describes how code should look, it belongs in a rule. Before moving on, open the repo in Cursor and ask the agent a throwaway question about the project. A good answer mentions the test command from AGENTS.md without prompting, which confirms both files load. For static checks on files like these, a dedicated Cursor rules linter https://shattered.io/cursor-rules-linter-agents-md-cursorignore-12-steps-2026/ catches frontmatter mistakes, while the harness you build below checks behavior. Steps 5 and 6: Scope Cursor Rules to Routes and Tests With Globs Scoped rules carry the detail that would bloat the baseline. A glob attaches a rule only when a matching file is in the conversation, so the route conventions never cost tokens while you edit a migration. That economy is the main argument for splitting one big rules file into several small ones. Step 5: the routes rule. It enforces input validation and a uniform response envelope, two things generated handlers often skip. --- description: Express route handlers globs: src/routes/ / .ts alwaysApply: false --- Route handlers - Validate params, query and body with zod before using them. Use safeParse . - Respond with the envelope { data, error } . - Success: error: null . - Failure: data: null and error: { code, message } . - Map AppError to an HTTP status in one place, src/lib/http.ts . Never call res.status 500 by hand. - Keep handlers thin. Business logic lives in src/services/ . Save it as .cursor/rules/routes.mdc . Keep the glob unquoted and use one pattern per rule while you learn. If a rule fails to attach, a single pattern is much easier to debug than a list. Step 6: the testing rule. Test conventions are a classic source of drift, because models trained on years of Jest code reach for Jest by default. --- description: Test conventions globs: / .test.ts alwaysApply: false --- Tests - Use Vitest. Import describe , it and expect from vitest . Never import jest. - Arrange, act, assert, in that order, with a blank line between the phases. - Never use snapshot assertions for business logic. - One behavior per test. Name each test as a sentence that states the behavior. Save it as .cursor/rules/testing.mdc . Add the scripts the rule mentions so the project stays coherent: { "scripts": { "test": "vitest run", "typecheck": "tsc --noEmit" } } Merge that block into your package.json by hand. Glob rules attach based on files in context, so in a headless run you must name the file path in the prompt. A prompt that says write a handler without naming a path may never trigger the routes rule. Every test case in the harness names its target file for this reason, and the same habit pays off in daily use. Use forward slashes in globs on every operating system, including Windows. Glob design follows the same logic as folder design. Match on directory boundaries your team already respects, such as src/routes/ , instead of guessing at file-name patterns. A glob that matches too much turns a scoped rule into an always-on rule by accident, and a glob that matches too little leaves the rule dormant. After you write one, list its matches with git ls-files and a pathspec, and read the output with a skeptical eye. Steps 7 and 8: Description-Triggered, Manual and Nested Rules Step 7: a description-triggered rule. This mode suits guidance that matters rarely but matters a lot. The agent reads the description, decides whether the task is relevant and loads the rule only then. The description is the trigger, so write it like a search query for the situation, not like a title. --- description: Use when creating or editing SQL migrations, schema changes or database indexes alwaysApply: false --- Migrations - Forward-only. Never edit an applied migration and never write down migrations. - Start every file with SET lock timeout = '5s'; . - Make reruns safe with IF NOT EXISTS and IF EXISTS . - Never drop a table or column in the same release that stops using it. Expand first, contract later. - Create indexes with CREATE INDEX CONCURRENTLY , outside a transaction. Save it as .cursor/rules/db-migrations.mdc . Then add the manual rule. It carries a marker line that proves it loaded, which lets the harness test it later. --- alwaysApply: false --- Begin the reply with the line RELEASE-CHECKLIST-V1. Then list these checks as a numbered list: 1. Changelog entry written 2. Migrations reviewed by a second person 3. Feature flags default to off 4. Rollback command documented Save it as .cursor/rules/release-checklist.mdc . With no globs and no description, the agent has nothing to match against, so the rule loads only when you mention it by name in the editor or point the agent at the file. Step 8: a nested rule. Rules can live in subfolders, so a directory can carry its own constraints. The frozen legacy folder is the perfect case. The rule applies only to work inside that directory, so the stricter policy never leaks into new code. --- description: Frozen legacy code alwaysApply: true --- Legacy folder - Fix the reported bug and nothing else. - Keep function names, signatures and the export function style exactly as they are. - Do not add dependencies and do not convert to newer syntax. Save it as src/legacy/.cursor/rules/legacy.mdc . The baseline asks for named exports and says nothing against function declarations, so the two rules agree here. If a nested rule contradicts the root baseline, state the precedence explicitly inside the nested rule, because the model will otherwise pick a winner at random. Check the tree now with find .cursor src/legacy/.cursor -name " .mdc" . You should see five files in the root folder baseline, routes, testing, db-migrations, release-checklist and one nested file. The seventh rule arrives in the next step. Step 9: Add an MCP Server and a Rule That Governs It Model Context Protocol servers give the agent tools and data beyond the repo. The official specification index marks version 2026-07-28 as the latest release, and the MCP introduction https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro describes the open protocol that Cursor’s editor and CLI both use for tools. The July release added a stateless protocol core, header-based routing, cacheable list results, authorization hardening and a formal extensions framework. Cursor reads server definitions from a JSON file under .cursor , and the editor and CLI reportedly share it. Start with a read-only filesystem server scoped to the docs folder, which keeps the blast radius small. { "mcpServers": { "docs-fs": { "command": "npx", "args": "-y", "@modelcontextprotocol/server-filesystem", "./docs" } } } Save it as .cursor/mcp.json and validate it with python3 -m json.tool .cursor/mcp.json . Pin a package version once you settle on a server, because an unpinned npx -y runs whatever is newest on every launch. Our walkthrough on setting up Cursor MCP servers https://shattered.io/set-up-cursor-mcp-servers-2026/ covers transports, environment variables and debugging in more depth. A server is a capability, and capabilities need policy. Write that policy as a rule, so it travels with the repo and every teammate’s agent inherits it. --- description: Use when a task needs an MCP tool or external data alwaysApply: false --- MCP usage - Use only servers defined in .cursor/mcp.json . Never suggest adding a server mid-task. - Prefer read tools. Ask before calling any tool that writes, deletes or sends. - Never pass secrets, tokens or customer data as tool arguments. - Name the tool you used in the reply. Save it as .cursor/rules/mcp-usage.mdc . That makes seven rule files in total. The third bullet matters most, since tool arguments often leave the machine and land in a third-party log. A rule cannot enforce that boundary on its own, but it sets the default and gives reviewers something concrete to check in pull requests. Treat every MCP server as untrusted code until you have read it. Cursor’s changelog from early November describes team marketplaces where administrators distribute MCP servers across cloud agents, the editor and the CLI, according to a single report, which makes central review more valuable, not less. Steps 10 and 11: Build the Cursor Rules Test Harness Writing rules is the easy half. Proving that a model follows them is the half most teams skip, and it explains why rule files rot. The harness sends fixed prompts to the CLI, extracts the code from each reply and checks it against patterns that must and must not appear. Because model output varies from run to run, each case runs several times and the suite reports a pass rate. Step 10: define the cases. Keep cases in a Python file rather than JSON, since regular expressions need raw strings and JSON forces double backslashes. Each case names the file it targets so glob rules attach. SUFFIX = " Reply with only the code in a single fenced block. Do not edit or create files." CASES = { "name": "baseline-no-any", "prompt": "In src/lib/money.ts, write parseAmount input that turns a decimal string like '12.50' into integer cents." + SUFFIX, "must match": r"export function|const parseAmount" , "must not match": r":\s any\b", r"export default" , }, { "name": "baseline-logger", "prompt": "In src/lib/money.ts, write parseAmount input that logs a warning and returns null when the string is not a valid decimal." + SUFFIX, "must match": r"logger\.warn" , "must not match": r"console\." , }, { "name": "routes-envelope", "prompt": "In src/routes/accounts.ts, write an Express handler for GET /accounts/:id that validates the id and returns the account." + SUFFIX, "must match": r"\bz\.", r"error:\s null" , "must not match": r"export default" , }, { "name": "testing-vitest", "prompt": "In src/routes/accounts.test.ts, write one test that checks GET /accounts/:id returns 404 for an unknown id." + SUFFIX, "must match": r"from '\" vitest '\" " , "must not match": r"\bjest\b", r"toMatchSnapshot" , }, { "name": "migration-safe", "prompt": "Create migrations/0002 add closed at.sql that adds a closed at timestamp column to accounts." + SUFFIX, "must match": r" ?i lock timeout", r" ?i if not exists" , "must not match": r" ?i drop table|column " , }, { "name": "legacy-keeps-shape", "prompt": "Fix the rounding bug in calcFee in src/legacy/fees.ts by rounding half up to whole cents. Show only the changed function.", "must match": r"export function calcFee\ " , "must not match": r"export const calcFee", r"export default" , }, { "name": "manual-not-auto", "prompt": "Draft the release notes for version 1.4.0 as three bullet points.", "must match": , "must not match": r"RELEASE-CHECKLIST-V1" , }, { "name": "manual-explicit", "prompt": "Follow .cursor/rules/release-checklist.mdc and give me the checklist for version 1.4.0.", "must match": r"RELEASE-CHECKLIST-V1" , "must not match": , }, Save it as scripts/rule-tests/cases.py . The last two cases work as a pair. The first proves the manual rule stays out of unrelated work, and the second proves it loads on request. Testing only the negative would pass even if the rule file were broken. Writing the patterns takes judgment. A pattern that is too strict fails correct code that happens to use different spacing or an alias. A pattern that is too loose passes broken code. Each case above checks one observable property, such as the absence of console calls or the presence of a lock timeout, and none of them asserts the full shape of a function. Prefer properties a reviewer could confirm in ten seconds. Avoid patterns that test the model’s phrasing, like a particular variable name, unless the rule demands that exact name. When a pattern misfires on correct code, fix the pattern and leave a comment beside the case so the next person does not tighten it again. Step 11: write the runner. It shells out to agent -p in print mode, saves every raw reply for debugging and exits non-zero when any case drops below the pass-rate bar, so CI can gate on it. python /usr/bin/env python3 import argparse import pathlib import re import subprocess import sys from cases import CASES ROOT = pathlib.Path file .resolve .parents 2 OUT = ROOT / ".rule-tests" / "out" FENCE = re.compile r" \w+- \n . ? ", re.S def ask prompt: str, model: str, timeout: int - str: cmd = "agent", "-p", prompt, "--model", model proc = subprocess.run cmd, cwd=ROOT, capture output=True, text=True, timeout=timeout if proc.returncode = 0: raise RuntimeError proc.stderr.strip :300 or f"exit {proc.returncode}" return proc.stdout def evaluate case: dict, reply: str - list str : blocks = FENCE.findall reply text = "\n".join blocks if blocks else reply problems = for pattern in case.get "must match", : if not re.search pattern, text, re.M : problems.append f"missing /{pattern}/" for pattern in case.get "must not match", : if re.search pattern, text, re.M : problems.append f"forbidden /{pattern}/" return problems def main - int: ap = argparse.ArgumentParser ap.add argument "--runs", type=int, default=3 ap.add argument "--model", default="composer-2.5" ap.add argument "--only", default="" ap.add argument "--min-rate", type=float, default=1.0 ap.add argument "--timeout", type=int, default=240 args = ap.parse args OUT.mkdir parents=True, exist ok=True failed = 0 for case in CASES: if args.only and args.only not in case "name" : continue passes, notes = 0, for n in range 1, args.runs + 1 : try: reply = ask case "prompt" , args.model, args.timeout except RuntimeError, subprocess.TimeoutExpired as err: notes.append f"run {n}: agent error: {err}" continue OUT / f"{case 'name' }-{n}.txt" .write text reply problems = evaluate case, reply if problems: notes.append f"run {n}: " + ", ".join problems else: passes += 1 rate = passes / args.runs status = "PASS" if rate = args.min rate else "FAIL" if status == "FAIL": failed += 1 print f"{status} {case 'name' :<22} {passes}/{args.runs}" for note in notes: print f" {note}" print f"\n{failed} case s below the {args.min rate:.0%} pass-rate bar" return 1 if failed else 0 if name == " main ": sys.exit main Save it as scripts/rule-tests/run.py . Two design choices deserve a note. The evaluator checks only the fenced code when a block exists, so an apology paragraph that mentions console.log does not trigger a false failure. And the runner never passes a flag that allows file writes, so the suite cannot change your repo even if a prompt goes sideways. Add .rule-tests/ to .gitignore . The raw replies are debugging material, not source. Step 12: Run the Suite and Read the Output Run the full suite from the repo root. Three runs per case is a sensible default, and eight cases means 24 CLI calls. python3 scripts/rule-tests/run.py --runs 3 A clean run prints one line per case and a summary: PASS baseline-no-any 3/3 PASS baseline-logger 3/3 PASS routes-envelope 3/3 PASS testing-vitest 3/3 PASS migration-safe 3/3 PASS legacy-keeps-shape 3/3 PASS manual-not-auto 3/3 PASS manual-explicit 3/3 0 case s below the 100% pass-rate bar Your first run will probably look worse. Rules rarely fail evenly. One case slips while the rest hold, and the failure points at a specific rule or a specific sentence. Reading a failure Suppose the logger case fails on one of three runs. The runner prints the pattern that broke and the run number: FAIL baseline-logger 2/3 run 2: missing /logger\.warn/, forbidden /console\./ 1 case s below the 100% pass-rate bar Open .rule-tests/out/baseline-logger-2.txt and read the raw reply. Three causes cover most failures. The rule was ambiguous, the prompt pulled in a competing habit, or the rule never loaded. A quick way to separate the last case from the first two is to run the single case several more times with --only baseline-logger --runs 6 . A rule that loads and is merely weak fails sometimes. A rule that never loads fails every time. Fix the wording first, since that is free. Replace a vague instruction with a concrete one, and name the file or function the model should use. Rerun the failing case, then the whole suite, because a change to one rule can disturb another. Commit rule changes and case changes together, so the history shows which wording produced which pass rate. The suite also turns the hunt for the right words into a measurable loop. Without it, rule tuning means changing a sentence, chatting once and trusting a single sample. With it, you see that a rule passes 3 of 3, then 2 of 3 after an edit, and you revert. That feedback loop is what keeps a rule pack healthy as models change underneath it. The loop also pairs well with Cursor’s Plan Mode https://shattered.io/cursor-plan-mode-tutorial-plangate-12-steps-2026/ for larger changes, where you want the agent to propose steps before it edits anything. Choose the pass-rate bar by consequence. A rule that guards against data loss, like the migration rule, deserves a 100% bar and a larger sample. Run it alone with --only migration --runs 10 after every wording change. A style rule can live with two passes out of three. Start strict on a fresh pack, then relax individual cases on purpose rather than lowering the global bar, and write the reason next to the case so the decision survives a staff change. Composer 2.5 Cost Math for Rule Testing Every case sends the attached rules plus the project context as input and returns a short answer. Cursor publishes Composer 2.5 prices on its model page and changelog, and the two speeds differ sharply. Standard costs $0.50 per million input tokens and $2.50 per million output tokens. Fast costs $3.00 per million input and $15.00 per million output. Cursor says Fast is the default in the product and prices it lower than other fast models at similar speeds. | Speed | Input per 1M tokens | Output per 1M tokens | 24-call suite assumed 6,000 in, 800 out per call | 30 nightly runs | |---|---|---|---|---| | Standard | $0.50 | $2.50 | $0.12 | $3.60 | | Fast | $3.00 | $15.00 | $0.72 | $21.60 | The suite figures rest on an assumption, not a measurement. The table multiplies an assumed token count for 24 calls by the published Standard rates of $0.50 per million input tokens and $2.50 per million output tokens, and by the Fast rates of $3.00 and $15.00. Your real token counts depend on how many rules attach and how much project context the agent pulls in, so treat the table as a template and replace the assumption with numbers from your own usage page. Standard versus Fast Because Fast is the in-product default, check which speed your CLI calls bill at before you schedule anything nightly. A forum report from October described Composer 2.5 subagents running at Fast speed even when the parent request used another setting, and Cursor’s release notes now state that standard subagents bill at standard speed. For a test suite where latency barely matters, Standard saves about 83% at these list prices. A forum response also states that an explicitly selected Composer 2.5 request stays on that model, and automatic routing applies only when Auto is selected, so pass --model explicitly and the harness measures the model you think it measures. Plan mix matters too. Forum reports say new plans include Cursor’s own models and Grok models in weekly included usage while third-party models bill separately, so a suite pointed at a Claude or GPT model can cost real money even when Composer runs are covered. For a closer look at what each model costs per task, read our breakdown of Cursor model pricing https://shattered.io/cursor-models-pricing-12-steps-2026/ . And keep in mind that rule text is input on every request, so trimming a 60-line baseline to 30 lines cuts cost on every call, not only in the harness. Three levers keep spend flat as the suite grows. Run stable cases once and risky cases many times, keep the always-on baseline short, and pin the speed and model so a default change on Cursor’s side cannot quietly triple the bill. Check the usage page after the first full run and write the real token counts into your own copy of the table above. Common Pitfalls When Writing Cursor Rules These are the mistakes that show up most often once rule files meet real teams. Each one has a cheap fix. - Writing essays instead of constraints. A rule file that reads like a style guide dilutes its own best lines. Write short imperative bullets and delete anything the model already does correctly. Run the harness before and after a cut, and keep the shorter version if the pass rate holds. - Marking everything as always-on. Always-on rules load on every request. Ten of them bury the model in text and invite contradictions. Reserve them for facts that apply to every file, and push the rest behind globs or descriptions. - Using the wrong extension. Ordinary .md files in the rules folder do not behave like .mdc rules, and the root .cursorrules file is described as deprecated in current documentation. Rename files to .mdc and keep frontmatter on line one. - Forbidding without replacing. Never use console.log leaves a gap. Name the substitute, as the baseline does with logger , and the model fills the gap correctly. - Expecting globs to attach in a vacuum. A glob rule loads when a matching file is in context. A headless prompt that never names a path may skip it. Name the target file in every prompt and test case. - Testing once and trusting it. Model output varies. A single passing chat proves little. Run each case several times and track the rate. - Relying on Settings rules for automation. User rules in Cursor Settings do not reach scripts and CI runs. Move anything automation needs into the repo. - Skipping review for rule files. A rule file is an instruction set for an agent with shell access. Treat changes to it like changes to a CI pipeline, with a code owner and a pull request. An attacker who lands a malicious line in a rules file can steer every future run, so open unfamiliar repositories with the same caution you apply to unfamiliar scripts. - Letting rules replace planning. Rules shape style. They do not break a refactor into safe steps. For large changes, ask for a plan first and review it. Two pitfalls deserve extra attention because they hide well. The first is silent contradiction. A baseline that says named exports only and a nested rule that says default export in this folder will not raise an error, and the model will follow one or the other unpredictably. The second is rot. A rule that mentions src/lib/http.ts keeps mentioning it after the file moves. Review rule files whenever you refactor, and let the harness catch the rules that no longer match reality. A short authoring routine prevents most of the pitfalls above. Start from a real failure, such as a pull request where the agent used Jest in a Vitest repo. Write the smallest rule that would have prevented it, add the case that reproduces the mistake and confirm the case fails without the rule. Only then add the rule and watch the pass rate climb. Rules written this way answer a documented problem, they stay short and they come with proof. Rules written from a blank page tend to restate what the model already does and to grow without limit. Troubleshooting Cursor Rules That Do Not Apply Use this table when a rule seems to be ignored. Work from the top, because the first rows cover the most common causes. | Symptom | Likely cause | Fix | |---|---|---| | A glob rule never attaches | No matching file in context, or the glob does not match the path | Name the file in the prompt, check the pattern with git ls-files , use forward slashes | | The rule file is ignored entirely | Wrong extension or missing frontmatter | Rename to .mdc and put the --- block on line one | | The editor follows a rule but the CLI does not | The rule lives in Cursor Settings, not the repo | Move it into .cursor/rules and commit it | | agent: command not found | The CLI is not on your PATH, or an older guide used another name | Reinstall, restart the shell, and try the cursor-agent alias | | The harness hangs on the first case | The CLI is waiting on login or an approval prompt | Run agent login , then retry the smoke prompt from Step 2 | | manual-explicit fails while manual-not-auto passes | The agent did not read the file, or the working directory is wrong | Run from the repo root and use the full relative path in the prompt | | An MCP server does not show up | Invalid JSON, or npx is missing | Validate with python3 -m json.tool .cursor/mcp.json and check Node | | A case passes sometimes and fails sometimes | The rule is weak or the pattern is too strict | Raise --runs , tighten the wording, and loosen the regex only after reading raw replies | | Two rules contradict each other | A nested rule and a root rule disagree | Narrow the scope and state precedence inside the narrower rule | | Costs jump after a change | The suite runs at Fast speed or on a third-party model | Pass --model explicitly, check usage, and cut the runs | | An always-on rule fades in a long chat | Context grew and the rule got less weight | Start a fresh chat, and trim the rule to its core lines | Two details help across all of these rows. First, the saved replies under .rule-tests/out separate model behavior from harness behavior in seconds, so read them before you change any rule. Second, the CLI exposes MCP management commands, and the /mcp list command reportedly shows which servers loaded. Check your build with agent --help , since command names evolved during 2026 and guides written before October may describe older behavior. If a failure survives every row above, reduce the problem. Delete all rules except the one under test, run its case six times, and add rules back one at a time. The rule that breaks the pass rate is your culprit. This bisect works because rule loading is deterministic even though the model’s reply is not, so a failure that appears only with a certain combination points straight at an interaction. Advanced Tips: Canary Rules, CI and the Agent SDK Add canary lines to prove loading. A canary is a harmless sentence that makes a rule’s presence visible, like the marker in the release checklist. Add a canary to a rule you suspect is not loading, ask a question that should trigger it, and look for the marker. Remove the canary once you have your answer, since it pollutes real replies. Gate rule changes in CI. The runner exits with code 1 on failures, so a pull request that touches .cursor/rules/ can run the suite with a lowered bar, such as --runs 3 --min-rate 0.67 , to tolerate noise while still catching a rule that stopped working. Pair that with a code owner for the rules folder. Cursor’s Bugbot also reviews pull requests, and our comparison of AI code review tools including Bugbot https://shattered.io/ai-code-review-tools-bugbot-vs-codex-2026/ shows where that fits next to your own checks. Reuse rules in scripted agents. Cursor offers an Agent SDK, and the same rule files may be worth sharing with scripted agents. Whether local SDK sessions load your on-disk rules is not something to take for granted, so test it in your own setup. Our hands-on Cursor SDK tutorial https://shattered.io/cursor-sdk-tutorial-typescript-review-agent-12-steps-2026/ builds a review agent that could share this exact rule pack. Compare models with the same suite. Because the cases are model-agnostic, swap the --model value and rerun. Cursor’s changelog lists Claude Sonnet 5.5, Claude Opus 5.5 and Grok 4.7 among the models added this autumn, so you can see which one follows your conventions best and at what price. Run agent --help or the model list command to confirm the exact model identifiers your account exposes. Add a frontmatter check to pre-commit. A tiny script catches the most common authoring error before it reaches a teammate: bash /bin/sh bad=0 for f in $ git ls-files ' .mdc' ; do head -n 1 "$f" | grep -q '^---$' || { echo "missing frontmatter: $f"; bad=1; } done exit $bad Save it as scripts/check-rules.sh , make it executable and call it from your pre-commit hook. Roll the pack out in stages. Land the baseline and the harness first, then add scoped rules one pull request at a time, each paired with its case. Put the before-and-after pass rate in the pull request description, since reviewers can judge a rule diff only with that number beside it. Assign the rules folder to a code owner group and review it on the same cadence as your CI configuration. When a rule stops changing results, delete it. Dead rules cost input tokens on every call, and they mislead new teammates about what the team actually enforces. Final project tree After all twelve steps, the repository holds the files below. Everything except the stubs under src comes straight from the steps above. ledger-api/ AGENTS.md package.json .gitignore contains .rule-tests/ .cursor/ mcp.json rules/ 00-baseline.mdc routes.mdc testing.mdc db-migrations.mdc release-checklist.mdc mcp-usage.mdc docs/ migrations/ scripts/ check-rules.sh rule-tests/ cases.py run.py src/ lib/logger.ts lib/errors.ts legacy/fees.ts legacy/.cursor/rules/legacy.mdc routes/ services/ Commit this state and tag it. From here, every new convention follows the same loop: write the rule, add a case that would fail without it, run the suite and commit both together. A repo that works that way accumulates rules that earn their place, and it loses the ones that stop earning it. FAQ: Cursor Rules in 2026 What is the .cursor/rules folder? It is the project-level directory where Cursor reads rule files. Each .mdc file carries frontmatter that controls when it loads, and the body holds the instructions. The folder is supported by the editor and by the CLI agent, and nested folders can hold rules scoped to a subdirectory. Should I use .mdc, .md or .cursorrules? Use .mdc inside .cursor/rules . A documentation snapshot from early November 2026 describes the root .cursorrules file as deprecated, and ordinary .md files in the rules folder do not act as rules. Keep AGENTS.md for repo facts like commands and layout. Does the Cursor CLI follow the same rules as the editor? Per AgentVera’s CLI guide, the CLI agent applies the same rules system as the editor. User rules set in Cursor Settings are the exception, since a forum thread says local SDK processes do not load them, so keep automation-critical rules in the repository. How many rules should a project have? Start with one short always-on baseline and add scoped rules only when a real mistake repeats. Seven files, as in this build, suits a small service. More rules mean more to maintain and more chances to contradict each other, so delete rules that your test suite shows no longer change behavior. Do rules cost tokens? Yes. Attached rule text is part of the input on each request, so a long always-on rule costs money every time. At Composer 2.5 Standard rates of $0.50 per million input tokens, the cost per request is small, but it multiplies across a team and a CI schedule. Which model is best for following rules? Composer 2.5 is Cursor’s own agentic model and the newest Composer release as of November 11, 2026, so it is a sensible default. Adherence still differs by model and by rule, so run your own suite against two or three models before you decide. Can I share rules across repositories? Yes, through a template repository or a shared folder that teams copy or vendor into each project. Keep the harness cases next to the rules so every repo that adopts the pack also adopts the tests. Are Cursor rules a security boundary? No. Rules steer the model, but they do not enforce permissions. Use them to set defaults, and rely on real controls such as CLI permission settings, scoped MCP servers and code review for anything that matters.