---
name: dwy-semver
description: "语义化版本号（SemVer 2.0.0）决策助手。涉及以下任何场景必须使用此 skill（即使用户没说 'semver'）：用户问'这个改动该升大版本还是小版本'、'该 bump major/minor/patch 吗'、'这个改动算不算 breaking change'、'版本号怎么定'、'1.2.3 下一个版本是多少'、'加了个新功能版本怎么走'、'0.x 阶段版本怎么算'、'预发布/alpha/beta/rc 版本号怎么写'；用户描述了一段代码变更想知道对应什么版本级别；用户在发版前纠结版本号。本 skill 只负责'版本号该升哪一位 + 算出具体号'，不负责执行发版流程（那是 dwy-publish）。"
---

# 语义化版本决策（SemVer 2.0.0）

判断一次变更该升 **MAJOR / MINOR / PATCH** 的哪一位，并算出具体版本号。规则源自 [semver.org](https://semver.org/) 2.0.0 官方规范。

本 skill 解决的核心难点不是"加 1"，而是**判断一个变更到底算 breaking、feature 还是 fix**——这一步判错，版本号语义就废了。

## 版本号格式

`MAJOR.MINOR.PATCH`，如 `2.1.3`。三位都是非负整数。可选附加 `-预发布` 和 `+构建元数据`，如 `1.0.0-rc.1+20260521`。

## 核心决策表

前提：SemVer 围绕**公开 API**（对外导出的函数/组件/类型/CLI 参数/HTTP 接口/配置项）定义。判断"兼容性"永远站在**依赖方**视角——升级后调用方代码不改还能跑 = 兼容。

| 级别 | 触发条件 | 含义 |
|------|---------|------|
| **MAJOR** | 向后**不兼容**的公开 API 变更 | 调用方升级后**可能要改代码** |
| **MINOR** | 向后兼容地**新增**功能，或标记 deprecated | 调用方不改代码，多了能力 |
| **PATCH** | 向后兼容的 **bug 修复** | 调用方无感，行为回到本应正确的样子 |

**归零规则**（升高位时低位清零，最易错）：
- 升 MAJOR：minor、patch 都归零 —— `1.4.2 → 2.0.0`
- 升 MINOR：patch 归零 —— `1.4.2 → 1.5.0`
- 升 PATCH：直接 +1 —— `1.4.2 → 1.4.3`

不要手算，用 `scripts/bump.py`（见下方「算出具体版本号」）。

## breaking change 判断清单（最关键）

### 一定是 MAJOR（破坏兼容）
- 删除 / 重命名 公开的函数、组件、类型、字段、CLI 参数、配置项
- 改函数签名：删参数、调整参数顺序、**新增必填参数**
- 改返回值结构 / 类型（删字段、改字段类型、改字段含义）
- 改默认行为（同样的输入产出不同结果，且旧行为是被依赖的）
- **加严校验**：原来能通过的输入现在报错（如新增必填字段、收紧取值范围）
- 删除 / 修改 已有枚举值
- 移除对某运行环境 / 依赖版本的支持（如最低 Node 版本抬高）

### 一定是 MINOR（兼容新增）
- 新增函数 / 组件 / 类型 / 可选参数 / 可选返回字段
- 新增**带默认值**的配置项
- 标记某 API 为 deprecated（仅警告，未删除）
- 放宽校验：原来报错的输入现在也能通过

### 一定是 PATCH（兼容修复）
- 修内部实现 bug，公开 API 签名与契约不变
- 不改 API 的性能优化、内部重构
- 文档 / 注释 / 类型声明修正（不改运行时行为）
- 依赖的 patch 级升级（不波及自身 API）

### 边界 case（想清楚再定）
- **bug 修复改变了已有行为**：若有调用方依赖那个"错误"行为，修复对它们就是 breaking → 倾向 **MAJOR**（或至少在 CHANGELOG 显著标注）。问自己："有人靠这个 bug 干活吗？"
- **新增枚举值**：若调用方代码对枚举做了穷举（switch 全分支 / 模式匹配），新值可能让其逻辑漏处理 → 视契约严格度，保守按 MINOR 但 CHANGELOG 提示
- **类型收窄**（TS 把 `string` 改成 `'a' | 'b'`）：原来传任意 string 的调用方会编译失败 → MAJOR
- **修改错误信息文案 / 日志**：通常 PATCH；除非有人解析它（不该依赖，但现实存在）

判不准时，问：**"一个只读了 CHANGELOG 标题的用户，升级后会不会被意外搞坏？"** 会 → MAJOR。

## 0.y.z 初始开发阶段（特殊规则）

主版本号为 0（如 `0.9.1`）表示**公开 API 尚不稳定**，规范允许此阶段任何变更随时发生。`1.0.0` 才正式定义稳定 API。

社区惯例（也是 dwy-shared 内 ekit/eapi/cli 当前所处阶段）：
- **breaking 变更** → 升 minor：`0.9.1 → 0.10.0`
- **feature / fix** → 升 patch：`0.9.1 → 0.9.2`
- 准备好对外稳定承诺时，发 `1.0.0`

即 0.x 阶段把 minor 当"大版本"、patch 当"小版本"用。

## 决策流程

1. **划清公开 API 边界**：这次改的东西对外可见吗？纯内部改动通常只到 PATCH
2. **判兼容性**：对照上方清单，是 breaking / 新增 / 修复 哪一类
3. **看是否 0.x**：当前主版本是 0 → 套用 0.y.z 惯例
4. **是否预发布**：要先发 alpha/beta/rc 试水 → 见下
5. **算具体号**：用 `scripts/bump.py`，别手算归零

## 预发布与构建元数据（简述）

- **预发布**：正式版前的试验版，如 `1.0.0-alpha`、`1.0.0-beta.2`、`1.0.0-rc.1`。优先级**低于**同号正式版：`1.0.0-rc.1 < 1.0.0`
- **构建元数据**：`+` 后接构建信息，如 `1.0.0+20260521`，**不参与**版本比较，仅作标记

预发布优先级排序、版本比较 11 条规则等细节见 [references/semver-spec.md](references/semver-spec.md)。

## 算出具体版本号

`scripts/bump.py` 按 SemVer 归零规则与 node-semver 预发布语义计算，避免手算出错：

```bash
python3 scripts/bump.py <当前版本> <级别> [--preid alpha|beta|rc]
```

级别：`major | minor | patch | premajor | preminor | prepatch | prerelease`

```
python3 scripts/bump.py 1.2.3 minor                  # -> 1.3.0
python3 scripts/bump.py 0.9.1 minor                  # -> 0.10.0  (0.x breaking)
python3 scripts/bump.py 1.0.0 preminor --preid rc    # -> 1.1.0-rc.0
python3 scripts/bump.py 1.1.0-rc.0 prerelease        # -> 1.1.0-rc.1
python3 scripts/bump.py 2.0.0-rc.1 major             # -> 2.0.0  (预发布转正式)
```

## 输出格式

给用户决策时，固定回三件事：

1. **级别**：MAJOR / MINOR / PATCH（+ 是否预发布）
2. **新版本号**：具体数字（用 bump.py 算）
3. **理由**：依据清单里的哪一条，一句话说清为什么这次变更属于该级别

例：
> 变更：给 `createRequest()` 新增了一个带默认值的可选参数 `timeout`。
> → **MINOR**，`2.2.1 → 2.3.0`。理由：新增可选参数，旧调用方不传也照常工作，向后兼容的功能新增。

## 与 dwy-publish 的边界

- **dwy-semver**（本 skill）：只回答"该升哪一位、新号是多少"
- **dwy-publish**：拿到版本号后执行发版流程（测试 → bump 版本文件 → changelog → build → publish → tag）

发版时若不确定版本号，先用本 skill 决策，再交给 dwy-publish 落地。

## 风格

- 中文回答
- 判级别必给**理由**，指向清单具体条目
- 版本号一律用 `bump.py` 算，不手算归零
- 引用规范细节附来源：`（来源：https://semver.org/）`
