pm-ratchet: cap ceilinged lines at 120 bytes, re-wrap the legacy corpus, re-pin the map - #11948
Merged
Merged
Conversation
…us, re-pin the map (#11106) The line ratchet counted LINES while its header prices a per-session token read; tokens track bytes. Measured: one green 134/134 run grew platform-readings.md 16,953 -> 22,559 bytes (+33%) with nothing able to see it. Maintainer ruling 2026-08-23 (option B). - rule: a max-line-length check (120 bytes) over every CEILINGS file, with STRUCTURAL exemptions (fence, table, heading, front matter, line-anchored Blocked-by:-family directives, blockquotes, multi-line verbatim ruling quotes, and lines with no legal break point) and no allowlist an author can add a line to. - legacy re-wrap: 555 over-long lines re-flowed, content proved byte-identical after whitespace normalization and code-span-sequence identical per file. - ratchet sync: 18 ceilings re-pinned to the post-wrap counts, one lowered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RMTpSRF5CjMmQBFfPtPCwJ
hotlong
marked this pull request as ready for review
August 25, 2026 03:13
This was referenced Aug 25, 2026
os-trump
pushed a commit
that referenced
this pull request
Aug 25, 2026
…ut, 1150 -> 1158 The branch's original raise (961 -> 969) was measured against the pre-#11948 layout and was dissolved by the merge. Re-measured on current main: AGENTS.md is 1150 there and 1158 with the mirror re-sync applied, so the honest cost is again +8 -- published block 11 -> 17 lines, prose paragraph 4 -> 6. The maintainer's option-C1 ruling is quoted verbatim and untranslated beside the value, per the map's convention. Gate reads at the new value: check-skill-line-ratchet: AGENTS.md is 1158 lines (ceiling 1158; headroom 0). check-skill-line-ratchet self-test: 71 cases pass.
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 1, 2026
…nt (objectstack-ai#11908) * feat(gates): ratchet AGENTS.md's published spellings list to the constant `scripts/check-cross-package-test-inputs.mjs` is a source scan: a path spelling it does not know produces no flag, so a test whose reads escape its package goes undeclared silently. That is why its recognised set is published in AGENTS.md rather than left in the implementation -- and nothing compared the two copies. The mirror drifted three times (objectstack-ai#10163, objectstack-ai#10854, and this one). Twice the stale line was the stated REASON FOR A PROHIBITION, so a rotting mirror does not merely misinform: it launders an obsolete rule into a live one. And one claim was already false BEFORE the PR that supposedly staled it, so a lag-only check would not have caught it either. Adds `scripts/check-published-list-mirrors.mjs`, which asserts line-for-line EQUALITY between a declared constant and the block a document publishes: - equality, not containment -- a comment-only drift is invisible to containment, and the comments are where the prohibitions live; - the block is located by heading + fence, never by line number (this card was filed against `AGENTS.md:96-106`; the block sat at `:92-104` three days later); - every unreadable state REFUSES: renamed heading, duplicate heading, re-tagged fence, unterminated fence, empty block, two candidate fences, and a constant that is missing, renamed, empty or not a string list; - it can only ever go RED. AGENTS.md is governed, human-merge-only, so the gate never repairs -- it prints the exact block to paste. Stated in its header. Repairs the published block in the same change: it was short by 13 of the constant's 24 lines, including the two `findUp` ANCHOR seeds PR objectstack-ai#10852 added, and the lead-in prose named only two of the three seed kinds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx * fix(gates): publish the spellings, not the failure box's notes Two standing rulings on AGENTS.md govern what a mirror may put there, and a byte-identical copy of the whole constant breaks one of them outright: - the shrink-only line ceiling (`check-skill-line-ratchet`), and - "operative text carries lessons self-contained ... numbers go" (maintainer ruling 2026-08-12, `check-skill-id-lint`). The constant is the text of a CLI failure box: spellings (code, each with the comment that annotates it) plus free-standing NOTE PARAGRAPHS set off by blank entries, two of which carry issue-ID citations. Publishing all 24 lines put those citations into the instruction surface and check:pm-skill-id-lint went red -- the gate meant to keep the document honest would have broken the document's own prose standard. So the mirror judges a projection with no judgement in it: an entry is PUBLISHED unless it is blank, or it is a comment line that no published entry directly precedes. A comment continuing a spelling travels with that spelling; a note paragraph standing alone stays in the failure box. Every spelling is code, so no spelling can hide from it. 24 entries project to 17 published lines, zero issue-ID citations, and the published block grows 11 -> 17: the two `findUp` anchor seeds, the ⛔ manifest prohibition that qualifies them, and the `-> repo root` annotation. A self-test case now pins the citation property against the live constant, so an issue number added to a spelling line goes red HERE, naming the ruling, instead of landing in AGENTS.md. check:pm-skill-id-lint: 22 file(s) clean. check:pm-skill-ratchet is red by 8 lines (969 vs 961) and needs a maintainer ruling -- see the PR body. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx * chore(ratchet): raise the AGENTS.md ceiling 961 -> 969 per the maintainer ruling The one-line follow-up PR objectstack-ai#11908's body pre-wrote: the published-spellings mirror re-sync costs +8 lines, lossless rewrap headroom measured 0, and the mirror is now mechanically enforced. Maintainer ruling 2026-08-25, verbatim: 「同意,帮我合并,然后继续」 (option A as presented in the PM-chat batch review; provenance on PR objectstack-ai#11908). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9StxQgG2DPA26XzZZqnJB * chore(ratchet): re-measure the AGENTS.md ceiling on the reflowed layout, 1150 -> 1158 The branch's original raise (961 -> 969) was measured against the pre-objectstack-ai#11948 layout and was dissolved by the merge. Re-measured on current main: AGENTS.md is 1150 there and 1158 with the mirror re-sync applied, so the honest cost is again +8 -- published block 11 -> 17 lines, prose paragraph 4 -> 6. The maintainer's option-C1 ruling is quoted verbatim and untranslated beside the value, per the map's convention. Gate reads at the new value: check-skill-line-ratchet: AGENTS.md is 1158 lines (ceiling 1158; headroom 0). check-skill-line-ratchet self-test: 71 cases pass. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11106
The line ratchet counts lines while its own docblock states it is pricing a per-session token read — and tokens track bytes. The card measured the gap: one PR grew
references/platform-readings.md16,953 → 22,559 bytes (+33%) at a flat 134/134 line count, green on every run, with no ceiling raised and nothing in the gate able to see it.Maintainer ruling 2026-08-23 (option B), recorded verbatim and untranslated in comment 5386683155:
Ruled: a max-line-length rule (~≤120 bytes/line) on the ceilinged files, a one-off re-wrap of the existing long lines, and the ratchet-table sync — not a parallel byte/token ratchet (A), not a disclaimer-only docblock edit (C). The rider from the analysis — measure the other ceilinged files' byte drift in the same pass — is at the bottom of this body.
Governed surface:
.claude/**+AGENTS.md. Draft, human merge. Not ready-flipped, no auto-merge.1. The rule — 120 bytes, and why that number
scripts/pm/check-skill-line-ratchet.mjsnow checks every line of every file in itsCEILINGStable against a 120-byte budget, alongside the existing per-file line ceiling.120 is the corpus's own upper bound, not a new house style. Measured over the 4,028 lines of the pre-change corpus: 34 lines land at exactly 120 bytes, and the two wrap conventions already in use both sit under it — ASCII prose wraps at ≤91 bytes (~88 columns) and CJK prose at ≤120 bytes (50–60 characters, ~100–120 display columns, a CJK character being 3 bytes and 2 columns wide). 120 is the one number both conventions already satisfy, so the rule codifies what the careful authors were doing instead of reflowing the corpus to an invented width. Bytes rather than characters or columns, because bytes is what the header prices.
The exemptions live in the RULE, and there is no allowlist
A length rule that can demand an illegal wrap is worse than no rule — it teaches authors to break a table, split a fence, or wrap a 行锚定 directive whose whole contract is that a grep finds it at a line start. Every exemption is therefore derived from the line's own syntax. There is deliberately no exemption registry and no per-file allowlist: a list of blessed long lines is exactly the ratchet-expanding remedy #8435 keeps out of an author's reach, and it would rot.
fencetable|row is a different tableheadingfrontmatteranchoredBlocked-by:/Restart-when:/Restart-touch:/Unlock-when:blockquote>, so wrapping inserts a non-whitespace byte into a quotationquotationunbreakablewrapLinereturns them unchanged — the gate never demands a wrap it cannot itself produceTwo of those were found by running the re-wrap and reading what broke, not designed up front, and both are declared as judgment calls the reviewer can reverse in one predicate each:
blockquote— the re-wrap's content-identity proof went red on three files because a blockquote continuation needs a>. Wrapping would have edited verbatim maintainer rulings. Cost: 10 lines stay long.quotation— an earlier cut split 「我们是一个创业项目,…」 across two lines and turnedcheck-skill-frame-sync's self-test red: a phrase inside a governed quotation stopped being findable on one line. A soft break does not change what a reader sees, but it does change what a matcher sees, and quoted rulings are the text most likely to be matched. Cost: 68 lines stay long.A quote that fits on one line is not exempt — it is an atom instead, so surrounding prose still wraps around it intact.
What a wrap may move
Only whitespace: a space becomes a newline, or a newline is inserted between two East Asian characters (a segment break there is removed by the CSS segment-break transformation rules, which is why the corpus already wraps CJK prose mid-run). No break is offered at a CJK↔Latin junction without a space, because that one would render as a space. Inline code spans, links, autolinks, URLs and single-line 「…」 quotes are atoms — never broken into. A continuation line is never allowed to begin with a sequence markdown reads as a new block.
Self-test
--self-testgoes 26 → 71 cases, with a red/green pair for the rule and for each exemption class — every exemption case carries a RED twin in a non-exempt shape, so a case can only pass by the rule actually discriminating:2. The legacy re-wrap — 555 lines, zero content changes, proved per file
The re-wrap tool imports
classifyLine/wrapLine/advanceStatefrom the gate itself, so the re-wrap and the rule it satisfies cannot disagree. Two equality proofs run per file, and the tool refuses to write a file that fails either:`…`spans byte-identical, which is what protects every path, everyBlocked-by: #N, andcheck-governed-prose's code-span scan.3. Per-file arithmetic, ceiling sync, and the equality proof
Counts against
origin/main. Every raise is the mechanical consequence of wrapping, and headroom is 0 on every row.landing-operations.mdmoved the other way (82 → 80, its standing headroom locked in); six files were already within budget on every line and did not move at all..claude/skills/pm-dispatch/SKILL.md.claude/skills/pm-dispatch/references/dispatch-runbook.md.claude/skills/pm-dispatch/references/state-machine.md.claude/skills/pm-dispatch/references/contract-review.md.claude/skills/pm-dispatch/references/decision-analysis.md.claude/skills/pm-dispatch/references/platform-readings.md.claude/skills/pm-dispatch/references/rest-channel.md.claude/skills/pm-dispatch/references/review-checklist.md.claude/skills/pm-dispatch/references/landing-operations.md.claude/skills/pm-dispatch/references/release-aftercare.md.claude/skills/pm-dispatch/references/seat-post-protocol.md.claude/skills/pm-dispatch/references/lanes/engine.md.claude/skills/pm-dispatch/references/lanes/services.md.claude/skills/pm-dispatch/references/lanes/cli.md.claude/skills/pm-dispatch/references/lanes/devx.md.claude/skills/pm-dispatch/references/lanes/skills.md.claude/skills/pm-dispatch/references/lanes/spec.md.claude/skills/pm-dispatch/references/lanes/hotcrm.md.claude/agents/os-dev.md.claude/skills/checklist-test/SKILL.md.claude/skills/checklist-author/SKILL.md.claude/skills/dogfood-verification/SKILL.md.claude/skills/spec-property-retirement/SKILL.mdAGENTS.mdCLAUDE.mdTotals: 25 files · 555 lines re-wrapped · line delta +903 · bytes 367,452 → 369,586 (+2,134, +0.58% — the inserted newlines and list-continuation indents, which is precisely what "whitespace-only" costs) · all equality proofs pass.
How to read the jumps. The pre-change numbers are not comparable to these. A line that used to hold 400–830 bytes is now three to seven lines holding the same bytes; 666 → 1,008 is not slack, it is the same text measured in a unit that finally means something. Headroom is 0 on every row, and each line is now capped at 120 bytes — which is what makes the count track the token read the header prices.
133 lines remain over budget, all structurally exempt: quotation×68, table×46, blockquote×10, unbreakable×6, fence×2, anchored×1. Table rows are the largest remaining unmetered channel (the five longest surviving lines are all table rows, topping out at 1,081 bytes in
AGENTS.md) and are filed as a separate finding — see below.4. Rider from the ruling: byte drift across the whole ceilinged corpus
"Measure the other ceilinged files' drift in the same pass (it falls out for free)." Bytes at the commit where each ceiling was last set (derived by replaying the gate script's own history and reading each
CEILINGSvalue at each commit) against bytes onorigin/main:mainmain…pm-dispatch/references/lanes/hotcrm.md…pm-dispatch/references/state-machine.md…pm-dispatch/references/dispatch-runbook.md…pm-dispatch/references/review-checklist.mdAGENTS.md.claude/agents/os-dev.md…pm-dispatch/SKILL.md…pm-dispatch/references/seat-post-protocol.md…pm-dispatch/references/landing-operations.mdThe answer to the card's open question is yes. Eight files grew in bytes at a perfectly flat line count, one of them by 27.4%, every one of them green on every run — the same silent mechanism the card reported for
platform-readings.md, confirmed across the corpus. And the windows are days, not weeks: every ceiling in the map was set between 2026-08-21 and 2026-08-24, which makes +27.4% in that span sharper evidence, not softer.platform-readings.mdreads +0% only because its ceiling was re-pinned on 08-24, after the incident this card reports.Verification
Reverse verification — plant, prove on disk, read the gate's own verdict, restore. The on-disk probe is anchored on the text being changed in both directions, and it earned its keep: the first attempt used a hand-written anchor that was not in the file, the probe reported
joined=0and the run was discarded rather than re-tried until something landed.joined=0 continuation-at-line-start=1 max=115B✓ … services.md is 30 lines (ceiling 30; headroom 0).joined=1 continuation-at-line-start=0 max=209B✗ … services.md has 1 line(s) over the 120-byte budget: L12 (209B).blockquoted=1 bare-joined=0✓ … services.md is 29 lines (ceiling 30; headroom 1).joined=0 continuation-at-line-start=1 max=115B✓ … services.md is 30 lines (ceiling 30; headroom 0).Leg 1 is worth reading twice: on the mutated tree the line ratchet went green with headroom 1 while the new rule went red. Fewer lines, more bytes, old control blind — the drift this card is about, reproduced on demand.
Derived gate union —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no path args; 160 families discovered, 19 matched). All 19 run green, pluscheck:ratchet-remedy-authorityrun explicitly. Exit codes captured before any pipe; each line is the gate's own verdict.check:pm-skill-ratchet✓ check-skill-line-ratchet self-test: 71 cases pass.+ 25 file rows, allheadroom 0check:skill-frame-sync✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 filescheck:pm-governed-prose✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfacescheck:pm-skill-id-lint✓ check-skill-id-lint: 22 file(s) clean (pattern /#[0-9]{3,}/g).check:pm-governed-merges✓ check-governed-merges --self-test: 129 assertionscheck:nul-bytescheck-nul-bytes: OK (scanned 6650 text file(s) … no raw ASCII control bytes).check:agent-model-declared✓ check-agent-model-declared: 1 agent definition(s) … all declare a modelcheck:agent-test-spelling✓ check-agent-test-spelling: 0 violations — 364 file(s)check:doc-authoring✓ doc authoring guard: 389 files cleancheck:docs-audit-scope✓ release-owned pages are in scope and read-onlycheck:entry-guard✓ check:entry-guard: 154 scripts/ file(s)check:parse-guard✓ check:parse-guard: 153 scripts/ file(s)check:pnpm-filter-targets✓ check:pnpm-filter-targets: 135/168 --filter occurrence(s)check:required-contexts(×2 spellings)✓ check-required-contexts: 6 required context name(s) pinnedcheck:cross-package-test-inputs(×2)OK: 16 package(s) read outside themselves, all declaredcheck-ci-filter-parityOK: all 96 declared cross-package glob(s) … coveredcheck:doc-formula-expressions✓ … 9 @example(s) judged clean across 1001 packages/spec/src filescheck:ratchet-remedy-authorityOK … 138 scripts swept (scripts/*.{mjs,mts}); 8 mark the expanding remedy ⛔ MAINTAINER-ONLY, 5 turn it down outright, 125 hand out no ratchet-expanding remedy.Green union run at
117fc0bd, the final commit.On
check:ratchet-remedy-authority. It did not appear in the derivation (consistent with #11935) and it does not reach this script either way: its corpus isscripts/*.{mjs,mts}, non-recursive, and this gate lives inscripts/pm/. Run explicitly regardless, and it neither flags this file nor reports it UNCLASSIFIED — 138 scripts swept, none of them this one. Independently of that: the new failure text offers no registry-expanding remedy to mark. Its remedy is "wrap the line"; the exemptions are structural predicates, not a list, and the message says so in terms (there is no allowlist to add a line to), which is a self-test case. The pre-existing ceiling-raise remedy in the ratchet's other message is untouched by this PR.ESLint — the repo-wide sweep is CI's run; the local run is a declared narrowing, with all three readings:
.mdfiles returnsFile ignored because no matching configuration was supplied— markdown is outside the population. The only file in this diff ESLint judges isscripts/pm/check-skill-line-ratchet.mjs.--format json: 1 file linted, 0 errors, 0 warnings.eslint.config.mjsnever enables type-aware linting (its own docblock, verbatim: "this repo runs oneeslint.config.mjs, which never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file, test or not"), so nothing in this diff can move an untouched file's verdict.Not touched: the published
skills/catalog. This diff is.claude/**+AGENTS.md+scripts/pm/**only, so the published-catalog line-budget clause does not apply and no two-reading report is owed. The gate's header boundary — the published root is deliberately outside the ceiling, and extending it is a policy change needing its own ruling — is unchanged and still pinned by its self-test case.Changeset: none.
.claude/**+scripts/pm/**publish nothing;skip-changesetapplied.Out-of-scope finding filed
finding+domain:skills, not fixed here.Generated by Claude Code