Write Rules That Encode Your Conventions
What it is
Why it works
Without rules you re-explain your conventions in every prompt ('use our error type', 'no default exports', 'tests colocated'), and the model forgets between chats. Rules move that knowledge into the context automatically, so generated code matches your codebase instead of generic patterns.
When to use it
Any project with real conventions: a chosen stack, house patterns, naming rules, libraries to prefer or ban. The more opinionated the codebase, the higher the payoff.
When not to use it
A brand-new prototype with no conventions yet. Writing rules before you have patterns just encodes guesses you'll fight later.
Prompt
In .cursor/rules, write specific, testable rules — not vibes:
"- Use the `Result<T, E>` type for fallible functions; never throw for expected errors.
- No default exports; named exports only.
- Tests live next to source as `*.test.ts` and use Vitest.
- Prefer `zod` for validation; do not add new validation libraries."Example
A team kept getting throw new Error(...) from Cursor despite using a Result type. A three-line rule banning throws for expected errors fixed it permanently — new code now matches the codebase on the first try.
Advanced version
Split rules by scope: global rules for the whole repo, and path-scoped rules for areas with different conventions (e.g. stricter rules under packages/api). Keep each rule concrete enough that you could write a lint check for it — vague rules get ignored.
Common mistakes
- Writing aspirational, fuzzy rules ('write clean code') the model can't act on.
- Letting rules rot after you change conventions, so Cursor enforces last quarter's patterns.
- Putting rules in a personal note instead of the repo, so teammates get different output.