本教程带你从零创建一个名为 complexity-hotspot 的自定义 Claude Code Skill:它能扫描项目里过深、过长的函数,按"复杂度分数"排出重构优先级清单。过程中你会完整走通 Skill 的三大组件、渐进式披露(progressive disclosure)、SKILL.md frontmatter、以及如何挂载一段零依赖的可执行脚本。适合已经用 Claude Code 跑过项目、想把重复工作流固化成可复用能力的开发者。
claude 命令)Anthropic 官方仓库对 Skill 的定义很直白:一个文件夹,里面装着指令、脚本和资源,Claude 按需动态加载,以便在专门任务上表现更好。最小形态只有一个 SKILL.md;完整形态通常包含四类文件:
| 组件 | 是否必需 | 作用 |
|---|---|---|
SKILL.md |
必需 | 指令主体 + YAML frontmatter,告诉 Claude 何时用、怎么用 |
scripts/ |
可选 | 可执行脚本(Bash/Python/Node),把确定性逻辑从自然语言里剥离出来 |
references/ |
可选 | 深度参考文档,只在需要时打开 |
assets/ |
可选 | 模板、字体、图片等静态资源 |
关键是渐进式披露的三级加载:Claude 永远先读 frontmatter(Level 1,几十字节);只有判断当前任务相关时,才加载 SKILL.md 正文(Level 2);正文里点到某个脚本或参考文档时,才真正打开那个文件(Level 3)。这套机制让 Claude 不必一次性把所有 Skill 的全部内容塞进上下文。
flowchart LR
A[用户提问] --> B{扫描所有 Skill 的<br/>frontmatter description}
B -->|关键词命中| C[加载该 Skill 的<br/>SKILL.md 正文 Level2]
B -->|不命中| Z[Skill 保持沉默<br/>不占上下文]
C --> D{正文是否需要<br/>确定性计算?}
D -->|是| E[执行 scripts/xxx.js Level3]
D -->|需要背景资料| F[打开 references/xx.md Level3]
E --> G[把脚本输出 + 正文指令<br/>合成最终建议]
F --> G
G --> H[返回给用户]
记住这张图,后面每一步都在实现它。
Skill 可以是个人级(~/.claude/skills/<name>/,所有项目可用)或项目级(<repo>/.claude/skills/<name>/,随仓库共享给团队)。教学阶段先用个人级。
操作:
mkdir -p ~/.claude/skills/complexity-hotspot/scripts
mkdir -p ~/.claude/skills/complexity-hotspot/references
cd ~/.claude/skills/complexity-hotspot
目录命名铁律(踩坑高发区):
complexity-hotspot ✅,Complexity_Hotspot ❌SKILL.md,大小写敏感:skill.md / SKILL.MD 都不会被识别README.md,所有指令只写进 SKILL.md 或 references/name 字段要与文件夹名完全一致,否则按文件系统遍历的工具会错位常见问题: 改了文件夹名却忘了同步 frontmatter 的 name,导致 /complexity-hotspot 调用报"找不到命令"——这是最高频的初学者故障。
frontmatter 是 Claude 判断"要不要激活这个 Skill"的唯一依据,它夹在两个 --- 之间。最少只要 name 和 description 两个字段:
---
name: complexity-hotspot
description: 扫描 JS/TS/Python 源码,定位行数过长或嵌套过深的函数,按复杂度分数排序输出重构优先级清单。Use when the user asks to "find complex functions", "locate refactoring targets", "哪个函数最该重构", "复杂度分析", or wants to reduce a file's cognitive load before touching it.
allowed-tools: Bash, Read
---
description 是整个 Skill 最该打磨的字段,三条硬规则:
< 或 >(出于安全考虑,尖括号会被拒)。把触发词写成用户真实会说的句子,命中率高得多。中英双语触发词都写上,覆盖面更广。
allowed-tools 限定这个 Skill 激活时允许调用的工具。这里只需要 Bash(跑脚本)和 Read(读被分析的文件),最小权限原则能让 Skill 更安全、也更可预测。
可选字段速览(按需添加):metadata: {author, version} 记录归属;argument-hint 给 /complexity-hotspot 这类用户可调用命令提供参数提示。这些不影响触发,只影响元信息。
自然语言指令有歧义,关键计算最好交给脚本——这是官方 Office 类 Skill 普遍采用的模式。我们把复杂度扫描写成一段零依赖的 Node.js,放进 scripts/complexity.js:
#!/usr/bin/env node
/**
* complexity.js — 启发式复杂度热点扫描器(零依赖)
* 用法: node scripts/complexity.js <目录或文件> [--top 20] [--json]
* 指标: span(函数行数) + maxDepth(最大嵌套深度) -> score = span + maxDepth*5
*/
const fs = require('fs');
const path = require('path');
const EXT = { '.js':'js','.jsx':'js','.mjs':'js','.ts':'ts','.tsx':'ts','.py':'py' };
const SKIP = /^(node_modules|\.git|dist|build|out|vendor|\.next|coverage)$/;
const argv = process.argv.slice(2);
const root = argv.find(a => !a.startsWith('--')) || '.';
const topN = (a => a ? +a.split('=')[1] : 20)(argv.find(a => a.startsWith('--top')));
const asJson = argv.includes('--json');
function walk(dir, out = []) {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, e.name);
if (e.isDirectory()) { if (!SKIP.test(e.name)) walk(p, out); }
else if (EXT[path.extname(e.name)]) out.push(p);
}
return out;
}
const indent = ln => ln.match(/^[ \t]*/)[0].replace(/\t/g, ' ').length;
const FN = {
py: ln => /^\s*(async\s+)?def\s+(\w+)|^class\s+(\w+)/.exec(ln),
js: ln => /\bfunction\s+([\w$]+)|\bclass\s+(\w+)|([\w$]+)\s*\([^)]*\)\s*\{|([\w$]+)\s*[:=]\s*(async\s*)?\([^)]*\)\s*=>/.exec(ln)
};
const nameOf = m => m.slice(1).find(Boolean) || '<anon>';
function analyze(file) {
const lang = EXT[path.extname(file)];
const lines = fs.readFileSync(file, 'utf8').split('\n');
const out = [];
for (let i = 0; i < lines.length; i++) {
const m = FN[lang](lines[i]); if (!m) continue;
const ind = indent(lines[i]);
let depth = 0, max = 0, span = 0, j = i, done = false;
while (j < lines.length && span < 1000) {
const ln = lines[j];
for (const ch of ln) { if (ch === '{') depth++; else if (ch === '}') depth--; }
if (lang === 'py') depth = Math.max(0, Math.floor((indent(ln) - ind) / 4));
max = Math.max(max, depth); span++;
if (lang === 'py' && j > i && ln.trim() && indent(ln) <= ind && !/^\s*(#|def |class |return\b)/.test(ln)) done = true;
else if (lang !== 'py' && j > i && depth <= 0 && /[,);}]/.test(ln)) done = true;
j++; if (done) break;
}
out.push({ file, name: nameOf(m), line: i + 1, span, maxDepth: max, score: span + max * 5 });
}
return out;
}
const files = fs.statSync(root).isDirectory() ? walk(root) : [root];
const all = files.flatMap(analyze).filter(f => f.span >= 3)
.sort((a, b) => b.score - a.score).slice(0, topN);
if (asJson) { console.log(JSON.stringify(all, null, 2)); process.exit(0); }
console.log(`# 复杂度热点 Top ${all.length}\n`);
console.log('| # | 分数 | 行数 | 嵌套 | 函数 | 位置 |');
console.log('|---|---|---|---|---|---|');
all.forEach((f, i) => console.log(`| ${i + 1} | ${f.score} | ${f.span} | ${f.maxDepth} | \`${f.name}\` | ${f.file}:${f.line} |`));
console.log(`\n> 评分=行数+嵌套×5。建议优先重构分数>40 的函数。`);
预期结果:在一个真实仓库根目录跑 node ~/.claude/skills/complexity-hotspot/scripts/complexity.js . --top 10,你会得到一张 Markdown 表格,列出最该重构的 10 个函数及其位置。脚本对 JS/TS 用花括号深度、对 Python 用缩进深度,是启发式而非精确 AST 解析——但作为"第一遍热点筛选"足够可靠,且零安装。
常见问题:
Cannot find module → 你目录建错了,确认文件在 scripts/complexity.js。SKIP 正则过滤(如全在 dist/ 里)。SKILL.md 里提示 Claude 人工复核。frontmatter 让 Claude 知道何时登场,正文教它登场后做什么。正文要具体、可执行,并明确引用脚本与参考文档:
# 代码复杂度热点定位
本 Skill 帮用户在动手重构前,先用数据看清"哪个函数最该改"。
## 工作流程
1. 确认分析目标(目录或文件)。未指定时,默认用项目根目录 `.`。
2. 执行扫描,把结果落到 JSON 方便后续引用:
`node scripts/complexity.js <目标> --top 20 --json > /tmp/hotspots.json`
3. 再跑一次输出人类可读的 Markdown 表格,直接展示给用户。
4. 对分数 > 40 的 Top 3 函数,用 Read 打开源文件对应行,给出 1-2 句
具体重构建议(拆分 / 提取函数 / 早返回 / 策略模式)。
5. 如需更系统的判定标准,查阅 references/refactor-checklist.md。
## 输出格式
- 先给 Markdown 热点表(分数、行数、嵌套、函数名、位置)。
- 再给"本周建议优先处理的 3 个函数"及理由。
- 不要替用户直接改代码,除非他明确说"帮我重构"。
## 边界
- 脚本是启发式,对装饰器、嵌套类可能误判,给出建议前必须 Read 核对。
- 只处理 JS/TS/Python,其他语言提示用户暂不支持。
注意正文里每一处指令都指向一个具体动作或文件,而不是"请认真分析复杂度"这类空话。
把"重构判定标准"这种长内容从正文里剥离,放进 references/refactor-checklist.md,只在 Claude 真要下结论时才加载:
# 重构判定清单
- 分数 0-20:健康,保持。
- 分数 21-40:可优化,关注嵌套>4 的分支。
- 分数 >40:高风险,强烈建议拆分。
- 单函数 >80 行:考虑按职责切分。
- 嵌套 >4 层:优先用早返回 / 卫语句压平。
效果:日常触发只加载 ~600 字的 SKILL.md;只有当 Claude 要解释"为什么这个函数要重构"时,才会打开这份清单。这就是渐进式披露省 token 的本质。
个人级 Skill 落到 ~/.claude/skills/ 后自动注册,无需额外命令。打开任意项目,启动 Claude Code,这样触发:
帮我看看 src/ 里哪些函数最该重构user-invokable):/complexity-hotspot src/验证清单:
不触发的排查:90% 是 description 里缺触发词或写了模糊的"帮助开发者"。把用户真实问法逐字写进去。
误触发的排查:把 description 写得太宽(如"分析代码")会让它在无关任务里乱登场。加约束,如"仅在用户明确要求复杂度/重构分析时使用"。
把以上拼起来,你的目录长这样:
~/.claude/skills/complexity-hotspot/
├── SKILL.md # frontmatter + 正文(第三、五步)
├── scripts/
│ └── complexity.js # 确定性扫描核心(第四步)
└── references/
└── refactor-checklist.md # 深度判定标准(第六步)
在一个有历史包袱的仓库里发起对话:
用户:
complexity-hotspot 看看 server/
Claude 会:加载 Skill → 跑 complexity.js server/ --json → 展示热点表 → Read Top 3 函数源码 → 给出"建议把 handleRequest 的鉴权分支提取成 authorize(),当前嵌套 6 层、132 行"这样的可执行结论。整个过程不到 10 秒,且每次结论都基于同一套评分口径——这就是把重复工作流固化成 Skill 的价值。
description 的触发词)。@babel/parser),或新增"圈复杂度"指标,体会"脚本升级、Skill 接口不变"的解耦好处。/plugin marketplace add anthropics/skillsmetadata、argument-hint、model)的完整语义见 frontmatter 深度参考[!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 等场景全覆盖。
