Software Development with AI 11 min read Updated Jul 16, 2026

How to Configure AI to Follow Your Codebase's Conventions

The code compiles, the tests pass, and it doesn't look like anything else in the repo. "Follow our conventions" is a wish: yours aren't written down, they're spread across nine hundred files that disagree. Here's how to extract the real rules and test them.

Build a Conventions Profile

It compiles, it passes, and it doesn't look like anything else here

The change is correct. That's the awkward part. The tests pass, the logic is right, and the reviewer still sends it back — because the query is in the handler instead of behind a repository, the type is called `AppointmentManager` when everything else in the package is a `Service`, and the error came back as a raw database error that a caller three layers up now has to know about. Nothing here is a bug. It's all just *not how this codebase does things*, and the reviewer knows that the way you know your own handwriting: instantly, and without being able to produce the rule.

So the next prompt says "follow our existing conventions," which reads like an instruction and functions as a wish. The model has no access to your conventions. It has whatever you pasted, and if you pasted a file, it now has one sample of a codebase that has been arguing with itself for four years — the old pattern, the new pattern, the one file where someone was in a hurry. There's nothing to follow, so it follows the general habits of code that looks vaguely like yours. What actually helps is unglamorous: find the rules, write the ones that matter, show what obeying looks like, and then check whether the document works by running a real change through it. NewPrompt is where that profile gets assembled, in your browser, from what you type — nothing connects to your repository or source control, nothing reads your file tree, and nothing scans a codebase to work out what your conventions are. And the document you end up with is an instruction, not a control: a model usually follows a clear written rule and sometimes doesn't, so the rules that a formatter, a linter, a compiler or an architecture test can enforce should stay with those tools, where following is not optional. What's left is the part that needs judgment, and it's checked the same way it always was — by you, or by whoever owns the code.

Your codebase is not a rulebook

The tempting move is to point the model at the code and let it infer. It's tempting because it feels rigorous — the code is the ground truth, after all — and it fails for a reason worth understanding before you write a single rule. A codebase records *what happened*, not what was decided. Every file is some mix of the current convention, the previous one nobody migrated, a deliberate exception, and a Tuesday afternoon. Nothing in the source marks which is which. So a model reading your repo learns the majority pattern, and the majority pattern is frequently the old one, because there's more history than present.

That's the distinction the whole job turns on: a **rule** is something a human decided and would reject a pull request over; a **pattern** is something the code merely does a lot. They look identical from the inside of a file. The three handlers that talk straight to the database aren't a convention — they're the three we haven't fixed — but a model can't see the difference, and neither can a new engineer, which is why they copy them. Worth separating too: an **invariant** you would never knowingly break, versus a **preference** that's just what we happened to settle on. Both belong in a rules document; only one of them is worth an argument.

Which means the source of a rule is part of the rule. "The architecture decision record says services never import handlers" is a decision with a name attached. "Most files seem to do it this way" is an observation, and it might be an accident. When you write the document, the ones that came from a human decision are load-carrying, and the ones you inferred from reading are provisional until someone confirms them.

Step 1: Pick the files you'd want copied

Before rules, pick exhibits. Somewhere in the repo are two or three files that everyone would agree are how it should be done — the newest service, the one written after the team finally settled the argument, the one you'd hand a new hire and say "like this." Those are your canonical examples, and choosing them is a human act. The model cannot identify them: from the inside, a canonical file and a legacy file are both just code, and the legacy one may well be longer and more numerous.

Say the choice out loud, because it's the highest-value sentence in the whole document. *This file is how we do it. That directory is not — it's being deleted, don't copy from it and don't fix it either.* Then decide the authority order for when your exhibits disagree, because they will: does the architecture decision record beat the code, does the newest file beat the older one, is there a directory that's never canonical? Write that down as a rule, not as an assumption, because the moment two examples conflict without one, the model picks — and it picks by frequency, which is precisely the wrong tiebreak. This is also the point where reading the codebase properly pays off; if the conventions aren't clear to *you* yet, that's a separate job first, and the answer to "what does this codebase already do" is what feeds this step.

Step 2: Separate the rules from the habits

Now go through what you know and sort it. For each candidate rule, two questions: did a human decide this, or did I just notice it? And would I reject a change for breaking it, or would I only mention it? The first question separates rules from patterns. The second separates invariants from preferences, and it matters because a document where everything is equally emphatic teaches the model that nothing is, and the layer boundary ends up carrying the same weight as which quote character you use.

The categories that earn their place are the ones with consequences: naming, so new code is findable; file and folder placement, so it lands where people look; **dependency direction and layer boundaries**, which are the rules a codebase can't recover from silently — a handler reaching into the database doesn't fail, it just quietly makes the layering a fiction; error handling, because a convention half-followed is worse than none; test structure and naming; and what's allowed to become a dependency. Then the negative space, which people forget: the forbidden shortcuts. The thing that looks reasonable, that a model will absolutely reach for, and that your team decided against for a reason.

Be equally deliberate about what *doesn't* go in. Indentation, import order, spacing, quote style — if a formatter already enforces it, writing it as an AI instruction buys you nothing and costs you attention. The formatter wins every argument and never gets tired; the instruction is a suggestion the model follows most of the time. Spend the document on what tooling can't check: the rules that need a human to notice they were broken.

Step 3: Write the rules a reviewer would send a PR back over

Rules get followed when they're specific enough to fail. "Follow clean architecture" is not a rule; nobody can tell whether they've broken it. "Handlers may import services; services may import repositories; repositories import neither" is one, and you can point at a line and say it's wrong. Write each rule so that obedience is checkable by looking, and be honest about the ones that need judgment rather than dressing them up as mechanical — "keep functions small" is a preference with a number bolted on if you write it as a line count.

Scope them, because most rules aren't universal. A rule that says "always" and has three known exceptions will teach the model that your rules have exceptions, which is not the lesson. Say where it applies: this holds in the service packages, the legacy directory is exempt, tests may do things production code may not. And keep the document a document — the Project Context Builder assembles the profile you paste into your own AI tool's instructions, and it's worth knowing exactly what it does: it carries your typed conventions and never-assume rules through verbatim, and it also turns the stack you name into the conventions that stack implies. That second part is genuinely useful and worth reading with care, because those are *implied* conventions, not observed ones — the tool has no idea what your repo does. The Coding Conventions for AI and Configure AI for an Existing Codebase resources are rendered sample profiles rather than prompts: finished examples showing what a filled-in profile looks like for a component library and for a microservices backend. Read them as a shape to copy, and note the profile itself is plain markdown — the only vendor-shaped parts are the line at the top about where to save it and its matching note near the bottom, under AI Usage Notes.

Step 4: Show the pair, not the principle

A rule tells the model what you want. A pair shows it. For the rules that keep getting broken, include the wrong version and the right one side by side, small, from your actual codebase — and make them differ *only* in the thing the rule is about. This is where most example pairs fail: the bad one is also badly named, badly formatted and missing a test, so the contrast teaches nothing in particular. If the rule is about the layer boundary, both examples should be equally clean in every other respect, and differ only in who imports whom.

Pairs also close a gap that rules alone leave open. A rule phrased as a prohibition — *handlers may not import repositories* — tells the model what not to do and nothing about what to do instead, and a model with a prohibition and no alternative will produce something else you didn't want. Show the handler calling the service. It costs six lines and it removes the guessing. And keep them short: an example that spans a screen is teaching five things at once, and you don't control which one gets learned.

Step 5: Run one real change through it

The document is a claim about your codebase, and it isn't true until something tests it. So pick a small task that touches the rules you care about — a new endpoint, a new repository method, something with a handler and a service and a test — and run it with the profile loaded. Not a toy task; one where you already know what the right answer looks like, because that's how you can tell.

Then read the output against the rules, one at a time, by number. This is a conformance check, not a risk review — the question is "does this obey rule four," not "is this safe," and those are different passes with different findings. The Code Review Prompt Generator builds review prompts with an objective, criteria and severity rules, which is the natural fit once you have written rules to check against; the conventions resource itself points at it for exactly this, since reviewing a specific diff against the rules is a different job from writing them. Whether the change is *correct and safe* is its own review and stays its own review — a change can obey every convention and still be wrong.

What you find is usually about the document, not the model. A violated rule is often a rule that was never in the profile, or one that was written as a prohibition with no example, or one whose canonical file didn't actually demonstrate it. That's the loop: the trial finds the gap, the gap tells you which rule or example to fix, and the document gets sharper each time. And it decays — when the conventions change, the profile is part of that change, or it starts teaching last year's rules with total confidence. Nothing here is watching the code.

Common mistakes

A conventions document is easy to write and easy to write uselessly:

  • Letting the model infer the rules from the repo. It learns the majority pattern, and the majority is usually the old one — there's more history in a codebase than present. Someone has to say which files are canonical.
  • Treating legacy as convention. The three handlers that talk to the database aren't a rule, they're a backlog item. Unmarked, they're the most convincing examples in the repo.
  • Writing what the formatter already enforces. Indentation and quote style are settled by tooling that never gets tired. Spending instructions on them costs attention and buys nothing.
  • Rules nobody could fail. "Follow clean architecture" can't be broken visibly, so it can't be followed. "Repositories import neither handlers nor services" can be checked by looking at the imports.
  • Good/bad pairs that differ in five ways. If the bad example is also badly named and untested, the contrast teaches nothing in particular. Change only the thing the rule is about.
  • Asking the model whether it complied. It'll say yes. Compliance is read off the diff against the rules, by someone who can tell — its own report is not evidence.
  • Expecting the document to enforce anything. It's an instruction a model usually follows — NewPrompt doesn't scan your repo, run your linter, or block a change that breaks a rule. Real enforcement stays in your tooling and your review.

Extracting the rules from a layered scheduling service

A Go service with four years of history, worked from exhibits to rules to a trial change that found the gap.

Nine hundred files that disagree, narrowed to canonical exhibits and rules tagged by source and strength — and a trial whose two failures turn out to be exactly the two rules the document itself marked as soft
THE ASK THAT DOES NOTHING
  "add a CancelAppointment endpoint, follow our existing conventions"
  the model has no conventions. it has this file and its instincts.

WHAT READING THE REPO ACTUALLY SHOWS  (nine hundred files, arguing)
  internal/scheduling/service/booking.go       written last quarter
  internal/scheduling/repository/booking.go    same
  internal/legacy/appointment_manager.go       2022. still shipping.
  internal/api/reminder_handler.go             queries the DB directly

  a model reading these learns: managers and services both exist,
  handlers sometimes query the DB. both wrong. both true of the code.

EXHIBITS  (a human picks these -- the model cannot)
  canonical:  internal/scheduling/**   (the newest, post-argument)
  never copy: internal/legacy/**       (being deleted; don't fix it)
  authority on conflict:
     1. the ADR wins over the code
     2. internal/scheduling/ wins over anything older
     3. internal/legacy/ is never evidence of anything

  note the package split: service/ and repository/ are separate
  packages on purpose. that is what makes the layer rule below a fact
  about imports rather than a wish -- and what makes it testable.

RULES  (every one tagged: source | strength)
  [decided | ADR-0009 | invariant]
    dependency direction: api -> service -> repository.
    a handler must never import a repository package.
  [decided | team, 2025 | invariant]
    repositories return domain errors (ErrNotFound). raw driver
    errors never cross the repository boundary.
  [decided | team, 2025 | invariant]
    no panic in library code. no context.TODO() in committed code --
    it marks unfinished plumbing. context.Background() at main and in
    tests; everywhere else the context is a parameter.
  [observed | -- | provisional]
    types are XService / XRepository / XHandler.
    legacy uses XManager. nobody has confirmed this is a rule.
  [decided | team, 2024 | preference]
    table-driven tests, one _test.go per source file,
    TestFunc_Scenario naming.
  [scope]
    all of the above apply to internal/scheduling/**.
    internal/legacy/** is exempt. tests may use fixtures that
    production code may not.
  [not in this document]
    import order, spacing, formatting -> gofmt and goimports.
    they already win those arguments; instructions here would just
    dilute the ones that need judgment.
  [not anybody's]
    line length. Go has no limit and gofmt will not wrap a line. we
    are not inventing one here either -- a rule no tool checks and no
    reviewer enforces teaches the model that our rules are optional.

ONE PAIR  (same signature, same ctx, same error handling --
           they differ ONLY in who the handler calls)
  bad:
    func (h *AppointmentHandler) Cancel(ctx context.Context, id string) error {
        _, err := h.db.ExecContext(ctx,
            "UPDATE appointments SET status='canceled' WHERE id=", id)
        return err
    }
  good:
    func (h *AppointmentHandler) Cancel(ctx context.Context, id string) error {
        return h.svc.CancelAppointment(ctx, id)
    }
  the prohibition alone ("handlers may not import repositories") says
  nothing about what to do instead. the pair does.

THE TRIAL  (the same task, profile loaded)
  asked again: "add a CancelAppointment endpoint"
  output:
    - dependency direction: obeyed. handler -> service.
    - errors: obeyed. returned ErrNotFound, mapped in the handler.
    - naming: FAILED. it produced CancellationManager.
    - tests: FAILED. one test, not table-driven.

WHAT THE TRIAL WAS ACTUALLY TELLING US
  look at the tags, not the failures:
    obeyed:  dependency direction  [decided | invariant]
    obeyed:  errors                [decided | invariant]
    FAILED:  naming                [observed | provisional]
    FAILED:  tests                 [decided | preference]

  the two rules that broke are exactly the two the document itself
  marked as soft. we told the model which rules were optional and it
  believed us. that is not a model defect -- it is the document
  working precisely as written, against what we actually wanted.
    naming: confirm it with the team and promote it to [decided |
      invariant], or accept that it will keep losing.
    tests:  if we would reject a PR over it, it was never a
      preference. the tag was the bug.

  note what this rules out: the pair is not the explanatory variable.
  the errors rule had no pair and was obeyed anyway. strength was.

Where this fits in NewPrompt

The artifact is a profile, and the Project Context Builder is the tool that assembles it — deterministically, in your browser, from fields you fill in. Your own conventions and never-assume rules are carried through word for word; the stack you name contributes the conventions that stack implies, which is a real head start and is exactly as good as your stack description, since nothing is reading your code. Two rendered sample profiles show the finished shape: Coding Conventions for AI for a component library, and Configure AI for an Existing Codebase for a microservices backend — neither is a layered service like the example below, so the shape to copy is the profile, not the architecture. They're examples, not prompts — you read them the way you'd read a filled-in form. Then the Code Review Prompt Generator builds the prompt for checking a diff against the rules once you have them, which is the handoff the conventions resource itself names.

It's worth being clear about what the builder does and doesn't reach. It emits the profile; it doesn't pick your canonical files, doesn't know which of your patterns were decided and which just accumulated, doesn't write good and bad pairs, and has no way to test whether the rules work. Those are the parts this guide is about, and they're the parts that make a profile more than a stack list — the tool gets you the document, the judgment gets you a document that's true.

Three neighbours use adjacent words for different jobs. A project glossary settles vocabulary — which word means which thing, so the model says "appointment" and not "booking" — and says nothing about where a file goes. A context packet is what the model needs to know for one task in one chat: the goal, the state, what's already been decided. This is neither: it's the standing rules the model's *output* has to satisfy, and it's tested rather than merely provided. And explaining a codebase for onboarding runs the other direction entirely — it produces an understanding of how the system works today, which is the raw material this step turns into rules. Its output is this guide's input.

What makes a conventions document worth the afternoon is that it turns a feeling into something arguable. Right now the rule lives in a reviewer's head and surfaces only as "this doesn't look right," which is unanswerable — the author can't fix a vibe, and the next change breaks it again. Written down, a rule can be checked, disagreed with, scoped, or deleted because it turned out to be nobody's decision. That happens: half of what a team believes are its conventions dissolve the moment someone asks who decided them. The model is the reason to finally write them, and the useful side effect is that you find out what your team actually agreed on — which nobody knew, because it was never a document, only nine hundred files and a habit.

Tools for this guide

Each generates the prompt described above — you run it in your own AI assistant.

Ready-made resources

Reusable prompts and templates for the exact steps in this guide.

FAQ

How is a conventions profile different from a project glossary or a context packet?

They answer different questions and it's worth not merging them. A glossary is vocabulary: the model should write "appointment" rather than "booking," and "tenant" means this specific thing here. It governs words, not structure — it has no opinion about which package a file lands in. A context packet is what the model needs to know to do one task in one conversation: the goal, the current state, what's already decided, what not to touch. It's per-task and disposable. A conventions profile is neither — it's the standing set of rules the model's *output* has to satisfy every time it writes code here, and the thing that makes it a different artifact is that it's testable: you can run a change through it and see which rule broke. Most projects want all three eventually, and mixing them into one wall of text is how each stops working.

Which rules should stay with the linter instead of going in the document?

Anything a tool already decides. Formatting, indentation, import order, line length, quote style, and most lint rules are settled by software that applies them identically every time and never negotiates — putting them in an instruction set converts a guarantee into a suggestion, and dilutes the rules that actually need attention. The same logic runs further than people expect: if you can express a layer boundary as an architecture test, that test is worth more than the sentence, because it fails a build rather than hoping. What's left for the document is the judgment: which file is canonical, what to do instead of the forbidden shortcut, how errors cross a boundary, when a rule doesn't apply. That's the part no tool checks — and it's the part a reviewer currently carries in their head.

The AI says it followed the conventions. How do I actually know?

Read the diff against the rules, by number — its own report isn't evidence of anything. A model asked whether it complied will generally say yes, in good faith, because it's assessing its output against its own reading of the rules rather than against your codebase. So the check is mechanical: rule one, does this obey it, yes or no. Keep that pass separate from the safety pass — a change can follow every convention and still be wrong, and hunting risk and checking conformance are different jobs that find different things when mixed. And when something fails, look at the document before you blame the model: in practice, most violations are a rule that was never written, a prohibition with no positive example, or a canonical file that didn't demonstrate the thing it was chosen for.

Does this depend on a specific AI tool's rules file?

No, and it's worth keeping it that way. The profile is plain markdown — project overview, stack, conventions, naming rules, architecture principles, never-assume lines. Where you put it is the only vendor-shaped decision: some assistants read a file at the repo root, some read a path in a config directory, some take it as custom instructions you paste once. That's a save location, not a format, and the sample profiles carry it in exactly two places — the install line at the top and the matching note at the bottom — which is all that changes between tools. Writing the document against one product's file convention ties your team's actual engineering rules to a tool you might replace next year. Keep the rules portable, keep the install note swappable, and the document survives changing your mind about the assistant.