关于 swain-release
swain-release 是面向软件仓库的发布技能,可根据项目现有约定拟定发布方案,并指导生成变更日志、更新版本、执行安全检查和创建标签。
主要功能
- 01从仓库识别发布约定
- 02根据提交建议版本升级
- 03按类别编写变更日志
- 04更新版本文件并创建标签
- 05打标签前执行安全检查
适用场景
- 01依据现有 Git 标签准备发布
- 02审核建议版本和变更日志
- 03打标签前更新项目版本文件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cristoslc/ swain --skill swain-release安装后在 Claude Code 中输入/swain-release确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitkeepchangelog-contract.yamlscripts/render_changelog.pytemplates/changelog.md.j2tests/test_render_changelog.py详细介绍
发布
在执行任何会更改状态的操作之前,请检查是否存在活动会话:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO_ROOT/.agents/bin/swain-session-check.sh" 2>/dev/null
如果 JSON 输出中的 "status" 不是 "active",请告知操作员:“没有活动会话——要用 /swain-init 启动一个吗?”如果对方不接受,则继续。
通过识别项目的版本管理上下文、生成变更日志、更新版本号并创建标签来发布。适用于任何代码仓库:通过读取 Git 历史和项目结构来获取上下文,而不是硬编码假设。
覆盖文件
开始之前,如果 .agents/release.override.skill.md 存在,请先读取。这是由项目所有者编写的自由格式 Markdown 文件,其中的指令会叠加在此技能之上;如有冲突,以该文件内容为准。它可以缩小默认范围、指定版本文件位置、设置标签格式、添加发布前/后的步骤,或包含其他内容。
如果不存在覆盖文件,则仅根据上下文进行检测。
工作流
1. 收集上下文
根据项目现有内容推断其发布惯例。在向用户提出任何建议之前,先完成以下所有检查。
标签历史:
git tag --sort=-v:refname | head -20
根据现有标签推断:
- 标签格式 —
v1.2.3、1.2.3、name-v1.2.3或其他格式 - 版本管理方案 — semver、calver 或自定义方案
- 当前版本 — 与检测到的格式匹配的最新标签
如果没有任何标签,请注明这是首次发布,并询问用户希望采用哪种格式。
上次发布以来的提交:
git log <last-tag>..HEAD --oneline --no-decorate
如果不存在标签,则使用所有提交(或选取合理的时间范围——如果有数百条提交,请询问用户)。
版本文件 — 扫描通常包含版本号的文件:
# Look for common version carriers
grep -rl 'version' --include='*.json' --include='*.toml' --include='*.yaml' --include='*.yml' -l . 2>/dev/null | head -20
还要检查 VERSION 文件、SKILL.md frontmatter 中的 version:,以及 package.json、pyproject.toml、Cargo.toml 等文件中的 version 字段。暂时不要修改任何内容,只需记录现有文件。
2. 确定版本更新幅度
使用 Conventional Commit 前缀解析上次标签以来的提交,并据此建议更新幅度:
| 提交前缀 | 建议 |
|---|---|
feat | 次版本更新 |
fix | 修订号更新 |
docs、chore、refactor、test、ci | 修订号更新 |
正文中有 BREAKING CHANGE,或类型后有 ! | 主版本更新 |
以最高级别的信号为准(任何不兼容变更都意味着主版本更新;任何 feat 都意味着至少次版本更新;否则更新修订号)。
如果提交不遵循 Conventional Commit 格式,则改为列出这些提交,并询问用户认为合适的更新幅度。
3. 提议发布方案
执行任何操作之前,先向用户展示发布方案。包括:
- 当前版本(取自最新标签,或标注“首次发布”)
- 建议版本(应用检测到的更新幅度后得到)
- 变更日志预览(主题式叙述——参见第 4 步)
- 待更新文件(第 1 步中找到的版本文件,如有)
- 要创建的标签(采用检测到的格式)
等待用户确认、调整版本或取消。如果用户想采用不同于建议的版本,则使用用户指定的版本——建议仅供参考,并非强制要求。
4. 生成变更日志
综合归纳,不要照抄。 变更日志是写给阅读发布说明的人看的,不是给 git log --oneline 换个形式用的。应将数十条提交归纳为少数连贯的叙述。
开始撰写之前,先阅读现有的 CHANGELOG.md(如有),以匹配项目已有的语气、详略程度和结构。变更日志应读起来像是由同一个人撰写的每条记录。
基于模板的变更日志
变更日志由 Jinja2 模板(templates/changelog.md.j2)根据 JSON 数据文件渲染生成。这样可以将分类决策(某项变更属于哪个章节?)与渲染方式(Markdown 格式如何呈现?)分开。
步骤 4a — 将提交分为五类。 每条提交只能归入一类:
| 分类 | 应包含的内容 | 不应包含的内容 |
|---|---|---|
features | 已发布且操作者今天就能使用的功能:新技能、新 CLI 命令、操作者调用的新脚本、面向操作者的工作流修复。判断标准:“这项工作是否让操作者能够做一些以前做不到或有所不同的事?”对应的工件必须处于 Complete 状态(或者工作已部署但没有跟踪工件)。 | 规划工件、内部质量改进、对内部工具的阈值调整、内部测试套件、操作者不会直接调用的脚本修复、仍处于 Active 状态的部分实现工作。 |
in_progress | 已部分发布的工作——代码已进入主干,操作者可以使用其中一部分,但跟踪工件仍处于 Active 状态(尚未 Complete)。写成“由于 Y 仍需要 Z,X 现在已以早期形式提供”。如实说明不足之处。 | 已完整发布的工作(归入 features)。没有代码进入主干的纯计划工作(归入 roadmap)。 |
roadmap | 对计划中工作的前瞻性预览——即将推出什么,以及它对用户为何重要。写成“正在规划/设计 X,因为 Y”,而不是“已创建 SPEC-NNN”。除非读者会搜索工件 ID,否则省略它们。跳过纯内部杂务。 | 工件状态转换(“已激活 EPIC” “已创建 SPEC”)、任何已发布的内容(归入 features)、任何已有代码进入主干的内容(归入 in_progress)。 |
research | Trove 集合、探索性工作的完成事项、研究工件、收集到的证据。 | 研究得出的规格(归入 roadmap 或 in_progress)。 |
supporting | 内部质量改进、内部测试套件、对内部工具的阈值调整、内部脚本修复、依赖项升级、交叉引用补充、小型重构、CI 变更。 | 任何操作者会直接使用的内容(归入 features)。 |
Agent 容易弄错的关键区别:创建 SPEC 或 EPIC 属于 roadmap 变更,而非功能。 功能是操作者因为本次发布而今天就能使用的东西。SPEC 是计划在之后发布的内容。
Agent 容易弄错的第二个区别:代码已进入主干但工件处于 Active 状态,属于进行中工作,而非功能。 如果跟踪规格的阶段为 Active,这项工作仅部分可用——应使用 in_progress 分类。只有当工件达到 Complete 状态(或者没有工件且工作已部署并可用)时,才将其移入 features。
Agent 容易弄错的第三个区别:内部改进属于 supporting,而非功能。 提高内部阈值、修复内部脚本中的反模式、为 swain 自身代码添加测试套件——这些都能提升质量,却不会为操作者带来新能力。可以问:“操作者在日常工作流中会注意到这项改动吗?”如果答案是“只有之前出现故障时才会注意到”,那就属于 supporting。
Roadmap 反模式: “已激活 EPIC-029,包含 3 个子 SPEC(SPEC-118、SPEC-119、SPEC-120)”是无关信息——它描述的工件状态转换只对项目维护者有意义。应改为:“正在泛化主干检测,以便 swain 无需配置即可在任意分支名称上运行。”读者应该了解即将推出什么,以及他们为何会关心,而不是哪些内部跟踪工件发生了状态变化。
Supporting 反模式——不应归入 supporting 的内容:
- 开发流程变更(“重新排序 close handler”“增加对 session-state 的容忍度”)——这些是实现细节;完全省略。
- 工件状态转换(“EPIC-053 已获批并激活”“已创建 SPEC-220”)——这些描述的是规划活动,而非已发布的工作;省略或放入 roadmap。
- 发布窗口开始前就已存在的未跟踪文件——只包含 diff 中有变更的文件。
- 面向操作者的工作流修复——这些属于 features,而非 supporting。 包含的内容: 依赖项升级、CI/配置变更、内部重构、清理死代码、可读性修复、内部测试套件、内部工具的阈值变更、内部脚本的 Bug 修复(操作人员不会直接调用的脚本)。
完全省略机械性提交:合并提交、生命周期哈希标记、索引刷新、书签前进。
分类互斥。 如果某项内容出现在功能中,就不能出现在进行中、路线图或支持中。如果出现在进行中,就不能出现在功能或路线图中。如果出现在路线图中,就不能出现在支持中。每个提交必须且只能归入一个类别,或被省略。
步骤 4b — 直接编写变更日志 Markdown。 将 templates/changelog.md.j2 中的模板用作结构指南,而非渲染目标。请按照以下格式自行编写 Markdown:
## [0.10.0-alpha] - 2026-03-21
### Features
#### CLI Roadmap Renderer
chart.sh roadmap --cli produces deterministic, terminal-friendly
output grouped by Eisenhower quadrant with all first-degree children
nested under their parent initiative. New swain-roadmap skill wraps
it as the user-facing entry point: regenerate, open, display.
### In Progress
- CHORE artifact type — ADR-045 and script support are in
trunk, but skill docs and Python tooling (SPEC-312/313) are still
Active.
### Planned
- Session facilitation rebuild — rethinking how swain helps the operator maintain focus, make decisions, and recover context across sessions.
### Research
- Google Stitch SDK trove — 7 sources collected.
### Supporting Changes
- Dependency graph rendering switched to flowchart TD for clearer layout.
- Cross-reference enrichment across ~100 doc files.
格式规则:
- 功能部分仅使用标题——每项功能都使用一个
#### 标题,并附上说明段落。功能部分不得使用独立项目符号。如果某项变更太小,不适合单独设标题,就将其并入相关标题的说明,或移至支持部分。 - 进行中部分可以混用标题和项目符号——选择最能说明当前进展和剩余差距的形式。
- 计划和路线图部分可以混用标题和项目符号——对内容充实、足以写成段落的主要计划工作使用标题;简短内容使用项目符号。
- 研究和支持部分始终使用扁平项目符号列表。
- 完全省略空部分。
应省略的内容
合并提交、生命周期哈希标记、索引刷新、书签前进及其他机械性提交都应完全省略——它们只会增加噪声,不提供信息。对于最终写入变更日志的文本,应去掉提交类型前缀(feat:、fix:)。
使用提交前缀判断版本升级类型,而非决定变更日志结构
传统提交类型用于判断应进行主要、次要还是补丁版本升级(步骤 2)。此后就不要再考虑这些类型——变更日志读者不关心某项内容属于 feat 还是 docs。
变更日志的位置:
- 如果存在
CHANGELOG.md,就在顶部(标题下方)插入新部分 - 如果不存在变更日志,询问用户是否要创建,以及放在哪里
- 如果用户不想创建文件,就直接在对话中输出
5. 升级版本
更新步骤 1 中确定的文件里的版本字符串。请精准修改——只改版本值,不要改动周围内容。对于每种文件类型:
- package.json / composer.json:更新
"version"字段 - pyproject.toml / Cargo.toml:更新
version = "..." - SKILL.md frontmatter:更新 YAML 标头中的
version: - VERSION 文件:替换文件内容
如果文件中有多个类似版本的字符串,且无法确定应更新哪一个,请询问用户,不要猜测。
5.5. 安全门禁
打标签前,运行安全扫描器,检查密钥、依赖项漏洞和静态分析问题。调用 swain-security-check 技能(如果该技能不可用,则直接运行扫描脚本):
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
SCANNER="$REPO_ROOT/.agents/bin/security-scan.sh"
if [[ -n "$SCANNER" ]]; then
bash "$SCANNER"
fi
如果报告中包含任何严重或高危级别的问题,请停止发布并向用户说明。用户可以选择:
- 修复问题后继续
- 确认这些问题并仍然继续(由用户决定,而非你决定)
中危及以下级别的问题应予以报告,但不会阻止发布。
如果未安装安全扫描器,请说明这一缺失并继续——不要因为缺少工具而阻止发布。
5.7. README 门禁(SPEC-211)
这是一个双重硬性门禁,用于验证 README 是否一致,以及 README 中承诺的内容是否有测试覆盖。打标签前,两项都必须通过或被明确豁免。
跳过条件: 如果不存在 README.md,则跳过此门禁——swain-doctor 已会标记 README 缺失。不要因为一个从未创建过的文件而阻止发布。
第 1 部分:对齐检查
阅读 README.md,并提取所有关于项目用途、目标用户、工作方式和支持内容的声明。与当前的 Active 工件树(Visions、Designs、Journeys、Specs)进行比较。
指出每处不一致:
- 过时的承诺 — README 声称提供某项功能或行为,但工件明确说明该内容已被删除或取代。
- 覆盖缺失 — 工件描述了已交付的功能,但 README 未提及。
- 相互矛盾 — README 与工件在行为、受众或范围方面存在分歧。
未解决的偏差会阻止发布。对于每项发现,操作员必须:
- 更新 README,使其与实际情况一致,或
- 更新工件,使其与 README 一致,或
- 明确豁免——说明为何可以接受该偏差。豁免原因会记录在发行说明中。
第 2 部分:验证检查
找出 README 中暗示可测试行为的声明:
- 安装/设置声明 — “运行
npm install && npm start”意味着应进行冒烟测试,确认安装流程可用。 - CLI 行为 — “
foo init会创建项目”意味着应进行集成测试。 - API 表面 — “导出
createWidget函数”意味着应进行导出存在性测试。 - 行为承诺 — “失败时自动重试”意味着应进行重试行为测试。
- 集成声明 — “支持 PostgreSQL 15+”意味着应进行特定版本的集成测试。
对于每项未经测试的承诺,操作员可以选择:
- 添加测试 — 暂停发布,创建测试后重新运行检查。
- 移除承诺 — 更新 README 并重新运行检查。
- 接受差距 — 将该差距记录在发行说明中。
豁免
操作员可以通过明确声明豁免整个 README 检查关卡。豁免声明及其原因会记录在发行元数据中(变更日志或标签注释)。不得静默绕过——操作员必须说明豁免原因。
6. 提交并打标签
暂存已更改的文件(变更日志和版本号更新)并提交:
git add <changed-files>
git commit -m "release: v1.5.0
Co-Authored-By: <model-name-from-system-prompt> <noreply@unknown>"
从系统提示词中解析模型名称。如果子 Agent 使用了不同的模型运行,则为每个模型添加一条 Co-Authored-By 署名行(参见 AGENTS.md 中的 Model attribution)。
然后创建附注标签:
git tag -a <tag> -m "Release <tag>"
使用第 1 步检测到的标签格式(或用户指定的格式)。
6.5. 压缩合并到发布分支
如果存在 release 分支(使用 git rev-parse --verify release 2>/dev/null 检查),则将当前分支(trunk)压缩合并到该分支:
# Detect trunk branch dynamically (EPIC-029)
REPO_ROOT=$(git rev-parse --show-toplevel)
TRUNK=$(bash "$REPO_ROOT/.agents/bin/swain-trunk.sh")
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Ensure we're on the trunk (development) branch
if [ "$CURRENT_BRANCH" != "$TRUNK" ]; then
echo "Warning: not on $TRUNK ($CURRENT_BRANCH). Skipping release branch update."
else
# Tag trunk first (lifecycle hashes must be reachable from trunk per ADR-012)
# Tag was already created in step 6
# Squash-merge trunk into release
git checkout release
git merge --squash "$TRUNK"
git commit -m "release: <tag>
Co-Authored-By: <model-name-from-system-prompt> <noreply@unknown>"
# Return to trunk
git checkout "$TRUNK"
fi
如果不存在 release 分支,则静默跳过此步骤——该项目尚未采用 trunk+release 模式。
7. 提议推送
询问用户是否要推送。推送前,务必先获取并整合上游更改,以避免推送被拒绝:
# Fetch upstream to detect divergence before pushing
git fetch origin
# Rebase trunk onto upstream to incorporate any changes that landed since the release was prepared
TRUNK_UPSTREAM="origin/$TRUNK"
if ! git merge-base --is-ancestor "$TRUNK_UPSTREAM" HEAD; then
echo "Trunk has diverged from upstream. Rebasing..."
git rebase "$TRUNK_UPSTREAM" || {
echo "Rebase conflict — resolve before pushing."
git rebase --abort
exit 1
}
fi
# If release branch was updated, rebase it too
if git rev-parse --verify release >/dev/null 2>&1; then
RELEASE_UPSTREAM="origin/release"
if git rev-parse --verify "$RELEASE_UPSTREAM" >/dev/null 2>&1; then
if ! git merge-base --is-ancestor "$RELEASE_UPSTREAM" release; then
echo "Release branch has diverged from upstream. Rebasing..."
git checkout release
git rebase "$RELEASE_UPSTREAM" || {
echo "Rebase conflict on release — resolve before pushing."
git rebase --abort
git checkout "$TRUNK"
exit 1
}
git checkout "$TRUNK"
fi
fi
fi
如果第 6.5 步更新了发布分支:
git push origin "$TRUNK" && git push origin release && git push origin <tag>
如果不存在发布分支,则照常推送:
git push && git push origin <tag>
只推送指定标签——git push --tags 会尝试推送所有本地标签,并导致远程已存在的标签产生大量无用的拒绝消息。
未经询问不得推送——用户可能想先审查,或者他们的 CI 流水线会在标签上触发。
边缘情况
包含多个版本流的 Monorepo: 如果标签历史表明各软件包使用单独的标签(例如 frontend-v1.2.0、api-v3.1.0),应询问用户要发布哪个软件包,而不是自行假定。
预发布版本: 如果用户要求预发布版本(alpha、beta、rc),则在版本号后追加预发布后缀:1.5.0-alpha.1。如果之前已有预发布标签,则遵循现有惯例。
没有 Conventional Commits: 如果提交历史未使用 Conventional Commits 前缀,则不要强行分组。提供一个平铺列表,并让变更日志采用简单的项目符号列表。
工作区有未提交更改: 如果调用 /swain-release 时存在未提交更改,应警告用户并询问是继续(这些更改不会包含在此次发布中),还是中止操作,以便用户先提交更改。
会话书签
成功发布后,更新书签:bash "$REPO_ROOT/.agents/bin/swain-bookmark.sh" "Released v{version}"