---
name: subtraction-audit
description: |
  代码减法审计与反腐化工程。用于给代码库做减法审计（找死代码/重复实现/死依赖/投机性泛化/死门禁），产出证据驱动的删除候选清单并按风险分级执行；也用于给 AI 重度开发的仓库搭建防膨胀门禁（CI、钩子、死代码基线）。核心信念：代码最终会腐化，人写机器写都一样；防腐化唯一可靠的方式是把约束变成机器门禁、把为什么变成文档、把删除变成一等工作流。方法学源自 DeepSeek Harness（dsh-find-simplifications）。
triggers:
  - "减法审计"
  - "清理死代码"
  - "找重复实现"
  - "代码腐化"
  - "哪些可以删"
  - "anti-bloat"
  - "dead code audit"
  - "repo cleanup"
  - "simplify this repo"
  - "防膨胀"
  - "unused code"
  - "refactor cleanup"
---

# 减法审计 · Subtraction Audit

一次 AI 对话的历史就是一部腐化史：AI 写代码太容易、太膨胀、太容易发散，人写机器写都一样，代码最终会腐化。这套方法把"防腐化"变成可重复的流程：**证据驱动的减法审计 + 风险分级执行 + 机器门禁**。

## 核心原则（先读，永远不要跳过）

1. **门禁 > 散文**：约定必须变成可执行检查（退出码非零即失败），否则 Agent 不会遵守。AI 对强制门禁的遵从远高于对散文式约定的遵从。
2. **测试不是真理**：测试钉住的是当前行为，不是正确行为——行为可能是历史妥协的产物。测试必须能为你报告的机制而失败，否则它在撒谎。
3. **删除是一等公民**：简化是常规工作流，不是大扫除。每月的删除行数为零 = 腐化警报。
4. **决策必须写下来**：每条非平凡删除配一条决策记录（Problem / 证据 / 代价 / 回退条件）。
5. **证据先行**：没有消费证据的候选一律不报。"看起来没用"不是证据，rg 才是。

## 何时使用

- 用户想"清理一下仓库"、"找找死代码"、"这些能不能删"
- 用户担心 AI 写的代码膨胀/发散/不可维护
- 用户想给仓库（尤其是 AI 主力开发的仓库）搭建质量门禁

## 工作流

### Phase 0 · 先懂架构（最重要，最容易翻车）

**在声称任何东西"死"之前，先回答这些**——跳过这步等于把报告建立在地基上：

- 入口在哪？路由/端点如何挂载？（静态 import / 动态 require / 框架自动发现如 Next.js pages / 字符串路径）
- 有没有**同名活文件**？（例：`routes/agent/feedback.ts` 疑似死，但 `routes/feedback.ts` 是活的并挂在 `/api/feedback`——两个不同文件）
- 构建/部署怎么描述它？（docker-compose、deploy manifest、runbook、README 声称 vs 实际）
- 有没有看起来像"占位/骨架"但其实是**生产核心**的东西？（小包可能是队列消费者、worker、调度器）
- 环境变量/配置旋钮有没有被外部引用（.env.example、部署平台 env、容器编排）
- 数据库表 / 队列 / 事件名有没有被字符串引用

> 反例教训（真实事故）：一个 101 行的"worker 骨架"其实是生产必需的 DB 队列消费者（轮询 generation_tasks 表）。部署清单里没有它 ≠ 它可有可无——那可能是**配置漂移**，甚至意味着生产队列已停摆。别把"部署配置里没有"当成"不需要"。

### Phase 1 · 基线测量

1. **死代码基线**：`npx knip --reporter json`（未使用导出 / 类型 / 文件 / 依赖 / 重复实现 / 二进制）
2. **仓库卫生**：误提交文件（提交消息与内容不符的）、`.gitignore` 覆盖、`.env` 是否被跟踪（安全项，必须查）、被提交的大产物/工具输出、空目录、死门禁（脚本存在但没接 CI/钩子）
3. **CI/钩子盘点**：`.github/`、husky pre-commit 实际跑了什么；lint/test/typecheck 有没有任何自动流程执行。**零 CI 是腐化积累的根因**，要单独列成系统性问题
4. 记录基线数字——之后每次清理都应下降，只许降不许升

### Phase 2 · 分域并行调查

按域派并行子代理（api / web / worker / shared / 根与脚本），每个域用同一套方法论提示词：

- 调查类别：① 死代码（零生产引用，仅测试引用也算，标注证据）② 重复表示（同一事实两份镜像：两个对象镜像同一批字段、手写类型与生成类型并存）③ 手搓 vs 成熟依赖（协议解析/重试/glob/diff/节流——npm 成熟包或 Node 内置可替代）④ 投机性泛化（没 owner 的配置旋钮、占位服务、注释掉的大块、只被测试保护来维护未用 API）⑤ 死依赖（package.json 有、源码零 import）
- 铁律：每个候选先全仓库搜消费方，**区分生产 / 测试 / 文档**三类消费；无证据不报；末尾必须给"有意排除"清单（已核实为活/有意保留的）

### Phase 3 · 高危复核（删除前必做）

对每个候选追加复核，尤其是 api/web 路由与公共导出：

- 全仓库 rg，**含动态引用与字符串路径**（`import.meta.glob`、`require(`、`.route("字符串")`）
- **同名活文件**检查（见 Phase 0 教训）
- **框架自动发现**检查（Next.js pages/API routes 会被框架自动注册——knip 在这里会误报；确认应用用的是框架路由还是 React Router 等显式路由）
- 部署/文档引用检查（deploy manifest、runbook、env）
- 测试是否钉死了它不存在（如 `doesNotMatch(/<Component/)` 断言）——那是删除的助攻证据

### Phase 4 · 风险分级

| 级别 | 含义 | 处置 |
|---|---|---|
| 🟢 绿 | 证据闭环（生产+测试+文档零消费，无同名/动态/框架引用） | 可放心删 |
| 🟡 黄 | 删前确认（是否存在运行时 parse？本地实现是否自持一份？合并后是否要冒烟？） | 先查再删 |
| 🔴 红 | 需产品决策（两条实现路径是否都活着？生产依赖哪个？部署是否受影响？） | 单独立项，问人 |

### Phase 5 · 分批次执行

- 每批 = 一类候选（卫生 → 死文件 → 死导出 → 重复合并 → 死依赖），不要混批
- 每批配一条决策记录（`DECISIONS.md`：Problem / 证据 / 代价 / 回退条件）
- 每批后跑验证：`tsc --noEmit`（**全包**，不是只查一半）+ lint + knip 基线不升
- 涉及生产组件的改动（队列消费者、worker、部署相关、env 旋钮）改后必须跑冒烟测试

### Phase 6 · 系统修复（比删代码重要）

- **零 CI 是根因**：建最小 CI——lint + test + typecheck 全包 + knip 基线，失败即红
- 钩子补齐：pre-commit 跑 lint/typecheck（staged 范围）
- 把"删除行数"放进月度复盘指标：为零就是警报

## 交付物模板（报告结构）

1. 腐化存量基线（knip 数字）
2. 系统性问题（CI/门禁/部署漂移——单独列，比删代码重要）
3. P0 卫生（误提交/产物/ignore）→ P1 死文件 → P2 死导出 → P3 重复实现 → P4 死状态 → 死依赖
4. 安全专项（.env 是否入库、密钥扫描）
5. 有意排除清单（已核实为活的）
6. 分风险执行计划 + 必须由产品回答的问题清单

## 常见陷阱

- 把"部署清单里没有"当成"不需要"——可能是配置漂移或生产停摆（worker 教训）
- 被 knip 误报带偏——框架自动发现、动态 require、字符串引用都会造成误报，必须人工复核
- 只见树木——只删明显死符号，漏掉文件级重复生命周期/防御机制最重的地方（检查：两个文件实现同一份 withRetry、两套并行组件树）
- 一次全删不验证——必须分批 + 每批验证基线
- 不区分生产/测试/文档消费——"只有测试引用"也是死代码，但要标注证据
- 不问产品问题就删 🔴 级候选

## 配套模板

**子代理分域提示词骨架**（Phase 2 用）：

```
你是代码简化审计员。审计目标：<路径>（<规模>）。只读，绝不修改文件、不跑构建。
方法：只报有证据的候选。每个候选先在**整个仓库**用 rg 搜消费方，区分"生产代码消费" vs "只有测试/文档消费"。没有消费证据的一律不报。
重点找：①死代码 ②重复表示 ③手搓 vs 成熟依赖 ④投机性泛化 ⑤死依赖。
产出：Markdown 表格，按置信度排序，最多 15 条。每列：候选（文件:行+符号）| 证据（rg 搜了什么）| 建议动作 | 置信度（高/中/低）。末尾列"有意排除的"。中文输出。
```

**决策记录模板**（Phase 5 用）：

```markdown
# DECISION: <删除/合并/降级> <对象>

## Problem
<当前 API/文件/依赖是什么，消费证据（生产 N 处 / 仅测试 / 仅文档）>

## 证据
<rg 命令与结果；同名/动态/框架引用检查结果>

## 代价 / 我们放弃什么
<最强的反对理由；未来可能需要的场景>

## 回退条件
<什么情况下要恢复>

## 验证
<tsc / lint / knip 基线对比；冒烟范围>
```

## 参考来源

- 方法学源自 DeepSeek Harness：`dsh-find-simplifications` skill、AGENTS.md 约定、144 条简化类 Agent Notes、质量门禁记录、postmortem 0002/0003（测试不是真理）
- 实战案例：berryon 减法审计（4 域并行 + knip 基线 + 风险分级），教训已内嵌于本文
