Skip to content
BlackFig

Cursor codebase messy: cleaning up after the agent, and keeping it clean

AntPublished Updated 4 min read

Checked against Cursor's documentation on 10 November 2026.

Quick answer

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.

On this page
  1. Symptom
  2. Why it happens
  3. The fix
  4. How to prevent it
  5. When to get help

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

  1. 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
  1. Split what is already there. Cursor ships a /split-to-prs skill 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.

  2. Rewrite your rules as small .mdc files. 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.

  1. Add hooks so checks run without asking. Hooks live in .cursor/hooks.json and run from the project root. An afterFileEdit hook formats each file the agent touches; a stop hook 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.

  1. 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");
  });
});
  1. 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.

Sources

The vibe-code rescue checklist, as a PDF

About 40 checks across auth, data, payments, errors, mobile and secrets, on two pages.

You get the PDF now and two short follow-ups over the next week. Reply “stop” and the rest never arrive.

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