How to Make AI Generate Code That Fits an Existing Codebase

Learn how to prompt AI to modify existing code safely by supplying architecture, conventions, dependencies, constraints, and precise change boundaries

How to Make AI Generate Code That Fits an Existing Codebase

One of the biggest problems with AI-generated code isn't that the code fails to work.

It's that the code works but doesn't belong.

The naming style is different. The architecture is inconsistent. A new dependency appears for something the project already handles. Existing abstractions are ignored. Error handling follows a completely different pattern.

In a small standalone script, these problems may not matter much.

Inside an established application, they can create technical debt surprisingly quickly.

The solution is not simply to tell an AI assistant to "follow the existing code style."

You need to give it enough information to understand the local rules of the codebase before asking it to make changes.

Why AI Often Produces Code That Doesn't Fit

When you ask an AI model to implement a feature, it usually has to infer several things:

  • Where the new code belongs.
  • Which existing functions should be reused.
  • Which libraries are already available.
  • How errors are normally handled.
  • How data moves through the application.
  • What naming conventions the project uses.
  • What architectural patterns the developers prefer.

If these details aren't supplied or discoverable from the provided context, the model may fill the gaps with reasonable assumptions.

Those assumptions can still be completely wrong for your project.

Stop Prompting for "Code" and Start Prompting for a "Change"

Compare these two requests.

Build a user authentication system in Python.

and:

Add password-reset functionality to the existing authentication module. Reuse the project's existing email service and token utilities. Do not introduce a new authentication library. Preserve the current API structure.

The second prompt defines a change boundary.

That distinction is extremely important when working with an existing codebase.

Give the AI a Map Before Giving It a Task

You don't necessarily need to provide the entire repository.

A compact architectural summary can be much more useful.

For example:

Project structure:

/api — HTTP routes and request handling
/services — business logic
/models — database models
/utils — shared utilities
/tests — automated tests

Routes should remain thin. Business logic belongs in services. Database access should use the existing model layer.

Now the model has a basic map of the system.

Without this information, it may put business logic directly into a route simply because that is a common implementation pattern.

Tell It What Already Exists

One of the most valuable pieces of information is a list of reusable components.

For example:

The project already contains:

  • EmailService.send() for outgoing email.
  • TokenService.create() for signed tokens.
  • UserRepository.findByEmail() for user lookup.
  • AuthError for authentication-related failures.

Reuse these components instead of creating replacements.

This prevents one of the most common forms of AI-generated technical debt: duplicate abstractions.

Specify What the AI Must Not Change

Developers often describe what they want changed but forget to describe what must remain untouched.

That leaves the model with a large solution space.

Add explicit boundaries:

Do not modify the database schema, authentication middleware, public API response format, or existing login flow. Only add the password-reset workflow.

This is especially useful for maintenance tasks.

Use a "Do Not Invent" Section

For mature projects, this can be surprisingly effective.

Do not invent:

  • New libraries when an existing dependency can solve the problem.
  • New utility functions when an equivalent utility already exists.
  • New architectural layers unless required.
  • New API conventions.
  • Alternative error-handling patterns.

This doesn't guarantee perfect results, but it establishes a strong default.

Show Representative Code, Not Just Rules

Written instructions are useful, but examples can communicate local conventions much faster.

Suppose your project handles errors like this:

try { ... } catch (error) { logger.error(error); throw new ServiceError(...); }

Give the AI one or two representative examples and say:

Use this implementation pattern for new service-level error handling.

The example provides concrete evidence of how the project actually works.

Distinguish "Must Match" From "May Improve"

Not every existing convention needs to be copied blindly.

You can divide requirements into two categories.

Must match:

  • Public API behavior
  • Database access patterns
  • Error types
  • Authentication mechanisms
  • Existing dependency choices

May improve:

  • Local variable naming
  • Small readability improvements
  • Minor duplication

This prevents the model from treating every existing line as sacred while still protecting important architectural contracts.

Ask for a Change Plan Before Code

This is one of the most useful techniques for modifying an unfamiliar codebase.

Instead of immediately asking for implementation, use two stages.

First analyze the existing code and propose the smallest set of files and components that need to change. Do not write code yet.

Review the plan.

Then:

Implement only the approved changes. Do not modify additional files unless a dependency is discovered that makes it necessary.

This creates a useful approval checkpoint.

Ask for a "Change Surface"

For larger modifications, explicitly ask the AI to identify the change surface.

Identify:

  1. Files that must change.
  2. Files that may need changes.
  3. Existing components that should be reused.
  4. Interfaces that must remain unchanged.
  5. Tests that should be added or modified.

This gives you a map before implementation begins.

Don't Ask AI to Refactor While Adding a Feature

A particularly risky prompt looks like this:

Add the feature and clean up the code while you're at it.

"Clean up" has no clear boundary.

The model may:

  • Rename unrelated functions.
  • Move files.
  • Replace libraries.
  • Rewrite working code.
  • Change error behavior.

Separate feature implementation from refactoring whenever possible.

Implement the requested feature only. Do not perform unrelated refactoring.

Use Tests as Behavioral Contracts

Tests are more than verification tools.

They are also valuable context for an AI assistant.

If the existing tests demonstrate how a component is expected to behave, include the relevant ones.

Then say:

Treat the existing tests as behavioral constraints. Preserve their intended behavior unless the requested change explicitly modifies it.

This is often more precise than describing expected behavior entirely in natural language.

Tell the AI How to Handle Unknowns

When working with incomplete repository context, the model will inevitably encounter unknowns.

Don't encourage it to guess.

If required information is missing, identify the uncertainty and explain what file, interface, or configuration would resolve it. Do not invent project behavior.

This turns uncertainty into something visible instead of silently converting it into assumptions.

Use Evidence-Based Repository Reasoning

A useful instruction is:

Base architectural conclusions on the supplied code. If a convention is not demonstrated or documented, label it as an inference rather than a confirmed project rule.

This is particularly important when the AI sees only a subset of the repository.

A Strong Prompt Structure for Existing Codebases

Try organizing the prompt into these sections:

OBJECTIVE
What needs to change?

PROJECT CONTEXT
What is the application and architecture?

RELEVANT FILES
Which files or components are involved?

EXISTING COMPONENTS
What should be reused?

CONSTRAINTS
What must not change?

BEHAVIOR
What should happen after the change?

EXAMPLES
Which existing implementations demonstrate the desired pattern?

TEST REQUIREMENTS
How should the change be verified?

UNKNOWNs
What information is missing?

Example: Adding a Feature to an Existing API

Imagine an existing REST API that already has user management.

A weak prompt would be:

Add a profile endpoint.

A stronger prompt might be:

Objective: Add a GET endpoint that returns the authenticated user's profile.

Architecture: Routes handle HTTP concerns only. Business logic belongs in services. Database operations use repositories.

Existing components: Reuse the existing authentication middleware and UserRepository.

Constraints: Do not modify authentication middleware, database schema, or existing response formats.

Response: Return the existing public user fields used by the account endpoint.

Tests: Add tests for authenticated access, unauthenticated access, and a missing user record.

Before coding: Identify the files that need to change and explain why. Do not implement until the change plan is clear.

This prompt gives the AI considerably less room to invent architecture.

Use a Diff-Oriented Mindset

For existing projects, think in terms of the smallest useful diff.

Instead of:

Rewrite this authentication system to support password resets.

try:

Implement password reset with the smallest set of changes necessary to the existing authentication system. Preserve unrelated behavior.

Then ask:

Before finalizing, identify every changed file and explain why the change was necessary.

This makes unnecessary modifications easier to spot.

Ask for a Self-Check Before the Final Answer

Once the implementation is complete, don't simply ask whether it works.

Give the AI a concrete checklist.

Before returning the implementation, verify:

  1. No new dependency was introduced unnecessarily.
  2. Existing project abstractions are reused.
  3. Public interfaces remain unchanged.
  4. Error handling follows the existing pattern.
  5. Relevant tests cover the new behavior.
  6. Unrelated files were not modified.

Report any item that could not be verified.

The final sentence is important.

It prevents the model from treating every checklist item as automatically satisfied.

A Reusable Existing-Codebase Prompt

You are modifying an existing codebase, not creating a standalone example.

Objective:
[Describe the requested change.]

Architecture:
[Describe the relevant architecture.]

Relevant files:
[List the files/components that matter.]

Existing components to reuse:
[List utilities, services, repositories, libraries, or interfaces.]

Required behavior:
[Describe the expected result.]

Must not change:
[List protected interfaces, behavior, schemas, or modules.]

Implementation rules:

  • Prefer existing abstractions over new ones.
  • Do not introduce dependencies unless necessary.
  • Follow demonstrated project conventions.
  • Do not perform unrelated refactoring.
  • Do not invent missing project behavior.

Before implementation:
Analyze the relevant code and provide the smallest reasonable change plan. Identify affected files, reusable components, risks, and any missing information.

After implementation:
Explain the changed files, tests added or modified, and any assumptions that remain unresolved.

When the AI Still Gets It Wrong

Even with a strong prompt, AI-generated code should be reviewed.

A good workflow is:

  1. Provide repository context.
  2. Ask for a change plan.
  3. Review the plan.
  4. Generate the implementation.
  5. Inspect the diff.
  6. Run tests and static checks.
  7. Ask the AI to investigate actual failures.

The AI should be treated as a development assistant, not as an authority on your codebase.

Final Takeaway

The quality of AI-generated code depends heavily on the quality of the context surrounding the request.

If you give an AI only a feature description, it has to invent the architecture around that feature.

If you give it a map of the codebase, existing abstractions, behavioral examples, constraints, and a clearly defined change boundary, the task becomes much more controlled.

The most useful mindset is simple:

Don't ask AI to build something inside your codebase as if your codebase doesn't exist.

Tell it what already exists, what must remain stable, what can change, and what evidence it should use to make its decisions.

That is how you move from "AI-generated code" toward AI-assisted development that actually fits the project.

Post a Comment