Skip to content

Commit 2951c0f

Browse files
os-steveclaude
andauthored
docs(skills): the retirement playbook prescribes a minor changeset, not the major a live gate refuses (#19446)
Fixes #19360 Clause-②: no ## The contradiction `.claude/skills/spec-property-retirement/SKILL.md` :291 — the **Changeset** item of the retirement checklist — told the executing agent to grade `@objectstack/spec` a `major` bump. That checklist is executed verbatim by every retirement dev, and the per-PR **Check Changeset** job (`.github/workflows/pr-automation.yml` :244, running `node scripts/check-changeset-no-major.mjs --base "$MERGE_BASE"` at :1075) refuses exactly that bump while the launch window stands. So the line reddened the author's own PR for doing what the instructions said — and then left them to decide, mid-round on an already delicate public-surface deletion, whether the playbook or the gate was authoritative. ## Reproduction, both legs measured in this worktree A throwaway `.changeset/repro-19360.md` was committed on this branch, the gate run against the branch point `23f1de078`, the exit captured before any pipe, and the tree restored (`git diff HEAD` empty, `git status --porcelain` empty) after each leg. **Leg 1 — the checklist line copied verbatim (`'@objectstack/spec': major`)** :: exit **1** ``` ⛔ This PR introduces changeset(s) that declare a `major` bump. .changeset/repro-19360.md - @objectstack/spec: major Every publishable package is in the Changesets `fixed` (lockstep) group, so a single `major` promotes the ENTIRE monorepo to a new major version. During the launch window ship breaking changes as `minor` instead (they do not burn a major version number). ``` **Leg 2 — the prescription this PR lands (`'@objectstack/spec': minor`)** :: exit **0** ``` ✓ This diff introduces no `major` bump. ``` The same file's own header states the rule the fix restores (:47-:51): "During the launch window we ship breaking changes as `minor` (pre-1.0 semantics …). This guard makes that convention enforceable instead of tribal". `.changeset/pre.json` is absent on `main`, so the RC exemption is not standing the guard down — the `enforce` branch is live. ## Before / after **Before** (:291-:296, 6 lines): ``` - [ ] **Changeset** —— `@objectstack/spec` 用 `major`。AGENTS.md:breaking changeset 必须带 FROM → TO 映射与一行修复;它作为 npm 包里的 `CHANGELOG.md` 发出,是升级中的 agent 撞上墓碑报错后 grep 的东西。 样板是任一条活着的 `.changeset/*-retired.md` —— 抄它的 "The retirement kit:" 段。⚠ 并带上 ADR-0087 处置标记(AGENTS.md 的 changeset 一节; `pnpm check:adr-0087-registration` 把门)—— 退役正是它点名的那一类。 ``` **After** (:291-:296, 6 lines): ``` - [ ] **Changeset** —— `@objectstack/spec` 用 `minor`,⛔ 不用 `major`:逐 PR 的 `Check Changeset` 跑 `scripts/check-changeset-no-major.mjs`,发射窗口期内拒收 `major`,照抄即红。破坏性语义由 BREAKING banner 承载 —— AGENTS.md:breaking changeset 必须带 FROM → TO 映射与一行修复;它作为 npm 包里 的 `CHANGELOG.md` 发出,是升级中的 agent 撞上墓碑报错后 grep 的东西。样板抄任一条活着的 `.changeset/*-retired.md` 的 "The retirement kit:" 段。⚠ 并带上 ADR-0087 处置标记(AGENTS.md 的 changeset 一节;`pnpm check:adr-0087-registration` 把门)—— 退役正是它点名的那一类。 ``` Everything the item already carried survives — the `AGENTS.md:breaking` pointer, the FROM → TO mapping requirement, the `CHANGELOG.md` rationale, the `.changeset/*-retired.md` template pointer and the ADR-0087 disposition marker. What is added is the level (`minor`), the prohibition (`⛔ 不用 major`), the gate and job that enforce it, and the sentence that the breaking semantics ride the BREAKING banner rather than the bump level. ## Why the level is stated in the playbook at all, on the four axes It would have been cheaper to delete the level and defer it to each card's ruling. That is the option this PR rejects, and the four axes say why. - **实际业务需求** — measured, not assumed: the one retirement that ran against this contradiction is **#18623**, whose ruling (comment `5725503716`, maintainer 「同意」, 2026-09-18) states the level `minor` explicitly. That card therefore needed no stop. A retirement whose ruling is silent on the level has nothing to break the tie, and the checklist is the only artifact it reads. A blank slot in an executed checklist is not neutral — it is a prompt to guess. - **项目长远合理性** — the split the fix writes down is already the repo's declared architecture, not a new convention: `pr-automation.yml`'s "WHICH LEVEL" prose says verbatim "During the launch window `major` stays refused by `check-changeset-no-major` and breaking-ness is carried by the BREAKING banner plus the ADR-0087 disposition, not by the level" (maintainer ruling 2026-09-04, decision batch #35). Naming the gate in the playbook makes the playbook derive from that rule instead of racing it, so the line goes stale only when the rule does — and the gate's own header carries the GA end condition that will retire both together. No workaround: the consumer-side alternative (teach the dev to add `allow-major`) would route around a guard whose error message says the label means "a whole-stack major release is genuinely intended", which is false for a property retirement. - **防 AI 写代码犯错** — this is the axis that decides it. The playbook is a checklist an agent executes verbatim; the failure mode is 「示例照抄即失败」. A declared step that the runtime refuses is exactly the shape the house rules forbid — 声明即强制, never let an agent declare what the runtime will not honour. Deferring the level would replace a wrong instruction with an absent one, which is the softer version of the same defect: the agent still has to guess, and the guess is now invisible in review. Stating the level AND naming the refusing gate makes the instruction self-checking — the next author can run the named script and see the verdict for themselves rather than trusting the line. - **创业阶段不扩散需求** — net 0 lines, one checklist item, one file. No new gate, no new label, no allowlist, no transition window and no second spelling kept alive: the `major` prescription is replaced outright rather than deprecated beside its replacement. `scripts/check-changeset-no-major.mjs` is untouched — the launch-window guard is the thing being obeyed, not the thing being changed. ## Budget | reading | before | after | |:--|--:|--:| | `.claude/skills/spec-property-retirement/SKILL.md` lines | 337 | 337 | | ceiling in `scripts/pm/check-skill-line-ratchet.mjs` | 337 | 337 | | widest line in the touched item (bytes, cap 120) | 98 | 120 | Net 0. The item is repacked in place at 6 lines; every rewrapped line was produced by the ratchet's own `wrapLine`/`breakLegal` atoms, so no break lands at a Han + ASCII-punctuation junction. `node scripts/pm/check-skill-line-ratchet.mjs` :: exit 0. ## Gates `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran` at head `ee26ec6`: **18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN** — a derived zero, every family recorded an exit code and none of them is 3. Route-named additions beyond the derived union, all exit 0: `check-skill-line-ratchet.mjs`, `check-skill-id-lint.mjs`, `check-governed-prose.mjs`, `check-skills-token-ratchet.mjs`, `check:pm-settings-deny-roster`, and the declared cross-package consumer `packages/spec/src/shared/retired-key-migrate-sentence.test.ts` (14 tests passed), which reads this playbook as a scanned corpus. `pnpm lint` narrowed to a measurement rather than skipped: eslint's covered population is `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` read from `eslint.config.mjs` :971 — a `.md` path is not in it; `--format json` over the one changed file returns 1 entry, `errorCount` 0, with the message "File ignored because no matching configuration was supplied"; and the config declares no `parserOptions.project` (type-aware linting deliberately off, :328), so this diff cannot move the verdict on any untouched file. The intersection of this diff with eslint's population is empty. ## Acceptance notes - Swept for the same defect elsewhere and found none: `grep` for a `major` changeset prescription across `AGENTS.md`, all of `.claude/**` and the published `skills/**` catalog returns zero hits outside the line this PR fixes. The playbook was the only site. - `scripts/check-changeset-no-major.mjs` is deliberately untouched: the launch-window guard is correct and carries its own GA end condition. This PR moves the instruction to the gate, not the gate to the instruction. ## 维护者速读(草稿) **改了什么** —— 退役 playbook 的 Changeset 那一条,原来写「`@objectstack/spec` 用 `major`」,现在 写「用 `minor`,不用 `major`」,并点名是哪个门禁在拒收(`Check Changeset` job / `scripts/check-changeset-no-major.mjs`)。一个文件、一条清单项、净增 0 行。 **为什么改** —— 这条清单是 agent 照着一条条执行的,不是读着理解的。照抄这一行写出来的 changeset 会被逐 PR 的门禁当场拒收,PR 变红。更贵的是红之后:agent 得在一张本来就很敏感的「删除公开键」 卡上,临时判断到底是 playbook 算数还是门禁算数。上一次撞上它的是 #18623,那张卡的裁决里恰好写了 `minor`,所以没停;下一张裁决里没写级别的退役卡就没有东西能打破平局。 **风险与代价(含回滚)** —— 风险面就是这一条清单项的措辞,没有代码、没有门禁、没有 schema 改动。 本地实测两条腿:写 `major` 的 changeset 门禁退出码 1,写 `minor` 退出码 0。回滚 = revert 这一个 commit,退役流程回到今天的状态(即:回到会红的那一版)。发射窗口在 GA 结束时,那条门禁自己的 文件头写着终止条件;到那天 `major` 重新合法,这一行要跟着门禁一起改——所以这里点名了门禁, 让下一位读者顺着指针找得到。 **席位意见** —— **你要做的** —— 确认一句话:发射窗口期内,退役的 changeset 用 `minor` + BREAKING banner 承载破坏性 语义,这与 `pr-automation.yml` 里 2026-09-04 的裁决一致。如果同意,这张卡按 Tier S 走席位 contract review 落地,不需要你再做别的。 --- _Generated by [Claude Code](https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 57ceb9d commit 2951c0f

1 file changed

Lines changed: 6 additions & 6 deletions

File tree

  • .claude/skills/spec-property-retirement

‎.claude/skills/spec-property-retirement/SKILL.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,12 +288,12 @@ conversion 是消费者跟的。两个都要写。
288288
Guardrails)。release notes 在发布时从 changesets + D2/D3 registry 集中编写,你的
289289
changeset(下一项)才是通往它们的输入。`content/docs/**` 的其余部分:先 grep 键
290290
名,再读周边文件 —— 被删的键会藏在离参考表三节远的一个 `defineFlow` 示例里。
291-
- [ ] **Changeset** —— `@objectstack/spec` 用 `major`。AGENTS.md:breaking
292-
changeset 必须带 FROM → TO 映射与一行修复;它作为 npm 包里的 `CHANGELOG.md`
293-
发出,是升级中的 agent 撞上墓碑报错后 grep 的东西。
294-
样板是任一条活着的 `.changeset/*-retired.md` —— 抄它的 "The retirement
295-
kit:" 段。⚠ 并带上 ADR-0087 处置标记(AGENTS.md 的 changeset 一节;
296-
`pnpm check:adr-0087-registration` 把门)—— 退役正是它点名的那一类。
291+
- [ ] **Changeset** —— `@objectstack/spec` 用 `minor`,⛔ 不用 `major`:逐 PR 的 `Check Changeset` 跑
292+
`scripts/check-changeset-no-major.mjs`,发射窗口期内拒收 `major`,照抄即红。破坏性语义由 BREAKING
293+
banner 承载 —— AGENTS.md:breaking changeset 必须带 FROM → TO 映射与一行修复;它作为 npm 包里
294+
的 `CHANGELOG.md` 发出,是升级中的 agent 撞上墓碑报错后 grep 的东西。样板抄任一条活着的
295+
`.changeset/*-retired.md` 的 "The retirement kit:" 段。⚠ 并带上 ADR-0087 处置标记(AGENTS.md 的
296+
changeset 一节;`pnpm check:adr-0087-registration` 把门)—— 退役正是它点名的那一类。
297297
- [ ] **`check:generated` 明确不跑的源码审计 —— 整组跑,永不单点。** 它的输出会点名
298298
它们;陷阱是跑了五个漏了第六个。咬退役的是 `check:variant-docs`:删掉一个
299299
discriminated union 会孤儿化它的 variant/doc-ledger 条目(实测:条目比它的 union

0 commit comments

Comments
 (0)