停止膨脹你的CLAUDE.md:AI程式碼工具的漸進式揭露

停止膨脹你的CLAUDE.md:AI程式碼工具的漸進式揭露

Hacker News·

本文反對將所有學到的經驗塞入單一CLAUDE.md檔案以供AI程式碼工具使用的常見做法。作者提倡使用「漸進式揭露」來管理LLM有限的上下文窗口,避免因不相關資訊導致效能下降。

Stay Updated!

Get the latest posts and insights delivered directly to your inbox

Stop Bloating Your CLAUDE.md: Progressive Disclosure for AI Coding Tools

Yesterday I spent an hour debugging a Nuxt Content gotcha with Claude. We figured it out together—you need to use stem instead of slug in page collection queries. Today? Claude made the same mistake. Yesterday’s session was gone.

The examples in this post come from my Second Brain—a personal wiki built with Nuxt and Nuxt Content that uses Zettelkasten-style wiki-links for knowledge management. You can see the actual CLAUDE.md file on GitHub.

That’s the constraint. Your context is just an array of tokens—a sliding window that forgets everything the moment the conversation ends.1

Each message you send adds to the array. The context window is just a sliding array of messages.

There’s no hidden memory. No database of past conversations. Just this array, rebuilt fresh every session.

Dex Horthy calls this “context engineering”—since LLMs are stateless, the only way to improve output is optimizing input.2 The array is all you have. Everything outside it doesn’t exist to the model.

But that array has a size limit. Fill it with noise, and you’re working in what Dex calls the “dumb zone”—where performance degrades because irrelevant context competes for attention.

Most developers respond to this by putting every lesson learned into their CLAUDE.md file. I’ve seen files balloon to 2000 lines. Style guides, architectural decisions, war stories from that one bug that took three days to fix.

This makes things worse.

Bloated CLAUDE.md Makes Things Worse#

When Claude makes a mistake, the instinct is to add a rule: “Never use slug in page collection queries—use stem instead.”

Then another mistake, another rule. Then another.

Before long, your CLAUDE.md looks like this:

Half your context budget is gone before any work begins.

Drag the slider to see how CLAUDE.md size affects your available context budget.

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

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:

✅ Let ESLint handle it:

The rules are already there—you just don’t repeat them in prose:

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.4 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:

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.5

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). It:

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

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

When Claude needs to work with Nuxt Content, it reads the gotchas doc. When it’s writing tests, it reads the testing strategy. Progressive disclosure—the right context at the right time.3

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. 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 detailed comparison of CLAUDE.md, skills, and subagents Claude Code customization guide: CLAUDE.md, skills, subagents explained Compare CLAUDE.md, slash commands, subagents, and skills in Claude Code. Learn when to use each with practical Dexie.js examples. claude-codeaitooling +1 Dec 21, 2025
function initInternalLinks() {
const wrappers = document.querySelectorAll(".internal-link-wrapper");

wrappers.forEach(wrapper => {
  const card = wrapper.querySelector(".preview-card");
  const link = wrapper.querySelector(".internal-link");
  if (!card || !link) return;

  // Skip if already initialized
  if (wrapper.dataset.initialized === "true") return;
  wrapper.dataset.initialized = "true";

  // Function to hide the card
  const hideCard = () => {
    card.classList.remove("is-fixed");
    card.style.removeProperty("--pc-top");
    card.style.removeProperty("--pc-left");
    card.style.opacity = "";
    card.style.visibility = "";
    // Remove scroll listener when hiding
    window.removeEventListener("scroll", hideCard);
  };

  wrapper.addEventListener("mouseenter", () => {
    // Make visible first so it has size
    card.style.opacity = "1";
    card.style.visibility = "visible";

    // Use fixed positioning to escape overflow clipping
    const linkRect = link.getBoundingClientRect();
    const cardRect = card.getBoundingClientRect();

    // Position above by default; flip below if too close to top
    const aboveTop = linkRect.top - 8 - cardRect.height;
    const belowTop = linkRect.bottom + 8;
    const top = aboveTop < 10 ? belowTop : aboveTop;

    // Center horizontally on the link
    const left = linkRect.left + linkRect.width / 2 - cardRect.width / 2;

    // Constrain to viewport
    const vw = document.documentElement.clientWidth;
    const safeLeft = Math.max(8, Math.min(left, vw - cardRect.width - 8));

    // Apply fixed position with coordinates
    card.classList.add("is-fixed");
    card.style.setProperty("--pc-top", `${Math.round(top)}px`);
    card.style.setProperty("--pc-left", `${Math.round(safeLeft)}px`);

    // Hide card on scroll
    window.addEventListener("scroll", hideCard, { passive: true });
  });

  wrapper.addEventListener("mouseleave", hideCard);
});

}

// Initialize on both initial load and after client-side navigation
document.addEventListener("astro:page-load", initInternalLinks);
.

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 why I use custom research agents instead of MCPs Why You Don't Need the Nuxt MCP When You Use Claude Code Why I use custom research agents instead of MCP servers for AI-assisted development. Learn how llms.txt enables context-efficient documentation fetching with a practical Nuxt Content agent example. aiclaude-codenuxt +1 Dec 31, 2025
function initInternalLinks() {
const wrappers = document.querySelectorAll(".internal-link-wrapper");

wrappers.forEach(wrapper => {
  const card = wrapper.querySelector(".preview-card");
  const link = wrapper.querySelector(".internal-link");
  if (!card || !link) return;

  // Skip if already initialized
  if (wrapper.dataset.initialized === "true") return;
  wrapper.dataset.initialized = "true";

  // Function to hide the card
  const hideCard = () => {
    card.classList.remove("is-fixed");
    card.style.removeProperty("--pc-top");
    card.style.removeProperty("--pc-left");
    card.style.opacity = "";
    card.style.visibility = "";
    // Remove scroll listener when hiding
    window.removeEventListener("scroll", hideCard);
  };

  wrapper.addEventListener("mouseenter", () => {
    // Make visible first so it has size
    card.style.opacity = "1";
    card.style.visibility = "visible";

    // Use fixed positioning to escape overflow clipping
    const linkRect = link.getBoundingClientRect();
    const cardRect = card.getBoundingClientRect();

    // Position above by default; flip below if too close to top
    const aboveTop = linkRect.top - 8 - cardRect.height;
    const belowTop = linkRect.bottom + 8;
    const top = aboveTop < 10 ? belowTop : aboveTop;

    // Center horizontally on the link
    const left = linkRect.left + linkRect.width / 2 - cardRect.width / 2;

    // Constrain to viewport
    const vw = document.documentElement.clientWidth;
    const safeLeft = Math.max(8, Math.min(left, vw - cardRect.width - 8));

    // Apply fixed position with coordinates
    card.classList.add("is-fixed");
    card.style.setProperty("--pc-top", `${Math.round(top)}px`);
    card.style.setProperty("--pc-left", `${Math.round(safeLeft)}px`);

    // Hide card on scroll
    window.addEventListener("scroll", hideCard, { passive: true });
  });

  wrapper.addEventListener("mouseleave", hideCard);
});

}

// Initialize on both initial load and after client-side navigation
document.addEventListener("astro:page-load", initInternalLinks);
.

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.

Entry point loaded every session. Keep minimal.

Learnings and gotchas. Read when relevant.

Domain specialists. Loaded via Task tool.

Click each layer to expand. Press "Animate" to see on-demand loading.

It Compounds#

This system creates a feedback loop:

Click "Next Step" to walk through the feedback loop, or "Auto" for automatic playback.

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.6 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:

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:

That’s it. Universal context only. Everything else lives in docs, agents, or tooling.

Cross-Tool Compatibility#

If you use multiple AI coding tools, you don’t need separate config files. VS Code Copilot and Cursor both support agents.md for project-level instructions. You can symlink it to share the same configuration:

Now your minimal, focused instructions work across Claude Code, Copilot, and Cursor. One source of truth, no drift between tools.

How This Played Out Last Week#

Last week I was implementing semantic search. Claude suggested using slug in a query. I corrected it—we use stem for page collections. Claude fixed it.

Then I ran /learn.

Claude analyzed the conversation, found the existing entry in nuxt-content-gotchas.md, and noted that we’d already captured this pattern. No duplicate needed.

But during the same session, we discovered something new: queryCollectionSearchSections returns IDs with a leading slash. Don’t add another slash when constructing URLs.

I added it. Next time I work on search, Claude will know.

AI tools being stateless isn’t a bug to fight. It’s a design constraint—like limited screen real estate or slow network connections. Accept it, and you can build systems that work with it.

Keep CLAUDE.md minimal. Let tooling enforce what it can. Capture learnings as you go. Load context on demand.

One caveat: you can never be 100% sure agents will read your docs when they face issues. For tricky domains like Nuxt Content—where training data is sparse or outdated—I’ve learned to be explicit in my prompts. When I know I’m working on something with poor training coverage, I’ll add to the plan: “If you encounter issues with Nuxt Content APIs, read docs/nuxt-content-gotchas.md first.” This nudge makes the difference between the agent guessing based on outdated patterns and actually consulting current knowledge.

The AI forgets. Your documentation doesn’t.

Footnotes#

LLMs have no memory between sessions—context is just tokens in a sliding window. See Factory’s analysis in The Context Window Problem. ↩

Dex Horthy, No Vibes Allowed: Solving Hard Problems in Complex Codebases. Dex is the founder of HumanLayer and creator of the Ralph technique for autonomous AI coding. His 12 Factor Agents manifesto includes “Make Your Agent a Stateless Reducer” as Factor 12. ↩

HumanLayer’s guide on Writing a Good CLAUDE.md recommends keeping files under 60 lines and using progressive disclosure for detailed instructions. ↩ ↩2 ↩3

Moss, Don’t Waste Your Back Pressure. Backpressure—automated feedback from type systems, linters, and build tools—is what enables agents to work on longer-horizon tasks without constant human intervention. ↩

Geoffrey Huntley, Ralph. Ralph is a technique for autonomous AI coding where tasks are queued and executed without human intervention, making automated checks on commit essential. ↩

Developers Digest, Self-Improving Skills in Claude Code. A pattern for capturing learnings automatically: skills analyze sessions, extract corrections, and update themselves. ↩

Stay Updated!

Subscribe to my newsletter for more TypeScript, Vue, and web dev
insights directly in your inbox.

Image

Image

Hacker News

相關文章

  1. 使用 Claude Code 進行 AI 輔助編碼的最佳實踐與建構 Claude.md

    3 個月前

  2. 探索前沿人工智能的極限

    3 個月前

  3. Claude 4.5 Opus 的靈魂文件曝光

    Lesswrong · 5 個月前

  4. 使用 Claude Code:會話管理與 100 萬上下文指南

    Thariq · 10 天前

  5. 通用 CLAUDE.md:減少 Claude 輸出 Token 達 63%

    26 天前

其他收藏 · 0