TL;DR
AI-generated code is not low quality merely because a model wrote it, and human-written code is not reliable merely because a person wrote it. The useful question is whether a change satisfies a versioned contract and produces enough evidence to merge safely.
Treat generation as proposal and verification as acceptance. A strong workflow combines a precise task boundary, repository-aware context, deterministic checks, structural assertions, independent review, and staged production evidence. A passing unit test is one signal, not a certificate of maintainability or security.
Define Quality as Observable Evidence
“Clean code” is too subjective to operate as a release gate. Define the properties the change must preserve and the evidence that proves each one.
| Quality dimension | Question | Stronger evidence |
|---|---|---|
| Functional correctness | Does the requested behavior work? | Regression, property, integration, and negative tests |
| Compatibility | Did public contracts or persisted data change? | API/schema diff, migration rehearsal, consumer tests |
| Maintainability | Does the change fit existing ownership and abstractions? | Change-locality and structural assertions |
| Security | Can an untrusted caller cross a boundary? | Authorization tests, SAST, dependency and secret checks |
| Reliability | What happens on timeout, retry, cancellation, and restart? | Fault injection, idempotency, reconciliation tests |
| Performance | Did latency, memory, or compute regress? | Workload-specific benchmark with fixed environment |
| Operability | Can the team observe and reverse the change? | Metrics, logs, alerts, rollout and rollback proof |
This contract should be written before implementation. Spec-Driven Development is useful when the spec names behavior, invariants, failure semantics, and acceptance evidence. It is not useful when it merely turns a vague request into a longer vague document.
What Current Evidence Actually Shows
Research does not support a universal claim that AI code is always worse. It shows that quality varies by task, language, workflow, and measurement method, and that functional success misses important failure classes.
- Needle in the Repo (NITR) is a 2026 preprint that pairs functional tests with structural oracles for 21 curated C++ repository probes. In that diagnostic setting, some changes passed the behavioral tests while violating the intended maintainability structure. Its percentages belong to that benchmark, not to every language or production repository.
- A 2026 production C++ study follows authoring-time provenance through review, static findings, and operational signals in one large enterprise. It reports a distinct issue profile and targeted-feedback improvements in that environment. The organization, language, tooling, and observational design limit generalization.
- NIST SSDF supplies stable software-development controls: protect software, produce well-secured releases, respond to vulnerabilities, and address root causes. It does not create a special exemption or a single score for AI-authored code.
The engineering conclusion is narrower and more useful: use the same release accountability for every change, then add controls for the volume, uncertainty, provenance, and tool access introduced by coding agents.
Start With a Change Contract
A coding agent should receive a bounded change contract, not a persona.
Goal:
Add idempotent invoice retries without changing pricing behavior.
Authoritative sources:
api/invoice.yaml
internal/billing/repository.go
internal/billing/retry_test.go
Invariants:
Preserve tenant and principal checks.
The same operation key cannot create two charges.
A timeout after dispatch becomes outcome_unknown.
Allowed scope:
internal/billing/**
api/invoice.yaml
Evidence:
Focused tests, race test, schema diff, reviewed migration plan.
Escalate:
Any authorization, database, dependency, or external API change.
“Act as a senior engineer” is not an invariant. “No public API change” can be checked. AI coding assistant customization explains how to keep reusable repository rules separate from task-specific acceptance criteria.
Build a Layered Verification Gate
No single checker can prove software quality. Order the gates so cheap deterministic failures stop the change before expensive review.
- Scope and provenance: identify generated files, changed ownership boundaries, and unexpected files.
- Build and static checks: compile, type-check, format, lint, scan secrets, and resolve dependency policy.
- Behavioral tests: cover the requested path, invalid inputs, authorization, concurrency, and prior regressions.
- Structural checks: assert that the patch reuses the intended abstraction, keeps side effects isolated, and does not duplicate policy.
- Independent review: inspect the diff and the evidence. A second model can find candidates, but an accountable human or service owns the decision.
- Release controls: canary, feature flag, rollback, and monitoring connect source-level confidence to production behavior.
A Runnable Evidence Gate in Go
The following standard-library program evaluates evidence records, not model prose. It intentionally fails closed when a required gate is missing or unsuccessful.
package main
import (
"errors"
"fmt"
)
type Check struct {
Name string
Required bool
Passed bool
Evidence string
}
func Evaluate(checks []Check) error {
seen := map[string]bool{}
for _, check := range checks {
if check.Name == "" || seen[check.Name] {
return errors.New("invalid or duplicate check")
}
seen[check.Name] = true
if check.Required && (!check.Passed || check.Evidence == "") {
return fmt.Errorf("required check failed: %s", check.Name)
}
}
for _, required := range []string{
"scope", "build", "tests", "security", "review", "rollback",
} {
if !seen[required] {
return fmt.Errorf("required check missing: %s", required)
}
}
return nil
}
func main() {
checks := []Check{
{Name: "scope", Required: true, Passed: true, Evidence: "diff.json"},
{Name: "build", Required: true, Passed: true, Evidence: "build.log"},
{Name: "tests", Required: true, Passed: true, Evidence: "tests.xml"},
{Name: "security", Required: true, Passed: true, Evidence: "sast.sarif"},
{Name: "review", Required: true, Passed: true, Evidence: "review-42"},
{Name: "rollback", Required: true, Passed: true, Evidence: "runbook.md"},
}
fmt.Println(Evaluate(checks))
// Output: <nil>
}
In production, sign or store evidence by immutable revision and bind it to the exact commit. A text line saying “tests passed” is not equivalent to test output from the reviewed revision.
Add Structural Oracles
Behavioral tests ask whether the current examples work. Structural oracles ask whether the implementation preserves an engineering property needed by future changes.
Useful structural assertions include:
- only the intended package owns a business rule;
- a new type extends an existing interface instead of duplicating dispatch logic;
- production code does not import a test-only or unapproved dependency;
- authorization remains at the service boundary;
- generated clients are regenerated rather than edited by hand;
- nondeterministic time, randomness, and network calls are injected for tests;
- external side effects pass through one idempotent adapter;
- schema changes include compatible readers, migration, and rollback.
Do not encode arbitrary style preferences as hard gates. Each oracle should protect a named risk and return an actionable diagnosis. Linting is valuable for repeatable local rules, but architecture and domain invariants often need repository-specific checks.
Verify Security and Supply Chain Separately
An agent can satisfy the feature request and still widen the attack surface. Review:
- caller, tenant, object, and operation authorization;
- secret access, logging, telemetry, and prompt/tool payload retention;
- new package names, versions, licenses, registries, and install scripts;
- command, filesystem, network, and credential scope used during generation;
- unsafe deserialization, injection, path traversal, SSRF, and shell construction;
- timeout, retry, duplicate request, cancellation, and partial-commit behavior.
Instructions such as “use secure code” cannot enforce these properties. Pin dependencies, use trusted registries, run security tooling, isolate execution, and test the application boundary. Treat repository text and issue descriptions as untrusted input because they can carry prompt injection.
Keep Human Review Evidence-Driven
Human review is strongest when the reviewer receives a small, coherent diff and concrete proof:
- the requested behavior and non-goals;
- changed public contracts and data migrations;
- failed tests observed before the fix;
- passing tests on the final revision;
- security and dependency findings;
- unresolved assumptions and skipped checks;
- rollout, monitoring, and rollback plan.
Do not ask reviewers to infer intent from thousands of generated lines. Split independent changes, cap unrelated formatting churn, and reject generated artifacts that cannot be reproduced. A reviewer should challenge the contract and the evidence, not merely the syntax.
Measure the Lifecycle, Not Suggestion Acceptance
Acceptance rate rewards plausible output. Measure whether accepted changes survive.
| Signal | What it reveals |
|---|---|
| Task success by risk slice | Whether the workflow solves the intended jobs |
| Rework before merge | Hidden steering and verification cost |
| Escaped defects and rollback | Failures that passed pre-merge gates |
| Change locality | Whether small requests spread across ownership boundaries |
| Dependency and API growth | Long-term compatibility and supply-chain burden |
| Review latency and reviewer confidence | Human bottlenecks and evidence quality |
| Production latency, cost, and incidents | Operational consequences after merge |
| Time to understand and repair | Comprehension and maintenance burden |
Record model, agent, instruction, tool, repository, and evaluator revisions. Without provenance, a score cannot explain which system improved.
Common Failure Modes
- using “senior engineer” personas instead of explicit invariants;
- approving a patch because the new happy-path test passes;
- letting the model add dependencies without registry and license checks;
- treating model confidence or self-review as calibrated evidence;
- mixing unrelated cleanup into a functional change;
- accepting snapshot churn without inspecting semantics;
- granting broad credentials because the task sounds routine;
- retrying an external write without an idempotency or reconciliation contract;
- measuring generated lines instead of successful maintained outcomes;
- shipping without an accountable reviewer and rollback path.
Frequently Asked Questions
Why can AI-generated code pass tests and still be poor quality?
Tests cover only encoded observations. They may miss duplicated policy, wrong ownership, a bypassed abstraction, an accidental public API, or a future extension path. Add structural assertions and review the change against repository architecture, not only the current examples.
Can a better prompt guarantee maintainable code?
No. Better context and instructions improve the proposal distribution, but they do not enforce the result. Deterministic tools, repository policy, independent review, and production controls must decide whether the proposal is accepted.
What is the minimum verification pipeline for AI-generated code?
Review the diff, compile or type-check, run focused and negative tests, inspect dependency and security changes, verify authorization and side effects, and require accountable approval. Raise the bar for migrations, external writes, privacy-sensitive paths, and large changes.
Should teams reject all large AI-generated changes?
No, but size changes the burden of proof. Prefer independent, reviewable commits. When a migration must be large, provide generated-versus-handwritten boundaries, deterministic regeneration, consumer tests, staged rollout, and a rehearsed rollback.
How should teams measure AI code quality?
Track task success and the downstream cost of acceptance: rework, defects, rollback, structural debt, security findings, operational regressions, and repair time. Segment by task and risk. A global “AI acceptance rate” hides the failures that matter.
Summary
The durable way to improve AI-generated code is to make acceptance evidence stronger than generation confidence. Define a change contract, constrain scope, run layered deterministic and structural gates, review the exact diff, and connect release decisions to production evidence. The model may write the patch; the engineering system decides whether it is fit to keep.
Primary Sources
- Needle in the Repo: Maintainability in AI-Generated Repository Edits
- Characterizing the Quality Profile of AI-Generated C++ in Production
- NIST Secure Software Development Framework 1.1