If you drop an AI coding assistant into a large brownfield codebase without guardrails, it will generate plausible, wrong code. Not randomly wrong. Wrong in ways that look idiomatic, compile cleanly, and pass a casual review — and then quietly violate the conventions that years of engineers built on top of, in ways that only surface when something breaks.

This post is about how to prevent that. Specifically: how to give AI assistants the structured, ambient context they need to work correctly in complex legacy systems, and why that context — not the model, not the prompt — is the actual multiplier.

The failure mode

In a large multi-project .NET codebase, a common pattern is maintaining multiple generations of ORM simultaneously. A legacy application using EF6 coexists with newer services using EF Core, both touching the same underlying database. Different mapping strategies, different transaction patterns, different rules about which layer owns which tables.

When you drop an AI assistant into that environment without providing that context, you tend to get:

  • EF Core LINQ syntax in codebases that use EF6
  • Wrong primitive types on timestamp columns (e.g., DateTime where the convention requires DateTimeOffset)
  • Direct database access that bypasses the service layer
  • Entity or table naming that ignores the project’s established prefix conventions
  • Repository implementations that silently omit soft-delete handling
  • Cross-system calls that ignore the documented integration boundaries

None of these is catastrophic in isolation. Collectively, over weeks, they’re how a codebase accrues the kind of subtle inconsistency that makes future changes expensive. And unlike a compile error or a failing test, these violations are invisible until they aren’t.

The problem is not that the AI is bad at code. The problem is that it’s generating code calibrated to the median codebase — not your codebase. It doesn’t know your codebase’s rules unless you tell it.

The anchor: project_context.md

The first structural fix is a single versioned file: docs/project_context.md, placed at the repository root and explicitly referenced in both CLAUDE.md and AGENTS.md.

Not a wiki page. Not a Confluence article that lives outside the IDE. A file that loads into every AI session automatically, as ambient context, before any code is generated.

The file is a list of rules written in the declarative negative: NEVER mix ORM versions in the same repository. NEVER bypass the service layer. NEVER use DateTime for timestamp columns — use DateTimeOffset. NEVER create database queries directly in controllers.

Then the positive conventions: naming schemes, soft-delete patterns, which systems are allowed to call each other, which database tables belong to which application tier.

This is not prompt engineering in the clever sense. It’s documentation that happens to be machine-readable. The same file a new engineer should read on day one is the same file the AI loads before it writes a line. The audience is both.

The key discipline is writing the rules as constraints, not as descriptions. “Use DateTimeOffset for all timestamp columns” is actionable. “We have a history of inconsistent timestamp types” is noise. AI models respond to constraints. They don’t self-correct from prose complaints.

Here is a starting template. The specific rules are yours — but the structure matters. Hard rules first, conventions second, boundaries third.

# Project Context — [Your Project Name]

## Architecture
- Runtime: [e.g., .NET 8 / .NET Framework 4.8 / mixed]
- ORM: [e.g., EF Core for new services, EF6 for legacy]
- Database: [vendor and version]

## Hard Rules (NEVER do these)
- NEVER [your critical constraint #1]
- NEVER [your critical constraint #2]
- NEVER [your critical constraint #3]

## Conventions
- Naming: [entity prefixes, table naming, etc.]
- Data types: [e.g., DateTimeOffset for all timestamps]
- Service boundaries: [which layer owns what]

## Integration Boundaries
- [System A] may call [System B] via [mechanism]
- [System C] is read-only from [System A]'s perspective

Scaling workflow with BMAD

Documentation alone handles the guardrails. It doesn’t handle workflow complexity.

The BMAD Method (Breakthrough Method for Agile AI-Driven Development) provides the framework for the rest. BMAD structures AI-assisted development around role-based agents — Analyst, Scrum Master, Developer — each with defined responsibilities and commands. The base framework is open-source. What you build on top of it is a custom module for your specific codebase.

That module can produce a substantial library of commands and agent definitions covering the full development lifecycle: feature discovery, PRD generation, architecture documentation, sprint planning, story implementation. Each command pre-loads the relevant context — the project conventions, the active feature, the affected components. Each agent definition is a version-controlled file, reviewed and improved over time.

The maintenance cost is real. When BMAD released a new major version, the upgrade required a full module rewrite — the hook and workflow API changed significantly. That’s an accepted cost. The alternative is maintaining nothing and accepting the inconsistency tax permanently.

The Golden Path workflow

A well-structured BMAD module defines a canonical workflow:

init-feature → feature-discovery → create-prd → create-architecture → sprint-planning → dev-story

init-feature takes a work item identifier, creates synchronized branches across the affected repositories, and writes an active-feature.yaml file: ticket identifier, branch name, affected components, current phase.

Every subsequent command reads that file. feature-discovery scans the codebase for relevant files and generates a research document. create-architecture proposes an approach aligned with existing integration patterns. dev-story loads the full feature context — PRD, architecture, active ticket, affected systems — before writing a single line.

The branch name itself becomes ambient context. Extracting the active work item identifier from the current branch name lets agents auto-detect which feature they’re working on between sessions. No manual re-explanation. No “I was working on…” preamble. The context travels with the branch.

This is the Golden Path: a workflow where every AI interaction arrives pre-loaded with the context it needs to do correct work.

What changes

The measurable difference is in context assembly time. Without structured context, a developer spends significant time at the start of every AI session re-explaining the codebase, the conventions, and the current feature. With it, that overhead drops to near zero — the agent arrives pre-loaded. The productivity gain comes from eliminating repeated setup, not from the AI writing faster code.

In complex codebases, a significant portion of task time isn’t writing code — it’s finding the relevant files, understanding the dependencies, verifying the fix approach against conventions. When an agent is pre-loaded with that context, it handles the assembly. The engineer handles the judgment.

The subtler change is this: AI agents that systematically read a codebase against an explicit standard surface what human developers acclimate to ignoring. Unused mapping files. Missing indexes. Legacy patterns that were “known in a vague way” but never catalogued, never prioritized, never fixed. When you give an AI agent the task of auditing against an explicit ruleset, it doesn’t get tired or skip the boring parts.

The honest trade-off

The upfront investment is significant. Building and wiring a full command library and agent definition set doesn’t happen in an afternoon. Feature context systems — making work item detection reliable across submodules, handling edge cases in branch naming — take time to stabilize.

And framework upgrades are a maintenance commitment. Major BMAD versions have required full module rewrites. Future major versions will require the same.

The alternative — raw prompts, ad hoc context, no shared vocabulary — is also a maintenance commitment. It’s just invisible, paid continuously in small amounts of inconsistency, repeated context-setting, and the slow erosion of codebase coherence.

The visible cost beats the invisible one.

The principle

AI doesn’t replace domain knowledge. It amplifies whatever domain knowledge it receives.

Give it nothing, and it generates plausible code calibrated to the median codebase. Give it the specific conventions, the specific anti-patterns, the specific architecture of your system — and it generates code that fits.

The guardrails are the multiplier. Not the model. Not the prompt length. The structured, version-controlled, systematically loaded context that tells the AI where it is, what the rules are, and what it must never do.

In a large legacy codebase with multiple ORM generations, dozens of engineers, and years of accumulated conventions, that context is not optional. It’s the difference between AI that makes things worse in hard-to-see ways, and AI that makes the team genuinely faster.

Getting the context right is the engineering work. Once it’s right, the multiplication follows.

Further Reading


The views expressed here are my own. Examples and scenarios are composites drawn from broad industry experience and do not represent any specific organization, product, or system.