Every developer who's shipped more than two or three AI-generated features has hit the same moment: the fifth feature works fine, passes review, ships and still feels like it was written by someone who's never seen the other four. Different error-handling style. Business logic living in a controller this time instead of a service. A naming convention that almost matches the rest of the codebase, but not quite.
None of these are bugs in the traditional sense. Nothing throws, nothing fails a test. That's what makes drift a harder problem to catch than a defect, it doesn't announce itself, it just accumulates until the tenth feature is genuinely unpredictable relative to the first.
The mechanism behind drift
Every new feature involves dozens of small structural decisions: where business logic lives, how errors are shaped, what a service function returns, how a component is named, whether validation happens at the controller or schema layer. Without something in the prompt or context window anchoring those decisions to what already exists, a model defaults to whatever pattern is statistically dominant in its training data not whatever your team decided three sprints ago.
This shows up in the data at scale, not just anecdotally. GitClear's analysis of 211 million lines of changed code found duplicated code blocks in AI-assisted repositories rose roughly eightfold during 2024, and that copy-pasted code exceeded refactored ("moved") code for the first time in five years of tracked history. Moved/refactored code is generally a signal of reuse and consolidation; when copy-paste overtakes it, that's a direct measurement of consistency breaking down as generation scales across a codebase.
Rule files: necessary, not sufficient
The most common fix teams reach for is a shared, machine-readable rules file AGENTS.md, CLAUDE.md, .cursor/rules/, .github/copilot-instructions.md documenting tech stack, folder structure, approved libraries, naming conventions, business logic organization, testing commands, and security constraints in one place both humans and models can reference.
A well-written rule doesn't read like a style guideline. Compare:
Keep the architecture clean.
against:
Business logic belongs in service modules. Controllers validate input, call the service, and format the response. Follow
src/orders/order.service.tsfor the approved pattern.
The second version is machine-actionable because it's concrete: a file path, an explicit division of responsibility, a canonical example to imitate. Linking to a real file also ages better than a pasted code block, since it updates automatically as the reference implementation evolves.
It's worth being precise about what a rules file actually buys you, though. A study on AGENTS.md's measured effect on coding agents, analyzing 124 pull requests across 10 repositories, found the presence of an AGENTS.md file was associated with a 28.64% lower median runtime and a 16.58% reduction in output token consumption with no measurable improvement in task completion rate. Shared context makes agents operate more efficiently within a defined scope. It doesn't independently make the output more correct. Efficiency and correctness are separate variables, and conflating them is a common mistake when rolling out a rules file and expecting it to solve everything.
Where architecture-first generation fits in
Some platforms build the equivalent of a rules file into the generation workflow itself, rather than leaving it as a document a team has to remember to write and maintain. 8080.ai's approach generates a system requirements document and maps multi-tier architecture, database schemas, API contracts, component boundaries before any feature code is written, so that later features derive from the same architectural decisions instead of each one starting from a blank interpretation of the prompt. That's structurally similar to what a well-maintained AGENTS.md does for a repo already in flight: it gives the model a source of truth to work from instead of forcing it to infer conventions feature by feature.
The broader pattern across the ecosystem is the same regardless of implementation: context that persists across generations beats context that has to be re-supplied in every prompt. Whether that context is a checked-in markdown file, a generated architecture document, or a template repo, the mechanism doing the work is identical.
A reusable feature template
Beyond rules, teams get further by defining one canonical shape every feature follows:
feature-name/
├── feature.controller.ts
├── feature.service.ts
├── feature.schema.ts
├── feature.types.ts
└── feature.test.ts
This isn't about making every feature identical in substance, it's about removing repeated structural decisions from the loop. Asking the model to copy an existing feature's shape rather than invent a new one each time reduces the decision surface where drift originates.
Keeping context from going stale
An outdated rules file is arguably worse than no rules file, because a model follows it with full confidence even after it's wrong. If the test runner changes and the rules file still references the old command, the model won't question it, it'll execute the wrong instruction with total certainty. The practical fix is mechanical: update the rules file in the same PR as the architectural change it documents, not as a follow-up cleanup task that never quite happens.
Smaller diffs, reviewed more often
A large prompt asking for an entire feature in one pass maximizes the surface area a model can drift across before anyone looks at it, and produces a diff that's genuinely hard to review for architectural fit. A tighter loop works better in practice: have the model inspect a similar existing feature, propose a plan, build one vertical slice, model, service, route, test as one unit run linters and tests, then review that slice before starting the next one. Smaller increments make drift cheap to catch and cheap to revert.
What automated checks catch that documentation can't
A rules file states intent. It doesn't verify whether the generated code honored it. That's the job of CI: formatters and linters, type checking, import boundary enforcement, schema validation, API contract tests, duplicate-code detection, dependency and security scans, and pre-commit hooks that fail the build when a required check doesn't pass. Teams that rely on documentation alone tend to discover drift during a retrospective, weeks after it shipped. Teams with automated checks discover it in the pull request.
What review should actually be checking
Formatting consistency is the layer a linter already covers, it shouldn't be what a human reviewer spends time on. The harder, more valuable question during review: does this feature use the same abstraction as similar features already in the codebase? Do errors behave the same way? Was a new dependency introduced when an existing one already solved the problem? A practical litmus test: if someone already understands one feature in this codebase, will they understand the new one without learning a completely different pattern? A "no" here usually points to an architectural gap, not a style one and it's worth converting into a rule the next time it comes up, rather than re-litigating it feature after feature.
Consistency across AI-generated features isn't a property of the model. It's a property of the system around it: a current, specific rules file; concrete examples over abstract instructions; a repeatable feature template; automated checks that verify what documentation only states; and review that looks at behavior, not just formatting. None of these individually solves drift. Together, they're what makes the tenth generated feature look like it belongs in the same repository as the first.
Top comments (0)