Skip to content

feat(scripts): digest 把「无 changeset 的 commit」从计数升级为按 subject 逐条点名 (#6174) - #6358

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-6174-name-changeset-less-commits
Aug 7, 2026
Merged

feat(scripts): digest 把「无 changeset 的 commit」从计数升级为按 subject 逐条点名 (#6174)#6358
hotlong merged 1 commit into
mainfrom
claude/issue-6174-name-changeset-less-commits

Conversation

@hotlong

@hotlong hotlong commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #6174

本 PR 只做分诊 07:51Z 钉死的选项 C:把 console changeset 正文里「无 changeset 的 commit」从一个计数升级为按 subject 逐条点名。

1. 前提复核(对 origin/main a5ca08d,#6289 / #6335 两棒落地之后)

三条内容读数逐条复核,全部仍成立:

复核项 现状 结论
objectui-range.mjs 的点名能力 const SHOW_EXCLUDED = has('--all'),渲染 - _(no changeset)_ … 行,self-test 有 `--all` itemizes the excluded commits by subject 能力已在,本单只消费不改造
objectui-changeset-digest.mjs 只计数不点名 正文 accounting 行写 omitted: 20 commits carrying no changeset (they ship no package code).,再无别的痕迹 缺口真实且当前
classifyRange 已产出所需数据 返回值里已有 noChangesetCommits({ sha, subject } 数组),objectui-range.mjs 正是从这里取数 只差接线,无需新增采集逻辑

同文件前序两棒均已在 main 且未被本 PR 触碰:#6289(hasBreakingAnnotation,破坏性判据扩到作者正文标注)、#6335(readAt 回落静默,那句回落事实在 stderr)。

为什么这不是「已经有了」:--all 的点名只存在于 objectui-range.mjs 产出的发布页 Console 段;写进平台发布记录的 console changeset 正文buildDigest 生成,它从未消费 noChangesetCommits。两个产物,一个有名字一个只有数字。

2. 这次漏掉的到底是什么(实测,不是推断)

区间 f995a452d2ca..7dfbeb704e1e(#6159 / PR #6173)的 20 条无 changeset commit,按内容归类:

  • 1 条 release commit(chore: release packages (#3248))
  • 3 条 dependabot bump
  • 15 条 docs / ci / scripts
  • 1 条 0e50440e8 fix(form): bind previous for field rules and stop resubmitting read-only fields (#3518) —— 26 个文件、5 个包、全部十个 locale pack,真实用户可见的表单行为变更

在数字 20 里,这一条和 chore(deps-dev): Bump postcss from 8.5.25 to 8.5.26 完全无从分辨。这就是「计数不可读、subject 可读」的全部理由。

3. 设计决定与理由

(a) 落点 = changeset 正文,不是 stderr。 #6175 刚把回落事实放在 stderr 的 accounting 行旁边,理由是「工具怎么读到文件」不是关于这次发布的事实。本段是相邻但不同的事实:「哪些 commit 的代码进了 console build 却没有任何上游声明」恰恰是发布记录该有的内容,读者是读发布记录的人而不是看这次运行的操作员。故落正文。self-test 里有一条断言专门钉这条边界(见 §5)。

(b) 措辞。 正文段首为:

In this console build, declared nowhere — objectui merged 20 commits in this range with no .changeset/*.md. The code is inside the pin above and ships here, but nothing upstream declared them, so they appear in no objectui CHANGELOG and in no entry above. Listed by subject rather than counted, because a count cannot tell a dependency bump from a form-behaviour change (objectstack#6174); the upstream gate that would prevent this is objectui#3387.

三点是刻意的:代码确实进了 build(不是「丢了」);没有任何上游声明,所以 objectui 自己的 CHANGELOG 里也没有;指向 objectui#3387 让读者知道根因闸门在哪。

(c) 行格式沿用 objectui-range.mjs --all- _(no changeset)_ … 词汇。 同一件事在两个产物里用同一套词汇,读者不必学两遍;这也正是「接进已有能力」而不是「另造一套」的含义。

(d) 不分类、不筛选。 chore: release packages 和那条 form fix 一起平铺列出。判断哪条「重要」等于对 subject 做推断 —— 正是 #4731 拆掉的那一步,而且会以同样方式失败:值得点名的那条挂着 fix(form),和旁边的 fix(ci) 一样不特别。工具只负责停止隐藏,判断交给读者。

(e) 规模护栏:点名清单与 releasing 清单各自独立套用同一个 max(默认 100)。 两个真实区间是 20 条和 33 条,默认下都完整列出、不触发截断。截断只作为病态区间的兜底存在,且必须响亮 —— 下面是 self-test 里 max: 2 / 总数 3 时的实测输出:

- …and 1 more commit with no changeset — this list is capped at 2, the range has 3 in total. Run `node scripts/objectui-range.mjs --from a1b2c3d4e5f6 --to f6e5d4c3b2a1 --all` for the complete list.

真实总数,并给出取全量的命令。选独立配额而非共享配额的理由:共享时第二个清单的长度取决于第一个清单有多长,而读者无从推断这层耦合 —— 一次长发布就能把点名段挤成零条,那正好是本 PR 要消灭的形态。

(f) 零条不打。 无漏声明 commit 时不产生任何新行。这里的静默是安全的:计数非零时 accounting 行同时也在说这件事,本段从不是该事实的唯一载体,所以「没有这一段」只可能是「本区间每条 commit 都声明了」,不可能是「这段没实现」。

(g) ⛔ 不改判据。 noChangesetCommits 原样渲染,不过滤、不重排、不新增谓词。判据、收录集、分类、计数、accounting 行一律未动 —— §4 的逐字节 diff 是证据。

4. 真实区间重放 before / after

改前改后各跑一次,两个区间:

区间 非合并 commit releasing 无 changeset 退出码 before/after 正文 diff
f995a452d2ca..7dfbeb704e1e(#6159 实测) 28 9 20 0 / 0 纯插入 23 行,零删除
f5bc4c78be76..f995a452d2ca(对照) 98 64 33 0 / 0 纯插入 36 行,零删除

diff -u0 对区间 A 只有一个 hunk 头 @@ -18,0 +19,23 @@ —— 纯插入,accounting 行与 9 条 releasing 清单逐字节不变,objectui range: 页脚仍在末尾。区间 B 同形(@@ -75,0 +76,36 @@,被删除行计数为 0)。两个区间的 stderr 逐字节相同(diff 无输出),即 #6175 的回落句与 accounting 行都未受影响。

新增段实测节选(区间 A):

**In this console build, declared nowhere** — objectui merged 20 commits in this range with no `.changeset/*.md`. …

- _(no changeset)_ chore: release packages (#3248) (objectui `7dfbeb704`)
- _(no changeset)_ chore(deps): Bump fumadocs-core from 16.13.0 to 16.14.0 (#3255) (objectui `ce12564fa`)
- _(no changeset)_ fix(form): bind `previous` for field rules and stop resubmitting read-only fields (#3518) (objectui `0e50440e8`)
…

issue 正文点名的那条,现在在平台发布记录里有名字了。

5. 反向验证:申报在先,实测在后

变异体:把点名段的生成条件短路(if (false && noChangesetCommits.length > 0)),即删空功能、其余不动。

事先申报:10 条转红(肯定式)、3 条保持绿(结构上不会红,已分别说明理由)。

实测结果:10 failure(s),与申报逐条一致。

转红的 10 条(肯定式,真覆盖):

断言 变异后
每条无 changeset commit 按 subject 点名且带 sha 行数 3 → 0
渲染行数与计数一致 rows=0 noChangeset=3
段首说明「进了 build、无处声明」 段首消失
点名是追加的,releasing 清单在前且未变 indexOf 为 -1,顺序断言失败
截断时响亮报出真实总数与取全量命令 行数 0 ≠ 2
截断藏掉最该点名的那条 第二子句(the range has 3 in total)失败
两个 cap 相互独立 点名行消失
「旧正文只能计数」的那条现已具名 子串消失
只点名无 changeset 类,release-nothing 不扫进来 计数 1 → 0
正文点名 / stderr 回落 两个事实各归其位(#6175 边界) 第一子句失败

保持绿的 3 条 —— 申报为结构上不会红,不得当作覆盖度:

  1. 「零漏声明 ⇒ 无新增行」(否定式) —— 功能被删空时同样「没有新增行」,故平凡成立、无法转红。它钉的是另一类回归(空清单时误打段落),因此保留。为防空绿,配了两条肯定式护栏:allDeclared.noChangeset === 0releasing.length === 2,确保 fixture 确实是零漏声明的场景、且确实产出了 digest —— 没有它们,「无新增行」也会被空区间或根本没构建出 body 的情况满足。护栏防的是 fixture 退化,能让这条在变异体下转红,故如实申报为否定式。
  2. 「accounting 行未动」 —— 变异体不影响 accounting,故绿。它是对照断言,钉的是「本 PR 没碰计数」,不是点名功能的覆盖。
  3. bump-objectui.sh 的 pin changeset 只收 feat|fix 且静默截断到 40 条 —— 破坏性 refactor! 进不了前端发布记录 #4731 那条被修复的断言(见下) —— 否定式,变异前后皆绿。

空绿自查(逐条过了一遍新增断言的每个子句):发现两条断言各含一个在变异体下平凡为真的否定子句 —— !named2.body.includes(SUBJECT_3518)!digest.body.includes('- _(no changeset)_ fix(ci): hand the cross-repo token')。两条都已经各自配了肯定式子句(includes('the range has 3 in total');filter(...).length === 1)承载判定,实测也确实随变异转红,故不是空绿;此处如实记录,是因为若单独保留那个否定子句就会是空绿。除此之外未发现同类。

#4731 一条既有断言的实现方式必须修复(不是改判据、不是改计数):

原断言 a fix(ci) commit with no changeset at all is NOT represented!digest.body.includes('budget FAIL') 实现。它的原意始终是「判据没有把它收进 releasing」,而实现方式是「整段 body 里找不到这个 subject」—— 当 body 开始有意点名无 changeset commit 之后,这个代理就不再表达原意了。改为在两层直接断言原意:!digest.releasing.some(...)(分类集合)+ 渲染行不匹配 releasing 行的形状(渲染层)。fixture 与其精确计数一字未动;另在 #6174 组补了正向的另一半:它现在确实出现在点名段里。

一并核对:cross-repo token 那条是 release-nothing(空 frontmatter,作者确实声明了),不属无 changeset 类,因此不会被点名 —— 依赖它的两条既有断言(#4731 组与端到端组)实测均未受影响。

6. 门禁(均在 git add 之后跑)

门禁 EXIT
pnpm check:objectui-changeset(digest 57 条 + objectui-range 自测) 0
pnpm exec eslint scripts/objectui-changeset-digest.mjs --no-inline-config 0
pnpm check:nul-bytes(自测 56 断言 + 扫描 6003 文件) 0
控制字节自扫(grep -naP 控制字节类) 1(无命中,clean)
node scripts/check-objectui-pin-fresh.mjs --self-test(下游消费者,Console Pin Freshness) 0

digest self-test 45 → 57(新增 12 条,独立成 #6174 组)。下游 check-objectui-pin-fresh.mjs 只消费 classifyRange,而 classifyRange 本 PR 一行未改。

7. 不在本 PR 里

本 PR 不发布任何包(纯 scripts/ 工具链),故走 skip-changeset 路线,不建 .changeset/ 文件。


Generated by Claude Code

)

console changeset 正文此前只把漏声明 changeset 的 commit 写成一个数字
(`omitted: N commits carrying no changeset`),具体是哪些 commit,发布记录
里读不出来。实测 pin bump 区间 `f995a452d2ca..7dfbeb704e1e`(#6159 / PR
#6173)的 20 条里,19 条是 release commit / dependabot / docs·ci·scripts,
另有 1 条是 objectui#3518 `0e50440e8 fix(form): bind previous for field
rules…` —— 26 个文件、5 个包、全部十个 locale pack 的用户可见表单行为变更。
在数字 20 里,它和 `Bump postcss` 无从分辨。

改动只在呈现层:把 `classifyRange` 已经产出的 `noChangesetCommits` 原样
渲染进正文,行格式沿用 `objectui-range.mjs --all` 的 `- _(no changeset)_ …`
词汇,使两个产物读起来是同一套。判据、收录集、分类、accounting 行一律未动
(实测两个真实区间的 diff 为纯插入,零删除)。

规模护栏:点名清单与 releasing 清单各自独立套用 `max`(默认 100),截断时
输出真实总数与取全量的命令,绝不静默截断(#4731)。零条时不打新行。
与 #6175 的 stderr 回落句是相邻但不同的事实——那句讲工具怎么读文件,这段
讲哪些 commit 进了 build 却没声明,故落点在正文。

self-test 新增 12 条独立成组的断言(45 → 57);#4731 一条既有断言的实现
方式(整体 body 子串)已不再表达其原意,改为在 releasing 集合与渲染行两层
断言,原意与精确计数均不变。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 3:05pm

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants