AI Development

The Anatomy of a Claude Code Project

Ryan Mitchell
Author
Mar 1, 2026
15 min read
The Anatomy of a Claude Code Project

Most people think using Claude Code is about writing better prompts.

It is not.

The real unlock is structuring your repository so Claude can think like an engineer -- not a chatbot you are explaining things to over and over.

I run a 20-agent AI orchestration platform called Bureau. Four business verticals, six Docker containers, 30+ agents, each with a different LLM and a defined role. Claude Code is how I build it. Not Cursor, not Copilot -- Claude Code, operating inside a codebase that is designed for it.

The difference between Claude Code behaving like a chatbot and Claude Code behaving like a developer who lives inside your codebase comes down to one thing: project structure.

A messy repo gives Claude nothing to work with. It guesses, hallucinates file paths, invents API patterns that don't match yours, and asks you to confirm things it should already know. A structured repo gives Claude the equivalent of a senior engineer's institutional knowledge -- on every session, from the first prompt.

Your project needs four things:

  • The Why -- what the system does and how it is architected
  • The Map -- where things live and how they connect
  • The Rules -- what is allowed, what is forbidden, and what breaks if you do it wrong
  • The Workflows -- how work actually gets done

Here is how to build each one.


1. CLAUDE.md -- Your Repo's Institutional Memory

CLAUDE.md is the single most important file in your project. It is loaded into every Claude Code session -- the first thing Claude reads before it touches your code. Think of it as the onboarding document you would write for a senior engineer joining your team, except this engineer has perfect recall and reads it every single morning.

What CLAUDE.md Actually Is

It is a markdown file at your project root. Claude Code loads it as a user message after the system prompt. That distinction matters -- it is not a system-level instruction with guaranteed compliance. It is more like a strong recommendation from a trusted colleague. This is why concise, specific instructions massively outperform verbose ones. Anthropic's docs recommend under 500 lines. In practice, the community consensus is tighter: 200 lines or fewer per file.

HumanLayer's engineering team keeps theirs under 60 lines. SFEIR Institute found that well-structured CLAUDE.md files reduce instruction repetition by 25% across sessions. And Anthropic's own system prompt consumes roughly 24K tokens of the 200K context window -- every line in your CLAUDE.md is competing with your actual code for attention.

The Three Things It Needs

1. Purpose -- why the system exists

Not a product description. An architectural statement that tells Claude what kind of system it is working inside.

## Project Overview

Bureau -- Multi-agent AI orchestration framework with four business verticals. React SPA frontend talks to a FastAPI backend, which dispatches jobs to Celery workers that call LLM agents through OpenClaw (a Node.js gateway). PostgreSQL + pgvector for persistence and memory, Redis for task brokering.

That is 6 lines. Claude now knows it is working in a multi-service Python/Node/React system with a task queue architecture. Every file it reads, every function it writes, every suggestion it makes will be informed by that context.

2. Map -- how the project is structured

This is where most people go wrong. They either include nothing (Claude guesses), or they paste their entire directory tree (Claude drowns in noise).

The sweet spot is a functional map -- not every file, but every important area with what it does:

## Code Layout

    Backend (celery_app/)

  • api.py -- FastAPI app, startup hooks, router registration
  • models.py -- All SQLAlchemy ORM models, enums, init_db(), seed functions
  • tasks.py -- Celery tasks: execute_agent_job, orchestrate_task
  • memory.py -- pgvector cosine similarity retrieval + embedding storage

    Routers (celery_app/routers/)

  • bookings.py -- Central workflow (status: enquiry to confirmed)
  • quotes.py, invoices.py -- Financial lifecycle
  • conversations.py -- Multi-channel messaging (WhatsApp/SMS/email)

    Frontend (frontend/)

  • src/pages/ -- Route-level components
  • src/lib/api/ -- Typed API clients with auto-refresh interceptor
  • src/stores/ -- Zustand stores for auth, UI state, SSE

Claude does not need to know about every utility file. It needs to know where to look and what pattern each area follows.

3. Rules -- what is allowed and what breaks

This is the section that separates productive Claude Code sessions from debugging sessions. Every codebase has non-obvious constraints -- things that work differently from the default, things that break silently, things that a new engineer would get wrong on their first day.

Document them:

## Critical Gotchas
  • Container builds use COPY not volume mount -- code changes require
  • docker compose build, not just restart
  • Celery worker MUST listen on celery queue (not just custom queues)
  • or chord unlock tasks never fire
  • The context column is JSON not JSONB -- use jsonb_exists()
  • instead of the ? operator
  • Boolean columns use Integer (0/1) not Boolean

These four lines have saved me hours. Claude reads them once, and never makes those mistakes. Without them, Claude would confidently write a query using the wrong operator, and you would spend 30 minutes debugging a query that returns nothing.

The Anti-Patterns

Don't include things Claude can figure out from the code. If you are using standard Python conventions, you don't need to say "use snake_case for functions." Claude already knows.

Don't include things that change frequently. Your CLAUDE.md should be stable. If you are updating it every day, you are putting transient information in a permanent file. Use auto-memory (MEMORY.md) for that.

Don't include long explanations. If something needs 200 lines of context, it belongs in a separate document that Claude can read on demand -- not in a file that is loaded on every session.

Don't use /init uncritically. Claude Code's auto-generation is a starting point, not a finished product. It tends to include information Claude can already infer from your code. Manually curate what actually needs to be there.

The Size Question

Here is the reality: Claude Code handles roughly 150-200 instructions reliably per session. Your CLAUDE.md system prompt consumes some of that budget. Every line that does not earn its place is noise that dilutes the lines that do.

Our Bureau CLAUDE.md is 401 lines -- larger than the recommended 200 -- but it covers a genuinely complex system (6 containers, 30+ agents, 4 verticals, 24 specific gotchas). For most projects, you should be well under 200. If you are over, ask yourself: is this something Claude would get wrong without this line? If not, delete it.


2. .claude/rules/ -- Contextual Rules That Load When You Need Them

This is the feature most people have not discovered yet, and it solves the biggest problem with CLAUDE.md: not everything is relevant all the time.

The .claude/rules/ directory holds markdown files with optional path-scoping. Rules without a paths frontmatter load at session start (just like CLAUDE.md). Rules with paths only load when Claude is working on matching files.

# Router Conventions (scoped to celery_app/routers/**/*.py)

Every router in this project follows these patterns:

  1. Session management: SessionLocal() with try/finally
  2. Multi-tenancy: Every query filters by organization_id
  3. Auth: Read = get_current_user, writes = require_role("member")
  4. Pagination: All list endpoints take limit and offset
  5. Activity feed: Mutations call publish_activity() after flush()
  6. 404 pattern: Check existence then check org_id

When Claude opens a router file, these rules load automatically. When it is working on frontend code, they don't exist -- no wasted context.

This is the pattern for scaling Claude Code to large codebases. Instead of cramming everything into root CLAUDE.md, you distribute rules to where they are relevant:

.claude/rules/
  api-conventions.md          # Always loaded
  router-patterns.md          # paths: celery_app/routers/**/*.py
  frontend-components.md      # paths: frontend/src/components/**/.tsx
  database-gotchas.md         # paths: celery_app/models.py
  deployment-rules.md         # paths: docker-compose.yml, Dockerfile.

The result: Claude gets exactly the right context at exactly the right time, without paying the token cost of loading everything on every session.


3. .claude/skills/ -- Reusable Expert Modes

Skills are where Claude Code stops being a general-purpose assistant and starts being a specialist. A skill is a reusable prompt with metadata that Claude can invoke -- either when you ask for it (via /skill-name) or automatically when the task matches.

What a Skill Actually Is

A directory in .claude/skills/ with a SKILL.md file:

.claude/skills/
  code-review/
    SKILL.md
  deploy-checklist/
    SKILL.md
  new-router/
    SKILL.md

The SKILL.md file has YAML frontmatter and markdown instructions:

---
name: new-router
description: Create a new API router following project conventions
allowed-tools: Read, Edit, Write, Grep, Glob

# Create New Router

When creating a new router for this project:

  1. Read celery_app/routers/bookings.py as reference
  2. Create the new router file in celery_app/routers/
  3. Follow all 11 router patterns from CLAUDE.md
  4. Register the router in api.py
  5. Add the Pydantic request models at the top
  6. Include the _serialize helper function
  7. Verify: pagination, multi-tenancy, activity feed hooks

Now when you say "create a new router for certificates," Claude does not improvise. It loads the skill, reads the reference implementation, and follows the exact patterns your codebase uses. Every router comes out consistent.

Skills vs. CLAUDE.md -- When to Use Which

NeedUse
Always-relevant contextCLAUDE.md
File-type-specific rules.claude/rules/ with paths
Repeatable workflowsSkills
Hard safety guaranteesHooks
The decision framework: CLAUDE.md is memory. Rules are scoped memory. Skills are routines. Hooks are guarantees.

Skills shine for workflows that happen repeatedly but are not relevant to every session: code reviews, deployment procedures, data migrations, new module scaffolding. Things you would document in a runbook for a human engineer -- except this runbook executes itself.


4. Hooks -- Guardrails That Never Forget

Skills are probabilistic -- Claude chooses to follow them. Hooks are deterministic -- they execute regardless of what Claude decides.

Hooks are shell commands or HTTP calls that fire at specific lifecycle events. They are configured in your settings files (.claude/settings.json for team-shared, .claude/settings.local.json for personal).

Why Hooks Matter

Models forget. Over a long session, Claude might stop running your formatter, forget to run tests, or edit a file it should not touch. Hooks make these things impossible to skip.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python -m py_compile $CLAUDE_FILE_PATH"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo $CLAUDE_FILE_PATH | grep -qE '(auth|billing)/' && echo 'BLOCKED' && exit 2 || exit 0"
          }
        ]
      }
    ]
  }
}

The first hook: after every file edit, Python syntax-checks the file. If the syntax is wrong, Claude sees the error immediately -- before it moves on and makes 10 more edits based on a broken file.

The second hook: before any file edit, check if it is in a protected directory. Exit code 2 blocks the action entirely. Claude cannot edit auth or billing files -- period. Not because it was told not to, but because the system will not let it.

The Hook Lifecycle

Claude Code exposes 22 hook events. The ones that matter most for project structure:

EventWhenUse Case
PreToolUseBefore tool executesBlock edits to protected files
PostToolUseAfter tool succeedsRun formatters, syntax checks
UserPromptSubmitUser sends a messageInject context, validate task scope
SessionStartSession beginsLoad environment, check prerequisites
StopClaude finishesRun final validation, generate summary
The decision pattern: exit code 0 proceeds, exit code 2 blocks, everything else warns.

The Power Pattern: Hooks + Skills

The most effective Claude Code setups combine hooks and skills:

  • Hook ensures every Python edit is syntax-valid (deterministic, can not be skipped)
  • Skill defines how to write a new router (probabilistic, but consistent when invoked)
  • CLAUDE.md provides the architectural context for both (always loaded)
  • Rules scope domain-specific patterns to relevant files (loaded on demand)

This is a layered system. Each layer handles a different kind of knowledge at a different level of enforcement.


5. docs/ and Local CLAUDE.md -- Progressive Context Loading

Claude Code does not need everything in memory. It needs to know where truth lives.

The docs/ Directory

There is no magic treatment of a docs/ directory -- Claude reads files on demand, like any other directory. But that is exactly the point. A well-organized docs directory becomes Claude's reference library:

docs/
  architecture.md           # System design overview
  SDD.md                    # Software Design Document
  adr/                      # Architecture Decision Records
    001-celery-over-temporal.md
    002-pgvector-over-pinecone.md
    003-egress-proxy-pattern.md
  runbooks/
    deployment.md
    incident-response.md
    database-migration.md

Reference these from CLAUDE.md:

## Documentation
  • Architecture overview: docs/architecture.md
  • Design decisions: docs/adr/ (read before proposing changes)
  • Deployment procedures: docs/runbooks/deployment.md

Claude does not load these at startup. It reads them when it needs them -- when you ask it to make an architectural change, it reads the ADRs. When you ask it to deploy, it reads the runbook. Progressive context loading, not upfront context dumping.

Local CLAUDE.md for Danger Zones

Some parts of your codebase have hidden complexity that the root CLAUDE.md can not cover without becoming massive. Subdirectory CLAUDE.md files solve this.

These files load lazily -- only when Claude reads files in that directory. This is the key feature. A CLAUDE.md in src/auth/ only loads when Claude is actually working on auth code, not when it is editing a frontend component.

celery_app/
  CLAUDE.md                   # Backend-specific patterns
  routers/
    CLAUDE.md                 # Router conventions (lazy load)
  services/
    CLAUDE.md                 # Service layer rules
  signals/
    CLAUDE.md                 # Signals subsystem gotchas

This pattern -- lazy-loading subdirectory instructions -- is how you scale Claude Code to large codebases without hitting the context ceiling. The root CLAUDE.md stays lean (the 30,000-foot view), and each directory adds its own depth only when Claude is actually working there.


6. Auto-Memory -- Claude's Own Notes

Auto-memory is the feature most people either don't know about or don't configure well. It is Claude Code's ability to write notes to itself that persist across sessions.

How It Works

Claude stores notes in ~/.claude/projects/project-name/memory/MEMORY.md. The first 200 lines are loaded at session start. Additional topic files (like debugging.md or patterns.md) exist in the same directory but load on demand.

Claude writes to memory automatically when:

  • You correct it on something ("No, we use Integer not Boolean for that column")
  • It discovers a pattern it wants to remember
  • You explicitly ask it to remember something

What to Store vs. What Not To

Good for memory:

  • Transformation roadmaps and long-term plans
  • Coupling audits (what needs to change for the next phase)
  • Deployment architecture specifics (especially non-obvious ones)
  • Patterns confirmed across multiple sessions
  • User preferences ("always use bun", "never auto-commit")

Bad for memory:

  • Session-specific context (current task, in-progress work)
  • Information that duplicates CLAUDE.md
  • Speculative conclusions from reading a single file
  • Anything that changes frequently

The Index Pattern

Keep MEMORY.md as a concise index under 200 lines, with links to topic files for depth:

# Project Memory

Architecture

Bureau is being transformed from plumbing-specific to vertical-agnostic. Three-layer model: Universal kernel + Configurable middleware + Vertical templates.

Deployment -- CRITICAL

Two directories on spark: ~/projects/openclawd_agency/ (SMB mount) vs ~/openclawd_agency/ (Docker build). Must rsync before building. Never overwrite .env on spark.

    See Also

  • patterns.md -- Router patterns, coding conventions
  • personal_agents.md -- Agent data model, family data

The first 200 lines of MEMORY.md load on every session. Everything else loads when Claude needs it. Structure accordingly -- critical context at the top, deep dives in linked files.


7. Settings -- Permission Boundaries and Team Conventions

Settings files control what Claude can and cannot do. They exist at four scopes:

FileScopeShared?
Managed policy (OS-level)OrganizationYes (IT deployed)
~/.claude/settings.jsonAll your projectsNo
.claude/settings.jsonThis projectYes (git)
.claude/settings.local.jsonThis project, you onlyNo (gitignored)
The most important settings for project structure:

Team-shared conventions (.claude/settings.json -- committed to git):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "ruff check --fix $CLAUDE_FILE_PATH" }
        ]
      }
    ]
  }
}

Every team member gets the same auto-formatting. No style drift, no "but it works on my machine."

Personal preferences (.claude/settings.local.json -- gitignored):

{
  "permissions": {
    "allow": [
      "Bash(docker compose:)",
      "Bash(ssh spark:)"
    ]
  }
}

Your deploy shortcuts. Your machine-specific paths. Your personal permission grants.


8. Putting It All Together

Here is the complete anatomy of a well-structured Claude Code project:

my-project/
  CLAUDE.md                          # Root: purpose, map, gotchas
  .claude/
    settings.json                    # Team-shared: hooks, permissions
    settings.local.json              # Personal: local permissions
    rules/
      api-conventions.md             # Always loaded
      router-patterns.md            # paths: src/routers/**/*.py
      frontend-rules.md             # paths: frontend/**/.tsx
    skills/
      code-review/SKILL.md          # /review
      new-router/SKILL.md           # /new-router
      deploy/SKILL.md               # /deploy
  docs/
    architecture.md                  # Deep reference
    adr/                             # Decision records
    runbooks/                        # Operational procedures
  src/
    auth/
      CLAUDE.md                      # Lazy: auth constraints
    api/
      CLAUDE.md                      # Lazy: API patterns
  ~/.claude/projects/project/memory/
    MEMORY.md                        # Auto-memory index
    patterns.md                      # Confirmed code patterns
    debugging.md                     # Recurring issues and fixes

The Loading Order

Understanding when each piece loads is critical:

  1. Session start: Root CLAUDE.md + .claude/rules/ (no paths) + MEMORY.md (first 200 lines) + parent directory CLAUDE.md files
  2. On demand (file access): Subdirectory CLAUDE.md files + .claude/rules/ with matching paths + memory topic files
  3. On invocation: Skills (manual via /skill-name or auto when description matches)
  4. Always: Hooks (deterministic, event-driven, cannot be skipped)

The Context Budget

This loading order exists because context is finite. Claude Code's 200K window sounds large, but:

  • System prompt: ~24K tokens
  • CLAUDE.md + rules: 2-8K tokens (depending on size)
  • MEMORY.md: 1-3K tokens
  • Your actual code and conversation: everything else

Quality degrades noticeably around 147-152K tokens. Optimal sessions stay under 80K. This is why progressive loading matters -- don't pay for context you don't need yet.


The Shift Most People Miss

Prompting is temporary. Structure is permanent.

A good prompt helps Claude once. A good CLAUDE.md helps Claude on every session, for every team member, for the life of the project. A well-placed hook prevents a class of mistakes forever. A scoped rule loads exactly when it is needed and costs nothing when it is not.

The developers getting the most out of Claude Code are not writing longer prompts. They are building repositories that make Claude smarter by default -- repos where the structure itself is the instruction.

Once your repository is designed for AI:

  • Claude stops asking questions it should already know the answer to
  • New team members (human or AI) onboard in minutes, not days
  • Mistakes happen once, get documented, and never happen again
  • Every session starts with institutional knowledge, not a blank slate

The model keeps getting smarter. The next version of Claude will be better than this one. But it will still need to know your architecture, your patterns, your gotchas, and your workflows. The projects that have this documented -- structured, scoped, and progressive -- will get disproportionately more value from every model upgrade.

The investment in structure compounds. The investment in prompts does not.

Build the repo. Claude will figure out the rest.


Quick-Start Checklist

If you are starting from zero, do these five things in order:

  1. Create CLAUDE.md at your project root. Include: what the project does (3-5 lines), where the code lives (functional map, not full tree), how to build/test/deploy (exact commands), and the top 5 things that break non-obviously. Keep it under 200 lines.
  1. Create .claude/rules/ for file-type-specific patterns. Start with one file for your most opinionated area (API routes, component patterns, database queries). Use paths frontmatter to scope it.
  1. Create one skill for your most repeated workflow. Code review, new module scaffolding, or deployment -- whatever you explain to Claude most often. Put it in .claude/skills/your-workflow/SKILL.md.
  1. Add one hook to .claude/settings.json. Start with a PostToolUse hook that runs your linter or syntax checker after every edit. This one hook will prevent more bugs than anything else on this list.
  1. Let auto-memory work. Don't fight it. When Claude gets something wrong, correct it once -- it will write the correction to memory and never make that mistake again. Check ~/.claude/projects/project-name/memory/MEMORY.md occasionally to make sure it is accurate.

That is it. Five files, maybe an hour of work. The return is every Claude Code session from now on being materially better.


I build Bureau -- a multi-agent AI orchestration platform with 20+ specialized agents across 4 business verticals. Built entirely with Claude Code. The CLAUDE.md alone has saved more engineering hours than any single feature I have shipped.*

Claude CodeCLAUDE.mdAI coding assistantClaude Code project structure

Ready to Transform Your Development?

Let's discuss how AI-first development can accelerate your next project.

Book a Consultation

© 2026 Clarvia AI Services. All rights reserved.

clarvia

Cookie Preferences

We use cookies to enhance your experience. By continuing, you agree to our use of cookies.