avatar


3.Skills

简介

Claude Code的本质是"大模型+工具调用循环+有限上下文窗口",与本章相关的限制是最后一项:凡常驻注入的内容,都会在每一轮对话中持续占用上下文、持续付出token成本。《2.Memory》解决了"指令会丢"的问题,但CLAUDE.md是启动时全量加载的——无论当前任务是否用得上,里面的每一节都躺在上下文里。

对"项目用什么构建命令"这类短事实,常驻是划算的。但另一类指令不一样:如何做一次代码审查、如何生成发布说明、如何迁移一个组件——它们是成段的工作流,只在特定任务时才需要。把工作流塞进CLAUDE.md,代价是每轮都为它付费,还稀释了真正需要常驻的规则。

Claude Code对此的解法是Agent Skills。Skill(技能)是调用时才加载的指令包:平时参与匹配的只有几行描述,成段的正文在被调用的那一刻才进入上下文。本章我们依次讨论Skill的结构、存放位置与发现、两种调用方式、front matter字段、参数与动态注入,最后给出三个可直接使用的示例。

Skill是什么

一个Skill就是一个目录,目录名即命令名,核心是其中的SKILL.md文件。SKILL.md分两部分:

  • YAML front matter
    文件顶部的元信息:名字、描述、触发条件、参数、工具权限等,供Claude Code决策"要不要用这个Skill"。
  • Markdown正文
    真正的指令内容:步骤、清单、格式要求。只在Skill被调用时才注入上下文。

这个两段式结构就是按需加载的实现。会话中常驻的只有描述那几十个字,正文躺在磁盘上,调用才计费。与CLAUDE.md"每字常驻"相比,这是另一种成本模型:CLAUDE.md为"每轮都可能用到"的内容优化,Skill为"偶尔用到、一用就是一整套"的内容优化。

什么时候该建Skill,有三个信号:

  • 重复粘贴
    同一段指令,我们已经在不同会话里手动贴过不止一次。
  • 多步骤清单
    某件事有固定的检查清单或操作顺序,希望每次执行都不走样。
  • CLAUDE.md某节长成了流程
    原本一两行的约定膨胀成"第一步……第二步……"。事实该常驻,流程该按需。

与旧式自定义命令的关系

在Skills之前,Claude Code的自定义斜杠命令放在.claude/commands/目录,一个Markdown文件对应一条命令,如.claude/commands/deploy.md对应/deploy。这一形态仍然兼容:旧文件不做任何修改也能继续使用;新旧同名时,Skill的优先级高于旧式命令。

两者的能力并不对等。旧式命令只是一个纯文本文件;Skill是一个目录,除SKILL.md外还能容纳辅助文件(supporting files),并且支持按语义自动触发。迁移路径很直接:把commands/name.md改名为skills/name/SKILL.md即可,功能等价,之后再按需添加辅助文件。

存放位置与发现

Skill可以放在四个层级,前三个层级同名时优先级从高到低;Plugin的Skill以命名空间隔离,不参与同名覆盖:

层级 位置 作用范围 优先级
Enterprise Managed设置(组织策略下发) 组织内全部用户 最高
Personal ~/.claude/skills/<skill-name>/SKILL.md 当前用户的所有项目
Project .claude/skills/<skill-name>/SKILL.md 当前项目
Plugin 插件内的skills/目录 插件启用范围 命名空间隔离

插件这一行只需要知道"Skill也可以打包进插件分发",机制见《9.Plugins》

发现是自动的。Claude Code会扫描项目根目录及其父目录下的.claude/skills/,也会扫描当前工作子目录下的.claude/skills/——后者让monorepo中的每个子项目都能带自己的Skill。子目录与上层出现同名Skill时不互相覆盖,两个都可用,嵌套的那个显示为dir:skill-name以作区分。

Skill支持热加载:编辑SKILL.md后,改动在当前会话内立即生效,不需要重启。

注意:即时生效仅限SKILL.md本身,hooks、agents等其他组件的改动不在此列。

调用方式

Skill有两条激活通道,两条通道都支持参数传递。

第一条是手动调用。目录名就是命令名,输入/skill-name即加载执行,可以带参数,如/fix-issue 123

第二条是自动触发。Claude根据front matter中descriptionwhen_to_use的语义,判断当前任务是否与某个Skill相关,命中即自动加载。语义匹配只看这两个字段,Markdown正文不参与匹配——唯一的例外是省略description时,正文首段会被当作默认描述。若还设置了paths,则附加一个路径条件:只有在处理匹配路径的文件时才会自动加载(字段详情见下一节)。这条通道只在disable-model-invocationfalse(默认值)时开放。

两条通道各有一个开关:

  • disable-model-invocation
    设为true时关闭自动触发:该Skill不会被自动加载,也不会被预加载(Sub-Agent经skills字段预加载Skill的机制见《4.SubAgent》),只能由用户手动/skill-name调用。适合有副作用的动作类Skill——提交、部署、发通知,这类事应当由人显式发起,而不是由模型自行决定。
  • user-invocable
    设为false时该Skill从/菜单中隐藏,用户无法手动调用,但Claude仍可自动加载。适合纯知识型的"后台手册",既不占命令菜单,又保留自动出场的能力。

默认配置下两个开关都不启用,Skill对用户和模型双向开放;两个开关各自关闭其中一条通道,互不影响。

既然自动触发依赖描述做语义匹配,描述本身就必须常驻上下文。所以Skill省下的是正文的注入成本,描述的成本省不掉——这也是描述被要求写短、且设有长度上限的背景(上限数字见本文经验一节)。

front matter字段

SKILL.md的front matter字段按用途分四组,完整清单如下:

分组 字段 说明
标识与触发 name 显示名称,不改变/命令名(仅插件根级Skill例外)
标识与触发 description 自动触发的决策信号;省略时取正文首段
标识与触发 when_to_use 补充触发条件(触发词、示例请求),与description合并截断
标识与触发 paths Glob路径模式,仅匹配文件时自动触发
标识与触发 disable-model-invocation true时仅可手动调用(见上一节)
标识与触发 user-invocable false时从/菜单隐藏(见上一节)
参数 argument-hint 参数自动完成提示,如[issue-number][filename] [format]
参数 arguments 声明位置参数名,空格分隔或YAML列表,正文以$name$0/$1引用
工具权限 allowed-tools Skill活跃期间无需权限提示的工具列表,支持模式匹配
工具权限 disallowed-tools Skill活跃期间移除的工具,下一条消息后限制清除
执行环境 model 覆盖此Skill使用的模型,inherit表示沿用会话当前模型
执行环境 effort 覆盖努力级别,取值low/medium/high/xhigh/max
执行环境 shell 命令块使用的shell,bash(默认)或powershell
执行环境 context 设为fork时在隔离的上下文中运行
执行环境 agent context: fork时使用的Sub-Agent类型
执行环境 hooks Skill生命周期内生效的Hook配置

三点补充:

  • allowed-toolsdisallowed-tools的取值沿用权限规则的Tool(specifier)语法,语法本身见《1.概述》;两个字段只作用于当前Skill的作用域。
  • context: fork会让Skill正文在一个隔离的Sub-Agent(子智能体)上下文中执行,agent指定所用的Sub-Agent类型,隔离执行的机制见《4.SubAgent》
  • hooks可在Skill激活期间挂载Hook(钩子),事件与协议见《5.Hooks》

参数与动态上下文

Skill正文不是静态文本,它支持两类替换:调用参数与命令输出。

参数占位符如下:

占位符 含义
$ARGUMENTS 用户输入的整段参数字符串
$ARGUMENTS[N]$N 第N个位置参数,从0开始计数
$name arguments字段声明的命名参数

例如/migrate foo React Vue,则$0foo$1React$2Vue

若正文未包含$ARGUMENTS而用户传了参数,Claude Code会自动把ARGUMENTS: <input>追加到内容末尾,参数不会丢失。

动态上下文注入是另一类替换:正文中的!`<shell-command>`会在内容发送给模型之前先执行,命令输出原地替换占位符;多行命令也可以用带!标记的代码块承载。所用shell由shell字段决定。典型用法是注入!`git diff HEAD`——模型看到Skill正文时,里面已经是发送那一刻的真实diff,省去"先跑个命令看看"的一轮往返。

路径占位符${CLAUDE_PROJECT_DIR}展开为项目根目录的绝对路径(v2.1.196+),既可以出现在正文中,也可以用在allowed-tools的规则里,例如Bash(${CLAUDE_PROJECT_DIR}/scripts/lint.sh *)

示例

示例代码:手动触发的动作类Skill,按编号修复GitHub issue。文件位于~/.claude/skills/fix-issue/SKILL.md

1
2
3
4
5
6
7
8
9
---
name: fix-issue
description: Fix a GitHub issue by number
disable-model-invocation: true
argument-hint: "[issue-number]"
---

Fix GitHub issue $ARGUMENTS following standards.
Read issue, understand requirements, implement, test, commit.

调用:/fix-issue 123。修issue会改代码、提交仓库,属于有副作用的动作,所以设disable-model-invocation: true,保证流程只由我们显式发起。

示例代码:自动触发加动态注入,总结未提交的改动。文件位于~/.claude/skills/summarize-changes/SKILL.md

1
2
3
4
5
6
7
8
9
---
description: Summarize uncommitted changes; use when asked what changed
---

## Current diff
!`git diff HEAD`

## Instructions
Summarize in 2-3 bullets, flag risks (missing error handling, hardcoded values).

这一篇没有写name,命令名取目录名summarize-changesdescription写明了触发场景(use when asked what changed),当我们问"改了什么"时它会被自动加载;!`git diff HEAD`保证注入的是发送那一刻的真实diff,模型不需要再自己去跑命令。

示例代码:位置参数加工具权限,跨框架迁移组件。文件位于项目内.claude/skills/migrate-component/SKILL.md

1
2
3
4
5
6
7
---
description: Migrate component between frameworks
arguments: component from to
allowed-tools: Bash(mv *) Read Edit
---

Migrate component $component from $from to $to preserving behavior.

调用:/migrate-component SearchBar React Vue,三个位置参数依次绑定到$component$from$toallowed-toolsBash(mv *)ReadEdit这三类操作在Skill活跃期间免于权限提示——迁移只需要移动文件和改代码,就只放行这些。

经验

  • 描述有长度上限
    descriptionwhen_to_use合计上限1536字符,超出即被截断,过长的描述等于失效。触发词写精炼,不要把正文内容搬进描述。
  • YAML错误静默降级
    front matter有语法错误时,/skill-name手动调用仍然可用,但描述丢失、自动触发失效,且没有报错。怀疑Skill"失灵"时,用--debug启动查看解析错误。
  • 该触发没触发,先查描述
    正文写得再好也不参与语义匹配。触发场景要写进descriptionwhen_to_use,否则Skill不会被自动想起。
  • paths只管自动触发
    路径不匹配只是不自动加载,手动/skill-name不受paths限制。
  • $N越界原样保留
    参数不足时$N不被替换、原样留在文本里。在argument-hint里写清参数格式,或在正文中交代参数缺省时怎么办。
  • 权限字段的时效
    allowed-tools只在Skill活跃期间生效,disallowed-tools的限制在下一条消息后清除,两者都只作用于当前Skill。

与CLAUDE.md的分工可以归结为一句:事实常驻,流程按需。构建命令、代码约定这类每轮都可能用到的短事实留在CLAUDE.md;成段的工作流搬进Skill,只在调用的那一刻付token。

最后是Skills的边界。按需加载省下的是注入成本,但Skill被调用之后,工具调用循环(agent loop)仍然在当前这个上下文窗口里推进——读文件、跑命令产生的全部中间产物照样堆进主上下文。要把高噪声的活隔离出去,需要独立的上下文窗口,这是下一章《4.SubAgent》的主题。

文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/128030
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

留言板