Kill every mutant in a file and you've proven something real: the tests would notice if the behaviour changed. I wrote about that loop a few weeks ago, including Chaos-MCP, the tool I built to run it from inside the agent itself, and the number is worth having.
It also tells you nothing about the function underneath the tests. Whether it's nested five levels deep. Whether three other files already do the same job under a different name. Whether it reaches straight into a module it has no business touching. A mutation score is a statement about the tests. It has no opinion on the code.
A different question than "did the tests catch it"
Coverage answers "did this line run". Mutation testing answers "would a test have caught a mistake here". Both are questions about correctness at a single point in time.
What happens to the codebase over the next six months is a different question: whether the function you shipped today is a good place for the next person, human or agent, to work. That's a separate axis, and it has its own instruments: complexity, duplication, and architecture. None of them show up in a green CI badge.
Complexity grows where you weren't looking
A large-scale study of AI-generated pull requests across real-world repositories, built on the AIDev dataset, found cyclomatic complexity for AI-written functions barely above human ones: 2.62 versus 2.47, a small gap. The more interesting finding was where that complexity sat. AI-generated code built it through deeper nesting, especially loops, while human code concentrated it in conditional logic. Different shape of the same score.
That shape has a mechanism. A model generates one token at a time, optimising for what's locally coherent, without tracking how much a function has accumulated by line forty.
Sonar's most recent leaderboard run, covering GPT-5.2 High, Opus 4.5 Thinking, Claude Sonnet 4.5 and Gemini 3 Pro (this predates this month's Opus 5 and Sonnet 5 launches, so treat the pattern as durable and the exact numbers as a snapshot), found code smells made up 92 to 96 percent of every issue flagged, across every model regardless of how capable it was. Their conclusion: as models attempt more sophisticated, stateful solutions, simplicity is the first thing they give up. A smarter model writes a more ambitious function, and ambition is where complexity hides.
The stakes showed up in a Carnegie Mellon study of Cursor adoption across 807 GitHub projects against 1,380 matched controls over 20 months, a Distinguished Paper at MSR '26. Lines added spiked 281% in month one, dropped to 48% by month two, and was effectively gone by month three. Static analysis warnings rose 30%. Code complexity rose 41%.
Neither number came back down. The paper also found a feedback loop: a 100% increase in complexity was associated with a 64.5% drop in future velocity. The speed you were chasing evaporates in a quarter. The complexity tax it left behind does not, and it bills you on every PR after.
A complexity ceiling on the whole file only flags files that were already bad. Track the delta per pull request instead: did this specific change make the function harder to read than it was yesterday. SonarQube's cognitive complexity rule, ESLint's complexity rule, radon for Python, gocyclo for Go, all of them can report that delta alongside the absolute. Gate on the delta and you catch the slow climb before it's a file nobody wants to open.
The clone detector that can't see the clones
I've written before about an inherited codebase with twenty different ways to format money, none of them sharing a name, none of them findable by grep. Run an actual clone detector against that kind of codebase and you'd expect a red report.
You might get the opposite. The same AIDev study found AI-generated code had a lower duplicated-lines ratio than human code: 18.69% versus 25.89%. On paper, the AI codebase looks cleaner.
The tools explain the gap. jscpd and PMD CPD both work by matching tokens, Rabin-Karp string matching under the hood, which is excellent at catching exact or renamed copies: Type-1 and Type-2 clones. Each near-duplicate an agent produces is generated cold, with its own variable names and its own shape, because the model has no memory of the formatter it wrote three prompts earlier, which makes it a Type-3 or Type-4 clone: same behaviour, different structure, and exactly the kind a token-matcher is built to miss.
A clean jscpd report on an AI-heavy codebase says more about what the tool can't see than about whether the code is DRY. Pair it with the grep-by-verb approach from the duplication post, and treat a suspiciously low duplication score with the same scepticism you'd give a suspiciously high one.
Architecture that drifts one file at a time
There's a name for this in the research literature: architecture erosion, the gap between the architecture a system was designed with and the one it actually has. A systematic mapping study by Li, Liang, Soliman and Avgeriou frames it across four angles: violations, structural decay, quality impact, and how it evolves. None of it requires a single dramatic change. It accumulates.
An agent has no concept of layers. It sees the file open in its context and whatever import solves the problem in front of it. Ask it to pull a user's billing history and if the shortest path runs straight from the UI into the payments module, it will take that path, because nothing in its context told it a rule existed.
There's no violation in its mind. There's just an import that made the test pass.
This is where fitness functions earn their keep: executable rules that check the architecture, the same way tests check behaviour. ArchUnit does it for Java, asserting which packages may depend on which. dependency-cruiser does the equivalent for JavaScript and TypeScript, walking the import graph and failing the build on a forbidden edge. Both turn "we don't call the payment module directly from the UI" from a comment in a wiki into a check that runs.
I'm building a version of this for agents directly. Knossos-MCP, still early, scans a repository once into an evidence-backed dependency graph, so an agent can ask "what breaks if I change this" or get flagged for a boundary violation without re-reading the whole tree on every prompt. Same idea as ArchUnit and dependency-cruiser, aimed at the agent's own context instead of only the build.
The part that actually changes agent behaviour is where you wire the check in. If the fitness function only runs in CI after the pull request is open, a human catches the violation three days later, in review, after the agent has moved on to something else.
Put the same check in the command the agent runs after every edit, the way hooks give you deterministic control over an agent's actions, and the agent sees the failure immediately, as feedback it can act on in the same turn. One version teaches the agent where the boundary is. The other just documents that it was crossed.
Watch it as a trend
Coverage misled people partly because it got measured once and treated as an answer. The same trap catches every metric in this post if you only ever look at the absolute number. Churn taught that lesson first: the number that matters is how many lines survive the week. Complexity, duplication and architecture violations deserve the same treatment: watch them per week or per PR, continuously.
None of this replaces judgement. A mutation score, a complexity delta, a duplication ratio, an architecture fitness function: each one answers a narrow question, and each one goes quiet the moment you stop asking it. Ask all of them, on a schedule, and the drift shows up while it's still a diff. Stop asking, and you find out about it the way most teams do: the day someone opens the file and realises nobody has understood it in months.