Claude Code Harness 第19章:CLAUDE.md——用户指令作为覆盖层
Claude Code Harness 第19章:CLAUDE.md——用户指令作为覆盖层
系列说明:本文是《Claude Code Harness:从提示词到 AI 工程化实践》第19章,完整目录请参考系列导读。
在之前的章节中,我们探索了 Claude Code 的诸多核心机制:从工具定义(第3-5章)、权限控制(第6-7章)、会话管理(第8-10章)、到 Hooks 系统(第18章)。这些章节展现了一个共同的主题:通过代码执行扩展 Agent 行为。
本章将探讨另一个同样重要的维度:通过自然语言指令控制模型输出。如果说 Hooks 是"硬扩展"通道,那么 CLAUDE.md 就是"软控制"通道——它不是配置文件,而是一套完整的指令注入系统,具有四级优先级层叠、传递性文件包含、路径范围限定、HTML 注释剥离,以及明确的覆盖语义声明。
CLAUDE.md 的设计哲学可以用一句话概括:
用户的指令覆盖模型的默认行为。
这句话不是修辞——它被字面注入到系统提示词中。本章将从文件发现、内容处理、到最终注入提示词的完整链路,深入剖析这套系统的实现原理和最佳实践。
19.1 为什么 CLAUDE.md 至关重要
在深入技术细节之前,让我们先理解 CLAUDE.md 系统解决的核心问题。
问题背景
AI Agent 的行为受两方面控制:
- 硬编码逻辑:工具定义、权限系统、执行流程等代码层面的控制
- 提示词工程:通过自然语言指令引导模型输出
CLAUDE.md 系统关注的是第二方面。在实际开发中,不同层级的用户有不同的指令需求:
- 企业管理员:需要推送安全策略、合规要求
- 团队负责人:需要定义项目规范、技术栈约定
- 个人开发者:需要个性化设置、编程风格偏好
CLAUDE.md 通过分层覆盖架构满足这些需求,每一层都可以被下一层覆盖或补充。
与其他系统的关系
CLAUDE.md 系统与之前章节探讨的多个系统密切相关:
| 系统 | 控制维度 | 优先级机制 | 典型场景 |
|---|---|---|---|
| Hooks | 代码执行 | 前置/后置钩子 | 自动化测试、代码格式化 |
| Permissions | 工具访问 | 权限表 + 提示词注入 | 安全限制、资源保护 |
| CLAUDE.md | 自然语言指令 | 四级优先级层叠 | 代码规范、项目约定 |
Hooks 和 CLAUDE.md 并不冲突——它们共同构成用户的"驾驭层"(harness layer)。Hooks 通过代码执行自动化任务,CLAUDE.md 通过指令声明引导模型决策。
19.2 四级加载优先级架构
CLAUDE.md 系统采用四级优先级模型。文件按反向优先级顺序加载——最后加载的优先级最高,因为 LLM 对对话末尾的内容关注度更高(recency bias)。
优先级层级概览
flowchart TB
subgraph L1 ["Level 1: Managed Memory(最低优先级,最先加载)"]
M1["/etc/claude-code/CLAUDE.md
企业策略推送
适用于所有用户"]
end
subgraph L2 ["Level 2: User Memory"]
M2["~/.claude/CLAUDE.md
~/.claude/rules/*.md
用户私有全局指令
适用于所有项目"]
end
subgraph L3 ["Level 3: Project Memory"]
M3["CLAUDE.md, .claude/CLAUDE.md
.claude/rules/*.md
从项目根到 CWD 每层遍历
提交到 git,团队共享"]
end
subgraph L4 ["Level 4: Local Memory(最高优先级,最后加载)"]
M4["CLAUDE.local.md
已 gitignore
仅本地生效"]
end
L1 -->|"被覆盖"| L2 -->|"被覆盖"| L3 -->|"被覆盖"| L4
style L4 fill:#e6f3e6,stroke:#2d862d,stroke-width:3px
style L1 fill:#f3e6e6,stroke:#862d2d,stroke-width:1pxLevel 1:Managed Memory(企业策略)
路径:/etc/claude-code/CLAUDE.md
特点:
- 通过 MDM(Mobile Device Management)推送
- 适用于组织内的所有用户和所有项目
- 优先级最低——可被其他层级覆盖
- 企业 IT 部门用于强制执行安全策略、合规要求
典型用例:
# /etc/claude-code/CLAUDE.md
企业安全策略:
- 禁止在代码中硬编码密钥和密码
- 所有 API 调用必须通过企业网关
- 敏感数据必须加密存储
- 遵循 OWASP 安全最佳实践Level 2:User Memory(个人全局配置)
路径:
~/.claude/CLAUDE.md~/.claude/rules/*.md
特点:
- 用户私有配置,不提交到 git
- 适用于该用户的所有项目
- 优先级高于 Managed Memory
- 用于设置个人编程风格、偏好设置
特权机制:User Memory 层级的 @include 指令可以引用项目目录外的文件(includeExternal: true),这是其他层级不具备的权限。
典型用例:
# ~/.claude/CLAUDE.md
个人编码风格:
- 使用 2 空格缩进
- 偏好函数式编程风格
- 变量命名使用 camelCase
- 注释使用中文
工具链偏好:
- 优先使用 pnpm 而非 npm
- 测试框架使用 Vitest
- 代码检查工具:ESLint + PrettierLevel 3:Project Memory(项目级配置)
路径:
CLAUDE.md(项目根目录).claude/CLAUDE.md.claude/rules/*.md
特点:
- 从当前工作目录(CWD)向上遍历到文件系统根目录
- 每层目录的
CLAUDE.md和.claude/rules/*.md都会被加载 - 距离 CWD 越近的文件优先级越高
- 提交到 git,团队共享
遍历逻辑:
flowchart TD
A["开始:当前工作目录
/home/user/project/src/components"] --> B["向上遍历"]
B --> C["/home/user/project/src/components
加载 CLAUDE.md(如果存在)"]
C --> D["/home/user/project/src
加载 CLAUDE.md(如果存在)"]
D --> E["/home/user/project
加载 CLAUDE.md
加载 .claude/CLAUDE.md
加载 .claude/rules/*.md"]
E --> F["/home/user
加载 CLAUDE.md(如果存在)"]
F --> G["/home
加载 CLAUDE.md(如果存在)"]
G --> H["/(文件系统根目录)
停止遍历"]
style E fill:#e6f3ff,stroke:#0066cc
style H fill:#f0f0f0,stroke:#999999典型用例:
# /home/user/project/CLAUDE.md
项目技术栈:
- 前端框架:Next.js 14 + TypeScript
- 状态管理:Zustand
- 样式方案:Tailwind CSS
- 测试框架:Jest + React Testing Library
架构规范:
- 所有 API 调用必须通过 /services/api.ts 统一入口
- 组件按 feature 分组到 /features 目录
- 共享类型定义放在 /types 目录
- 环境变量使用 .env.local 管理Level 4:Local Memory(本地覆盖)
路径:CLAUDE.local.md(任意层级)
特点:
- 优先级最高,最后加载
- 已被
.gitignore排除,仅本地生效 - 用于临时覆盖、调试配置、个人工具链
典型用例:
# CLAUDE.local.md
本地开发配置:
- 启用详细日志输出
- 使用本地 Mock 数据而非真实 API
- 跳过时间密集型测试
个人调试工具:
- 优先使用 Bun 而非 Node.js
- 启用 Hot Module Replacement配置源开关
每一级(除 Managed 外)的加载都受配置源开关控制:
| 配置源 | 控制层级 | SDK 默认值 |
|---|---|---|
userSettings |
User Memory | false |
projectSettings |
Project Memory | false |
localSettings |
Local Memory | false |
SDK 模式下默认将 settingSources 设为空数组,意味着除非显式启用,否则只有 Managed Memory 生效。这是最小权限原则的体现——SDK 使用者需要明确授予配置权限。
19.3 @include 指令:模块化指令组织
CLAUDE.md 支持 @include 语法来引用其他文件,实现模块化的指令组织。这避免了单个巨型配置文件,提升了可维护性。
语法格式
@include 使用简洁的 @ 前缀加路径语法:
| 语法 | 含义 | 示例 |
|---|---|---|
@path |
相对于当前文件目录的路径 | @./docs/api-rules.md |
@./path |
同上(显式相对路径) | @./coding-standards.md |
@~/path |
相对于用户 home 目录 | @~/.claude/snippets/react.md |
@/absolute/path |
绝对路径 | @/usr/share/docs/standards.md |
@path#section |
带片段标识符(# 及之后被忽略) |
@docs.md#advanced |
@path\ with\ spaces |
反斜杠转义空格 | @./my\ file.md |
实际使用示例
# CLAUDE.md
## 项目概述
这是一个基于 Next.js 的全栈应用,遵循以下规范:
@./docs/coding-standards.md
@./docs/api-conventions.md
@./docs/testing-guide.md
## 全局规则
@~/.claude/rules/common-patterns.md被引用的文件(./docs/coding-standards.md):
# 编码规范
## TypeScript 规则
- 禁止使用 `any` 类型,使用 `unknown` 替代
- 所有函数必须有明确的返回类型注解
- 接口定义使用 `interface`,类型别名使用 `type`
## React 规则
- 组件使用函数式组件 + Hooks
- Props 必须定义接口
- 禁止在 JSX 中写复杂逻辑,提取到函数路径提取机制
路径提取不是简单的字符串匹配——它使用 marked lexer 预处理 token 流,确保以下规则:
- 代码块中的
@被忽略:代码示例中的@include不会被误解析 - HTML 注释中的
@被忽略:注释内的指令不生效 - 仅处理文本节点:递归进入嵌套结构(列表、引用等)
示例:
# 指令文件
@include config.md # ✅ 会被解析
```typescript
// 这是代码块,@include 不会被解析
const path = "@include/test.md";
@include another.md # ✅ 会被解析
### 传递性包含与安全限制
#### 循环引用防护
`@include` 支持传递性包含(A 包含 B,B 包含 C),但有两个安全机制:
**最大深度限制**:最多 5 层嵌套
```mermaid
flowchart LR
A["CLAUDE.md"] --> B["@include rules.md"]
B --> C["@include api.md"]
C --> D["@include db.md"]
D --> E["@include utils.md"]
E --> F["@include common.md"]
F --> G["⚠️ 超过深度限制,停止解析"]
style G fill:#f3e6e6,stroke:#cc0000循环引用检测:
# a.md
@include b.md
# b.md
@include a.md # ⚠️ 检测到循环,已被处理过,跳过实现原理:通过 processedPaths Set 追踪已处理的文件路径,路径在比较前经过规范化(处理 Windows 盘符大小写差异)。
外部文件安全
当 @include 指向项目目录外的文件时,默认不加载(安全边界)。只有以下情况允许外部包含:
- User Memory 层级:
~/.claude/CLAUDE.md中的@include可以引用任何位置 - 用户显式批准:设置
hasClaudeMdExternalIncludesApproved: true
如果发现未批准的外部包含,系统会显示警告:
⚠️ Warning: CLAUDE.md contains @include directives to files outside the project directory.
These external includes were not loaded. To enable them, approve external includes in settings.符号链接处理
每个文件在处理前都解析符号链接到真实路径。这防止了一个安全漏洞:攻击者可能通过符号链接绕过循环引用检测。
flowchart LR
A["读取 CLAUDE.md"] --> B{是否为符号链接?}
B -->|是| C["解析到真实路径"]
B -->|否| D["直接使用路径"]
C --> E["检查循环引用"]
D --> E
E --> F["处理文件内容"]
style C fill:#fff3cd,stroke:#ff980019.4 frontmatter paths:按需加载规则
.claude/rules/ 目录中的文件可以通过 YAML frontmatter 的 paths 字段限定其适用范围。只有当 Claude 操作的文件路径匹配这些 glob 模式时,规则才会被注入上下文。
基本语法
---
paths: src/**/*.ts, tests/**/*.test.ts
---
# API 开发规范
所有 API 端点必须:
1. 使用 Zod 进行请求/响应验证
2. 返回标准化的错误响应
3. 包含 OpenAPI 文档注释或 YAML 列表格式:
---
paths:
- src/api/**/*.ts
- src/api/**/*.test.ts
- src/types/api.ts
---
# API 开发规范
...花括号展开
支持 Bash 风格的花括号展开:
---
paths: src/*.{ts,tsx}, tests/**/*.test.{ts,tsx}
---会被展开为:
paths:
- src/*.ts
- src/*.tsx
- tests/**/*.test.ts
- tests/**/*.test.tsx多层花括号也被支持:
---
paths: src/{api,components}/{index,types}.{ts,tsx}
---展开为:
paths:
- src/api/index.ts
- src/api/index.tsx
- src/api/types.ts
- src/api/types.tsx
- src/components/index.ts
- src/components/index.tsx
- src/components/types.ts
- src/components/types.tsxYAML 解析的容错处理
glob 模式常包含 YAML 特殊字符({}[]*, &#!|>%@),直接解析会失败。系统使用两层容错机制:
第一次尝试:直接解析
---
paths: src/**/*.{ts,tsx} # ❌ 解析失败:{ } 是特殊字符
---第二次尝试:自动引用问题值
---
paths: "src/**/*.{ts,tsx}" # ✅ 解析成功
---这意味着用户可以直接写 paths: src/**/*.{ts,tsx} 而无需手动加引号——解析器会在第一次失败后自动修复。
条件规则 vs 无条件规则
.claude/rules/ 目录中的文件分为两类:
| 类型 | frontmatter | 加载时机 | 示例 |
|---|---|---|---|
| 无条件规则 | 无 paths 字段 |
会话启动时预加载 | 编码规范、通用约定 |
| 条件规则 | 有 paths 字段 |
操作匹配文件时按需加载 | 特定技术栈规则 |
无条件规则示例:
# .claude/rules/general-conventions.md
# 通用约定
- 所有函数必须添加 JSDoc 注释
- 变量命名使用 camelCase,常量使用 UPPER_SNAKE_CASE
- 文件名使用 kebab-case条件规则示例:
---
paths: src/**/*.svelte
---
# Svelte 组件规范
- 使用 `