数据截至 (上游 commit 0a64e398ec6b)
一个 Skill 的解剖
本章讲什么: 把「skill = 文件夹」拆开看——
SKILL.md的 YAML 到底有哪些被代码强制的规则、正文该写什么、scripts//references//assets/三者分工,以及一个 skill 怎么被打包成.skill分发。读完你能从零写出一个合法的 skill 骨架。
1. 最小可用的 skill(先看终点)
仓库给的模板就是一个合法 skill 的下限:
---
name: template-skill
description: Replace with description of the skill and when Claude should use it.
---
# Insert instructions below
来源:template/SKILL.md(全文 6 行)。也就是说——两个 YAML 字段 + 一个标题,就是一个能跑的 skill。其余一切(脚本、reference、assets)都是可选的「加料」。
2. frontmatter 契约:以代码为准,不以 spec 为准
这一节要点明一个容易踩的坑:仓库里 spec/agent-skills-spec.md 只有一行重定向到线上,正文不在克隆里。所以「frontmatter 到底允许什么」的权威来源是真实执行的校验函数 validate_skill(skills/skill-creator/scripts/quick_validate.py)。
2.1 允许哪些键
校验器写死了允许的键集合:
# skills/skill-creator/scripts/quick_validate.py 内,ALLOWED_PROPERTIES
# 示意,符号名与值取自源码
ALLOWED_PROPERTIES = {'name', 'description', 'license',
'allowed-tools', 'metadata', 'compatibility'}
来源:skills/skill-creator/scripts/quick_validate.py:42(ALLOWED_PROPERTIES)。出现任何其它键,validate_skill 直接判不合格(:44-50)。
一个有意思的事实: 全仓库 17 个 skill + 模板里,实际只用到了 3 个键——name、description、license。其余允许键(allowed-tools/metadata/compatibility)是「可用但很少用」。这印证了 skill-creator 的说法:compatibility 等字段「optional, rarely needed」(skills/skill-creator/SKILL.md 约 68 行)。
2.2 name 与 description 的硬规则
name 和 description 是仅有的两个必需键(quick_validate.py:53-56)。它们各自有被代码强制的约束:
| 字段 | 规则 | 校验位置(符号 / 行) |
|---|---|---|
name | 必须 kebab-case:^[a-z0-9-]+$ | quick_validate.py:65 |
name | 不能以 - 开头/结尾,不能连续 -- | quick_validate.py:67 |
name | 长度 ≤ 64 | quick_validate.py:70-71 |
description | 不能含尖括号 < 或 > | quick_validate.py:80-81 |
description | 长度 ≤ 1024 | quick_validate.py:82-84 |
compatibility | 若有:字符串且 ≤ 500 | quick_validate.py:87-92 |
为什么 description 禁尖括号?(inferred)很可能因为 skill 元数据会被拼进给模型看的 XML 化列表里,裸 </> 会破坏结构——代码里只看到「禁止」,原因属推断。
2.3 校验入口长什么样
validate_skill(skill_path) 的逻辑很直白,值得记住它的检查顺序(任何一步失败即返回 (False, 原因)):
1. SKILL.md 存在吗? (:16-18)
2. 以 '---' 开头吗? (:23-24)
3. 能正则切出 frontmatter 块吗? (:27-30)
4. 是合法 YAML 且是 dict 吗? (:34-39)
5. 有没有不在白名单里的键? (:44-50)
6. name / description 在不在? (:53-56)
7. name 命名规范 + 长度 (:64-71)
8. description 无尖括号 + 长度 (:78-84)
来源:skills/skill-creator/scripts/quick_validate.py 的 validate_skill(:12-94)。这是「合法 skill」的可执行定义——比线上 spec 文字更可靠,因为打包时真的会跑它(见 §5)。
3. 正文(L2):写「总纲 + 指针」,不写「全部细节」
SKILL.md 的 markdown 主体是 L2:只要 skill 被触发就整份进上下文。所以它要短而像总纲,把又长又专的内容推到 L3。
两条来自 skill-creator 的硬建议:
- 正文控制在 500 行以内;接近上限就「加一层层级 + 明确指针,告诉模型下一步该去读哪个文件」。来源:
skills/skill-creator/SKILL.md约 96 行。 - 大 reference 文件(>300 行)要带目录,方便按需跳读。来源:同上,约 98 行。
正文里最该出现的东西是『指针』——「需要 X 时去读 references/x.md」「重复活儿跑 scripts/y.py」。mcp-builder 是教科书级示例:正文是四阶段工作流骨架,真正的语言细节全在 reference/python_mcp_server.md / node_mcp_server.md,正文只用一行链接把它们挂出来(skills/mcp-builder/SKILL.md:60-66)。
4. 三级加载里的 L3:scripts vs references vs assets
三个可选目录职责不同,别混:
| 目录 | 装什么 | 进不进上下文 | 典型例子 |
|---|---|---|---|
scripts/ | 确定性/重复活儿的可执行代码 | 可以只执行、不读入(省上下文) | skills/skill-creator/scripts/package_skill.py |
references/ | 按需读进上下文的细节文档 | 读入(用到才读) | skills/mcp-builder/reference/evaluation.md |
assets/ | 产出里要用的素材 | 通常不读、被引用/拷贝进结果 | 模板、图标、字体 |
来源:skills/skill-creator/SKILL.md「Anatomy of a Skill」+「Progressive Disclosure → Bundled resources」(约 75-91 行)。
「脚本当黑盒」是一条明确纪律。 webapp-testing 把这点写死:「先跑 --help,别读源码;这些脚本可能很大,读进来会污染上下文窗口。它们就是要被当黑盒直接调用,而不是被吃进上下文。」来源:skills/webapp-testing/SKILL.md:11-14 与「Best Practices」(约 85 行)。这正是 L3「脚本可只执行不读入」的现实体现——详见 03 章。
按域组织 references。 当一个 skill 跨多个框架/方言时,按变体拆 reference,让模型只读相关那份:
cloud-deploy/
├── SKILL.md ← 工作流 + 选哪个的判断
└── references/
├── aws.md
├── gcp.md
└── azure.md ← Claude 只读相关那一份
来源:skills/skill-creator/SKILL.md「Domain organization」(约 100-109 行)。mcp-builder(python/node 两份)和 claude-api(按语言分目录:python/ typescript/ go/ java/ …)都是这套(skills/claude-api/ 目录布局)。
5. 打包成 .skill:把校验焊进流程
一个 skill 文件夹通过 package_skill.py 变成可分发的单文件 .skill(本质是个 zip)。最妙的设计是:打包前强制先校验,校验不过就拒绝打包。
# skills/skill-creator/scripts/package_skill.py 内,package_skill()
# 示意,逻辑取自源码
valid, message = validate_skill(skill_path) # 复用 §2 的校验器
if not valid:
print(f"Validation failed: {message}")
return None # 不合格 → 不打包
# ……随后把文件夹 rglob 进 zip,命名 <skill_name>.skill
来源:skills/skill-creator/scripts/package_skill.py 的 package_skill(:42-104),校验调用在 :71-77,zip 写入在 :90-101。
打包时会排除的东西(避免把构建垃圾/评测数据塞进分发包):
| 排除项 | 范围 | 符号 |
|---|---|---|
__pycache__、node_modules | 任意层 | EXCLUDE_DIRS(:20) |
*.pyc | 任意层 | EXCLUDE_GLOBS(:21) |
.DS_Store | 任意层 | EXCLUDE_FILES(:22) |
evals/ | 仅 skill 根目录 | ROOT_EXCLUDE_DIRS(:24) + should_exclude(:27-39) |
注意 evals/ 只在根层被排除(should_exclude 里 parts[1] 的判断,:33-35)——因为评测集是开发期产物,不该跟着分发,但更深层若恰好叫 evals 则不误伤。
6. 巧妙之处
- 「合法」有可执行定义。 frontmatter 规则不是散落在文档里的口头约定,而是一个会在打包时真跑的函数
validate_skill。这让「写错 frontmatter」在分发前就被拦下(package_skill.py:71-77)。 - 校验器自带『修正建议』。 报错信息会把允许键列出来给你看(
quick_validate.py:46-50),而不是只说「键非法」。 - 三级模型把『成本』显式化。 L1 永远在(所以要极省、要准)、L2 触发才进(所以 <500 行)、L3 用到才取(所以脚本能只跑不读)——整套设计就是在管理「上下文这件最贵的资源」。
7. 代码地图
| 主题 | 文件路径 | 符号 / 锚点 |
|---|---|---|
| frontmatter 校验全流程 | skills/skill-creator/scripts/quick_validate.py | validate_skill |
| 允许键集合 | skills/skill-creator/scripts/quick_validate.py | ALLOWED_PROPERTIES |
| 打包 + 强制校验 | skills/skill-creator/scripts/package_skill.py | package_skill |
| 打包排除规则 | skills/skill-creator/scripts/package_skill.py | should_exclude, ROOT_EXCLUDE_DIRS |
| 三级加载 / 目录分工 | skills/skill-creator/SKILL.md | 「Anatomy of a Skill」「Progressive Disclosure」 |
| 脚本黑盒纪律 | skills/webapp-testing/SKILL.md | 「Best Practices」 |
| 最小模板 | template/SKILL.md | frontmatter |