# CLAUDE.md

## Project Overview
...50 lines...

## Code Style
...200 lines of formatting rules...

## Architecture Decisions
...150 lines of historical context...

## Gotchas
...300 lines of edge cases...

## Testing Conventions
...100 lines...
```

**Half your context budget is gone before any work begins.**

HumanLayer keeps their CLAUDE.md under 60 lines.[^2] Frontier LLMs reliably follow 150-200 instructions—and Claude Code's system prompt already uses about 50 of those.[^2]

The math doesn't work. You can't stuff everything in one file.

## Stop Writing Prose About Lint Rules

Why write two hundred lines about code style when one line handles it? I stopped putting anything a tool can enforce in CLAUDE.md.

❌ **Don't write prose about style rules:**
```markdown
## Code Style
- Use 2-space indentation
- Prefer single quotes
- Always add trailing commas
- Maximum line length: 100 characters
```

✅ **Let ESLint handle it:**
```json
{
  "extends": ["@nuxt/eslint-config"]
}
```

The rules are already there—you just don't repeat them in prose:
```js
// What @nuxt/eslint-config contains:
{
  rules: {
    'indent': ['error', 2],
    'quotes': ['error', 'single'],
    'comma-dangle': ['error', 'always-multiline'],
    'max-len': ['error', { code: 100 }]
  }
}
```

The AI can run `pnpm lint:fix && pnpm typecheck` and know immediately if it violated a rule. No interpretation needed. No ambiguity.

**If a tool can enforce it, don't write prose about it.** ESLint for style. TypeScript for types. Prettier for formatting. These rules are verifiable, not interpretable.

Moss calls this *backpressure*—automated feedback mechanisms that let agents self-correct.[^7] Without a linter, you waste your time typing messages like "you forgot to add the import" or "that should be a const, not let." With backpressure, the agent runs the build, reads the error, and fixes itself. You remove yourself from trivial corrections and focus on higher-level decisions.

My CLAUDE.md now just says:

```markdown
Run `pnpm lint:fix && pnpm typecheck` after code changes.
```

One line instead of two hundred. Or skip it entirely—use husky to run checks automatically on commit. This is especially useful for techniques like Ralph, where AI works autonomously through a queue of tasks.[^8]

## The Gotchas ESLint Won't Catch

ESLint won't catch this:

> "Nuxt Content v3 caches aggressively in `.data/`. When you modify transformation logic in hooks, you must clear the cache to test changes."

Or this:

{/* > "Don't mock `@nuxt/content/server` internals in tests—it breaks when Nuxt Content updates. Extract pure logic to `server/utils/` instead." */}

Or this:

> "Wiki-links to data collections require path prefixes. Use `[[authors/john-doe]]`, not `[[john-doe]]`."

These are *gotchas*—non-obvious behaviors that bite you once. The kind of thing you'd tell a new team member on their first day. They need documentation, but they don't belong in CLAUDE.md.

**The insight: CLAUDE.md is for universal context. Gotchas are situational.**

You don't need the wiki-link prefix rule in every conversation—only when you're writing content with author links. Loading it every time wastes tokens.

So where do these gotchas go? And how do you capture them without breaking your flow?

## My /learn Skill

My system: when I notice Claude struggling with something we've solved before, I run `/learn`.

This is a Claude Code skill I built ([see full prompt](/prompts/claude/claude-learn-command)). It:

1. Analyzes the conversation for reusable, non-obvious insights
2. Finds the right place in `/docs` to save it (or proposes a new file)
3. Asks for my approval before saving

I end up with a growing knowledge base in my docs folder:

```
docs/
├── nuxt-content-gotchas.md    # 15 hard-won lessons
├── nuxt-component-gotchas.md  # Vue-specific pitfalls
├── testing-strategy.md        # When to use which test type
└── SYSTEM_KNOWLEDGE_MAP.md    # Architecture overview
```

**CLAUDE.md stays stable.** It just tells Claude where to look:

```markdown
## Further Reading

**IMPORTANT:** Before starting any task, identify which docs below are relevant and read them first. Load the full context before making changes.

- `docs/nuxt-content-gotchas.md` - Nuxt Content v3 pitfalls
- `docs/testing-strategy.md` - Test layers and when to use each
```

The **IMPORTANT** instruction is critical—without it, Claude won't automatically read these docs. With it, Claude identifies relevant docs before starting work: content queries trigger the gotchas doc, testing tasks trigger the testing strategy. Progressive disclosure—the right context at the right time.[^2]

Another approach: build skills that load domain-specific gotchas automatically. A `nuxt-content` skill that injects the gotchas doc whenever you're working with content queries. In theory, this is cleaner—context loads without you thinking about it. In practice, I've found skills don't always activate when expected. The trigger conditions can be fuzzy, and sometimes Claude just doesn't invoke them. Vercel's agent evals confirmed this: skills were never invoked in 56% of their test cases, producing zero improvement over baseline.[^9] The docs-based setup is more predictable: I know Claude will read what I point it to.

## One Agent Per Domain

I take this further with custom agents. Each agent has its own documentation file that loads only when needed. If you're new to how these customization layers work together, I wrote a <InternalLink slug="claude-code-customization-guide-claudemd-skills-subagents">detailed comparison of CLAUDE.md, skills, and subagents</InternalLink>.

```
.claude/agents/
├── nuxt-content-specialist.md   # Content queries, MDC, search
├── nuxt-ui-specialist.md        # Component styling, theming
├── vue-specialist.md            # Reactivity, composables
└── nuxt-specialist.md           # Routing, config, deployment
```

When I'm debugging a content query, Claude loads the nuxt-content-specialist. When I'm styling a component, it loads nuxt-ui-specialist. The specialist agents know to fetch the latest documentation from official sources—they don't rely on stale training data.

This is why I don't use MCPs like context7 for documentation. Agents can fetch llms.txt directly from official docs sites and find what they need. No tool definition bloat, no intermediate tokens—just a focused research task in its own context window. I wrote more about <InternalLink slug="why-you-dont-need-nuxt-mcp-claude-code">why I use custom research agents instead of MCPs</InternalLink>.

Skills work similarly—with `context:fork`, they run in isolated contexts without polluting your main conversation. The agent has both the ability and motivation to read real documentation. No context7, no MCP overhead.

## It Compounds

This system creates a feedback loop:

Over time, my `/docs` folder becomes a curated knowledge base of *exactly the things AI coding tools get wrong* in my codebase. It's like fine-tuning, but under my control.

I got this idea from a pattern for self-improving skills where agents automatically analyze sessions and update themselves.[^5] I adapted it to use markdown documentation and a `/learn` command instead—giving me explicit control over what gets captured and where it goes.

An actual entry from my `nuxt-content-gotchas.md`:

```markdown
## Page Collection Queries: Use `stem` Not `slug`

The `slug` field doesn't exist in page-type collections.
Use `stem` (file path without extension) instead:

// ❌ Fails: "no such column: slug"
queryCollection('content').select('slug', 'title').all()

// ✅ Works
queryCollection('content').select('stem', 'title').all()
```

Claude will never make this mistake again in my project. Not because I added it to CLAUDE.md—but because when it's working with content queries, it reads the gotchas doc first.

## My 50-Line CLAUDE.md

The structure:

```markdown