---
name: changelog-writing
description: 写或修改 CHANGELOG.md（待发布条目、版本亮点段、GitHub Release 说明）时使用。含按改动性质分组落位、中英双语体例、发版前的两道门禁（三处版本号同步 + 亮点段抽取会 fail-fast）与写后自检命令。
---

# CHANGELOG 写作（dsh-config-manager）

这份 changelog 是**发布说明的唯一来源**：打 tag 时 CI 直接抽取当前版本段当 GitHub Release 描述。
所以它既要给人读，也要能被机器切段 —— 写错位置、写错版本号、漏写当前版本段，都会在发版那一刻变成红灯或一条错误的发布公告。

本 Skill 只规定**落位、体例与自检**；具体改哪些功能、有没有发布权限由当前任务决定。

## 何时使用 / 何时跳过

用：
- 用户可见的变化（新功能 / 行为变更 / 修复 / 移除 / 安全）准备写进 CHANGELOG 时。
- 发版前补「当前版本亮点段」时。

跳过：
- 只改注释、重构、内部测试且**用户可感知行为没变**（这类不写条目；没把握就问一句）。
- 纯文档改动（除非它改变了用户的操作方式）。

## 1. 落位：写进哪一段

- 文件 = 仓库根 `CHANGELOG.md`，格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
- **未发布的内容一律写进 `## [Unreleased]`**，按**改动性质**分组，顺序固定：

  | 分组 | 放什么 |
  |---|---|
  | `### 🆕 新增 · Added` | 新功能、新来源、新通道、新入口 |
  | `### 🔧 变更 · Changed` | 行为/口径改变、原本静默的变可见、性能与体验变化 |
  | `### 🐛 修复 · Fixed` | 修 bug、修误报、补护栏 |
  | `### 🗑️ 移除 · Removed` | 删功能（**有内容才写这个标题**） |
  | `### 🔐 安全 · Security` | 安全修复（**有内容才写这个标题**） |

- **没有内容的分组不写标题**；分组内的条目之间空一行；分组之间空一行。
- 已发布的历史版本段**保留当时写法，不回改**（那 40 多个版本段是发布记录，整批重排只会产生巨大无意义 diff）。用户明确要求时才动。
- 分组标题下的子条目如果本来有自己的小节标题，用 `####`（比分组低一级）。

## 2. 体例：一条条目怎么写

- **中英双语**，两种写法都算合规（同一条目内）：
  - 段落式：中文块（`> **中文标题**…`）在前，英文块（`> **English title**…`）紧随，中间空行 —— Unreleased 段常用；
  - 行内式：一条 bullet 里先中文、后英文加粗点 —— 已发布段常用。
- 标题用**加粗块**，不用 `###` 顶掉分组：`> **中文标题**：一句话说清症状/变化`。
- 每条尽量带：**症状 → 根因 → 改法 → 护栏（哪个测试/脚本钉住）→ issue 编号**（`（issue #75）`）。
- emoji 可选：分组标题必须带（见上表）；条目内可用（`🔌` `🧩` `🐛` `🔐` 等本仓已有用法）。

### 口径纪律（本仓最看重的一条）

- **不得夸大**：没做的写没做 ——「只报不修」「真机未逐家验证」「本轮收益为零」「未验证（unavailable 不算验证成功）」都要如实写。
- **不把计划写成已完成**：还没合并/还没验证的东西不进 changelog。
- **不承诺未发布的版本号**：发版前不写「将在 v0.1.x 修复」。
- **数字要带口径**：写清是哪个快照、哪台机器、什么时间（例：`（2026-10-06 真机快照：units 1195）`）。
- **证据要可复现**：点名测试文件 / 复核脚本 / 命令。

## 3. 发版时才做（不是现在）

两道门禁，漏一条 CI 直接 fail：

1. **三处版本号同步**：`package.json.version` ≡ `src/index.ts` 的 `PLUGIN_VERSION` ≡ `package-lock.json` 的根对象与 `packages[""].version`。
   守卫 = `tests/packaging-contract.test.ts` 的 `V-1`（源码级正则，任一处漏改即 `npm test` 红灯）。
2. **在文件顶部加当前版本段**：`## [X.Y.Z] - YYYY-MM-DD`，正文是本版本的中英双语亮点。
   CI 用 `.github/scripts/extract-release-notes.py` 从 `## [X.Y.Z]` 切到**下一条 `## `**（`###` 子分组原样进发布说明）；**抽不到或为空 → fail fast 拒绝发版**。

**不发布的时候**：不要 bump 版本号、也不要写 `## [X.Y.Z]` 段 —— 内容留在 `## [Unreleased]`（版本号 = 最近已发布的版本）。

## 4. 写后自检（必须做）

```bash
# ① 结构与分组（Unreleased 段应当只有「有内容」的分组标题）
grep -nE '^### |^#### ' CHANGELOG.md | head -20          # bash
Select-String -Path CHANGELOG.md -Pattern '^### |^#### ' # PowerShell

# ② 中英配平：中文块与英文块数量应当一致（段落式体例）
grep -c '^> \*\*' CHANGELOG.md

# ③ 发版前：本地跑一次抽取，确认当前版本段非空（这一步就是 CI 的门禁）
python3 .github/scripts/extract-release-notes.py "$(node -p 'require("./package.json").version')" CHANGELOG.md | head -20
```

自检不通过就别宣布完成：宁可让用户看见「还差一段」，也不要让 CI 在打 tag 时才发现。

## 5. 常见错误

| 错误 | 后果 | 正确做法 |
|---|---|---|
| 新条目直接写 `## [X.Y.Z]` 段 | 版本号没 bump 就对不上；发版时又得搬一次 | 未发布一律写 `## [Unreleased]` 的对应分组 |
| 修复写进「新增」、变更写进「修复」 | 发布说明误导读者 | 按第 1 节的分组表落位 |
| 空分组也留标题 | 发布说明里全是空壳 | 没内容就不写那个标题 |
| 只写中文或只写英文 | 本仓 changelog 是双语的 | 中英成对写 |
| 把「计划做」写成「已做」 | 失信；读者按它决策 | 只写已合并、已验证的变化 |
| 顺手回改历史版本段 | 巨大无意义 diff，且改动已发布的记录 | 只动 `## [Unreleased]`（用户明确要求除外） |
| 改了分组约定但只改代码 | 约定漂移 | 三处一起改：`CHANGELOG.md` 头部「条目落位」、本 Skill、必要时 `DEVELOPERS.md` §自动发布 |
