Context Engineering
How to give AI coding tools the right context — rule files, repo conventions, MCP servers, retrieval, and structuring prompts so the model actually understands your project
Context Engineering
The biggest lever on AI output quality is not the model — it is the context you give it. Two engineers using the same tool on the same codebase get wildly different results, and the difference is almost always context. One pastes a vague request into an empty window. The other has a well-maintained rule file, points the tool at the relevant code, and structures the request so the model knows exactly what it is working with.
Context engineering is the discipline of deciding what the model sees, what it ignores, and how that information is structured. It is the part of AI-assisted development that compounds: invest once in good project context and every interaction after that gets better.
The Mental Model: The Model Knows Nothing About You
An LLM has read most of the open-source code in the world, but it has read none of your code until you show it. It does not know your naming conventions, your error-handling philosophy, which logging library you use, or that your team banned any in TypeScript two years ago.
Every request starts from zero unless you supply context. The model fills gaps with the statistical average of its training data — which is why it suggests Express when you use Fastify, or console.log when you have a structured logger. The context you provide is the difference between "generic plausible code" and "code that fits this project."
| Context the model has | Context you must supply |
|---|---|
| General language and framework knowledge | Your conventions and house style |
| Common library APIs | Which version you are pinned to |
| Standard design patterns | Which patterns your team has chosen |
| Public best practices | Your domain rules and constraints |
| How most projects are structured | How this project is structured |
Project Rule Files
The single highest-leverage investment is a project rule file: a document the AI tool reads automatically on every interaction. Different tools have different names for it.
| Tool | File | Scope |
|---|---|---|
| Claude Code | CLAUDE.md | Repo root, plus nested files in subdirectories |
| Cursor | .cursor/rules/*.mdc (or legacy .cursorrules) | Repo root, with glob-scoped rules |
| GitHub Copilot | .github/copilot-instructions.md | Repo root |
| Windsurf | .windsurfrules | Repo root |
These files solve the "model knows nothing about you" problem permanently. Instead of repeating your conventions in every prompt, you write them down once and the tool injects them automatically.
What Belongs in a Rule File
A good rule file is short, specific, and actionable. It is not documentation for humans — it is instructions for a model that will follow them literally.
# CLAUDE.md
## Project
Next.js 14 app router + TypeScript + Prisma + PostgreSQL.
Package manager is pnpm. Never use npm or yarn.
## Conventions
- Validation: always use Zod schemas, never manual checks.
- Errors: throw typed errors from `lib/errors.ts`, never bare `Error`.
- Database: all queries go through the repository layer in `lib/db/`,
never call `prisma` directly from route handlers.
- Tests: colocate as `*.test.ts`, use Vitest, mock with `vi.fn()`.
## Commands
- Test: `pnpm test`
- Typecheck: `pnpm typecheck`
- Lint: `pnpm lint`
Run typecheck and test before considering any change complete.
## Don't
- Don't add dependencies without asking.
- Don't use `any`. Use `unknown` and narrow.
- Don't edit files in `generated/` — they are auto-generated.What Does NOT Belong in a Rule File
- Long prose explanations. The model does not need three paragraphs on why you chose Prisma. State the rule, not the history.
- Information that changes constantly. If you list "current sprint goals," it goes stale in two weeks and misleads the model.
- Secrets or credentials. Rule files get committed. Never put tokens or connection strings in them.
- Everything. A 600-line rule file dilutes the important rules. Keep it under ~100 lines and link to deeper docs if needed.
Keep Rule Files Fresh
A stale rule file is worse than none, because the model follows it confidently. When you migrate from Jest to Vitest, the rule file must change in the same PR. Treat it like code: it goes through review, and "the rule file is wrong" is a valid review comment. A good habit is to review CLAUDE.md whenever a teammate's AI output drifts from house style — the drift usually means the rule file is missing something.
Conveying Repo Conventions
Rule files state conventions explicitly. But the model also infers conventions from the code it sees. This means the fastest way to get conventional output is to show the model an example.
Here is how we write API route handlers in this project:
[paste app/api/users/route.ts]
Now write the equivalent handler for /api/orders following
exactly the same structure, error handling, and validation approach.This "show, don't tell" technique consistently outperforms describing conventions in words. The model is excellent at pattern-matching from a concrete example and weaker at applying abstract rules. When both the rule file and an example point the same direction, output quality is highest.
MCP Servers: Giving the Model Live Tools
The Model Context Protocol (MCP) is a standard that lets AI tools connect to external systems — databases, issue trackers, documentation, internal APIs — as structured tools rather than copy-pasted text. Instead of you pasting a schema, the model queries the database directly. Instead of you summarizing a ticket, the model reads it from Jira.
| MCP server | What it gives the model |
|---|---|
| Postgres / database | Live schema, ability to run read queries |
| GitHub | PRs, issues, code search, CI status |
| Filesystem | Scoped read access to specific directories |
| Sentry / observability | Recent errors and stack traces |
| Internal docs | Searchable company knowledge base |
The value of MCP is fresh, authoritative context fetched on demand rather than stale context you remembered to paste. The model can look up the actual current schema instead of working from a snapshot you provided last week.
MCP Guardrails
MCP is powerful precisely because it gives the model real access — which is also the risk. Apply discipline:
- Read-only by default. A database MCP server should not have write access unless you have a specific reason. The model running a
DELETEagainst production is a catastrophe waiting to happen. - Scope the access. Filesystem servers should be pointed at the project directory, not your home folder with SSH keys.
- Prefer official servers. A third-party MCP server runs with your credentials. Audit what you install the same way you audit any dependency.
- Do not connect production carelessly. Point at a staging or read replica when you can.
Retrieval and Indexing
For anything larger than a handful of files, the model cannot see your whole codebase at once. Tools solve this with retrieval: they index the repository and pull in the most relevant snippets for each request.
- Cursor indexes your codebase into embeddings and retrieves relevant files automatically based on the request.
- Claude Code explores on demand — it greps, reads files, and follows references rather than relying on a precomputed index.
You do not usually configure retrieval directly, but you influence it. Clear file and symbol names make code more retrievable. A function called validateAndNormalizeUserRegistration surfaces for a relevant query; one called proc2 does not. Good naming is, among other things, context engineering for both your future self and the AI.
Help Retrieval With Explicit Pointers
When you know where the relevant code lives, say so. Do not make the tool guess.
Bad: Fix the bug in the checkout flow.
Good: There's a bug in the discount calculation in
lib/pricing/discount.ts — the `applyVolumeDiscount`
function. It should only apply to orders over $500,
but it's applying to all orders. Read that file and
the test in discount.test.ts, then fix it.The good version saves the tool from searching, points it at the exact files, and states the expected behavior. Even with retrieval, an explicit pointer is faster and more accurate than letting the tool hunt.
What to Include vs Exclude
Context windows are large but not infinite, and — more importantly — irrelevant context actively degrades output. A model given ten files when two are relevant has to figure out which two matter, and it sometimes guesses wrong. The skill is curation, not maximal stuffing.
| Include | Exclude |
|---|---|
| The files you are changing | Unrelated modules |
| Direct dependencies of those files | The entire node_modules tree |
| One example of the pattern to follow | Five redundant examples |
| Relevant type definitions | Generated code and lockfiles |
| The failing test or error message | Old commented-out code |
| The rule file | Your whole git history |
The "Too Much Context" Failure Mode
Pasting your entire 5,000-line file when the bug is in one function does not help — it hurts. The model spends attention on irrelevant code and is more likely to "fix" things that were not broken. When debugging, isolate the relevant function and its immediate collaborators. When a tool keeps making strange unrelated edits, the usual cause is that you gave it too much surface area, not too little.
Use ignore files to keep noise out of indexing:
# .cursorignore / .aiignore
node_modules/
dist/
*.lock
generated/
*.min.js
coverage/Structuring Prompts and Context Windows
How you order information inside a single request matters. A useful structure:
1. ROLE / GOAL — what you want, in one or two sentences
2. CONTEXT — relevant code, types, conventions
3. CONSTRAINTS — what must hold (use Zod, don't add deps, etc.)
4. TASK — the specific change, stated precisely
5. VERIFICATION — how to confirm it worked (run these tests)GOAL: Add rate limiting to the login endpoint.
CONTEXT:
[paste app/api/auth/login/route.ts]
[paste lib/redis.ts]
We already use Redis (see lib/redis.ts). Conventions are in CLAUDE.md.
CONSTRAINTS:
- Use the existing Redis client, do not add a new dependency.
- 5 attempts per IP per 15 minutes, sliding window.
- Return 429 with a Retry-After header on limit.
TASK:
Add the rate-limit check at the start of the handler, before
password verification, so we don't leak timing information.
VERIFICATION:
Add a test in login.test.ts that asserts the 6th request returns 429.
Run `pnpm test` and `pnpm typecheck`.Putting the goal first orients the model before it reads the context. Putting verification last gives it a way to check its own work. The model treats this like a spec, and specs produce far better output than open-ended requests.
Keeping Context Fresh Within a Session
Long sessions accumulate stale context. The model may still be "thinking about" a file you edited away from three steps ago, or holding onto a decision you have since reversed. Manage this:
- Start fresh for unrelated tasks. Do not debug a payments bug in the same conversation where you just refactored the auth module. Open a new session so old context does not bleed in.
- Re-state the current state after big changes. "We've now switched to the repository pattern; the old direct-Prisma code is gone" prevents the model from reverting.
- Summarize and reset for very long sessions. When a conversation gets unwieldy, ask for a summary of decisions so far, then start a new session seeded with that summary.
The Compounding Payoff
Context engineering is the highest-leverage skill in AI-assisted development because it compounds. A good CLAUDE.md improves every future interaction. A well-curated set of MCP servers means the model always works from fresh facts. Clear naming makes your code retrievable forever.
The engineer who invests an hour writing a sharp rule file gets that hour back within a week, and keeps getting it back on every subsequent task. The one who pastes vague requests into empty windows re-pays the context cost on every single interaction.
The Bottom Line
The model does not know your project — you have to tell it, and how you tell it determines the quality of everything it produces. Write a tight rule file and keep it fresh. Show examples rather than describing conventions. Connect MCP servers for live facts, with read-only guardrails. Include the relevant context and ruthlessly exclude the rest. Structure each request like a spec.
Context engineering is not overhead on top of AI-assisted development. It is AI-assisted development.