Project rules that stop Cursor from inventing bad architecture

Table of Contents
Table of Contents
People paste “build me an invoices module” into Cursor with empty project rules, then they get a new package, a new utils folder, and an architecture nobody on the team uses.
That is not a smarter model problem. That is a missing fence.
I’m Aris Setiawan. Senior full-stack, first Cursor Ambassador in Indonesia. I ship client work in Cursor every week. This post is the fence: .cursor/rules, or the older .cursorrules file. Short, local, boring.
The full production loop is here: How to use Cursor AI to ship faster without wrecking the codebase. The messy-repo version is How to use Cursor AI effectively on a real repo. After the fence, pick the surface: Chat vs Composer in Cursor. A cursor ai workflow that survives a client repo starts with rules.
Why Cursor invents architecture
Cursor is helpful. Helpful without a map invents.
Three setups I still see:
No fence. You open the repo, skip rules, and type “build me X.” The model fills gaps from training data. That is a greenfield Next.js tutorial, not this codebase. New folders. New packages. A lib/api-client.ts next to the one you already have.
Empty or essay rules. A file that says “write clean code” and “follow best practices.” The model nods and still creates packages/invoices-v2 because nothing said “do not create packages.”
A wish with no paths. “Add export on invoices.” No @. No folder map. Composer (or Agent) will find a place. Often the wrong place.
The model is not rebellious. It completes a pattern. If the repo does not name the pattern, it picks one. Project rules name it once.
What belongs in a project rule

I keep rules on half a page. If I cannot read them in a minute, Cursor will not either.
What I actually put in:
Stack. Next.js App Router, TypeScript, pnpm, Tailwind, whatever is true here. I have watched it invent a lockfile in a pnpm repo because nobody said pnpm.
Folder map. Where routes live. Where UI primitives live. Where shared helpers live. Real paths, not “the feature folder.” If this app is app/ and the helper is lib/, I write those strings.
Do not create new packages. Or new top-level folders. If a helper is missing, ask. Prefer the existing lib/ over a new utils/. This line has saved me more diffs than any style guide.
Error and API shapes. { error: string, code?: string } or whatever the nearby route already returns. “Handle errors well” is not a shape. A shape is copy-pasteable.
Naming. Kebab-case files. PascalCase components. Match the sibling.
Test command. pnpm test. Tests next to the module as *.test.ts, if that is what we do. If I skip this, it will skip tests or invent a Jest config in a Vitest repo.
What I leave out
I do not paste a 2,000-line style guide into .cursor/rules.
The model half-reads long files. It will remember “be clean” and forget “do not touch billing.” The long file also drifts. The repo changes. The essay does not.
Out:
- A manifesto about clean architecture
- Opinions the formatter already owns
- A history of the company
- “Always write beautiful, production-ready code”
- Every lint rule, copied from the linter
If a convention is local to one package, I say it in that ticket, not in global rules.
Good rules stop random creativity. They do not make Cursor brilliant. You still have to scope the ticket.
A rules file I would actually commit

Generic Next.js app. I drop this in .cursor/rules and stop typing.
# Project rules
Stack: Next.js App Router, TypeScript, pnpm, Tailwind.
Folders:
- Routes: app/
- UI primitives: components/ui. Reuse these. Do not invent a new button.
- Shared helpers: lib/
- Do not create a new package or a new top-level folder.
If a helper is missing, ask before creating it. Prefer lib/ over a new utils/.
API errors: { error: string, code?: string }. Match the nearby route handler.
Naming: kebab-case files, PascalCase components. Match the sibling file.
Tests: pnpm test. Colocate as *.test.ts.
Do not change schema, auth, or payments unless the prompt names those files.
No drive-by refactors.
Half a page. Extra goes in the prompt or next to the code.
Rules, then Chat, then Composer
Rules are the standing fence. They do not replace a scoped prompt.
My loop on client work:
- Rules exist and fit on half a page.
- Chat finds where the change lives. Plan only. Name paths. Do not write files yet. Same split as Chat vs Composer in Cursor.
- Open those files.
@them. Run Composer with a blast radius. - Still read the PR. Rules reduce invented folders. They do not catch a wrong business rule.
A prompt I actually use, on top of the rules file:
Add CSV export on the invoices list. Follow project rules. Touch
app/invoicesandlib/exportonly. Do not create a new package. Reuse the existing CSV helper.
Composer without rules and without named files is Chat with write access. That is the 30-file diff. Rules plus Chat plus a fenced Composer job is the cursor ai workflow I trust.
If I cannot name the folders, back to Chat. More on that habit: effective-on-a-real-repo.
Checklist I actually use
Before I generate:
- Rules file exists. It names the stack and the folder map.
- It says “do not create new packages” in plain words.
- Error shape and test command are in there, not in my head.
- I am not asking Composer to think. Chat already did the plan.
After Composer:
- No new top-level folder.
- No new package I did not ask for.
- No duplicate helper next to the real one.
- Diff is still PR-sized. I can finish it in one sitting.
If a box fails, I revert the extra, tighten the rule or the prompt, and run once more.
What I tell mentees
Install the fence first. Then pick the surface. Then review like you own the on-call.
I coach this in Cursor mentoring when a team keeps getting invented architecture. The fix is almost never “use a smarter model.” It is a half-page of rules, then Chat to plan, then Composer with named files.
Most people who ask me about project rules just need the file above, on their actual repo, once. If the team needs that workflow installed, that is the Level up lane. I sit with the real repo, not a toy app.



