关于 gen-release-notes
根据指定标签以来的变更生成符合 Keep a Changelog 格式的发行说明。它指导变更分析、分类、版本建议、更新 changelog,并验证比较链接。
主要功能
- 01从前一标签收集变更
- 02按 Keep a Changelog 分类
- 03提供 Semantic Versioning 版本建议
- 04CHANGELOG.md 更新步骤
- 05页脚比较链接验证关卡
适用场景
- 01准备标签版本的发行说明
- 02按用户影响整理变更
- 03建议发行版本号
- 04检查 changelog 比较链接
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ existential-birds/ beagle --skill gen-release-notes安装后在 Claude Code 中输入/gen-release-notes确认已加载。
详细介绍
发布说明生成器
按照 Keep a Changelog 标准生成专业的发布说明。
输入:上一个标签(例如,v0.0.1)
$ARGUMENTS
生成发布说明前,请彻底分析变更。
门槛
不要编造标签、PR 编号或链接。每一行都必须通过检查,才能进行依赖于该检查的工作。
| 时机 | 通过条件(证据) | 未通过时 |
|---|---|---|
执行 git log / git diff 前 | git tag -l "$PREV_TAG" 恰好输出一行,且与 PREV_TAG 匹配 | 停止;报告标签缺失——不要编写更新日志条目 |
| 分类前 | git rev-parse "$PREV_TAG^{commit}" 以状态码 0 退出 | 停止;修正 PREV_TAG 或仓库检出状态 |
使用 gh pr list 时 | 命令以状态码 0 退出,且 JSON 有效 | 回退到提交主题和合并提交 URL;不要编造 PR 编号 |
| 第 5 步页脚编辑后 | 第 6 步页脚门槛以状态码 0 退出(针对暂存的 CHANGELOG.md 的两次 grep -q 检查均通过) | 重新执行第 5 步的页脚编辑,然后重新执行第 6 步,直到以状态码 0 退出 |
第 1 步:收集变更
运行以下命令,收集自提供的标签以来的变更信息:
# Store the previous tag
PREV_TAG="$ARGUMENTS"
# Gate: tag must exist (output must be non-empty and match PREV_TAG)
git tag -l "$PREV_TAG"
# If the line above prints nothing, STOP — do not continue below.
# Get the repo URL for PR links
git remote get-url origin
# List commits since last tag
git log ${PREV_TAG}..HEAD --pretty=format:"%h %s" --no-merges
# Get detailed diff stats
git diff ${PREV_TAG}..HEAD --stat
# List changed files by directory
git diff ${PREV_TAG}..HEAD --name-only | sort | uniq
另行收集 PR 信息:
# Get merged PRs since the tag (requires gh CLI)
gh pr list --state merged --search "merged:>=$(git log -1 --format=%ci $PREV_TAG | cut -d' ' -f1)" --json number,title,author,labels
第 2 步:分析并分类
将每项变更归入以下类别之一,且只能归入一个类别(按此顺序):
| 类别 | 包含 | 排除 |
|---|---|---|
| Added(新增) | 新功能、新的公开 API、新的 CLI 命令 | 未向用户公开的内部工具 |
| Changed(变更) | 行为修改、性能改进、对用户有影响的依赖更新 | 不改变行为的重构 |
| Deprecated(弃用) | 标记为将来移除的功能 | - |
| Removed(移除) | 删除的功能、移除的公开 API | 移除的内部代码 |
| Fixed(修复) | Bug 修复、错误处理改进 | 仅涉及测试的修复 |
| Security(安全) | 漏洞补丁、安全加固 | - |
完全排除:
- CI/CD 配置变更(除非会影响用户)
- 仅涉及文档的变更(除非揭示了新功能)
- 代码风格/格式变更
- 仅涉及测试的变更
- 不影响用户体验的内部重构
- 合并提交
第 3 步:确定版本号
根据变更,按照语义化版本规范建议下一个版本:
- MAJOR(X.0.0):破坏性变更影响公开 API
- MINOR(x.Y.0):新增功能,且向后兼容
- PATCH(x.y.Z):仅修复 Bug
根据现有标签检测标签格式(带或不带 v 前缀)。
第 4 步:编写发布说明
使用以下精确格式生成 CHANGELOG.md 条目:
## [VERSION] - YYYY-MM-DD
### Added
- **scope:** Add new feature description ([#54](REPO_URL/pull/54))
### Changed
- **Breaking:** Rename `oldName()` to `newName()` for consistency ([#145](REPO_URL/pull/145))
**Migration:** Replace all calls to `oldName()` with `newName()`.
### Deprecated
- **scope:** Deprecate `legacy_function()` in favor of `new_function()` ([#143](REPO_URL/pull/143))
### Removed
- **Breaking:** Remove deprecated `old_function()` ([#141](REPO_URL/pull/141))
### Fixed
- **scope:** Fix race condition when multiple workers access shared state ([#139](REPO_URL/pull/139))
### Security
- **deps:** Update vulnerable package to patched version ([#49](REPO_URL/pull/49))
编写规则
格式要求:
- 每个条目均以祈使动词开头:Add、Fix、Remove、Update、Improve、Rename、Deprecate、Patch
- 存在作用范围前缀时,使用粗体:
**server:**、**cli:**、**api:** - 每项变更占一行(破坏性变更除外,需附迁移说明)
- 在行末添加 PR/issue 链接
- 在每个类别内按重要性排序条目(影响最大的排在最前)
- 完全省略空类别
破坏性变更:
- 添加粗体前缀
**Breaking:** - 在所属类别中排在首位
- 在下一行添加
**Migration:**区块,准确说明用户必须做哪些更改 - 对 API 签名变更提供更改前后的代码示例
语气:
- 面向库的使用者,而非维护者
- 关注用户有哪些变化,而非实现方式
- 具体明确——绝不要写“各种改进”或“Bug 修复”
- 每个条目都应无需阅读 PR 即可理解
应避免的不良示例:
# BAD - Too vague
- Fixed bugs
- Performance improvements
- Updated dependencies
# BAD - Implementation-focused
- Refactored the internal state machine to use async/await
# BAD - Missing context
- Fixed #234
应遵循的良好示例:
# GOOD - Specific and user-focused
- **server:** Fix timeout errors when processing files larger than 100MB ([#234](URL))
- **cli:** Add `--dry-run` flag to preview changes before execution ([#235](URL))
- **api:** Improve cold-start latency from 2.3s to 0.8s by lazy-loading plugins ([#236](URL))
步骤 5:更新 CHANGELOG.md
-
如果存在
CHANGELOG.md:- 在
## [Unreleased]部分后插入新版本(如果没有 Unreleased,则插入到顶部)
- 在
-
如果不存在
CHANGELOG.md,则使用以下标题创建:
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
-
更新文件底部的页脚引用链接。 此步骤为必做项——如果缺少这些链接,CodeRabbit 和其他审查者会将变更日志标记为不完整。需要进行两项编辑:
a. 推进
[Unreleased],使其与新版本进行比较,而非与之前的版本比较。 b. 在[Unreleased]正下方插入一行[NEW_VERSION],比较之前的标签与新版本。示例差异(在
3.1.0之后发布3.2.0):-[Unreleased]: https://github.com/OWNER/REPO/compare/v3.1.0...HEAD +[Unreleased]: https://github.com/OWNER/REPO/compare/v3.2.0...HEAD +[3.2.0]: https://github.com/OWNER/REPO/compare/v3.1.0...v3.2.0 [3.1.0]: https://github.com/OWNER/REPO/compare/v3.0.0...v3.1.0编辑后,运行步骤 6,验证两行页脚均存在且正确。不要跳过步骤 6——它是硬性门槛,可防止反复出现“缺少页脚比较链接”的审查反馈。
步骤 6:验证 CHANGELOG 页脚比较链接(硬性门槛)
这是针对页脚比较链接的强制检查。必须在 gen-release-notes 报告成功之前,以及发布工作流中的任何提交步骤之前运行。此检查并非建议项——两个 grep -q 检查都必须以 0 退出,否则检查失败并指出缺失的行。
请原样运行以下代码块:
# Read the staged CHANGELOG.md blob (index), not the working tree, so unstaged
# edits cannot make the gate pass when the commit would actually fail.
STAGED_CHANGELOG=$(git show :CHANGELOG.md)
# Extract NEW and PREV versions from the staged CHANGELOG.md.
# We only match numeric `## [X.Y.Z]` headings so `## [Unreleased]` is skipped.
NEW_VERSION=$(printf '%s\n' "$STAGED_CHANGELOG" | grep -m1 -E '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' | sed -E 's/^## \[([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/')
PREV_VERSION=$(printf '%s\n' "$STAGED_CHANGELOG" | grep -m2 -E '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' | sed -n '2p' | sed -E 's/^## \[([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/')
if [ -z "$NEW_VERSION" ] || [ -z "$PREV_VERSION" ]; then
echo "GATE FAIL: could not extract NEW_VERSION ($NEW_VERSION) or PREV_VERSION ($PREV_VERSION) from staged CHANGELOG.md"
exit 1
fi
# Infer optional tag prefix (`v` or empty) from the existing [Unreleased] footer.
# Step 3 explicitly allows tags with or without `v`; never hardcode `v` here.
# Fall back to `v` only when no [Unreleased] footer exists yet (first release).
TAG_PREFIX=$(printf '%s\n' "$STAGED_CHANGELOG" | sed -nE 's|^\[Unreleased\]: .*/compare/(v?)[0-9]+\.[0-9]+\.[0-9]+\.\.\.HEAD$|\1|p' | head -1)
if ! printf '%s\n' "$STAGED_CHANGELOG" | grep -qE '^\[Unreleased\]:'; then
TAG_PREFIX="v"
fi
echo "Gating footer compare links: NEW=${TAG_PREFIX}${NEW_VERSION}, PREV=${TAG_PREFIX}${PREV_VERSION}"
# Escape dots so they match literally inside the regex.
NEW_RE=${NEW_VERSION//./\\.}
PREV_RE=${PREV_VERSION//./\\.}
# Check 1: the new [NEW_VERSION] footer line exists and points PREV->NEW.
printf '%s\n' "$STAGED_CHANGELOG" | grep -qE "^\[${NEW_RE}\]: .*compare/${TAG_PREFIX}${PREV_RE}\.\.\.${TAG_PREFIX}${NEW_RE}\$" \
|| { echo "GATE FAIL: missing footer line: [${NEW_VERSION}]: .../compare/${TAG_PREFIX}${PREV_VERSION}...${TAG_PREFIX}${NEW_VERSION}"; exit 1; }
# Check 2: the [Unreleased] footer line is advanced to compare from the new tag.
printf '%s\n' "$STAGED_CHANGELOG" | grep -qE "^\[Unreleased\]: .*compare/${TAG_PREFIX}${NEW_RE}\.\.\.HEAD\$" \
|| { echo "GATE FAIL: [Unreleased] is not advanced; expected: [Unreleased]: .../compare/${TAG_PREFIX}${NEW_VERSION}...HEAD"; exit 1; }
echo "Footer compare links verified: [${NEW_VERSION}] and [Unreleased] both present and correct."
通过条件(客观标准): 上方两个 grep -q 调用针对暂存的 CHANGELOG.md 均以 0 退出。代码块必须打印 Footer compare links verified: ... 并以 0 退出。
失败时: 检查会指出缺失或错误的行。返回步骤 5,编辑 CHANGELOG.md 以添加或更正该行,然后重新运行此代码块。在代码块以 0 退出之前,不要继续执行步骤 7,也不要报告成功。
步骤 7:输出摘要
更新变更日志后,提供:
- 建议的版本号及理由
- 分类变更摘要
- 需要特别注意的任何重大变更
- 确认已更新 CHANGELOG.md
Conventional Commits 映射
将提交前缀映射到变更日志类别:
| 提交前缀 | 变更日志类别 |
|---|---|
feat(scope): | 新增 |
feat!(scope): | 新增(带有 Breaking 前缀) |
fix(scope): | 修复 |
perf(scope): | 变更 |
security(scope): | 安全 |
docs:, chore:, ci:, test:, style: | 排除 |