普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月11日首页

连载04-最重要的Skill---一起吃透 Claude Code,告别 AI coding 迷茫

2026年4月10日 19:48

screenshot-20260410-194956.png

别再直接 Fork 别人的 Claude Skill:公开模板只是原材料,项目规则才是成品

AI Coding 系列第 05 篇 · 核心工具

我第一次批量导入公开 Skill 模板的时候,是真的以为自己走了捷径。

GitHub 上一堆 star 很高的仓库,code review、需求分析、文档编写、调研、拆任务,看起来什么都有。我当时的想法特别简单:既然别人已经把常见工作流整理好了,我直接 fork 一份,全量导入,不就能让 Claude 立刻更稳、更懂项目吗?

结果用了几天,我反而越来越不放心。

它每次都输出得很像那么回事。格式完整,措辞专业,检查项也不少。可真正让我在项目里反复吃亏的那几件事,它一次都没替我盯住。异步链里是不是又漏了 await,这次 migration 有没有回滚方案,新同学是不是又顺手写了 throw new Error(),数据库 schema 改了之后 Prisma 类型是不是也一起更新了。

它会提醒一堆“大家普遍都应该注意”的东西,却不懂“我们团队到底最怕什么”。

后来我才慢慢明白,问题不是 Skill 机制不好,而是我导入的根本不是自己的 Skill,只是别人整理好的经验。

这些经验当然有用,但它们解决的是共性问题,不会天然长成你项目里的“肌肉记忆”。

这篇文章就讲一件事:

怎么把公开模板当原材料,而不是成品;怎么从自己的项目里,提炼出一个真正会被反复复用、而且越用越准的 Skill。

如果你只想先记住一句话,那就是:

公开 Skill 模板是素材库,不是最终产品。


先给你一个判断框架

为了后面不绕,我先把最核心的判断放前面:

  • 对所有任务都生效的规则,放 CLAUDE.md
  • 只对某一类任务生效的规则,做成 Skill
  • 只对这一次任务有效的约束,写进 Prompt
  • 只有“输入相对稳定、输出有共同模式、而且容易漏步骤”的任务,才值得沉淀成 Skill
  • 第一个 Skill 不要选最关键的任务,先拿中等风险任务练手

如果你现在就卡在“这条到底该写哪”,后面大部分内容其实都可以用这五条往回推。


一、为什么很多公开 Skill 模板,一开始觉得香,后来却越用越别扭

我现在反而会对“看起来很全”的公开 Skill 模板保持一点警惕。

不是因为它们没用,而是因为它们太容易制造一种错觉:好像什么都覆盖到了,但真正最重要的东西其实没进去。

公开模板最常见的问题,不是方向错,而是下面这三种。

1. 太宽泛

它什么都管一点,但什么都不够深。

它会告诉你“注意异常处理”“注意性能”“注意安全”,这些当然没错。但这些话本身不构成你项目里的工作流。它不知道你们统一用的是 AppError,不知道你们数据库变更必须检查回滚,也不知道你们哪几个目录历史包袱最重。

2. 太嘈杂

50 行模板里,真正有价值的可能只有 5 行。

剩下的 45 行不是完全没用,而是在和那 5 行争夺 Claude 的注意力。对于 agent 来说,规则不是越多越强。很多时候,8 行写透项目约束的 Skill,比 50 行“样样都提一点”的模板更有用。

3. 太不像你的项目

这点才是最致命的。

公开模板知道“大家普遍应该注意什么”,但不知道“你们团队反复死在哪些地方”。而真正有价值的 Skill,恰恰应该把那些项目特有、团队高频踩坑的东西固化下来。

说得更直白一点:你把一个新同事扔进团队,给他一份行业通用培训材料,当然比什么都不给强;但如果你不告诉他“我们团队最容易出错的是哪三件事”,他依然干不好你最在意的活。

所以正确姿势不是“找一个最全的模板直接用”,而是:

先借鉴,再裁剪,最后只留下真正属于你项目的那几条。


二、先把 Prompt、CLAUDE.md、Skill 这三件事分清楚

很多人不是不会写 Skill,而是一开始就把这三件事混在一起了。

判断方法其实很简单,只问一个问题的三个变体:

  • 这个要求对所有任务都成立吗?如果是,放 CLAUDE.md
  • 这个要求只对某一类任务成立吗?如果是,做成 Skill
  • 这个要求只对这一次成立吗?如果是,写进 Prompt

举几个特别典型的例子:

“所有 throw 必须是 AppError
这是全局规则。不管你是在写新功能、修 bug,还是做重构,都要遵守。它应该进 CLAUDE.md

“代码审查时按固定顺序检查数据库、异步和错误处理”
这只在 code review 这种任务里才触发,它不是全局规则,而是任务模板,所以应该做成 Skill

“这次先只分析原因,不要动代码”
这只对当前这次任务有效,应该写进 Prompt

最容易搞混的是 CLAUDE.mdSkill。它们都能约束 Claude 的行为,但本质完全不同:

  • CLAUDE.md 是永远生效的规则
  • Skill 是遇到对应任务才触发的模板

如果要打个比方:

  • CLAUDE.md 是交通规则
  • Skill 是导航路线
  • Prompt 是你这次上车前临时交代的一句话

这三层一旦分清楚,后面很多混乱都会自动消失。


三、什么时候一个任务值得被沉淀成 Skill

不是所有重复任务都值得沉淀。

我现在给自己的标准其实很克制,就一句话:

同一类任务做了三次以上,而且每次都要重新给 Claude 解释背景。

反过来说,如果某个任务每次背景和目的都完全不同,就不值得沉淀。比如“写文档”这个动作本身很常见,但公司文档、API 文档、用户手册的写法完全不同,它们应该是三个不同的 Skill,而不是一个叫“写文档”的通用模板。

在真正开始写之前,我会先做三个检查。

1. 输入是否稳定

“根据 Figma 设计稿生成 React 组件”这种任务,输入格式相对稳定,比较适合沉淀。

“根据 SQL 查询结果生成图表”这种任务,每次数据格式和图表类型都可能差很多,Skill 会很难写得稳。

2. 输出是否有共同模式

“写 Pull Request 描述”很适合,因为它天然就有固定框架:改了什么、为什么改、怎么测试。

但“和 AI 讨论技术方案”这种任务,每次深度、重点、结论都不同,就不太适合硬沉淀成一个模板。

3. 有没有容易漏掉的关键步骤

最值得沉淀成 Skill 的任务,通常不是“最复杂”的任务,而是那些不特别提醒就容易漏一步的任务。

Skill 最有价值的地方,不是让 Claude 变得更聪明,而是把你每次最容易忘的检查项,固化成默认动作。

所以一个任务如果同时满足下面三点:

  • 输入相对稳定
  • 输出有共同模式
  • 总有一两步容易漏

它就很值得沉淀成 Skill。


四、一个真正好用的 Skill,内容层通常只需要四个部分

很多人一开始会把 Skill 写得很重,像在写规范文档。但实际用起来之后你会发现,真正好用的 Skill 通常很短。

它一般只需要四个部分。

1. 触发条件

什么时候用,一句话说清楚。

❌ 代码审查
✅ 当我提交 PR 前,检查我的实现是否符合项目约定

2. 执行步骤

按什么顺序做,列出来。尽量不要超过五步。

1. 读完整个改动的 diff
2. 检查是否用了禁用的库或模式
3. 检查异步操作的错误处理
4. 检查是否有 SQL 注入的风险
5. 给出修改建议

3. 输出格式

不要写“请清晰输出”。这种话几乎没有约束力。直接给模板。

❌ 用清晰的格式列出所有问题

✅ 给个模板:
Found 3 issues:
🔴 Critical: ...
🟡 Warning: ...
✅ Suggestion: ...

4. 注意事项

说清楚边界。什么情况不适用,有哪些常见陷阱。

- 不适用于新增功能的初始实现,只适用于 PR 前的最终检查
- 不关注 UI 层细节
- 改动超过 500 行,先拆成多个 Skill 请求

Skill 不是规范手册,更不是把所有经验一次性塞进去。它本质上是一个高频任务的最小可执行模板。


五、真正落到 SKILL.md 文件层,哪些字段最值得你花心思

讲完“内容怎么提炼”,还得讲“文件怎么写”。

很多人第一次写 SKILL.md 会卡在另一个地方:字段太多,不知道哪些真有用,哪些只是“看起来高级”。

一个完整的 SKILL.md,通常会长这样:

---
name: code-review
description: 提交 PR 前的代码审查
when_to_use: 当用户要求 review 代码或提交 PR 前检查时
allowed-tools:
  - Read
  - Grep
  - Glob
  - Bash(git diff *)
argument-hint: "[PR 分支名或文件路径]"
arguments:
  - target
---

# Code Review

## 步骤
1. 读取 ${target} 的改动 diff
2. 检查错误处理:所有 throw 必须是 throw new AppError()
3. 检查异步操作:Promise 链是否有遗漏的 await
4. 检查数据库查询:是否有 N+1 问题

## 输出格式
🔴 Critical: ...
🟡 Warning: ...
✅ 通过: ...

这里最值得你认真写的,其实是下面几个字段。

name
名字别太抽象。要让人一眼知道它是做什么的。

description
一句话说清这个 Skill 的用途,不要写成空泛口号。

when_to_use
这是最容易被低估的字段之一。它不是装饰,它直接影响 Claude 在什么场景下会想到这个 Skill。

allowed-tools
它决定这个 Skill 具备哪些能力。这个字段后面我会在源码部分展开讲,因为它比很多人想象的更“硬”。

arguments
让 Skill 接受参数,比如目标文件、目录、分支名。${target} 会在正文里被替换成你传进去的实际值。

还有几个很好用,但不是每次都要上的字段。

argument-hint
告诉调用者这个 Skill 期待什么参数。

model: haiku
简单任务可以指定更轻量的模型,直接省成本。像格式化、重命名、简单改写这类工作,很多时候没必要上更重的模型。

paths
让 Skill 只在某些路径下激活。适合模块边界明确的项目。

context: fork
高风险操作放进独立上下文,避免污染主会话。

大多数 Skill 根本不需要把字段填满。真正实用的思路不是“功能全”,而是“正好够用”。

如果一个 Skill 只是做常规代码审查,前四五个字段通常就够了。只有当你真的遇到参数化、模块隔离、上下文隔离这些需求时,再往上加。


六、如果只停在经验层,这篇其实还差半口气:我后来去翻了源码

前面这些判断,靠经验其实也能总结出来。

但我后来还是不太满足。因为有几个问题如果不看实现,心里总会悬着:

  • when_to_use 到底是不是自动触发的关键?
  • allowed-tools 到底只是提示,还是硬限制?
  • paths 到底是真过滤,还是只是写给人看的说明?

我后来去翻了一遍源码,结论是:这些字段比我一开始以为的更“硬”。

1. Claude 只在启动时读 frontmatter,Skill 正文是懒加载的

loadSkillsDir.ts 里有一个函数 estimateSkillFrontmatterTokens,注释写得非常直接:

/**
 * Estimates token count for a skill based on frontmatter only
 * (name, description, whenToUse) since full content is only loaded on invocation.
 */
export function estimateSkillFrontmatterTokens(skill: Command): number {
  const frontmatterText = [skill.name, skill.description, skill.whenToUse]
    .filter(Boolean)
    .join(' ')
  return roughTokenCountEstimation(frontmatterText)
}

这段代码背后的意思非常重要。

Claude Code 启动时,主要只把每个 Skill 的 namedescriptionwhen_to_use 这些 frontmatter 信息算进上下文。Skill 正文不是一开始就全量塞进去,而是在你真正触发它的时候才加载。

这直接解释了两件事。

第一,when_to_use 写得越具体,自动命中的效果就越稳定。Claude 不是先把你整篇 Skill 读完再判断要不要触发,它先看的就是前面这几行。

第二,你有十个 Skill 还是三个 Skill,对启动时上下文的占用差距没你想的那么大。真正的成本在触发时才发生。

所以 when_to_use 不能写成“代码相关任务时使用”这种空话。它要写成“当用户要求 review TypeScript 后端代码或提交 PR 前做最终检查时”这种具体到能命中的描述。

这也是为什么我现在越来越重视 frontmatter。以前我会把心思都放在正文步骤上,后来才发现,前面几行写虚了,后面写得再好都不一定有机会被用上。

2. allowed-tools 是系统层权限,不是给 Claude 的礼貌性建议

这一点是我看源码之后感受最强的一处。

Skill 执行时,getPromptForCommand 会在返回内容之前把 allowedTools 写进工具权限上下文:

getAppState() {
  const appState = toolUseContext.getAppState()
  return {
    ...appState,
    toolPermissionContext: {
      ...appState.toolPermissionContext,
      alwaysAllowRules: {
        ...appState.toolPermissionContext.alwaysAllowRules,
        command: allowedTools,
      },
    },
  }
}

这说明 allowed-tools 不是“提醒 Claude 尽量这样做”,而是权限层的强制限制。

比如一个 code review Skill 只开放 ReadGrepGlobBash(git diff *),那它就不是“理论上不该写文件”,而是从架构上根本没有写文件的能力Bash(git diff *) 这种写法也不是装饰,它真的只允许 git diff 开头的命令,其他 Bash 调用会被挡住。

这让我对 allowed-tools 的理解完全变了。它不是“不信任模型”,而是最小权限设计。就像你给数据库只读账号只开 SELECT 权限,不是因为你怀疑这账号会作恶,而是因为这个任务本来就不该拥有写权限。

3. paths 不是文档字段,它会把 Skill 放进条件激活区

这一点也比表面上看起来更硬。

源码里,带 paths 的 Skill 在加载时会被单独分流到一个 conditionalSkills Map:

// Separate conditional skills (with paths frontmatter) from unconditional ones
for (const skill of deduplicatedSkills) {
  if (skill.type === 'prompt' && skill.paths && skill.paths.length > 0
      && !activatedConditionalSkillNames.has(skill.name)) {
    newConditionalSkills.push(skill)
  } else {
    unconditionalSkills.push(skill)
  }
}

// Store conditional skills for later activation when matching files are touched
for (const skill of newConditionalSkills) {
  conditionalSkills.set(skill.name, skill)
}

// 最后只返回无条件的 Skill
return unconditionalSkills

这段逻辑的含义是:带 paths 的 Skill,根本不会像普通 Skill 一样直接进入启动时上下文。它会先待在一个“条件激活区”里,只有当你在会话里碰到了匹配路径的文件,它才会被真正激活。

这点对复杂项目非常有价值。

比如你给支付模块写一个 paths: src/payment/** 的 Skill,在你处理用户系统、文章系统、管理后台时,这个 Skill 对 Claude 几乎是隐身的。只有当你真的进入 src/payment/ 相关文件,它才“出现”。

这也是我现在很认同的一种团队实践:不要在根目录堆一个什么都想管的大 Skill 集合,而是让复杂模块在自己的目录附近维护自己的 Skill。

4. Skill 发现是沿目录向上找的,而且离文件越近优先级越高

还有一个很容易被忽略,但工程上非常实用的机制:Claude Code 会从当前文件所在目录一路向上寻找 .claude/skills

源码大概是这样:

// Walk up to cwd but NOT including cwd itself
while (currentDir.startsWith(resolvedCwd + pathSep)) {
  const skillDir = join(currentDir, '.claude', 'skills')
  // ...check if exists, then load
  currentDir = dirname(currentDir)
}

// Sort by path depth (deepest first) so skills closer to the file take precedence
return newDirs.sort((a, b) => b.split(pathSep).length - a.split(pathSep).length)

这里最关键的是最后一行:deepest first。也就是说,越靠近当前文件的 Skill,优先级越高。

这意味着你放在 src/auth/.claude/skills/ 里的 Skill,可以自然覆盖根目录下更通用的同名 Skill。对 monorepo 或大仓库来说,这个机制非常好用:

  • packages/api/.claude/skills/ 可以放 API 专属 Skill
  • packages/web/.claude/skills/ 可以放前端专属 Skill
  • 根目录只保留真正的全局规则

如果把上面四点放在一起看,设计 Skill 的顺序其实会变得很清楚:

  • 先把 frontmatter 写准,再去打磨正文步骤
  • 先按最小权限收紧 allowed-tools,再考虑要不要给更多能力
  • 只有模块边界明确时再上 paths,不要为了“高级”硬加
  • 多目录项目优先做“离代码更近”的局部 Skill,而不是维护一个大而全的总模板

七、完整案例:把一个通用 code review 模板,提炼成你项目真正需要的 Skill

上面说了这么多抽象原则,不如走一遍完整例子。

假设你们团队每周都做后端代码审查,而且总在重复盯这几件事:

  • 有人改一个功能,顺手动了三个不相关模块
  • 新同学不知道项目里统一用 AppError,直接 throw new Error()
  • Promise 链里漏了 await
  • 数据库查询没有索引,或者潜在 N+1 没被看出来

这就是非常典型的“该沉淀 Skill 的信号”。

第一步:先确认痛点到底是什么

这一步别着急写模板,先把“你们到底在反复出什么问题”说清楚。

很多团队的问题不是“没有 code review”,而是每次 review 的注意力都被分散了。真正高频出错的点,永远是那几类项目特有的约束。

所以要沉淀的不是“代码审查”这四个字,而是你们团队在代码审查里最容易漏掉的那几类检查。

第二步:从公开模板里提取真正有用的部分

这时候公开模板就有用了,但它的用途不是直接上生产,而是当素材库。

假设你找到一个 50 行的通用 code review 模板。你真正该提取的,可能只有下面这几类东西:

  • 逻辑正确性,尤其是异步操作
  • 项目约定的遵守,比如 AppError、错误处理模式
  • 数据库相关的风险,比如 N+1、索引、查询范围
  • 改动范围是否聚焦,不要顺手改不相干文件

剩下那些跟你们项目关系不大的部分,就应该果断删掉。

第三步:把它压缩成一个真正能用的 Skill

最后落地出来的 Skill,应该更像这样:

# Code Review Skill

## When to use
在提交 PR 前,请 Claude 做最后的 code review。只用于 TypeScript 后端代码。

## Steps
1. 读 diff,确认改动是否只涉及这个 PR 的范围(不要顺手改无关文件)
2. 检查错误处理:所有 throw 都必须是 throw new AppError(),不能 throw new Error()
3. 检查异步操作:Promise 链是否有遗漏的 await,错误是否被正确 catch
4. 检查数据库查询:是否有 SELECT * 的懒惰写法,是否明显的 N+1 查询,关键查询是否 explain 过

## Output Format
Issues found (Critical → Warning → Info):

🔴 **Line 45**: Missing `.select()` in Prisma query - this will fetch unnecessary columns
🟡 **Line 67**: Potential N+1: loop inside `posts.map()` should use `Promise.all()`**No AppError violations** — all errors properly handled

Summary: 1 critical issue to fix before merge.

## Caveats
- 不审查 UI 层代码(只关心后端逻辑)
- 不关注代码风格(那是 prettier 的事)
- 如果一个改动涉及多个不相关功能,分别提交 PR 再 review

你会发现,到这一步之后,Skill 就不再是“通用模板的中文版”了,而是你们项目真正有用的一个局部工作流。

50 行公开模板,最后可能只剩下 4 个真正属于你项目的核心关注点。但恰恰是这 4 个点,才决定它到底值不值得用。

第四步:在真实使用里继续迭代

Skill 从来不是一次写完的。

比如你用了两周之后,又发现一个常见问题:改了数据库 schema,但忘记更新 Prisma 类型。那就把它加进去:

4.5. 检查 Prisma 类型:如果改了数据库,Prisma schema 和生成的 types 是否都已更新

这时候你会发现,Skill 真正的价值不是“第一次写出来”,而是在真实工作里被持续打磨。


八、Skill 的维护节奏,比第一次写出来更重要

Skill 不是写好就扔。

如果你写完之后三个月不看,它很快就会从“项目经验”重新退化成“历史遗留文档”。

我更推荐一个很轻的维护节奏:

第一个月
高频使用,快速迭代。每次用完就问自己三个问题:步骤是不是太复杂?输出是不是太啰嗦?有没有漏掉今天刚踩到的新坑?

之后每个月
回顾一次。看看最近有没有经常被遗漏的步骤,有没有新的痛点需要加入。

每个季度
系统清理一次。把已经不再是问题的注意事项删掉,把那些已经变成全局共识的规则移进 CLAUDE.md

Skill 应该越用越精炼,而不是越写越臃肿。


九、一个特别反直觉,但很重要的经验:第一个 Skill,故意别写最重要的任务

这条我非常想单独拿出来讲。

因为很多人第一次沉淀 Skill,会本能地想挑一个最关键的任务,比如“生产环境发布前检查”“数据库迁移前审查”“支付流程改动 review”。

但工程上更稳的做法,其实正好相反。

大多数人写的第一个 Skill,质量都不会太高。触发条件偏模糊,步骤偏啰嗦,输出格式也不够稳定。这很正常,因为你第一次做这件事时,对“什么是好的 Skill”还没有直觉。

如果你一开始就把它用在最关键的任务上,一旦写得不够好,伤害会非常直接:要么关键场合出问题,要么你从此对这套机制失去信心。

更好的策略是:先拿一个重要程度中等、容错率比较高的任务练手。

比如:

  • 写周报
  • 生成 PR 描述
  • 做一次常规 code review

先跑两周,迭代两三次,等你对 Skill 的节奏有感觉了,再去沉淀真正关键的流程。

第一个 Skill 的目的,不是直接解决最大的问题,而是让你学会怎么写 Skill。


十、如果你今天就想开始,可以直接做这三个动作

别先想着搭一整套系统,直接从最小动作开始:

任务一:列任务
列出你最近一个月里重复做过三次以上的任务。

任务二:做分类
用“三问判断法”确认它该放进 CLAUDE.mdSkill 还是 Prompt

任务三:先写一个 5 到 10 行版本
先写触发条件、执行步骤、输出格式、注意事项。不要追求完美,先拿去用一次,再立刻改第一轮。

真正好的 Skill,几乎都不是第一次就写对的,而是在实际使用里慢慢长出来的。


下篇预告

第 06 篇:Sub-agents 实战——什么时候应该拆任务,怎么设计子任务边界

单个 Claude 实例有上下文上限,复杂任务拆成多个子任务让 Sub-agents 并行处理,理论上能大幅提速。但什么时候值得拆?拆错了会有什么代价?下一篇会拆开 Sub-agents 的真实适用场景,以及最常见的过度设计陷阱。


写在最后

公开 Skill 模板当然有用,但它的价值更像脚手架,而不是成品。

真正管用的 Skill,不是 star 最多的那个,也不是字段最全的那个,而是最贴近你项目真实工作流的那个。

你不需要一个很复杂的 Skill 系统。

你需要的,往往只是把团队最容易反复犯错的那几件事,提前写下来,让 Claude 每次都替你盯住。

这才是 Skill 真正应该发挥的作用。

如果你已经开始写 Skill 了,我反而建议你先检查一个问题:

你现在最卡住的,到底是“写不出规则”,还是“根本没分清哪些该放 CLAUDE.md、哪些该做成 Skill”?

这两个问题看起来很像,但解法完全不同。


AI Coding 系列持续更新。用别人的 Skill 模板是起点,不是终点。真正管用的 Skill,只有你自己的项目才能提炼出来。

昨天以前首页

连载04-CLAUDE.md ---一起吃透 Claude Code,告别 AI coding 迷茫

2026年4月8日 17:24

CLAUDE.md 完整指南——让 Claude 真正理解你的项目

AI Coding 系列第 04 篇 · CLAUDE.md 到底是什么:不是文档,而是 Claude 的规则层


CLAUDE.md 被严重误解

很多人对 CLAUDE.md 的理解有偏差。有人把它当项目文档来写,两百行的架构介绍、API 清单、数据库设计,然后疑惑为什么 Claude 经常无视其中的规则。有人复制了一个通用模板,放在那里从来不改。还有人干脆不知道它到底是干什么的。

这类误解有一个共同点:
CLAUDE.md 当成了“给 AI 看的项目说明书”。

CLAUDE.md 的本质不是文档,而是规则层

它不是用来完整介绍项目的,而是用来告诉 Claude:

  • 这个项目里哪些边界不能碰
  • 哪些行为默认是错的
  • 哪些约定会反复影响决策
  • 哪些高风险区域必须更保守

如果把它写成“项目背景”,Claude 最多只是“看过了”;
如果把它写成“行为规则”,Claude 的默认工作方式才会真正改变。

所以更准确的定义是:

CLAUDE.md 不是项目文档,而是把稳定偏好、高风险边界和重复纠正,提前变成 Claude 默认上下文的规则层。


一、CLAUDE.md 到底解决什么问题

一个好用的 CLAUDE.md,主要解决四类问题。

1. 把反复提醒的内容沉淀下来

如果你总是在 prompt 里反复说这些话:

  • 这个项目不要改 .github/workflows
  • 错误统一用 AppError
  • 不要默认新增依赖
  • 数据库变更前先讲回滚方案

那这些内容就不该每次重新说,而应该进入 CLAUDE.md

2. 给 Claude 的默认积极性加边界

Claude 默认会尽量帮你完成任务,但很多项目里真正危险的,不是它不做事,而是它做得太多。

比如:

  • 看见旧代码就想顺手重构
  • 看见没测试就想补一整套基础设施
  • 看见当前实现笨重就建议换栈

这些行为在通用场景里未必错,但在具体项目里可能是噪音,甚至是风险。
CLAUDE.md 的一个重要作用,就是给这种默认积极性划边界。

3. 把“代码里看不出来”的规则显式化

很多项目真正重要的约束,并不直接写在代码里。

例如:

  • 某个目录是历史包袱区,轻易别碰
  • 某些 migration 一旦上线后绝不能回写修改
  • 某个模块表面简单,背后连着外部系统
  • 某类接口一改就会影响前端联调和埋点

这些东西人类同事待久了会知道,但 AI 初来乍到不会知道。
CLAUDE.md 的价值,就在于把这些隐性知识提前说透。

4. 降低上下文成本

技术栈、关键路径、错误处理方式、依赖策略、部署边界,这些稳定规则本来就适合长期存在。把它们放进 CLAUDE.md,每次 prompt 就能专注当前任务,而不是重复灌输基础背景。


二、它不只是纠错层,也是预防层

前面说 CLAUDE.md 是纠偏器,这个说法是对的,而且很重要。因为它能一下子把很多人从“项目文档思维”拉回来。

但如果只停在“纠偏器”这一层,对它的理解还是不完整。

更准确地说,CLAUDE.md 既是纠错层,也是预防层

1. 纠错层:把重复犯的错写成规则

比如:

  • 你已经说过两次不要直接 throw new Error()
  • 你已经纠正过几次不要改 .github/workflows
  • 你已经反复提醒过不要随便 npm install

这些都属于典型的“纠错”。

2. 预防层:提前声明高代价边界

真正好用的 CLAUDE.md,并不只是在事后补锅。它还有一个同样重要的作用:提前声明那些一旦做错,代价就很高的边界。

比如:

  • 支付模块改动前先确认幂等逻辑
  • migration 文件上线后只能新增,不能回写修改
  • 生成目录不要手改,因为下次生成会覆盖
  • 新增重大依赖前先说明必要性和替代方案

这些规则不一定是 Claude 已经犯过的错,也可能是你提前告诉它:

“这里不是不能动,而是这里的错误成本很高,你默认要更保守。”

所以从完整定位上说,CLAUDE.md 的作用不是单纯“记录反复犯的错”,而是:

把稳定偏好、风险边界和高代价约束,提前变成 Claude 的默认工作上下文。

CLAUDE.md 的真实定位

图:CLAUDE.md 不是项目文档,而是纠偏层 + 预防层 + 长期约束层。


三、文档式写法 vs 纠偏式写法

说一百遍不如直接对比。

❌ 文档式写法(Claude 读了,但行为不变)

本项目是一个电商平台,使用 Node.js + Express + TypeScript 开发,
数据库采用 PostgreSQL,通过 Prisma 进行 ORM 管理。
项目包含用户模块、订单模块、支付模块和通知模块,
遵循 RESTful API 设计规范……
✅ 纠偏式写法(Claude 读了,行为立刻改变)

- 禁止 throw new Error(),统一用 AppError 类
- API 响应必须含 success / data / timestamp 三个字段,不能自己发明格式
- 禁止在 controller 层直接写 SQL,必须通过 service 层
- 所有异步函数必须有 try-catch,不靠外层中间件兜底
- 新增依赖前必须问我,不要自行 npm install

文档式写法让 Claude “知道”了,但知道不等于行动。
纠偏式写法告诉 Claude:“在这个项目里,你的默认行为哪里不对。” 这才是它真正听进去的语言。

判断一条规则是不是纠偏式,只用问一个问题:

这条规则是在纠正 Claude 的某个具体行为,还是在描述项目背景?

能对应到一个具体行为变化的,是纠偏。
其他的,是文档。


四、它和 Prompt、文档、Memory、Skill 的边界

很多人用不好 CLAUDE.md,不是不会写规则,而是把它和别的东西混在一起了。

最容易混淆的有四个对象:Prompt、项目文档、Memory、Skill。

CLAUDE.md 和其他机制的边界

图:Prompt 管当前任务,文档管背景,Memory 管自动沉淀,Skill 管重复流程,CLAUDE.md 管稳定规则。

源码里的分工也很明确

如果去看 Claude Code 的源码,CLAUDE.mdMemory 的边界其实分得很清楚。相关实现可以看 src/utils/claudemd.ts。在这部分实现里,CLAUDE.md 被归在一套明确的 instruction loading 顺序里:

  1. Managed memory:全局托管规则
  2. User memory:~/.claude/CLAUDE.md
  3. Project memory:仓库里的 CLAUDE.md.claude/CLAUDE.md.claude/rules/*.md
  4. Local memory:CLAUDE.local.md

这套机制本质上是在加载指令文件

而同一个文件里又能看到另一套独立机制:当 auto memory 打开时,系统会额外读取 getAutoMemEntrypoint() 返回的 MEMORY.md,其类型是 AutoMem,团队记忆则是 TeamMem。源码里甚至专门写了注释:

AutoMem/TeamMem are intentionally excluded — they're a separate memory system, not "instructions" in the CLAUDE.md/rules sense.

这句话非常关键。它说明:

  • CLAUDE.md 这一层,本质上是 instructions / rules
  • MEMORY.md 这一层,本质上是 auto memory / persistent memory

它们最后都会进入上下文,但在架构里并不是同一个东西。

所以如果从源码上更严格地说,CLAUDE.md 不是 MEMORY.md 的别名,更不是 auto-memory 的索引。
真正扮演“索引 + 主题文件”角色的,是后面的 MEMORY.md 系统。

1. Prompt 负责当前任务

Prompt 解决的是这一次你到底要 Claude 做什么。

比如:

  • 这次只修 bug,不要顺手重构
  • 这次只分析原因,先不要改代码
  • 这次只改前端,不动后端

这些都是单次任务边界,适合写在 prompt 里,不适合沉淀进 CLAUDE.md

2. 项目文档负责完整背景

README、设计文档、接口文档、架构说明,负责回答的是:

  • 这个项目是什么
  • 系统怎么设计
  • 模块如何划分
  • 业务流程怎么走

这些内容通常信息量大、细节多、更新频繁,它们的职责是“说明项目”,不是“约束 Claude 的默认行为”。

3. CLAUDE.md 负责稳定规则

CLAUDE.md 解决的是那些跨多次任务都成立、而且会持续影响 Claude 决策的东西。

比如:

  • 高风险文件和目录
  • 错误处理规范
  • 依赖策略
  • migration 边界
  • 哪些行为必须先确认

它不负责讲完整背景,只负责把真正影响行为的规则提炼出来。

4. Memory 负责自动沉淀

它更像 Claude 在长期协作里逐步学到的东西,是补充,不是替代。

你可以把它理解成“模型慢慢记住了你们项目里的某些偏好和事实”,但这类记忆不适合代替明确规则。因为对于关键边界来说,你明确写下来的东西,永远比它自己学到的更稳。

结合源码看,这个分工会更清楚:

  • CLAUDE.md 通过 instruction loader 进入系统 prompt
  • MEMORY.md 则是 auto memory 的入口文件
  • 相关 topic files 会在需要时被检索和召回,而不是把所有细节都塞进一个大文件

因此,更准确的理解是把它们视为“两套协作机制”,而不是“一份文件的两种叫法”。

5. Skill 负责重复流程

CLAUDE.md 管的是“长期规则”,Skill 更适合管“这类任务应该怎么做”。

比如:

  • 需求分析怎么展开
  • Code Review 按什么顺序做
  • 排查线上 bug 用什么流程
  • 新功能开发先看哪些文件、再做哪些验证

这类内容本质上是“做事模板”,更像流程,不像规则。

可以概括成一句话:

  • 当前任务Prompt
  • 稳定规则CLAUDE.md
  • 完整背景进项目文档
  • 自动沉淀交给 Memory
  • 重复流程沉淀成 Skill

一旦边界分清楚了,很多人最头疼的那个问题就会自动消失:

为什么我明明写了很多东西,但 Claude 还是不按我想的来?

因为你很可能把应该放在不同位置的信息,全塞进了 CLAUDE.md


五、三层分层架构

CLAUDE.md 不是一个单一文件,而是一个分层的规则系统。

三层分层架构

图:先按稳定性分层,再决定规则应该写到用户级、项目级还是公司级。

用户级~/.claude/CLAUDE.md
你电脑上所有项目都生效,写个人偏好。

项目级:仓库根目录的 CLAUDE.md
只在这个项目生效,写项目特有约定,提交进 Git。

公司级:企业统一管理的配置位置
整个组织生效,写合规要求和架构标准。大型企业才更常用,普通团队通常不需要。

判断一条规则放哪层,只用一个标准:

换个项目还成立吗?

成立放用户级。
比如:“我的变量命名用驼峰。” 换到任何项目都一样。

不成立放项目级。
比如:“这个项目用 Prisma,禁止用 Sequelize。” 换到 MongoDB 项目就不适用了。

这个区分看起来简单,但它直接决定后面的维护成本。


六、用户级:写你的默认行为偏好

用户级规则要少而精,不超过 50 行。这里写的是覆盖 Claude 默认值的个人偏好。

# 我的个人偏好

## 代码风格
- 缩进:2 个空格
- 变量命名:camelCase,类名 PascalCase
- 单行不超过 100 字符

## 我固定用的库(不要建议替代品)
- 日期处理:date-fns,不用 moment.js
- HTTP 请求:axios,不用 node-fetch
- 测试:Jest,不用 Vitest 或 Mocha

## 从不做的事
- 不要在我没要求时修改测试文件
- 不要建议我换版本控制工具
- 不要在随意讨论时提出架构大改动

## Git 提交格式
feat(模块名): 简短描述

- 改动说明 1
- 改动说明 2

注意措辞:写的是“我的偏好”,不是“你必须”。前者 Claude 当作信息接收,后者听起来像命令,反而可能在某些场景被跳过。

用户级不该写什么

  • 一次性的任务背景
  • 大段项目文档
  • 经常变动的技术现状
  • 只在某个仓库成立的规则

比如:“我现在在做一个电商系统。” 这不是偏好,是当前任务。应该放在 prompt 里。


七、项目级:记录这个项目特有的边界

项目级可以稍长,100 行左右。核心是三类内容:

1. 关键文件保护

## 禁止修改的文件
- src/config/env.ts — 改了会影响生产环境变量加载
- .github/workflows/* — CI/CD 流水线,改动需要 DevOps 审核
- 数据库 migration 文件一旦执行,不得修改,只能新增

2. 编码规范,必须具体到代码动作

## 错误处理
统一使用 AppError 类,禁止 throw new Error():
throw new AppError('用户不存在', 404, 'USER_NOT_FOUND')

## API 响应格式
所有响应必须符合:
{ "success": true, "data": {}, "timestamp": "ISO字符串" }
错误响应:
{ "success": false, "error": "ERROR_CODE", "message": "描述" }

3. 高风险路径标注

## 高风险区域(修改前必须告知我)
- src/modules/auth/* — 认证核心,任何改动都需要 review
- src/handlers/payment/* — 对接支付商,出错直接影响收入
- src/database/migrations/* — 不可逆操作,要有回滚方案

项目级真正决定效果的,不是“把整个项目介绍一遍”,而是:

把这个仓库里最容易做错、最不能做错的东西写出来。


八、一条好规则到底该怎么写

很多人不是不会列规则,而是写出来之后没有约束力。

比如:

  • 代码要整洁
  • 数据库迁移要小心
  • 不要随便改配置

这些话人类看得懂,但模型不一定知道“到底怎样做才算遵守”。

一条好规则,尽量包含这几个元素:

  • 触发场景
  • 期望动作
  • 禁止动作
  • 原因
  • 示例

一条好规则怎么写

图:好规则最少要把场景、动作、边界和原因交代清楚。

看一个例子就很清楚。

❌ 只有规则
- 使用 Prisma 生成迁移,不要写原生 SQL
✅ 规则 + 原因 + 行为边界
- 涉及 schema 变更时,优先走现有 migration 工作流,不要临时手写 SQL 直接改结构。
  原因:团队的审查、回滚和环境同步流程都围绕当前 migration 体系建立。
  如果必须做破坏性变更,先说明影响范围和回滚方案。

再比如:

❌ 太抽象
- 注意统一错误处理
✅ 可执行
- 所有业务异常统一使用 AppError,禁止直接 throw new Error()。
  原因:前端依赖统一错误码和 message 做提示与埋点归类。

关键就在这里:

CLAUDE.md 不是写原则,而是写可执行规则。


九、为什么有时有效,有时又像没生效

这也是很多人真正困惑的地方。

不是所有写进 CLAUDE.md 的内容,效果都一样。有些规则一写进去,Claude 的行为马上变化;有些规则写了之后,几乎没感觉。

通常不是因为它“没读”,而是因为规则本身写得不够能执行。

第一,规则写成了背景介绍

例如:

本项目采用分层架构,强调可维护性和扩展性。

这句话是背景,不是约束。Claude 即使看到了,也很难从里面推导出具体行动。

第二,规则太抽象

例如:

- 代码要整洁
- 注意性能
- 数据库修改要谨慎

这些话人类看得懂,但模型不知道“怎样才算遵守”。

第三,规则太多,信噪比下降

不是说长文一定不好,而是低价值内容一多,真正重要的规则就会被埋掉。

如果一份 CLAUDE.md 里面既有项目概述、又有接口说明、又有架构文档、又有零碎提醒,那 Claude 真正应该优先遵守的那些边界,反而不够突出。

第四,规则之间互相冲突

比如你在用户级里写了“我习惯四空格缩进”,项目级里又写“这个项目统一两空格”,但没有说明项目级覆盖团队标准。
这种情况下,Claude 不是一定做错,而是判断空间会变大。

第五,单次任务 prompt 和长期规则打架

如果你在 CLAUDE.md 里长期写“默认不要大改”,但当前 prompt 又说“请你重构这一块并统一风格”,那单次任务会临时改变优先级。

这不是 CLAUDE.md 失效,而是上下文优先级在变化。

真正决定它能不能稳定生效的,是三件事:

规则足够具体,边界足够清楚,信噪比足够高。只有这三件事同时成立,CLAUDE.md 才会真正改变行为。


十、它很重要,但不是万能控制器

把这一点想清楚,对 CLAUDE.md 的期待反而会更稳。

CLAUDE.md 很强,但它不是万能控制器。它做不到下面这些事:

  • 它不能替代清晰的任务描述
  • 它不能替代 README 和设计文档
  • 它不能替代你对复杂任务的即时判断
  • 它不能保证 Claude 在任何场景下 100% 机械执行

它真正擅长的是:

  • 让默认行为更接近你的项目习惯
  • 让高风险边界更早暴露
  • 让重复提醒沉淀成长期规则
  • 让每次 prompt 更聚焦当前任务

所以最好的理解方式不是:

“只要我把 CLAUDE.md 写好了,后面什么都不用管了。”

而是:

“我用 CLAUDE.md 把稳定规则立住,再用 prompt 管当前任务,用文档承载背景,用 Skill 沉淀流程。”

只有在这套分工里,CLAUDE.md 的作用才会既强,又稳定。


十一、连接第 03 篇:为什么它能解决“纠正回退”

第 03 篇讲过一个现象:你在对话里纠正了 Claude,它承认了,但过几轮又犯同样的错。这不是 Claude 不配合,而是对话历史会随时间衰减,纠正效果也会随之消退。

更稳定的纠正方式,就是写进 CLAUDE.md

写进 CLAUDE.md 的规则,每次对话开始时都会被系统自动注入,不受对话长度影响,也不会像临时纠正那样快速衰减。

判断标准很简单:

同一件事你纠正了两次以上,就应该写进 CLAUDE.md,不要再在对话里重复说。

# 这条规则在对话里说了三次,该进 CLAUDE.md 了:
- 日志统一用 logger.info/warn/error,禁止 console.log

十二、Claude 会主动学习,但它补充不了规则层

CLAUDE.md 不是单向的。你往里写规则,Claude 也会在长期协作中逐步积累知识。

每轮对话结束后,Claude Code 会在后台启动一个独立的子 Agent,分析对话里有没有值得保留的项目知识,自动写入 Memory 文件,下次会话时注入:

对话结束 → 后台子 Agent 分析 → 提取项目偏好和技术决策
→ 写入 ~/.claude/projects/[项目]/memory/ → 下次会话自动读取

你在某次对话里说了“我们禁止用 moment.js,改用 date-fns”,下次打开 Claude Code,它已经记得了。

几个要知道的细节:

它补充 CLAUDE.md,不取代它。
自动记忆是“Claude 学到的”,CLAUDE.md 是“你明确要求的”,关键约束还是应该写在 CLAUDE.md 里。

明确说出来的比隐含的更容易被记住。
在对话里直接说“我们统一用 date-fns”,提取率更高;只是悄悄在代码里换了库,Claude 可能记不到。

你可以检查它记了什么。
/memory 命令可以查看当前记忆内容,发现记错了直接改,它本质上还是普通文本文件。

实际效果是:Claude Code 越用越懂你的项目。头几天需要反复解释背景,用了几周后,很多背景已经自动沉淀,你的 prompt 可以越写越短。

Memory 的索引 + 主题文件结构

图:从源码看,Memory 更像索引入口 + topic files,而不是一个无限膨胀的大文件。

从源码看,Memory 本质上是一套“索引 + 主题文件”的结构

从实现上看,auto memory 不是把内容都堆在一个文件里。相关实现可以看 src/memdir/memdir.ts。在这部分实现里,入口常量就是:

export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000

这三行信息已经说明了很多问题:

第一,真正被当作 memory 入口文件的,是 MEMORY.md,不是 CLAUDE.md
第二,系统从设计上就不希望这个入口文件无限膨胀。
第三,memory 架构默认就不是“把所有内容堆在一个大文件里”。

同一个文件里,源码把保存流程直接写成了两步:

  1. 先把记忆写入独立主题文件
  2. 再在 MEMORY.md 里增加一个索引指针

源码注释原话基本就是这个意思:

  • Step 1:write the memory to its own file
  • Step 2:add a pointer to that file in MEMORY.md

而且它还专门强调:

MEMORY.md is an index, not a memory

从实现上看,Claude Code 的 auto memory 更像:

  • MEMORY.md:目录页 / 索引页
  • topic files:按主题拆开的详细内容

这也解释了一个很多人会问的问题:

如果记忆越积越多,MEMORY.md 不会越来越大吗?

答案是:源码层面已经考虑了这个问题。

truncateEntrypointContent() 会对 MEMORY.md 做双重限制:

  • 超过 200 行会截断
  • 超过 25KB 也会截断

截断后甚至还会追加警告,提醒把细节移到 topic files,只把一行短索引留在 MEMORY.md

换句话说,这套设计本身就在强制你保持:

  • 索引足够短
  • 细节分散到主题文件
  • 入口文件永远尽量装得进上下文

这和 CLAUDE.md 的定位,是什么关系

最容易混在一起的,恰恰是规则系统和记忆系统。

如果站在源码架构的角度看:

  • CLAUDE.md 更像 instruction layer
  • MEMORY.md 更像 memory index layer
  • topic files 更像 memory payload layer

这三层不是互相替代,而是互相配合。

所以把 CLAUDE.md 定义成“规则层”是成立的,而且和源码是对齐的。

放到 Claude Code 的完整架构里看,CLAUDE.md 负责规则,MEMORY.md 负责记忆索引,topic files 负责详细内容。

这样去理解,规则、索引和记忆详情各自负责什么,就不会再混成一团。

从源码看“自愈”和写入一致性

把这套机制类比成一种带“自愈”倾向的写入纪律,可以作为理解辅助,但不宜把类比直接当成源码结论。

从目前能看到的实现和解析文档来看,至少可以确定三件事:

  • memory 保存采用“先写主题文件,再更新 MEMORY.md 指针”的两步方式
  • 这种顺序天然更有利于一致性,因为索引最终指向的是已经成功落盘的内容
  • 它的思路更接近“先落数据,再更新索引”,和很多数据库 / 存储系统的一致性设计取向相似

更稳妥的理解是,把它当作一种 可以类比理解 的一致性思路,而不是直接把它等同于“源码明确实现了 WAL 逆向”。

因为源码里我能确认的是:

  • 两步保存存在
  • MEMORY.md 是索引存在
  • 入口大小控制存在
  • 按需检索 topic files 存在

这些都是可以直接从源码和解析文档里站得住的。


十三、两个最常见的陷阱

陷阱一:写得太多,关键规则被淹没

CLAUDE.md 写得太长时,Claude 往往只会抓住其中最显眼、最强约束的那部分,其他内容会逐渐退化成背景噪音。规则越多,真正稳定生效的比例通常越低。

解决方法:

  • 定期删掉已经不再是问题的规则
  • 删掉太细节、没有行为约束力的规则
  • 删掉重复表达

CLAUDE.md 应该是个活跃的 hotlist,不是越来越臃肿的文档。

陷阱二:规则放错层级

用户级放了项目特有规则,Claude 在其他项目里也按这个来。
项目级放了所有项目通用规则,十几个项目各自维护一份重复内容,改一条要改十几个地方。

解决方法还是那一句:

换个项目还成立吗?

成立放用户级,不成立放项目级,一次定好就别再改。


十四、维护节奏

CLAUDE.md 写好之后不是扔着不管,需要定期维护。

第一个月:初始化

/init 生成草稿,花半小时补充:

  • 关键文件保护
  • 错误处理规范
  • API 格式约定
  • 高风险路径说明

这是最重要的一次,做好了后面会省很多事。

每两周:维护

回顾最近 Claude 犯过什么错。

  • 同一个错出现两次以上,加进 CLAUDE.md
  • 已经不构成问题的规则,删掉
  • 写得太空的规则,改具体一点

每季度:清理

把整个文件读一遍:

  • 删冗余
  • 合并重复
  • 简化过细规则

目标是让文件保持高信噪比,而不是越写越长。


十五、检查清单

提交项目级 CLAUDE.md 前过一遍:

  • 规则是纠偏式的,不是文档式的
  • 每条规则能对应到 Claude 的一个具体行为变化
  • 关键文件有明确的保护声明
  • 高风险路径有标注和警告
  • 重要规则附上了“为什么”
  • 用户级和项目级没有混放
  • 文件总长度不超过 200 行
  • 对话里纠正过两次以上的规则已经写进来了

本篇实践任务

任务一: 打开你现有的 CLAUDE.md,把里面每条规则过一遍:它是纠偏式,还是文档式?把文档式的删掉或者改成纠偏式。

任务二: 回想最近一周,你在对话里纠正过 Claude 几次同一个问题?把这些问题整理成具体规则,写进 CLAUDE.md,下次对话观察效果。

任务三: 运行 /memory,看看 Claude 已经自动记住了什么。和你的 CLAUDE.md 对比,有没有重复的内容?有没有记错的内容需要修正?


下篇预告

第 05 篇:Skill 提炼——把重复任务沉淀成可复用模板

CLAUDE.md 管的是全局规则,Skill 管的是任务模板。当同一类任务反复出现,把“怎么做这类任务”浓缩成一个 Skill,下次直接触发。下一篇会讲什么时候沉淀 Skill、怎么写一个真正有效的 Skill,以及 Skill 和自定义命令的边界在哪。


AI Coding 系列持续更新。CLAUDE.md 是规则层,不是项目文档。写法不同,效果天壤之别。

连载03-commands ---一起吃透 Claude Code,告别 AI coding 迷茫

2026年4月3日 17:59

为什么 Claude Code 要有指令(Commands):本质是上下文管理

AI Coding 系列第 03 篇 · 上下文与 Commands


这篇想回答三个问题:

  1. 为什么一开始很好用的 Claude Code,会在长会话里越来越"跑偏"?
  2. 为什么有些纠正你明明说过很多次,它还是会回退?
  3. 为什么 Claude Code 要用 /clear/compact/memory 这种命令,而不是全做成按钮?

如果你已经是高频用户,这篇不会提供很多新奇技巧,但会把这些现象背后的机制串成一套可操作的框架。对刚进入稳定开发阶段的用户,这比记住几个命令更重要。


先给结论

Claude Code 里的很多命令,看起来像快捷操作,实际上是在帮你管理三个东西:

  1. Claude 现在看到了什么
  2. Claude 接下来应该忘掉什么
  3. 哪些规则不应该继续留在“会衰减的对话历史”里

如果只把 /clear/compact/memory 当成“方便一点的小功能”,你会低估它们的价值。它们真正解决的,是长会话里最常见、也最让人误判的协作问题:Claude 不是突然变差了,而是上下文开始劣化了。


你大概遇到过这种情况

开始一个新任务,头几轮对话质量很高——Claude 理解你的意图,给出准确的方案,代码风格和项目一致。

但聊着聊着,情况变了:它开始重提已经否掉的方案,重复犯你纠正过的错误,或者在一个局部问题上越绕越深,忘了你们最初要解决什么。

这不是 Claude 变笨了,也不是你的 Prompt 写差了。这是上下文劣化——一个有规律、可以预测、也可以干预的现象。


一个贯穿全文的真实场景

假设你在做一个登录模块,从头到尾大概会经历这样一条线:

  1. 前期你和 Claude 一起讨论技术方案,聊过 Session、JWT、Redis、PostgreSQL,最后决定先用 PostgreSQL + JWT,把主流程跑通。
  2. 接着你开始写代码,发现 Claude 老是顺手加 console.log,你纠正了它两次。
  3. 做到一半你又决定把 ORM 从 Sequelize 换成 Prisma,因为前者在这个项目里太重。
  4. 再往后,对话已经很长了,你只是问一个事务边界问题,Claude 却开始同时聊缓存、日志、前端错误提示和部署建议。

如果你不理解上下文管理,这整条链看起来像是:“Claude 前面挺聪明,后面越来越不靠谱。”

但如果从上下文角度看,这其实是四类不同问题叠在一起:

  • 前面讨论过但已经否掉的方案,还残留在历史里
  • 你纠正过的规则,还停留在会衰减的对话层
  • 已经废弃的 Sequelize 路线,还在干扰后续回答
  • 对话长度过大以后,Claude 的注意力开始发散

也正因为它们是四类不同问题,所以不能指望一个万能 Prompt 解决。你需要的不是“再说清楚一点”,而是把正确的动作放在正确的层级上:该 /compact 的时候压缩,该 /clear 的时候重开,该进 CLAUDE.md 的规则就不要继续留在聊天记录里。


为什么 Claude Code 用命令,而不是全做成按钮

刚接触 Claude Code 的人常问:为什么不把这些能力做成 GUI?为什么要用 /clear/compact 这种命令?

因为 Claude Code 管理的不是"功能开关",而是 Claude 此刻正在处理的内容。

举个对比:你在 VS Code 里点"开启 dark mode",改的是软件的配置文件,和编辑器当前打开了什么文件没关系。但你在 Claude Code 里输入 /clear,清掉的是 Claude 脑子里正在处理的所有东西——之前讨论过的方案、做过的决策、来来覆去的对话,全部抹掉,让它重新认识你。这不是改配置,是在决定 Claude 此刻"知道什么、记得什么"。

命令形式在这里有几个优势:它可重复,你可以稳定复现同一种操作;它可传达,你可以直接告诉同事"先 /clear 再开始";它也更容易固化成工作流,比如"任务切换先 /clear,长会话中途 /compact,提交前 /review"。对还在快速迭代的 AI 工具来说,命令也比 GUI 更容易快速交付新能力。

所以 Claude Code 的命令系统,本质上是一套让你主动管理 AI 工作现场的操控面板


四种上下文劣化模式

Claude Code 的 4 种上下文劣化转存失败,建议直接上传图片文件

模式一:早期探索污染后期决策

任务开始时,你和 Claude 在讨论方案,思路是发散的。"要不要用 Redis?""用 JWT 还是 Session?""这个表要不要拆?"——这些都是探索性的讨论,很多想法最终被否决了。

问题是:这些被否决的想法还留在上下文里,和最终确定的方案并排存在。Claude 看到的是一串讨论记录,它会对"用 Redis"和"不用 Redis"这两种可能性都保持某种权重。对话越长,早期探索的内容就越像已确认的决策。

例如:

你:认证这块先别上 Redis,先用 PostgreSQL 把主流程跑通。
Claude:好,先按 PostgreSQL 方案实现。
...
(十几轮后)
Claude:为了提高性能,建议把 session 放到 Redis。

你明明在第 5 轮确认了用 PostgreSQL,第 25 轮它开始建议你考虑一下 Redis 的方案。

模式二:纠正回退

你发现 Claude 用错了某个写法,纠正了它。它承认了,改对了。五轮之后,它犯了同样的错误。

这是因为"纠正"发生在对话的第 8 轮,而你现在在第 15 轮。纠正的内容在上下文里的位置越来越靠后,在 lost-in-the-middle 的注意力分布下,它的权重持续降低,直到 Claude 实际上已经"忘了"那次纠正,重新回到了训练数据里的默认行为。

例如:

你:不要用 console.log,用 logger.info。
Claude:好的,我改成 logger.info。
...
(五轮后)
Claude:这里我先加一个 console.log 方便排查。

你已经说了三次"不要用 console.log,用 logger.info",但它还是偶尔会写 console.log

模式三:废弃方案的幽灵

你尝试了一种实现方式,做到一半发现不对,放弃了,换了另一种方式。旧的代码删掉了,但关于旧方案的讨论还留在上下文里。

这段"废弃的历史"会持续影响 Claude 的输出——它可能在新方案里混入旧方案的逻辑,或者在你遇到问题时建议你回到旧方案,因为旧方案在它的上下文里看起来也是一个"被讨论过的合理选项"。

例如:

你:Sequelize 这条路不走了,切到 Prisma。
Claude:明白,后续都按 Prisma 来。
...
(后来你问一个查询问题)
Claude:你可以在 Sequelize 的 include 里这样写...

你已经换掉了 ORM,但 Claude 还在参考旧 ORM 的写法给你示例。

模式四:注意力发散

随着上下文增长,Claude 需要处理的信息量越来越大。注意力是有限的——分给了 A 就少给了 B。越是长对话,Claude 越难在某一个具体问题上保持高度聚焦。它的回答开始变得面面俱到但不够深入,或者在你问一个具体问题时夹带了很多你没问的背景讨论。

例如:

你:只看这个接口的事务边界,哪里可能有问题?
Claude:这个接口本身有事务问题。另外认证模块、日志方案、缓存策略、
      前端错误提示也建议一起调整...

任务越来越复杂,但 Claude 的回答越来越泛,不够犀利。


理解了劣化,命令就有了意义

这四种模式,对应的干预手段是不同的:

劣化模式 干预手段 原因
早期探索污染 /compact 明确声明保留哪些决策 压缩时主动过滤探索内容,只留结论
纠正回退 把纠正写进 CLAUDE.md 从"对话历史"变成"系统注入",每轮强制生效
废弃方案幽灵 /clear 重开一个干净会话 彻底清除废弃历史,而不是试图覆盖它
注意力发散 拆任务 + 每个任务独立会话 每个会话只有一个聚焦点

但这些命令本质上是不同类型的东西,可靠性和适用场景差别很大——这是大多数人没意识到的。


5 条最实用的决策规则

如果你只想带走最实用的部分,可以先记这 5 条:

  1. 任务已经切换,就先 /clear。不要让上一个任务的残留背景继续污染当前任务。
  2. 任务没切,但会话已经很长,就 /compact。而且最好写清楚“只保留哪些已确认决策”。
  3. 同一条规则纠正两次以上,就别再聊了,直接写进 CLAUDE.md
  4. 你要复用的是“提示词流程”,就做成自定义 command;你要复用的是“带权限约束的能力”,就做成 skill。
  5. 当 Claude 开始“什么都懂一点但什么都答不深”时,优先怀疑上下文过载,而不是先怀疑模型突然变差。

这 5 条的价值,在于它们能把“模糊感觉”迅速翻译成明确动作。真正影响协作质量的,不是你知不知道这些概念,而是你能不能在出现问题的当下做对动作。


Claude Code 命令的三种类型

Claude Code 的 3 类指令 / Commands转存失败,建议直接上传图片文件

第一类:CLI 状态操作命令

这些命令直接操作 Claude Code 进程的内部状态,不经过 AI 模型,执行的是确定性的代码逻辑。

/clear    → 直接清空内存里的对话历史数组
/cost     → 读取 token 计数器,格式化输出
/model    → 修改当前会话的模型配置
/memory   → 读取和展示 Memory 目录下的文件内容
/help     → 输出命令列表

关键特性:结果确定,不依赖 Claude 的理解。 /clear 必然清空,/cost 必然显示费用,不会因为你的 Prompt 写得好不好而有差异。这类命令是系统层面的操作,不是 AI 行为。

第二类:自定义 Slash Command(提示词模板)

这类命令存放在 .claude/commands/ 目录下,每个命令是一个 .md 文件。文件名就是命令名,文件内容就是命令触发时注入给 Claude 的提示词。

.claude/
  commands/
    review.md      → /review 命令
    pr-desc.md     → /pr-desc 命令
    standup.md     → /standup 命令

可以用 $ARGUMENTS 接收参数:

<!-- .claude/commands/review.md -->
对以下代码做专项 review,聚焦:$ARGUMENTS

检查顺序:
1. 安全漏洞(SQL 注入、权限校验缺失)
2. 边界条件和错误处理
3. 项目规范符合性(参考 CLAUDE.md)

每个问题标注严重等级:blocking / warning / suggestion

关键特性:本质上是一次对话,经过 AI 模型处理,结果有随机性。 你写的是提示词,不是程序——好的自定义命令写法和好的 Prompt 写法是一回事:具体、有约束、有明确的输出格式。

第三类:Skills(能力包触发)

Skills 比自定义命令更重,有完整的元数据配置:限制工具权限(allowed-tools)、指定触发条件(when_to_use)、选择模型(model)、设置上下文隔离(context: fork)。

---
name: security-audit
description: 安全审查
when_to_use: 审查代码安全漏洞时
allowed-tools:
  - Read
  - Grep
  - Glob
model: claude-opus-4-5
context: fork
---
 ${target} 执行安全审查...

Skills 触发时,会在隔离的子上下文里运行,工具权限是物理隔离的(不是靠 Claude 自律),结束后把结果返回主会话。

关键特性:比自定义命令更结构化,工具权限有硬约束,可以和主会话隔离运行。 适合封装有副作用、需要权限控制的操作。第 05 篇会专门讲 Skills 的设计。


三类命令的选用原则

需要确定性结果,不想靠 Claude 判断 → 第一类 CLI 命令。清空上下文、查费用、切模型,这些操作不该有歧义。

想复用一套工作流程,不需要特殊权限控制 → 第二类自定义命令。把反复用到的提示词结构固化下来,/standup/pr-desc/review 这类日常命令都适合。注意:它还是提示词,不是代码。

封装有风险的操作,或者需要隔离运行 → 第三类 Skills。权限隔离只有 Skills 能做到。


两个值得停下来想的洞见

什么时候该 /clear、/compact、改 CLAUDE.md转存失败,建议直接上传图片文件

把命令类型和劣化模式放在一起,有两个反直觉的结论。

洞见一:在对话里纠正 Claude 是徒劳的——这是机制决定的,不是你说得不够清楚

模式二"纠正回退"的根本原因不是 Claude 不配合,而是你在用错误的工具纠正它。

在对话里说"不要用 console.log",这条纠正被写进了"会随时间衰减的历史"——位置越来越靠后,注意力权重持续降低,最终必然回退。这不是偶然的,是 lost-in-the-middle 的机制决定的。

更重要的是,这件事其实很容易自己验证。你可以做一个小实验:

  1. 在纯对话里告诉 Claude:"不要用 console.log,用 logger.info。"
  2. 继续推进几轮任务,再让它生成新代码。
  3. 然后把同一条规则写进 CLAUDE.md,再重复一次类似流程。

大多数时候你会发现,两种方式的持久性差别非常明显。前者更容易回退,后者更稳定。这比单纯讲原理更有说服力,因为你能亲手看到规则所在层级不同,稳定性就不同。

真正有效的纠正只有一种:把规则从对话历史移进系统注入层。

# CLAUDE.md
- 日志统一用 logger.info/warn/error,禁止 console.log
- 所有异步函数必须有 try-catch,不依赖外层中间件捕获
- 禁止使用 any,类型必须明确

写进 CLAUDE.md 的规则,在每次对话开始时被系统自动注入,优先级高于对话历史,不会随对话长度衰减。这是 CLAUDE.md 存在的真实原因——不是"项目文档",是绕过对话历史衰减的唯一可靠手段

判断标准很简单:如果你对同一件事纠正了两次以上,就不该继续在对话里纠正,而应该把它写进 CLAUDE.md。

洞见二:/compact 不是无损压缩,它本身就是一次 AI 调用

很多人以为 /compact 是把历史"存档"了,实际上 Claude Code 在压缩时会调用模型生成摘要——这意味着压缩结果的质量,取决于 Claude 怎么理解这段历史。

这里不需要依赖源码猜。单从行为上你就能判断出来:/compact 不是简单的机械压缩,而是在"理解历史之后生成摘要"。

为什么这么说?因为如果它只是确定性的算法压缩,那么你补不补"保留说明",结果应该差异很小;但实际使用中,空着用和带明确保留说明用,摘要质量往往差很多。这更像是模型在根据你的提示重新组织历史,而不是程序在做无损归档。

你在 /compact 后面附加的保留说明,本质上就是在告诉 Claude:哪些内容应该成为压缩后的锚点。有没有写、写了什么,会直接影响压缩后的摘要长什么样。

这有两个实际含义:

第一,/compact 不在第一类"确定性命令"里——尽管它看起来是内置命令,但压缩结果是 AI 行为,不是代码行为,存在质量差异。

第二,空着用和带保留说明用,结果可以差很多:

❌ /compact
   → Claude 自己判断什么重要,探索性讨论和已确认决策同等对待

✅ /compact 只保留已确认的决策:JWT 方案、Prisma 数据库表结构、
            错误处理用 AppError 类。探索阶段被否决的方案不需要保留。
   → Claude 围绕这些锚点生成摘要,后续对话里这些决策记得最清楚

如果你一直在空着用 /compact,本质上是在让 Claude 替你决定什么值得记住。


两个可以立刻自己验证的小实验

如果你想判断这篇文章讲的是不是“经验之谈”,最好的办法不是相信我,而是自己试一下。

实验一:同一条规则,留在对话里 vs 写进 CLAUDE.md

找一条你平时经常纠正的规则,比如:

不要用 console.log,用 logger.info

先只在对话里说这条规则,继续推进几轮任务,再让 Claude 生成新代码。然后把同一条规则写进 CLAUDE.md,重新开始一个类似任务,再观察它的稳定性。

你大概率会看到一个很明显的差别:
留在对话里的规则更容易回退;写进 CLAUDE.md 的规则更稳定。

这个实验最重要的启发不是“CLAUDE.md 很有用”,而是:规则所在的层级不同,稳定性就不同。

实验二:空着 /compact vs 带保留说明 /compact

找一段讨论过很多方案的长会话。先在类似场景里直接执行:

/compact

再换一次,在压缩时明确写:

/compact 只保留已确认的决策:JWT 方案、Prisma 表结构、错误处理规范。
探索阶段被否掉的方案不保留。

然后比较压缩后的后续表现。你通常会发现,后者更不容易把探索阶段的噪音继续带下去。

这个实验说明的不是“提示词可以调得更好”,而是:/compact 本身就在重新组织历史,所以你不该把它当成无脑归档。


完整视图:五类控制机制

把命令扩展到所有控制机制,共五类:

类型 触发方式 经过 AI 可靠性
CLI 状态命令(/clear/cost 等) 手动输入 确定性
自定义 Slash Command 手动输入 依赖提示词质量
Skills 命令或自然语言触发 工具权限有硬约束
Hooks(PreToolUse 等) 工具执行事件自动触发 确定性
键盘快捷键(Plan Mode 等) 键盘操作 确定性

不经过 AI 的机制(CLI 命令、Hooks、快捷键)是确定性的,适合做强约束;经过 AI 的机制(自定义命令、Skills)有随机性,需要好的提示词设计,但表达能力更强。


这篇文章的边界

这里讲的现象,并不是 Claude Code 独有的怪癖,而是长上下文 AI 协作的一类共性问题。不同模型、不同客户端、不同版本,细节会有差别,但下面这几个事实不会变:

  • 对话历史不是长期稳定记忆
  • 早期探索内容会污染后续判断
  • 留在对话层的规则会衰减
  • 当上下文过长时,注意力一定会分散

所以这篇文章真正想讲的,不是“背命令表”,而是一个更底层的判断:

Claude Code 的核心问题,不是你会不会再写一个更长的 Prompt,而是你有没有把信息放在正确的层级上。

项目背景、长期规则、任务上下文、阶段性探索,这四种东西不应该混在一起管理。Claude Code 之所以有这些 Commands,本质上就是为了让你把它们拆开。


本篇实践任务

任务一: 找你最近一次“感觉 Claude 越来越糊涂”的会话,对照四种劣化模式,判断到底是哪一种在起作用,不要再笼统地归因于“模型变差”。

任务二: 检查你现在的 CLAUDE.md,有没有把“曾经在对话里纠正过不止一次”的规则写进去?把它们补进去,下次对话观察差异。

任务三: 做一次对照实验:一段长任务会话里分别试试“空着 /compact”和“带保留说明 /compact”,比较后续回答质量。

任务四:.claude/commands/ 里创建一个你最常用操作的自定义 command,比如 /pr-desc/standup,感受一下它和直接输提示词的区别。


下篇预告

第 04 篇:CLAUDE.md 完整指南——让 Claude 真正理解你的项目

你已经知道 CLAUDE.md 是"系统注入的长期记忆",优先级高于对话历史,是纠正回退的唯一可靠手段。但写什么进去、怎么写才能真正影响 Claude 的行为而不只是让它"读到",是另一个问题。下一篇专门讲这个。


AI Coding 系列持续更新。上下文劣化有规律,干预就有方法。

❌
❌