<!-- title: 从零构建许可证合规Skill -->

## 概述

本教程面向已经会用 Claude Code 跑任务、但还没亲手造过一个 Skill 的开发者。我们将从一句模糊需求——"帮我把项目里不合规的开源依赖揪出来"——出发，完整走通一个自定义 Skill 的诞生全过程：需求拆解、目录骨架、frontmatter 描述工程、捆绑脚本、参考表、主体指令、本地触发测试与迭代。最终产出的 `license-radar` Skill 能够扫描多语言依赖清单，按团队许可证策略分级（通过/复审/阻断），并输出一份可评审的报告。

## 前置条件

- 已安装 Claude Code v1.0 及以上（`claude --version` 可正常输出）
- 一台能跑 `python3` 与 `bash` 的 macOS / Linux / WSL 环境
- 一个带有真实依赖文件（`package.json`、`requirements.txt`、`go.mod`、`pom.xml`、`Cargo.toml` 任意一种）的测试项目
- 了解 Markdown 与 YAML 基本语法即可，不要求精通

---

## 正文

### 第 1 步：先拆需求，再动键盘

很多人造 Skill 失败在第一步：直接打开编辑器写 `SKILL.md`。Skill 不是普通文档，它是一段"模型按需加载的能力声明"。开写前，先用三句话回答清楚三个问题：

1. **触发场景是什么？** —— 用户会说什么话、在什么上下文里，才需要这个能力？
2. **核心动作是什么？** —— 这个 Skill 到底让模型干一件什么事？
3. **确定性部分交给谁？** —— 哪些工作适合写成脚本（确定、可复现、不占上下文），哪些适合写成自然语言指令（需要模型判断）？

对"许可证合规扫描"这个需求，拆解如下：

| 维度 | 结论 |
|------|------|
| 触发场景 | 用户说"检查依赖/许可证合规""引入新包前审一下""有没有 GPL 风险" |
| 核心动作 | 扫描依赖清单 → 抽取许可证 → 按策略分级 → 出报告 |
| 确定性脚本 | 扫描文件、抽取依赖名、按映射表分类、生成表格 |
| 模型判断 | 解释为什么某个许可证被阻断、给出替换建议、与用户确认策略豁免 |

这一步的产出决定了后面三件事：`description` 怎么写（对应触发）、脚本放什么（对应确定性）、主体指令写什么（对应判断）。**预期结果**：一张类似上面的拆解表，能一句话讲清这个 Skill 的边界。

**常见问题**：需求边界太宽（"扫描所有代码问题"）。**解决**：把 Skill 收窄到单一职责。一个"许可证扫描器"远比一个"万能安全扫描器"更容易被精准触发。

### 第 2 步：创建 Skill 目录骨架

Claude Code 通过扫描固定目录发现 Skill，按优先级为：个人级 `~/.claude/skills/` ＞ 项目级 `.claude/skills/` ＞ 插件级。教学阶段建议放在个人级，便于跨项目练习：

```bash
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`）才会被重新发现。

### 第 3 步：frontmatter 是整个 Skill 的命门

Skill 采用**渐进式披露（progressive disclosure）**架构，分三阶段加载：

- **阶段一**：会话启动时，只把每个 Skill 的 `name` + `description`（约 30–50 tokens）载入系统提示，建立能力索引；
- **阶段二**：模型语义匹配命中后，才加载完整 `SKILL.md` 主体；
- **阶段三**：主体指令引用到的参考文件、脚本，按需读取或执行——**脚本代码本身不进入上下文，只有输出进入**。

这意味着：**决定 Skill 能否被触发的，只有 `description` 这一个字段**。它是 Skill 的"门口招牌"，写不好就永远进不去。一份合格的 frontmatter：

```yaml
---
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 **不会报错**，只是悄悄不加载——这是最隐蔽的坑。

### 第 4 步：把确定性逻辑写成捆绑脚本

"扫描文件 + 抽依赖 + 查表分类"是确定性的、最适合交给代码的工作。把这些放进脚本，模型只需 `python3 scan.py` 调用一次，只有最终报告进入上下文——这是 Skill 省 token 的核心手段。

`scripts/scan.py`：

```python
#!/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` 在脚本里补全（联网部分单独成函数，离线时优雅降级）。

### 第 5 步：把策略表放进按需加载的参考文件

策略细节（为什么 AGPL 阻断、各许可证传染性如何）属于"偶尔才需要"的知识，放主体会浪费 token，放进 `references/` 按需读取最合适。

`references/license-policy.md`（节选）：

```markdown
# 许可证分级策略

## 🔴 阻断（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"，模型只在需要解释时才读它，平时不占上下文。

### 第 6 步：写主体指令——告诉模型"怎么用这个能力"

主体（frontmatter 之后的 Markdown）是阶段二加载的完整指令。核心是交代清楚：输入怎么来、脚本怎么调、结果怎么呈现、遇到未知怎么处理。`SKILL.md` 主体：

```markdown
# 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`。**解决**：在主体里把脚本调用写成**明确的命令行**，并说明"确定性扫描必须用脚本"。越具体，模型越听话。

### 第 7 步：重启、触发、验证

1. 退出当前会话，重新 `claude` 启动，让索引重建。
2. 验证显式触发：输入 `/license-radar`，应看到命令候选。
3. 验证语义触发：换几种自然语言输入，确认自动命中：
   - "帮我审一下这个项目有没有 GPL 风险"
   - "我们要引入一个新依赖，先做个 license compliance 检查"
   - "扫一下依赖的许可证合不合规"

**预期结果**：三种说法都应自动加载 `license-radar`，模型在"思考"里会出现类似 "Reading license-radar skill" 的迹象，并按既定流程产出报告。

**常见问题**：某句说法不触发。**解决**：把这句真实措辞补进 `description` 的触发词列表，重启再试。这是一个反复打磨的过程。

### 第 8 步：迭代调优的三个杠杆

Skill 不是一次成型，靠三个杠杆持续打磨：

- **描述杠杆**：触发不稳定 → 改 `description`，加真实措辞、收窄边界。
- **结构杠杆**：主体太长拖慢响应 → 拆分到 `references/`，让阶段二更轻。
- **确定性杠杆**：某类误判反复出现 → 把判断规则下沉进 `scripts/`，从"模型猜"变"脚本算"。

---

## 完整实战案例：给一个 Node 项目做合规体检

准备一个含 `package.json` 的玩具项目：

```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 的实际效果。

---

## 工作流程图

```mermaid
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[完成]
```

## 延伸阅读

- Claude 官方 Skill 创建文档：`https://claude.com/docs/skills/how-to` —— frontmatter 必填字段与文件格式权威说明
- ClaudSkills 的 SKILL.md frontmatter 全字段参考 —— 每个字段类型、是否必填、运行时如何读取
- `anthropics/skills` 仓库 Developer Guide —— 官方 Skill 设计规范与示例
- Claude Code Skills vs MCP Servers —— 何时该用 Skill、何时该用 MCP

## 训练营适用性评估

- **适用类型**：面向"想从使用者进阶为定制者"的开发者训练营，建议安排在"入门首跑"之后、"插件打包分发"之前作为第二阶段核心实验。
- **教学时长**：约 90–120 分钟（含动手）。其中 frontmatter 描述工程与触发调试是难点，建议预留 30 分钟专门做"换说法测触发"的互动练习。
- **前置衔接**：学员需先掌握 `CLAUDE.md` 记忆与基本对话，再进入本节。
- **后续拓展**：可自然过渡到"用 Evals 打磨 Skill""把 Skill 打包成 Plugin 分发""Skill 调用外部脚本工程化"等进阶主题。

## 参考来源

1. [Claude Code Skills: Complete Technical Deep-Dive（gregpriday/claude-code-docs）](https://github.com/gregpriday/claude-code-docs/blob/main/claude-code-skills.md) —— 渐进式披露三阶段加载、无持久化、模型触发机制的系统性技术综述
2. [SKILL.md frontmatter reference — every YAML field explained（ClaudSkills）](https://claudskills.com/learn/skill-md-frontmatter-reference/) —— name/description/model/allowed-tools/user-invokable 等字段的逐项权威参考
3. [Creating custom skills — Claude 官方文档](https://claude.com/docs/skills/how-to) —— SKILL.md 必填字段、YAML 格式与目录放置的官方规范
4. [Claude Code Skill Frontmatter: Every YAML Option Explained（allahabadi.dev）](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) —— 含 disable-model-invocation 等全部字段的初学者友好图解，并附 description 触发优化建议

> [!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)
