一个技能就是一个目录,里面至少一份 SKILL.md。 目录名就是它的调用名,文件头是一段 YAML,写着它叫什么、什么时候用、能用哪些工具。 六份 SKILL.md 里四份有毛病,而这四份在真机上不会报错——它们只是不存在。
上一节定了「什么内容该做成技能」,这一节把它拆开看结构。 结构本身很简单,难的是那几条硬规则:不满足它们,技能直接不可见,而且没有任何报错。
一、骨骼:一个目录,一份 SKILL.md
最小可用的技能只有一个目录加一个文件:
skills/
deploy-check/
SKILL.md
refs/
checklist.md
目录名 deploy-check 就是它的调用名,也是斜杠命令的名字。改目录名等于改调用名,别指望在文件头里改。 refs 是惯例目录,不是强制。 放进去的文件不会在触发时自动读,得正文点名才会读——这一点在 3.3 节讲三级披露时会说清。 一个目录只能有一个 SKILL.md。 多个技能就多个平级目录,扫描器按「任意层目录下有 SKILL.md」来找,所以嵌套几层都不影响。
二、纹理:YAML 前置元数据
文件开头是两条三个短横线夹起来的一段 YAML,叫前置元数据。它写的是元信息,不是内容:
---
name: deploy-check
description: 部署前自检:核对迁移脚本、环境变量、回滚方案是否齐备。
用户说要发版、上线、部署时使用。
allowed-tools: Read, Grep, Bash(uv run alembic:*)
---
name 是内部标识,description 是给模型看的那一句话,allowed-tools 是权限清单。 description 可以折行,YAML 会把缩进续行拼成一行。写长描述时靠折行保持可读,但拼起来仍然是一条。 三个字段之外还有可选的,比如版本号、作者、模型偏好,但核心就是这三个。 其余字段在多数场景里不影响行为。
三、四条硬规则
第一条:name 只能是小写字母、数字、连字符。大写和下划线在某些平台上会被改写,改写之后就找不到了。 第二条:name 必须和目录名一致。 调用方按目录找,加载方按 name 认,两边对不上就取不到——这是最常见的一种「静默失效」。 第三条:description 长度在二十到一千零二十四字符之间。 太短模型判断不出场景,太长会挤占所有技能的常驻预算(3.3 节细算)。
第四条:正文不能为空。空正文的技能被触发之后什么也做不了,等于白调一次。 四条都满足,扫描器才认。演示里六份样本过了两份,另外四份各撞一条。
四、为什么这些错误不报错
代码写错会崩,配置写错通常也会报。 但技能这几条错了,表现是它消失了:扫描的时候直接跳过,你打斜杠命令也补不出来。 这个设计有它的道理:技能目录是用户自己的东西,扫描器不敢因为一份坏文件就中断整个加载。 但对写的人就成了坑:你写完觉得没问题,实际一次也没被触发过,而没有任何信号告诉你。
所以写完一个技能,第一件事是跑一遍这四条,第二件事才是看内容写得好不好。顺序反了会白改半天。
五、正文放什么,先给个轮廓
正文是触发之后才读的那一段。它不是文档,是这一次任务的执行说明。 所以它该有的东西是:这次要做什么、按什么顺序做、每步怎么判断做完了、遇到什么情况停下来问。 它不该有的是:背景介绍、设计理由、概念解释、历史沿革。 这些放 refs,或者放项目的文档目录,正文只留一句「为什么这么定,见 docs/xxx.md」。
这条界线在 3.5 节会用三份真实形状的正文对比着讲。 这里先记住一句话:正文是给「正在干这件事的模型」看的,不是给「想了解这件事的人」看的。
六、allowed-tools 为什么写在头上
权限是元信息,不是内容,所以写在 YAML 里而不是正文里。 它管的是这个技能能用哪些工具。 触发时按这份清单收窄,清单外的工具即使全局允许,在这个技能里也用不了。 演示里通过的那份写着 Read、Grep、Bash(uv run alembic:*),就是把「只能读、只能搜、 只能跑迁移命令」钉死。
这个字段单独在 3.6 节讲,包括 Bash 的子命令语法,以及前缀匹配那个绕不过去的漏洞。
七、动手 Demo:六份 SKILL.md,四份有毛病
造六份 SKILL.md,各撞一条规则:名字带大写和下划线、name 与目录名不一致、缺 description、正文为空, 另外两份是对的。 逐份检查四条硬规则,看哪份能过。再看一眼通过的那份头部长什么样。
def check(d, text):
head, body = split_fm(text) # 拆 YAML 头与正文
nm, desc = head.get(u"name"), head.get(u"description")
if not re.match(r"^[a-z0-9-]+$", nm):
return u"name 有非法字符"
if nm != d: # 目录名才是调用名
return u"name 与目录名不一致"
if not desc or not (20 <= len(desc) <= 1024):
return u"description 长度不合规"
if not body.strip():
return u"正文为空"
return u"通过"
实跑输出:
--------------------------------------------------------------------
六份 SKILL.md,四份有毛病
--------------------------------------------------------------------
| 目录名 | name | 正文字数 | 检查结果 |
|---|---|---|---:|
| deploy-check | deploy-check | 130 | 通过 |
| Deploy_Check | Deploy_Check | 130 | name 有非法字符 |
| deploy-check-old | deploy-check | 130 | name 与目录名不一致 |
| deploy-check | deploy-check | 130 | 缺 description |
| deploy-check | deploy-check | 1 | 正文为空 |
| deploy-check | deploy-check | 130 | 通过 |
① 六份里只通过两份
--------------------------------------------------------------------
通过 2 份,不通过 4 份。
不通过的这几份,在真机上连扫描都扫不到——它们不会报错,只是不存在。这是最难查的一类问题。
② 四条规则分别在防什么
--------------------------------------------------------------------
| 规则 | 不满足会怎样 |
|---|---|
| name 全是小写字母数字连字符 | 目录名就是它的调用名,大写和下划线在某些平台上会被改写 |
| name 与目录名一致 | 两个名字打架时,调用方按目录找、加载方按 name 认,对不上就取不到 |
| description 20 到 1024 字符 | 太短模型判断不出场景,太长会挤占所有技能的常驻预算 |
| 正文非空 | 空正文的技能被触发后什么也做不了,等于白调一次 |
③ 看一下通过的那份长什么样
--------------------------------------------------------------------
把 deploy-check 的头三行打出来:
name deploy-check
description 部署前自检:核对迁移脚本、环境变量、回滚方案是否齐备。
用户说要发版、上线、部署时使用。
allowed-tools Read, Grep, Bash(uv run alembic:*)
注意 allowed-tools 那一行:它把这个技能能用到的工具钉死在这三个上。
这件事下一节(3.6)单独讲,这里只要知道它写在头上。
④ 为什么这些规则是硬规则,不是风格建议
--------------------------------------------------------------------
代码风格错了,读起来难看但不影响跑。这几条错了,技能直接不可见。
所以写完一个技能,第一件事是跑一遍这四条;第二件事才是看内容写得好不好。
①六份里只通过两份。不过的那四份,在真机上连扫描都扫不到——它们不报错,只是不存在。 这是最难查的一类问题:没有任何信号。 ②四条规则分别防什么:name 防的是「平台上被改写」,name 与目录一致防的是「两个名字打架」, description 长度防的是「判不出场景」和「挤占预算」,正文非空防的是「白调一次」。 四条里最容易被忽略的是第二条。 它不报错,也不影响单个文件的可读性,但调用时找不到——改个目录名就好了,可你未必想得到去看目录名。 ③通过的那份头三行:name 与目录同名、description 一句话说清「什么时候用」、 allowed-tools 三个工具钉死。 description 那两行值得单独看:第一行说做什么(核对三样),第二行说什么时候用(发版、上线、部署)。 这两件事分开写,是 3.4 节讲触发时的核心。 ④最后一段是落点:代码风格错了难看,这四条错了技能直接不可见。 所以检查顺序是先规则后内容,反了会白改。
30 秒实验
- 把 name 改成全大写,重跑一遍。它撞的是第一条还是第二条?两条规则的检查顺序会影响结论吗?
- 把 description 写到一千一百字符,看结果。再想想:长度超了是「不能用」还是「能用到但太贵」?
- 在 refs 里再放一份 SKILL.md,扫描器会把它当成第二个技能吗?这说明了扫描规则是什么?
常见误区
- 以为 SKILL.md 写错会报错。它不报错,只是被跳过。写完必须自己核四条规则。
- 在文件头里改 name 却不改目录名。调用名是目录名,两个不一致就取不到。
- 把 description 当成「介绍这个技能有多好」。 它是给模型做判断用的,要写「什么时候用」,不是写「功能强大、支持多种场景」。
- 把正文写成项目文档:背景、原则、概念、示例堆一大段。触发时读的是执行说明,不是读物。
- 在 refs 里平行放文档却指望它自动加载。refs 不会自动读,正文点名才读。
自测三问(点开对答案)
1. 一个技能的最小结构是什么? —— 一个目录加一份 SKILL.md。目录名就是调用名,文件头是 YAML 前置元数据,后面是正文。 refs 目录是惯例,不是必须。 2. 四条硬规则分别是什么? —— name 只能是小写字母数字连字符;name 必须与目录名一致; description 长度二十到一千零二十四字符;正文不能为空。 3. 为什么这些错误特别难查? —— 因为它们不报错。 扫描器会直接跳过不合规的文件,技能表现为「不存在」,斜杠命令也补不出来,没有任何提示。结构清楚了,下一节看它凭什么「平时不占地方」:三级披露各在什么时候付钱。
- ☐ (本篇完,下一篇 3.3 渐进式披露:知识的投资回报率)
相关笔记
- 知识的两个维度:什么内容该做成技能
- 渐进式披露:三级加载各付多少
- Claude Code 橙皮书 · 索引
- 🐎 Claude Code 实战 · 索引