本教程聚焦 Claude Code Skill 的进阶工程能力:渐进式披露(Progressive Disclosure)、动态上下文注入、子 Agent 执行(context: fork)与触发词工程。适合已能写出基础 SKILL.md、希望把零散提示词升级为"分层加载、权限收敛、可编排"工程化资产的开发者。我们将从启动期的上下文经济学讲起,最终交付一个完整可运行的 release-notes(发布说明生成器)Skill。
/run、/verify 等打包 Skill 需要该版本,自定义 Skill 在更早版本也可用).mjs 脚本)与 gitSKILL.md 由 frontmatter + 正文两部分组成很多新手把 Skill 当成"放大的 prompt",把几百行规范全塞进 SKILL.md。这是对加载机制的误解。
Claude Code 的 Skill 加载是三段式的:
name + description(元数据)注入上下文。这是常驻成本,所以 description 必须精炼。SKILL.md 正文。一旦加载,正文跨轮次常驻——每一行都是反复计费的 token。SKILL.md 里引用的 references/、scripts/、examples/ 等附属文件,只有在真正用到时才被读取。flowchart LR
A[会话启动] -->|仅注入元数据<br/>name+description| B{描述匹配<br/>当前任务?}
B -->|否| C[保持休眠<br/>几乎零成本]
B -->|是| D[加载 SKILL.md 正文<br/>跨轮常驻]
D --> E{需要更多细节?}
E -->|按需| F[读取 references/]
E -->|按需| G[执行 scripts/]
E -->|按需| H[套用 examples/]
F --> I[完成任务]
G --> I
H --> I
官方文档把这个原则总结为一句话:"上下文窗口是公共资源(The context window is a public good)"。你的 Skill 和系统提示词、对话历史、其他 Skill 元数据、用户请求共享这同一块窗口。因此两个核心结论:
SKILL.md 正文要短:一旦加载就常驻,每一行都是持续成本。只写"Claude 还不知道的"内容,默认假设 Claude 已经很聪明。references/,让 Claude 用到再读。这在 description 里几乎零成本。常见问题:我的 Skill 触发了但 Claude 总"忘记"细节。 解决:把细节挪进
references/xxx.md,并在正文里写明"需要 X 规则时,读取references/xxx.md"。常驻正文越短,注意力越集中。
一个 Skill 就是一个目录,SKILL.md 是入口:
my-skill/
├── SKILL.md # 主指令(必需)
├── template.md # 供 Claude 填充的模板
├── examples/
│ └── sample.md # 期望输出的示例
├── references/
│ └── spec.md # 按需加载的详细规范
└── scripts/
└── validate.sh # 可执行脚本
frontmatter 字段(全部可选,仅 description 强烈推荐):
| 字段 | 作用 | 关键约束 |
|---|---|---|
name |
显示名,缺省取目录名 | 小写连字符,应与目录名一致 |
description |
做什么 + 何时用,决定是否触发 | 与 when_to_use 合计在列表中被截断于 1536 字符 |
when_to_use |
触发短语/示例请求,拼接到 description 后 | 计入 1536 上限 |
allowed-tools |
Skill 可免确认使用的工具白名单 | 收敛权限,提升安全 |
disable-model-invocation |
true 时禁止自动触发,只能手动 /name |
手动流程用 |
user-invocable |
false 时从 / 菜单隐藏 |
纯背景知识用 |
context |
fork 表示在隔离子 Agent中运行 |
保护主上下文 |
argument-hint |
自动补全提示,如 [version] |
改善手动调用体验 |
arguments |
命名位置参数,供 $name 替换 |
空格分隔或 YAML 列表 |
下面是一个具备"高级感"的最小 frontmatter:
---
name: release-notes
description: 从 git 历史生成发布说明。当用户要写 changelog、准备发版、询问"这版改了什么"时使用。
when_to_use: 关键词包括 release、changelog、发版、发布说明、version bump
allowed-tools: Bash Read Write
argument-hint: [version]
arguments: version
---
操作说明:在个人目录创建该 Skill,目录名即命令名:
mkdir -p ~/.claude/skills/release-notes
# 编辑 ~/.claude/skills/release-notes/SKILL.md
预期结果:保存后无需重启——Claude Code 会监视 Skill 目录,文件改动在本会话内即时生效("Live change detection")。此后输入 /release-notes 或说"帮我生成发版说明"都会命中。
常见问题:description 里我写了
<和>符号。 解决:description 中禁止使用<>(会破坏元数据解析),用括号或引号代替。
好的 Skill 正文像一张"目录页",把详细材料指向附属文件,需要时再读。这正是渐进式披露的工程化落地。
设计原则——按"自由度"分层。官方把指令的约束强度分为三档,正好对应三种存放方式:
references/。scripts/,正文只写"运行该脚本,勿加参数"。以 release-notes 为例,把 Conventional Commits 和 Keep a Changelog 两套规范各拆成独立参考文件,正文里只在需要时点名:
## 发布说明生成流程
1. 读取 `!git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:"%h %s"`
2. 对提交进行分类(见下)
3. 若涉及破坏性变更,加载 `references/conventional-commits.md` 确认判定规则
4. 按 Keep a Changelog 格式起草,格式细节参考 `references/keep-a-changelog.md`
5. 运行 `scripts/validate-changelog.sh $version` 校验产物
### 分类速查(高频,常驻正文)
- `feat:` → ✨ 新功能(归入 Added)
- `fix:` → 🐛 修复(归入 Fixed)
- `BREAKING CHANGE` / `feat!` → ⚠️ 破坏性(归入 Changed,单列)
- `perf:` → ⚡ 性能(归入 Performance)
注意:分类速查是高频且简短的,留在常驻正文;两套长规范下沉到 references/,用到才加载。这就是"目录页"思维——常驻的永远是高频摘要,细节按需。
常见问题:我每条规则都写了三段解释,Skill 正文涨到 2000 字。 解决:对每一段问"Claude 真的需要这个解释吗?能假设它已经懂吗?"。把解释删掉或下沉。官方对比示例里,50 token 的精炼版完胜 150 token 的啰嗦版。
很多 Skill 的第一个痛点是"Claude 在猜"——猜你的代码、猜你的提交、猜你的配置。动态上下文注入(Dynamic Context Injection)用一行反引号命令解决:在 ! 命令 `` 这种语法里,Claude Code 会先执行命令、把输出替换进正文,再让 Claude 看到内容。
这意味着指令到达时,当前的真实数据已经内联其中,Claude 不再靠猜:
## 当前未发布提交
!`git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:"%h|%s"`
## 当前版本号
!`node -p "require('./package.json').version"`
操作说明:把上面两行放进 release-notes/SKILL.md 顶部。
预期结果:每次激活 Skill,Claude 看到的正文里这两行已被替换成真实输出,例如:
## 当前未发布提交
a1b2c3d|feat: 支持断点续传
d4e5f6g|fix: 修复空指针崩溃
h7i8j9k|feat!: 重构上传 API(破坏性)
## 当前版本号
1.4.2
这是把"实时事实"喂给 Skill 的标准手法,比让 Claude 自己跑命令再总结更稳——数据天然嵌在指令语境里。
常见问题:
!命令` 没有执行,原样出现在输出里。 **解决**:检查命令在$SHELL下能否独立跑通;动态注入只支持单行命令,多步逻辑请封装进scripts/`。
context: fork 隔离重活起草一篇长 changelog 可能消耗上万 token。如果让主会话直接干,这笔上下文会污染并常驻主对话,挤压后续任务的窗口。解法是 context: fork:让该 Skill 在一个隔离的子 Agent里运行,子 Agent 用独立上下文完成工作,只把最终结果交回主会话。
---
name: release-notes
description: ...
context: fork
allowed-tools: Bash Read Write
---
启用 fork 后的语义:
allowed-tools 列出的工具——这同时实现了权限收敛与上下文收敛;sequenceDiagram
participant U as 用户主会话
participant M as 主上下文(有限)
participant S as fork 子Agent(独立窗口)
U->>M: /release-notes 1.5.0
M->>S: 激活 Skill(独立上下文)
S->>S: 注入 git log / 版本号
S->>S: 分类 + 起草 changelog
S->>S: 跑 validate 脚本校验
S-->>M: 仅返回最终 CHANGELOG 段落
M-->>U: 展示结果(主上下文几乎无污染)
何时用 fork:任务会产生大量中间 token 但最终产物精简(如本例);或任务需要严格工具沙箱。何时不该用:任务需要持续读取主对话历史、或需要与用户多轮交互确认——fork 是"一次性委派",不适合交互式流程。
常见问题:开了 fork 后 Skill 拿不到主会话里我粘贴的需求。 解决:fork 子 Agent 不继承完整对话历史。把外部输入通过
arguments传入(如/release-notes 1.5.0),或在正文里用动态注入从文件读取。
触发是 Skill 最常翻车的环节:要么该触发不触发(描述太泛/太窄),要么不该触发乱触发(描述与高频词撞车)。官方把"description + when_to_use"在列表中截断于 1536 字符,所以每一个词都要精打细算。
四条工程化准则:
首句即用例:description 第一句写"做什么 + 何时用",把最高频触发场景放最前,因为列表显示时靠前的内容权重最高。
正向触发短语优先:when_to_use 填具体的用户原话和关键词("发版说明""这版改了什么""version bump"),而非抽象概念。Claude 靠语义相似度匹配,真实用户措辞命中率最高。
避免与高频通用词撞车:别把 description 写成"帮助用户处理各种文本任务"——这种描述会在几乎所有对话里被弱匹配,造成噪声。
双向开关兜底:纯手动流程加 disable-model-invocation: true(如 deploy);纯背景知识加 user-invocable: false(如内部编码规范,不该被 / 直接调用,但 Claude 处理相关任务时自动引用)。
对比一个差的和一个好的 description:
# ❌ 太泛,易误触发
description: 一个强大的工具,帮助处理发布相关的各种事项
# ✅ 具体,用例优先
description: 从 git 历史生成结构化发布说明。当用户要写 changelog、准备发版、
或问"这版改了什么"时使用。输出遵循 Keep a Changelog 格式。
when_to_use: release、changelog、发版、发布说明、version bump、release notes
常见问题:Skill 触发太频繁,写无关代码也跳出来。 解决:收窄 description 用例边界(强调"发版/发布"语境),或直接加
disable-model-invocation: true改为纯手动。
allowed-tools 最小化Skill 默认可继承会话全部工具。但对于"只需读 + 跑脚本 + 写一个文件"的任务,授予全部工具既危险又不必要。allowed-tools 让 Skill 声明它真正需要的最小集合:
allowed-tools: Bash Read Write
进阶用法:还能对 Bash 进一步限定命令前缀,把"能跑任意 shell"收敛为"只能跑这几条命令":
allowed-tools: Bash(git log:*), Bash(git describe:*), Read, Write
这样一来,release-notes 即便被恶意/异常输入诱导,也无法执行 rm -rf 之类的越权命令。结合 context: fork,子 Agent 的工具面被双重收窄——这是把 Skill 投入生产前的安全基线。
常见问题:限制后 Skill 报"工具不可用"。 解决:先把
allowed-tools留空跑通流程,观察 Claude 实际调用了哪些工具,再据此收紧白名单。
release-notes Skill下面把前七节整合成一个开箱即用的发版说明生成器。目录结构:
~/.claude/skills/release-notes/
├── SKILL.md
├── references/
│ ├── conventional-commits.md
│ └── keep-a-changelog.md
├── scripts/
│ ├── classify-commits.mjs
│ └── validate-changelog.sh
└── examples/
└── sample-CHANGELOG.md
SKILL.md(常驻正文刻意精简):
---
name: release-notes
description: 从 git 历史生成结构化发布说明。当用户要写 changelog、准备发版、或问"这版改了什么"时使用。输出遵循 Keep a Changelog 格式。
when_to_use: release、changelog、发版、发布说明、version bump、release notes
allowed-tools: Bash(git log:*), Bash(git describe:*), Bash(node:*), Bash(sh:*), Read, Write
argument-hint: [version]
arguments: version
context: fork
---
## 当前未发布提交
!`git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:"%h|%s"`
## 当前版本号
!`node -p "require('./package.json').version"`
## 流程
1. 调用 `node scripts/classify-commits.mjs`(本 Skill 目录下)对上方提交分类
2. 破坏性变更判定规则,按需查阅 `references/conventional-commits.md`
3. 按 Keep a Changelog 起草,格式细节见 `references/keep-a-changelog.md`
4. 输出风格参考 `examples/sample-CHANGELOG.md`
5. 用 `sh scripts/validate-changelog.sh $version` 校验产物,失败则修正后重跑
参数 `$version` 由用户传入(如 `/release-notes 1.5.0`),未传则用"当前版本号"推断下一版本。
scripts/classify-commits.mjs(低自由度:固定逻辑,吃 stdin、吐 JSON):
import readline from "node:readline";
const rl = readline.createInterface({ input: process.stdin });
const buckets = { Added: [], Fixed: [], Changed: [], Performance: [], Other: [] };
rl.on("line", (line) => {
const [hash, subject = ""] = line.split("|");
const s = subject.trim();
if (/BREAKING CHANGE|!:/i.test(s)) buckets.Changed.push(`⚠️ ${hash} ${s}`);
else if (/^feat/i.test(s)) buckets.Added.push(`${hash} ${s.replace(/^feat(\(\w+\))?:/, "")}`);
else if (/^fix/i.test(s)) buckets.Fixed.push(`${hash} ${s.replace(/^fix(\(\w+\))?:/, "")}`);
else if (/^perf/i.test(s)) buckets.Performance.push(`${hash} ${s}`);
else buckets.Other.push(`${hash} ${s}`);
});
rl.on("close", () => console.log(JSON.stringify(buckets, null, 2)));
scripts/validate-changelog.sh(校验门禁):
#!/usr/bin/env sh
set -e
ver="${1:?用法: validate-changelog.sh <version>}"
file="${2:-CHANGELOG.md}"
grep -q "## \[$ver\]" "$file" || { echo "缺少 [$ver] 标题"; exit 1; }
grep -q "$(date +%Y-%m-%d)" "$file" || { echo "缺少当天日期"; exit 1; }
echo "✅ 校验通过:$file 含 [$ver] 且日期合法"
examples/sample-CHANGELOG.md(输出范本,极简):
## [1.5.0] - 2026-06-13
### ⚠️ Changed
- 重构上传 API(破坏性,a1b2c3d)
### ✨ Added
- 支持断点续传(h7i8j9k)
### 🐛 Fixed
- 修复空指针崩溃(d4e5f6g)
运行与验证:
# 1. 让 Claude 自动触发
> 帮我准备 1.5.0 的发版说明
# 2. 或手动调用并传参
> /release-notes 1.5.0
预期结果:Claude 在隔离子 Agent 中读取真实提交、分类、起草、跑校验,最终把一段结构化 CHANGELOG 写回主会话/文件。主对话上下文几乎不被中间过程污染——这就是渐进披露 + 动态注入 + fork 子 Agent 三者协同的效果。
常见问题:classify 脚本把 Skill 目录路径搞错。 解决:Skill 的附属脚本以 Skill 目录为工作基点;若不确定,在正文用
!pwd`` 注入当前真实路径辅助排查。
把分散脚本/提示词升级为工程化 Skill 的决策路径如下:
flowchart TD
A[重复粘贴的指令/CLAUDE.md里的流程] --> B{值得做成Skill?}
B -- 否 --> Z[留在 CLAUDE.md]
B -- 是 --> C[建目录 + 写 frontmatter]
C --> D{内容多且重?}
D -- 是 --> E[下沉到 references/<br/>正文留摘要]
D -- 否 --> F[直接写在正文]
E --> G{需要实时数据?}
F --> G
G -- 是 --> H[用 !`cmd` 动态注入]
G -- 否 --> I{会产生大量中间token?}
H --> I
I -- 是 --> J[context: fork<br/>隔离子Agent]
I -- 否 --> K[内联运行]
J --> L[allowed-tools 最小化<br/>+ 触发词工程]
K --> L
L --> M[实战验证 + 收紧权限]
code.claude.com/docs/en/skills(含 frontmatter 全字段、动态注入、字符串替换)platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices(精炼度、自由度三档)anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skillsgithub.com/anthropics/skills(可直接克隆学习真实结构)SKILL.md 的进阶开发者;也适合团队 Tech Lead 用于制定内部 Skill 规范。release-notes 并跑通校验);若扩展到团队 Skill 库治理与版本分发,可设计为 半天工作坊。validate-changelog.sh 门禁的 Skill,且主会话上下文增量低于阈值"作为通过标准——同时检验工程质量与上下文经济性。context: fork 子 Agent 执行、Skill 存放位置与优先级[!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 等场景全覆盖。
