mcpField
S
swain-release

swain-release

github.com/cristoslc开发2126 次安装26 周前更新
代码仓库

关于 swain-release

swain-release 是面向软件仓库的发布技能,可根据项目现有约定拟定发布方案,并指导生成变更日志、更新版本、执行安全检查和创建标签。

主要功能

  1. 01从仓库识别发布约定
  2. 02根据提交建议版本升级
  3. 03按类别编写变更日志
  4. 04更新版本文件并创建标签
  5. 05打标签前执行安全检查

适用场景

  1. 01依据现有 Git 标签准备发布
  2. 02审核建议版本和变更日志
  3. 03打标签前更新项目版本文件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cristoslc/swain --skill swain-release

安装后在 Claude Code 中输入/swain-release确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.gitkeep
changelog-contract.yaml
scripts/
render_changelog.py
templates/
changelog.md.j2
tests/
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)。
researchTrove 集合、探索性工作的完成事项、研究工件、收集到的证据。研究得出的规格(归入 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 与工件在行为、受众或范围方面存在分歧。

未解决的偏差会阻止发布。对于每项发现,操作员必须:

  1. 更新 README,使其与实际情况一致,或
  2. 更新工件,使其与 README 一致,或
  3. 明确豁免——说明为何可以接受该偏差。豁免原因会记录在发行说明中。
第 2 部分:验证检查

找出 README 中暗示可测试行为的声明:

  • 安装/设置声明 — “运行 npm install && npm start”意味着应进行冒烟测试,确认安装流程可用。
  • CLI 行为 — “foo init 会创建项目”意味着应进行集成测试。
  • API 表面 — “导出 createWidget 函数”意味着应进行导出存在性测试。
  • 行为承诺 — “失败时自动重试”意味着应进行重试行为测试。
  • 集成声明 — “支持 PostgreSQL 15+”意味着应进行特定版本的集成测试。

对于每项未经测试的承诺,操作员可以选择:

  1. 添加测试 — 暂停发布,创建测试后重新运行检查。
  2. 移除承诺 — 更新 README 并重新运行检查。
  3. 接受差距 — 将该差距记录在发行说明中。
豁免

操作员可以通过明确声明豁免整个 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}"

评论

支持 Markdown