codeflash-agent/docs/context-engineering-guide.md
2026-04-03 17:36:50 -05:00

38 KiB

Context Engineering for Claude Code Projects

A comprehensive guide to structuring CLAUDE.md files, rules, skills, hooks, and configuration for effective Claude Code projects. Sourced from official Claude Code documentation.


1. CLAUDE.md Architecture

Discovery & Loading

Claude Code walks up the directory tree from the current working directory, loading every CLAUDE.md and CLAUDE.local.md it finds:

~/.claude/CLAUDE.md                    # User scope (all projects)
/path/to/project/CLAUDE.md            # Project scope (team-shared)
/path/to/project/.claude/CLAUDE.md    # Alternative project location
/path/to/project/CLAUDE.local.md      # Local overrides (gitignored)
packages/foo/CLAUDE.md                 # Subdirectory (lazy-loaded)

Loading order (all files concatenate — they don't override):

Priority Scope File When Loaded
1 (highest) Managed /Library/Application Support/ClaudeCode/CLAUDE.md Session start, cannot exclude
2 User ~/.claude/CLAUDE.md Session start
3 Project ./CLAUDE.md or ./.claude/CLAUDE.md Session start
4 Local ./CLAUDE.local.md Session start, appended after project
5 Subdirectory subdir/CLAUDE.md Lazy — when Claude reads files in that directory

Key behavior: All files are concatenated in full, not merged or replaced. When instructions conflict, Claude may pick one arbitrarily. There is no explicit override mechanism.

What Belongs at Each Level

Level Content Example
User (~/.claude/CLAUDE.md) Personal preferences across all projects Work style, response format, git workflow preferences
Project (./CLAUDE.md) Team-shared standards, build commands, architecture prek run --all-files, module structure, coding standards
Local (./CLAUDE.local.md) Machine-specific settings, gitignored Sandbox URLs, local test data, personal overrides
Subdirectory Package/module-specific rules packages/frontend/CLAUDE.md for React conventions

File Imports

CLAUDE.md supports importing other files:

See @README.md for project overview.
Git workflow: @docs/git-instructions.md
  • Paths resolve relative to the importing file
  • Absolute paths supported: @~/shared/instructions.md
  • Recursive imports up to 5 levels deep
  • First external import triggers approval dialog

What Makes Instructions Stick

Do:

  • Be specific and concrete: "Use 2-space indentation" not "format code properly"
  • Use structured markdown (headers, bullets) — Claude scans structure like a reader
  • Include exact commands: "Run npm test before committing"
  • Keep each file under 200 lines — longer files reduce adherence

Don't:

  • Write vague instructions ("keep code clean")
  • Contradict instructions across files
  • Write dense prose paragraphs
  • Put critical instructions only in conversation (lost after compaction)

CLAUDE.md Survives Compaction

CLAUDE.md is re-read from disk after /compact. Instructions in CLAUDE.md persist across sessions and compaction. Instructions given only in conversation do not.

Monorepo Exclusions

Skip irrelevant CLAUDE.md files with claudeMdExcludes in .claude/settings.local.json:

{
  "claudeMdExcludes": [
    "**/irrelevant-package/CLAUDE.md"
  ]
}

2. Rules System (.claude/rules/)

File Format

Rules are markdown files with optional YAML frontmatter in .claude/rules/:

---
paths:
  - "src/api/**/*.ts"
  - "src/**/*.{ts,tsx}"
---

# API Development Rules

- All endpoints must include input validation
- Use standard error response format

Path Scoping

Without paths: — Rule loads at session start, applies to all files (same cost as CLAUDE.md).

With paths: — Rule loads lazily when Claude reads files matching the patterns. Zero context cost until triggered.

Supported glob patterns:

Pattern Matches
**/*.ts All TypeScript files in any directory
src/**/* Everything under src/
*.md Markdown files in the directory only
src/**/*.{ts,tsx} Brace expansion for multiple extensions
tests/**/*.test.ts Specific naming patterns

Wildcards: * (anything except /), ** (across directories), ? (single char), [abc] (character class), {a,b} (alternation).

Rules vs CLAUDE.md

Aspect Rules CLAUDE.md
Location .claude/rules/*.md ./CLAUDE.md, ~/.claude/CLAUDE.md
Path scoping Yes (paths: frontmatter) No
Lazy loading Yes (path-scoped rules) No (always at startup)
Organization Multiple modular files Single file (or imports)
Context cost Zero until triggered (if path-scoped) Always costs tokens
Use case File-type or directory-specific rules Universal project standards

Priority: Rules and CLAUDE.md at the same scope level have equal priority. All are concatenated as context.

Organizing Rules for Monorepos

project/
├── .claude/rules/
│   ├── commits.md              # Unconditional — always loaded
│   └── testing.md              # Unconditional — always loaded
├── packages/
│   └── .claude/rules/
│       ├── patterns.md         # paths: */src/**/*.py — lazy
│       ├── philosophy.md       # paths: */src/**/*.py — lazy
│       └── uv.md              # paths: */pyproject.toml — lazy

Rules in nested .claude/rules/ directories are discovered when Claude's working context includes that subtree. Path-scoped rules within only trigger when matching files are accessed.

InstructionsLoaded Hook

Track when rules/CLAUDE.md load with the InstructionsLoaded event:

{
  "InstructionsLoaded": [{
    "matcher": "path_glob_match",
    "hooks": [{
      "type": "command",
      "command": "echo 'Rule loaded: $INSTRUCTION_FILE'"
    }]
  }]
}

Load reasons: session_start, nested_traversal, path_glob_match, include, compact.


3. Skills Design

SKILL.md Frontmatter Schema

---
name: my-skill                        # Defaults to directory name
description: What this skill does     # Keywords help auto-invocation
argument-hint: [issue-number]         # Autocomplete hint for user
paths:                                # Glob patterns for auto-activation
  - "src/api/**/*.ts"
  - "tests/**"
user-invocable: true                  # Show in /menu (default: true)
disable-model-invocation: false       # Prevent Claude auto-invoke (default: false)
allowed-tools:                        # Restrict available tools
  - Read
  - Grep
  - Bash(git:*)
model: claude-sonnet-4-6              # Override session model
effort: medium                        # Override session effort
context: fork                         # Run in forked subagent
agent: Explore                        # Subagent type
shell: bash                           # bash (default) or powershell
---

Path Scoping

When paths is set, the skill activates automatically only when working with files matching the patterns:

paths:
  - "src/api/**/*.ts"      # API routes
  - "src/handlers/**/*.ts"  # Request handlers

Without paths: Skill applies to all files.

Monorepo pattern: Nest .claude/skills/ per package. Claude auto-discovers from current directory and parents:

packages/frontend/.claude/skills/react-patterns/SKILL.md
packages/backend/.claude/skills/api-handler/SKILL.md

Invocation Control Matrix

user-invocable disable-model-invocation /menu? Claude auto-invokes? Use case
true (default) false (default) Yes Yes Standard skill
true true Yes No Side-effect workflows (deploy, commit)
false false No Yes Background knowledge
false true No No Not useful

When disable-model-invocation: true:

  • Skill description is NOT loaded into context
  • Full content loads only when user manually invokes with /name
  • Use for: deployments, commits, side-effect workflows where timing is critical

When user-invocable: false:

  • Description IS always in context (Claude knows about it)
  • Does NOT appear in / menu
  • Claude can invoke automatically when relevant
  • Use for: background knowledge, legacy system context, reference material

Progressive Disclosure

  1. Session start: Only skill descriptions loaded (budget: ~1% of context window, minimum 8000 chars)
  2. On invocation: Full skill content loaded
  3. Supporting files: Reference from SKILL.md for on-demand loading
For complete API details, see [reference.md](reference.md)
For examples, see [examples.md](examples.md)

Descriptions are truncated at 250 chars in listings. Write descriptions that front-load keywords.

Allowed-Tools Restrictions

Restrict what tools are available when a skill is active:

allowed-tools:
  - Read
  - Grep
  - Bash(git:*)         # Only git commands

Formats:

  • Single: allowed-tools: Read
  • Comma-separated: allowed-tools: Read, Write, Edit
  • YAML list with patterns: Bash(npm:*), Bash(docker:*)
  • MCP tools: mcp__github__search_repositories

Dynamic Content in Skills

Inject command output into skill content with !`command`:

Current PR diff: !`gh pr diff`
PR comments: !`gh pr view --comments`

String substitutions:

Variable Description
$ARGUMENTS All arguments passed to skill
$0, $1, ... Specific arguments by index
${CLAUDE_SESSION_ID} Current session ID
${CLAUDE_SKILL_DIR} Directory containing SKILL.md

Skills Interaction with Rules and CLAUDE.md

  • CLAUDE.md and rules are in context before any skill loads
  • When a skill is invoked, its content is added to existing context
  • Skills cannot override CLAUDE.md or rules — everything is additive
  • Skills with context: fork run in a subagent that gets its own copy of CLAUDE.md + preloaded skills
  • Scope precedence: Enterprise > Personal > Project > Plugin

4. Hooks as Guardrails

Hook Events Reference

Pre-execution:

  • SessionStart — Session begins/resumes
  • InstructionsLoaded — CLAUDE.md or rules loaded
  • UserPromptSubmit — User submits prompt (before processing)

Tool lifecycle:

  • PreToolUse — Before tool execution (can block)
  • PermissionRequest — Permission dialog about to show
  • PostToolUse — After tool succeeds
  • PostToolUseFailure — After tool fails

Session:

  • Stop — Claude finishes responding
  • PreCompact — Before context compaction
  • PostCompact — After compaction

Other:

  • Notification — Waiting for input/permission
  • SubagentStart / SubagentStop — Agent lifecycle
  • FileChanged — Watched file changes
  • ConfigChange — Settings/skills file changes

Hook Types — Decision Framework

Type Best for Timeout Cost
command Deterministic shell operations, fast checks 10-30s Low (no LLM)
prompt Yes/no decisions based on hook input data 30s Medium (single LLM call)
agent Verification requiring file reads or commands 60s High (LLM + tools)
http External service logging, team audit 10s Network latency

PreToolUse — Blocking Dangerous Actions

{
  "PreToolUse": [{
    "matcher": "Bash",
    "hooks": [{
      "type": "command",
      "command": "bash .claude/hooks/validate-bash.sh",
      "timeout": 10
    }]
  }]
}

Decision responses:

  • exit 0 or "permissionDecision": "allow" — Allow the tool
  • exit 2 or "permissionDecision": "deny" — Block with reason
  • "permissionDecision": "ask" — Show permission prompt normally

Important: Hook returning "allow" does NOT override permission deny rules. Hooks can tighten restrictions but not loosen past what permission rules allow.

Rewriting tool input:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "updatedInput": { "command": "modified-command" }
  }
}

PostToolUse — Auto-formatting and Logging

{
  "PostToolUse": [{
    "matcher": "Edit|Write",
    "hooks": [{
      "type": "command",
      "command": "prettier --write $TOOL_INPUT_FILE_PATH",
      "timeout": 30
    }]
  }]
}

Cannot undo the action (already executed), but can inject context or block further work.

Stop — Task Completion Verification

{
  "Stop": [{
    "matcher": "",
    "hooks": [{
      "type": "command",
      "command": ".claude/hooks/check-complete.sh",
      "timeout": 45
    }]
  }]
}

Anti-loop pattern (critical — Stop fires on every response):

#!/bin/bash
INPUT=$(cat)

# Check if this hook already triggered to avoid infinite loops
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0  # Allow stop — already verified once
fi

# Your verification logic
if ! all_tasks_done; then
  echo "Tasks X and Y still incomplete" >&2
  exit 2  # Block stop
fi

exit 0  # Allow stop

PermissionRequest — Auto-approving Safe Patterns

{
  "PermissionRequest": [{
    "matcher": "Read",
    "hooks": [{
      "type": "command",
      "command": "echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PermissionRequest\",\"decision\":{\"behavior\":\"allow\"}}}'"
    }]
  }]
}

Keep matchers narrow. An empty matcher auto-approves everything (dangerous).

The if Field — Argument-Level Filtering

Finer than matcher (which only matches tool name). The if field matches tool arguments:

{
  "PreToolUse": [{
    "matcher": "Bash",
    "hooks": [{
      "type": "command",
      "if": "Bash(git *)",
      "command": "check-git-policy.sh"
    }]
  }]
}

When if doesn't match, the hook process doesn't spawn (zero overhead). Uses permission rule syntax: Bash(git:*), Edit(*.ts), etc.

PreCompact / PostCompact — Preserving Context

{
  "PostCompact": [{
    "matcher": "auto",
    "hooks": [{
      "type": "command",
      "command": "echo 'Reminder: use uv, not pip. Current task: refactor auth.'"
    }]
  }]
}

PostCompact output goes directly to Claude's context after compaction. Use this to re-inject critical reminders that might be lost.

Settings Hierarchy for Hooks

Hooks merge across scopes (all matching hooks run):

  1. Managed policy (highest, cannot override)
  2. Project local (.claude/settings.local.json)
  3. Project shared (.claude/settings.json)
  4. User (~/.claude/settings.json)
  5. Plugin (<plugin>/hooks/hooks.json)
  6. Skill/agent frontmatter (while active)

When multiple scopes define hooks for the same event, all hooks run. For conflicting decisions, most restrictive wins (deny > ask > allow).

Performance Considerations

High-frequency hooks (run often — keep fast):

  • PreToolUse — fires before every tool call
  • PostToolUse — fires after every tool call
  • Use command type + if field to minimize overhead

Low-frequency hooks (run rarely — can be heavier):

  • SessionStart — once per session
  • Stop — once per response
  • PreCompact / PostCompact — on compaction events

Optimization:

  • Use if field to skip hook process on non-matching arguments
  • Use command type (fast) over agent type (slow, uses model tokens)
  • Mark expensive hooks "async": true to not block

5. Context Window Management

What Gets Loaded and When

At session start (always in context):

  1. System prompt (~4,200 tokens)
  2. Auto memory — first 200 lines or 25KB of MEMORY.md
  3. Environment info (CWD, platform, git status, recent commits)
  4. User CLAUDE.md
  5. Project CLAUDE.md
  6. Unconditional rules (.claude/rules/ without paths:)
  7. Skill descriptions only (~1% of context window budget)
  8. MCP tool names (schemas loaded on-demand)

During session (lazy-loaded):

  • Path-scoped rules — when matching files are read
  • Subdirectory CLAUDE.md — when accessing files in that directory
  • Full skill content — when a skill is invoked
  • MCP tool schemas — when Claude considers using a tool

Token Budget Awareness

Component Approximate Cost When
System prompt ~4,200 tokens Always
Auto memory Variable (capped 25KB) Always
Environment ~280 tokens Always
CLAUDE.md (typical) 500-2,000 tokens Always
Each unconditional rule 200-400 tokens Always
Skill descriptions (all) ~450 tokens total Always
Each path-scoped rule 200-400 tokens When triggered
Full skill content Variable When invoked

Strategy: Move instructions into path-scoped rules to defer their context cost until relevant files are accessed.

Compaction Behavior

When context fills up, Claude Code compacts:

Preserved: System prompt, CLAUDE.md (re-read from disk), auto memory, rules, your intent, key decisions Dropped: Verbatim conversation, full tool outputs, intermediate reasoning Lost: Skill descriptions (only invoked skills survive)

Strategies for Clean Context

  1. Path-scoped rules — Instructions only load when relevant files are accessed
  2. Skills with disable-model-invocation: true — No description in context until user invokes
  3. Subagents for exploration — Heavy file reads happen in a separate context window; only the summary returns
  4. Targeted reads — Read specific file + line range instead of full files
  5. PostCompact hooks — Re-inject critical reminders after compaction
  6. CLAUDE.md imports — Keep main file concise, import details from supporting files

6. Project Configuration Patterns

.claude/settings.json — Shared Team Config

{
  "permissions": {
    "allow": [
      "Bash(prek *)",
      "Bash(uv run pytest *)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git log *)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./secrets/**)"
    ]
  },
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "ruff format --quiet $TOOL_INPUT_FILE_PATH",
        "timeout": 10
      }]
    }]
  },
  "env": {
    "UV_PYTHON": "3.12"
  }
}

Commit this to git. Team members get shared permissions, hooks, and environment.

.claude/settings.local.json — Personal Overrides

{
  "permissions": {
    "allow": ["Bash(docker *)"]
  },
  "model": "claude-opus-4-6"
}

Add to .gitignore. Personal preferences that don't affect the team.

.mcp.json — MCP Server Configuration

{
  "mcpServers": {
    "github": {
      "command": "node",
      "args": ["path/to/github-server.js"],
      "type": "stdio"
    },
    "postgres": {
      "url": "http://localhost:3000/mcp",
      "type": "http"
    }
  }
}

Lives at project root (committed) or ~/.claude.json (personal).

Settings Precedence

From highest to lowest:

  1. Managed (enterprise IT, cannot be overridden)
  2. CLI arguments (temporary session overrides)
  3. Local project (.claude/settings.local.json)
  4. Shared project (.claude/settings.json)
  5. User (~/.claude/settings.json)

Array settings (like permissions.allow) merge across scopes (concatenate + deduplicate), not replace.


7. Real-World Patterns

Monorepo Setup

monorepo/
├── CLAUDE.md                          # Workspace-wide: build commands, architecture
├── .claude/
│   ├── settings.json                  # Shared permissions and hooks
│   ├── rules/
│   │   └── commits.md                 # Unconditional: commit conventions
│   └── skills/
│       └── deploy/SKILL.md            # Manual-only deployment skill
├── packages/
│   ├── .claude/
│   │   └── rules/
│   │       ├── patterns.md            # paths: */src/**/*.py
│   │       └── uv.md                  # paths: */pyproject.toml
│   ├── frontend/
│   │   ├── CLAUDE.md                  # React conventions (lazy-loaded)
│   │   └── .claude/skills/
│   │       └── react-patterns/SKILL.md
│   └── backend/
│       ├── CLAUDE.md                  # API conventions (lazy-loaded)
│       └── .claude/skills/
│           └── api-handler/SKILL.md

How it works:

  • Root CLAUDE.md always in context (workspace build commands)
  • commits.md always in context (applies to all code)
  • patterns.md loads only when editing Python source
  • packages/frontend/CLAUDE.md loads when Claude reads frontend files
  • React skill available only when working in frontend package

CI/CD Quality Gates via Hooks

.claude/settings.json:

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "ruff check --fix $TOOL_INPUT_FILE_PATH && ruff format $TOOL_INPUT_FILE_PATH",
        "timeout": 15
      }]
    }],
    "Stop": [{
      "matcher": "",
      "hooks": [{
        "type": "agent",
        "prompt": "Check if code was modified in this session. If so, verify tests pass by running: uv run pytest packages/ -v"
      }]
    }]
  }
}

What to Commit vs. Keep Local

Commit Gitignore
.claude/settings.json .claude/settings.local.json
.claude/rules/ CLAUDE.local.md
.claude/skills/ Personal MCP configs
.claude/hooks/ (scripts) API keys / tokens
CLAUDE.md .mcp.json (if contains secrets)
.mcp.json (if no secrets)

Onboarding Pattern

A new developer clones the repo and runs claude. Automatically:

  1. Project CLAUDE.md loads — they learn build commands, architecture, coding standards
  2. Shared rules load — commit conventions, code style enforced
  3. Shared permissions activate — safe commands pre-approved, dangerous ones blocked
  4. Hooks engage — auto-formatting on edit, quality checks on stop
  5. Skills available — /deploy, /review-pr ready to use
  6. MCP servers connect — project-specific tools available

No manual setup required. Everything is in the committed .claude/ directory.


Interaction Map

How the pieces compose:

┌─────────────────────────────────────────────────────┐
│ Context Window                                       │
│                                                      │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────┐ │
│  │ CLAUDE.md   │  │ Rules        │  │ Skills     │ │
│  │ (always)    │  │ (lazy/eager) │  │ (on-demand)│ │
│  └─────────────┘  └──────────────┘  └────────────┘ │
│                                                      │
│  All provide context (soft guidance)                 │
│  None enforce behavior (use hooks/settings for that) │
└─────────────────────────────────────────────────────┘
          │                    │
          ▼                    ▼
┌──────────────────┐  ┌──────────────────┐
│ settings.json    │  │ hooks            │
│ (hard enforce)   │  │ (hard enforce)   │
│                  │  │                  │
│ • permissions    │  │ • PreToolUse     │
│ • deny rules     │  │   (block actions)│
│ • sandbox        │  │ • PostToolUse    │
│                  │  │   (auto-format)  │
│ Cannot be        │  │ • Stop           │
│ overridden by    │  │   (verify tasks) │
│ CLAUDE.md or     │  │                  │
│ conversation     │  │ Exit codes and   │
│                  │  │ decisions are    │
│                  │  │ enforced         │
└──────────────────┘  └──────────────────┘

Key principle: CLAUDE.md, rules, and skills are context (soft guidance — Claude reads and usually follows, but no guarantee). Settings and hooks are configuration (hard enforcement — permissions block tools, hooks can deny actions regardless of Claude's intent).

For critical constraints, don't rely on CLAUDE.md alone. Use permissions.deny in settings.json or PreToolUse hooks for hard enforcement.


8. Custom Agents (.claude/agents/)

File Format

Agents are markdown files with YAML frontmatter. Unlike skills (which are directories), agents are single .md files:

.claude/agents/
├── code-reviewer.md
├── test-writer.md
└── researcher.md

Frontmatter Schema

---
name: code-reviewer
description: |
  Use this agent when the user asks for code review. Examples:

  <example>
  Context: User wants feedback on a PR
  user: "Review this PR"
  assistant: "I'll use the code-reviewer agent."
  <commentary>
  Code review request triggers this agent.
  </commentary>
  </example>  

model: inherit                    # sonnet, opus, haiku, inherit, or full model ID
color: blue                       # red, blue, green, yellow, purple, orange, pink, cyan
tools: ["Read", "Grep", "Glob"]  # Restrict available tools (inherit all if omitted)
disallowedTools: ["Write"]        # Deny specific tools
permissionMode: default           # default, acceptEdits, auto, dontAsk, bypassPermissions, plan
maxTurns: 50                      # Max agentic turns before stopping
skills: ["my-skill"]              # Skills injected into agent context at startup
mcpServers: {}                    # MCP servers scoped to this agent
hooks: {}                         # Hooks during agent lifecycle
memory: project                   # user, project, or local — persistent memory scope
background: false                 # Always run as background task
effort: medium                    # low, medium, high, max
isolation: worktree               # Git-isolated execution
initialPrompt: "Start analysis"  # Auto-submitted first turn
---

You are a code reviewer. Your core responsibilities:
1. Check for bugs and edge cases
2. Verify test coverage
3. Review naming and documentation

The markdown body below the frontmatter becomes the agent's system prompt.

Agents vs Skills

Aspect Agent Skill
Context Separate context window Inline in main thread
System prompt Custom per agent None (injected into session)
File format Single .md file Directory with SKILL.md + supporting files
Tool restrictions Per agent via tools field Per skill via allowed-tools
Worktree isolation Yes (isolation: worktree) No
Path scoping No Yes (paths: frontmatter)
Invocation Auto-delegation or /agents menu /skill-name or auto
Supporting files No — use skills field instead Yes (reference.md, scripts/, etc.)

Discovery

  • Auto-discovered from .claude/agents/ at session start
  • Scope priority: Managed > CLI > Project > User > Plugin
  • Plugin agents use namespace: plugin-name:agent-name
  • Plugin agents cannot define hooks or mcpServers (security restriction)

When to Use Agents vs Skills

Use agents when:

  • Task needs a separate context window (heavy exploration, large codebases)
  • You want tool restrictions (read-only agent, no-bash agent)
  • Task benefits from worktree isolation
  • You need a custom system prompt that overrides default behavior

Use skills when:

  • Task runs inline in the main conversation
  • You need path scoping (activate only for certain files)
  • You have supporting reference files
  • You want progressive disclosure (description → full content)

9. Commands (.claude/commands/)

Format

Commands are single markdown files — the simpler predecessor to skills:

.claude/commands/
├── review-pr.md
└── run-tests.md

Same frontmatter as skills (description, allowed-tools, disable-model-invocation, etc.) but no supporting files — everything in one .md file.

Commands vs Skills

Aspect Command Skill
Structure Single .md file Directory with SKILL.md + supporting files
Supporting files No Yes (reference.md, scripts/, templates/)
Progressive disclosure No (full content on invoke) Yes (description → full content)
${CLAUDE_SKILL_DIR} Not available Available
Dynamic injection !`command` works !`command` works

Commands are NOT deprecated but skills are recommended for new work. Both create /name shortcuts identically.


10. Plugin Structure

Directory Layout

my-plugin/
├── .claude-plugin/
│   ├── plugin.json            # Manifest (optional but recommended)
│   └── marketplace.json       # Multi-plugin marketplace config
├── agents/                    # Subagent definitions
│   └── reviewer.md
├── skills/                    # Skills with supporting files
│   └── optimize/
│       ├── SKILL.md
│       └── references/
│           └── patterns.md
├── commands/                  # Legacy commands
│   └── deploy.md
├── hooks/
│   └── hooks.json             # Plugin hook definitions
├── references/                # Shared reference material
│   └── shared/
│       └── conventions.md
├── .mcp.json                  # Plugin MCP servers
├── bin/                       # Executables (added to PATH)
├── output-styles/             # Custom output styles
└── settings.json              # Default plugin settings

plugin.json Schema

{
  "name": "my-plugin",
  "version": "1.0.0",
  "description": "What this plugin does",
  "author": {"name": "Team", "email": "team@example.com"},
  "commands": ["./custom/cmd.md"],
  "agents": "./custom/agents/",
  "skills": "./custom/skills/",
  "hooks": "./hooks.json",
  "mcpServers": "./mcp.json",
  "outputStyles": "./styles/",
  "lspServers": "./.lsp.json",
  "userConfig": {
    "api_key": {
      "description": "Your API key",
      "sensitive": true
    }
  }
}

Plugin Variables

Variable Resolves To Use For
${CLAUDE_PLUGIN_ROOT} Plugin installation directory Ephemeral references (scripts, hooks, config)
${CLAUDE_PLUGIN_DATA} ~/.claude/plugins/data/{plugin-id}/ Persistent data (caches, installed deps)
${CLAUDE_PROJECT_DIR} Project root directory Accessing project files from hooks

CLAUDE_PLUGIN_ROOT changes on plugin updates. CLAUDE_PLUGIN_DATA persists across updates.

Plugin Hooks vs Project Hooks

  • Plugin hooks run only when the plugin is enabled
  • Project hooks always run
  • Both execute in parallel for the same event
  • Most restrictive decision wins (deny > ask > allow)
  • Plugin hooks are defined in hooks/hooks.json (not in settings.json)

References in Plugins

Plugins can bundle reference material that agents/skills access via ${CLAUDE_PLUGIN_ROOT}:

<!-- In an agent .md file -->
Read the conventions at ${CLAUDE_PLUGIN_ROOT}/references/shared/conventions.md

Reference files are not auto-loaded — they're read on-demand when an agent or skill needs them. This keeps them out of context until relevant.


11. Memory System

Auto Memory

~/.claude/projects/<project-id>/memory/
├── MEMORY.md              # Index file (required)
├── debugging.md           # Topic files (auto-created)
├── architecture.md
└── decisions.md

Loading: First 200 lines OR 25KB of MEMORY.md loaded at session start. Topic files read on-demand.

Storage: Project-scoped by git repo path. All worktrees in same repo share one memory directory. Machine-local (not shared across machines).

Configuration:

{
  "autoMemoryEnabled": true,
  "autoMemoryDirectory": "~/.claude/projects/<id>/memory/"
}

Toggle with /memory command or CLAUDE_CODE_DISABLE_AUTO_MEMORY=1.

How Memory Interacts with Other Features

  • MEMORY.md is separate from CLAUDE.md — both load at startup
  • CLAUDE.md is deterministic (you control content); memory is Claude-managed
  • Both survive compaction (re-read from disk)
  • Memory is per-machine; CLAUDE.md is shared via git

Agent Memory

Agents can have their own persistent memory via the memory frontmatter field:

---
name: researcher
memory: project    # user, project, or local
---

Stored in .claude/agent-memory/<agent-name>/MEMORY.md.


12. References & Supporting Files

In Skills

Skills can bundle arbitrary supporting files alongside SKILL.md:

my-skill/
├── SKILL.md                # Entry point (required)
├── reference.md            # Detailed API docs
├── examples.md             # Usage examples
├── scripts/
│   ├── validate.sh         # Executable scripts
│   └── helper.py
└── templates/
    └── pr-template.md      # Templates

Loading behavior: Supporting files are NOT auto-loaded. Claude reads them on-demand when SKILL.md references them:

For complete patterns, see [reference.md](reference.md)
Run validation: !`bash ${CLAUDE_SKILL_DIR}/scripts/validate.sh`

This is the key progressive disclosure mechanism — SKILL.md is concise, details live in supporting files.

In Plugins

Plugins use a references/ directory for shared material accessible by all agents/skills in the plugin:

plugin/
├── references/
│   ├── shared/
│   │   ├── conventions.md
│   │   └── pr-preparation.md
│   ├── async/
│   │   └── guide.md
│   └── memory/
│       └── guide.md
├── agents/
│   └── optimizer.md        # References: ${CLAUDE_PLUGIN_ROOT}/references/shared/conventions.md
└── skills/
    └── optimize/SKILL.md   # References: ${CLAUDE_PLUGIN_ROOT}/references/async/guide.md

Referenced via ${CLAUDE_PLUGIN_ROOT}/references/... in agent/skill content. Not auto-loaded — read on-demand.

In CLAUDE.md (@ Imports)

# Project Guide

@docs/architecture.md
@docs/api-reference.md

## Quick Start
...

Imported files expand at session start (not lazy). Recursive up to 5 levels.


13. Other Features

Handoffs (.claude/handoffs/)

Session continuity mechanism:

  • /handoff saves current session state to .claude/handoffs/latest.md
  • New session can restore context from handoff
  • Gitignored — session-specific, not shared

.worktreeinclude

Lists gitignored files that should be copied into git worktrees:

.env
.env.local
config/secrets.json

Syntax follows .gitignore patterns. Ensures worktree-isolated agents have access to necessary config files.

Additional Directories (--add-dir)

claude --add-dir ../shared-lib

What loads from --add-dir:

  • .claude/skills/ — auto-discovered
  • Files — read access

What does NOT load:

  • CLAUDE.md (unless CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1)
  • Agents, commands, hooks, MCP servers, output styles

Output Styles

Custom response formatting in ~/.claude/output-styles/ or .claude/output-styles/:

---
description: Concise teaching style
keep-coding-instructions: true
---

Be concise. Lead with code, follow with brief explanation.
Use bullet points. No preamble.

Selected via /config or outputStyle in settings.json.

Environment Variables in Hooks/Skills

Variable Available In Purpose
${CLAUDE_SESSION_ID} Skills, hooks Current session ID
${CLAUDE_SKILL_DIR} Skills only Skill directory path
${CLAUDE_PROJECT_DIR} Hooks, skills Project root
${CLAUDE_PLUGIN_ROOT} Plugin content Plugin install dir
${CLAUDE_PLUGIN_DATA} Plugin content Persistent plugin data
${CLAUDE_ENV_FILE} Hooks only Write env vars that persist across tool calls
$ARGUMENTS, $0, $1 Skills Skill invocation arguments

claudeMdExcludes

Skip specific CLAUDE.md files in monorepos:

{
  "claudeMdExcludes": [
    "**/node_modules/**/CLAUDE.md",
    "vendor/**/CLAUDE.md"
  ]
}

Glob patterns matched against absolute paths. Managed CLAUDE.md cannot be excluded.


Complete Discovery & Loading Sequence

When Claude Code starts a session:

1. Load managed settings + managed CLAUDE.md (cannot exclude)
2. Walk up directory tree from CWD:
   └─ Load CLAUDE.md + CLAUDE.local.md at each level
3. Discover .claude/rules/*.md:
   ├─ Unconditional rules → load immediately
   └─ Path-scoped rules → register for lazy loading
4. Load auto memory (first 200 lines / 25KB of MEMORY.md)
5. Enumerate skills (descriptions only, ~1% context budget)
6. Enumerate agents (descriptions for delegation)
7. Load MCP server configs (.mcp.json)
8. Register hooks from all scopes
9. Session begins

During session:
├─ Path-scoped rules fire when matching files accessed
├─ Subdirectory CLAUDE.md loads when Claude reads files there
├─ Full skill content loads on invocation
├─ MCP tool schemas load when Claude considers using a tool
├─ Hooks fire on their respective events
└─ Compaction re-reads CLAUDE.md, memory, rules from disk

Complete Feature Matrix

Feature Location Load Time Path Scoping Auto-discovered
CLAUDE.md Project root, ~/.claude/ Startup No Yes (walk up)
CLAUDE.local.md Project root Startup No Yes
Rules (unconditional) .claude/rules/ Startup No Yes (recursive)
Rules (path-scoped) .claude/rules/ On file access Yes (paths:) Yes (recursive)
Skills .claude/skills/*/ Description: startup; Full: on invoke Yes (paths:) Yes (nested)
Commands .claude/commands/ On invoke No Yes
Agents .claude/agents/ Description: startup; Full: on delegate No Yes
Hooks settings.json, plugin On event Via matcher + if No (configured)
Auto memory ~/.claude/projects/ Startup (25KB cap) No Auto-created
MCP servers .mcp.json Startup No Yes
Output styles .claude/output-styles/ Startup No Yes
Plugin refs plugin/references/ On demand No No (referenced)
Skill refs skill-dir/ files On demand No No (referenced)