OP Stack 发布说明 PR 分诊实战:用 pr-facts.sh 从嘈杂草稿中筛出真正影响操作者的变更
2026/9/17 21:51:59 网站建设 项目流程

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.tomlrust/op-alloy/rust/alloy-op-evm/rust/alloy-op-hardforks/rust/op-revm/全部分支。

op-core/op-service/承载着每一个Go 服务共享的代码,同时被测试脚手架(op-devstackop-e2eop-acceptance-tests)和独立发布工具(op-deployerop-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/下的txmgrbgpo以及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.sumrust/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.go

Keep 与 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。对最终版发布,处理方式为:

  1. 合并到最终 tag 之下;
  2. 按 PR 号去重;
  3. 对并集做分诊——发布覆盖所有这些内容。

这对应 SKILL.md 第 2 步的同一提醒,也与 house-style 中"已发布说明必须 final tag 对 final tag 做 compare"的规则呼应。

分诊之后:链接进完整发布流程

triage 只是整条流水线的一环。完整链路为(SKILL.md):

  1. just release-notes <component>生成 git-cliff 原始草稿(justfile 中可见其用release-paths展开 include-path 并转成 glob、用--tag-pattern定位 base);
  2. pr-facts.sh打标签 → 按本文规则分诊;
  3. 被裁 PR 的原始 bullet 以 HTML 注释形式保留在草稿底部并附一句原因(如<!--* op-core/fees: add Jovian DA-footprint calculation (#22163) — doesn't affect the batcher-->),便于审阅者一键恢复;
  4. 按 house-style.md 的骨架(## Overview## Breaking changes## Chain Configuration## Other changes)写精选变更列表,条目遵循"impact, not implementation"原则;
  5. 经用户审阅(含完整 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询