avatar


3.Skill

引子

如何做一次代码审查、如何生成发布说明、如何迁移一个组件,这些工作只在特定任务时才需要。把这些塞进CLAUDE.md,不仅浪费token,还稀释了真正需要常驻的规则。

那么:只在特定任务时才需要的成段指令,怎样做到平时只留几行描述、调用那一刻才注入正文?

这就是Skill要解决的问题。
Skill,一种调用时才加载的指令包。

官方文档:https://code.claude.com/docs/en/skills

原理

理解Skill,有三个要点:

  1. 两段式结构怎样实现按需加载
  2. 两条调用通道怎么触发
  3. 按需加载的边界在哪

两段式结构:按需加载的实现

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

一份SKILL.md分两部分:

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

这个两段式就是"按需加载"的实现:会话中常驻的只有"YAML front matter"部分,正文躺在磁盘上,调用那一刻才进入上下文。

即,Skill帮我们省的是正文,不是描述。"要不要用"的判断靠描述做语义匹配,描述本身就必须常驻上下文,这部分成本省不掉;按需加载省下的只是正文的注入成本。
所以,触发词要写精炼,别把正文内容搬进描述。

两条调用通道

Skill有两条调用通道:

  1. 手动执行:输入/skill-name(目录名即命令名)即加载执行,可带参数,如/fix-issue 123
  2. 自动触发:Claude读取"YAML front matter"里的descriptionwhen_to_use,基于语义判断当前任务是否与某个Skill相关,命中就自动加载。

自动触发的匹配只看descriptionwhen_to_use,Markdown正文不参与(唯一例外是没写description时,正文首段被当默认描述)。
所以,某个Skill"该触发却没触发"时,第一件事是检查描述,而不是改正文。

按需加载的边界

按需加载有一条边界:Skill被调用后,它的工具调用循环仍在主上下文里推进,读文件、跑命令的中间产物照样堆进主窗口。
也就是说,默认情况下Skill管的是"注入",管不了"执行"。要让执行也在隔离的上下文中进行,需要用context: fork字段——它会让Skill正文在一个独立的SubAgent中执行,过程不污染主上下文。细节见下一章《4.SubAgent》

选型:什么时候该建Skill

一句话的判断标准:这段指令是"成段的流程",且"只在特定任务时才用到"。两条都占,就该把它收进Skill,而不是塞进CLAUDE.md

这个标准落到日常,遇到下面任意一种情况,多半就该建Skill了:

  • 重复粘贴
    同一段指令,我们已经在不同会话里手动贴过不止一次。比如每开一个新会话,都要把"提交信息该怎么写"那套规矩重贴一遍,重复本身就是它该被固化下来的证据。
  • 多步骤清单
    某件事有固定的操作顺序或检查清单,希望每次都照同一套走、不走样。比如"发布新版本"固定是改版本号、更新changelog、打tag、推送这一串,漏一步就出岔子。
  • CLAUDE.md某节长成了流程
    原本一两行的约定,膨胀成了"第一步……第二步……"的操作手册。比如CLAUDE.md里"怎么加一个数据库迁移"从一句话堆成了七八步的清单,这正是《2.Memory》里"不写废话"要清出去的内容。

示例

这里提供三个真实的Skill,建立直观印象。它们各有一个"卖点":手动锁定、自动触发加动态注入、位置参数加工具权限。

第一个:手动触发的动作类Skill

该Skill的作用是修复指定编号的GitHub issue。
文件位于~/.claude/skills/fix-issue/SKILL.md

  • disable-model-invocation: true把该Skill锁成"只能手动触发",保证只能由我们显式发起。
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.

第二个:自动触发加动态注入。

该Skill的作用是,总结未提交的改动。
文件位于~/.claude/skills/summarize-changes/SKILL.md

  • description写明了触发场景(use when asked what changed),我们一问"改了什么",该Skill就自动上场。
  • 正文里的!`git diff HEAD`会在加载那一刻先执行、把输出填进正文,于是模型看到的直接就是真实diff,不必再自己跑一趟命令。
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).

第三个:位置参数加工具权限。

该Skill的作用是跨框架迁移组件。
文件位于项目内.claude/skills/migrate-component/SKILL.md

  • 调用/migrate-component SearchBar React Vue,三个位置参数依次绑定到$component$from$to
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.

实践

front matter字段

SKILL.md的front matter字段按用途分四组:

  1. 标识与触发
  2. 参数
  3. 工具权限
  4. 执行环境

标识与触发

字段 说明
name 显示名称,不改变/命令名(仅插件根级Skill例外)
description 自动触发的决策信号;省略时取正文首段
when_to_use 补充触发条件(触发词、示例请求),与description合并截断
paths Glob路径模式,仅匹配文件时自动触发
disable-model-invocation true时仅可手动调用
user-invocable false时从/菜单隐藏
  • descriptionwhen_to_use合计有 1536 字符上限,超出会被截断。即,过长的描述等于失效。
  • paths在自动触发上再叠加一个路径条件:只有当前处理的文件匹配这些glob时才自动加载。注意paths只收窄自动触发这一条通道,路径不匹配仅仅是不自动加载,手动/skill-name调用不受影响。

参数

字段 说明
argument-hint 参数自动完成提示,如[issue-number][filename] [format]
arguments 声明位置参数名,空格分隔或YAML列表,正文以$name$0/$1引用

工具权限

字段 说明
allowed-tools Skill活跃期间无需权限提示的工具列表,支持模式匹配
disallowed-tools Skill活跃期间移除的工具,下一条消息后限制清除
  • allowed-toolsdisallowed-tools的取值沿用权限规则的Tool(specifier)语法,语法本身见《1.Start》;两个字段只作用于当前Skill的作用域。
  • 与settings中的权限规则冲突时,禁止的一方生效allowed-tools免掉的只是权限提示,相当于Skill活跃期内的一条allow规则,按deny > ask > allow的优先级,压不过settings里的denyask同理,仍会弹提示);disallowed-tools则是把工具从可用池中直接移除,settings的allow只负责免提示,不能把被移除的工具加回来。

执行环境

字段 说明
model 覆盖此Skill使用的模型,inherit表示沿用会话当前模型
effort 覆盖努力级别,取值low/medium/high/xhigh/max
shell 命令块使用的shell,bash(默认)或powershell
context 设为fork时在隔离的上下文中运行
agent context: fork时使用的Sub-Agent类型
hooks Skill生命周期内生效的Hook配置
  • context: fork会让Skill正文在一个隔离的Sub-Agent(子智能体)上下文中执行
    • agent指定所用的Sub-Agent类型。
    • 隔离执行的机制见《4.SubAgent》
  • hooks可在Skill激活期间挂载Hook(钩子)。

存放位置及其调用名和规则

五个存放层级

Skill有五个存放层级,位置、作用范围与同名时的处理如下表。

层级 位置 作用范围 同名时
Enterprise Managed设置(组织策略下发) 组织内全部用户 优先级最高
Personal ~/.claude/skills/<skill-name>/SKILL.md 当前用户的所有项目 优先级中
Project 项目根目录 .claude/skills/<skill-name>/SKILL.md 当前项目 优先级低
子目录 工作子目录下的 .claude/skills/<skill-name>/SKILL.md 该子目录 撞名才加路径前缀,二者共存
Plugin 插件内的skills/目录 插件启用范围 永远带插件前缀,不撞名

调用名和规则

调用名有两种:

  1. 裸名(就是目录名,如/deploy
  2. 带前缀(如plugin-name:skillapps/web:deploy

具体规则三条:

  1. Enterprise、Personal、Project:都用裸名/skill-name。只有这三类才会"撞名覆盖",同名时按表中优先级取一个生效。
  2. Plugin:调用名永远带插件名前缀,形如plugin-name:skill-name。名字自带命名空间,跟别处的裸名永不相等。
  3. 子目录:平时也用裸名(目录名),只有当它和别处的Skill撞名时,才自动带上子目录路径前缀来消歧。
    比如根目录已经有/deploy,子目录apps/web/下又放了个同名的,后者的调用名就变成/apps/web:deploy,于是两个都留得下、谁也不盖谁。

旧式自定义命令

旧式自定义命令是存放在.claude/commands/目录下的命令文件(插件内对应commands/目录)。新做法优先用Skill,插件内形态见《7.Plugin》

动态上下文

Skill正文可以不是静态文本,而是一份模板,其中的占位符会在正文送入上下文之前先被求值、替换成实际内容。

可替换的有三类:

  1. 参数替换
  2. 动态命令注入
  3. 路径占位符

参数替换

调用Skill时跟在命令后面的参数,通过下列占位符注入正文:

占位符 含义
$ARGUMENTS 用户传入的整段参数字符串
$N(等价于$ARGUMENTS[N] 第N个位置参数,从0开始计数
$name 由front matter的arguments字段声明的命名参数

/migrate foo React Vue为例,$0$1$2依次替换为fooReactVue

两个边界情况要注意:

  1. 参数不足:越界的占位符不做替换、原样保留。上例只传了三个参数,正文里的$3就会原封不动显示为$3。所以要在argument-hint里写清参数格式,或在正文里交代参数缺省时该怎么处理。
  2. 正文没写$ARGUMENTS、用户却传了参数:参数不会丢失,ClaudeCode会把ARGUMENTS: <用户输入>自动追加到正文末尾。

动态命令注入

动态命令注入:把shell命令的输出嵌进正文。
即正文里的!`<shell命令>`会在正文发送给模型之前先执行,命令输出原地替换掉这个占位符。

多行命令如果塞在一行里可读性太差,可以改用代码块写法:三反引号的语言标记前加!,即```!bash(或直接```!),块内的命令照常书写。执行后,整个代码块同样被命令输出原地替换。例如:

1
2
3
4
```!bash
git diff HEAD
git log --oneline -5
```

命令由哪个shell执行,取决于front matter的shell字段(bashpowershell)。

路径占位符

路径占位符:正文里可以用${CLAUDE_PROJECT_DIR}引用项目根目录的绝对路径。

路径占位符不仅能出现在正文中,也能写进allowed-tools规则,例如Bash(${CLAUDE_PROJECT_DIR}/scripts/lint.sh *),据此按项目根定位脚本。

排障速查

一个前提

前提是立即生效:在编辑SKILL.md后,改动在当前会话内立即生效,不需要重启,可以边改边验证。

三个高频症状

  • 第一个症状,“该触发却没触发”:先检查描述,口诀见前文「两条调用通道」。
  • 第二个症状与YAML语法有关。对于"front matter"一旦有YAML语法错误,ClaudeCode不会报错,而是静默降级:/skill-name手动调用仍然可用,但描述丢失、自动触发失效。
    所以当一个Skill突然"只能手动、不再自动出场"时,优先怀疑front matter写错了。
  • 第三个症状,“声明了allowed-tools却仍被拦、仍弹提示”:先检查settings里是否有命中的denyask规则,冲突时禁止的一方生效,原因见前文「工具权限」。

关闭调用通道

「两条调用通道」默认都开放。要单独关掉某一条,各由一个字段控制、互不影响:user-invocable关手动,disable-model-invocation关自动。

  • user-invocable: false适合纯知识型的"后台手册":既不占命令菜单,又保留自动出场的能力。
  • disable-model-invocation: true适合有副作用的动作类Skill,提交、部署、发通知,这类事应当由人显式发起,而非模型自行决定。
    注意:设为true后,不光自动触发被关掉,连description(front matter)都不会注入上下文,这个Skill对ClaudeCode完全不可见,包括子智能体也不会预加载该Skill(参考《4.SubAgent》)。
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/12803X
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

留言板