Skip to content

Commit 6dfc7fa

Browse files
feat(scripts): --fix derives skill body versions; release guard and token ratchet follow (#31745)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbPjpDtuAfMiZPGY3g5SZw34)_ Fixes #31115 (item 3, the last; items 1 and 2 landed in #31432). Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么:** `check:skill-compatibility` 现在也检查技能正文里写死的版本号(如 `bootstrap.md` 起步 package.json 里的 5 个 `"@objectstack/…": "18.3.0"`),发版时由 `--fix` 自动改写;新导出 `bodyPinnedPaths()` 列出会被改写的文件,release.yml 的发版后守卫据此放行;token 棘轮不再计价这些由脚本写入的版本值。 - **为什么改:** 正文版本号以前靠手改,已经落后过一次;按裁决 B(「31115 … 同意」)把它变成派生值,并堵住 `18.9`→`18.10` 这类变宽导致 Version Packages PR 被棘轮打红的坑(`bootstrap.md` 余量只有 1)。 - **风险与回滚:** 只改两个检查脚本和 release.yml 的一个守卫步骤,不动运行时代码和技能文本;在本地模拟了一次升到 `18.10.0-next.12` 的发版:`--fix` 改写 12 个文件,守卫零意外路径,棘轮保持绿。回滚即 revert 本 PR。 - **先看:** `.github/workflows/release.yml`(新增 `BODY_LIST`,第六个过滤器,空列表合法);`scripts/check-skills-token-ratchet.mjs`(`bodyPinValues` 与 `BODY_PIN_REPIN`,`bootstrap.md` 行 1351→1343);`scripts/check-skill-compatibility-version.mjs`(`bodyPinnedPaths`、`applyFix`)。 - **两分钟验证:** 运行 `pnpm check:skill-compatibility` 和 `node scripts/check-skills-token-ratchet.mjs --self-test`,应分别看到「6 body version(s) in 71 skill markdown file(s)」和「104 cases pass」。 - **需要你做的:** 这是受管面 PR(`.github/workflows/release.yml`),需要一位受管审批人批准(APPROVED review)。 ## What changes - **`scripts/check-skill-compatibility-version.mjs`.** - The check now judges every body version under `skills/**`. A version must equal its workspace package's version, or sit in a sentence that reads as history (`since`, `removed in`, `until`, …). An install pin is always judged. - `--fix` rewrites body versions in the same pass as the frontmatter. `applyFix` writes each file once, and a range (`^`/`~`) is kept. - New export `bodyPinnedPaths()`: the files whose body the writer owns. The writer is asked with a probe value, so a file stays listed after the pass made it current. A history-only file is never listed. A moved skills directory throws; a catalog with no pins answers `[]`. - The reader, the history rule and `deriveBodyVersions` come from the first item 3 commit, now called. - **`.github/workflows/release.yml`, "Validate the post-version tree".** - `BODY_LIST` is resolved from `bodyPinnedPaths()` and applied as a sixth whole-line `grep -vxF -f` filter. - Unlike the four lists above it, an empty list is legitimate and admits nothing; a failed resolve still fails the step. - The content half already runs `check:skill-compatibility`, which now judges the bodies. - **`scripts/check-skills-token-ratchet.mjs`: the second exclusion.** - Body pin VALUES the writer rewrites are not priced. `bodyPinValues()` reads them off `deriveBodyVersions` with a probe, so there is no second parser. - The package name, quotes, range, and every history version stay priced. - `pricedTokens` takes `bodyPinned`, and `run()` reads `bodyPinnedPaths()`. - `bootstrap.md`'s row is re-pinned 1351 → 1343, the same 8 its count fell (five `18.3.0` values are 30 bytes, 5398 → 5368), so its headroom stays 1. This is recorded as `BODY_PIN_REPIN`. - The header gains the ruling and a hand audit (`grep | sed | tr | wc`) that the self-test reproduces on the live file. ## Why The maintainer ruled B on the item 3 decision (「31115 … 同意」, 2026-11-09; triage record 6730018726). `--fix` rewrites the install-shape body pins at version time; the written set is declared to the release guard; and the ratchet stops pricing writer-rewritten body pin values, generalising the frontmatter stamp-line rule. Without the third part, a width change at release reds `bootstrap.md` on the Version Packages PR. ## Tests <details><summary>Self-tests, scans and a simulated release</summary> - `pnpm check:skill-compatibility`: the self-test passes 22 check cases and 22 `--fix` derivation cases (floor 30 → 44). The new cases: the written set equals `stampedSkillPaths()` then `bodyPinnedPaths()` on an all-stale fixture; a history-only file is neither listed nor written; the guard's `grep -vxF -f` admits a body write only through the body list, and an empty list admits nothing; the export throws on a moved tree and answers `[]` with no pins; on the live tree the writer's list equals the reader's. The scan reads "6 body version(s) in 71 skill markdown file(s)", and `--fix` on main is a no-op. - `node scripts/check-skills-token-ratchet.mjs --self-test`: 104 cases pass (floor 97 → 104). The new cases: exactly the owned value bytes are unpriced, never a range, a name or a history version; any pin width from `17.7.0` to `18.10.0-next.12` moves no count; one authored byte over a zero-headroom row is red at every width; on the live file the writer's bytes equal the reader's; the header's hand audit reproduces the live count; and the re-pin record. The scan is green, with `bootstrap.md` at 1342 / 1343. - **Simulated release** (restored afterwards). The five pinned packages were set to `18.10.0-next.12` and `--fix` was run. - It rewrote 11 SKILL.md stamps and the five `bootstrap.md` pins (12 files), and the check stayed green. - With the body list, the guard filters left 0 unexpected paths. Without it, `bootstrap.md` was unexpected. - The ratchet stayed green at 1342 / 1343 (75 unpriced bytes). Priced whole, the file would read 1361 against the old 1351. </details> ## Acceptance notes - **Card item 3:** body versions are judged and derived. The `--fix` writes are declared to the release guard, and the ratchet does not price them, as ruled. - **Files outside the original claim surface, by ruling:** `.github/workflows/release.yml` and `scripts/check-skills-token-ratchet.mjs`. #31335 was already on main when this branch was cut, so no merge of main was needed. - **`skills/**` readings:** no file under `skills/**` changes. `bootstrap.md` stays at 190 → 190 lines, and the bundle (all SKILL.md) at 4592 → 4592 lines. - **Not judged:** a version that names a package the workspace lacks is listed by `bodyPinnedPaths()` as writable. The check reds it, so no green tree carries one. ## Verification - Head `1415775e` (merge base `f2abd4767`). - `node scripts/pm/dispatch-gates.mjs --ran`: 55 derived, 55 run, 0 NOT-MEASURED, 0 UNRUN, and all 55 exit 0 at this head. `--tier`: no path-derived mandate, so this was built on the default tier. - Left to this PR's check-runs: the artifact-roster and wide-population families, the path-scheduled CI jobs, the type-check lanes and the always-runs tail that dispatch-gates prints outside the derived total. The release lane itself runs only on a Version Packages pass; the simulated release under Tests stands in for it. - Deviations: none from the ruling. The branch was restarted from main after #31432 merged, and the held item 3 commit was re-applied from its patch, so history was not rewritten. --- _Generated by [Claude Code](https://claude.ai/code/session_01QsR2jU4PLtyyimMz72z285)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1f30be1 commit 6dfc7fa

3 files changed

Lines changed: 626 additions & 46 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -736,15 +736,31 @@ jobs:
736736
exit 1
737737
fi
738738
739-
# Five filters over the reviewed surface: the fixed
740-
# release paths by pattern, then the four declared lists by WHOLE-LINE
739+
# The skill BODIES the same `--fix` rewrites (install pins, #31115): resolved
740+
# from `bodyPinnedPaths()`, which asks that writer which files carry a
741+
# version it owns, and judged by the same `check:skill-compatibility`.
742+
# Unlike the lists above, EMPTY is a real answer here (a catalog with no
743+
# body pins), and `grep -vxF -f` over an empty list admits nothing. A moved
744+
# skills directory still fails: the export THROWS, and the resolve exits
745+
# non-zero.
746+
BODY_LIST="${RUNNER_TEMP}/post-version-skill-body-surfaces.txt"
747+
if ! node --input-type=module \
748+
-e 'import { bodyPinnedPaths } from "./scripts/check-skill-compatibility-version.mjs"; for (const p of bodyPinnedPaths()) console.log(p);' \
749+
> "${BODY_LIST}"; then
750+
echo "::error::could not resolve bodyPinnedPaths() from scripts/check-skill-compatibility-version.mjs, so the skill-body half of the post-version surface is unknown. Refusing to call this tree validated."
751+
exit 1
752+
fi
753+
754+
# Six filters over the reviewed surface: the fixed
755+
# release paths by pattern, then the five declared lists by WHOLE-LINE
741756
# EXACT match, so no derived filter can accept a path its declaration
742757
# does not name and none needs regex-escaping.
743758
UNEXPECTED="$(grep -vE '(^|/)package\.json$|(^|/)CHANGELOG\.md$|^\.changeset/|^packages/spec/src/kernel/protocol-version\.ts$' "${MOVED_FILE}" \
744759
| grep -vxF -f "${TEMPLATE_LIST}" \
745760
| grep -vxF -f "${SURFACE_LIST}" \
746761
| grep -vxF -f "${INDEX_LIST}" \
747-
| grep -vxF -f "${SKILL_LIST}" || true)"
762+
| grep -vxF -f "${SKILL_LIST}" \
763+
| grep -vxF -f "${BODY_LIST}" || true)"
748764
if [ -n "${UNEXPECTED}" ]; then
749765
printf '%s\n' "${UNEXPECTED}" | sed 's/^/::error:: unexpected: /'
750766
echo "::error::the version pass wrote outside the reviewed post-version surface (paths above). This is the treadmill guard: a new version-time output must arrive together with the gate that judges it. Add the surface to the declaration its rewriter reads, and its gate to the content half below — deliberately, in one reviewed diff."

0 commit comments

Comments
 (0)