Skills:让 Claude 按需加载你的领域知识

0 阅读21分钟
  1. 软件开发行业的 AI 范式演进:从辅助工具到工程智能体
  2. Claude Code 全景入门:装好、看懂、跑起来
  3. 上下文注入:用 @ 和 ! 精准喂给 Claude 它需要的信息
  4. 记忆系统:用 CLAUDE.md 告别每次对话都要"重新认识你"
  5. 斜杠命令:把你的重复操作变成一个单词
  6. Hooks 机制:让 AI 的每一步都在你的规则里
  7. MCP:给 Claude Code 接上"外设"
  8. Skills:让 Claude 按需加载你的领域知识
  9. Sub-agents:给 Claude 分身,让专家各司其职
  10. Agent Teams:组建你的 AI 开发小队
  11. 安全与回退:给 AI 戴上"安全带"
  12. SDK 与 Headless:把 Claude 变成你的自动化引擎
  13. 上下文工程:从记忆文件到分层知识架构
  14. 模型选择与成本控制:把每一分钱花在刀刃上

qrcode_for_gh_6219b0488be5_258.jpg

有一段时间我觉得 CLAUDE.md 越来越不对劲。

项目刚开始那个文件只有三十来行——技术栈、目录结构、几条明确的禁令。用起来效果很好,Claude 对项目的背景理解很准确。

然后慢慢往里面加内容:数据库访问的规范、企微接口的调用约定、并发编程的注意事项、gRPC 接口设计约定、测试覆盖策略……两个月后,那个文件快两百行了。

然后 Claude 开始"忘事"了。写数据库查询时不按连接池规范来,goroutine 启动时忘了传 context,企微 access_token 的刷新机制搞错了。

不是 Claude 变笨了——是那两百行里的信息互相稀释。当它处理一个具体任务时,和这个任务不直接相关的内容也占着上下文窗口,真正相关的规则反而没被充分关注到。上下文窗口虽然很大(200K tokens),但它也是有限的稀缺资源——而且存在注意力稀释效应:窗口里信息越多,模型对每条信息的关注度越低。

Skills 是解法。不是把所有知识都堆进 CLAUDE.md,而是把不同领域的知识封装成独立的"能力包",Claude 处理相关任务时按需加载对应的包,不相关时完全不加载。

Skills 的定位:可操作知识

在理解 Skills 之前,先搞清楚它在 Claude Code 能力体系里的位置。Claude Code 有几种核心扩展机制,各自回答不同的问题:

  • Tools 回答"能做什么"——读文件、改代码、跑命令,是操作层面的能力
  • Hooks 回答"什么时候检查"——在关键节点自动触发验证或约束
  • Skills 回答"怎么做,以及何时做"——它不是工具,也不是检查点,而是可操作的领域知识

(还有一种是 Sub-agents,回答"谁来做"——下篇会详细讲。)

Skills 最重要的特征:它不是一份被动的参考文档等人去查,而是一段具备语义入口的标准操作程序。它通过 description 告诉 Claude 在什么情况下应该加载这项能力,通过正文定义执行步骤和规范,在需要时自动加载到 Claude 的认知空间中。

一份 API 设计指南放在 Wiki 上是静态文本——它等着人去阅读。封装成 Skill 之后,它变成了 Claude 在写 API 时自动到场的行为规范。从"人去查文档"变成"知识主动找到 Claude"。

Skills 和斜杠命令的关系

第三篇讲斜杠命令时已经提到了 Skills 格式。这里说清楚它们的关系。

在新版 Claude Code 中,Commands 已经合并到 Skills.claude/commands/review.md.claude/skills/review/SKILL.md 都会创建 /review,行为一样。你之前写的 .claude/commands/ 文件继续有效,不需要迁移。新建的话推荐用 Skills 目录,因为它支持辅助文件和更完整的 frontmatter 配置。如果同名共存,Skill 优先

但 Skills 比斜杠命令多了一个关键特性:可以由 Claude 自动识别和加载。

斜杠命令只能被人触发——你输入 /pre-check,它才跑。Claude 不会自己判断"现在应该跑一下 pre-check"然后主动执行。

Skills 不一样。你描述任务,Claude 扫描所有 Skill 的 description,通过语义推理判断当前任务是否需要某个 Skill。如果判断需要,它会主动加载那个 Skill 里的知识,按照 Skill 定义的规范做事。不用每次都提醒它"记得遵守数据库访问规范"——Claude 自己会判断。

这是从"指令"到"技能"的范式转变:斜杠命令是祈使句("去做这件事"),Skill 是陈述句("我是一种能做某件事的能力")。前者由人调度,后者由模型自主调度。

渐进式加载:Skills 的核心机制

这个机制是理解 Skills 价值的关键,值得单独说。

Claude Code 不会一启动就把所有 Skill 的完整内容全加载到上下文里。它用的是三层渐进式加载

第一层:description 常驻(目录页)。 所有 Skill 的 description 始终在上下文中,Claude 随时能看到"有哪些能力可用"。这一层很轻,每个 Skill 只占几十个字。就像图书馆的目录索引——你不需要读完所有的书,只需要扫一眼目录就知道哪些书和你当前的研究相关。

第二层:按需加载正文(章节)。 当 Claude 判断某个 Skill 和当前任务相关(或者你手动输入 /skill-name),才会加载那个 SKILL.md 的完整内容。这是"即时加载"——需要成为某个领域的专家时才去"读那本书"。

第三层:辅助文件按需读取(附录)。 SKILL.md 里可以引用同目录下的其他文件(详细参考文档、示例代码、脚本),Claude 只在确实需要时才去读取。

举个直观的例子:你有 5 个 Skill,每个完整内容 1000 tokens。如果全部常驻上下文,每次对话都要花 5000 tokens 在"可能用不到"的知识上。用渐进式加载,只有 description 常驻(大约 250 tokens),实际用到的那一个才加载完整内容——token 节省 78% 到 98%。 大多数请求只需要部分资源,渐进式加载的优势是累积的。

这就是 Skills 和"全塞进 CLAUDE.md"的本质区别。不是信息量变少了,是加载时机变精确了

两类 Skill:参考型和任务型

从工程角度,Skill 分为两类:

参考型 Skill 提供领域知识,Claude 自动判断什么时候需要加载。没有执行步骤,没有输出模板——它影响的是 Claude "怎么做",而不是"做什么"。比如数据库访问规范、API 设计约定。

任务型 Skill 执行有副作用的具体任务——跑测试、生成报告、提交代码。加了 disable-model-invocation: true,只能由人手动 /skill-name 触发。Claude 不会自作主张执行它们。

简单判断:如果这个 Skill 是"遵循这些规范",用参考型;如果是"执行这个操作",用任务型。

参考型 Skill 实战

Skills 放在 .claude/skills/<skill-name>/SKILL.md(项目级)或 ~/.claude/skills/<skill-name>/SKILL.md(用户级)。最简单的结构:一个目录,一个文件。

拿我们 Go 项目举例。数据库访问有一套团队规范——连接池怎么管理、事务怎么用、查询超时怎么设、错误怎么包装。这些规则以前塞在 CLAUDE.md 里,现在提取成 Skill。

.claude/skills/db-access/SKILL.md

---
name: db-access
description: 数据库访问规范:定义连接池管理、事务使用模式、查询超时设置和错误处理方式。
  当编写数据库查询、使用事务、管理数据库连接、处理 SQL 错误时自动应用。
allowed-tools: Read, Grep, Glob
---

# 数据库访问规范

## 连接池

所有数据库连接通过 `pgxpool.Pool` 管理,禁止手动创建单连接:

- 连接池在 `main()` 初始化,通过依赖注入传递
- 设置 `MaxConns``MinConns``MaxConnLifetime`
- 每个请求从池中获取连接,用完归还,不要长期持有

## 事务使用

- 只有涉及多条写操作时才开事务
- 事务函数签名统一用回调模式:

```go
func WithTx(ctx context.Context, pool *pgxpool.Pool, fn func(pgx.Tx) error) error {
    tx, err := pool.Begin(ctx)
    if err != nil {
        return fmt.Errorf("begin tx: %w", err)
    }
    defer tx.Rollback(ctx)
    if err := fn(tx); err != nil {
        return err
    }
    return tx.Commit(ctx)
}
  • 不要在事务内做外部 HTTP 调用
  • 事务超时不超过 5 秒

查询规范

  • 所有查询必须通过 context 传递超时:ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
  • 批量查询用 pgx.Batch,不要循环单条执行
  • 查询结果用 pgx.CollectRows 收集,不要手动 for rows.Next()

错误处理

  • 数据库错误统一用 fmt.Errorf("操作描述: %w", err) 包装
  • 检查特定错误码用 pgconn.PgError,不要匹配错误字符串
  • pgx.ErrNoRows 在业务层判断,不要在 repository 层吞掉

文件分两部分:**frontmatter**`---` 包裹)是配置元数据,**Markdown 正文**是 Claude 加载后需要遵循的知识。

这是一个**参考型 Skill**——它提供领域知识,Claude 在处理相关任务时自动加载、自动遵守。没有执行步骤,没有输出模板,不是"先做 A 再做 B",而是"遵循这些规范"。没有设 `disable-model-invocation`,所以 Claude 可以自动判断何时需要。工具只给了 `Read, Grep, Glob`——查阅规范不需要改代码。

## description:Skill 的灵魂

`description` 是 Skill 里最关键的字段。**它不是给人看的文档说明——它是给 Claude 看的触发器。**

Claude 接到任务时,扫描所有 Skill 的 description,通过语义推理判断该不该加载。这不是关键词精确匹配,而是语义理解——用户说"帮我查一下订单数据",Claude 能理解这和"数据库查询"相关。

**写法公式:[做什么] + [怎么做] + [什么时候用]**

```markdown
# 太模糊——Claude 什么时候都不确定要不要加载
description: 数据库相关规范

# 精准——Claude 能准确判断触发时机
description: 数据库访问规范:定义连接池管理、事务使用模式、查询超时设置和错误处理方式。
  当编写数据库查询、使用事务、管理数据库连接、处理 SQL 错误时自动应用。

如果你有多个 Skill 功能有重叠,description 要明确区分:

# 区分清楚,Claude 才能选对
name: db-access
description: 数据库访问规范:连接池、事务、查询超时。
  当编写 SQL 查询、管理数据库连接时使用。

name: db-migration
description: 数据库迁移规范:migration 文件命名、字段类型选择、索引策略。
  当创建新的 migration 文件、修改表结构时使用。

description 的字符预算:所有 Skill 的 description 加在一起有总量限制——大约 15,000 字符。如果 Skill 很多导致 description 被截断,可以在 Claude Code 里运行 /context 查看警告,通过环境变量 SLASH_COMMAND_TOOL_CHAR_BUDGET 调大预算。

任务型 Skill 实战

参考型 Skill 提供知识,Claude 自动触发。任务型 Skill 执行有副作用的具体操作——跑测试、生成报告、清理缓存。这类操作必须由人手动触发,加一个字段:disable-model-invocation: true

拿 Go 的基准测试举例。我们项目每次优化性能之后要跑 benchmark、分析结果、生成对比报告。这个流程固定,但不应该自动触发。

.claude/skills/go-bench/SKILL.md

---
name: go-bench
description: Go 基准测试分析:运行 benchmark 并生成性能报告。
argument-hint: [可选:指定包路径,如 ./internal/service/...]
disable-model-invocation: true
allowed-tools: Bash(go test:*), Read, Grep, Glob
---

运行 Go benchmark 并分析性能数据。

## 当前基准数据

项目模块:
!`find . -name '*_test.go' -exec grep -l 'func Benchmark' {} \; 2>/dev/null | head -10`

运行 benchmark:
!`cd $CLAUDE_PROJECT_DIR && go test -bench=. -benchmem -count=3 $ARGUMENTS ./... 2>&1 | tail -40`

## 分析要求

1. 识别每个 benchmark 的 ns/op、B/op、allocs/op
2. 标注分配次数异常高的用例(可能有优化空间)
3. 如果存在历史 benchmark 数据文件,做前后对比
4. 关注 hot path 上的内存分配

## 输出格式

### 性能概览
- 总共 N 个 benchmark,M 个有优化空间

### 需要关注
- `BenchmarkXxx` — allocs/op 偏高(当前 N,建议排查 M 处分配)

### 性能良好
- `BenchmarkYyy` — ns/op 和内存分配均在合理范围

### 优化建议(如有)
[具体建议,指向代码位置]

几个关键点:

disable-model-invocation: true:只能用 /go-bench 手动触发。Claude 不会自己跑它。加了这个字段之后,这个 Skill 的 description 不会加载到上下文——Claude 完全看不到它,也不占 description 预算。

allowed-tools:精确控制权限。Bash(go test:*) 只允许 go test 开头的命令,其他 shell 操作一律禁止。权限最小化是任务型 Skill 的铁律——不要随便用 Bash(*)

!command``:这是动态上下文注入——Skill 内容发给 Claude 之前,这些 shell 命令先在本地执行,输出替换进提示词。Claude 启动时就拿到了完整的 benchmark 数据,不需要再花几轮对话自己去跑。这个机制和斜杠命令里的一样,但在 Skill 中配合 $ARGUMENTS 特别好用。

$ARGUMENTS:接收调用时传的参数。/go-bench ./internal/handler/... 会把包路径传进去。注意 $ARGUMENTS 参数会先被替换,再执行 !command``,也就是用户输入会进入 shell 命令——所以 allowed-tools 必须严格限制。

如果需要多个独立参数,用 $1$2

将 $1 模块从 $2 迁移到 $3,保留所有现有功能和测试。

/migrate payment-service Express Fastify$1 = payment-service,$2 = Express,$3 = Fastify。

多文件 Skill 与契约式引用

当规范比较复杂时,可以拆分成多个文件。SKILL.md 作为入口和路由,辅助文件按需加载——这就是渐进式加载的第三层。

.claude/skills/db-access/
├── SKILL.md              ← 入口(控制在 500 行以内)
├── query-patterns.md     ← 详细查询模式和示例
├── migration-rules.md    ← 迁移操作规范
└── scripts/
    └── check-slow-query.sh  ← 辅助脚本

关键是引用的写法。不要只写一个路径——要写一个契约,告诉 Claude 什么时候该加载、加载后能得到什么:

# 弱引用——Claude 不知道何时该加载
详细内容见 `query-patterns.md`# 契约式引用——Claude 清楚加载条件和预期内容
## 查询模式
当需要编写复杂查询(JOIN、子查询、CTE)或优化现有查询时:
→ 加载 `query-patterns.md` 获取标准查询模式和性能注意事项

契约式引用三要素:触发条件(什么情况下加载)、文件路径(去哪找)、内容预期(加载后能得到什么)。

Claude 只在确实需要查看详细内容时才去读取辅助文件。好处是 Skill 主体保持简洁,细节按需加载。官方建议 SKILL.md 控制在 500 行以内——超过 500 行意味着你可能把"参考资料"混进了"路由指令",该拆出去了。

${CLAUDE_SKILL_DIR} 变量指向 Skill 所在目录,可以在 !command`` 里引用辅助脚本:

!`bash ${CLAUDE_SKILL_DIR}/scripts/check-slow-query.sh`

更多 frontmatter 字段

除了上面用到的字段,还有几个在特定场景下很有用:

context: fork:在隔离的上下文中执行,Skill 的输出不会污染主对话。适合输出量大的任务——比如全量代码审查、大规模 benchmark 分析。

user-invocable: false:对用户隐藏,用户在 / 菜单里看不到它,但 Claude 可以自动触发。适合"背景知识"类的 Skill——比如一个描述遗留系统架构的 Skill,Claude 在改相关代码时需要了解它,但用户不需要手动调用。

model:指定执行模型。简单的格式化任务用 haiku 更快更省;复杂的分析任务用 sonnet

hooks:Skill 级别的 Hooks,只在这个 Skill 的生命周期内生效,随 Skill 一起分发。比如在 Skill 执行时每次写操作之后自动跑 go vet

hooks:
  PostToolUse:
    - matcher: Edit
      hooks:
        - type: command
          command: "cd $CLAUDE_PROJECT_DIR && go vet ./... 2>&1 | tail -5"

两个字段组合起来控制"谁能触发":

配置用户可触发Claude 可触发
默认(都不设)
disable-model-invocation: true
user-invocable: false

哪些知识放 CLAUDE.md,哪些放 Skill

判断标准:

放 CLAUDE.md:每个任务都需要知道的基础信息。技术栈、目录结构、绝对禁令——无论 Claude 在做什么都需要。控制在 50-100 行以内。

放 Skill:特定场景才需要的详细知识。数据库规范只在写数据库代码时有用;并发规范只在写 goroutine 时有用——有明确的触发场景,适合提取成 Skill。

拿不准的时候,往 Skill 里放。 在 CLAUDE.md 里保持一行简短引用("数据库规范见 db-access Skill"),具体内容在 Skill 里。这是官方文档也推荐的做法。

设计 Skill 的几条原则

单一职责。 一个 Skill 做一件事。与其做一个宽泛的"代码质量检查",不如拆成"静态分析"、"安全审计"、"性能分析"三个专注的 Skill。职责单一时 description 写得更精准,Claude 匹配更准确,上下文也更干净。

权限最小化。 参考型只给 Read, Grep, Glob;任务型精确到命令级(Bash(go test:*) 而不是 Bash(*))。权限越小越安全,也帮助 Claude 更好地聚焦。

description 当广告写。 它不是给人看的文档——它是给 Claude 看的触发器。包含用户可能说的关键词,明确说明触发场景。模糊的 description 导致该触发时不触发。

任务型 Skill 的设计清单。 设计一个任务型 Skill 时,按顺序回答这几个问题:

  1. 动作是什么?→ 命名(commit、deploy、review)
  2. 谁能触发?→ disable-model-invocation: true
  3. 需要什么权限?→ allowed-tools 精确到命令级
  4. 启动时需要什么上下文?→ !command`` 预注入
  5. 执行过程需要什么安全网?→ hooks
  6. 输出量大不大?→ 大则 context: fork
  7. 用什么模型?→ 简单 haiku,复杂 sonnet

提交到 Git。 项目级 Skill(.claude/skills/)是团队资产。团队里的人 clone 下来就能用同一套规范。有人发现 description 不够精准、prompt 可以改进,发个 PR。

内置 Skills

Claude Code 自带了几个开箱即用的 Skill,不需要配置:

  • /simplify:改完代码后跑一遍,它会并行启动三个审查代理(代码复用、代码质量、效率),汇总发现的问题并自动修复
  • /batch:大规模并行改动。描述要做的变更,它自动拆分成独立单元,每个单元在隔离的 git worktree 里执行
  • /debug:排查 Claude Code 本身的问题,读取会话调试日志进行分析

这些是 prompt 驱动的 Skill,不是硬编码的命令——它们和你自己写的 Skill 用的是同一套机制。

实际用起来是什么效果

db-access 这个 Skill 配好之后,我把 CLAUDE.md 里的数据库部分删掉了。

现在当我说:

帮我实现客户订单查询接口,需要支持按状态筛选和分页,
数据从 PostgreSQL 的 orders 表查。

Claude 识别到"数据库查询"这个场景,自动加载 db-access Skill,然后按照团队规范写代码——用 pgxpool.Pool 而不是单连接、查询带 context.WithTimeout、用 pgx.CollectRows 收集结果、错误用 fmt.Errorf 包装——我一句话都不用提醒。

CLAUDE.md 现在保持在四十行左右,都是真正每次都需要的基础信息。Claude 的注意力更集中,按规范输出的准确率明显提高了。

几个实际的坑

Skill 没触发? 先检查 description 里有没有包含用户自然会说的关键词。可以问 Claude "What skills are available?" 确认它能看到你的 Skill。如果确认能看到但没触发,试着调整 description 让它和你的请求更匹配。也可以随时 /skill-name 手动触发。

触发太频繁? 说明 description 写得太宽泛。把触发条件收窄,或者干脆加 disable-model-invocation: true 改成手动触发。

Skill 太多导致部分被截断? 运行 /context 看有没有警告。description 总预算大约 15,000 字符。解决办法:精简 description、合并功能相近的 Skill、或者通过 SLASH_COMMAND_TOOL_CHAR_BUDGET 环境变量调大预算。

monorepo 的 Skill 发现:如果你在 packages/frontend/ 目录下工作,Claude Code 会自动发现 packages/frontend/.claude/skills/ 下的 Skill。每个 package 可以有自己的 Skill,不需要全放在根目录。

Skills 的优先级:当多个作用域存在同名 Skill 时,优先级从高到低:Enterprise(企业级)> Personal(用户级)> Project(项目级)。Plugin Skills 使用 plugin-name:skill-name 命名空间,不与其他级别冲突。

先让 Claude 帮你写。 你不需要从零开始手写 SKILL.md。描述你想要的能力,让 Claude 生成初稿,在这个基础上迭代调优。团队使用中不断根据实际效果打磨 description 和正文——好用的 Skill 是在真实使用中迭代出来的。


到这里,Claude Code 的几个核心扩展机制都覆盖到了:CLAUDE.md 管记忆,斜杠命令管流程复用,Hooks 管硬约束,MCP 管能力扩展,Skills 管按需知识加载。

这些机制不是要你全部用上。按实际痛点选——如果 Claude 老是"忘规矩",先把 Skills 建起来;如果需要跨越工具边界,再考虑 MCP;如果有操作绝对不能碰,才需要 Hooks。