首页 › tutorials › 三段式机制:从零吃透Skill

概述

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

前置条件

一、先画清边界: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 开放标准,可跨多个 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 要克制,指令要写成"长期生效"而非"一次性步骤"。

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

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 文件夹长这样:

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 步:建目录

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

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

第 2 步:写 SKILL.md

---
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。

这里密集用到了四个特性,逐个解释:

第 3 步:写支持文件 release-rubric.md

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

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

第 4 步:验证

在任意 Git 项目里启动 claude,做一点小改动,然后:

/release-check

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

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

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

---
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 内容加它自己的系统提示。

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

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

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

scripts/audit.sh:

#!/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 全员部署。

整个构建流程:

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 自动化评估闭环:

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

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

八、延伸阅读

九、训练营适用性评估

参考来源

  1. Extend Claude with skills — Anthropic 官方文档 — Skill 创建、frontmatter 全字段、生命周期、分发与评估的权威规范
  2. Inside Claude Code Skills: Structure, prompts, invocation — Mikhail Shilkov — 逆向还原 Skill 工具定义、<available_skills> 注入与运行时调用往返的内部机制
  3. Claude Code Skills: The Complete Guide (2026) — duet.so — Skill 文件夹结构、目录层级与实战示例的综合指南
  4. Build a Claude Skill: YAML Frontmatter, SKILL.md Instructions — sjramblings — frontmatter 字段与 body 写作的分步教程
  5. Claude Code Customization Guide (2026) — alexop.dev — 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学通信知识星球海报-扫码加入