Cursor codebase messy: cleaning up after the agent, and keeping it clean
A Cursor codebase gets messy when the agent makes wide edits with nothing checking them: rules saved in the wrong format are ignored, long chats lose earlier conventions to summarisation, and no test or type check runs before it says it is done. Split the work, rewrite rules as small .mdc files, add a stop hook that runs checks, and review every diff.
Symptom
Cursor is an editor, not a hosted builder, so the mess is entirely in your repository. That is good news: everything below is files you can change and commit.
Why it happens
Nothing limits the size of a change
Cursor's docs say there is no limit on the number of tool calls Agent can make during a task, and that Agent can modify workspace files without approval, except configuration files. A vague prompt plus a capable model equals a wide change. Each edit may be reasonable; together they are hard to review, so they get accepted unread.
Rules that never load
This one is easy to miss, because nothing warns you. Cursor's rules docs say project rules must be .mdc files in .cursor/rules, and a plain .md file there is ignored because it has no frontmatter for description, globs and alwaysApply. A rule set to Apply Intelligently without a description is never picked. The docs also say @ file references in a rule are read when needed, not inlined, so a rule that only says "follow @conventions.md" may never surface the actual conventions.
Long rules are a problem too. Cursor recommends keeping each rule under 500 lines and splitting large ones.
Long chats forget
Every chat shares a fixed context window. Cursor's docs say that when it fills, older parts of the conversation are compressed into a summary. The convention you agreed in the first ten minutes of a three-hour session may survive only as a sentence, or not at all.
Nothing checks the work
Unless you ask, the agent decides when it is finished. If type checks and tests are not part of that decision, the code is done when it looks done.
The fix
- Branch and measure. Before changing anything, record where you are:
git switch -c chore/cleanup
npx tsc --noEmit | tail -n 1
git ls-files "*.ts" "*.tsx" | xargs wc -l | sort -n | tail -n 15 # largest files
rg -n "export function (formatDate|formatCurrency|fetcher|apiClient)" --type ts # likely duplicates-
Split what is already there. Cursor ships a
/split-to-prsskill that splits large changes into smaller pull requests. Use it on any open branch with a diff too big to review, then merge the pieces one at a time. -
Rewrite your rules as small
.mdcfiles. One always-on rule for the few things that matter everywhere, and scoped rules for specific folders:
---
alwaysApply: true
---
- Next.js 15 App Router, TypeScript strict, pnpm.
- Before creating a helper, search lib/ for an existing one and reuse it.
- Data access only in lib/db/*. Components never query the database.
- Finish every task by running `pnpm typecheck` and `pnpm test` and fixing failures.---
globs: src/components/**/*.tsx
alwaysApply: false
---
- Named exports only. One component per file.
- Keep components under 200 lines; extract subcomponents into the same folder.
- Formatting helpers come from @src/lib/format.ts.Save them as .cursor/rules/core.mdc and .cursor/rules/components.mdc. Check Customize then Rules to confirm both show as active. If you prefer plain markdown, use AGENTS.md instead; Cursor reads nested ones per folder.
- Add hooks so checks run without asking. Hooks live in
.cursor/hooks.jsonand run from the project root. AnafterFileEdithook formats each file the agent touches; astophook runs checks when it finishes and sends failures back as the next message:
{
"version": 1,
"hooks": {
"afterFileEdit": [{ "command": "node .cursor/hooks/format.mjs" }],
"stop": [{ "command": "node .cursor/hooks/verify.mjs", "loop_limit": 3, "timeout": 300 }]
}
}// .cursor/hooks/verify.mjs: the stop hook receives status and loop_count on stdin
import { execSync } from "node:child_process";
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync(0, "utf8"));
if (input.status !== "completed") {
console.log("{}");
process.exit(0);
}
const run = (cmd) => {
try {
execSync(cmd, { stdio: "pipe" });
return null;
} catch (e) {
return `${cmd} failed:\n${String(e.stdout ?? "").slice(-2000)}`;
}
};
const failures = [run("pnpm typecheck"), run("pnpm test --run")].filter(Boolean);
console.log(
JSON.stringify(
failures.length ? { followup_message: `Checks failed. Fix these, then stop:\n\n${failures.join("\n\n")}` } : {}
)
);// .cursor/hooks/format.mjs
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
const { file_path } = JSON.parse(readFileSync(0, "utf8"));
if (/\.(ts|tsx|css|json|md)$/.test(file_path)) {
try {
execFileSync("npx", ["prettier", "--write", file_path], { stdio: "ignore" });
} catch {
// never block the agent on a formatter failure
}
}
console.log("{}");The scripts assume typecheck (running tsc --noEmit) and test (running Vitest) entries in package.json. Cursor's docs set the stop-hook loop limit to 5 by default; I lower it so a stuck agent stops and asks for help rather than looping.
- Add tests where duplicates were. When you merge three formatters into one, pin its behaviour so the next prompt cannot quietly change it:
// src/lib/format.test.ts
import { describe, expect, it } from "vitest";
import { formatCurrency, formatDate } from "./format";
describe("format", () => {
it("formats pence as pounds", () => {
expect(formatCurrency(123456)).toBe("£1,234.56");
});
it("formats ISO dates for the UK", () => {
expect(formatDate("2026-11-09")).toBe("9 Nov 2026");
});
});- Review before merge. Agent Review runs from the Source Control tab against your main branch; its docs suggest Deep for large refactors. On GitHub, Bugbot reviews pull requests and reads
.cursor/BUGBOT.md. Cursor's docs include an example rule that flags backend changes with no accompanying tests, which suits this problem well.
How to prevent it
- Use Plan Mode (Shift+Tab) for anything that touches many files. Its docs recommend going back to the plan, not patching, when the result is wrong.
- Start a new chat per task, so conventions come from rules rather than an aging summary.
- Commit before each agent task. Checkpoints are local and separate from Git; use them for quick undo only.
- Add generated folders and fixtures to
.cursorignore. Cursor's docs say ignoring irrelevant parts of a codebase helps the agent find the right files.
If you are weighing whether the code is worth saving, my guide to refactoring or rebuilding a vibe-coded app sets out the questions. Messy auth is the most dangerous part of a messy codebase, covered in Cursor auth not working. If someone else will take the code on, read handing over AI-generated code.
When to get help
Guardrails stop new mess; they do not remove the old. If you are not sure which parts carry risk, the free vibe code health check is a quick start. A Rescue Sprint audits the codebase, fixes what blocks real users and leaves the rules and hooks in place. For the wider picture, see the guide to fixing vibe-coded apps.
- Cursor docs: Agent overview (tools, checkpoints), checked 10 November 2026
- Cursor docs: Agent security, checked 10 November 2026
- Cursor docs: Rules, checked 10 November 2026
- Cursor docs: Prompting agents (context usage), checked 10 November 2026
- Cursor docs: Hooks, checked 10 November 2026
- Cursor docs: Agent Skills (built-in skills), checked 10 November 2026
- Cursor docs: Plan Mode, checked 10 November 2026
- Cursor docs: Agent Review, checked 10 November 2026
- Cursor docs: Bugbot, checked 10 November 2026
- Cursor docs: Ignore file, checked 10 November 2026
The vibe-code rescue checklist, as a PDF
About 40 checks across auth, data, payments, errors, mobile and secrets, on two pages.
Questions founders ask
Why does Cursor ignore my rules?
Project rules must be .mdc files in .cursor/rules; Cursor's docs say a plain .md file there is ignored. Rules set to Apply Intelligently need a description, and file-scoped rules need globs that match the files in context.
Should I use AGENTS.md or .cursor/rules?
Cursor supports both. AGENTS.md is plain markdown, including nested files per folder. Use .cursor/rules when you need a rule to apply only to certain file patterns.
Can Cursor run my tests automatically?
Yes, with hooks. A stop hook runs when the agent finishes, and can send a follow-up message back to the agent, so failing checks become its next task. Cursor caps these loops at 5 by default.

Ant
Product designer who builds MVPs, based in Nottingham. 10+ years designing digital products, and the only person at BlackFig. More about me