<!-- title: 从零造复杂度热点Skill -->

# 从零造一个代码复杂度热点定位 Skill

## 概述

本教程带你从零创建一个名为 `complexity-hotspot` 的自定义 Claude Code Skill：它能扫描项目里过深、过长的函数，按"复杂度分数"排出重构优先级清单。过程中你会完整走通 Skill 的三大组件、渐进式披露（progressive disclosure）、`SKILL.md` frontmatter、以及如何挂载一段零依赖的可执行脚本。适合已经用 Claude Code 跑过项目、想把重复工作流固化成可复用能力的开发者。

## 前置条件

- 已安装 Claude Code（CLI 可正常运行 `claude` 命令）
- 本机有 Node.js 18+（脚本用，不装任何 npm 包）
- 会读 Markdown 和基础 YAML
- 准备一个真实的代码仓库当"靶子"（JS/TS/Python 均可）

---

## 一、先理解 Skill 的解剖结构

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 的全部内容塞进上下文。

```mermaid
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>/`，随仓库共享给团队）。教学阶段先用个人级。

**操作：**

```bash
mkdir -p ~/.claude/skills/complexity-hotspot/scripts
mkdir -p ~/.claude/skills/complexity-hotspot/references
cd ~/.claude/skills/complexity-hotspot
```

**目录命名铁律**（踩坑高发区）：

1. 文件夹名必须是 **kebab-case**：`complexity-hotspot` ✅，`Complexity_Hotspot` ❌
2. 主文件必须叫 `SKILL.md`，**大小写敏感**：`skill.md` / `SKILL.MD` 都不会被识别
3. 文件夹内**不要放 `README.md`**，所有指令只写进 `SKILL.md` 或 `references/`
4. `name` 字段要与文件夹名**完全一致**，否则按文件系统遍历的工具会错位

**常见问题：** 改了文件夹名却忘了同步 frontmatter 的 `name`，导致 `/complexity-hotspot` 调用报"找不到命令"——这是最高频的初学者故障。

## 三、写好 frontmatter（决定 Skill 能否被触发）

frontmatter 是 Claude 判断"要不要激活这个 Skill"的唯一依据，它夹在两个 `---` 之间。最少只要 `name` 和 `description` 两个字段：

```yaml
---
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 最该打磨的字段**，三条硬规则：

- **必须同时包含 WHAT（做什么）+ WHEN（何时用）**。光写"分析代码复杂度"太虚，Claude 不会主动触发；要塞进具体的触发短语。
- **长度控制在 1024 字符以内**。
- **不要出现 `<` 或 `>`**（出于安全考虑，尖括号会被拒）。

把触发词写成用户真实会说的句子，命中率高得多。中英双语触发词都写上，覆盖面更广。

**`allowed-tools`** 限定这个 Skill 激活时允许调用的工具。这里只需要 `Bash`（跑脚本）和 `Read`（读被分析的文件），最小权限原则能让 Skill 更安全、也更可预测。

**可选字段速览**（按需添加）：`metadata: {author, version}` 记录归属；`argument-hint` 给 `/complexity-hotspot` 这类用户可调用命令提供参数提示。这些不影响触发，只影响元信息。

## 四、编写确定性脚本（Skill 的"计算核心"）

自然语言指令有歧义，关键计算最好交给脚本——这是官方 Office 类 Skill 普遍采用的模式。我们把复杂度扫描写成一段**零依赖的 Node.js**，放进 `scripts/complexity.js`：

```javascript
#!/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/` 里）。
- Python 函数跨度偏大 → 启发式对"模块级常量穿插在函数之间"敏感，可在 `SKILL.md` 里提示 Claude 人工复核。

## 五、写 SKILL.md 正文（教 Claude 怎么用脚本）

frontmatter 让 Claude 知道**何时**登场，正文教它**登场后做什么**。正文要具体、可执行，并明确引用脚本与参考文档：

```markdown
# 代码复杂度热点定位

本 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/ 做深度文档（Level 3 渐进披露）

把"重构判定标准"这种长内容从正文里剥离，放进 `references/refactor-checklist.md`，只在 Claude 真要下结论时才加载：

```markdown
# 重构判定清单
- 分数 0-20：健康，保持。
- 分数 21-40：可优化，关注嵌套>4 的分支。
- 分数 >40：高风险，强烈建议拆分。
- 单函数 >80 行：考虑按职责切分。
- 嵌套 >4 层：优先用早返回 / 卫语句压平。
```

**效果**：日常触发只加载 ~600 字的 `SKILL.md`；只有当 Claude 要解释"为什么这个函数要重构"时，才会打开这份清单。这就是渐进式披露省 token 的本质。

## 七、注册、触发、验证

个人级 Skill 落到 `~/.claude/skills/` 后**自动注册**，无需额外命令。打开任意项目，启动 Claude Code，这样触发：

- 自然语言：`帮我看看 src/ 里哪些函数最该重构`
- 直接命令（若 frontmatter 开了 `user-invokable`）：`/complexity-hotspot src/`

**验证清单：**

1. 输入触发短语，确认 Claude 主动调用本 Skill（顶部会出现 Skill 加载提示）。
2. 确认它执行了脚本并展示 Markdown 表格。
3. 确认它对 Top 3 函数给出了**带源码行号**的具体建议。

**不触发的排查**：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 的价值。

## 训练营适用性评估

- **适合**：工程师入门营（Day 2-3）、团队效能/平台工程营、AI 辅助研发专题营。
- **教学时长**：讲授 + 跟做约 **60-90 分钟**；含学员自造一个同类 Skill 的练习，约半天。
- **难度**：⭐⭐（会写脚本即可，难点在打磨 `description` 的触发词）。
- **延伸作业**：让学员把脚本换成真正的 AST 解析（如 `@babel/parser`），或新增"圈复杂度"指标，体会"脚本升级、Skill 接口不变"的解耦好处。

## 延伸阅读

- 把同一套 Skill 打包成 Plugin 通过 marketplace 分发：`/plugin marketplace add anthropics/skills`
- 进阶字段（`metadata`、`argument-hint`、`model`）的完整语义见 frontmatter 深度参考
- Skill 与 MCP 的分工：MCP 提供"工具与数据访问"，Skill 教 Claude"最佳使用姿势"，两者互补

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — 官方 Skill 创建、管理、共享权威文档
2. [anthropics/skills — GitHub](https://github.com/anthropics/skills) — 官方 Skill 示例仓库，含 SKILL.md 模板与渐进式披露范式
3. [How to create custom skills — Anthropic Support](https://support.claude.com/en/articles/12512198-creating-custom-skills) — 创建自定义 Skill 的官方步骤说明
4. [Claude Code SKILL.md Frontmatter Fields: Complete Reference — ClaudSkills](https://claudskills.com/learn/claude-code-skill-frontmatter-fields/) — 逐字段解析 frontmatter（name/description/allowed-tools/metadata）
5. [Claude Code Skills: How to Set Them Up and Use Them — DEV Community](https://dev.to/padawanabhi/claude-code-skills-how-to-set-them-up-and-use-them-1chm) — 三级渐进式披露、目录命名铁律等实战细节

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