首页 › tutorials › 从零构建许可证合规Skill

概述

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

前置条件


正文

第 1 步:先拆需求,再动键盘

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

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

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

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

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

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

第 2 步:创建 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)才会被重新发现。

第 3 步:frontmatter 是整个 Skill 的命门

Skill 采用渐进式披露(progressive disclosure)架构,分三阶段加载:

这意味着:决定 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: "[项目根目录,默认当前目录]"
---

逐字段说明:

预期结果:重启会话后,输入 /license-radar 能看到该命令出现在候选列表里。

常见问题①(最高频):装好了却从不自动触发。根因 99% 是 description。模糊的"分析依赖文件"无法被语义匹配命中。修复办法是加入具体触发词与"何时用"的描述,并尽量覆盖用户真实措辞。

常见问题②:YAML 写错导致 Skill 静默失效。解决:分隔符必须是恰好三个连字符 ---(开头一个、YAML 块结束后一个);YAML 缩进必须用空格不能混 Tab;多行描述用 | 块标量。出错时 Claude Code 不会报错,只是悄悄不加载——这是最隐蔽的坑。

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

"扫描文件 + 抽依赖 + 查表分类"是确定性的、最适合交给代码的工作。把这些放进脚本,模型只需 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 在脚本里补全(联网部分单独成函数,离线时优雅降级)。

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

策略细节(为什么 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",模型只在需要解释时才读它,平时不占上下文。

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

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

第 7 步:重启、触发、验证

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

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

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

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

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


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

准备一个含 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[完成]

延伸阅读

训练营适用性评估

参考来源

  1. Claude Code Skills: Complete Technical Deep-Dive(gregpriday/claude-code-docs) —— 渐进式披露三阶段加载、无持久化、模型触发机制的系统性技术综述
  2. SKILL.md frontmatter reference — every YAML field explained(ClaudSkills) —— name/description/model/allowed-tools/user-invokable 等字段的逐项权威参考
  3. Creating custom skills — Claude 官方文档 —— SKILL.md 必填字段、YAML 格式与目录放置的官方规范
  4. Claude Code Skill Frontmatter: Every YAML Option Explained(allahabadi.dev) —— 含 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学通信知识星球海报-扫码加入