How to Make Generated Code Match the Project Style
An AI coding assistant will happily generate a 40-line function that works, passes the test, and reads like it was written by someone who has never opened the repository before. That is because it hasn't. The model learned Python or TypeScript from the entire public internet, which means its sense of "normal" is an average of millions of codebases that share almost nothing with the one on the screen.
Matching project style is the part of AI-assisted development that nobody puts on the roadmap. Teams adopt Copilot or Cursor, get a burst of speed, and then spend the next six months quietly reformatting generated code during review. The velocity gain is real, and so is the tax.
There is a way to close the gap. It involves treating style as infrastructure rather than taste, and being honest about which parts of a codebase a model can actually learn from.
What "Project Style" Actually Contains
Most engineers hear "style" and think about indentation and semicolons. Those are the parts a formatter already handles. The interesting layer sits above it: naming conventions, error handling philosophy, how modules are split, whether the codebase favors composition or inheritance, whether a function is allowed to be 30 lines or 8.
Consider a Rails shop that wraps every external call in a service object with a single public method. A generated controller that calls an HTTP client inline will pass every linter and still fail review, because it violates an unwritten law. No config file encodes that law. It lives in the heads of the senior engineers and in the patterns of the last 200 pull requests.
That is the layer worth targeting. A formatter fixes the mechanical tier in milliseconds. The architectural tier is where generated code creates real friction.
Style Guides Written for Humans Rarely Help a Model
A typical CONTRIBUTING dot md says things like "write clean, readable code" and "prefer clarity over cleverness." A model reads that and learns nothing actionable. The document was written to set a tone for new hires, not to constrain a text generator.
The fix is a separate, machine-facing document: short, imperative, and full of examples. Instead of "use meaningful names," write "event handlers are named on plus the event, so onClick, onSubmit, onDismiss." Instead of "handle errors properly," write "every async function returns a Result type; raw throws are reserved for programmer errors."
The key is that each rule pairs a directive with a code snippet. Models are pattern matchers. A rule with a before-and-after example is worth twenty adjectives.
Curating the Files the Model Sees
Retrieval is the lever most teams ignore. When an assistant suggests code, it pulls context from open files, recently edited files, and whatever the tool's indexer decided was relevant. If the repository contains a legacy folder where the style is completely different, that folder becomes a source of bad examples.
A practical move: create a small directory of exemplar files, one per common task, and point the assistant's context at it. Need a new API endpoint? The exemplar shows the canonical endpoint, with the project's routing, validation, logging, and error shape. Need a React component? The exemplar shows the hooks pattern, the prop typing convention, and how the team structures styles.
Ten to fifteen of these files, kept current, do more for consistency than any prompt. They are the closest thing to showing the model the actual house style rather than describing it.
Rules Files and Prompt Layering
Tools like Cursor, Aider, and Claude Code support project-level instruction files, and these have become the default answer to the style problem. They work, with limits. A rules file that lists 60 preferences gets partially ignored, because attention is finite and models prioritize the most recent and most specific instructions.
The effective version is short and tiered. Five to ten global rules at the top: language version, module system, error convention, test framework. Then per-directory overrides, since a frontend folder and a backend folder rarely share conventions. Then, crucially, a line telling the model what to do when unsure: read two nearby files before writing.
The Linter as a Style Enforcement Layer
Prettier and its equivalents solve formatting. They do not solve naming, imports order, complexity, or the difference between a helper and a service. That gap is where a stricter linter earns its keep.
ESLint with a curated rule set, or RuboCop, or Ruff, can encode a surprising amount of convention: naming patterns for files and identifiers, banned APIs, import grouping, maximum function length. Generated code that violates these rules gets flagged before a human ever looks at it. The assistant can then be told, in the prompt, to run the linter and fix what it reports.
This creates a feedback loop that costs almost nothing. The model generates, the linter rejects, the model revises. What survives is closer to the project's actual standards, and the reviewer's time goes to substance instead of spacing.
Tests as the Sharpest Style Signal
Test files are a secret weapon for style matching, and almost nobody uses them this way. A test suite contains the project's real vocabulary: how fixtures are named, how mocks are set up, whether tests use factories or literals, how assertions are phrased. A model that reads three existing tests will write a fourth that looks like it belongs.
This also solves the "generate a test for this function" problem, which is one of the most common assistant requests. When the prompt includes a neighboring test file, the output stops looking like a textbook example and starts looking like the project.
Review as a Correction Loop, Not a Rejection Loop
The instinct when generated code comes back wrong is to rewrite it by hand. That fixes the current pull request and teaches the model nothing, because the correction never makes it back into the context.
A better habit: when a reviewer edits generated code, capture the edit. If the same correction shows up three times, it belongs in the rules file or the exemplar set. "We always return early instead of nesting" is a rule that emerged from review, not from a style guide written in advance.
Teams that treat review comments as training data for their own configuration get compounding returns. The assistant improves because the inputs improve, and the inputs improve because someone noticed a pattern in the diffs.
When the Project Has No Consistent Style
Some codebases are a collage. The auth module was written by a contractor in 2019, the billing code was rewritten twice, and the frontend migrated from one framework to another mid-project. Asking a model to match this style produces nonsense, because there is no single style to match.
The honest move is to pick a target. Decide which part of the codebase represents the direction the team is heading, and point the assistant at that. Generated code should match the future, not the median of the past. This sometimes means generated code looks different from adjacent files, which is fine when the adjacent files are the ones scheduled for cleanup.
The Cost of Getting This Wrong
Style drift is easy to dismiss as cosmetic. It isn't. A codebase with three competing conventions for error handling is harder to debug at 2 a.m., harder to onboard into, and more expensive to refactor. When assistants generate code all day, drift accelerates. What used to take a year of hires now takes a quarter of prompts.
The teams that get value from AI coding tools are the ones that treat generated code as a junior contributor with perfect recall and zero context. They invest in the context: the exemplars, the rules, the linter, the tests. The tool gets sharper as the inputs do.
None of that requires new tooling. It requires admitting that "the model should know our style" is a sentence with a missing clause, and writing the clause down.

