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
- Session start: Only skill descriptions loaded (budget: ~1% of context window, minimum 8000 chars)
- On invocation: Full skill content loaded
- 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: forkrun 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/resumesInstructionsLoaded— CLAUDE.md or rules loadedUserPromptSubmit— User submits prompt (before processing)
Tool lifecycle:
PreToolUse— Before tool execution (can block)PermissionRequest— Permission dialog about to showPostToolUse— After tool succeedsPostToolUseFailure— After tool fails
Session:
Stop— Claude finishes respondingPreCompact— Before context compactionPostCompact— After compaction
Other:
Notification— Waiting for input/permissionSubagentStart/SubagentStop— Agent lifecycleFileChanged— Watched file changesConfigChange— 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 0or"permissionDecision": "allow"— Allow the toolexit 2or"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):
- Managed policy (highest, cannot override)
- Project local (
.claude/settings.local.json) - Project shared (
.claude/settings.json) - User (
~/.claude/settings.json) - Plugin (
<plugin>/hooks/hooks.json) - 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 callPostToolUse— fires after every tool call- Use
commandtype +iffield to minimize overhead
Low-frequency hooks (run rarely — can be heavier):
SessionStart— once per sessionStop— once per responsePreCompact/PostCompact— on compaction events
Optimization:
- Use
iffield to skip hook process on non-matching arguments - Use
commandtype (fast) overagenttype (slow, uses model tokens) - Mark expensive hooks
"async": trueto not block
5. Context Window Management
What Gets Loaded and When
At session start (always in context):
- System prompt (~4,200 tokens)
- Auto memory — first 200 lines or 25KB of
MEMORY.md - Environment info (CWD, platform, git status, recent commits)
- User CLAUDE.md
- Project CLAUDE.md
- Unconditional rules (
.claude/rules/withoutpaths:) - Skill descriptions only (~1% of context window budget)
- 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
- Path-scoped rules — Instructions only load when relevant files are accessed
- Skills with
disable-model-invocation: true— No description in context until user invokes - Subagents for exploration — Heavy file reads happen in a separate context window; only the summary returns
- Targeted reads — Read specific file + line range instead of full files
- PostCompact hooks — Re-inject critical reminders after compaction
- 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:
- Managed (enterprise IT, cannot be overridden)
- CLI arguments (temporary session overrides)
- Local project (
.claude/settings.local.json) - Shared project (
.claude/settings.json) - 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.mdalways in context (workspace build commands) commits.mdalways in context (applies to all code)patterns.mdloads only when editing Python sourcepackages/frontend/CLAUDE.mdloads 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:
- Project
CLAUDE.mdloads — they learn build commands, architecture, coding standards - Shared rules load — commit conventions, code style enforced
- Shared permissions activate — safe commands pre-approved, dangerous ones blocked
- Hooks engage — auto-formatting on edit, quality checks on stop
- Skills available —
/deploy,/review-prready to use - 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:
/handoffsaves 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) |