<!-- title: 三段式机制：从零吃透Skill -->

## 概述

本教程不讲"又一个具体场景的 Skill"，而是带你**拆透 Skill 的运转机制本身**——从被发现、被曝光、到被调用的完整三段式链路，并对照官方规范逐字段吃透 `SKILL.md`。学完后你将具备"看一眼任何 Skill 就知道它何时触发、加载什么、消耗多少上下文"的判断力，并能从零构建一个跑通动态注入、参数替换、支持文件、子代理分叉四大特性的 `release-check` 发布体检技能。适合已经装好 Claude Code、写过几条 `CLAUDE.md`、想从"会抄"升级到"会设计"的开发者。

## 前置条件

- Claude Code v2.1.145 及以上（子代理 `/run`、`/verify` 相关特性需此版本；本教程核心机制在更早版本即可）
- 一个 Git 仓库作为练手场地（任何语言均可，示例用 npm 项目演示）
- 装好 `gh` CLI（示例的动态注入会调用它，没有也不影响主线）
- 能用编辑器创建目录和 Markdown 文件

## 一、先画清边界：Skill 到底是什么

很多人把 Skill、`CLAUDE.md`、slash command、MCP server 混为一谈。官方文档给了一句精准定位：**Skill 是"按需展开的提示词包"**——你把一段反复粘贴的指令、检查清单、多步流程固化成一个文件夹，Claude 在相关时自动加载，或你用 `/skill-name` 手动唤起。

四者的本质区别：

| 机制 | 加载时机 | 形态 | 适合场景 |
| :--- | :--- | :--- | :--- |
| `CLAUDE.md` | 每次会话全程常驻 | 纯事实/约定 | 项目事实、不变规则 |
| Skill | 仅被调用时加载 body | 文件夹 + `SKILL.md` + 脚本 | 流程、清单、可复用能力 |
| Slash command | 已并入 Skill | `.claude/commands/x.md` 等价于 `.claude/skills/x/SKILL.md` | 历史命令，新写推荐用 Skill |
| MCP server | 提供**新工具** | 独立进程，走协议 | 接外部 API、数据库 |

一个关键判断：当 `CLAUDE.md` 里的某一段**从"事实"长成了"流程"**，就该把它拆成 Skill。因为 `CLAUDE.md` 全程占用上下文，而 Skill 的 body 只在被用到时才进上下文——长篇参考资料在不用时**几乎零成本**。

还要知道一个事实：**自定义命令已并入 Skill**。`.claude/commands/deploy.md` 和 `.claude/skills/deploy/SKILL.md` 都会生成 `/deploy`，行为完全一致，但 Skill 额外支持目录存放支持文件、用 frontmatter 控制调用方、以及 Claude 自动加载。官方明确：新写推荐用 Skill。Claude Code 的 Skill 同时遵循 [Agent Skills](https://agentskills.io) 开放标准，可跨多个 AI 工具复用。

## 二、三段式运转机制：发现 → 曝光 → 调用

这是本教程的核心。理解了这三段，你写 Skill 时做的每一个决策都有了依据。

### 第一段：发现（Discovery）

Claude Code 启动时扫描四个层级的位置，逐层发现 Skill 文件夹：

| 层级 | 路径 | 生效范围 |
| :--- | :--- | :--- |
| Enterprise | 托管设置 | 组织内所有用户 |
| Personal | `~/.claude/skills/<name>/SKILL.md` | 你的所有项目 |
| Project | `.claude/skills/<name>/SKILL.md` | 仅当前项目 |
| Plugin | `<plugin>/skills/<name>/SKILL.md` | 插件启用处 |

**同名覆盖规则**：enterprise > personal > project；任意层级都能覆盖同名内置 Skill（比如项目里的 `.claude/skills/code-review/` 会顶掉内置 `/code-review`）。Plugin Skill 走命名空间（`plugin-name:skill-name`），不会和上述层级冲突。

还有两个容易被忽略的发现行为：**向上继承**——从启动目录到仓库根的每一级 `.claude/skills/` 都会被加载，所以你在子目录启动也能拿到根目录定义的 Skill；**向下按需**——当你编辑 `packages/frontend/` 下的文件时，`packages/frontend/.claude/skills/` 也会被发现。后者在 monorepo 里特别有用，但同名会以目录限定名出现，比如 `apps/web:deploy`。

**热重载**：会话进行中，对 `~/.claude/skills/` 或项目 `.claude/skills/` 下文件的增删改会即时生效，无需重启。唯一的例外是——会话启动时还不存在的**顶层 skills 目录**，新建后需要重启 Claude Code 才能被监听。

### 第二段：曝光（Surfacing）

被发现不等于被加载。Claude Code 把所有 Skill 的 `name` + `description`（加上可选的 `when_to_use`）打包成一段 `<available_skills>` 清单，**注入到 Skill 工具的描述里**塞给模型。也就是说，模型每次都"看得见"所有 Skill 的名字和一句话简介，但**只有简介、没有 body**。

这里有两个硬约束决定你 description 写得好不好：

1. **单条上限 1,536 字符**：`description` + `when_to_use` 合并后被截断到 1,536 字符。超过部分被砍，可能砍掉触发关键词。
2. **总预算**：清单总预算默认是模型上下文窗口的 **1%**。装了太多 Skill 溢出时，**最少被调用的 Skill 的 description 先被丢**，只留名字。

这就是为什么 description 必须把"最关键的用法"放最前面。想看自己的清单有没有被截断，运行 `/doctor`——它会报告多少条 description 被缩短或丢弃。需要更大的清单预算，可设 `skillListingBudgetFraction`（如 `0.02`）或环境变量 `SLASH_COMMAND_TOOL_CHAR_BUDGET`。

逆操作也支持：用 `skillOverrides` 把低优先级 Skill 设成 `"name-only"`，让它只露名字不占描述预算，腾出空间给真正常用的。

### 第三段：调用（Invocation）

调用分两种来源：模型自己调用（看到 description 匹配就发起），或你手动 `/skill-name`。无论哪种，机制都是一次**工具调用往返**：

1. 模型或你发起对 Skill 工具的调用，参数是 `command: "<name>"`（**只传名字，不传参数**）。
2. 系统返回一个 tool_result，里面包含三样东西：一段 `"Launching skill: <name>"`、**Skill 的 base path**（即文件夹绝对路径）、以及 **`SKILL.md` 渲染后的 body**（去掉 frontmatter）。
3. 这段 body 作为**一条消息**进入对话，并在**整个会话剩余部分常驻**。

最关键的认知：**Skill 不是独立进程、不是子代理、不是外部工具，而是被注入的指令**。它通过按需展开提示词来扩展能力，**完全不修改系统主提示词**。这是整个设计最巧妙的地方——主提示词保持精简，能力却能无限扩展。

正因为 body 会常驻且不重读，官方给了生命周期细节：会话中 Auto-compaction 压缩上下文时，每个被调用过的 Skill 会**只保留最近一次调用、前 5,000 token**，所有被保留的 Skill 共享 **25,000 token** 预算，从最近调用的开始填充——调用太多时，较早的 Skill 可能被整体丢弃。所以**写 body 要克制，指令要写成"长期生效"而非"一次性步骤"**。

下面这张图把三段串起来：

```mermaid
flowchart LR
    subgraph D["① 发现 Discovery"]
        D1[Enterprise/Personal<br/>Project/Plugin 目录] --> D2[热重载监听]
    end
    subgraph S["② 曝光 Surfacing"]
        S1["name + description<br/>注入 Skill 工具描述"]
        S1 --> S2{"超 1536 字符?<br/>超总预算?"}
        S2 -->|是| S3["截断/降级为 name-only"]
        S2 -->|否| S4["完整列出"]
    end
    subgraph I["③ 调用 Invocation"]
        I1["模型自调 / 用户 /name"] --> I2["tool_result:<br/>base path + body"]
        I2 --> I3["body 常驻本会话<br/>压缩后保留前 5K token"]
    end
    D --> S --> I
```

## 三、`SKILL.md` 解剖：逐字段吃透 frontmatter

一个 Skill 文件夹长这样：

```text
release-check/
├── SKILL.md          # 必需，主指令
├── release-rubric.md # 支持文件，按需加载
└── scripts/
    └── audit.sh      # 可执行脚本，执行而非加载
```

`SKILL.md` 由两部分组成：顶部 `---` 包裹的 YAML frontmatter，和下面的 Markdown body。frontmatter 全字段表如下（**只有 `description` 是推荐项，其余可选**）：

| 字段 | 作用 |
| :--- | :--- |
| `name` | 列表里的显示名；默认取目录名。**注意：命令名来自目录名而非此字段**（plugin 根 SKILL.md 是唯一例外） |
| `description` | 推荐。告诉模型何时用；省略则取 body 首段 |
| `when_to_use` | 额外触发线索（短语/示例请求），拼到 description 后，共享 1,536 字符额度 |
| `disable-model-invocation` | `true` 则**只有用户**能调；description 不进上下文（适合 `/deploy`、`/commit` 这类有副作用、要控时机的） |
| `user-invocable` | `false` 则**只有模型**能调，从 `/` 菜单隐藏（适合背景知识） |
| `allowed-tools` | Skill 激活期间，列出的工具免审批调用 |
| `disallowed-tools` | 激活期间从工具池移除（下一条用户消息后解除） |
| `context` | 设 `fork` 在隔离子代理中运行 |
| `agent` | 配合 `context: fork`，指定 `Explore`/`Plan`/`general-purpose` 或自定义代理 |
| `model` / `effort` | 激活期间覆盖会话模型/思考强度 |
| `paths` | glob 限定：只在工作区匹配文件时自动激活 |
| `arguments` / `argument-hint` | 声明命名参数与自动补全提示 |
| `hooks` | 绑定到此 Skill 生命周期的钩子 |
| `shell` | 行内 `` !`cmd` `` 用 bash 还是 powershell |

**两个调用控制字段的最易混淆点**，务必记住这张表：

| frontmatter | 用户能调 | 模型能调 | description 是否常驻 |
| :--- | :--- | :--- | :--- |
| 默认 | ✅ | ✅ | ✅ 常驻，body 调用时才加载 |
| `disable-model-invocation: true` | ✅ | ❌ | ❌ 不进上下文 |
| `user-invocable: false` | ❌ | ✅ | ✅ 常驻 |

## 四、从零构建 `release-check` 发布体检技能

现在用一个真实可用的例子，把动态注入、参数替换、支持文件、调用控制四大特性一次跑通。这个 Skill 的目标：发版前一键汇总变更、跑测试、按清单逐项核对，给出 GO/NO-GO。

### 第 1 步：建目录

```bash
mkdir -p ~/.claude/skills/release-check
```

放 `~/.claude/skills/` 是 Personal 级，跨你所有项目可用。若只想在某个项目用，放该项目的 `.claude/skills/`。

### 第 2 步：写 `SKILL.md`

```yaml
---
name: release-check
description: 发布前体检：汇总自上次 tag 以来的变更、运行测试、按发布清单逐项核对并给出 GO/NO-GO 结论。Use when the user asks 发布前检查 / release readiness / 能否发版 / 可以发版了吗。
disable-model-invocation: true
allowed-tools: Bash(git *) Bash(npm test) Bash(npm test *)
---

# 发布前体检

## 当前仓库状态（动态注入，发送前已执行）
- 最新 tag：!`git describe --tags --abbrev=0 2>/dev/null || echo "无 tag"`
- 自该 tag 以来的提交（前 20 条）：
  !`git log --oneline $(git describe --tags --abbrev=0 2>/dev/null)..HEAD 2>/dev/null | head -20`
- 未提交改动数：!`git status --porcelain | wc -l`
- 近 5 次提交的变更统计：!`git diff --stat HEAD~5 2>/dev/null | tail -1`

## 执行步骤
1. 完整读取发布清单 `${CLAUDE_SKILL_DIR}/release-rubric.md`，理解每项判定标准。
2. 运行测试套件 `npm test`。若失败，立即 NO-GO 并列出失败用例。
3. 逐项核对清单，对每一项给出 ✅ / ⚠️ / ❌ 与一句证据（引用上面的真实数据）。
4. 输出结论表 + 一句话 GO / NO-GO。
```

这里密集用到了四个特性，逐个解释：

- **动态上下文注入**：`` !`git ...` `` 这一行会在**模型看到内容之前**先执行，输出替换占位符。注意规则——`!` 必须出现在**行首或空白之后**才被识别；`KEY=!`cmd`` 这种紧跟字符的写法会被当字面量。多行命令用 ```` ```! ```` 围栏块。注入只对原始文件跑**一次**，输出按纯文本插入、**不会再被扫描**，所以命令不能"生成"新的占位符。
- **`${CLAUDE_SKILL_DIR}`**：解析为 `SKILL.md` 所在目录，无论 Skill 装在哪一层都正确，是引用打包脚本和支持文件的正确姿势。
- **`disable-model-invocation: true`**：发版是有副作用的动作，**绝不能让模型自作主张触发**，必须你手动 `/release-check`。
- **`allowed-tools`**：免去每次 `git`/`npm test` 的审批弹窗。注意它**只是预授权，不限制**可用工具——别的工具仍可调，仍受你的权限设置约束。

### 第 3 步：写支持文件 `release-rubric.md`

```markdown
# 发布清单 v1
- 版本号已升级：package.json 的 version 与即将打的 tag 一致
- CHANGELOG 已更新至本次版本
- 全部测试通过（上一步 npm test 必须绿）
- 工作区干净：无未提交改动
- 本次新增代码中无明显 TODO/FIXME
- 依赖无高危漏洞（建议 npm audit）
```

把它独立成文件，是因为清单会随团队演进、可能变长，而**主 `SKILL.md` 应保持在 500 行以内**。大段参考资料移到独立文件、按需加载，是控制常驻上下文成本的关键。

### 第 4 步：验证

在任意 Git 项目里启动 `claude`，做一点小改动，然后：

```text
/release-check
```

**预期结果**：模型先拿到被替换成真实 git 输出的 body（你会看到 tag、提交列表、改动数都是当前仓库的真实数据），读取 rubric，跑测试，逐项打分，最后给出结论表和 GO/NO-GO。**常见问题**：

- **`/release-check` 没出现**：检查目录名是否就是 `release-check`（命令名取自目录名而非 frontmatter 的 `name`）；确认会话启动时该目录已存在。
- **动态注入没执行、显示原文**：确认 `!` 在行首或空白后；确认没有在设置里开 `disableSkillShellExecution: true`（开了的话占位符会被替换成 `[shell command execution disabled by policy]`）。
- **每次都要审批 git 命令**：`allowed-tools` 写法要对，`Bash(git *)` 匹配所有 git 子命令。

## 五、进阶：用 `context: fork` 把 Skill 跑进子代理

有些任务重、产出大，不想污染主会话上下文。给 frontmatter 加 `context: fork`，Skill 的 body 就变成驱动一个**隔离子代理**的提示词——它看不到你的对话历史，跑完只把结论摘要返回。配合 `agent` 字段选执行环境：

```yaml
---
name: deep-dive
description: 深度调研代码库某主题，只读分析后返回带文件引用的总结。Use when the user asks 深入分析 / 这个模块是怎么实现的 / 帮我调研。
context: fork
agent: Explore
allowed-tools: Bash(git log *) Grep Glob Read
---
调研主题：$ARGUMENTS

1. 用 Glob/Grep 定位相关文件与调用链。
2. 精读关键实现，记录 file_path:line。
3. 返回结构化结论，每条结论附可点击的 file_path:line 引用。
```

这里用到了**参数替换**：调用 `/deep-dive PFCP 会话建立流程` 时，`$ARGUMENTS` 被替换成"PFCP 会话建立流程"。还有更细的 `$0`、`$1`（等价 `$ARGUMENTS[0/1]`）、`arguments` 声明的命名参数 `$name`、以及 `${CLAUDE_SESSION_ID}`、`${CLAUDE_EFFORT}`、`${CLAUDE_SKILL_DIR}` 等内置变量。多词参数记得用引号：`/deep-dive "PFCP 建立" 流程` 让 `$0`=PFCP 建立、`$1`=流程。

**`context: fork` 的使用前提**：Skill body 必须是**可执行的任务**，而非"用这些 API 约定"这类无目标指南——否则子代理收到一堆约定却没有任务，会空跑返回。内置 `Explore`/`Plan` 代理会跳过 `CLAUDE.md` 和 git status 以保持上下文精简，所以 fork 到 `Explore` 时子代理只看到 SKILL.md 内容加它自己的系统提示。

## 六、实战案例：从零到可分发的完整闭环

把上面两步整合成一个可分发的小技能包，结构如下：

```text
~/.claude/skills/release-check/
├── SKILL.md
├── release-rubric.md
└── scripts/
    └── audit.sh        # 可选：把 npm audit 包成脚本
```

`scripts/audit.sh`：

```bash
#!/usr/bin/env bash
# 输出高危依赖，供 Skill 调用
npm audit --omit=dev 2>/dev/null | grep -E "high|critical" || echo "无高危依赖"
```

在 `SKILL.md` 的执行步骤里加一条：`运行 ${CLAUDE_SKILL_DIR}/scripts/audit.sh 并纳入核对`。注意 `allowed-tools` 要补 `Bash(${CLAUDE_SKILL_DIR}/scripts/audit.sh)` 或更宽的 `Bash(bash *)`。

**分发三种姿势**：

1. **项目内共享**：把 `.claude/skills/release-check/` 提交进版本控制，队友 clone 即得。
2. **做成插件**：文件夹加 `.claude-plugin/plugin.json`，就能打包成插件（可同时含 agents/hooks/MCP），通过 `/plugin` 安装。
3. **组织级下发**：通过 managed settings 全员部署。

整个构建流程：

```mermaid
flowchart TD
    A["需求：反复粘贴的流程<br/>CLAUDE.md 长成流程"] --> B["建目录 .claude/skills/x"]
    B --> C["写 SKILL.md<br/>description 放关键用法"]
    C --> D{"有副作用?"}
    D -->|是| E["disable-model-invocation: true"]
    D -->|否| F["保持默认双向可调"]
    E --> G["加 allowed-tools 免审批"]
    F --> G
    G --> H{"body 超 500 行?"}
    H -->|是| I["拆支持文件<br/>reference/rubric/scripts"]
    H -->|否| J["保持单文件"]
    I --> K["测试：/x 手动 + 自然语言自动"]
    J --> K
    K --> L{"触发不准 / 产出不稳?"}
    L -->|是| M["skill-creator 跑 evals<br/>A/B 对比+调 description"]
    L -->|否| N["提交/打插件/组织下发"]
    M --> N
```

## 七、调试与评估：触发 ≠ 正确

最容易踩的坑：**看到 Skill 被触发了，就以为它工作正常**。官方明确：触发只能说明"模型找到了它"，不代表产出符合预期。要分别度量两件事——该触发的提示有没有触发、触发后产出对不对。

**基线对比法**：收集几条真实提示，每条在**全新会话**里分别跑"有 Skill"和"禁用 Skill"两遍，对比结果。为什么必须全新会话？因为你在编写 Skill 时残留的上下文会**掩盖**书面指令的漏洞。

**用 `skill-creator` 自动化评估闭环**：

```text
/plugin install skill-creator@claude-plugins-official
```

装不上先 `/plugin marketplace update claude-plugins-official` 刷新市场。装完 `/reload-plugins`，然后让 Claude 评估：`evaluate my release-check skill with skill-creator`。它会：

- 把测试用例（提示、输入文件、期望行为）存进 `evals/evals.json`
- 每个用例起一个**干净子代理**跑，记录 token 数与耗时
- 逐条断言打分，写进 `grading.json`
- 汇总通过率/耗时/token 成 `benchmark.json`，对比"有/无 Skill"
- 支持**版本盲测 A/B**：改 description 前先确认确实是改进再提交
- 支持**description 调优**：生成 should-trigger / should-not-trigger 提示，测命中率，命中错时建议改写

## 八、延伸阅读

- **Anthropic 官方 Skill 文档**：[docs.anthropic.com/en/docs/claude-code/skills](https://docs.anthropic.com/en/docs/claude-code/skills) —— frontmatter 全字段、生命周期、分发权威来源
- **Agent Skills 开放标准**：[agentskills.io](https://agentskills.io) —— 跨工具复用规范，含评估工作流
- **skill-creator 公告**：[claude.com/blog/improving-skill-creator-test-measure-and-refine-agent-skills](https://claude.com/blog/improving-skill-creator-test-measure-and-refine-agent-skills) —— 基准与版本对比模式背景
- **Skill 作者最佳实践**：[platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) —— 跨 Claude 产品的写作指南

## 九、训练营适用性评估

- **适用类型**：AI 工程实战训练营、Agent 开发进阶营、企业内部 Claude Code 推广营。不适合纯零基础营（需先建立 CLAUDE.md 与会话循环的概念）。
- **教学时长**：讲授 + 跟做约 **90–120 分钟**（机制讲解 30min、SKILL.md 字段 20min、release-check 跟做 30min、子代理与 evals 演示 20min）。若含学员自带场景的动手工作坊，再加 60 分钟。
- **考核点建议**：① 能口述三段式机制并解释为何 description 要前置关键用法；② 独立交付一个含动态注入 + 支持文件 + `disable-model-invocation` 的可运行 Skill；③ 用 skill-creator 跑出一份 benchmark.json 并据此改进一次 description。满足三项即视为掌握。

## 参考来源

1. [Extend Claude with skills — Anthropic 官方文档](https://docs.anthropic.com/en/docs/claude-code/skills) — Skill 创建、frontmatter 全字段、生命周期、分发与评估的权威规范
2. [Inside Claude Code Skills: Structure, prompts, invocation — Mikhail Shilkov](https://mikhail.io/2025/10/claude-code-skills/) — 逆向还原 Skill 工具定义、`<available_skills>` 注入与运行时调用往返的内部机制
3. [Claude Code Skills: The Complete Guide (2026) — duet.so](https://duet.so/guides/claude-code-skills-complete-guide) — Skill 文件夹结构、目录层级与实战示例的综合指南
4. [Build a Claude Skill: YAML Frontmatter, SKILL.md Instructions — sjramblings](https://sjramblings.io/building-skills-for-claude-part-2/) — frontmatter 字段与 body 写作的分步教程
5. [Claude Code Customization Guide (2026) — alexop.dev](https://alexop.dev/posts/claude-code-customization-guide-claudemd-skills-subagents/) — CLAUDE.md / Skill / slash command / subagent 的选用决策对比

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
