用 AI Agent 跑发布列车:Munder Difflin 如何把版本号、变更日志与发布说明交给 Hive 自动化
【免费下载链接】munder-difflinA local multi-agent harness that works with your existing Claude Code, Codex subscriptions, allows you to run an office of agents项目地址: https://gitcode.com/GitHub_Trending/mu/munder-difflin
一次发布只有一个决策,剩下的四五十件杂务(版本号、变更日志、发布说明、站点更新、链接校验)都属于"同步类工作"。本文以 Munder Difflin 在八天内完成七个版本(0.3.8 → 0.4.4)的真实发布流程为主线,拆解这套"发布列车"(release train)的每一节车厢:从基于 diff 证据起草 changelog,到全仓版本引用的一次性同步,再到把漂移变成 CI 失败测试的护栏。读完你将得到一套与具体技术栈无关、可直接复制的多 Agent 发布流水线搭建方法。
为什么发布流程总是"第八个版本开始腐烂"
几乎每个团队的发布流程都以同一种方式退化:第一个版本,事无巨细、一丝不苟;第五个版本,changelog 只剩一句 "misc fixes";第九个版本,README 徽章落后两个版本,网站 FAQ 里还挂着已经不存在的套餐名。不是大家不再上心——而是同步类杂务的数量与"表面"(surface)数量线性相关(package.json、README、网站、llms.txt、发布页……每多一个展示版本号的地方就多一份同步工作),而人在截止日期压力下砍掉的恰好就是这些角落。
Munder Difflin 在 2026 年 8 月 11 日到 18 日连续发布 0.3.8 → 0.4.4 七个版本而没有发生这种退化,原因正是:杂务是一列由 Hive-Agent(多 Agent 蜂群)跑在"轨道"上的列车,决策仍然由人做,杂务交给清单。下面逐节拆解这列火车。
八个自然日、七个版本:发布列车把每个版本从"一下午"压缩成"一个决策"。
第一节车厢:基于证据的 changelog 条目
职责:Agent 读取自上一个 tag 以来实际合并的 commit 和 PR(而不是任何人对这一周的记忆),据此起草 changelog 条目。
Munder Difflin 从自己的事后复盘里偷来一条硬性规则,写进了这条 Agent 的提示词:
每一条 changelog 都必须从用户视角说明"什么东西坏了",而不是"哪个函数变了"。
例如 "Agents booted, looked healthy, and had no idea they could message anyone"(Agent 正常启动、看起来一切健康,却根本不知道自己能给别人发消息)这样一句话能活进 v0.4.4 发布博客 和 CHANGELOG.md,正是因为它起源于 diff 和 issue 讨论串——那句话是在那里被"挣来"的。
changelog Agent 只基于合并后的 diff 与 issue 线程工作,而非任何人对这一周的记忆。
第二节车厢:把版本引用一次同步到位
版本号字符串出现在的地方远比你以为的多:
package.json(包清单)- changelog 头部
- README 徽章
- 网站首页(
docs/index.html里的下载地址) - GitHub Release 页面(
RELEASE.md中的下载表) docs/llms.txt(面向爬虫与 LLM 的"Current version:"行)
这节车厢里 Agent 的清单非常简单:找到每一处引用、更新每一处引用、然后列出它查过哪些地方——最后一步让"遗漏"变得可见。这节车厢存在的原因是:一次审计发现docs/llms.txt连续两个版本都在宣传一个过期的版本号,而从来没有人在心理清单上给它留过位置。
在仓库里,这条"版本表面"清单是可以直接核对的:docs/llms.txt明确写有Current version: x.y.z一行,RELEASE-CHECKLIST.md 要求RELEASE.md、build/release-notes.md与CHANGELOG.md必须命名同一个版本,且package.json的版本必须是真实发布版本(不允许-rc字符串)。
第三节车厢:写给人的发布说明
changelog 是记录,release notes 是故事。第二遍处理把条目改写成人话——你会注意到什么、你需要做什么(通常什么都不用做,因为应用自己更新自己)。从 0.4.4 起,发布说明甚至能以设计过的页面形式直接渲染在应用内(见下文"发布投放(release drop)")。
这一节同时也是贡献者每版必署名的地方——@gts-47 的八个 PR 和 @baziyer 的渲染修复登上了 0.4.4 的致谢头条,因为 Agent 的清单写着"找出本版本所有社区 PR 并写出作者名",而清单永远不会害羞。
仓库中的对应实现见 src/shared/releaseNotes.ts:它把 GitHub release body(即RELEASE.md全文)切成 3~5 行纯文本摘要,供更新弹窗(toast)展示。其设计决策本身就是"人话化"工程:
- 最多 5 条 bullet、总预算 280 字符、单条不超过 110 字符(
RELEASE_NOTES_MAX_BULLETS/RELEASE_NOTES_MAX_CHARS/RELEASE_NOTES_MAX_BULLET_CHARS),因为"弹窗不是 changelog"; - 只截取
## What's new in <version>小节,跳过 tagline、下载表、构建说明等用户已经看过的东西; - 折叠跨行 bullet、把链接折叠成其文字标签、剥离图片徽章,但只剥离真正的强调符号——
DO_NOT_TRACK、first_run这类真实字符串必须原样存活。
第四节车厢:护栏——把漂移变成不可能
Agent 发布杂务最棒的地方在于:当 Agent 发现一类漂移,你就把它从"靠记忆记住"升级为"结构上不可能"。一个小型"链接与版本一致性检查器"现在在 CI 中运行,只要任何已知表面与当前版本不一致,构建就会失败。列车从此不再依赖列车长的注意力。
这个护栏在仓库里的实体是 tools/check-release-links.cjs,它正是文档中所说"Audit 发现漂移 → 写测试防止复发"的落地实现。其检查规则(全部以package.json的 version 为基准):
| # | 检查表面 | 规则 |
|---|---|---|
| 1 | RELEASE.md中所有Munder-Difflin-x.y.z-*资产名 | 必须等于当前版本,且至少存在一个下载资产 |
| 2 | archive/refs/tags/vx.y.z源码包链接 | 标签版本必须匹配,否则会静默发布上一版的源码 |
| 3 | docs/index.html的REL下载回退版本 | 允许更新(站点发布主机可能领先于 main),但更旧视为过期 |
| 4 | docs/llms.txt的Current version:行 | 必须精确匹配package.json |
两种运行模式:打 tag之前跑离线模式(此时资产还不存在);发布之后跑--live模式,对每个 URL 发 HEAD 请求并要求 200。这个脚本的注释还记录了一个真实事故:RELEASE.md的下载表从 v0.3.4 到 v0.3.7 一直钉死在 0.3.2,mac DMG 下载量从两位数十位数跌到个位数,页面却毫无报错——"nothing failed, nothing warned"。
发现一次漂移,就把它变成一条失败测试。列车从此不再依赖任何人的注意力。
配套的发布检查清单
RELEASE-CHECKLIST.md 是这套"机械闸门"的完整操作手册,值得原样照搬的核心做法包括:
- 升级跳板要先彩排:0.4.6 由 0.4.5 的更新器送达,所以只有"从新代码起跳"的跳板才能证明更新器本身。先发布
0.4.6-rc.1再发布一个 no-op 的0.4.7-rc.1,在测试机上先驱动一次0.4.6-rc.1 → 0.4.7-rc.1,让任何失败都落在可抛弃的预发布上; - rc 与正式版必须是同一条流水线的完整产物:macOS 走 Squirrel.Mac,需要
mac-universal.zip+.blockmap+latest-mac.yml(且path:必须指向 zip 而非 dmg),缺任一项就静默回退到手动安装,等于什么都没证明; - 健康版本永远触达不到的路径要手工注入:断网触发检查(~30 秒内必须到达 error 态而非永久 spinner)、
downloaded状态连点两次重启(不许卡死在 "The command is disabled")、最新版本上点徽章必须显示正向确认——这些只在用户出问题时才执行,健康的发布永远不会跑到它们。
发布投放(release drop):发布说明的一种设计形态
"第三节车厢"提到的应用内设计页,源码实现在 src/shared/releaseDrop.ts:作者在 GitHub release body 中<!-- drop -->与<!-- /drop -->标记之间写任意 HTML,应用端 extractDropHtml 将其抽出,包成自包含文档后在完全沙箱化的 iframe中渲染。因为这段标记是"远程、作者可控"的 HTML,仓库对它做了三层防御,全部被 test/release-drop.test.cjs 钉死:
- iframe 只授予
allow-popups(sandbox="allow-popups"),绝不出现allow-scripts+allow-same-origin组合; - 文档 CSP 用
default-src 'none'靠"未列出即拒绝"封死脚本,只放开img-src/media-src的 https/data/blob 与style-src 'unsafe-inline'、font-src data:; - 正则防御兜底剥离
<script>、on*=内联处理器和远程@import(后者是白屏事故的根源——远程字体样式表会阻塞渲染,在 Google 被墙的网络上是数十秒的 TCP 超时)。
为什么 Agent 天生适合这种形状的工作
发布杂务是理想的 Agent 负载,原因有三:
- 基于证据(evidence-based)——真相在 diff、tag 和文件里,验证是机械性的,不需要推断;
- 清单形状(checklist-shaped)——每次都是同一批表面,Agent 以人类只留给"第一个版本"的热情去执行;
- 可中断(interruptible)——每一节车厢都产出可审阅的工件(草稿条目、改动文件清单),人可以在任何一站上车检查。
同时注意什么不在列车上:决定发布什么、判断某个修复是否真实有效、选定头条文案。在 Munder Difflin 的八天里,这些判断恰恰因为不跟杂务争抢注意力,才能在几分钟内完成。
搭建你自己的发布列车:四步便携方案
无论你的技术栈是什么,都可以把这套流程搬走:
- 把清单写下来一次——列出每一个提到版本的文件、tag 到公告之间的每一步。这份文档本身就是 Agent 的 brief。Munder Difflin 的对应物就是 RELEASE-CHECKLIST.md 与 RELEASE.md。
- 让 Agent 指向证据——diff、合并的 PR、关闭的 issue。明令禁止凭记忆写作。
- 通过一个调度器(dispatcher/orchestrator)路由,让各节车厢按顺序运行——这正是编排器(orchestrator)存在的意义。
- 把发现转化为 CI 守卫——Agent 发现漂移是好事,但让漂移不可能的测试才是真正的胜利。参照 tools/check-release-links.cjs 的四个检查面。
结语:让发布只值一个决策
Munder Difflin 的发布节奏不是"打字更快"换来的,而是把每次发布的成本从"一下午"降到"一个决策",并让一列按时刻表运行的列车在人类睡觉时继续跑。八个自然日、七个版本、0.3.8 → 0.4.4 的完整车厢巡礼见 seven-releases-in-eight-days 的故事复盘;如果你想知道"某节车厢没跑"会发生什么,why-our-auto-update-never-ran 记录了同一种精神下最安静的一次失败。
【免费下载链接】munder-difflinA local multi-agent harness that works with your existing Claude Code, Codex subscriptions, allows you to run an office of agents项目地址: https://gitcode.com/GitHub_Trending/mu/munder-difflin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考