首页 › tutorials › 渐进式披露造依赖许可证合规Skill

Claude Code Skills 进阶实战:用渐进式披露架构造一个多文件合规扫描技能

概述

本教程面向已经会写「一个 SKILL.md 文件搞定一切」、但想把技能做得更稳健、更省 token、更易维护的开发者。你将掌握 Claude Code Skills 的三层渐进式披露(Progressive Disclosure)架构,并通过一个完整的、可直接运行的依赖许可证合规扫描 Skill(dep-license-checker),学会何时把逻辑拆成 bundled 脚本、何时放进按需加载的 references、何时用 assets 模板,最终完成「构建 → 安装 → 触发测试 → eval 验证 → 打包分发」的完整生命周期。适合训练营中「进阶工程化」阶段的学员,预计 2.5–3 小时。

前置条件


一、为什么不能「一个大 SKILL.md 走天下」

很多入门教程教的是「最小可用技能」:一个目录、一个 SKILL.md、几十行指令。这在原型阶段够用,但一旦技能变复杂(分类规则、评分体系、输出模板、几百行分析逻辑),单一文件会出现三个致命问题:

  1. token 失控:每次触发都把全部内容塞进上下文,长会话里持续占用。
  2. 靠模型「猜」而不是「算」:让模型用文本生成去分类、排序、统计,既慢又不稳定。
  3. 难以维护:规则散在自然语言段落里,改一处怕动全身。

Claude Code 官方的设计哲学叫渐进式披露:技能按需分层加载,每一层只在前一层不够用时才进入上下文。理解它,是写出「生产级」技能的分水岭。

二、三层渐进式披露架构

官方文档把 Skills 的加载明确分为三个阶段,每一层都在为 token 效率服务:

阶段 触发时机 进入上下文的内容 典型开销
第 1 层 · 元数据扫描 会话启动时 仅 name + description 每个技能约 30–50 tokens
第 2 层 · 全文加载 模型判定相关后 SKILL.md 正文(不含子文件) 通常 < 5000 tokens
第 3 层 · 资源执行 实际需要时 运行脚本(仅输出进上下文)、按需读 references/assets 脚本代码本身永不进入上下文

关键认知有两条:

注意:会话启动时 Claude 才扫描技能目录,修改任何技能文件后必须重启 Claude Code 才能生效。这是排查「改了不生效」问题的第一站。

flowchart TD
    A[会话启动] --> B[第1层: 扫描所有 SKILL.md 元数据<br/>name+description, 各30-50 tokens]
    B --> C[用户提问]
    C --> D{模型语义匹配}
    D -->|相关| E[第2层: 加载本技能 SKILL.md 正文]
    D -->|无关| Z[技能保持休眠]
    E --> F{需要确定性计算?}
    F -->|是| G[第3层: 执行 bundled 脚本<br/>代码不进上下文, 仅输出]
    F -->|否| H{需要查细则?}
    H -->|是| I[第3层: 按需读 references/*.md]
    H -->|否| J[生成结果]
    G --> J
    I --> J
    J --> K[填充 assets 模板输出报告]

三、规划技能目录结构

我们的目标技能 dep-license-checker:用户问「帮我检查这个项目的依赖许可证合规性」时自动触发——读取 package.json / requirements.txt / go.mod 里的依赖,用确定性脚本分类为宽松型(permissive)/ 左版权型(copyleft)/ 未知型,标记违规项,最后套用模板生成 Markdown 合规报告。

它的目录结构如下,每一层都对应渐进式披露的一个角色:

dep-license-checker/
├── SKILL.md                      # 第2层入口:指令,<500行
├── scripts/
│   └── classify.py               # 第3层:确定性分类器,纯标准库
├── references/
│   ├── license_knowledge.md      # 第3层:SPDX 分类细则,按需加载
│   └── policy_default.md         # 第3层:默认合规策略,按需加载
└── assets/
    └── compliance_report.md      # 第3层:报告模板,按需填充

设计原则一句话:「能用代码算的就别让模型猜,细则文档只在要用时才加载,输出靠模板而非现编。」

四、步骤 1:编写 SKILL.md(第 2 层入口)

SKILL.md 由 YAML frontmatter 和 Markdown 正文组成。frontmatter 有两个必填字段,校验很严格——写错会导致技能静默加载失败(不报错,就是不出现在索引里):

写作诀窍:description 必须同时回答「做什么」和「何时用」,并塞进用户可能脱口而出的触发词。

---
name: dep-license-checker
description: 扫描项目的依赖清单(package.json / requirements.txt / go.mod / Cargo.toml),用确定性脚本将每个依赖的许可证分类为宽松型 / 左版权型 / 未知型,标记违反「仅允许宽松许可证」策略的依赖,并生成 Markdown 合规报告。当用户提到「许可证合规」「license compliance」「检查依赖 license」「GPL/AGPL 风险」「开源许可审查」「引入第三方依赖前检查」时使用。
---

# 依赖许可证合规检查

## 何时触发
用户要把第三方依赖引入项目、或要做开源合规审查、或担心左版权(GPL/AGPL/LGPL/MPL)污染时。

## 执行步骤(务必按顺序)
1. 用 Glob/Grep 找到项目根下的清单文件:`package.json`、`requirements.txt`、`go.mod`、`Cargo.toml`。没有就停止并提示用户。
2. 运行确定性分类器(**首选,代码不进上下文**):
   `python3 scripts/classify.py <清单文件路径>`
   解析它的 JSON 输出,不要自己用文本去分类。
3. 仅当存在 `copyleft` 或 `unknown` 项、且需要向用户解释某许可证含义时,再去读 `references/license_knowledge.md`。否则跳过以省 token。
4. 仅当用户询问「为什么默认禁止左版权」或要改策略时,才读 `references/policy_default.md`。
5. 把脚本输出填进 `assets/compliance_report.md` 模板,生成最终报告。违规项必须高亮,并给出「替换为宽松许可的替代品」建议。

## 边界
- 不联网。本技能基于本地清单与内置的常见包许可证映射做静态判断。
- 对不在映射表里的包标记为 `unknown`,请用户人工确认,不要臆测许可证。

注意正文里大量出现「仅当……才读……」——这正是手动编排渐进式披露的写法:你明确告诉模型第 3 层资源何时该被唤醒。

五、步骤 2:编写 bundled 脚本(第 3 层 · 确定性引擎)

把分类逻辑写成纯标准库 Python,保证零依赖、可单跑、输出 JSON。脚本体量可以很大,但只有最终 JSON 进入上下文。下面是可直接保存运行的 scripts/classify.py:

#!/usr/bin/env python3
"""依赖许可证确定性分类器。纯标准库,输出 JSON。
分类口径:
  permissive = MIT / Apache-2.0 / BSD-* / ISC
  copyleft   = GPL / AGPL / LGPL / MPL
  unknown    = 映射表未覆盖(需人工确认)
退出码:存在 copyleft 违规返回 1,否则 0。
"""
import json, re, sys
from pathlib import Path

# 常见包 -> SPDX 许可证(演示用,可按团队实际扩展)
NPM = {
    "react": "MIT", "react-dom": "MIT", "vue": "MIT", "lodash": "MIT",
    "express": "MIT", "axios": "MIT", "moment": "MIT", "chalk": "MIT",
    "jquery": "MIT", "typescript": "Apache-2.0", "rxjs": "Apache-2.0",
    "elasticsearch": "Apache-2.0", "left-pad": "WTFPL",
    "chart.js": "MIT", "bluebird": "MIT",
}
PY = {
    "requests": "Apache-2.0", "django": "BSD-3-Clause", "flask": "BSD-3-Clause",
    "numpy": "BSD-3-Clause", "pandas": "BSD-3-Clause", "fastapi": "MIT",
    "celery": "BSD-3-Clause", "scipy": "BSD-3-Clause", "pyyaml": "MIT",
    "gunicorn": "MIT", "uvicorn": "BSD-3-Clause",
}
PERMISSIVE = {"MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause",
              "ISC", "0BSD", "WTFPL", "Unlicense"}

def category(license_id: str) -> str:
    if not license_id:
        return "unknown"
    lid = license_id.upper()
    if lid in {x.upper() for x in PERMISSIVE}:
        return "permissive"
    if re.search(r"\b(GPL|AGPL|LGPL|MPL)\b", lid):
        return "copyleft"
    return "unknown"

def parse_package_json(text):
    data = json.loads(text)
    deps = {**data.get("dependencies", {}), **data.get("devDependencies", {})}
    return [(name, NPM.get(name.lower(), "")) for name in deps]

def parse_requirements(text):
    out = []
    for line in text.splitlines():
        line = line.split("#")[0].strip()
        m = re.match(r"^([A-Za-z0-9_.\-]+)", line)
        if m:
            name = m.group(1).lower()
            out.append((name, PY.get(name, "")))
    return out

def parse_go_mod(text):
    out = []
    for line in text.splitlines():
        m = re.match(r"^\s*(require\s+)?([^\s]+)\s+v[\d.]+", line)
        if m:
            pkg = m.group(2).lower()
            # 粗映射:含 / 的取最后一段
            last = pkg.split("/")[-1]
            out.append((pkg, NPM.get(last) or PY.get(last) or ""))
    return out

PARSERS = {
    "package.json": parse_package_json,
    "requirements.txt": parse_requirements,
    "go.mod": parse_go_mod,
}

def main():
    path = Path(sys.argv[1]) if len(sys.argv) > 1 else None
    if not path or not path.exists():
        print(json.dumps({"error": f"未找到清单文件: {path}"}))
        sys.exit(2)
    parser = PARSERS.get(path.name)
    if not parser:
        print(json.dumps({"error": f"不支持的清单类型: {path.name}"}))
        sys.exit(2)

    result = {"permissive": [], "copyleft": [], "unknown": []}
    for name, lic in parser(path.read_text(encoding="utf-8")):
        cat = category(lic)
        result[cat].append({"name": name, "license": lic or "未声明/未知"})

    violations = result["copyleft"]
    result["summary"] = {
        "total": sum(len(result[k]) for k in ("permissive", "copyleft", "unknown")),
        "violations": len(violations),
        "policy": "仅允许宽松许可证 (MIT/Apache/BSD/ISC)",
        "verdict": "不合规" if violations else "合规",
    }
    print(json.dumps(result, ensure_ascii=False, indent=2))
    sys.exit(1 if violations else 0)

if __name__ == "__main__":
    main()

预期结果(针对一个含 react、lodash、elasticsearch 的 package.json):

{
  "permissive": [
    {"name": "react", "license": "MIT"},
    {"name": "lodash", "license": "MIT"},
    {"name": "elasticsearch", "license": "Apache-2.0"}
  ],
  "copyleft": [],
  "unknown": [],
  "summary": {"total": 3, "violations": 0,
              "policy": "仅允许宽松许可证 (MIT/Apache/BSD/ISC)",
              "verdict": "合规"}
}

一旦混入一个 GPL 依赖,copyleft 列表非空、退出码变 1、verdict 变「不合规」——确定性、可复现、零幻觉。

六、步骤 3:编写按需 references(第 3 层 · 知识库)

这两份文件默认休眠,只有 SKILL.md 指引、且任务需要时才被读取。可以写得详尽而不怕拖累日常请求。

references/license_knowledge.md(节选):

# 许可证分类细则

## 宽松型(permissive)——通常可放心引入
- MIT / BSD-2 / BSD-3 / ISC / 0BSD:几乎无义务,保留版权声明即可。
- Apache-2.0:需保留 NOTICE 文件,含专利授权条款,企业友好。

## 左版权型(copyleft)——触发「传染」,需法务介入
- GPL:衍生作品必须同样以 GPL 开源。
- AGPL:网络服务也触发开源义务,SaaS 场景高风险。
- LGPL:可作为库链接而不传染,但修改库本身需开源。
- MPL-2.0:文件级 copyleft,比 GPL 弱。

## 未知型(unknown)
映射表未覆盖。处置:去 npm/PyPI 页面或源码 LICENSE 文件人工核实,
**不要由模型猜测**。

references/policy_default.md(节选):

# 默认合规策略:仅宽松许可证
- 允许:MIT、Apache-2.0、BSD-2/3、ISC、0BSD、Unlicense
- 禁止:GPL、AGPL、LGPL、MPL(除非走例外审批)
- 未知:阻断引入,待人工确认后更新 scripts/classify.py 的映射表
- 若团队策略不同(如允许 LGPL),在此文件修改即可,无需动脚本

七、步骤 4:编写 assets 模板(第 3 层 · 输出骨架)

assets/compliance_report.md(节选)——让模型「填空」而非「从零写」,输出更稳定一致:

# 依赖许可证合规报告

- 检查范围:{{manifest_file}}
- 依赖总数:{{total}}
- 违规项数:{{violations}}
- 结论:**{{verdict}}**

## 违规依赖(需处理)
{{#each copyleft}}
- ⚠️ `{{name}}` —— 许可证 `{{license}}`,建议替换为等价的宽松许可替代品
{{/each}}

## 合规依赖
{{#each permissive}}
- ✅ `{{name}}` —— `{{license}}`
{{/each}}

## 待人工确认
{{#each unknown}}
- ❓ `{{name}}` —— `{{license}}`
{{/each}}

八、步骤 5:安装并触发测试

把整个目录放到技能发现路径之一。三种位置按优先级覆盖(同名时 personal > project > plugin):

# 个人级(跨项目可用)
mkdir -p ~/.claude/skills && cp -r dep-license-checker ~/.claude/skills/

# 或项目级(随 git 共享给团队)
mkdir -p .claude/skills && cp -r dep-license-checker .claude/skills/

重启 Claude Code(发现扫描只发生在启动时),然后在终端对一个小项目说:

「帮我检查一下这个项目依赖的许可证合规性。」

预期:Claude 不需要你输入任何 / 命令——它读到 dep-license-checker 的 description 后自动语义匹配并加载全文,接着调用 scripts/classify.py,最后套模板出报告。这就是「模型调用(model-invoked)」模式:技能是被「想起来」的,不是被「点开」的。

自检要点:若不触发,99% 是 description 不够具体——对照第二条,补足触发词。

九、步骤 6:用 skill-creator 跑 eval 验证

生产级技能必须可量化。用内置的 skill-creator 技能为 dep-license-checker 生成评测集:准备 5–8 条「应触发」的正例(如「这个 npm 包能不能引入」「AGPL 的依赖有风险吗」)和 5–8 条「不应触发」的负例(如「帮我写个单元测试」「重构这个函数」),跑触发准确率;再准备带已知许可证的样例工程,跑分类正确率与退出码。把 eval 结果写进技能 README,作为回归基线。每次改 description 或脚本后重跑,确保没有劣化。

十、步骤 7:打包与分发

flowchart LR
    subgraph 构建
      S1[SKILL.md] --- S2[scripts/classify.py]
      S2 --- S3[references/]
      S3 --- S4[assets/]
    end
    S4 --> R[重启 Claude Code]
    R --> D[启动扫描<br/>仅加载 name+description]
    D --> M{用户提问<br/>模型语义匹配}
    M -->|命中| L2[加载 SKILL.md 正文]
    L2 --> EXE[执行 classify.py<br/>仅 JSON 输出进上下文]
    EXE --> REF{需要细则?}
    REF -->|是| RD[按需读 references]
    REF -->|否| TPL
    RD --> TPL[填 assets 模板]
    TPL --> OUT[Markdown 合规报告]
    EV[skill-creator eval<br/>触发率+分类正确率] -.回归.-> S1

十一、常见问题与解决方案

现象 根因 解决
技能装了却不触发 description 太泛(如「分析数据」) 补「做什么 + 何时用 + 触发词」,参照步骤 1
技能完全不出现 frontmatter YAML 非法 / name 含保留词或大写 校验 --- 三连、name 全小写连字符;用 debug 启动看加载错误
改了文件没生效 启动后才扫描 重启 Claude Code
分类结果不稳定 让模型用文本分类而非跑脚本 在 SKILL.md 强制「先跑 classify.py 再解析 JSON」
上下文莫名变满 references 被无条件读取 把「仅当……才读」写进正文,减少默认加载
依赖不在映射表 设计如此 标 unknown 让用户人工确认,并提示扩充映射表
大团队技能过多变慢 超 ~100 个技能,元数据开销显著 精简到「少而精」,删不用、禁用不常用

十二、训练营适用性评估

延伸阅读

参考来源

  1. Claude Code Skills: Complete Technical Deep-Dive(gregpriday/claude-code-docs) — 三层渐进式披露架构、SKILL.md frontmatter 校验规则、脚本不进上下文等核心机制的权威技术拆解(本文主要事实来源)。
  2. Extend Claude with skills — Claude Code 官方文档 — 技能的创建、目录发现路径(~/.claude/skills/、.claude/skills/、plugin)、管理与共享的官方说明。
  3. Claude Code Skills 2026 — Progressive Disclosure and How to Standardise Workflow — 工程团队视角的三层披露实践与工作流标准化建议。
  4. How to Build a Production-Ready Claude Code Skill — Towards Data Science — 生产级技能的打包、版本管理与生态现状(截至 2026 年 3 月)。
  5. Claude Skills Documentation: Official Reference — Verdent — SKILL.md 格式、目录结构、API 端点与版本管理的开发者参考。

[!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学通信知识星球海报-扫码加入