A spec-driven CLAUDE.md template for .NET solutions (and what each section actually does)
A complete, copy-pasteable CLAUDE.md for .NET 10 + Aspire + EF Core 10 — annotated section by section, with the reasoning behind every rule.
Every Claude Code session starts by reading your CLAUDE.md, which makes it the highest-leverage file in your repository — and most teams write it like a README. Prose about what the project is, a paragraph of vibes about code quality, maybe a bulleted wish list. The agent reads it, nods, and then does whatever the last error message suggests.
Ours is written like a spec, and after a year of running it across a five-developer .NET team and client codebases, the difference is not subtle. This post gives you the full template for a .NET 10 + Aspire + EF Core 10 solution — copy-pasteable, annotated section by section — and the reasoning behind every rule, including the ones we got wrong first.
The background reading, if you want it: Anthropic’s Claude Code best practices covers the mechanics, and Boris Tane’s How I Use Claude Code is the best practitioner account of the daily workflow. This post covers the part both stop short of: what an enforceable CLAUDE.md looks like on a real .NET solution.
Spec-driven, not prose-driven
The principle behind every section below: a CLAUDE.md line earns its place when it changes agent behavior, and it changes behavior when it is written as a rule rather than a description. Three properties make a rule spec-grade:
- It is checkable. “Write clean code” is not checkable. “Queries are
AsNoTrackingunless the handler mutates the entity” is — by a reviewer, and by the agent itself. - It states the exception. Rules without stated exceptions get silently broken the first time reality disagrees. Rules with stated exceptions get followed, because the agent has an authorized escape path instead of an improvised one.
- Ideally, something enforces it. The strongest sections of our CLAUDE.md are shadows of hooks — the text explains the rule, and a PreToolUse script makes it physically un-skippable. A spec without enforcement is still a wish; more on that at the end.
One more constraint that shapes everything: the file is read on every session, so every line has a permanent context cost. Our internal rule is that CLAUDE.md is an index with teeth — rules inline, encyclopedic detail pushed out to docs/ files the agent is told when to read.
The template
Here is the full template. Everything below it is annotation.
# CLAUDE.md
Guidance for Claude Code when working in this repository.
## Project Overview
{One paragraph. What the system is, who uses it, what the money path is.
No marketing language — the agent needs orientation, not persuasion.}
## Tech Stack
.NET 10 (LTS) | C# 14 | Aspire | EF Core 10 + PostgreSQL | xUnit + Testcontainers
Pin versions here and update them when you upgrade. The agent's training
prior will happily generate .NET 6 idioms unless told otherwise.
## Commands
```bash
dotnet build # must pass before any task is "done"
dotnet test # full suite
dotnet test --filter "Category=Unit" # fast loop
dotnet run --project src/AppHost # Aspire — starts the full local topology
```
## Solution Structure
```
src/
├── AppHost/ # Aspire orchestration — service topology lives HERE, nowhere else
├── Api/ # FastEndpoints, vertical slice: Features/{Area}/{Feature}/
├── Domain/ # Entities, invariants. No EF, no HTTP, no exceptions to this
├── Infrastructure/ # EF Core, migrations, external services
└── Tests/ # Mirrors src/ structure one-to-one
```
## Investigation Before Implementation (MANDATORY)
Before writing or modifying ANY code:
1. Read the relevant feature folder end to end — endpoint, handler, entity, tests.
2. Verify every symbol you plan to call actually exists, with the signature
you think it has. Do not implement from memory of "how .NET usually works."
3. If existing docs or patterns contradict the request, STOP and ask.
This project has conventions that differ from framework defaults. Training-data
instincts are wrong here more often than they are right.
## Clarification Policy
Ask clarifying questions BEFORE planning or executing when anything is
ambiguous. Keep asking until the scope is unambiguous. Too many questions
beats one wrong assumption.
Exception: messages containing "automation run" skip clarification and
execute directly.
## Git Policy
- NEVER commit or push directly. The human runs the commit command.
- Branch names: feature/{ticket}-{slug}. No direct commits to main.
- Never rewrite history. Never force-push. Ever.
## Secrets Policy
- Never read, print, or copy values from .env, appsettings.*.json
connection strings, or user-secrets.
- Reference secrets by NAME in code and docs, never by value.
## Key Patterns (enforced in review)
- EF Core: AsNoTracking by default. Tracking queries only in handlers that
mutate, and the mutation must be in the same handler.
- Endpoints: one FastEndpoints class per file, suffix "Endpoint".
Request/Response records, suffix "Request"/"Response". Entities never
cross the API boundary.
- Async: suffix "Async" on public methods only. No async void. No .Result.
- Migrations: generated names only — never hand-edit a migration file;
add a new one.
- Nullability: nullable is enabled solution-wide. No #nullable disable. No !
unless a comment justifies it on the same line.
## Definition of Done
A task is complete when: dotnet build passes, dotnet test passes, new code
has tests matching the existing test style, and no TODO you introduced
remains. "It compiles" is not done.
What each section is doing
Project Overview — one paragraph, no more. The agent does not need your pitch deck; it needs to know what kind of system it is standing in so its thousand micro-decisions (error handling style, logging verbosity, how paranoid to be about money paths) get made in context. One paragraph is a deliberate cap — we have watched teams paste in three pages of product vision that the agent dutifully re-reads every session for zero behavioral change.
Tech Stack — pinned, with the reason stated. This is the cheapest high-value section in the file. A model’s training prior skews toward the .NET versions it saw most — which means Startup.cs patterns, IHostBuilder boilerplate, and pre-C#-12 syntax will leak into generated code unless the file says otherwise. One line of pinned versions moves the agent’s entire prior. The sentence explaining why the pin matters (“your training prior will generate .NET 6 idioms”) does more work than the pin itself — agents follow rules better when the rule carries its reason.
Commands — exact, copy-pasteable, with the fast path. Agents run what you write here verbatim, so write the commands you actually want run. The --filter "Category=Unit" line matters more than it looks: without a designated fast loop, an agent either runs the full suite after every small change (slow) or stops running tests at all (worse). Naming the fast path makes the right behavior the easy behavior. The AppHost line is Aspire-specific and load-bearing — it tells the agent the local topology is orchestrated, so it stops trying to start services individually and stops inventing connection strings that Aspire’s service discovery already provides.
Solution Structure — a map with one warning per line. The annotations do the work: “service topology lives HERE, nowhere else” and “No EF, no HTTP, no exceptions to this” are rules disguised as comments. This is the section that prevents the most common agent architecture failure on layered .NET solutions — the helpful shortcut, where a domain entity quietly gains an EF annotation or an endpoint reaches directly into infrastructure because it was the shortest path to green tests.
Investigation Before Implementation — the most valuable section in the file. This is the rule that kills hallucinated APIs, and it is the one we state most forcefully because it fights the model’s strongest instinct: implementing from its prior of “how .NET usually works.” On our own solutions this section is not a request — a PreToolUse hook blocks Edit and Write calls until investigation has actually happened, a mechanism we’ve written about as the investigation-gate hook. In the public template the section stands alone, and it still works — noticeably fewer invented method calls — it just works probabilistically instead of deterministically. That gap between “usually followed” and “cannot be skipped” is the difference between a CLAUDE.md and a guardrail system.
Clarification Policy — with an automation escape hatch. The policy itself is standard advice. The exception line is the part teams miss: if you ever run Claude Code from scripts, CI, or scheduled jobs, an agent that stops to ask clarifying questions hangs the pipeline. A declared sentinel phrase (“automation run”) gives unattended sessions a sanctioned way to proceed — which means you never have to weaken the interactive rule to accommodate automation. Rules with stated exceptions survive; rules without them get eroded case by case.
Git and Secrets Policies — short, absolute, no reasoning offered. Some rules should not carry nuance. “Never force-push. Ever.” reads differently from a paragraph explaining the circumstances under which force-pushing is discouraged — and the absolute version is the one that holds at 2 a.m. in a long session. The secrets section exists because agents read files to build context, and connection strings are inside files; “reference by name, never by value” is the line that keeps a debugging session from pasting your production credentials into a chat transcript.
Key Patterns — your conventions, stated as invariants. This section is where a generic template becomes your template, and it is the second-highest-leverage section after investigation. Every line here is a code-review comment you will never have to write again. Ours are .NET-specific for a reason: AsNoTracking by default (the single most common EF Core performance mistake agents inherit from tutorials), migration files as append-only artifacts, nullability as non-negotiable. On our engagements this section is generated rather than hand-written — extracted from the actual codebase into a pattern snapshot so it cannot drift from reality — and the solution’s architectural state lives in a per-PR AUTO-TABLE inventory the agent reads at session start. In the public template, write them by hand; ten honest lines beat a hundred aspirational ones.
Definition of Done — the section that changes what “finished” means. Without it, an agent’s natural definition of done is “the code I wrote exists.” With it, done means built, tested, and cleaned up — and the agent runs those checks before announcing completion instead of after being asked. The closing sentence (“It compiles” is not done) sounds like a joke and is not: it is the exact failure mode being ruled out.
What we deliberately left out
As much as the sections themselves, the template is shaped by what is not in it. No architecture essays — those live in docs/ and the investigation section tells the agent when to read them. No code style minutiae that a formatter already enforces — dotnet format does not need reinforcement, and every redundant line dilutes the lines that matter. No aspirational values (“we care deeply about quality”) — un-checkable statements train the agent that this file contains noise. The discipline is the same as any spec: if you cannot describe how a line would be violated, it does not belong.
Maintaining it: agent mistakes are spec bugs
The template above is a starting state, not an artifact — and the maintenance discipline matters as much as the initial writing. The operating rule we use: every time the agent does something wrong that a CLAUDE.md rule should have prevented, that is a spec bug, and it gets fixed the same day. Three fixes, in escalating order:
- Tighten the wording. If the agent misread a rule, the rule was ambiguous. Rewrite it as something checkable — usually this means replacing an adjective with a concrete condition.
- Add the missing exception. If the agent broke a rule because reality demanded it, the rule was incomplete. Encode the escape path explicitly, so the next session takes the sanctioned route instead of improvising.
- Add enforcement. If the same rule keeps failing under context pressure despite clear wording, prose has hit its ceiling — that rule graduates to a hook.
Two habits keep the file healthy over months. Prune on a schedule: once a month, delete any line that has not visibly changed behavior — the file has a context budget, and dead rules spend it. Ours hovers around 150 lines; when it grows past that, something usually belongs in docs/ instead. Measure by absence: the metric for a working CLAUDE.md is what stopped appearing in code review. When AsNoTracking comments vanish from your PRs, that line earned its keep. When a rule’s violations never show up at all, check whether the formatter or a hook made the line redundant — then delete it.
And resist the temptation to copy anyone’s file wholesale, including this one. A CLAUDE.md is a compressed record of your team’s past mistakes; the template gives you the structure and the .NET defaults, but the rules that will save you the most time are the ones you add after your own worst agent session.
The honest limitation — and what sits above it
A CLAUDE.md is advisory. Everything above improves the probability of correct behavior, and a good spec-driven file improves it a lot — but under context pressure, deep in a long session, any prose rule can lose to the most recent error message. That is not a flaw in your file; it is the nature of instructions versus enforcement.
The fix is to back the spec’s most important rules with mechanisms: hooks that block un-investigated edits, build gates that run dotnet build between steps, an oscillation detector that interrupts edit-thrashing before it burns the afternoon. The CLAUDE.md above is the entry layer of the twelve-layer system we install on .NET codebases — genuinely useful on its own, and the natural first step whether or not you ever add the other eleven.
The template — together with the guardrail hooks that enforce its hardest rules and two tiered subagent definitions — is clonable from Code-Majesty-Tech/dotnet-claude-template (MIT). Start with it, replace every placeholder with something checkable, and delete any line you cannot imagine the agent violating. If you want the full enforcement stack stood up on your own solution, that is what our Sprint Zero does — but the file above is yours either way.
Related Posts
GitHub Copilot modernize-dotnet vs. Claude Code on a real .NET 8 → 10 migration
Same model, three tools, one migration — measured. What Copilot's upgrade agent, plain Claude Code, and a guarded setup actually delivered, warts included.
A Roslyn MCP server is the cheat code for AI on .NET — here's what to put in one
Why every C# coding agent needs compiler-grade context, the nine tool methods worth exposing, and the design rules that keep a Roslyn MCP server fast.
AUTO-TABLE inventories: a structured codebase index Claude Code can actually trust
Deterministic, auto-regenerated codebase inventories embedded in your docs — the structured index that lets Claude Code stop re-deriving your architecture.