<!-- title: 渐进式披露构建分层Skill -->

## 概述

本教程教你用**渐进式披露（Progressive Disclosure）**架构，从零构建一个工业级的 Claude Code Skill。不再把所有逻辑塞进一个臃肿的 `SKILL.md`，而是拆成「入口 + 参考文件 + 脚本 + 模板」四层，让 Claude 按需加载，节省上下文、提升触发准确度。适合已经会写基础 Skill、想做大型可维护技能库的开发者。

## 前置条件

- 已安装 Claude Code 并能正常对话（输入 `claude` 进入 REPL）
- 了解 `SKILL.md` 基本结构（看过 Round 1 或官方文档）
- 本机有 Python 3.9+（实战案例的提取脚本用 Python）
- 一个真实代码仓库可选——本教程自带 Express 示例，无需额外项目
- 文末命令均在 Linux/macOS 验证；Windows 用户用 WSL2 或 Git Bash

## 核心概念：为什么需要分层

很多人第一次写 Skill，会把几百行规则、所有框架的提取逻辑、完整的脚本全部堆进 `SKILL.md`。结果是：**只要这个技能被加载，Claude 的上下文窗口就被吃掉一大块**，而其中 80% 的内容（别的框架的规则）在本次任务里根本用不上。

Claude Code 官方明确推荐「文件系统驱动的渐进式披露」设计——这也是 Skills 区别于普通 prompt 的核心机制：

| 层级 | 内容 | 何时加载 | 上下文成本 |
|---|---|---|---|
| L1 入口 `SKILL.md` | 触发描述 + 工作流概览 + 文件索引 | 技能被激活时 | 低（建议 < 200 行） |
| L2 参考文件 `references/*.md` | 某框架的详细规则、边界情况 | Claude 判断需要时用 `Read` 读取 | 仅按需 |
| L3 脚本 `scripts/*` | AST 解析、批量处理等可执行逻辑 | 工作流要求执行时通过 `Bash` 运行 | 几乎为零（只看输出） |
| L4 模板/示例 | 输出模板、样例产物 | 生成最终交付物时读取 | 仅按需 |

理解这张表，就理解了分层的全部价值：**入口越精简，触发越准；细节越靠后，成本越低。**

## 第一步：规划目录结构

我们要构建的实战 Skill 叫 `api-contract-extractor`——扫描代码仓库，自动提取 REST 接口并生成 OpenAPI 3.0 文档。它天然需要多框架支持（Express / FastAPI / Spring），是演示分层的理想场景。

在终端创建骨架：

```bash
mkdir -p ~/.claude/skills/api-contract-extractor/{references,scripts,templates,examples}
touch ~/.claude/skills/api-contract-extractor/SKILL.md
```

预期结果——最终目录长这样：

```
~/.claude/skills/api-contract-extractor/
├── SKILL.md                     # L1 入口：触发 + 工作流 + 索引
├── references/
│   ├── express-routes.md        # L2：Express 提取规则
│   ├── fastapi-routes.md        # L2：FastAPI 提取规则
│   └── spring-routes.md         # L2：Spring 提取规则
├── scripts/
│   └── extract_routes.py        # L3：AST 解析脚本
├── templates/
│   └── openapi-3.0.yaml         # L4：输出模板
└── examples/
    └── sample-output.yaml       # L4：样例产物
```

**常见问题**：路径找不到？Claude Code 的个人级技能固定放在 `~/.claude/skills/<技能名>/`；项目级则放在仓库根的 `.claude/skills/`；插件技能随插件安装。三类都会被自动发现，但**作用域不同**——个人级全局可用，项目级只在仓库内激活。

## 第二步：写精简的 SKILL.md 入口（L1）

入口文件遵循「少即是多」。它的唯一职责是：**让 Claude 判断是否该激活，并告诉它细节去哪找**。完整内容如下：

```markdown
---
name: api-contract-extractor
description: Extract REST endpoints from source code and generate OpenAPI 3.0 documentation. Use when the user asks to document APIs, produce Swagger/OpenAPI specs, inventory HTTP routes, audit API surface area, or generate API reference docs. Supports Express.js, FastAPI, and Spring Boot. Triggers on phrases like "生成接口文档", "导出 OpenAPI", "列出所有 API", "generate API docs".
allowed-tools: Read, Glob, Grep, Bash, Write
---

# API Contract Extractor

扫描源码，提取 HTTP 路由，输出 OpenAPI 3.0 文档。

## 何时使用
- "生成 OpenAPI 文档" / "导出 Swagger"
- "列出项目里所有接口" / "盘点 API 表面"
- "给这套服务出个接口手册"

## 标准工作流
1. **探测框架**：读 `package.json` / `requirements.txt` / `pom.xml`。
2. **加载规则**：按探测结果，`Read` 对应的 `references/<框架>-routes.md`。
3. **抽取路由**：运行 `python scripts/extract_routes.py <源码目录>`。
4. **填充模板**：用 `templates/openapi-3.0.yaml` 组织发现的路由。
5. **产出**：写出 `openapi.yaml` 并附一张接口汇总表。

## 框架路由表
| 信号文件 | 框架 | 规则文件 |
|---|---|---|
| package.json 含 "express" | Express | references/express-routes.md |
| requirements.txt 含 "fastapi" | FastAPI | references/fastapi-routes.md |
| pom.xml 含 "spring-boot" | Spring Boot | references/spring-routes.md |
| 其他 | 回退到通用正则 | references/generic.md（若存在）|

保持本文件精简。详细提取规则、字段映射、边界情况一律放 references/。
```

**几个关键设计点**：

1. **`description` 是触发的命根子**。它必须同时覆盖三类信号：明确动词（document / generate / inventory）、隐式场景（审计 API 表面）、以及中英文关键词。这是整个 Skill 最该反复打磨的字段。

2. **`allowed-tools` 收紧权限**。这里只允许 `Read, Glob, Grep, Bash, Write`，避免技能意外调用网络工具或删除文件，符合最小权限原则。

3. **入口里只放「做什么」和「去哪查」**，绝不放「怎么做」的细节——那是 L2 的活。

**预期结果**：在 Claude Code REPL 里输入 `/skills`，应能看到 `api-contract-extractor` 已被识别；输入「帮我导出这套 Express 服务的 OpenAPI 文档」，技能应自动激活。

**常见问题**：技能不被触发？九成是 `description` 写得太笼统（如「Helps with APIs」）。改成具体动词 + 场景 + 关键词清单，命中率会立刻上升。

## 第三步：写框架专属的参考文件（L2）

参考文件是「按需加载」的精华。入口不读它们，只有当 Claude 确定框架后，才会 `Read` 对应的那一个——其他框架的规则永远不会污染本次上下文。

以 `references/express-routes.md` 为例：

```markdown
# Express 路由提取规则

## 识别模式
Express 路由有三种写法，全部要捕获：

1. `app.get('/path', handler)` / `app.post(...)` 等 app 级方法
2. `router.get('/path', handler)` / `router.use('/prefix', subRouter)` 路由器
3. `app.route('/path').get(...).post(...)` 链式定义

## 路径前缀合并
`router.use('/api/v1', usersRouter)` 会给子路由加前缀。
抽取时必须递归合并：最终路径 = 所有祖先 use() 的前缀拼接 + 叶子路径。

## HTTP 方法映射
| Express 方法 | OpenAPI method |
|---|---|
| get / post / put / delete / patch | 直接对应小写 |
| all / use | 标记为 "通配，需人工确认" |

## 参数提取
- `'/users/:id'` → path 参数 id（string）
- `'/files/:filename.{ext}'` → 两个 path 参数
- query/body 参数需读 handler 源码推断，置信度低时要标注 `[需确认]`

## 边界情况
- 动态注册 `app[method](dynamicVar, ...)`：跳过并记入 `warnings`
- 中间件 `app.use(auth)` 不带路径：忽略
- 路由数组 `routes.forEach(r => app[r.m](r.p, r.h))`：记入 warnings
```

注意它的特点：**全是这个框架特有的、入口放不下的细节**。FastAPI 版会讲「装饰器 `@app.get()` + Pydantic 模型推断」；Spring 版会讲「`@RequestMapping` / `@GetMapping` 注解 + `@PathVariable`」。三者互不干扰，各司其职。

**预期结果**：当 Claude 检测到 Express 项目，它只会 `Read` 这一个文件（几十行），而不是把三个框架的几百行全读进来。这就是上下文成本的悬崖式下降。

**常见问题**：参考文件互相矛盾？为每个文件顶部写明适用范围，并在入口的「框架路由表」里给出明确分流逻辑，让 Claude 无需猜测。

## 第四步：写提取脚本（L3）

复杂的逻辑（AST 解析、批量遍历）绝不该让 Claude 用「读文件 + 脑补」来干——既慢又不可靠。把它们固化成脚本，Claude 只需 `Bash` 调用、读取结构化输出即可。

下面是 `scripts/extract_routes.py` 的 Express 版核心（FastAPI/Spring 分支结构类似，用对应解析库）：

```python
#!/usr/bin/env python3
"""extract_routes.py — 扫描源码目录，输出 JSON 格式的路由清单。
用法: python extract_routes.py <源码目录> [--framework express|fastapi|spring]
"""
import argparse, json, re, sys
from pathlib import Path

# 匹配 app/router 的 HTTP 方法调用，捕获方法、路径
ROUTE_RE = re.compile(
    r'(?:app|router|[\w]+Router)\.(get|post|put|delete|patch|all|use)\s*\(\s*[\'"`]([^\'"`]+)[\'"`]'
)

def scan(dir_path, framework="express"):
    routes, warnings = [], []
    suffix = {"express": ".js", "fastapi": ".py", "spring": ".java"}[framework]
    for f in Path(dir_path).rglob(f"*{suffix}"):
        if "node_modules" in f.parts or ".venv" in f.parts:
            continue
        for ln, line in enumerate(f.read_text(errors="ignore").splitlines(), 1):
            m = ROUTE_RE.search(line)
            if not m:
                continue
            method, path = m.group(1), m.group(2)
            if method in ("use", "all") and "/" not in path:
                continue  # 纯中间件，跳过
            entry = {"method": method.upper(), "path": path,
                     "file": str(f), "line": ln, "framework": framework}
            if "$" in path or "{" in path and "}" not in path:
                entry["confidence"] = "low"
                warnings.append(f"{f}:{ln} 疑似动态路由，需人工确认: {path}")
            routes.append(entry)
    return {"framework": framework, "count": len(routes),
            "routes": routes, "warnings": warnings}

if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("dir")
    ap.add_argument("--framework", default="express")
    a = ap.parse_args()
    result = scan(a.dir, a.framework)
    json.dump(result, sys.stdout, ensure_ascii=False, indent=2)
```

**关键工程化要点**：

- **输出必须是 JSON**，方便 Claude 直接解析，而不是要求它读一堆 `print`。
- **带 `warnings` 字段**，把不确定的东西（动态路由、低置信度）显式抛给上层，而不是静默丢弃。
- **跳过依赖目录**（`node_modules` / `.venv`），否则会把第三方库的路由也抽进来。
- **脚本无副作用**——只读、只输出，绝不写文件或调网络。这让它可安全地在任意权限模式下运行。

给脚本加可执行权限：

```bash
chmod +x ~/.claude/skills/api-contract-extractor/scripts/extract_routes.py
```

**预期结果**：在一个 Express 项目里运行 `python extract_routes.py . --framework express`，会得到类似：

```json
{
  "framework": "express",
  "count": 3,
  "routes": [
    {"method": "GET",    "path": "/api/users",     "file": "./routes/users.js", "line": 5,  "framework": "express"},
    {"method": "POST",   "path": "/api/users",     "file": "./routes/users.js", "line": 12, "framework": "express"},
    {"method": "DELETE", "path": "/api/users/:id", "file": "./routes/users.js", "line": 20, "framework": "express"}
  ],
  "warnings": []
}
```

**常见问题**：Claude 跑脚本报「command not found」？在 `SKILL.md` 里明确写出解释器前缀 `python scripts/extract_routes.py`，别只写脚本名；并确认 `allowed-tools` 含 `Bash`。

## 第五步：写模板与样例（L4）

模板让最终产物有统一骨架，Claude 只需填空而非从零发明格式。`templates/openapi-3.0.yaml`：

```yaml
openapi: 3.0.3
info:
  title: {{SERVICE_NAME}} API
  version: 1.0.0
  description: Auto-generated by api-contract-extractor skill
paths:
  {{#each routes}}
  {{this.path}}:
    {{lower this.method}}:
      summary: "{{this.method}} {{this.path}}"
      operationId: {{camelCase this.method}}{{this.path}}
      responses:
        '200':
          description: OK
  {{/each}}
```

样例文件 `examples/sample-output.yaml` 放一份填好的真实输出，给 Claude 一个「成品长什么样」的锚点，能显著提升产出质量与一致性。

## 第六步：控制激活方式（手动 vs 自动）

并非所有 Skill 都该自动触发。本教程的提取器适合自动激活；但若你做了一个**有副作用的技能**（发邮件、部署、删数据），应强制只能手动调用，防止 Claude 自作主张。

在 frontmatter 加一行即可：

```yaml
---
name: dangerous-deploy
description: ...
disable-model-invocation: true   # 关键：对自动选择器隐身
---
```

`disable-model-invocation: true` 的含义是：**技能保持加载，但 Claude 的自动匹配机制看不见它，只能由你显式输入 `/dangerous-deploy` 才会执行**。这是把「自动门」改成「按钮门」的一个字段。

三个激活维度的对照：

| frontmatter 设置 | 自动触发 | 手动 `/技能名` |
|---|---|---|
| 默认（不写） | ✅ | ✅ |
| `disable-model-invocation: true` | ❌ | ✅ |
| 仅描述含明确场景 | 视匹配度 | ✅ |

**常见问题**：设了 `disable-model-invocation: true` 却还能被自动触发？早期版本有过该 bug，确认升级到最新 Claude Code；若仍存在，去官方 issue 反馈。

## 实战案例：给一个 Express 服务生成接口文档

现在用一个最小可运行的 Express 项目验证整套分层架构。

**1. 准备示例项目**（任意目录）：

```bash
mkdir demo-express && cd demo-express
cat > routes/users.js <<'EOF'
const router = require('express').Router();
router.get('/api/users', (req, res) => res.json([]));
router.post('/api/users', (req, res) => res.status(201).json({}));
router.delete('/api/users/:id', (req, res) => res.status(204).end());
module.exports = router;
EOF
echo '{"dependencies":{"express":"^4.18"}}' > package.json
```

**2. 在 Claude Code 里触发技能**（REPL 内输入）：

```
帮我扫描这个项目，导出 OpenAPI 接口文档
```

**3. 观察分层如何运转**：

- Claude 先读 `package.json`，识别出 Express；
- `Read references/express-routes.md`（仅这一份 L2 文件）；
- `Bash python scripts/extract_routes.py . --framework express`；
- `Read templates/openapi-3.0.yaml` + `examples/sample-output.yaml`；
- 合成最终 `openapi.yaml` 并打印接口汇总表。

**4. 产物**（`openapi.yaml` 节选）：

```yaml
openapi: 3.0.3
info:
  title: demo-express API
  version: 1.0.0
paths:
  /api/users:
    get:
      summary: "GET /api/users"
      responses: {'200': {description: OK}}
    post:
      summary: "POST /api/users"
      responses: {'201': {description: Created}}
  /api/users/{id}:
    delete:
      summary: "DELETE /api/users/{id}"
      parameters:
        - {name: id, in: path, required: true, schema: {type: string}}
      responses: {'204': {description: No Content}}
```

整个过程中，Claude 实际读入的技能内容只有：精简的 `SKILL.md` + 一份 `express-routes.md` + 模板与样例。FastAPI、Spring 的规则文件**全程未被触碰**——这就是渐进式披露省下的上下文预算。

## 整体工作流

```mermaid
flowchart TD
    U[用户请求: 导出 API 文档] --> MATCH{description 匹配?}
    MATCH -->|是| L1[加载 L1: SKILL.md 入口]
    MATCH -->|否| X[不激活]
    L1 --> PROBE[探测框架: 读 package.json 等]
    PROBE --> DET{框架判断}
    DET -->|Express| R1[L2: Read express-routes.md]
    DET -->|FastAPI| R2[L2: Read fastapi-routes.md]
    DET -->|Spring| R3[L2: Read spring-routes.md]
    R1 --> RUN
    R2 --> RUN
    R3 --> RUN
    RUN[L3: Bash 运行 extract_routes.py] --> OUT[JSON 路由清单]
    OUT --> TPL[L4: 填充 openapi-3.0.yaml 模板]
    TPL --> FINAL[产出 openapi.yaml + 汇总表]
    style L1 fill:#fef3c7
    style RUN fill:#dbeafe
    style FINAL fill:#d1fae5
```

注意图里 L2 的三个分支**只会走一条**——这正是分层的核心收益可视化。

## 进阶：用 Evals 量化触发准确度

分层架构解决了「成本」，但「触发准不准」要靠测试。建议给每个技能配一组测试用例（正例应触发、负例不应触发），用 `/skill` 评测或自写脚本统计命中率。常见的反模式：`description` 写了「API」这种过于宽泛的词，导致任何提到 API 的对话都误触发。修法是收窄动词与场景描述，把宽泛词替换成具体动作短语。

## 延伸阅读

- Claude Code 官方 Skills 文档（frontmatter 全字段、自动发现机制）
- Progressive Disclosure 架构深度解析（社区 DeepWiki 总结）
- Skill Frontmatter 全字段指南（15 个 YAML 选项逐一讲解）
- `disable-model-invocation` 与手动触发的设计哲学
- SKILL.md 描述字段触发准确率调优实践

## 训练营适用性评估

- **适合**：中高级开发者训练营、AI 工程化实战营、企业内部 Claude Code 推广营
- **不适合**：零基础入门营（需先掌握基础 Skill 写法）
- **预计时长**：讲师演示 45 分钟 + 学员动手 90 分钟，建议用学员自己的真实项目做练习
- **考核点**：能否独立产出一个「入口 < 200 行 + 至少两份 L2 参考文件 + 一个 L3 脚本」的分层技能，并解释每层为何如此拆分

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — 官方权威：SKILL.md 结构、frontmatter、自动发现与激活机制
2. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — 15 个 frontmatter 字段逐一详解，含 `allowed-tools`、`disable-model-invocation`
3. [Progressive Disclosure Architecture — DeepWiki](https://deepwiki.com/spences10/claude-skills-cli/2.1-progressive-disclosure-architecture) — 分层加载架构的社区深度剖析
4. [How Claude Code Automatically Triggers Skills vs Manual Invocation](https://docs.bswen.com/blog/2026-03-24-skill-triggering/) — 自动触发与手动调用对照，`description` 匹配机制
5. [Disable Model Invocation — Developers Digest](https://www.developersdigest.tech/guides/disable-model-invocation) — `disable-model-invocation: true` 的使用场景与副作用技能防护

---

> 本文由微信公众号「51学通信」原创整理，站长爱卫生（微信：gprshome201101）。聚焦通信与 AI 工程化前沿，每日更新，欢迎关注交流。

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