<!-- title: 造变更影响分析Skill:精准定位改动风险面 -->

# 造变更影响分析Skill：精准定位改动风险面

> **概述**：本教程带你从零构建一个「变更影响分析」Skill，让 Claude Code 在你提交代码改动时，自动解析 `git diff`、反向追踪符号引用、定位需要回归的测试，并产出带 Mermaid 依赖图的风险报告。适合掌握基础 Skill 写法、想进阶学习「脚本 Skill + 渐进式披露」分层架构的开发者。

## 前置条件

- 已安装 Claude Code v2.x+，能在 `~/.claude/skills/` 或项目 `.claude/skills/` 下创建目录
- 仓库已纳入 Git 版本控制，存在可对比的基线分支（默认 `origin/main`）
- 系统装有 `rg`（ripgrep）、`git`、`awk`、`jq`（多数发行版自带或可一键装）
- 熟悉 Markdown 与 YAML 基本语法

## 为什么需要这个 Skill

改一行函数签名，到底会波及哪些文件？靠人脑记忆依赖关系在大型代码库里几乎不可能。每次 PR review 都要回答三个问题：**改了什么、影响了什么、要补哪些测试**。「变更影响分析」（Change Impact Analysis，社区俗称 **Blast Radius**）就是用静态追踪的方式，把改动的「爆炸半径」量化出来。

把这套流程固化成一个 Skill，Claude Code 就能在你输入「评估这次改动的影响」时自动跑完整链路，而不是每次都从头口述一遍分析方法。

## 第一步：规划三层渐进式披露结构

Claude Code 的 Skill 采用**渐进式披露**（Progressive Disclosure）设计——上下文按需加载，避免一次性把所有细节塞进模型窗口。一个进阶 Skill 通常分三层：

```mermaid
flowchart TB
    A["① metadata 层<br/>name + description<br/>常驻系统提示词"] --> B{模型判断<br/>是否触发?}
    B -->|是| C["② SKILL.md 正文<br/>触发后加载<br/>核心流程与约束"]
    C --> D{需要执行脚本<br/>或查阅规则?}
    D -->|是| E["③ 资源层<br/>scripts/ 与 references/<br/>按需读取"]
    B -->|否| F["不加载,零成本"]
    E --> G["返回结构化结果"]
    D -->|否| G
```

设计目录结构如下：

```
change-impact-analysis/
├── SKILL.md                      # 主指令：触发场景 + 工作流程
├── scripts/
│   ├── extract-changes.sh        # 从 diff 提取变更的导出符号
│   ├── trace-references.sh       # 反向追踪符号在仓库中的引用
│   ├── find-affected-tests.sh    # 定位需要回归的测试文件
│   └── score-risk.sh             # 计算风险评分
└── references/
    ├── destructive-patterns.md   # 破坏性变更判定规则
    └── report-template.md        # 输出报告模板
```

关键点：**SKILL.md 保持精简**（控制在 200 行内），把判定规则、报告长模板等「重内容」下沉到 `references/`，让模型在真正需要时才去读——这就是渐进式披露省 token 的核心。

## 第二步：写 SKILL.md 主指令

创建 `.claude/skills/change-impact-analysis/SKILL.md`：

```markdown
---
name: change-impact-analysis
description: 评估一次代码改动的"爆炸半径"。当用户提交/比较改动、做 PR review、
  问"这次改动会影响什么"、"哪些测试要回归"、"这个改动安不安全"时使用。
  解析 git diff，反向追踪符号引用，定位受影响测试，输出带 Mermaid 依赖图的风险报告。
allowed-tools: Bash(git diff:*), Bash(git log:*), Bash(git show:*), Bash(bash:*.sh),
  Bash(rg:*), Read, Grep, Glob
---

# 变更影响分析

当被触发时，按以下流程评估当前仓库相对基线分支的改动影响。

## 输入

- 基线分支：默认 `origin/main`。用户指定其他分支则用其指定值。
- 可选：用户给定的具体 commit 范围（如 `HEAD~3..HEAD`）。

## 执行流程

1. **提取变更实体**
   运行 `bash scripts/extract-changes.sh <base>`，得到被删除/修改的导出符号
   （函数、类、export 常量）。重点关注符号定义行的删除——这是破坏性变更的高危信号。

2. **反向追踪引用**
   把上一步的符号写入临时文件，运行 `bash scripts/trace-references.sh <symbols.txt> <root>`
   找出引用这些符号的所有文件，剔除定义文件自身。

3. **定位受影响测试**
   运行 `bash scripts/find-affected-tests.sh`，匹配受影响文件对应的测试
   （约定 `foo.ts` → `foo.test.ts` / `foo.spec.ts` / `test_foo.py`）。

4. **判定破坏性变更**
   读 `references/destructive-patterns.md`，逐条核对当前 diff 是否命中：
   导出符号被删除、函数签名改变、返回类型收窄、公开常量值变更等。

5. **计算风险评分**
   运行 `bash scripts/score-risk.sh <affected> <tests> <breaking(0|1)>`。

6. **产出报告**
   严格按 `references/report-template.md` 的结构输出，含一张 Mermaid 影响图。

## 约束

- 只读分析，绝不修改任何源文件或测试文件。
- 若 diff 为空，直接回报"无改动"，不要编造影响。
- 报告中每个受影响文件必须给出被引用的符号名作为依据，禁止空泛列举。
```

几个写法要点值得展开：

**`description` 是「触发条件」而非「功能简介」。** 它常驻在系统提示词里，决定模型何时主动调用本 Skill。写「当用户问 X、做 Y 时使用」比写「这是一个分析工具」触发率高得多。这是新手最常踩的坑。

**`allowed-tools` 用白名单收窄边界。** 这里只放开只读的 git 命令和脚本执行，把 `Edit`/`Write` 排除在外——影响分析天然是只读操作，白名单从机制上杜绝了「分析着分析着把代码改了」的越权风险。

## 第三步：编写四个分析脚本

把繁重的、确定性的工作交给 `awk`/`rg`/`git`，模型只负责调度与解读——这是「脚本 Skill」模式，比让模型逐文件 `Read` 再推理稳定得多。

**`scripts/extract-changes.sh`** —— 从 diff 提取变更的导出符号：

```bash
#!/usr/bin/env bash
# extract-changes.sh: 从 diff 提取被删除/修改的导出符号
# 用法: bash extract-changes.sh [base=origin/main]
set -euo pipefail
BASE="${1:-origin/main}"
ROOT="$(git rev-parse --show-toplevel)"

git diff "${BASE}...HEAD" --unified=0 --diff-filter=d |
  awk '
    /^\+\+\+ / { next }            # skip file headers
    /^[-+]/ && !/^[-+][-+][-+]/ {  # only real +/- diff lines
      line = $0
      # match exported / defined symbols across JS/TS/Python/Go-ish
      if (line ~ /(export[[:space:]]+(async[[:space:]]+)?function|export[[:space:]]+(default[[:space:]]+)?(const|class)|function[[:space:]]+|def[[:space:]]+|func[[:space:]]+|class[[:space:]]+)/) {
        # strip the leading +/-, collapse spaces
        sub(/^[-+]/, "", line); gsub(/[[:space:]]+/, " ", line)
        print line
      }
    }
  '
```

**`scripts/trace-references.sh`** —— 反向追踪符号引用：

```bash
#!/usr/bin/env bash
# trace-references.sh: 对符号列表反向追踪引用文件
# 用法: bash trace-references.sh <symbols_file> [root=.]
set -euo pipefail
SYMS="$1"; ROOT="${2:-.}"
[ -f "$SYMS" ] || { echo "symbols file not found" >&2; exit 1; }

while IFS= read -r raw; do
  # extract the identifier after the keyword (function|def|const|class ...)
  sym="$(printf '%s' "$raw" | grep -oE '[A-Za-z_][A-Za-z0-9_]*' | sed -n '2p')"
  [ -z "$sym" ] && sym="$(printf '%s' "$raw" | grep -oE '[A-Za-z_][A-Za-z0-9_]*' | head -1)"
  [ -z "$sym" ] && continue
  # whole-word match, list files only
  rg -l --no-ignore-vcs -w "$sym" "$ROOT" 2>/dev/null || true
done < "$SYMS" | sort -u
```

**`scripts/find-affected-tests.sh`** —— 定位对应测试：

```bash
#!/usr/bin/env bash
# find-affected-tests.sh: 受影响文件 → 对应测试文件
# 用法: bash find-affected-tests.sh <affected_files_file>
set -euo pipefail
IN="$1"
while IFS= read -r f; do
  [ -z "$f" ] && continue
  base="${f%.*}"; ext="${f##*.}"
  # 常见测试命名约定
  for pat in "${base}.test.${ext}" "${base}.spec.${ext}" \
             "$(dirname "$f")/__tests__/$(basename "${base}").test.${ext}" \
             "tests/test_$(basename "${base}").py"; do
    [ -f "$pat" ] && echo "$pat"
  done
done < "$IN" | sort -u
```

**`scripts/score-risk.sh`** —— 风险评分：

```bash
#!/usr/bin/env bash
# score-risk.sh: 受影响数 * 3 + 受影响测试 * 2 + 破坏性 * 20
set -euo pipefail
AFFECTED="${1:-0}"; TESTS="${2:-0}"; BREAKING="${3:-0}"
echo $(( AFFECTED * 3 + TESTS * 2 + BREAKING * 20 ))
```

**预期结果**：四个脚本都接受标准输入输出，可独立测试。例如在一个改动了 `utils/format.ts` 导出函数的仓库里：

```bash
$ bash scripts/extract-changes.sh origin/main
export function formatPrice(amount: number, currency: string)
$ bash scripts/extract-changes.sh origin/main > /tmp/syms.txt
$ bash scripts/trace-references.sh /tmp/syms.txt .
src/cart/summary.ts
src/invoice/print.ts
$ bash scripts/score-risk.sh 2 1 0
8
```

**常见问题**

- *符号提取漏掉非英文命名？* `awk` 正则只覆盖常见关键字，团队若有私有命名规范，在正则里补 keyword 即可，不要让模型去猜。
- *`trace-references.sh` 误报？* `rg -w` 全词匹配已能过滤大量误报；对动态调用（字符串拼接方法名）静态分析本就无能为力，报告中需如实标注「无法静态追踪」。
- *测试命名约定不符？* 在 `find-affected-tests.sh` 的 `for pat` 列表里加你们项目的约定路径。

## 第四步：编写引用文件

**`references/destructive-patterns.md`** —— 破坏性变更判定清单（只在与破坏性判定相关时才加载）：

```markdown
# 破坏性变更（Breaking Change）判定清单

命中以下任一条，即视为破坏性变更（`breaking=1`）：

1. **导出符号被删除**：`export function/const/class` 从 diff 中消失。
2. **函数签名改变**：参数数量减少、参数顺序变化、必填参数新增。
3. **返回类型收窄**：`string | undefined` → `string`，或 `any` → 具体类型。
4. **公开常量值变更**：`export const MAX = 100` 变为其他值。
5. **HTTP 接口契约变更**：路由路径、状态码、请求/响应字段移除。

以下不算破坏性：新增导出、内部实现重构、新增可选参数、放宽类型。
```

**`references/report-template.md`** —— 报告模板（控制输出一致性）：

```markdown
# 变更影响分析报告

**基线**：<base> → **HEAD**　**风险评分**：<score>（破坏性：<yes/no>）

## 改动概览
- 变更文件 N 个，删除 X 行 / 新增 Y 行
- 变更的导出符号：`sym1`, `sym2`

## 影响范围（爆炸半径）
| 文件 | 引用的符号 | 路径 |
|------|-----------|------|
| src/a.ts | sym1 | import/调用 |

## 需要回归的测试
- a.test.ts

## 影响关系图
```mermaid
graph LR
  A["utils/format.ts<br/>(改)"] --> B["cart/summary.ts"]
  A --> C["invoice/print.ts"]
```

## 结论与建议
（一句话结论 + 是否建议人工 review 重点模块）
```

## 第五步：实战演练——分析一次真实的破坏性改动

准备一个最小仓库模拟场景：

```bash
mkdir demo-cia && cd demo-cia && git init -q
npm init -y >/dev/null 2>&1

# 初始版本：一个导出函数
cat > utils/format.ts <<'EOF'
export function formatPrice(amount: number, currency = 'USD') {
  return `${currency} ${amount.toFixed(2)}`;
}
EOF

# 两处调用
cat > cart/summary.ts <<'EOF'
import { formatPrice } from '../utils/format';
export const line = (a: number) => formatPrice(a);
EOF
cat > invoice/print.ts <<'EOF'
import { formatPrice } from '../utils/format';
export const bill = (a: number) => formatPrice(a, 'EUR');
EOF
git add -A && git commit -qm "init" && git branch -M main

# 现在做一个破坏性改动：删掉默认参数，改成必填
cat > utils/format.ts <<'EOF'
export function formatPrice(amount: number, currency: string) {
  return `${currency} ${amount.toFixed(2)}`;
}
EOF
git add -A && git commit -qm "breaking: currency now required"
```

在 Claude Code 里把 Skill 放好后，直接对话触发：

> 帮我评估这次改动的影响。

模型会自动按流程执行，最终产出类似报告：

```
# 变更影响分析报告
基线：origin/main → HEAD　风险评分：22（破坏性：是）

## 改动概览
- 变更文件 1 个，删除 1 行 / 新增 1 行
- 变更的导出符号：formatPrice

## 影响范围
| 文件 | 引用符号 |
|------|---------|
| cart/summary.ts | formatPrice（无第二实参，将编译失败）|
| invoice/print.ts | formatPrice（已传 'EUR'，安全）|

## 需要回归的测试
- 暂无对应测试文件（建议补 utils/format 的单测）

## 影响关系图
graph LR
  A["utils/format.ts (改)"] --> B["cart/summary.ts ⚠"]
  A --> C["invoice/print.ts ✓"]

## 结论
破坏性变更：formatPrice 第二参数从有默认值变为必填。
cart/summary.ts 未传 currency，构建会直接失败，必须先修。
```

注意模型不是机械跑脚本——它在第 4 步对照 `destructive-patterns.md` 判定出「必填参数新增」属于破坏性，并在解读阶段点出 `summary.ts` 因缺参会编译失败这一**语义级**结论。脚本负责确定性检索，模型负责语义判断，二者分工。

## 进阶技巧

**让 Skill 自动触发而非手动喊。** 把 `description` 里的触发词覆盖到「PR review」「提交前检查」「这个改动安全吗」等高频说法，命中率会显著上升。可在团队约定里把「提 PR 前先做影响分析」写进 `CLAUDE.md`，配合自动触发形成肌肉记忆。

**用 `context: fork` 隔离重型分析。** 若分析过程会产生大量中间输出污染主上下文，可在 frontmatter 加 `context: fork`，让本次 Skill 执行在独立上下文里跑完再回传结论。

**缓存基线 diff。** 大仓库 `git diff origin/main...HEAD` 较慢，可在脚本里加 `git rev-parse HEAD` 做缓存键，避免重复全量 diff。

**接 hook 做自动门禁。** 用 `PreToolUse` hook 在执行 `git commit`/`git push` 前自动触发本 Skill，当风险评分超阈值时阻断提交——把分析从「事后报告」升级为「事前护栏」。

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合人群 | 中级及以上前端/后端工程师，有 PR review 经验 |
| 训练营类型 | 工程效能、AI 辅助研发、代码质量训练营 |
| 预计教学时长 | 讲解 45 分钟 + 动手实操 90 分钟 |
| 难度 | ★★★☆☆（脚本调试是主要耗时点）|
| 复用价值 | 高——可原样迁入任何 Git 仓库即用 |

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — 官方 Skill 开发规范，含 frontmatter 字段与渐进式披露机制
2. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — `name`/`description`/`allowed-tools`/`context` 等字段的逐项权威说明
3. [Blast Radius Analysis: Knowing What You'll Break Before You Ship](https://www.gunnard.org/writing/blast-radius-analysis/) — 变更影响分析（爆炸半径）的工程方法论与风险量化思路
4. [Test impact analysis: from a diff to the right tests to run](https://bugbrain.tech/blog/test-impact-analysis) — 测试影响分析（TIA）：从 diff 反推需回归测试的标准做法
5. [Claude Code Skills 渐进式披露实测](https://www.xiaoliblog.com/ai-native/claude-code-skills-progressive-disclosure/) — metadata / SKILL.md / resources 三层加载机制与省 token 实测

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