Your Agent Is Only as Good as Your Codebase
Blog article/Blog archive

Your Agent Is Only as Good as Your Codebase

Coding agents copy what they find. In a tangled repo they copy the tangle. Here's why structure changes what an agent writes, and what RayStack's new dev-refactor skill does about it.

Nov 6, 20267 min read0 comments1 view
AIRefactoringClaude CodeTesting

Do you want to receive these blog updates?

One email a week, only when there's a new post.

Picture an agent fixing a bug in one place. The same logic lives in two other files. It doesn't know, so it doesn't touch them, and the bug stays alive in two out of three code paths.

That's not the agent being dumb. It read the file I pointed at, found the logic, fixed it, and ran the tests. The tests passed because nothing tested the other two copies. The codebase lied to it, and it believed the codebase.

That's the whole argument of this post. Agents copy what they find. Give them a clean repo and they write clean code. Give them a tangled one and they write more tangle, faster than you ever could.

Why structure changes what an agent writes

A human who has lived in a repo for a year carries a map in their head. They know the tax logic is copied in checkout/ and invoices/. They know you have to call init() before load(). An agent starts every session without that map. It only has what it can read.

So every hidden link in your code turns into a trap:

  • Duplicated logic means the agent fixes one copy and ships.
  • A call order callers just have to know means it calls them in the wrong order, and nothing tells it.
  • An if for one special value looks like a pattern, so the agent adds a second one next to it for the next special value.
  • A module with no tests means the agent can change its behaviour and stay green.

It costs more, too. Not in money, in context. When one change has to land in five files, the agent has to find all five, read them, and keep them in its head at once. When the change lands in one place, it reads one file. Less to read means less to get wrong, and more room left in the context window for the actual task.

Fixing the codebase is the cheapest way to make an agent better. Better prompts won't fix a repo that hides its own rules.

What dev-refactor does

RayStack 1.3.0 ships a new skill called dev-refactor. You run it with /raystack project refactor, or the new short alias /rs refactor. It refactors a whole repo, or one area of it, without changing what the code does. The order never changes.

1. It asks two things. Scope first: the whole repo or one folder (it offers the folders it found). Then two optional extras: strip comments that only restate the code, and remove tests that can't catch a bug.

2. It names the architecture you already have. MVC, layered, hexagonal, feature folders, framework default. It writes down the pattern and the evidence, then refactors inside it. If the pattern itself is the problem, it says so and points you to dev-migrate. Switching architecture is a different job, and it won't sneak one in.

3. Safety net before any change. It finds the test runner, runs the whole suite, and records the result as a baseline. A red baseline stops the run and asks you what to do. Then, for every module it plans to touch that has no tests, it writes characterisation tests that pin today's behaviour, odd cases and errors included. Those go in their own commit, before a single line of refactor.

4. It lists every problem, and changes nothing yet. The list goes to docs/refactor/YYYY-MM-DD-log.md, one line per problem with file:line. It looks for hidden links, needless links (callers poking at internals, shallow wrappers), surplus code, and special cases. DRY counts only when the copies change for the same reason. Two lookalikes that change for different reasons stay apart. SOLID counts as far as it pays: no interface with one implementation, no extension point nobody uses.

5. Redesign. It works out the fixes as if today's requirements had been known from day one, and writes each fix above the problems it solves. One good fix usually kills three or four lines of the list.

6. Small commits. One fix group per commit, suite green after each. A red run gets fixed or reverted before the next step. It never weakens a test, raises a timeout, or adds a suppression to get green.

7. A fresh reviewer. A new read-only subagent gets the log, the diff and the problem lenses, and hunts for what's left. Real findings get fixed, then another fresh reviewer goes again. It stops when the reviewer finds nothing worth fixing.

8. Ship. PR, self-review, green CI, merge. The log records the PR and the before and after counts.

The change-together idea in step 4 (copies only count as duplication when they change for the same reason) comes from caldoai's untangle-codebase skill, which is MIT licensed. It's the rule that stops DRY from turning into a pile of shared helpers nobody can change safely.

A small trial, with real numbers

I built a tiny Node repo with a mess planted in it on purpose. Three modules, one test, the usual sins. Then I ran /rs refactor on it.

MeasureBeforeAfter
Special cases (an if on one value)101
Duplicated blocks30
Modules without tests2 of 30 of 4
Tests128

Old and new code gave identical output on 132,022 generated inputs.

The interesting part was the first reviewer. It caught a behaviour change that all 28 new tests missed. The old code compared a rule's country with a plain equality check. The refactor normalised it into a list lookup, roughly like this:

// before
if (rule.country === order.country) { /* ... */ }

// after (first pass)
if ([rule.country].flat().includes(order.country)) { /* ... */ }

Looks the same. It isn't. A rule with country: ['US'] was rejected by the old code and matched by the new one. No test fed it an array, so the suite stayed green. The reviewer read the diff with fresh eyes and flagged it. The fix went in, a second fresh reviewer found nothing, and the run ended.

That's why step 7 exists. Tests you write while refactoring share the blind spots of the person (or agent) doing the refactor. A reviewer that didn't write any of it doesn't.

It also kept one special case on purpose: a single if for the HALF coupon. That's a real business rule, not a missed abstraction. Building a coupon strategy system around one coupon would be the exact surplus step 4 is meant to catch.

Now the honest part. This was a small fixture I wrote myself, with problems I planted. It shows the steps work in order and that the reviewer catches real things. It doesn't prove anything about your 400,000-line monolith. Run it on one folder first and read the log before you trust it with the whole repo.

Try it

Install RayStack for every coding agent on your machine with one line:

curl -fsSL https://raw.githubusercontent.com/HarunRRayhan/RayStack/main/get.sh | bash

Then open your agent in the repo and run:

/raystack project refactor

Or /rs refactor if you're as lazy as I am. Other install options (one agent, one project, plugins) are in the README.

Hope you enjoyed this one. If you run it on a real codebase, I'd like to see what the log found.

Find me on X at https://x.com/harundotdev, and grab every other link from my bio at https://harun.dev/bio.

Get our services

Want this applied on your product? Start with MLOps, or browse all services.

Not sure which one fits? Book a consultation and we'll work out the right next step for your setup together.

Related reading

More posts, sliding underneath the article

Kept below the post instead of in a sidebar, with a slow continuous motion for a cleaner editorial feel.

On this post

Comments

A reply stays under the note it answers.

0 comments

No comments yet.

If you have a note on Your Agent Is Only as Good as Your Codebase, sign in and leave it.