# Claude Md Progressive Disclosurer

> Optimize CLAUDE.md files using progressive disclosure. Goal: Maximize information efficiency, readability, and maintainability. Use when: User wants to optimize CLAUDE.md, information is duplicated across files, or LLM repeatedly fails to follow rules.

- Skill: `zuoguyoupan2023/claude-md-progressive-disclosurer` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add zuoguyoupan2023/claude-md-progressive-disclosurer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zuoguyoupan2023/claude-md-progressive-disclosurer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: zuoguyoupan2023 (https://skillmd.com/u/zuoguyoupan2023)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/zuoguyoupan2023/claude-md-progressive-disclosurer

---


# CLAUDE.md 渐进式披露优化器

## 核心理念

> "找到最小的高信号 token 集合，最大化期望结果的可能性。" — Anthropic

**目标是最大化信息效率、可读性、可维护性。**

> 本 skill 自身遵守渐进式披露：新方法论以"精炼规则 + 触发条件"留在 SKILL.md，深度与引文沉到 references/。SKILL.md 保持 ≤500 行（Anthropic skill 规范）——skill 自己示范它要求别人做的事。

### 铁律：行数禁作 KPI，可作诊断症状

**禁作优化目标 / 成功指标**（不可削弱——案例 7/8/9 的防线就是这条）：

- 行数少不代表更好，行数多不代表更差
- 评判标准是：**单一信息源**（同一信息不在多处维护）、**认知相关性**（当前任务不需要的信息不干扰注意力）、**维护一致性**（改一处不需要同步另一处）——不是行数
- 禁止在优化方案 / 总结中出现"从 X 行精简到 Y 行"、"减少 Z%"作为成果
- 禁止把"减少行数"作为移动 / 删除某内容的理由
- 一个结构清晰、信息不重复的长文件，胜过砍掉关键信息的短文件

**可作诊断症状**（官方依据：Claude Code 文档"文件太长 → 规则被淹没 → Claude 不遵守"）：

- 允许把"行数异常大 + Claude 反复不遵守某规则"当成**触发调查的信号**，不是结论
- 调查动作仍是信号分诊（Step 2.1）+ 分层，**不是"砍到 N 行"**
- 一句话区分：行数可以让你**开始怀疑**，不可以成为你**优化的目标**或**汇报的成果**

### 两层架构

```
Level 1 (CLAUDE.md) - 每次对话都加载
├── 信息记录原则               ← 防止未来膨胀的自我约束
├── Reference 索引（开头）     ← 入口1：遇到问题查这里
├── 核心命令表
├── 铁律/禁令（含代码示例）
├── 常见错误诊断（症状→原因→修复）
├── 代码模式（可直接复制）
├── 目录映射（功能→文件）
├── 修改代码前必读             ← 入口2：改代码前查这里
└── Reference 触发索引（末尾） ← 入口3：长对话后复述

Level 2 (references/) - 按需即时加载
├── 详细 SOP 流程
├── 边缘情况处理
├── 完整配置示例
└── 历史决策记录
```

### 多入口原则（重要！）

同一 Level 2 资源可以有**多个入口**，服务于不同查找路径：

| 入口 | 位置 | 触发场景 | 用户心态 |
|------|------|----------|----------|
| Reference 索引 | 开头 | 遇到错误/问题 | "出 bug 了，查哪个文档？" |
| 修改代码前必读 | 中间 | 准备改代码 | "我要改 X，要注意什么？" |
| Reference 触发索引 | 末尾 | 长对话定位 | "刚才说的那个文档是哪个？" |

**这不是重复，是多入口。** 就像书有目录（按章节）、索引（按关键词）、快速参考卡（按任务）。

**边界（与 SSOT 的张力，必须守住）**：多入口成立**仅当**——每个入口 keyed 方式不同（错误索引 / 任务索引 / 末尾复述），且都只**指向**同一 Level 2 资源、**不复制它的正文**。如果你把同一段规则正文抄到 3 个地方，那是违反 SSOT 的重复（会各自漂移），不是多入口。一句话判据：入口存的是"路标 + 触发条件"，不是"内容副本"。

---

## 优化工作流

### Step 1: 备份

```bash
cp CLAUDE.md CLAUDE.md.bak.$(date +%Y%m%d_%H%M%S)
```

### Step 2: 内容分类

分两阶段。**先分诊，再分层**——跳过分诊会把噪音忠实搬进 Level 2，把 reference 变垃圾场。

#### 2.1 信号分诊（必要性闸门，先决）

对每个章节先问 Anthropic 官方 litmus：**"删掉这一条，Claude 会不会犯错？"**

- **会犯错** → 是信号，进入 2.2 分层
- **不会犯错**，且属以下任一 → 是**反信号**，列入"候选删除"清单：
  - 能从代码 / 项目结构 / 文件名推断的（如"本项目用 TypeScript"）
  - 语言 / 框架的标准约定（如"遵循 PEP 8"）
  - 自明常识（如"写干净的代码""提交前测试"）
  - 已有独立 canonical source 覆盖的（注明 source 在哪）
  - 已过时的一次性修复（不会再复发）
  - **确定性必须每次发生**的（如"提交前必跑 lint"）→ 标记"建议转 hook"，不替用户实现（散文保证不了确定性）

**安全栏（与移动同等严格，不可削弱）**：候选删除 ≠ 立即删除。必须事前逐项列出 + 注明属上面哪类 + 征求用户确认。说不出理由 = 不是反信号，回 2.2 当信号处理。

> 与案例 8/9 的边界：8/9 是把**真信号**（debug 提示、代码模式）在移动时压缩掉 = 永远错；这一步是移除**已确认反信号**（可推断 / 自明）= 正确。区别在"删的是不是信号"，不在"删不删"。详见 `references/progressive_disclosure_principles.md` 案例 10。

#### 2.2 分层分类

对**通过分诊的信号**分类：

| 问题 | 是 | 否 |
|------|----|-----|
| 高频使用？ | Level 1 | ↓ |
| 违反后果严重？ | Level 1 | ↓ |
| 有代码模式需要直接复制？ | Level 1 保留模式 | ↓ |
| 有明确触发条件？ | Level 2 + 触发条件 | ↓ |
| 历史/参考资料？ | Level 2 | 考虑删除 |

### Step 3: 创建 Reference 文件

命名：`docs/references/{主题}-sop.md`

**铁律：原样移动，禁止压缩**

移动内容到 Level 2 时，必须**完整保留原始内容**。不要在移动的同时"顺便精简"。

```
✅ 正确：把 100 行原封不动搬到 Level 2（100 行 → Level 2 100 行）
❌ 错误：把 100 行"精简"到 60 行搬到 Level 2（100 行 → Level 2 60 行，40 行消失）
```

**为什么**：压缩 = 变相删除。你认为"不重要"而删掉的内容，可能是某个未来 debug session 的关键线索。优化的目标是**改变信息的位置**（Level 1 → Level 2），不是**改变信息的存在**。

**怎么做**：
1. 从原始 CLAUDE.md 中精确复制要移动的段落
2. 原样粘贴到 Level 2 文件中
3. 可以在 Level 2 中添加结构（标题、分隔线），但**不要删减、改写、合并**原始内容
4. 如果确实有冗余（同一段话在原文中出现了多次），在 Level 2 中保留一份完整的，注释说明去重

### Step 4: 更新 Level 1

1. **在开头添加「信息记录原则」**（项目概述之后，Reference 索引之前）
2. **添加 Reference 索引**（紧随信息记录原则之后）
3. 用触发条件格式替换详细内容
4. 保留代码模式和错误诊断
5. **添加「修改代码前必读」表格**（按"要改什么"索引）
6. **在末尾再放一份触发索引表**

### Step 5: 验证（三项全部通过才算完成）

#### 5a. 引用文件存在性

```bash
# 检查引用文件存在
grep -oh '`docs/references/[^`]*\.md`' CLAUDE.md | sed 's/`//g' | while read f; do
  test -f "$f" && echo "✓ $f" || echo "✗ MISSING: $f"
done
```

#### 5b. 内容完整性（最关键）

对每个从原始 CLAUDE.md 移走的章节，逐一检查：

1. **恢复原始文件**：`git show HEAD:CLAUDE.md > /tmp/claude-md-original.md`
2. **逐节对比**：对原始文件的每个 `##` 章节，确认其内容在以下位置之一完整存在：
   - 新 CLAUDE.md 中（保留在 Level 1）
   - 某个 Level 2 reference 文件中（完整移动）

   **📖 快速暴露整章遗漏的辅助脚本见 `references/progressive_disclosure_principles.md` 附录 C**：触发场景——做下面逐节对比前的第一道筛查（脚本不替代人工逐节对比，只查章节标题是否存在）。

3. **标记所有差异**：
   - 如果某段内容在新文件中被缩短 → **必须补回被删减的部分**
   - 如果某段内容在两个位置都不存在 → **必须补回**
   - 唯一允许删除的情况：**该信息已有独立的 canonical source**（如 `docs/README.md` 已是文档索引的 canonical source），且在 Level 1 中有明确的指向

**禁止将"故意删除"作为分类来掩盖信息丢失。** 每一项"故意删除"都必须说明 canonical source 在哪里。如果说不出来，就不是"故意删除"，而是"遗漏"。

#### 5c. 行数不进验证标准

验证**不以行数为通过条件**，不计算"原始 X 行 vs 新 Y 行 = 减少 Z%"——这种对账会把你拉回 KPI 思维。

验证标准只有三条：
- 每段信息都有归属（Level 1 或 Level 2 或 canonical source）
- 没有信号丢失（反信号经确认删除不算丢失）
- Level 2 引用都有触发条件

（注：诊断阶段可以看行数当怀疑信号，见开头「铁律」；但**验证阶段**行数不是任何标准——这两个阶段对行数的态度不同，别混。）

---

## Level 1 内容分类

### 🔴 绝对不能移走

| 内容类型 | 原因 |
|---------|------|
| **核心命令** | 高频使用 |
| **铁律/禁令** | 违反后果严重，必须始终可见 |
| **代码模式** | LLM 需要直接复制，避免重新推导 |
| **错误诊断** | 完整的症状→原因→修复流程 |
| **目录映射** | 帮助 LLM 快速定位文件 |
| **触发索引表** | 帮助 LLM 在长对话中定位 Level 2 |

### 🟡 保留摘要 + 触发条件

| 内容类型 | Level 1 | Level 2 |
|---------|---------|---------|
| SOP 流程 | 触发条件 + 关键陷阱 | 完整步骤 |
| 配置示例 | 最常用的 1-2 个 | 完整配置 |
| API 文档 | 常用方法签名 | 完整参数说明 |

### 🟢 可以完全移走

| 内容类型 | 原因 |
|---------|------|
| 历史决策记录 | 低频访问 |
| 性能数据 | 参考性质 |
| 技术债务清单 | 按需查看 |
| 边缘情况 | 有明确触发条件时再加载 |

---

## 引用格式（四种）

四种引用格式各服务不同场景；规范的"触发条件"写法见下方 `原则 2`（已含可复制示例）。

| 格式 | 用途 | 触发场景 |
|---|---|---|
| 详细格式 | 正文中的重要引用 | 单条 reference 需展开说明何时读 |
| 问题触发表格 | 开头/末尾 Reference 索引 | 按"错误/问题"查 |
| 任务触发表格 | 「修改代码前必读」 | 按"要改什么"查 |
| 内联格式 | 简短引用 | 正文一句话带过 |

**📖 四种格式的完整可复制模板见 `references/progressive_disclosure_principles.md` 附录 B**：触发场景——产出 Reference 索引 / 任务表 / 内联 / 详细引用时。

**多样性原则**：不要所有引用都用同一格式。

### ⚠️ @import 不省上下文（技术正确性，最易踩）

`@path` import 在**启动时全量展开载入**——拆成 `@import` 只改善组织，**不减少任何上下文**（官方 *memory* 文档原文）。"我把内容拆进 `@import` 了所以优化了"是假优化。

全局 `~/.claude/CLAUDE.md` 真正能省上下文的杠杆只有三条：

1. 把非通用内容**移到项目级 CLAUDE.md**（全局文件会被无关项目加载）
2. 留**纯文字指针**（"需要时 Read `references/xxx.md`"，**不是 `@`**），让模型按需拉
3. 转 **skill**（描述常驻、正文按需）

本 skill 产出的引用一律用反引号路径，**禁止用 `@import` 做卸载**。详见 `references/progressive_disclosure_principles.md` 案例 11。

---

## 核心原则

### 原则 0：添加「信息记录原则」（防止未来膨胀）

**问题**：优化完成后，用户会继续要求 Claude "记录这个信息到 CLAUDE.md"，如果没有规则指导，CLAUDE.md 会再次膨胀。

**解决**：在目标 CLAUDE.md 开头（项目概述之后）注入一段「信息记录原则」——规定 Level 1 只记核心命令 / 铁律 / 代码模式 / 触发索引，Level 2 记详细 SOP / 边缘情况 / 历史决策，并定义"用户要求记录信息时"的高频→L1、低频→L2 判断流程（引用 L2 必带触发条件）。

**📖 完整可注入模板见 `references/progressive_disclosure_principles.md` 附录 A**：触发场景——执行 Step 4 更新 Level 1 时；附录含可整块复制进目标 CLAUDE.md 的 markdown。

**原因**：这条规则让 Claude 自己知道什么该记在哪里，实现"自我约束"，避免后续对话中 CLAUDE.md 再次膨胀。

### 原则 1：触发索引表放开头和末尾

**原因**：LLM 注意力呈 U 型分布——开头和末尾强，中间弱。

| 位置 | 作用 |
|------|------|
| **开头** | 对话开始时建立全局认知："有哪些 Level 2 可用" |
| **末尾** | 对话变长后复述提醒："现在应该读哪个 Level 2" |

**📖 首/尾索引表完整写法示例见 `references/progressive_disclosure_principles.md` 案例 4**：触发场景——决定触发索引表放哪、按什么格式写时。

### 原则 2：引用必须有触发条件

**错误**：`详见 native-modules-sop.md`

**正确**：
```markdown
**📖 何时读 `native-modules-sop.md`**：
- 遇到 `ERR_DLOPEN_FAILED` 错误
- 需要添加新的原生模块

> 包含：ABI 机制、懒加载模式、手动修复命令
```

**原因**：没有触发条件，LLM 不知道什么时候该去读。

### 原则 3：代码模式必须保留在 Level 1

**错误**：把代码示例移到 Level 2，Level 1 只写"使用懒加载模式"。

**正确**：Level 1 保留完整的可复制代码：
```javascript
// ✅ 正确：懒加载，只在需要时加载
let _Database = null;
function getDatabase() {
  if (!_Database) {
    _Database = require("better-sqlite3");
  }
  return _Database;
}
```

**原因**：LLM 需要直接复制代码，移走后每次都要重新推导或读取 Level 2。

### 原则 4：用三态优先级，不要"全标铁律"

**问题**：把每条规则都标"铁律 / HIGHEST / 全局" = 没有优先级。模型无法 triage，注意力被摊薄，最关键的不可逆规则反而被淹没。指令遵循存在约 150–200 条的上限，远超即整体衰减。

**解决**（GitHub 2500 仓库实证最有效的结构）：输出 Level 1 规则时用三态，而不是一律"铁律"：

| 标记 | 含义 | 例 |
|------|------|----|
| ✅ | 总是这样做 | ✅ 提交前跑测试套件 |
| ⚠️ | 先停下问 / 谨慎 | ⚠️ 改 schema 前先确认迁移脚本 |
| 🚫 | 绝不 | 🚫 绝不提交 secret |

**位置即优先级**（Lost-in-the-Middle，TACL 2024）：LLM 注意力 U 型分布，最高危的不可逆规则放文件**首或尾**，不要埋中间。真正"违反即不可逆伤害"的应是少数（5–7 条），其余降为普通规则——稀缺才有信号。

### 原则 5：每条保留规则带一行 Why

**问题**：不带原因的规则，一旦场景变化就被忽略（Builder.io 实证）。带 Why 的规则能跨场景泛化。

**解决**：Level 1 保留的每条铁律 / 禁令，跟一行 `Why:`，说明违反会发生什么具体坏事。

**错误**：`🚫 禁止 fallback 默认值`

**正确**：`🚫 禁止 fallback 默认值。Why：一个 || 'sk-xxx' 兜底在 .env 缺失时静默回退明文 key，曾在 48h 内被公开仓库扫描器用掉额度。`

> ⚠️ 重述规则时的硬边界：若原句嵌在 case study 混合段落里，原则 4/5 不得直接改写原句——见反模式 6（先整段 verbatim 移 L2，案例 14）。

---

## 反模式警告

### ⚠️ 反模式 1：以行数为目标的过度精简

**案例**：为了"减少行数"，移走了代码模式、诊断流程、目录映射

**结果**：
- 丢失代码模式，LLM 每次重新推导
- 丢失诊断流程，遇错不知查哪
- 丢失目录映射，找文件效率低

**正确**：保留所有高频使用的内容。优化的判断标准是信息是否重复维护、是否与当前任务无关，而不是"文件太长"。

### ⚠️ 反模式 2：无触发条件的引用

**案例**：`详见 xxx.md`

**问题**：LLM 不知道何时加载，要么忽略，要么每次都读。

**正确**：触发条件 + 内容摘要。

### ⚠️ 反模式 3：移走代码模式

**案例**：把常用代码示例移到 Level 2

**问题**：LLM 每次写代码都要先读 Level 2，增加延迟和 token 消耗。

**正确**：高频使用的代码模式保留在 Level 1。

### ⚠️ 反模式 4：删除而非移动

**案例**：删除"不重要"的章节

**问题**：信息丢失，未来需要时无处可查。

**正确**：移到 Level 2，保留触发条件。

### ⚠️ 反模式 5：用行数当 KPI

**案例**：优化方案写"从 2000 行精简到 500 行，减少 75%"

**问题**：把行数当成功指标，会驱动错误决策——为了凑数字而砍掉有用的信息。

**正确**：用信息质量评估优化效果——信息是否有重复？维护负担是否降低？LLM 是否能更快找到需要的信息？

### ⚠️ 反模式 6：移动时压缩（变相删除）

**规则**：移动是移动，精简是精简。这是两个独立操作，**不要同时执行**。

- 移动内容到 Level 2 时，必须**原样复制，不改一字**
- 如果发现冗余需要精简：作为**单独的后续步骤**，逐项列出要删除的内容及理由，征求用户确认
- "既然都在改了，顺便精简一下"是最隐蔽的删除——它披着"优化"的外衣，做着"删除"的事
- **混合段落（规则句 + case study/叙事）的硬边界**：原则 4/5、反模式 8 想把规则重述成 ✅/🚫+Why，但混合段落与本反模式冲突——**整段必须先 verbatim 移 L2**（规则句原句一字不改）；L1 的重述是**派生副本，与 L2 原句共存、不取代**。判据：优化后 grep 原规则句逐字节文本仍命中（在 L2 verbatim 块）。原则 4/5 管 L1 *如何呈现*，不授权销毁信号原句

> 完整案例分析见 `references/progressive_disclosure_principles.md` 案例 8、案例 14

### ⚠️ 反模式 7：用"故意删除"掩盖信息丢失

**规则**：任何"删除"都必须是**事前决策**（征求用户确认），不是**事后分类**（发现少了再编理由）。

- 对每项计划删除的内容，必须说明其 canonical source 在哪里
- 如果无法指出 canonical source → 不是"故意删除"，是"信息丢失"，必须补回
- 对丢失内容分类"严重性"（高/低风险）是在为自己的错误找台阶。正确的态度是：任何丢失都是 bug，fix it

> 完整案例分析见 `references/progressive_disclosure_principles.md` 案例 9

### ⚠️ 反模式 8：纯否定规则（不给替代）

**案例**：`🚫 不要用 X` —— 没说改用什么。

**问题**：纯否定会让 agent 瘫痪——它知道不能走这条路，但不知道该走哪条，于是要么卡住要么乱试（Shankar + GitHub 2500 仓库均实证）。

**正确**：每条 `🚫` 必配一个 `✅ 改用 Y`。

```
🚫 不要用全局 mutable 单例存请求状态
✅ 改用显式参数传递或 request-scoped context
```

优化时遇到孤立的禁令，补上正向替代再保留；补不出替代的禁令，说明规则本身没想清楚。

> ⚠️ 但若禁令原句嵌在 case study 混合段落里，先按反模式 6 整段 verbatim 移 L2，再在 L1 派生重述——不可改写原句（案例 14）。

---

## 信息量检验

### ✅ 正确的信息量

| 检验项 | 通过标准 |
|--------|---------|
| 日常命令 | 不需要读 Level 2 |
| 常见错误 | 有完整诊断流程 |
| 代码编写 | 有可复制的模式 |
| 特定问题 | 知道读哪个 Level 2 |
| 触发索引 | 在文档末尾，表格形式 |

### ❌ 不足的信号

- LLM 反复问同样的问题
- LLM 每次重新推导代码模式
- 用户需要反复提醒规则

### ❌ 过多的信号

- 大段低频详细流程在 Level 1
- **完全相同的内容**在多处（注意：多入口指向同一资源 ≠ 重复）
- 边缘情况和常见情况混在一起

---

## 项目级 vs 用户级

| 维度 | 用户级 | 项目级 |
|------|--------|--------|
| 位置 | `~/.claude/CLAUDE.md` | `项目/CLAUDE.md` |
| References | `~/.claude/references/` | `docs/references/` |
| 信息范围 | 个人偏好、全局规则 | 项目架构、团队规范 |

### 硬检查：scope 错放（官方层级文档裁定）

用户级 `~/.claude/CLAUDE.md` 会被**所有项目**加载，**只能放普遍适用**的东西。优化时对每节做 scope 检查：

| 内容特征 | 归属 | 不这样做的后果 |
|---|---|---|
| 项目名 / 部署目标 / 逐项目路径 / 项目凭据 | **项目级**，绝不全局 | 无关项目被污染；没人按项目维护 → 路径/状态腐烂（典型 staleness） |
| 个人偏好、跨项目行为规则 | 用户级 | — |
| 团队规范、项目架构 | 项目级（入 VCS） | — |

工作流加一条：**Step 2.1 分诊时，项目特定内容在用户级文件 = 自动判"搬到项目级"**，不是搬 Level 2、更不是原地修路径。详见 `references/progressive_disclosure_principles.md` 案例 13。

---

## 金丝雀检测法（可选，长期维护）

> 来源：HN 社区单源（"Mr Tinkleberry"），方法论成立、成本极低，作诊断不作保证。

优化后想知道 CLAUDE.md 哪天又膨胀到"规则开始被忽略"——在文件里植入一条**无害的命名指令**（如"提到临时变量时命名为 `tinkle_tmp`"）。日常对话中观察：Claude 还遵守 = 文件仍在遵守度阈值内；Claude 开始无视这条 = 文件已越过阈值，该重新分诊。比凭感觉判断"是不是太长了"廉价且客观。

---

## 快速检查清单

优化完成后，**必须逐项检查**（不可跳过）：

### 信息完整性（最重要）
- [ ] **原始文件的每个章节都有归属**——在新 Level 1、Level 2、或有明确 canonical source
- [ ] **Level 2 文件内容与原始内容完全一致**——没有在移动过程中被"精简"
- [ ] **没有信号被静默删除**——每项删除是反信号且有用户确认/canonical source（反信号删除正当，见 Step 2.1）
- [ ] **没有把行数当成果/KPI/移动理由/汇报指标**（诊断性观察不在此限，见「铁律」）

### 结构质量
- [ ] 「信息记录原则」在文档开头（防止未来膨胀）
- [ ] Reference 索引在文档开头（入口1：遇到问题查这里）
- [ ] 核心命令表完整
- [ ] 铁律/禁令有代码示例
- [ ] 常见错误有完整诊断流程（症状→原因→修复）
- [ ] 代码模式可直接复制
- [ ] 目录映射（功能→文件）
- [ ] 「修改代码前必读」表格（入口2：按"要改什么"索引）
- [ ] Reference 触发索引在文档末尾（入口3：长对话后复述）
- [ ] 每个 Level 2 引用都有触发条件
- [ ] 引用的文件都存在
- [ ] 信号分诊已执行：反信号有候选删除清单 + 用户确认（Step 2.1）
- [ ] 每条铁律/禁令带一行 `Why:`（原则 5）
- [ ] 优先级用 ✅/⚠️/🚫 三态，不是一律"铁律"（原则 4）
- [ ] 每条 🚫 都配了 ✅ 替代（反模式 8）
- [ ] 项目特定内容没有留在用户级文件（scope 硬检查）
- [ ] 引用未使用 `@import` 做卸载（@import 不省上下文）

