2026-06-11 · skills

第 4 章:第一个 SKILL.md

从零写一个 SKILL.md,让你的编码 agent 能自行发现并随时跑起 PR 审查助手。

小满 · 手艺作坊

小满每次都重新摸索同一套流程。今天你给它写下第一份可复用的手艺。

草稿章节。跑通格式用的第一版,正式索引前会再打磨。

本章目标

把贯穿全书的项目(PR 审查助手)打包成一个可复用的 Agent Skill。一个 skill 就是一个文件夹,里面放一份 SKILL.md。这份文件分两部分:--- 之间的 YAML frontmatter,告诉 agent 什么时候用这个 skill;下面是 markdown 指令,skill 加载后 agent 照着做。读完本章,编码 agent 就能自己发现你的 skill,判断什么时候该用,并跑出完整审查,你不用每次会话重新粘贴清单。

有两个机制先讲清楚。第一,触发完全靠 description。agent 启动时会读每个 skill 的 name 和 description,但只有当对话里有内容跟这个 description 对上了,它才会加载完整正文。第二,文件夹名就是命令:放在 .claude/skills/pr-reviewer/SKILL.md 的 skill,也能直接用 /pr-reviewer 调起。description 写对了,自动触发和手动调用你就都有了。

前置准备

  • 完成第 1 到 3 章:你已理解 agent 回路与上下文工程。
  • 一个支持 Agent Skills、已装好可用的编码 agent。具体路径和字段名以官方文档为准,它们会演进。
  • 一个至少有一个 pull request 或 diff 可供审查的仓库。
  • 若想用第 4 步的实时 diff 版本,准备好 GitHub CLI(gh)。

原理

skill 靠渐进披露(progressive disclosure)来省成本。启动时上下文里只有名字和描述,所以一个漏掉触发词的 description 实际上等于隐形:agent 根本没有可以匹配的东西。一旦请求匹配上,完整的 SKILL.md 正文会作为一条消息注入,并在这次会话剩下的时间里一直占着上下文,所以你写的每一行都是反复花掉的 token。下面所有准则都是从这一条推出来的:让 description 多放触发词,让正文短、且是命令式的。

动手做

1. 建 skill 文件夹

在 agent 扫描的位置建一个以 skill 命名的目录。项目级 skill 放在仓库里,队友 checkout 就能拿到;个人级 skill 放在你的家目录配置里,跟着你跨项目走。

# 项目级:提交进仓库,与团队共享
mkdir -p .claude/skills/pr-reviewer

# 个人级:只属于你,在每个项目里都可用(路径示意)
mkdir -p ~/.claude/skills/pr-reviewer

2. 写触发发现的 frontmatter

打开 SKILL.md 写 frontmatter。description 是整个文件里最重要的一行:agent 就拿它来匹配,所以必须把触发场景写明白,而且要用你实际会说的词。“审查代码”太含糊。要点名对象(PR、diff、pull request)和时机(用户要求在合并前审查改动时)。

---
name: pr-reviewer
description: >-
  审查一个 pull request 或 diff 的正确性、测试、安全与清晰度。
  当用户要求 review 一个 PR、审查改动、合并前检查 diff,
  或问"这能合并了吗"时使用。
---

description(连同任何 when_to_use 文本)在 skill 列表里会被截断到一个文档里写明的字符上限,所以把关键用例放最前面。触发词放前面,整段控制在几句话内。

3. 写精炼、命令式的指令

在 frontmatter 下面用纯 markdown 写审查流程。说做什么,别说为什么。因为正文一旦加载就一直占着上下文,把每一行都当成每轮都要花 token 的常驻指令来写。一份聚焦、有明确输出格式的清单,比一大段啰嗦的文字管用。

# PR 审查助手

从四个维度审查范围内的 diff。每条发现给出文件与行号、一句话问题陈述、
一个具体的修复建议。

## 清单
1. 正确性:逻辑错误、差一错误、空值处理、API 用法错误。
2. 测试:新路径覆盖了吗?现有测试还成立吗?标出未测分支。
3. 安全:注入、未校验输入、代码里的密钥、不安全的反序列化。
4. 清晰度:命名、死代码、过大的函数、缺失的错误信息。

## 输出格式
- 按严重度分组:阻塞、应修、小瑕。
- 末尾给一句结论:APPROVE、APPROVE WITH NITS 或 REQUEST CHANGES。
- 若 diff 为空或取不到,直说,而不是编造发现。

4. 喂给它真实的 diff(可选但推荐)

skill 可以在 agent 读取之前,用一种文档里写明的内联 shell 语法,把实时数据拉进提示词。这样审查就基于真实的工作树,而不是 agent 从打开的文件里猜出来的东西。具体语法以官方文档为准;形态是一个用反引号包住、前缀 ! 的命令。

## 待审 diff

!`gh pr diff`

## 改动文件

!`gh pr diff --name-only`

skill 加载时,agent 会先跑这些命令,把每一行替换成命令的输出,所以指令送到时,真实 diff 已经内联在里面了。模型看不到命令本身,只看到结果。

5. 触发发现,再调试不触发

开一次会话,用你平时的说法让 agent 审查一个 PR,确认它自己加载了 pr-reviewer。如果没触发,去改触发器,别改正文,因为不触发几乎总是 description 的问题。

1. 问 agent "有哪些 skill 可用?",确认 pr-reviewer 在列。
2. 在列但不触发:说明 description 缺了你用的词。
   把触发短语("审查这个 PR""检查 diff")加进去。
3. skill 很多时,列表里的描述可能被截断。
   精简低优先级的 skill,或裁短自己的,让触发词留存。
4. 兜底办法:直接调起 /pr-reviewer

习得「固化手艺」那份四维审查清单不用你每次会话重新粘贴了,你把它写进了一个 SKILL.md,小满凭 description 自己判断该不该用,再照着正文跑完整审查。

如何验证

最直接的验证是用 SDK 把它跑起来:项目级 skill 放在 .claude/skills/ 下,要让 SDK 会话能发现它,得让 SDK 加载项目设置源。Python 用 skills=["pr-reviewer"] 这一个开关(SDK 会替你把项目设置源和 Skill 工具一起配好);TypeScript 用 settingSources: ["project"] 加载 .claude/skills/,并把 Skill 列进 allowedTools。然后用平常的话下达任务,看消息流里有没有出现对 Skill 工具的调用。

import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock, TextBlock

async def main():
    async for message in query(
        prompt="审查这个 PR。",   # 不点名 skill,靠 description 自动触发
        options=ClaudeAgentOptions(
            skills=["pr-reviewer"],   # 唯一开关:自动配好项目设置源与 Skill 工具
            cwd="./repo",
        ),
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, ToolUseBlock) and block.name == "Skill":
                    print(f"[触发 skill] {block.input}")   # 看它自己加载了 pr-reviewer
                elif isinstance(block, TextBlock):
                    print(block.text)

anyio.run(main)
import { query } from "@anthropic-ai/claude-agent-sdk";

const q = query({
  prompt: "审查这个 PR。", // 不点名 skill,靠 description 自动触发
  options: {
    settingSources: ["project"],          // 加载 .claude/skills/ 下的项目 skill
    allowedTools: ["Skill", "Read", "Grep", "Glob", "Bash"],
    cwd: "./repo",
  },
});

for await (const message of q) {
  if (message.type === "assistant" && message.message) {
    for (const block of message.message.content) {
      if (block.type === "tool_use" && block.name === "Skill") {
        console.log("[触发 skill]", block.input); // 看它自己加载了 pr-reviewer
      } else if (block.type === "text") {
        console.log(block.text);
      }
    }
  }
}
  • 用平常的话说”审查这个 PR”,确认 agent 声明它在用 pr-reviewer skill,而不是临场发挥。
  • 检查输出覆盖全部四类,并以你指令要求的那句结论收尾。
  • 换种说法(“合并前帮我检查下这些改动?”),确认 skill 还能触发。这说明 description 能泛化,而不是只认某一句固定话术。
  • 直接跑 /pr-reviewer,确认手动调用也产出同样结构的审查。

习得「确认它真触发」你能换几种说法说「审查这个 PR」,看小满主动声明它在用 pr-reviewer,再跑一次 /pr-reviewer,确认自动和手动两条路产出同样结构的审查。

小结

你把一次性的提示词变成了一个能被发现、能复用的 skill:一个文件夹、一段触发词丰富的 description、一份精炼命令式的正文,需要的话再喂上实时 diff。什么时候用由 agent 自己决定,你也能按名字触发。机制就是渐进披露:描述始终加载,正文匹配上才加载,所以一句准的描述加一份精简的正文,就是全部诀窍。下一章把这个 skill 配上参数和工具权限,再把 hooks 与 slash-command 接成一套可重复的工作流。

常见坑

  • description 含糊。 不点明对象和时机,agent 没有可匹配的东西,永远不会加载该 skill。点名 PR、diff、审查、合并。
  • 正文臃肿。 因为加载后的正文每轮都占着上下文,长 SKILL.md 是一笔反复缴的税,还会让意图变模糊。保持命令式、简短;把长篇参考材料拆到单独的附带文件里,由 SKILL.md 引用。
  • 文件夹放错位置。 放错位置的 skill 是不可见的。对照官方文档确认项目级与个人级路径。
  • skill 太多、描述被截断。 skill 列表拥挤时描述会被裁短,你的触发词可能被切掉。精简低优先级的 skill,让常用的保留完整文本。

你写下第一份 SKILL.md,小满第一次无师自通地走完整套流程,完事还难得地省掉了所有免责声明,干脆利落。手艺作坊,亮了。

刚点亮 手艺作坊 · 地图已点亮 5 / 16

一件趁手的家伙不够,它还想要更多。下一站:机关廊。

来源

  1. Anthropic Agent Skills 官方文档 · official
  2. anthropics/skills(示例 skill 仓库) · official
下一章 · 第 5 章 skill 进阶:hooks 与 slash-command