本教程面向已经会用 Claude Code 跑任务、但还没亲手造过一个 Skill 的开发者。我们将从一句模糊需求——"帮我把项目里不合规的开源依赖揪出来"——出发,完整走通一个自定义 Skill 的诞生全过程:需求拆解、目录骨架、frontmatter 描述工程、捆绑脚本、参考表、主体指令、本地触发测试与迭代。最终产出的 license-radar Skill 能够扫描多语言依赖清单,按团队许可证策略分级(通过/复审/阻断),并输出一份可评审的报告。
claude --version 可正常输出)python3 与 bash 的 macOS / Linux / WSL 环境package.json、requirements.txt、go.mod、pom.xml、Cargo.toml 任意一种)的测试项目很多人造 Skill 失败在第一步:直接打开编辑器写 SKILL.md。Skill 不是普通文档,它是一段"模型按需加载的能力声明"。开写前,先用三句话回答清楚三个问题:
对"许可证合规扫描"这个需求,拆解如下:
| 维度 | 结论 |
|---|---|
| 触发场景 | 用户说"检查依赖/许可证合规""引入新包前审一下""有没有 GPL 风险" |
| 核心动作 | 扫描依赖清单 → 抽取许可证 → 按策略分级 → 出报告 |
| 确定性脚本 | 扫描文件、抽取依赖名、按映射表分类、生成表格 |
| 模型判断 | 解释为什么某个许可证被阻断、给出替换建议、与用户确认策略豁免 |
这一步的产出决定了后面三件事:description 怎么写(对应触发)、脚本放什么(对应确定性)、主体指令写什么(对应判断)。预期结果:一张类似上面的拆解表,能一句话讲清这个 Skill 的边界。
常见问题:需求边界太宽("扫描所有代码问题")。解决:把 Skill 收窄到单一职责。一个"许可证扫描器"远比一个"万能安全扫描器"更容易被精准触发。
Claude Code 通过扫描固定目录发现 Skill,按优先级为:个人级 ~/.claude/skills/ > 项目级 .claude/skills/ > 插件级。教学阶段建议放在个人级,便于跨项目练习:
mkdir -p ~/.claude/skills/license-radar/scripts
mkdir -p ~/.claude/skills/license-radar/references
骨架如下:
license-radar/
├── SKILL.md # 必需:核心指令 + frontmatter
├── scripts/
│ └── scan.py # 确定性扫描逻辑
└── references/
└── license-policy.md # 许可证分级策略表(按需加载)
关键规则:SKILL.md 是唯一必需文件,且文件名大小写必须完全一致。name 字段约定上要与目录名(slug)保持一致,只用小写字母、数字、连字符,长度不超过 64 个字符,且不能包含 "anthropic" 或 "claude" 等保留词。
预期结果:tree ~/.claude/skills/license-radar -L 2 能看到上述结构,目录已就位。
常见问题:改完 Skill 没生效。解决:Claude Code 只在会话启动时扫描一次 Skill 索引,任何文件改动后必须重启会话(退出后重新 claude)才会被重新发现。
Skill 采用渐进式披露(progressive disclosure)架构,分三阶段加载:
name + description(约 30–50 tokens)载入系统提示,建立能力索引;SKILL.md 主体;这意味着:决定 Skill 能否被触发的,只有 description 这一个字段。它是 Skill 的"门口招牌",写不好就永远进不去。一份合格的 frontmatter:
---
name: license-radar
description: |
扫描项目的依赖清单(package.json、requirements.txt、pyproject.toml、
go.mod、pom.xml、Cargo.toml),抽取每个依赖的开源许可证,按团队许可证
策略分级为 allow/review/block,并生成一份 Markdown 合规报告。
Use when the user mentions 许可证合规、依赖审计、license compliance、
引入新依赖前审核、GPL/AGPL 风险排查,或要求扫描开源依赖。
model: sonnet
allowed-tools:
- Read
- Bash
- Grep
- Glob
user-invokable: true
argument-hint: "[项目根目录,默认当前目录]"
---
逐字段说明:
name —— 唯一标识,slug 形态,与目录名一致。description —— 最关键字段,长度上限 1024 字符。必须同时回答"做什么 + 何时用"。Use when ... 是经过验证的高命中句式,要把用户可能说的真实措辞(中英文、口语)都铺进去。model —— 可选,opus/sonnet/haiku 别名比写死模型 ID 更耐用。涉及判断的用 sonnet/opus,纯文本快批用 haiku。allowed-tools —— 可选白名单。本 Skill 需要读文件、跑脚本,故放开 Read/Bash/Grep/Glob;安全敏感、绝不该写文件的 Skill,不放 Write/Edit 即可形成权限围栏。user-invokable: true —— 让该 Skill 也能被用户显式以斜杠命令 /license-radar 调起,绕过自动匹配,便于排错。argument-hint —— 仅在 user-invokable: true 时有意义,给斜杠命令 UI 提供参数占位提示。预期结果:重启会话后,输入 /license-radar 能看到该命令出现在候选列表里。
常见问题①(最高频):装好了却从不自动触发。根因 99% 是 description。模糊的"分析依赖文件"无法被语义匹配命中。修复办法是加入具体触发词与"何时用"的描述,并尽量覆盖用户真实措辞。
常见问题②:YAML 写错导致 Skill 静默失效。解决:分隔符必须是恰好三个连字符 ---(开头一个、YAML 块结束后一个);YAML 缩进必须用空格不能混 Tab;多行描述用 | 块标量。出错时 Claude Code 不会报错,只是悄悄不加载——这是最隐蔽的坑。
"扫描文件 + 抽依赖 + 查表分类"是确定性的、最适合交给代码的工作。把这些放进脚本,模型只需 python3 scan.py 调用一次,只有最终报告进入上下文——这是 Skill 省 token 的核心手段。
scripts/scan.py:
#!/usr/bin/env python3
"""license-radar 扫描脚本:抽取依赖 → 按策略分级 → 输出 Markdown 报告。"""
import json, sys, pathlib
# 团队许可证策略:allow=放行 review=人工复审 block=禁止引入
POLICY = {
"MIT": "allow", "Apache-2.0": "allow", "BSD-2-Clause": "allow",
"BSD-3-Clause": "allow", "ISC": "allow", "0BSD": "allow",
"MPL-2.0": "review", "LGPL-2.1": "review", "LGPL-3.0": "review",
"EPL-2.0": "review", "CDDL-1.0": "review",
"GPL-2.0": "block", "GPL-3.0": "block", "AGPL-3.0": "block",
"SSPL": "block", "BUSL-1.1": "block", " Commons Clause": "block",
}
SEVERITY = {"block": "🔴 阻断", "review": "🟡 复审", "allow": "🟢 通过"}
def scan_node(root):
f = root / "package.json"
if not f.exists(): return []
data = json.loads(f.read_text())
rows = []
for dep in {**data.get("dependencies", {}), **data.get("devDependencies", {})}:
rows.append(("node", dep, "未知")) # 许可证留待模型/工具补全
return rows
def scan_python(root):
rows = []
for name in ("requirements.txt", "pyproject.toml"):
f = root / name
if f.exists():
for line in f.read_text().splitlines():
pkg = line.split("=")[0].split(">")[0].split("<")[0].strip()
if pkg and not pkg.startswith("#"):
rows.append(("python", pkg, "未知"))
return rows
def main():
root = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".")
deps = scan_node(root) + scan_python(root)
print("| 来源 | 依赖 | 许可证 | 分级 |")
print("|------|------|--------|------|")
for src, dep, lic in deps:
level = POLICY.get(lic, "review")
print(f"| {src} | {dep} | {lic} | {SEVERITY[level]} |")
if __name__ == "__main__":
main()
要点:脚本保持单一职责、纯函数、无外部依赖(只用标准库),输出是 Markdown 表格,便于模型直接转述给用户。未知 许可证默认落到 review,由主体指令引导模型去查证。
预期结果:在任意含 package.json 的项目里执行 python3 ~/.claude/skills/license-radar/scripts/scan.py .,能打印出依赖分级表。
常见问题:脚本想联网查许可证真实类型。解决:API 沙箱里无网络;Claude Code 里虽能联网,但为了确定性,推荐把"已知包→许可证"的常用映射写进 references/license-policy.md 由模型查阅,或用 pip index versions/npm view <pkg> license 在脚本里补全(联网部分单独成函数,离线时优雅降级)。
策略细节(为什么 AGPL 阻断、各许可证传染性如何)属于"偶尔才需要"的知识,放主体会浪费 token,放进 references/ 按需读取最合适。
references/license-policy.md(节选):
# 许可证分级策略
## 🔴 阻断(block):禁止引入商业闭源产品
- **AGPL-3.0**:网络使用即触发开源义务,SaaS 场景高风险。
- **GPL-3.0 / GPL-2.0**:强 copyleft,动态/静态链接都可能传染主程序。
- **SSPL / BUSL-1.1 / Commons Clause**:非 OSI 认可,实质限制商用。
## 🟡 复审(review):需法务确认
- **MPL-2.0**:文件级 copyleft,跨文件引用通常安全,需确认。
- **LGPL-3.0**:动态链接库可用,静态链接有风险。
- **未知许可证**:必须人工/工具核实后归类。
## 🟢 通过(allow):可自由使用
- MIT / Apache-2.0 / BSD-2/3-Clause / ISC / 0BSD。
预期结果:主体指令里写"分级依据见 references/license-policy.md",模型只在需要解释时才读它,平时不占上下文。
主体(frontmatter 之后的 Markdown)是阶段二加载的完整指令。核心是交代清楚:输入怎么来、脚本怎么调、结果怎么呈现、遇到未知怎么处理。SKILL.md 主体:
# License Radar —— 开源许可证合规扫描
你是一个许可证合规审计助手。收到扫描请求时,按以下流程执行。
## 执行步骤
1. 确定目标项目根目录(未指定则用当前目录)。
2. 运行捆绑脚本抽取依赖与初步分级:
`python3 scripts/scan.py <根目录>`
3. 脚本输出的"未知"许可证,用 `npm view <pkg> license` 或
`pip show <pkg>` 等命令补全真实许可证。
4. 分级依据见 references/license-policy.md,按需查阅。
5. 汇总成一份 Markdown 报告:先给统计(阻断/复审/通过各几个),
再给明细表,最后对每个 🔴 给出可替换的开源替代建议。
## 输出格式
- 顶部一行结论:"发现 N 个阻断、M 个需复审"。
- 表格列:来源 / 依赖 / 许可证 / 分级。
- 末尾"建议"小节,仅针对阻断项。
## 边界
- 只分析直接依赖,传递依赖标注"未分析"即可。
- 不删除、不修改任何项目文件(你只有读与执行权限)。
- 拿不准的许可证一律归 review,让用户决定。
预期结果:触发后模型会自动按 5 步走,先跑脚本、补全许可证、出报告,而非漫无目的地读文件。
常见问题:模型跳过脚本自己硬读 package.json。解决:在主体里把脚本调用写成明确的命令行,并说明"确定性扫描必须用脚本"。越具体,模型越听话。
claude 启动,让索引重建。/license-radar,应看到命令候选。预期结果:三种说法都应自动加载 license-radar,模型在"思考"里会出现类似 "Reading license-radar skill" 的迹象,并按既定流程产出报告。
常见问题:某句说法不触发。解决:把这句真实措辞补进 description 的触发词列表,重启再试。这是一个反复打磨的过程。
Skill 不是一次成型,靠三个杠杆持续打磨:
description,加真实措辞、收窄边界。references/,让阶段二更轻。scripts/,从"模型猜"变"脚本算"。准备一个含 package.json 的玩具项目:
{
"dependencies": {
"express": "^4.18.0",
"lodash": "^4.17.0",
"some-gpl-lib": "^1.0.0"
}
}
在项目目录里对 Claude Code 说:
帮我审一下这个项目依赖的许可证合不合规
模型典型行为:自动加载 license-radar → 运行 scan.py 得到三行 → 用 npm view some-gpl-lib license 发现是 GPL-3.0 → 查策略表归为 🔴 → 输出:
结论:发现 1 个阻断、0 个需复审、2 个通过。
| 来源 | 依赖 | 许可证 | 分级 |
|------|----------------|-----------|----------|
| node | express | MIT | 🟢 通过 |
| node | lodash | MIT | 🟢 通过 |
| node | some-gpl-lib | GPL-3.0 | 🔴 阻断 |
建议:
- some-gpl-lib(GPL-3.0)会传染主程序,建议替换为 MIT 许可的同类库,
或评估商业许可版本。
整个过程模型没读脚本源码、没把 package.json 全文塞进上下文——只用了脚本输出。这就是渐进式披露省 token 的实际效果。
flowchart TD
A[会话启动] --> B[阶段一: 仅加载 name+description<br/>约30-50 tokens/Skill]
B --> C{用户输入}
C -->|语义匹配命中 description| D[阶段二: 加载完整 SKILL.md 主体]
C -->|未命中| E[不加载, 零开销]
D --> F[阶段三: 按需执行 scripts/ 读取 references/]
F --> G[仅脚本输出进入上下文]
G --> H[产出合规报告]
H --> I{结果不准?}
I -->|是| J[三杠杆调优<br/>描述/结构/确定性]
J --> A
I -->|否| K[完成]
https://claude.com/docs/skills/how-to —— frontmatter 必填字段与文件格式权威说明anthropics/skills 仓库 Developer Guide —— 官方 Skill 设计规范与示例CLAUDE.md 记忆与基本对话,再进入本节。[!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 等场景全覆盖。
