Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .agents/skills/mcpp-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,39 @@ git commit -am "ci: workspace mcpp bootstrap pin -> $NEW_VERSION (released, mirr
**顺序不能反**:pin 一旦领先于"索引里真实存在的版本",每个 CI job 的 bootstrap 都会
`package 'mcpp@X.Y.Z' not found`。这就是 bump PR 里不许碰这两处的原因。

**索引 PR 不要手写**:`publish-ecosystem` 已经开好了 bot PR —— 分支
`bump/mcpp-<ver>`,提交者 `xlings-ci <ci@xlings.dev>`,diff 恒为 **+22/-3**。
承重的是那三行删除:生成器对每个平台表**替换** `["latest"]` 那一行,而不是新增一行。
收尾时只要合入它。

2026-09-05 手写了一个平行的索引 PR(`xim-pkgindex#764`,分支 `feat/mcpp-<ver>`,
+22/**-0**),两个缺陷一起产生:

- 追加而非替换,于是每个平台表里有两个 `["latest"]` 键。Lua 表构造器以最后一次赋值
为准,`latest` 静默解析回上一个发布。精确版本条目仍在且正确,**所以 mcpp 自己的
CI(精确 pin)完全看不见**;只有不带版本的 `xlings install mcpp` 会拿到旧二进制。
- 它先落地,bot 那份正确的 PR 变成冲突。解冲突时取 **bot 分支**那一侧的
`pkgs/m/mcpp.lua` —— 它与手写后的 main 只差那几行陈旧的 `["latest"]`。

**索引传播有滞后**:索引 artifact 发布后,`latest` tag 上的指针文件在 GitHub
资产 CDN 上可能还要几分钟才更新。紧接着跑的 CI 可能仍拿到旧索引并报
`package 'mcpp@X.Y.Z' not found` —— 这不是 release 坏了,等指针稳定后重跑即可。

两个判据(都不是"文件里有这个版本字符串"):

```bash
# 1) 索引 main 上,latest 解析到新版本:恰好三行,每平台一行,都指向新版本
curl -fsSL https://raw.githubusercontent.com/openxlings/xim-pkgindex/main/pkgs/m/mcpp.lua \
| grep -n '\["latest"\]'

# 2) 推 pin 之前,指针已经跟上索引 main 的短 SHA
curl -fsSL https://github.com/xlings-res/xim-index/releases/download/latest/xim-index-latest.json \
| grep -E '"(index_version|source_commit)"'
```

滞后的量级实测过:2026-09-05 那次,指针资产 17:20:32 就替换完了,CI 17:21:23 才去解析
(晚 51 秒),拿到的仍是上一份 artifact。所以"索引 PR 合入了"不足以放行 pin,要看指针。

### 下游分发渠道(都是自动的,只需核验)

两条渠道都挂在 `release` workflow 的 `workflow_run: completed` 上,不需要人工推:
Expand All @@ -256,6 +285,8 @@ gh workflow run bump-formula.yml -R mcpp-community/homebrew-mcpp
|------|------|------|
| `mcpp X.Y.Z-1` 但 tag 是 `vX.Y.Z` | `fingerprint.cppm` 版本未更新 | 更新 `MCPP_VERSION`,重新打 tag |
| bump PR 里**所有** CI job 都红在 bootstrap,报 `package 'mcpp@X.Y.Z' not found` | 把 `.xlings.json` bootstrap pin 一起 bump 了,CI 去装一个还没发布的版本 | 把 `.xlings.json` 回退到上一个已发布版本;不要修改运行时推导的 `MCPP_PIN` |
| main 上**所有** workflow 都红在 bootstrap,报 `package 'mcpp@X.Y.Z' not found`,而该版本确实已发布并已合入索引 | pin 推得比索引指针稳定早;客户端读 CDN artifact 不读 git 树 | 等 `xim-index-latest.json` 的 `index_version` 等于索引 main 短 SHA 后重跑;不要改 pin |
| `xlings install mcpp`(不带 `@版本`)装到**上一个**版本,而索引文件里明明有新版本 | 同一平台表里两个 `["latest"]` 键(手写索引 PR 追加而非替换),Lua 后写的赢 | 删掉陈旧的那行,使每个平台表只剩一个 `["latest"]`。**精确 pin 仍可用,所以 mcpp 自己的 CI 不会报这个** |
| 自查 `--version` 显示旧版本,但源码已改 | `target/<triple>/<指纹>/` 的指纹随版本变,`ls \| head -1` 取到了上一次构建的目录 | 用 `ls -dt … \| head -1` 取最新构建 |
| Smoke test 输出旧版本 | CI 缓存了旧的 sandbox/target | 删除 GitHub Actions cache 后重跑 |
| e2e `01_help_and_version.sh` 挂 | 只改了 `mcpp.toml` 没改 `fingerprint.cppm`(它把两者交叉比对) | 同步两处正在构建的版本;注意这个 e2e 只在部分分片里跑,可能表现为"只有某个平台红" |
Expand Down
48 changes: 48 additions & 0 deletions docs/09-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,36 @@ Two steps are **not** automated:
the released version is downloadable but not installable via `xlings install`.
- **bumping `.xlings.json`** — see §4.

The bump PR is machine-authored: branch `bump/mcpp-<version>`, commits by
`xlings-ci <ci@xlings.dev>`, and a diff that is invariably +22/-3. The three
deletions are the load-bearing part — the generator *replaces* each platform
table's `["latest"]` line rather than adding one. A hand-written index PR is not
an equivalent shortcut, and filing one alongside the bot's carries two distinct
costs, both incurred on 2026-09-05 by `xim-pkgindex#764`:

- The hand-written diff appended `["latest"] = { ref = "<new>" }` above the
existing line instead of replacing it, leaving two `["latest"]` keys in every
platform table. A Lua table constructor takes the last assignment, so `latest`
resolved back to the previous release. The exact-version entry was present and
correct, so every pinned consumer stayed green — mcpp's own CI pins exactly and
never saw it. Only `xlings install mcpp` without a version served the stale
binary.
- Landing ahead of the bot's PR left that PR conflicting. The resolution is to
take the bot branch's side of `pkgs/m/mcpp.lua`; it differs from the
hand-written `main` only by the stale `["latest"]` lines.

A merged bump PR is therefore confirmed by what `latest` resolves to, which the
presence of the version string elsewhere in the file does not answer:

```bash
curl -fsSL https://raw.githubusercontent.com/openxlings/xim-pkgindex/main/pkgs/m/mcpp.lua \
| grep -n '\["latest"\]'
```

Exactly three lines come back, one per platform table, and each names the version
just released. A fourth line, or one naming the previous release, is the
duplicate-key defect above.

## 3. Verifying a release

The mirror script verifies its own uploads, but the checks worth doing by hand
Expand Down Expand Up @@ -169,6 +199,22 @@ is not yet installable. Bump it only after the release is published, mirrored,
`check_version_pins.sh` enforces the weaker "never newer than the version being
built"; the index condition requires human judgement.

"Merged into xim-pkgindex" is necessary but not sufficient: clients read a CDN
artifact, not the git tree. On 2026-09-05 the pin commit reached `main` 19
seconds before the index PR merged, and the CI jobs it triggered resolved the
index 51 seconds *after* the new pointer asset had been replaced — and still
received the previous artifact. All nine workflows failed at bootstrap, none of
them having compiled a line. The observable condition is the pointer itself:

```bash
curl -fsSL https://github.com/xlings-res/xim-index/releases/download/latest/xim-index-latest.json \
| grep -E '"(index_version|source_commit)"'
```

`index_version` must equal the short SHA of xim-pkgindex `main` before the pin is
pushed. Nothing enforces this, and in the job log the resulting failure is
indistinguishable from a genuinely wrong version name.

## 5. `MCPP_PIN` is derived, and why that matters

`ci-fresh-install.yml` used to carry a second hand-edited copy of the pin. It was
Expand Down Expand Up @@ -226,6 +272,8 @@ would again let the index guard and the installed version drift apart.
[ ] downloaded mcpp-release.json regenerates byte-identically from public assets
[ ] mirrors serve all four platforms on BOTH hosts, sha256 recomputed
[ ] merge the xim-pkgindex bump PR
[ ] index `latest` resolves to the new version in all three platform tables
[ ] xim-index-latest.json's `index_version` equals xim-pkgindex `main`
[ ] clean-room XLINGS_HOME: xlings install mcpp@<version> succeeds
[ ] (optional) bump .xlings.json — only now, never earlier
```
38 changes: 38 additions & 0 deletions docs/zh/09-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,29 @@ workflow 随后会再次下载公开 release、重新生成 manifest,并要求
可以下载,但无法通过 `xlings install` 安装。
- **bump `.xlings.json`** —— 见 §4。

bump PR 是机器生成的:分支 `bump/mcpp-<version>`,提交者 `xlings-ci
<ci@xlings.dev>`,diff 恒为 +22/-3。承重的是那三行删除 —— 生成器对每个平台表是
**替换** `["latest"]` 那一行,而不是新增一行。手写一个索引 PR 并不是等价的捷径;
在 bot 的 PR 之外另开一个,代价有两份,2026-09-05 的 `xim-pkgindex#764` 两份都付了:

- 手写的 diff 把 `["latest"] = { ref = "<新版>" }` **追加**在原有那行上面而不是替换
它,于是每个平台表里留下两个 `["latest"]` 键。Lua 的表构造器以最后一次赋值为准,
`latest` 因此解析回上一个发布。精确版本条目存在且正确,所以每个按精确版本 pin 的
消费者都是绿的 —— mcpp 自己的 CI 正是精确 pin,完全看不见这件事。只有不带版本的
`xlings install mcpp` 会拿到过期的二进制。
- 它先于 bot 的 PR 落地,使那个 PR 变成冲突。解法是取 bot 分支那一侧的
`pkgs/m/mcpp.lua`;它与手写后的 `main` 只差那几行陈旧的 `["latest"]`。

因此,bump PR 合入的判据是 `latest` **解析成什么**,而不是文件里别处出现了版本字符串:

```bash
curl -fsSL https://raw.githubusercontent.com/openxlings/xim-pkgindex/main/pkgs/m/mcpp.lua \
| grep -n '\["latest"\]'
```

恰好回来三行,每个平台表一行,且都指向刚发布的版本。出现第四行,或某一行指向上一个
发布,就是上述的重复键缺陷。

## 3. 验证一次发布

镜像脚本会自校验上传,但真正值得手工做的是那些**不信任边车文件**的检查:
Expand Down Expand Up @@ -153,6 +176,19 @@ $(find "$XLINGS_HOME" -name mcpp -type f -path '*/bin/*' | head -1) --version
`package 'mcpp@<unreleased>' not found` 失败。待其语法问题修复后,
`check_version_pins.sh` 能卡住较弱的「不得新于正在构建的版本」;索引那个条件需要人工把关。

「已合入 xim-pkgindex」是必要条件而非充分条件:客户端读的是 CDN 上的 artifact,不是
git 树。2026-09-05 那次,pin 的 commit 比索引 PR 的合入早到 `main` 19 秒,而它触发的
CI 作业在新指针资产被替换**之后** 51 秒才去解析索引 —— 拿到的仍然是上一份 artifact。
九条 workflow 全部死在 bootstrap,没有一条编译过一行。可观测的条件是指针本身:

```bash
curl -fsSL https://github.com/xlings-res/xim-index/releases/download/latest/xim-index-latest.json \
| grep -E '"(index_version|source_commit)"'
```

推 pin 之前,`index_version` 必须等于 xim-pkgindex `main` 的短 SHA。没有任何东西强制
这一点,而在作业日志里,由此产生的失败与「版本名真的写错了」无法区分。

## 5. `MCPP_PIN` 改为推导,以及这为什么重要

`ci-fresh-install.yml` 过去带着 pin 的第二份手工副本。它们从来就不是一回事:
Expand Down Expand Up @@ -191,6 +227,8 @@ $(find "$XLINGS_HOME" -name mcpp -type f -path '*/bin/*' | head -1) --version
[ ] 下载的 mcpp-release.json 能从公开资产逐字节重新生成
[ ] 双端都服务四个平台,sha256 重新算过
[ ] 合并 xim-pkgindex 的 bump PR
[ ] 索引的 `latest` 在三个平台表里都解析到新版本
[ ] xim-index-latest.json 的 `index_version` 等于 xim-pkgindex `main`
[ ] clean-room XLINGS_HOME:xlings install mcpp@<version> 成功
[ ] (可选)bump .xlings.json —— 只在此刻,绝不提前
```
Loading