<!-- title: 造变更影响Skill秒圈波及面 -->

# 造变更影响分析 Skill：改一处代码，秒圈全仓波及面

## 概述

本教程带你从零构建一个 `change-impact`（变更影响分析）Skill：当你在 Git 里改动了一个函数或类，它能自动反向追溯出**所有调用方、必须重跑的测试、待核对的文档**，并按风险等级（红/黄/绿）输出报告。

更重要的是，本教程借此讲解 Skill 最容易被忽视的核心机制——**渐进式披露（Progressive Disclosure）**：为什么 `SKILL.md` 应该只放"流程步骤"，而把繁重的确定性分析交给 `scripts/`、把可复用的规则知识放进 `references/`。学完你掌握的不是又一个孤立小工具，而是一套"让 Skill 经久耐用、不占上下文"的工程化方法。

**适用人群**：已跑通基础 Skill（写过单文件 `SKILL.md`）、想让 Skill 处理真实工程复杂度的开发者。

## 前置条件

- Claude Code 已安装并能在某项目目录下运行
- 一台装有 `git` 的机器；可选 `ripgrep`（`rg`），没有则脚本自动回退到 `grep`
- Python 3.8+（脚本引擎用，任何语言的项目都能分析）
- 一个带 Git 历史的代码仓库用来练手

---

## 一、为什么 90% 的 Skill 用一次就荒废：先搞懂渐进式披露

很多人写 Skill 的第一反应，是把所有知识、规则、示例代码一股脑塞进 `SKILL.md`。这恰恰是失败的根源。

根据 Agent Skills 官方规范，一个 Skill 的目录结构是这样的：

```
change-impact/
├── SKILL.md          # 必需：元数据 + 指令
├── scripts/          # 可选：可执行代码
├── references/       # 可选：参考文档
├── assets/           # 可选：图片等资源
└── skills/           # 可选：嵌套子 skill
```

关键在于 `SKILL.md` 的加载方式是**分阶段**的：

1. **常驻阶段**：只有 `SKILL.md` 的 YAML 元数据（`name` + `description`）被加载进上下文，通常只有几十个 token。Claude 据此判断"要不要激活这个 Skill"。
2. **激活阶段**：当判断相关时，才加载 `SKILL.md` 的正文指令（规范建议控制在 5000 token 以内）。
3. **按需阶段**：正文里若指引"详见 references/xxx.md"或"运行 scripts/xxx"，那些文件**只有真正需要时才被读取或执行**。

这就像图书馆：书名和摘要（元数据）永远摆在检索系统里，但书的具体内容（references）只有你借阅时才占你书桌。如果你把整本书抄进检索卡（巨型 `SKILL.md`），每查询一次就占满书桌，很快系统就转不动了。

业界对此有一条被反复验证的结论：**`SKILL.md` 应该只描述"流程步骤"（Process Steps）——什么时候做、按什么顺序做、调用哪个脚本/参考——而把详尽的规则、长篇示例、领域知识全部下沉到 `references/` 文件里按需读取。**

`change-impact` 正是这套理念的完美载体：它的核心是"对每个被改符号在全仓做反向追溯"——这种确定性、高重复的计算，交给 `scripts/blast_radius.py` 去跑，**一个 token 都不占 Claude 的上下文**；而风险评级细则、输出格式规范，则放进 `references/`，Claude 只在需要补充判断时才去读。

---

## 二、设计 change-impact：把"重活"交给脚本，把"判断"交给 Claude

先想清楚分工边界，这是写出好 Skill 的关键一步。

| 工作 | 谁来做 | 为什么 |
|------|--------|--------|
| 读 `git diff`、提取被改符号 | 脚本 `blast_radius.py` | 确定性逻辑，用正则最稳，不消耗 token |
| 全仓反向追溯调用点 | 脚本（`rg`/`grep`） | 比让 Claude 一个个读文件快几个数量级 |
| 分类（源码/测试/文档） | 脚本 | 规则固定，无需"思考" |
| 评估"这次改动是否破坏契约" | Claude | 需要语义理解，这是 LLM 的主场 |
| 把报告翻译成给队友看的人话 | Claude | 需要表达力 |

这张表直接决定了我们的目录骨架：脚本负责前三行的"苦力"，Claude 负责后两行的"判断"。

---

## 三、动手实现：四个文件，约 120 行代码

### 步骤 1：建立目录骨架

在项目的 `.claude/skills/`（个人级则放 `~/.claude/skills/`）下创建：

```bash
mkdir -p .claude/skills/change-impact/{scripts,references}
```

最终结构：

```
.claude/skills/change-impact/
├── SKILL.md
├── scripts/
│   └── blast_radius.py
└── references/
    └── risk-rules.md
```

### 步骤 2：写 SKILL.md（只放流程，不放知识）

这是整个 Skill 的"调度大脑"，必须精简。注意 `description` 字段——它是触发开关，要写清楚**什么时候该用**：

```markdown
---
name: change-impact
description: 分析一次代码变更（git diff）的波及范围。当用户改了函数/类/接口后问"影响哪些地方""要重跑哪些测试""合并前风险"，或在 PR 审查、重构、删函数前做影响评估时使用。先跑脚本做确定性溯源，再补充语义层面的契约影响。
---

# 变更影响分析

## 何时触发
用户改动了代码，且表达出对"影响范围""回归风险""要跑哪些测试"的关心。

## 执行流程（严格按序）

1. 确认比对区间。若用户给了基准/目标 ref 就用它；否则脚本会自动回退到 main/master/HEAD~1。
2. 运行溯源脚本（在仓库根目录执行）：
   ```bash
   python3 "$SKILL_DIR/scripts/blast_radius.py" [base] [head]
   ```
   把 `$SKILL_DIR` 替换为本 Skill 目录的实际绝对路径。
3. 脚本输出 Markdown 报告，含三档：🔴 高风险调用方 / 🟡 必跑测试 / 🟢 待核对文档。
4. 基于报告，由你（Claude）补充脚本无法判断的部分：
   - 被改符号若被导出为公共 API，提示对外契约可能破坏。
   - 若 🔴 清单里有"核心路径"文件，提升风险定级并说明理由。
   - 若 🟡 为空，明确提示"缺乏测试覆盖，建议补充回归用例"。
5. 输出给用户：一段结论 + 必跑测试清单 + 建议核对项。

## 风险评级细则
参见 references/risk-rules.md，仅在需要解释某条定级理由时读取。
```

**注意三个设计细节**：

- `description` 里密集铺设触发词（"影响范围""回归风险""要跑哪些测试""重构""删函数"），这是 Skill 能否被自动激活的关键。
- 正文只写"5 步流程"，没有一条领域知识，token 极省。
- `$SKILL_DIR` 用环境变量或绝对路径——Claude Code 激活 Skill 时会暴露相关路径，确保脚本可定位。

### 步骤 3：写 scripts/blast_radius.py（确定性分析引擎）

这是 Skill 的"苦力"。完整可运行，覆盖 Python/JS/TS/Go/Rust/Java 等主流语言：

```python
#!/usr/bin/env python3
"""blast_radius.py — 变更影响分析引擎
用法: python3 blast_radius.py [基准ref] [目标ref] [仓库根目录]
基准默认 main/master/HEAD~1(按可用性回退), 目标默认 HEAD。
输出 Markdown 报告: 🔴高风险调用方 / 🟡必跑测试 / 🟢待核对文档。
"""
import os, re, sys, subprocess

EXCLUDE_DIRS = {".git","node_modules","dist","build","vendor",
                "__pycache__",".venv",".next","target",".idea",".vscode"}
TEST_RES = [r"test_.+\.py$", r".+_test\.go$", r".+\.(test|spec)\.(ts|tsx|js|jsx)$",
            r"Test.+\.java$", r"tests?/.+\.(py|ts|js)$"]
# 多语言函数/类定义符号提取
DEF_RE = re.compile(
    r"\b(?:def|class|fn|func|function|interface|type|struct|enum|"
    r"export\s+(?:default\s+|async\s+)?function)\s+([A-Za-z_][A-Za-z0-9_]+)")
KEYWORDS = {"if","for","while","return","new","class","def","import","from",
            "const","let","var","function","func","fn","true","false","null",
            "void","this","self","async","await"}

def run(cmd):
    return subprocess.run(cmd, capture_output=True, text=True).stdout

def pick_base():
    for c in ("main","master","origin/main","origin/master","HEAD~1"):
        if subprocess.run(["git","rev-parse","--verify","--quiet",c],
                          capture_output=True).returncode == 0:
            return c
    return "HEAD~1"

def changed_symbols(base, head):
    diff = run(["git","diff","--unified=0",f"{base}..{head}"])
    syms = set()
    for line in diff.splitlines():
        if line.startswith("+") and not line.startswith("+++"):
            for s in DEF_RE.findall(line):
                if len(s) >= 2 and s not in KEYWORDS:
                    syms.add(s)
    return syms

def is_excluded(path):
    return any(p in EXCLUDE_DIRS for p in path.replace("\\","/").split("/"))

def search(symbol, repo):
    try:
        out = subprocess.run(["rg","-n","--no-heading","-w",symbol,repo],
                             capture_output=True, text=True, check=True).stdout
    except Exception:
        out = subprocess.run(["grep","-rnw",symbol,repo],
                             capture_output=True, text=True).stdout
    hits = []
    for l in out.splitlines():
        seg = l.split(":",2)
        if len(seg) < 3: continue
        path, ln, code = seg[0], seg[1], seg[2]
        if is_excluded(path): continue
        hits.append((symbol, path, ln, code.strip()))
    return hits

def kind(path):
    p = path.replace("\\","/")
    if any(re.search(r, p) for r in TEST_RES): return "test"
    if p.endswith(".md"): return "doc"
    return "src"

def main():
    repo = sys.argv[3] if len(sys.argv) > 3 else os.getcwd()
    base = sys.argv[1] if len(sys.argv) > 1 else pick_base()
    head = sys.argv[2] if len(sys.argv) > 2 else "HEAD"
    syms = changed_symbols(base, head)
    if not syms:
        print(f"## 变更影响分析 `{base}..{head}`\n\n未识别到函数/类级符号——"
              "可能仅改了函数内部实现或纯文本, 建议人工定位核心改动。")
        return
    buckets = {"src":[], "test":[], "doc":[]}
    for s in syms:
        for h in search(s, repo): buckets[kind(h[1])].append(h)
    print(f"# 变更影响分析报告\n")
    print(f"**比对** `{base}..{head}`  **改动的符号**: "
          + ", ".join(f"`{s}`" for s in sorted(syms)) + "\n")
    print(f"## 🔴 高风险调用方({len(buckets['src'])} 处)")
    for s,p,l,c in sorted(buckets['src'], key=lambda x:x[1])[:40]:
        print(f"- `{p}:{l}` 调用 `{s}` — `{c[:70]}`")
    print(f"\n## 🟡 必跑测试({len(set(x[1] for x in buckets['test']))} 个文件)")
    seen=set()
    for s,p,l,c in buckets['test']:
        if p not in seen: seen.add(p); print(f"- `{p}` 命中 `{s}`")
    if not seen: print("- 未找到直接覆盖, 建议补回归用例")
    print(f"\n## 🟢 待核对文档({len(buckets['doc'])} 处)")
    for s,p,l,c in buckets['doc'][:20]:
        print(f"- `{p}:{l}` 提到 `{s}`")
    print("\n---\n_确定性溯源由 blast_radius.py 完成; 契约/语义影响由 Claude 补充。_")

if __name__ == "__main__":
    main()
```

赋予执行权限：`chmod +x .claude/skills/change-impact/scripts/blast_radius.py`

**设计要点**：

- 符号提取用一条正则覆盖多语言，命中新增的 `def`/`function`/`func`/`class` 等，过滤关键字避免噪音。
- 反向追溯优先用 `rg`（快），没有则回退 `grep`，保证可移植。
- 排除 `node_modules`、`.venv`、`dist` 等目录，避免误报。
- 输出是结构化 Markdown，Claude 拿到后可直接"解读 + 补充语义"。

### 步骤 4：写 references/risk-rules.md（按需加载的规则）

这部分是"图书馆里的书"，**默认不加载**，只有 Claude 需要解释某条定级理由时才读：

```markdown
# 风险评级细则（change-impact 参考）

## 🔴 高风险（必须人工核对）
- 调用点位于 `src/api/`、`src/core/`、`internal/` 等核心路径。
- 被改符号出现在 ≥ 5 个文件的导入语句中（公共 API 特征）。
- 改动涉及函数签名（参数增删、返回类型变化）。

## 🟡 中风险（务必重跑测试）
- 存在直接覆盖的测试文件 → 必跑。
- 无覆盖但调用方 ≥ 3 → 必须先补用例再合并。

## 🟢 低风险（顺手核对）
- 仅文档/注释引用，行为不受影响。
- 调用点位于已废弃目录（`deprecated/`、`legacy/`）。

## 误报处理
- 短名符号（如 `get`、`id`）易误匹配，溯源时若某命中明显无关，可忽略并说明。
```

### 步骤 5：安装与触发

无需额外注册——放入 `.claude/skills/` 后，Claude Code 启动时会自动发现。触发方式有两种：

**自动触发**：直接用自然语言，description 里的关键词会命中。
```
我刚改了 parseConfig 函数，帮我看看影响哪些地方，要重跑哪些测试？
```

**强制触发**（调试时用）：`/change-impact` 或明确点名。

预期结果：Claude 先跑脚本，拿到三档报告，再补一段语义化结论，例如"这个函数是 8 个模块的公共入口，签名变更属高风险，必须先跑 `test_config.py` 再合并"。

---

## 四、实战：在真实项目上跑一次

准备一个有多处调用的项目，制造一次签名变更：

```bash
git checkout -b feat/add-timeout
# 编辑 config.py，给 parse_config 加一个 timeout 参数
sed -i 's/def parse_config(path):/def parse_config(path, timeout=30):/' config.py
```

在 Claude Code 里输入：

> 这次改动会影响哪些地方？合并前我要跑哪些测试？

Claude 会执行脚本，你看到的报告大致长这样：

```markdown
# 变更影响分析报告
**比对** `main..HEAD`  **改动的符号**: `parse_config`

## 🔴 高风险调用方(6 处)
- `src/cli/run.py:42` 调用 `parse_config` — `cfg = parse_config(args.file)`
- `src/api/server.py:118` 调用 `parse_config` — `conf = parse_config(p)`
- ... (共 6 处)

## 🟡 必跑测试(2 个文件)
- `tests/test_config.py` 命中 `parse_config`
- `tests/cli/test_run.py` 命中 `parse_config`

## 🟢 待核对文档(1 处)
- `docs/config.md:23` 提到 `parse_config`
```

随后 Claude 补充语义判断：

> `parse_config` 被 6 处调用，且新增了带默认值的 `timeout` 参数——由于有默认值，**现有调用方不会编译报错**，但行为会变化（超时会触发）。属于中高风险。**必跑** `tests/test_config.py` 和 `tests/cli/test_run.py`；**建议**核对 `docs/config.md` 补充新参数说明。

注意这处分工之美：脚本用 0 token 完成了 6 处调用 + 2 个测试 + 1 篇文档的定位；Claude 只贡献了"有默认值所以不破坏编译，但行为变了"这一条脚本永远说不出的人话判断。

---

## 五、进阶：嵌套 Skill 与验证反馈循环

**嵌套 Skill**：当 `change-impact` 长大到包含多个子能力时，可在 `skills/` 子目录里放嵌套 Skill。例如把"仅分析测试影响"拆成子 Skill `change-impact/skills/test-only/`，各自有独立 `SKILL.md`。复杂项目这样能进一步压缩主 Skill 的体积。

**验证反馈循环**：进阶实践是为 Skill 建一组"黄金案例"——准备 3-5 个已知影响范围的小改动（含期望报告），写成校验脚本，在 Skill 改动后跑一遍，确保溯源结果不回归。这是把 Skill 当"软件"来维护的关键习惯。

---

## 六、常见问题排查

**问题：脚本识别不到任何符号。**
原因：改动可能只在函数体内（如改一行赋值），没有新增 `def`/`function`。
对策：脚本已对这种情况给出提示。若想覆盖"改函数体也追溯调用方"，可在 `changed_symbols` 里额外提取被改文件中的**所有顶层符号**（用 AST 或更宽松的正则）。

**问题：误报太多，短名如 `get`、`run` 满屏。**
对策：在 `KEYWORDS` 集合里加上这些高频词；或要求符号长度 ≥ 4；或在 `search` 里要求符号后跟 `(`（函数调用特征）。

**问题：大仓库跑得慢。**
原因：每个符号都全仓 `rg` 一遍，符号多时线性放大。
对策：把多个符号合并成一条正则 `rg -w "sym1|sym2|sym3"` 一次性搜索，减少进程启动开销；或先用 `git diff --name-only` 限定只在被改模块的"依赖方"目录里搜。

**问题：Skill 没被自动触发。**
原因：`description` 里缺触发词，或措辞太泛。
对策：审视 `description`，补上用户真实会说的短语（"影响哪些""要跑哪些测试""能不能合并"），并避免与其它 Skill 的描述撞车。

**问题：`$SKILL_DIR` 找不到路径。**
对策：在 SKILL.md 流程里改用相对仓库根的明确路径，例如 `python3 .claude/skills/change-impact/scripts/blast_radius.py`；个人级 Skill 用 `~/.claude/skills/...`。

---

## 工作流全景图

下图展示 Skill 的渐进式披露架构——元数据常驻、正文按需、脚本零 token、参考最末：

```mermaid
flowchart TD
    A[用户: 这次改动影响哪些地方?] --> B[匹配 description 触发 Skill]
    B --> C[仅加载 SKILL.md 元数据<br/>name+description ~几十 token]
    C --> D{需要执行分析?}
    D -->|是| E[加载 SKILL.md 正文流程 ~300 token]
    E --> F[调用 scripts/blast_radius.py]
    F --> G[确定性溯源<br/>git diff + ripgrep<br/>不占 Claude 上下文]
    D -->|需解释定级| H[按需读 references/risk-rules.md]
    G --> I[Claude 解读报告<br/>补充语义/契约影响]
    H --> I
    I --> J[输出红黄绿三档影响清单]
```

下图是 `blast_radius.py` 内部的溯源流水线：

```mermaid
flowchart LR
    A[git diff base..head] --> B[正则提取被改符号<br/>def/class/function...]
    B --> C[对每个符号 ripgrep 全仓溯源]
    C --> D{按文件类型分类}
    D --> E1[🔴 源码调用方]
    D --> E2[🟡 测试文件]
    D --> E3[🟢 文档引用]
    E1 --> F[结构化 Markdown 报告]
    E2 --> F
    E3 --> F
    F --> G[Claude 接管: 语义判断 + 建议]
```

---

## 延伸阅读

- **Agent Skills 官方规范**：目录结构、frontmatter 字段、token 预算建议的权威出处，写任何 Skill 前都该读一遍。
- **Claude Code 官方 Skills 文档**（code.claude.com/docs/en/skills）：创建、管理、分享 Skill 的官方指南。
- **Skill 的渐进式披露架构**：社区对"为什么 SKILL.md 只放流程、细节下沉 references"的深度论证，是本教程第一部分的理论依据。
- **带脚本与参考文件的高级 Skill 实战**：45-60 分钟的中级教程，涵盖验证反馈循环与多文件组织，可与本教程互补。

---

## 训练营适用性评估

- **适合类型**：中高级开发者训练营、AI 辅助工程效能（DevEfficiency）专题、代码审查与质量保障实训。**不适合**纯零基础营——需要先掌握单文件 Skill 与 Git 基础。
- **教学时长**：建议 60-75 分钟（讲机制 15 分钟 + 跟练搭建 35 分钟 + 实战跑通 15 分钟 + 排错讨论 10 分钟）。
- **学员产出**：每人带走一个可在自己工作仓库直接落地的 `change-impact` Skill，并掌握"脚本扛确定性、references 存知识、SKILL.md 只做调度"的可迁移方法论。
- **延伸练习**：让学员把分析目标从"函数调用"扩展到"数据库表结构变更"（扫描 SQL/ORM 模型），体会同一套多文件架构如何复用到新领域。

---

## 参考来源

1. [Agent Skills Specification](https://agentskills.io/specification) — Agent Skills 完整格式规范：目录结构、frontmatter、body token 预算（<5000）与加载机制
2. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — Claude Code 官方 Skill 创建、管理与分享指南
3. [Claude Code Skills Architecture: Why Your skill.md Should Only Contain Process Steps](https://www.mindstudio.ai/blog/claude-code-skills-architecture-skill-md-reference-files) — 论证 SKILL.md 只放流程、细节下沉 references 的渐进式披露架构
4. [How to Create Agent Skills with Scripts and Reference Files](https://aihola.com/article/advanced-agent-skills-scripts-references) — 用 scripts 与 references 构建高级 Skill 的中级实战，含验证反馈循环
5. [Agent Skills — Claude Platform Docs](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) — Claude 平台对 Agent Skills 模块化能力的官方总览

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