本教程教你用渐进式披露(Progressive Disclosure)架构,从零构建一个工业级的 Claude Code Skill。不再把所有逻辑塞进一个臃肿的 SKILL.md,而是拆成「入口 + 参考文件 + 脚本 + 模板」四层,让 Claude 按需加载,节省上下文、提升触发准确度。适合已经会写基础 Skill、想做大型可维护技能库的开发者。
claude 进入 REPL)SKILL.md 基本结构(看过 Round 1 或官方文档)很多人第一次写 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),是演示分层的理想场景。
在终端创建骨架:
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/;插件技能随插件安装。三类都会被自动发现,但作用域不同——个人级全局可用,项目级只在仓库内激活。
入口文件遵循「少即是多」。它的唯一职责是:让 Claude 判断是否该激活,并告诉它细节去哪找。完整内容如下:
---
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/。
几个关键设计点:
description 是触发的命根子。它必须同时覆盖三类信号:明确动词(document / generate / inventory)、隐式场景(审计 API 表面)、以及中英文关键词。这是整个 Skill 最该反复打磨的字段。
allowed-tools 收紧权限。这里只允许 Read, Glob, Grep, Bash, Write,避免技能意外调用网络工具或删除文件,符合最小权限原则。
入口里只放「做什么」和「去哪查」,绝不放「怎么做」的细节——那是 L2 的活。
预期结果:在 Claude Code REPL 里输入 /skills,应能看到 api-contract-extractor 已被识别;输入「帮我导出这套 Express 服务的 OpenAPI 文档」,技能应自动激活。
常见问题:技能不被触发?九成是 description 写得太笼统(如「Helps with APIs」)。改成具体动词 + 场景 + 关键词清单,命中率会立刻上升。
参考文件是「按需加载」的精华。入口不读它们,只有当 Claude 确定框架后,才会 Read 对应的那一个——其他框架的规则永远不会污染本次上下文。
以 references/express-routes.md 为例:
# 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 无需猜测。
复杂的逻辑(AST 解析、批量遍历)绝不该让 Claude 用「读文件 + 脑补」来干——既慢又不可靠。把它们固化成脚本,Claude 只需 Bash 调用、读取结构化输出即可。
下面是 scripts/extract_routes.py 的 Express 版核心(FastAPI/Spring 分支结构类似,用对应解析库):
#!/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)
关键工程化要点:
print。warnings 字段,把不确定的东西(动态路由、低置信度)显式抛给上层,而不是静默丢弃。node_modules / .venv),否则会把第三方库的路由也抽进来。给脚本加可执行权限:
chmod +x ~/.claude/skills/api-contract-extractor/scripts/extract_routes.py
预期结果:在一个 Express 项目里运行 python extract_routes.py . --framework express,会得到类似:
{
"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。
模板让最终产物有统一骨架,Claude 只需填空而非从零发明格式。templates/openapi-3.0.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 一个「成品长什么样」的锚点,能显著提升产出质量与一致性。
并非所有 Skill 都该自动触发。本教程的提取器适合自动激活;但若你做了一个有副作用的技能(发邮件、部署、删数据),应强制只能手动调用,防止 Claude 自作主张。
在 frontmatter 加一行即可:
---
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 项目验证整套分层架构。
1. 准备示例项目(任意目录):
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. 观察分层如何运转:
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 节选):
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 的规则文件全程未被触碰——这就是渐进式披露省下的上下文预算。
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 的三个分支只会走一条——这正是分层的核心收益可视化。
分层架构解决了「成本」,但「触发准不准」要靠测试。建议给每个技能配一组测试用例(正例应触发、负例不应触发),用 /skill 评测或自写脚本统计命中率。常见的反模式:description 写了「API」这种过于宽泛的词,导致任何提到 API 的对话都误触发。修法是收窄动词与场景描述,把宽泛词替换成具体动作短语。
disable-model-invocation 与手动触发的设计哲学allowed-tools、disable-model-invocationdescription 匹配机制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 等场景全覆盖。
