Repository navigation
docs(agents): a Documentation Guardrails row for packages/*/CHANGELOG.md - #17078
Conversation
…ls row The table said nothing about the one generated, consumer-shipped documentation file that is neither a `.changeset/` input nor a `content/docs/releases/` page, so two cards prescribed a correction route that a release had already closed. The new row states three things: a code PR never edits it (it is compiled by `changeset version` and ships inside the npm tarball); the PR's input is its changeset, on a hard, unwatched deadline that the consuming release closes; and a factual error in a released entry is amended in place, in a dedicated docs-only PR, rather than corrected by an erratum in a later entry — the reader is an upgrading agent grepping the tombstoned symbol, and it lands on the old entry, so a correction anywhere else is one it never reaches. Paid within the file, per the shrink-only line ratchet (1068/1068, headroom 0): the two example sentences under the `packages/spec` regeneration table restated that table's own first two rows, three lines above them, and the sentence that survives tells you not to hand-match those rows in the first place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd
…ents-changelog-guardrail-row
维护者速读 — PR #17078(#16849,skills 席终稿,2026-09-09T06:52Z)改了什么 — 为什么改 — #15058 与 #15026 两张卡都写着「改一行 pending changeset」,守则允许;结果发版把 changeset 消费掉了,错句进了发运的 CHANGELOG,而表格里没有一行说这个文件该怎么办——分诊按更严读法处理了那两张卡并立了本卡。 风险与代价(含回滚) — 纯指令文本;回滚 = revert 本 PR。真正的取舍是 A(就地修正历史条目)vs B(后续勘误,保留发运原文):dev 与本席都选 A——唯一实测的读者是升级中 grep 墓碑符号的 agent,它只会落到那一条旧条目,勘误放在别处它永远读不到;「发运了什么」的记录由 npm 不可变 tarball 与 git 历史承担。B 的代价是真实的(main 上的文件不再逐字节等于发运物),但它保护的记录另有家。 席位意见 — 接受。本席在分支头上复核:棘轮 1068/1068、最宽行 768 未动(新行 752 B);删掉的两句在同节表格里各有家;id-lint 干净;控制字节 0;受管判定 exit 3; 你要做的 — 合并;或由 os-zhuang / hotlong 批准,本席随即入队。只需答一字:A(就地修正,已落地)还是 B(勘误)——选 B 就改那一格。 Generated by Claude Code |
Fixes #16849
The Documentation Guardrails table had four rows and none of them covered the one
generated, consumer-shipped documentation file that is neither a
.changeset/input nor acontent/docs/releases/page. This adds the fifth row, directly under thecontent/docs/releases/row whose disposition it takes.The row
751 bytes. The widest-row pin is 768 and is unmoved: the widest row in the file is still
the
translationsrow at 768 bytes (line 681 after this change).It answers the card's three questions:
it. It is compiled by
changeset versionand it ships inside the npm tarball.dedicated docs-only PR. Not an erratum in a later entry. Reasoning below; this is the one
thing on this PR that is a judgement call rather than a transcription.
The release that consumes it deletes the input and publishes the sentence, and nothing
warns anybody that the window has closed. That deadline is the whole reason finding(changeset): the pending field-rows/option-description changeset still says the canonical field-level spelling is depends_on — false for this package, and it is release-notes input #15058 and
finding(changeset): the pending changeset still tells authors the lint accepts the canonical ListView spelling — the step-1 tightening makes every clause of that sentence false #15026 now prescribe a route that no longer exists.
The decision the card asked me to make and not leave open
A — amend the historical entry in a dedicated docs-only PR (chosen) vs
B — add an erratum in a later entry and leave the record of what shipped intact.
B is not a strawman: #16671 independently reached for the word "erratum" for exactly this
situation. Along the four axes:
1. Real business need — measured, not "reads like it helps"
The reader is named by AGENTS.md itself, in the breaking-changeset rule: this text "ships to
consumers as
CHANGELOG.mdinside the npm package and is what an upgrading agent greps afterthe tombstone error." That reader is a machine that arrives at one entry, by grepping the
symbol it was just refused on. It does not browse the file and it has no reason to read
forward. The writers are measured too: the two cards that produced this gap each need exactly
one sentence corrected, now sitting at
packages/spec/CHANGELOG.md:1497(#15058) and at bothpackages/lint/CHANGELOG.md:436andpackages/spec/CHANGELOG.md:3641(#15026).The measurement that decides it: both routes have identical delivery latency. An already
published tarball is immutable, so neither an amendment nor an erratum reaches a consumer who
installed the affected version — both reach only consumers who install the next one. The
routes therefore do not differ in when the correction arrives, only in where it sits
relative to where the reader lands. Under A it is the sentence the greper lands on. Under B
the greper lands on the false sentence and there is nothing that points it at the erratum.
2. Long-term soundness
CHANGELOG.mdis a generated artifact and its generator appends; it never reconcileshand-written matter someone added underneath. An erratum is therefore a second, un-generated
layer on a generated file that nothing keeps in step — a workaround in this repo's sense.
Amending is the shape the file already tolerates: one deliberate out-of-band hand edit, in its
own PR, exactly like a
content/docs/releases/correction. Contract-first points the sameway: the contract the breaking-changeset rule states is that the entry which removes a symbol
carries its FROM → TO mapping. B leaves that contract broken on the entry that owns the
symbol and satisfies it somewhere else; A restores it where it was declared.
The honest cost, stated plainly: under A the file on
mainstops being byte-identical to whateach version shipped. That property does not vanish — it is held by the published npm tarballs
(immutable, one per version) and by git history — but it stops being held by the working file,
and anyone reading the working file as the shipping record has to go to those two places.
3. Making it structurally harder for an AI to get this wrong
This is the axis with the widest gap. The failure being corrected is an AI acting on a false
migration sentence. B keeps the false sentence and adds a true one elsewhere: two live
readings of one fact, reconciled by the reader. That is the tolerant shape this project rules
against — tolerance is where AI mistakes get covered up in bulk. A leaves one spelling.
B also declares a route the runtime does not honour. Nothing back-links an entry to a later
erratum; building that link is out of scope here by the card's own words (the "nothing notices
when a card's
.changeset/path is consumed" question is filed separately). Declaring anerratum route is declaring a capability nothing delivers.
And A is mechanical where B is a judgement call: A says correct the sentence where it is, in a
docs-only PR, never as a rider. B additionally asks the author to choose which later entry
and to phrase a back-reference — two decisions an AI will make inconsistently across the
repo's
packages/*/CHANGELOG.mdfiles.4. Startup stage — do not proliferate
B invents a document form that does not exist in this repo today, and to be usable it pulls in
a placement convention, a back-link, and eventually a gate. Nothing is pulling on it: the
demand is two sentences on two cards. A adds no new form at all — it reuses the route already
written in the row immediately above, which any agent reading the table has just read. The
"no gradual transition" ruling points the same way: B is precisely the "both readings stay
live, the reader reconciles" shape that ruling refuses for spellings and retirements.
Where they conflict: axis 1's counter-consideration — the file stops being a record of
what was actually published — is real, and it is the whole of B's case. It does not carry,
because that record is held by npm's immutable tarballs and by git, while the working file's
one measured reader is a greper that B cannot reach. It is a one-line row; flipping it to B is
a one-line edit.
What paid for the line
AGENTS.mdsits at the line ratchet's ceiling with headroom 0 (1068/1068), so the row ispaid for by deleting content in the same file, in the same section:
119 bytes of prose removed; no rule lost. Every fact in the two deleted sentences has a
home, and all of them are inside this same section:
.describe()string counts as a change.describe()/ TSDoc on any schema →check:docs→gen:schema && gen:docscontent/docs/references/packages/spec/scripts/build-docs.tscheck:api-surface→gen:api-surfaceapi-surface/check:exported-anybullet further down this section: "theapi-surface/snapshot records that an export exists, never what it resolves to"The sentence that survives is the paragraph's operative instruction — and it is the one that
tells you not to hand-match the very rows the deleted sentences were hand-matching. The
paragraph reads better without them.
This is not a re-wrap. 119 bytes of prose were deleted at the paragraph's existing wrap
width (about 82 columns); the surviving 96-byte sentence occupies two lines. The line was
bought by the deletion, not by re-flowing.
Evidence
All readings taken on the final commit,
3f72809c7(mergedorigin/main854639b31).Line ratchet — the two verdict lines the gate printed, both green:
wc -l AGENTS.md= 1068, before and after, and after the merge oforigin/main.The table went from 4 rows to 5; the new row is line 680.
Governed surface —
node scripts/pm/check-governed-merges.mjs --test AGENTS.md, exit 3:So: draft, human merge, no queue, no auto-merge, no seat approval.
Gates — derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon the final tree (change set:
AGENTS.md, 1 path). All 14 derived families run, all exit 0,plus
pnpm check:doc-authoringrun beyond the derivation:node scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjspnpm check:agent-test-spellingpnpm check:docs-audit-scopepnpm check:driver-memory-censuspnpm check:nul-bytespnpm check:pm-governed-mergespnpm check:pm-governed-prosepnpm check:pm-skill-id-lintpnpm check:pm-skill-ratchetpnpm check:refd-timer-probepnpm check:required-contextspnpm check:watch-hint-literalpnpm check:doc-authoring(beyond the derivation)Reconciled with
--ran:✓ dispatch-gates --ran: 14 derived famil(ies) accounted for — 14 run, 0 NOT-MEASURED.Repo-wide lint, narrowed and the narrowing proved. The linted universe is read from
eslint's own config, not guessed:
eslint.config.mjsscopes every one of its seven blocks to{ts,tsx,mts,cts,js,jsx,mjs,cjs}. Markdown is in none of them. Measured withnpx eslint --no-inline-config --format json AGENTS.md: one result, zero files actuallylinted —
"File ignored because no matching configuration was supplied.",errorCount: 0.Invariance: type-aware linting is not what decides this — the diff's single path is outside
every
filesglob in the config, so it cannot move the verdict on any untouched file. Arepo-wide
pnpm lintis CI's run, and this narrowing excludes nothing.Package build / test: none owed. The diff touches no package, so there is no dependency
closure to build and no affected package to test. The diff edits no gate or tool script, so no
checker's own suite is owed either.
Control bytes:
pnpm check:nul-bytesgreen, plus a self-scan of the changed file —grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' AGENTS.mdexits 1 (no match).skip-changeset: nothing published moves.AGENTS.mdis a repo-root instruction file; nopackage's
files[]can reach outside its own directory, so it ships in no tarball.维护者速读(草稿)
改了什么 —
AGENTS.md的 Documentation Guardrails 表加了第五行,管packages/*/CHANGELOG.md。这一行说三件事:代码 PR ❌ 不改它(它由
changeset version从.changeset/*.md编译出来,并随 npm 包发运);PR 的输入是 changeset,而这个输入有一条无人看守的硬截止——发版消费它的那一刻,
输入被删掉、句子被发布出去;已发布条目里的事实错误,在原条目上就地改正,走专门的 docs-only PR,
不用「在后续条目里写勘误」。表从 4 行变 5 行,新行 751 字节,放在
content/docs/releases/那一行下面。为什么改 — 这个洞是量出来的,不是预想的。#15058 和 #15026 两张卡当初写的修法是
「改
.changeset/*.md里的一句话」,这在守则里是明确允许的;然后一次发版把两个 changeset 都消费了,两句错话从可编辑的输入变成了已发运的 CHANGELOG,卡上写的路线就此不存在,而接替的路线没人写过。
更尴尬的是:
AGENTS.md唯一提到这个文件的地方(破坏性 changeset 那条)恰恰说明它是发给消费者、被 agent 读的,而管「什么能改」的那张表里一个字都没有。#16671 是同一类还在窗口期内的活例子。
风险与代价(含回滚) — 代价集中在「就地改正」这个选择上:
main上的 CHANGELOG 文件不再逐字等于「当时实际发布了什么」。这个属性没有消失,它由 npm 上每个版本不可变的 tarball 和 git
历史承担,但确实不再由工作文件承担——谁以前把工作文件当发运记录读,得改去这两处读。
反过来,勘误方案保住了这个属性,代价是读者得自己找到勘误:而实测的读者是一个照着墓碑错误
grep 符号的升级 agent,它落在旧条目上,不会往后翻,也没有任何机制把它指过去。两条路线到达消费者的
延迟其实一样(都只影响下一次发布的 tarball),差别只在「更正放在读者落点上,还是放在别处」。
回滚成本极低:这是一行表格,翻成勘误方案是一行改动;整个 PR 只动
AGENTS.md一个文件,git revert即可。另一项代价是付账:棘轮 1068/1068 无余量,新行由同一节里删掉的两句话买单(那两句严格复述了它们上方三行的表格)。
席位意见 —
你要做的 — 只需要回一个字母:A(已选:在原条目上就地改正)还是 B(改成在后续条目加勘误)?
Generated by Claude Code