DEV Community

DaymondHyper
DaymondHyper

Posted on

How to make your AI coding agent stop writing slop

Every AI coding agent I've used shares one habit: it writes the plausible thing. The code compiles. The tests pass. And a senior engineer reviewing it would reach for a red pen. any where a union belongs. Tests that assert on implementation so they survive any refactor. catch (e) {} blocks that quietly swallow production errors.

The fix isn't a better model or a cleverer prompt. It's a set of rules at the repo level, written in a format the agent is guaranteed to read.

What rules files are

Cursor reads .cursor/rules/*.mdc. Claude Code reads CLAUDE.md and AGENTS.md. The .mdc format is plain markdown with YAML frontmatter. Here's the opening of the TypeScript rule file from the AgentForge sample pack:

---
description: "Strict TypeScript discipline for production code"
globs: "**/*.{ts,tsx}"
alwaysApply: true
---
Enter fullscreen mode Exit fullscreen mode

Three fields carry the weight. description tells the agent in one sentence what the file is for. globs scopes it, so a TypeScript rule never fires on a Python file. alwaysApply: true loads it into every session. Agents skip a 2,000-line rules file. They read a 40-line one.

Write a discipline contract, not a wish list

Claude Code follows instructions frighteningly well, and that cuts both ways. Tell it "write good code" and it will be confidently, grammatically wrong.

An AGENTS.md contract fixes that. Start with a baseline of rules: think before you act, take small verifiable steps, never claim what you haven't verified, no drive-by refactoring. Then add a verification ladder with six rungs. Does it compile? Does the changed behavior work? Does it break anything adjacent? Does it follow the codebase's conventions? Does it hold at the boundaries? Is it observable in production? The first three are mandatory for every change.

Then the failure protocol, which is the line that pays for itself:

First failure: fix and re-verify. Second failure: re-derive, your mental model is wrong, form at least two new hypotheses. Third failure: stop, revert to last known-good, document, and re-approach from a different angle.

Agents iterate fast and they'll happily burn tokens on a broken hypothesis. A rule that forces a stop is cheap insurance.

Rules that actually change output

The discipline that matters most lives in the specific rules. From the sample TypeScript file:

Never use any. When you cannot name a type, use unknown and narrow it with type guards before use.
Never suppress errors with @ts-ignore / @ts-expect-error.
Type every function signature explicitly, parameters and return types.
Handle every branch of a discriminated union with exhaustiveness checking.

The last one is a typed-error pattern that converts future bugs into compile errors:

type OrderStatus = "pending" | "approved" | "rejected" | "refunded";

function handleStatus(status: OrderStatus) {
  switch (status) {
    case "pending":  return queueForReview();
    case "approved": return fulfill();
    case "rejected": return notifyCustomer();
    default:         return assertNever(status);
  }
}

function assertNever(x: never): never {
  throw new Error(`Unexpected: ${x}`);
}
Enter fullscreen mode Exit fullscreen mode

Add a fifth status and the build fails until every case is handled. The agent can't silently skip it, and neither can you.

Errors get the same treatment. catch (e) {} is an anti-pattern; the sample rule says log, rethrow, or return an explicit error, never swallow. And e is unknown, so narrow it with a type guard before touching it. That one habit removes a whole class of silent production failures.

Testing that proves behavior

Tests are where agents generate the most waste. The sample testing file sets the standard: test behavior through public interfaces, name tests after the behavior they prove, and assert on outcomes, not calls. Write the assertion first, then the code that makes it pass. That's the closest thing to TDD an agent can follow mechanically. A good name reads like a spec:

it("rejects orders above the account credit limit", () => {
  const result = placeOrder({
    userId: "u1",
    totalCents: 900_000,
    creditLimitCents: 100_000,
  });
  expect(result).toMatchObject({
    status: "rejected",
    reason: "credit_limit_exceeded",
  });
});
Enter fullscreen mode Exit fullscreen mode

And the hard rule: every bugfix ships with a regression test that fails on the old code and passes on the fix. If you can't write one, you haven't understood the bug.

Try it on your own project

The files quoted above, plus the full AGENTS.md contract, are free. Drop them in a repo and watch what your agent does differently. If the structure holds up for you the way it did for me, the full pack extends it to 24 files across frontend, backend, testing, DevOps, database and agent workflows, with project templates and a one-command installer.

Free sample: https://github.com/DaymondHyper/agentforge
Full pack ($29 one-time, no subscription): https://dedyclan.gumroad.com/l/agentforge

Top comments (0)