DEV Community

Olivia Craft
Olivia Craft

Posted on

How CLAUDE.md Rules Transform Your AI Coding Workflow

How CLAUDE.md Rules Transform Your AI Coding Workflow

You start a new chat, paste your usual instructions, fix the same three things you fixed yesterday, and finally start coding.

Tomorrow you'll do it again.

Ad-hoc prompts don't scale. Every session is a clean slate, every teammate has their own version of "the prompt", and the AI keeps making the same mistakes because nothing in the project tells it not to.

CLAUDE.md fixes that. It's not magic — it's a plain text file in your repo that Claude Code reads automatically. But used well, it changes how the whole team interacts with AI.

This post is the practical version: what CLAUDE.md is, why it works, and five concrete rule examples you can drop into real projects today.


What is CLAUDE.md?

CLAUDE.md is a markdown file at the root of your project (and optionally in subdirectories). Claude Code reads it at the start of every session and treats its contents as project-scoped instructions.

my-project/
├── CLAUDE.md          ← read first, applied to every session
├── src/
│   └── CLAUDE.md      ← additional rules for src/
└── server/
    └── CLAUDE.md      ← rules that only apply inside server/
Enter fullscreen mode Exit fullscreen mode

Three things make CLAUDE.md different from a one-off prompt:

  1. It persists. It's a file, versioned with git, reviewed in PRs.
  2. It's shared. Every teammate (and every future session) starts with the same context.
  3. It's hierarchical. Rules can be scoped per directory, so frontend conventions don't leak into the backend.

Why CLAUDE.md beats ad-hoc prompts

I spent three months going back and forth between "just paste better instructions" and "write proper rules". Three observations:

1. Ad-hoc prompts decay. You forget which version was the good one. Two months in, you're working from a prompt full of fixes for bugs that no longer exist.

2. Ad-hoc prompts don't survive teammates. Your colleague writes their own prompt. Now the AI generates two different code styles depending on who's at the keyboard.

3. Ad-hoc prompts can't reference your real tooling. A CLAUDE.md rule that says "format with Prettier (config in .prettierrc)" gives the AI a verifiable signal. A pasted instruction that says "be consistent" gives it nothing.

The shift from "I'm prompting an assistant" to "I'm configuring a teammate" is the whole point.


Five concrete rule examples

These are the patterns I keep coming back to. Each one solves a specific failure mode I've watched AI hit on real projects.

Example 1 — TypeScript + Next.js project

## Stack
- TypeScript 5.x (strict mode), Next.js 15 (App Router)
- Drizzle ORM with PostgreSQL
- Vitest + React Testing Library

## Rules
- No `any`. Use `unknown` and narrow, or define explicit types.
- No `as` assertions except at parse boundaries (Zod, JSON.parse).
- All API route handlers validate input with Zod before any logic.
- Server components by default. Client components only when state, effects, or browser APIs are required — and they must say so in a comment on the first line.
- All exported functions have at least one Vitest test in `__tests__/`.

## Architecture
- App router pages: `src/app/`
- Shared UI: `src/components/`
- Server actions: `src/actions/`
- DB schema: `src/db/schema.ts`
Enter fullscreen mode Exit fullscreen mode

Why it works: every rule has a concrete enforcement signal (a tool, a path, a library). Vague rules like "write good code" get ignored. Specific rules with real tooling get followed.

Example 2 — Python + FastAPI service

## Stack
- Python 3.12, FastAPI, SQLAlchemy 2.x
- Pydantic v2 for all request/response models
- pytest with pytest-asyncio

## Rules
- Type hints on every function signature. Use `from __future__ import annotations`.
- Pydantic models live in `app/schemas/`. Never inline a request model in a route file.
- Database access only through repository classes in `app/repositories/`. Routes never import SQLAlchemy directly.
- Bare `except:` is banned. Catch specific exceptions; if you don't know which, use `except Exception` and log with structured context.
- Async all the way down — no `requests`, use `httpx.AsyncClient`. No `time.sleep`, use `asyncio.sleep`.

## Testing
- Every route handler has a happy-path test and at least one error-path test.
- Fixtures live in `tests/conftest.py`. Don't redefine the test client per file.
Enter fullscreen mode Exit fullscreen mode

Why it works: it tells the AI not just what to write, but where to put it. The repository-pattern rule alone prevents a whole class of "SQLAlchemy session leaked into the route" bugs.

Example 3 — Go microservice

## Stack
- Go 1.23, chi router, sqlc for database access
- Structured logging via `log/slog`

## Rules
- Errors are values. No panics in request paths — return errors and let middleware translate to HTTP.
- Wrap errors with context: `fmt.Errorf("loading user %d: %w", id, err)`. Never return `err` bare from a non-trivial call.
- Context is the first argument of every function that does I/O. Never store a `context.Context` in a struct.
- All exported types and functions have doc comments starting with the identifier name.
- Generate, don't hand-write SQL. New queries go in `db/queries/*.sql`, regenerate with `sqlc generate`.

## Boundaries
- Do not add new dependencies without listing the alternative considered and the reason.
- Keep handlers thin: parse → validate → call service → render. Business logic lives in `internal/service/`.
Enter fullscreen mode Exit fullscreen mode

Why it works: Go AI output drifts toward Java idioms (panics for errors, context in structs, hand-written SQL). The rules pull it back to idiomatic Go.

Example 4 — Rust CLI

## Stack
- Rust 2021 edition, clap for CLI parsing
- anyhow for application errors, thiserror for library errors
- Tokio runtime where async is needed

## Rules
- Library crates use `thiserror` and return rich, typed errors.
- Binary crates use `anyhow::Result` at main and command-handler boundaries.
- No `.unwrap()` or `.expect()` outside of tests and `main` startup. Propagate with `?`.
- Public functions must have `///` doc comments with at least one `# Examples` block.
- Run `cargo clippy -- -D warnings` and `cargo fmt --check` before considering a task complete.

## Performance
- No `.clone()` to "make the borrow checker happy" — fix the lifetime instead.
- Allocate once, iterate many times. Prefer iterator chains over collecting into intermediate `Vec`s.
Enter fullscreen mode Exit fullscreen mode

Why it works: Rust AI output loves to .unwrap() and .clone() its way out of every problem. Naming the lint commands and explicitly banning the shortcut pushes the AI toward the harder-but-correct path.

Example 5 — Cross-stack testing rules

This one lives in any CLAUDE.md, regardless of language.

## Testing rules
- Test behavior, not implementation. If a refactor that doesn't change behavior breaks a test, the test is wrong.
- One assertion per test where possible. If you need many, name the test after the scenario, not the function.
- Tests are deterministic. No real network, no real clock, no real filesystem unless the test is explicitly about that.
- Use real dependencies in integration tests (real DB, real Redis) — mocks lie at module boundaries.
- A failing test is a P0 blocker. Do not commit code on top of red tests.
Enter fullscreen mode Exit fullscreen mode

Why it works: this is the rule set that catches the AI's worst habit — writing tests that pin the current implementation rather than the desired behavior. Once the rule is explicit, the AI stops generating those tests.


How to structure CLAUDE.md

A CLAUDE.md that tries to say everything ends up saying nothing. The structure that has held up across dozens of projects:

# Project Name

One-paragraph description of what this project does and who reads this file.

## Stack
- Language + version
- Framework + version
- Database, key infra
- Test runner + linter

## Rules
- Strong, specific rules with enforcement signals
- Each rule under 25 words
- Group by concern (style, types, errors, testing)

## Architecture
- File path conventions
- Layering rules (what can import what)
- Naming conventions for files, classes, functions

## Boundaries
- What NOT to modify (legacy code, generated files)
- What requires explicit approval (new dependencies, schema changes)
- Scope discipline (one logical change per commit)
Enter fullscreen mode Exit fullscreen mode

Four short sections. Anything longer gets skipped — by humans first, by the AI eventually.

A few rules of thumb:

  • Lead with the strongest rule. The first three lines get the most weight.
  • Reference real tooling. "Run pnpm lint" lands harder than "lint your code".
  • Use negative rules with alternatives. "Never use var — use const by default, let only when reassigning" beats "don't use var".
  • Keep it under 200 lines. If you need more, split into per-directory CLAUDE.md files.

Anti-patterns I've stopped writing

  • Vague aspirations. "Write clean code" is noise. Replace with a specific, checkable rule.
  • Rules that contradict the codebase. If CLAUDE.md says "use functional components" but the code is half class components, the AI guesses. Update the rule or the code, but don't ship the contradiction.
  • Tone instructions. Things like "be helpful and friendly" belong in a system prompt, not a project file.
  • Long preambles. No one reads 30 lines of context before the first rule. Lead with the rules.
  • Rules without examples for hard cases. If a rule has an obvious edge case, write the edge case down. AI won't infer it.

Start with proven rules

Writing effective CLAUDE.md rules from scratch takes weeks of trial and error. If you want to skip that step:

  • Free sample: I've published a CLAUDE.md Free Sample on GitHub Gist — a complete, working CLAUDE.md you can drop into a project today.
  • Full pack: The CLAUDE.md Rules Pack covers TypeScript, Python, Go, Rust, React, Next.js, FastAPI, and testing — every rule production-tested across multiple projects, organized by the patterns described above.

Both are designed to be edited, not blindly copied. Keep what fits, delete what doesn't, ship the rest.


The takeaway

The developers shipping the most with AI aren't the ones writing the longest prompts. They're the ones who turned their best prompt into a file, committed it, and stopped retyping it.

Write the file. Commit it. Iterate on it like code.

That's the entire transformation.

Top comments (0)