本教程不讲"又一个具体场景的 Skill",而是带你拆透 Skill 的运转机制本身——从被发现、被曝光、到被调用的完整三段式链路,并对照官方规范逐字段吃透 SKILL.md。学完后你将具备"看一眼任何 Skill 就知道它何时触发、加载什么、消耗多少上下文"的判断力,并能从零构建一个跑通动态注入、参数替换、支持文件、子代理分叉四大特性的 release-check 发布体检技能。适合已经装好 Claude Code、写过几条 CLAUDE.md、想从"会抄"升级到"会设计"的开发者。
/run、/verify 相关特性需此版本;本教程核心机制在更早版本即可)gh CLI(示例的动态注入会调用它,没有也不影响主线)很多人把 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 时做的每一个决策都有了依据。
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 才能被监听。
被发现不等于被加载。Claude Code 把所有 Skill 的 name + description(加上可选的 when_to_use)打包成一段 <available_skills> 清单,注入到 Skill 工具的描述里塞给模型。也就是说,模型每次都"看得见"所有 Skill 的名字和一句话简介,但只有简介、没有 body。
这里有两个硬约束决定你 description 写得好不好:
description + when_to_use 合并后被截断到 1,536 字符。超过部分被砍,可能砍掉触发关键词。这就是为什么 description 必须把"最关键的用法"放最前面。想看自己的清单有没有被截断,运行 /doctor——它会报告多少条 description 被缩短或丢弃。需要更大的清单预算,可设 skillListingBudgetFraction(如 0.02)或环境变量 SLASH_COMMAND_TOOL_CHAR_BUDGET。
逆操作也支持:用 skillOverrides 把低优先级 Skill 设成 "name-only",让它只露名字不占描述预算,腾出空间给真正常用的。
调用分两种来源:模型自己调用(看到 description 匹配就发起),或你手动 /skill-name。无论哪种,机制都是一次工具调用往返:
command: "<name>"(只传名字,不传参数)。"Launching skill: <name>"、Skill 的 base path(即文件夹绝对路径)、以及 SKILL.md 渲染后的 body(去掉 frontmatter)。最关键的认知: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。
mkdir -p ~/.claude/skills/release-check
放 ~/.claude/skills/ 是 Personal 级,跨你所有项目可用。若只想在某个项目用,放该项目的 .claude/skills/。
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。
这里密集用到了四个特性,逐个解释:
!`git ...` 这一行会在模型看到内容之前先执行,输出替换占位符。注意规则——! 必须出现在行首或空白之后才被识别;KEY=!cmd`` 这种紧跟字符的写法会被当字面量。多行命令用 ```! 围栏块。注入只对原始文件跑一次,输出按纯文本插入、不会再被扫描,所以命令不能"生成"新的占位符。${CLAUDE_SKILL_DIR}:解析为 SKILL.md 所在目录,无论 Skill 装在哪一层都正确,是引用打包脚本和支持文件的正确姿势。disable-model-invocation: true:发版是有副作用的动作,绝不能让模型自作主张触发,必须你手动 /release-check。allowed-tools:免去每次 git/npm test 的审批弹窗。注意它只是预授权,不限制可用工具——别的工具仍可调,仍受你的权限设置约束。release-rubric.md# 发布清单 v1
- 版本号已升级:package.json 的 version 与即将打的 tag 一致
- CHANGELOG 已更新至本次版本
- 全部测试通过(上一步 npm test 必须绿)
- 工作区干净:无未提交改动
- 本次新增代码中无明显 TODO/FIXME
- 依赖无高危漏洞(建议 npm audit)
把它独立成文件,是因为清单会随团队演进、可能变长,而主 SKILL.md 应保持在 500 行以内。大段参考资料移到独立文件、按需加载,是控制常驻上下文成本的关键。
在任意 Git 项目里启动 claude,做一点小改动,然后:
/release-check
预期结果:模型先拿到被替换成真实 git 输出的 body(你会看到 tag、提交列表、改动数都是当前仓库的真实数据),读取 rubric,跑测试,逐项打分,最后给出结论表和 GO/NO-GO。常见问题:
/release-check 没出现:检查目录名是否就是 release-check(命令名取自目录名而非 frontmatter 的 name);确认会话启动时该目录已存在。! 在行首或空白后;确认没有在设置里开 disableSkillShellExecution: true(开了的话占位符会被替换成 [shell command execution disabled by policy])。allowed-tools 写法要对,Bash(git *) 匹配所有 git 子命令。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 *)。
分发三种姿势:
.claude/skills/release-check/ 提交进版本控制,队友 clone 即得。.claude-plugin/plugin.json,就能打包成插件(可同时含 agents/hooks/MCP),通过 /plugin 安装。整个构建流程:
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。它会:
evals/evals.jsongrading.jsonbenchmark.json,对比"有/无 Skill"disable-model-invocation 的可运行 Skill;③ 用 skill-creator 跑出一份 benchmark.json 并据此改进一次 description。满足三项即视为掌握。<available_skills> 注入与运行时调用往返的内部机制[!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 等场景全覆盖。
