OP Stack 发布说明 PR 分诊实战:用 pr-facts.sh 从嘈杂草稿中筛出真正影响操作者的变更
【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism
导读:OP Stack 每个组件(op-node、op-batcher、op-supernode、kona-node 等)的发布说明并非简单的 PR 清单罗列,而是一份经过严格分诊(triage)的精选变更列表。本文基于.claude/skills/release-notes/reference/triage.md展开,讲解为什么just release-notes生成的原始草稿会包含大量与二进制无关的提交,以及如何借助 pr-facts.sh 的 LINKED/CONFIG/DEPS/--/? 五类标签、判断通行(judgment pass)与地面真值验证,把 30+ 条噪声 PR 收敛为一份每条都值得操作者阅读的发布说明。读完你将掌握一套可复制的发布说明分诊方法论,并能直接用仓库内的脚本对任意组件复现全过程。
为什么原始草稿是嘈杂的
just release-notes <component>基于 git-cliff 按include-path 白名单选取提交,而这些路径是刻意放宽的。以 Go 服务为例,草稿会命中如下路径:
--include-path "<component>/**/*" --include-path "go.*" --include-path "op-core/**/*" --include-path "op-service/**/*"这些 include-path 的真实来源是 justfile 中的release-pathsrecipe —— 它是"组件到底发布什么"的唯一事实来源。从源码可见,op-node等核心 Go 服务共享一份go_shared="shared=go.*,op-core/,op-service/",而kona-*组件则会命中rust/kona/、rust/Cargo.toml、rust/op-alloy/、rust/alloy-op-evm/、rust/alloy-op-hardforks/、rust/op-revm/全部分支。
op-core/与op-service/承载着每一个Go 服务共享的代码,同时被测试脚手架(op-devstack、op-e2e、op-acceptance-tests)和独立发布工具(op-deployer、op-chain-ops)使用。于是草稿必然捡起大量永远不会进入二进制的提交——文档给出了一组量化证据:
op-batcher v1.16.13 的草稿列出了 21 个 PR,其中 8 个完全没有触碰 op-batcher 编译所需的任何代码。
同理,kona-*组件同时过滤rust/kona/**、rust/op-alloy/**和rust/alloy-op*/**,所以 kona-node 的草稿也会收进 kona-client、kona-host、kona-sp1 的改动。宽路径是刻意的:它保证不遗漏,但去噪就成了发布流程中不可省略的一步。
关键问题:不是"碰没碰组件目录",而是"是否改变二进制行为"
分诊的第一原则是纠正提问方式。不要问"这个 PR 是否触碰了<component>/目录"——共享代码会被编译进二进制,op-service/下的txmgr、bgpo以及op-core/fees都能直接改变 op-batcher 的运行行为。
正确的提问是:
这个 PR 是否改变了被编译进该二进制的代码,并且这种改变是否改变了操作者会注意到的行为?
文档特别强调:下游 Go 导入者不计数。即"为 monorepo 作为 Go 模块的下游导入者解锁"这类改动不构成用户可见面(详见 house-style.md 中对 Go-module importability 的处理)。
pr-facts.sh精确地回答了前半问("是否编译进二进制"),并给每个 PR 打上标签。
pr-facts.sh 的标签体系:五类裁决
对草稿运行:
.claude/skills/release-notes/scripts/pr-facts.sh /tmp/<component>-draft.md <component>脚本为每个 PR 输出一行制表符分隔的记录:<tag> #<number> <author> <n> files <title> <paths>,其中 tag 的含义如下表:
| Tag | 含义 | 默认处置 |
|---|---|---|
LINKED | 改动了二进制传递依赖集中的某个包;列出的包即为到达二进制的那些 | 候选——进入判断通行 |
CONFIG | 移动了内嵌 superchain 注册表(子模块 pin、生成归档校验和,或 kona 的快照);若同时移动了编译代码则为LINKED+CONFIG | 永远人工阅读 |
DEPS | 改动依赖清单但未触碰编译包 | 丢弃,除非是安全升级 |
-- | 没有触碰任何二进制会编译的内容 | 丢弃 |
? | 依赖无法解析(或未指定组件、PR 无法抓取) | 人工判断 |
这些标签并非来自文件名猜测,而是精确的依赖闭包计算(见 pr-facts.sh):
- Go 组件:
go list -deps ./<component>/cmd(失败则回退./<component>/...),再剥离模块前缀github.com/ethereum-optimism/optimism/得到包集合; - Rust 组件(
kona-*与op-reth):cargo tree -p <component> -e normal --prefix none,再用cargo metadata把工作区成员的目录映射回 crate 名。Rust 依赖解析约需 2 分钟,因此按组件缓存在$TMPDIR,仅在rust/Cargo.lock变化后失效。
标签实现细节值得注意(脚本END块):
- 测试文件已被排除在链接判定之外:Go 的
_test.go不参与go list -deps,故只增加覆盖率的 PR 会落在--;但 Rust 测试代码藏在#[cfg(test)]内、无法按路径排除,所以LINKED的 Rust 行仍需人工检查; CONFIG是叠加标签而非LINKED的替代:注册表 bump 与编译代码同现很常见(如新硬分叉激活时间 + 代码),此时必须输出LINKED+CONFIG;- 只要清单(
go.mod/go.sum、rust/Cargo.toml/Cargo.lock)移动过就判DEPS而非--,哪怕旁边还躺着一个无关文件; - 抓取 PR 失败绝不会静默变成"没碰任何东西":无法抓取的 PR 被强制标记为
?,必须人工裁决,而不是被悄悄丢弃; gh最多返回 100 个文件,更大的 PR 会标注(truncated, verify by hand),提示其可能隐藏了链接包。
CONFIG 行:最容易被忽略、却往往最要紧
CONFIG行永远不携带链接证据,也永远不会携带——注册表归档是在构建期生成的,pin bump 只是一个 gitlink 加一个校验和,任何包都解析不出"链接"。但它常常是整个发布中最有后果的变更:新的硬分叉激活时间就是一条## Chain Configuration条目,可能让发布对相关链变成required。处理方法是读 PR 正文,确认哪些链、哪些值发生了移动。
DEPS行如果列出的是路径而非(manifest only),说明它还顺带改了别的东西——虽然没被编译进去。丢弃前要对照 lock diff 与组件的依赖集核对,文档给了活例:#22714看起来像 kona 的活,实际移动了hickory-resolver——op-reth 用它做 DNS 发现——从而移除了一个 High 级安全通告。
LINKED 行的判断通行:链接≠运行时路径
对每个LINKED行,只查看 diff 落在列出的那些包里的部分。先看意图,意图不清再看 diff:
gh pr view <N> --json title,body # 意图 gh pr diff <N> # 意图不清时能熬过机械关卡的那道陷阱是:链接证明包被编译进来,并不证明被改的函数位于组件的运行时路径上。这是判断通行真正存在的理由。文档给出了两个典型例子:
op-core/fees确实链接进 op-batcher,但 Jovian DA-footprint 工作(#22163、#22219)是从op-chain-ops/cmd/check-*工具触达的,batcher 运行时根本不走这条路——两个 PR 最终都以"不影响 batcher"的理由被裁掉;op-node/rollup/derive同样链接进 op-batcher,但纯 derivation 的改动不是 batcher 面向的。
询问"组件做什么",而不是"组件链接什么":
grep -rn "<ChangedSymbol>" <component>/ --include='*.go' | grep -v _test.goKeep 与 Drop 的判定标准
保留当改动到链接包的部分属于:
- 改变运行时行为——构建、提交、派生(derive)、gossip 或日志中的任何一环;
- 改变 flag、环境变量、配置键、默认值、指标(metric)或 RPC 表面;
- 修复可在生产环境触达的 bug、panic、竞态或正确性问题;
- 安全修复。
丢弃当它是:
- 纯测试、fixtures、mock 或
testutils噪音; - 无行为变化的纯重命名/移动;
- 移除一个生产环境从未开启的开发特性开关(dev-feature toggle);
- 注释、TODO 或文档清理;
- 只是擦过共享包的其他组件的工作;
- 无操作者影响的 Go API 变更,包括仅为 monorepo 的 Go 模块下游导入者解锁的变更。
跨组件 PR 的例外
当组件内嵌另一个组件时,跨组件 PR 应保留。op-supernode 运行虚拟 op-node,所以 op-node 的 follow-source reorg 指标应写进 supernode 的发布说明,即便该 PR 未触碰任何op-supernode/路径。
完全局限于未发布特性的改动整体裁掉,只有当它也触达今天仍在线的路径时才保留(活性检查见 house-style.md 的 Proportionality 一节:硬分叉查superchain-registry里的<fork>_time,DevFeature 位看默认值)。
从未发布过的 bug 修复:不参与升级建议
在让一个吓人的修复决定升级推荐之前,先确认 bug 是否落在同一发布区间内:
git tag --contains <sha-that-introduced-the-bug> | grep '<component>/v'输出为空意味着bug 从未进入任何发布版本。此时:PR 保留在列表中,但不得让它决定升级推荐,也不要在说明里提及它。"两者都落在本发布内,所以没有已发布版本受影响"是分诊结论,不是读者需要读的内容。
依赖与安全升级的处理
DEPS行通常是噪音,但有一条例外值得写一行:修补了二进制所链接库的 CVE 的升级——如果它是发布里最严重的事,还能抬高升级推荐。先确认模块真的在二进制里:
go list -deps ./<component>/cmd | grep <module-path>没有安全标签的 Renovate/dependabot 例行 bump:直接丢弃。
地面真值:用已发布版本校准判断
文档提供了两组可复现的对照数据(对重新生成的原始草稿复核):
op-node/v1.19.4—— 原始 32 个 PR,发布 15 个。裁掉的是:op-dispute-mon 与 op-challenger 的活、kona 变更、dependabot bump、测试修复、一个过时 TODO 清理、两个开发特性开关移除;op-batcher/v1.16.12—— 原始 23 个 PR,发布 1 个。几乎整份草稿都是其他组件的工作顺着 include-path 涌进了 op-batcher。
任何已发布版本都可以重新生成原始草稿来核对一次判断:
GITHUB_TOKEN=$(gh auth token) mise exec -- just release-notes op-node v1.19.3 v1.19.4注意这里的mise exec --并非可有可无:git-cliff 是 mise 固定版本的工具、不在PATH上,省掉它只会得到一条令人困惑的报错git: 'cliff' is not a git command(详见 SKILL.md)。
v1.19.5 列车是当前实践最接近的参考,各组件存活率:op-node 30 中留 10、op-batcher 21 中留 6、op-supernode 24 中留 8、kona-node 17 中留 6。"原始草稿里 60% 以上的 PR 被裁掉"是常态,不是异常。
多 section 草稿:合并与去重
如果早期的 RC 从未发布,git-cliff 会在同一份草稿里输出多个## What's Changed in <tag>section。对最终版发布,处理方式为:
- 合并到最终 tag 之下;
- 按 PR 号去重;
- 对并集做分诊——发布覆盖所有这些内容。
这对应 SKILL.md 第 2 步的同一提醒,也与 house-style 中"已发布说明必须 final tag 对 final tag 做 compare"的规则呼应。
分诊之后:链接进完整发布流程
triage 只是整条流水线的一环。完整链路为(SKILL.md):
just release-notes <component>生成 git-cliff 原始草稿(justfile 中可见其用release-paths展开 include-path 并转成 glob、用--tag-pattern定位 base);pr-facts.sh打标签 → 按本文规则分诊;- 被裁 PR 的原始 bullet 以 HTML 注释形式保留在草稿底部并附一句原因(如
<!--* op-core/fees: add Jovian DA-footprint calculation (#22163) — doesn't affect the batcher-->),便于审阅者一键恢复; - 按 house-style.md 的骨架(
## Overview→## Breaking changes→## Chain Configuration→## Other changes)写精选变更列表,条目遵循"impact, not implementation"原则; - 经用户审阅(含完整 drop list)后
gh release edit应用。
对想深入验证的读者,pr-facts.sh 是理解本方法论的最佳源码样本:resolve_go/resolve_rust展示精确依赖闭包的计算方式,unit()函数展示 Go 按包目录、Rust 按最长匹配 crate 目录的编译单元归属逻辑,fetch的失败兜底则体现了"无法裁决就显式标记,绝不静默丢弃"的分诊伦理。
【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考