五者各自负责什么
刚装好 Claude Code 时只看得到一个对话框。这样也能干活,但用几天就会发现自己在反复输入同样的话:这个项目这样启动、那个目录别动、提交前先跑测试。
消除这种重复的机制有五个。名字看着相似,性格却完全不同。区分它们的有两条轴:什么时候被读取,以及是叮嘱还是强制。
先看全景图。下面这张表就是全文的摘要。
| 名称 | 创建位置 | 何时读取 | 性质 |
|---|---|---|---|
| CLAUDE.md | 项目根目录或 ~/.claude/ | 每次会话开始时 | 叮嘱 |
| Rules | .claude/rules/*.md | 始终,或仅在触及匹配文件时 | 叮嘱 |
| Hooks | .claude/settings.json | 指定事件每次发生时 | 强制 |
| Skills | .claude/skills/<名称>/SKILL.md | 仅在调用时 | 流程 |
| Agents | .claude/agents/<名称>.md | 仅在移交工作时 | 分工 |
my-project/
├── CLAUDE.md # 每次会话最先读取的说明
└── .claude/
├── settings.json # 写钩子的地方
├── rules/
│ ├── testing.md
│ └── api.md # 可以用 paths 加条件
├── skills/
│ └── deploy/SKILL.md # 用 /deploy 调用
└── agents/
└── code-reviewer.md # 评审在另一个窗口进行
CLAUDE.md — 会话开始时最先读取的文件
顾名思义就是一个文件。在项目目录里建一个名为 CLAUDE.md 的文件,Claude Code 每次启动都会先读它再开始对话。
为什么需要它。语言模型记不住过去的对话,看上去像记得,是因为每次都把保存下来的内容和提示一起发过去,而那份保存的内容就是这个文件。不想今天再讲一遍昨天的构建命令,就写在这里。
写什么。写那些否则你还得再解释一遍的东西。判断信号有几个:同样的错误第二次出现、同样的纠正上次也打过、新同事同样会问到的内容。具体来说是项目做什么、用什么技术栈、用什么命令运行测试部署,以及绝对不能做的事。
文件可以放在四处,从大范围到小范围全部拼接后读取:面向组织下发的托管策略文件、作用于账号的 ~/.claude/CLAUDE.md、与团队共享的项目 CLAUDE.md,以及只给自己用的 CLAUDE.local.md。最后一个要加进 .gitignore,别提交。
从空白文件写起有困难,就在终端敲 /init。Claude 会扫一遍代码库,生成含有构建命令、测试方法和它发现的约定的草稿。已有文件时不覆盖,只提改进建议。
初学者在这里最常犯的错是篇幅。有人写成自我介绍,而官方文档建议每个文件保持在 200 行以内。这个文件每次会话都整份进入上下文,越长越费 token,也越不容易被遵守。措辞也要具体到可以验证:写「缩进用两个空格」比写「把代码格式化好」管用。
还有一个必须知道的事实。CLAUDE.md 是上下文而不是配置。它以用户消息的形式送达而非系统提示的一部分,所以 Claude 会读会参考,但不保证照做。必须拦住的事情属于后面的钩子。
想确认文件是否真的读进来了,在会话里敲 /context 看内存文件清单。想打开修改就用 /memory。
如果你已经为别的工具维护 AGENTS.md,Claude Code 不读那个文件。建一个 CLAUDE.md,第一行写 @AGENTS.md 引入即可两边共用一份。
| 范围 | 位置 | 适用场景 |
|---|---|---|
| 托管策略 | macOS 为 /Library/Application Support/ClaudeCode/CLAUDE.md | 组织级标准 |
| 个人账号 | ~/.claude/CLAUDE.md | 适用于所有项目的个人偏好 |
| 项目 | ./CLAUDE.md 或 ./.claude/CLAUDE.md | 与团队共享的规则 |
| 个人·项目 | ./CLAUDE.local.md | 只给自己用的值,加入 .gitignore |
# my-shop
## 项目
用 Next.js 15 和 Supabase 做的在线下单页面。
## 命令
- 开发服务器: `npm run dev`
- 测试: `npm test`
- 部署: `vercel deploy --prod`
## 规则
- 金额计算只在 `src/lib/price.ts` 一处进行。
- 提交前必须运行 `npm test`。
- 绝不打开也不提交 `.env` 文件。
Rules — CLAUDE.md 变长后拆到这里

「保持在 200 行以内」马上带来一个问题:多出来的往哪放。答案是 .claude/rules/。
做法很简单。在项目里建 .claude/rules/ 目录,按主题放 markdown 文件。取名让人一眼看出主题,比如 testing.md、api-design.md、security.md。放进子目录也会被递归找到。
只做到这一步,与把 CLAUDE.md 拆成几个文件没有区别。真正的差异在下一个功能。
在文件顶部写上 paths 就变成条件加载。像下面的例子那样写好 glob 模式,只有当 Claude 真的打开匹配的文件时,这条规则才进入上下文。也就是说,改前端时那三十行 API 规则不占位置。
没有 paths 的规则文件在会话开始时无条件全部读取,优先级与 .claude/CLAUDE.md 相同。
模式就是常见的 glob 语法。**/*.ts 是所有 TypeScript 文件,src/**/* 是 src 下全部,*.md 是根目录的 markdown,src/components/*.tsx 只指那个目录里的 React 组件。也可以用花括号合并扩展名,如 src/**/*.{ts,tsx}。
还有依附于「人」而非项目的规则。放在 ~/.claude/rules/ 里就作用于本机所有项目。读取顺序是个人规则在前、项目规则在后,所以项目规则位置更强。
记住一个区别就够了。规则是自动进入上下文的,要么始终,要么在触及对应文件时。而接下来的技能只在调用时进入。必须始终成立的句子是规则,只在特定任务用到的流程是技能。
---
paths:
- "src/api/**/*.ts"
---
# API 编写规则
- 所有端点都要校验输入。
- 错误响应只用一种约定格式。
- 新端点要写 OpenAPI 注释。
Hooks — 不靠叮嘱,用代码拦住
前面两者都是叮嘱。Claude 会读会参考,但也可能不照做。有些事必须成立,就需要性质不同的装置,那就是钩子。
钩子是在指定事件发生时自动执行的 shell 命令。不是 Claude 判断后执行,而是条件符合就一定执行。所以只有它才是强制。
写在 .claude/settings.json 里。结构分三层:挂在哪个事件上、在这个事件里只针对什么、以及执行什么命令。
事件名有很多,一开始知道五个就够。SessionStart 是会话开始时,UserPromptSubmit 是我发出提示时,PreToolUse 是工具执行前,PostToolUse 是执行后,Stop 是 Claude 结束回答时。
matcher 用来在事件内缩小对象。工具类事件按工具名过滤:写 Bash 只对 Bash 工具生效,写 Edit|Write 两个都生效,留空或写 * 则全部生效。
被执行的命令从标准输入收到 JSON,里面写明是哪个事件、哪个工具、传了什么参数。下面的例子就是读这些值来拦截强制推送。
关键在退出码,它就是钩子的判定。以 0 结束表示放行,以 2 结束则该工具调用被取消,写到 stderr 的句子会作为理由交给 Claude。其他值记为错误但工作照常进行。
实际用来做什么。改完文件自动跑格式化、禁止修改某个目录、任务结束时发通知,都很常见。如果 CLAUDE.md 里写了「提交前跑测试」却总被跳过,那句话就该降到钩子里。
| 退出码 | 含义 | 会发生什么 |
|---|---|---|
| 0 | 成功 | 照常进行。标准输出一般进调试日志 |
| 2 | 拦截 | 该工具调用被取消,stderr 作为理由交给 Claude |
| 其他 | 非拦截错误 | 工作照常进行,只记录错误 |
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/no-force-push.sh"
}
]
}
]
}
}
#!/bin/bash
# 钩子从标准输入接收 JSON,tool_input.command 里是即将执行的命令。
cmd=$(jq -r '.tool_input.command // ""')
if [[ "$cmd" == *"push --force"* || "$cmd" == *"push -f"* ]]; then
# 退出码 2 表示拦截。写到 stderr 的内容会作为理由交给 Claude。
echo "本仓库禁止强制推送,请使用 --force-with-lease。" >&2
exit 2
fi
exit 0
Skills — 把重复的流程变成斜杠命令
如果你在对话框里一再粘贴同一组指示,或者 CLAUDE.md 的某一段已经从「事实」长成了「流程」,那就是该搬进技能的信号。
做法是一个目录加一个文件。在 .claude/skills/ 下按名字建目录,里面放 SKILL.md。目录名就是命令名,用 /名称 调用。
SKILL.md 分两部分:三横线之间的 frontmatter,以及下面的正文。frontmatter 的字段全是可选的,但 description 实际上是必需的,因为 Claude 靠它判断何时使用这个技能。要同时写清做什么和什么时候用。
正文写 Claude 要遵循的指示。技能真正的好处在这里:正文只在技能实际被使用时才读取,所以写长了平时也完全不占上下文。把长流程放进 CLAUDE.md 每次会话都要付费,放进技能则是零。
调用方式有两种。直接敲 /名称,或者提出符合 description 的请求让 Claude 自己加载。不想被自动调用、只想手动使用时,在 frontmatter 里把 disable-model-invocation 设为 true。
在正文里用感叹号加反引号包住命令,Claude 看到内容前那里就已经填好了执行结果。下面例子里的 git diff 就是这样,Claude 读技能时改动已经在里面了。
存放位置有三处。~/.claude/skills/ 作用于所有项目,项目的 .claude/skills/ 只作用于该项目,插件里的 skills/ 在插件启用处生效。另外,早先的 .claude/commands/ 已并入技能,原有文件继续可用。
改了文件不用重启会话,当场生效。
| 位置 | 路径 | 适用范围 |
|---|---|---|
| 个人 | ~/.claude/skills/<名称>/SKILL.md | 我的所有项目 |
| 项目 | .claude/skills/<名称>/SKILL.md | 仅此项目 |
| 插件 | <插件>/skills/<名称>/SKILL.md | 插件启用之处 |
---
description: 概括改动内容并指出风险点。当用户问改了什么或需要提交信息时使用。
---
## 当前改动
!`git diff HEAD`
## 指示
用两三行概括上面的改动,然后列出缺失的错误处理、硬编码的值、
需要一并修改的测试。如果没有改动就直说。
Agents — 把旁支工作放到另一个窗口

前面四个是告诉 Claude 做什么、怎么做,最后这个分的是「谁来做」。
子智能体是拥有独立上下文窗口的助手型 Claude。把工作交给它,它在自己的窗口里独立进行,只回传结果摘要。翻代码库产生的大量搜索结果和文件内容不会堆进我的对话。想到上下文窗口越满答案越差,就知道这为什么是一件工具。
做法是一个 markdown 文件。放到 .claude/agents/ 下,frontmatter 写四项:name 是由小写字母和连字符组成的唯一名称,description 是何时把工作交给它,tools 是可用工具清单,model 是用哪个模型跑。
name 和 description 是必填,其余可省。省略 tools 就继承子智能体可用的全部工具。想设为只读,只写 Read, Grep, Glob 即可让它无法改文件。把 model 设为 haiku 会用更便宜的模型从而省钱,省略则继承主对话的模型。
位置有两处。~/.claude/agents/ 作用于所有项目,项目的 .claude/agents/ 只在该仓库生效。后者提交上去,团队就能共用共改。
不自己做也有现成的。Explore 是翻代码库的只读智能体,Plan 在计划模式下负责调研,General-purpose 负责既要探索又要修改的复合任务。Claude 靠各智能体的 description 决定是否移交,所以自己写时要把说明写成「何时使用」的句子。
新建了文件却看不到,就重启会话。这只在会话开始时 agents 目录本身还不存在的情况下发生。
---
name: code-improver
description: 扫描文件并就可读性、性能与最佳实践提出改进建议。写完或改完代码后使用。
tools: Read, Grep, Glob
model: sonnet
---
你是代码改进专家。对发现的每个问题,先说明问题所在,
再展示当前代码,然后给出改进后的版本。
五者之中该选哪个
名字和格式都清楚了,真正卡住的还是这一步:手上这句话该放进哪里。
三个问题就能分开。第一,这是必须始终知道的事实,还是某项任务的流程?事实进 CLAUDE.md,流程进技能。第二,是不是只在触及特定文件时才需要?那就是带 paths 的规则。第三,这是可以违反的建议,还是必须拦住的事?后者是钩子。
还有第四个。这项工作做完后,还会再看它的日志吗?不会的话就交给智能体,让自己的对话保持干净。
常见的错误也是对称的。一种是把流程塞进 CLAUDE.md,文件胀到 300 行,结果什么都不被遵守;另一种是把必须拦住的事写成 CLAUDE.md 里的一句话,然后为它不被遵守而着急。前者搬到技能,后者降到钩子,就都解开了。
| 手上的东西 | 该去的地方 |
|---|---|
| 构建命令、目录结构、始终要守的约定 | CLAUDE.md |
| 只在特定目录或扩展名下成立的规则 | Rules(指定 paths) |
| 必须无例外拦住或必须执行的事 | Hooks |
| 由多个步骤组成的重复作业 | Skills |
| 只要结果、不看过程的调研与评审 | Agents |
现在就动手
不必一次做齐五个。它们有顺序,前一个装不下时才轮到下一个。
先在正在做的项目里敲 /init。Claude 扫过代码库后会生成 CLAUDE.md 草稿。打开草稿确认命令是否正确,再手动补上 Claude 无从知晓的几条规则。第一天做到这里就够。
之后是被动式的。文件接近 200 行时按主题拆到 .claude/rules/,只在特定目录成立的就加 paths。同一组指示第三次粘贴时,搬到 .claude/skills/。写下来却总不被遵守的句子,那就是该降到钩子的句子。调研把对话刷成日志时,就做一个智能体。
确认用 /context。这一屏会显示当前会话实际加载了哪些文件、上下文占了百分之多少。写了却不在清单里,问题不在内容而在位置。
