Repository navigation
[finding] The pm-skill ratchet counts LINES while its header prices a per-session token read — a green 134/134 run grew platform-readings.md ~33% in bytes #11106
Description
Activity
Triage: added
finding(title declares it, label was missing — half-annotated shape). Already routeddomain:skills; that seat self-triages.
Generated by Claude Code
首触定级(skills 席,session
session_01RMTpSRF5CjMmQBFfPtPCwJ,2026-08-23 集中定级轮):转needs-user-decision—— 卡自陈「Which of them is worth it is a maintainer call」,动的是治理门禁的计量口径。一句话问题:我们给协议文本上了「只降不升」的闸,但闸量的是行数;写长行的人可以在闸全绿的情况下把文件养肥 33%(按字节)——每个座位每次开工都要整读这些文件,肥的是每一轮的真实开销。
选项 × 真实代价:
- A. 加一道字节/token 棘轮并行——做什么:复用已有 token 棘轮机器(对外 skills 已在用)盖到
.claude/面。真实代价:双门并行,每次合法新增要同时付两种账,维护者要裁两种抬顶。业务上 = 一件货过两道秤。 - B. ceilinged 文件加最大行长规则——做什么:一条 lint 式规则(如 ≤120 字节/行),行数重新正比于体量。真实代价:存量长行要一次性重排(one-off 整理 PR);此后单闸继续有效。业务上 = 统一包装规格,原来的秤继续用。
- C. 改写门禁头部,承认行数只是近似——做什么:一行 docblock。真实代价:门明知失真继续放行,33% 那类增长永远不会被任何机器看见。业务上 = 在秤上贴「仅供参考」。
四棱:
① 项目长远合理性:B 让既有控制恢复语义,零新机制;A 是第二套机制的永久义务;C 是放弃控制;
② 实际业务拉动:实测已发生(134/134 绿灯下 +33% 字节),付账方是每个座位会话的每次整读——这是维护者自己的 token 预算;
③ 防 AI 犯错:AI 有把内容塞进长行避税的结构性激励(已实测发生),B 直接拆掉激励并响亮拒绝;C 维持静默;
④ 创业阶段不扩散:B 一条规则一次重排;A 双门双账扩散维护面。
推荐:B,回退 C(若认为.claude/面体量不值得管)。
本分析看不见的:其余 17 个 ceilinged 文件是否同样漂移(卡自陈未普查;B 落地时顺手量一次即得)。
裁决格式:回一个字母即可。裁后执行(你不用管):B ⇒ 一张实现卡入队(规则 + 存量重排 + 棘轮表同步,治理面 draft);C ⇒ docblock 一行小卡;A ⇒ 实现卡按 token 棘轮形状立。
Generated by Claude Code
- A. 加一道字节/token 棘轮并行——做什么:复用已有 token 棘轮机器(对外 skills 已在用)盖到
<!-- os-decision-facets -->— marker addendum: the raw HTML-comment form was stripped by the body sanitizer from the facets block in the grading comment above (read-back verified on the sibling card); this backtick-quoted line is the surviving machine-greppable spelling. The facets block itself is intact.
Generated by Claude Code
Maintainer ruling recorded (atomic four-part write)
Ruled 2026-08-23, live PM chat, verbatim: 「10950 不考虑存量,其他接受你的建议」 — this card falls under the accepted recommendations. Ruled: B — add a max-line-length rule to ceilinged files (~≤120 bytes/line), with a one-off re-wrap of existing long lines, restoring the line ratchet's semantics; no parallel byte/token ratchet (A), no disclaimer-only (C).
needs-user-decision→pm:queuein the same stroke (domain:skills): implementation card = rule + legacy re-wrap + ratchet-table sync, governed-face draft + human merge. Rider from the analysis: measure the other ceilinged files' drift in the same pass (it falls out for free).
Generated by Claude Code
Claim: skills seat dispatch (session
session_01RMTpSRF5CjMmQBFfPtPCwJ). Serialization released this stroke: every in-flight PR that touched a ceilinged reference file has MERGED (#11749, #11870, #11874, #11884, #11928 all onmain; PR #11931 touchesskills/**+check-doc-authoring.mjs, disjoint from this surface) — the re-wrap sweeps final content.- Branch:
claude/issue-11106-ratchet-line-length - Worktree:
objectstack-11106-line-length - Ruling being implemented (maintainer 2026-08-23, verbatim 「10950 不考虑存量,其他接受你的建议」, recorded in comment 5386683155): B — a max-line-length rule (~≤120 bytes/line) on ceilinged files in
scripts/pm/check-skill-line-ratchet.mjs, a one-off re-wrap of existing over-long lines in the ceilinged corpus, and the ratchet-table sync (re-wrap changes line counts ⇒ every touched ceiling re-pinned to its new reading in the same PR — shrink where it shrinks, and a raise ONLY as the mechanical consequence of wrapping, with the arithmetic shown per file). Rider from the ruling: measure and report every ceilinged file's byte drift in the same pass. - File surface:
scripts/pm/check-skill-line-ratchet.mjs(+ its self-test), the ceilinged reference/docs files listed in its CEILINGS table (re-wrap only — ⛔ zero content changes, byte-identical after whitespace normalization, prove it mechanically per file), the CEILINGS table itself. Stop on breach. - Governed face: draft PR + human merge. Clause-②: no.
⚠️ Known trap: markdown re-wrap can break table rows, fenced code, and line-anchored grep targets (Blocked-by:行锚定 doctrine!) — the rule and the re-wrap must EXEMPT lines that cannot wrap (table rows, fences, URLs, anchored directive examples), and the exemption must be in the RULE so the gate never demands an illegal wrap.
Generated by Claude Code
- Branch:
- added a commit that references this issue
on Aug 25, 2026 { "issue": 11106, "status": "done", "branch": "claude/issue-11106-ratchet-line-length", "pr": "https://github.com/objectstack-ai/objectstack/pull/11948", "premise_still_valid": true, "summary": "Implemented the ruled option B on scripts/pm/check-skill-line-ratchet.mjs: a 120-byte max-line-length rule over every CEILINGS file, a one-off re-wrap of 555 legacy over-long lines, and the ratchet-table sync (18 ceilings re-pinned to post-wrap counts, landing-operations.md LOWERED 82 -> 80, six files unmoved). 120 is the corpus's own upper bound, not an invented width: 34 lines already landed at exactly 120 bytes, ASCII prose already wrapped at <=91B and CJK prose at <=120B (50-60 chars). Exemptions are STRUCTURAL and carry no allowlist (fence, table, heading, front matter, line-anchored Blocked-by:-family directives, blockquote, multi-line verbatim ruling quote, and lines with no legal break point) - the last one is the predicate 'wrapLine returns it unchanged', so the gate never demands a wrap it cannot itself produce. Two exemption classes were FOUND by running the re-wrap and reading what broke, not designed up front, and both are declared as reversible judgment calls: `blockquote` (a continuation must repeat `>`, so wrapping would insert a non-whitespace byte into a verbatim maintainer ruling - 10 lines stay long) and `quotation` (an earlier cut split the ruling quote 「我们是一个创业项目,…」 across two lines and turned check-skill-frame-sync's self-test RED, because a phrase inside a governed quotation stopped being findable on one line - 68 lines stay long). 133 lines remain over budget, all structurally exempt; table rows are the largest remaining unmetered channel and are filed separately, not fixed here.", "tests": "All at final commit 117fc0bd; exit codes captured before any pipe (redirect-then-read), each result quoted from the gate's OWN verdict line, never a bare $?. (1) Gate + self-test: `node scripts/pm/check-skill-line-ratchet.mjs --self-test` EXIT=0 -> '✓ check-skill-line-ratchet self-test: 71 cases pass.' (26 -> 71 cases: a red/green pair for the rule and for EACH exemption class, every exemption case carrying a RED twin in a non-exempt shape so it can only pass by discriminating). Gate itself EXIT=0, 25 file rows, all 'headroom 0'. (2) CONTENT-IDENTITY PROOF for the re-wrap, per file, and the tool REFUSES to write a file failing either: whitespace-stripped byte identity (every non-whitespace char unchanged in order => only whitespace moved) AND inline code-span sequence identity (protects every path, every `Blocked-by: #N`, and check-governed-prose's code-span scan). Result: 25 files, 555 lines re-wrapped, ALL PROOFS PASS; totals 367,452 -> 369,586 bytes (+0.58%, the inserted newlines and list-continuation indents) and +903 lines. Per-file arithmetic in the PR body. The re-wrap tool IMPORTS classifyLine/wrapLine/advanceState from the gate, so re-wrap and rule cannot disagree. (3) REVERSE VERIFICATION, four legs, mutation proved ON DISK each leg with anchored grep -Fxc in BOTH directions (injected text AND deleted text), script carrying a `trap ... EXIT INT TERM` restore: leg0 baseline probe joined=0/continuation=1/max=115B, gate exit=0 '✓ ... services.md is 30 lines (ceiling 30; headroom 0).'; leg1 MUTATE (join a wrapped pair into one 209B prose line) probe joined=1/continuation=0/max=209B, gate EXIT=1 '✗ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/lanes/services.md has 1 line(s) over the 120-byte budget: L12 (209B).'; leg2 CONTROL (the SAME line in an exempt shape, blockquote) probe blockquoted=1/bare-joined=0, gate exit=0 - green; leg3 RESTORE probe joined=0/continuation=1/max=115B, gate exit=0 green. OBSERVED DIRECTION, reported as seen rather than as templated: 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, i.e. this card's drift reproduced on demand. The first reverse-verification attempt used a hand-written anchor that was NOT in the file: the probe reported joined=0 and the run was DISCARDED and re-anchored from disk, not silently re-tried until something landed. (4) DERIVED GATE UNION: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`, no path args, EXIT=0, provenance line verified ('derived from the tree of objectstack-ai/objectstack at commit ... --repo checked against this checkout's origin remote - it holds'); 160 families discovered, 19 matched, ALL 19 GREEN plus check:ratchet-remedy-authority run explicitly. Verdicts: check:pm-skill-ratchet exit 0; check:skill-frame-sync exit 0 '✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files'; check:pm-governed-prose exit 0 '✓ ... 2 instruction surface(s) name all 5 registered governed surfaces'; check:pm-skill-id-lint exit 0 '✓ ... 22 file(s) clean'; check:pm-governed-merges exit 0 '129 assertions'; check:nul-bytes exit 0 'OK (scanned 6650 text file(s) ... no raw ASCII control bytes)'; check:agent-model-declared, check:agent-test-spelling, check:doc-authoring, check:docs-audit-scope, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check:required-contexts (x2 spellings), check:cross-package-test-inputs (x2), check-ci-filter-parity all exit 0; check:doc-formula-expressions exit 0 after building its closure (`pnpm --filter '@objectstack/lint...' build` BUILD_EXIT=0 - it refuses to run against unbuilt dist and its own message warns that piping it reads green either way). (5) check:ratchet-remedy-authority: exit 0, 'OK check-ratchet-remedy-authority: 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. Control corpus: 22 hand-classified scripts, set-equality audited both ways.' It did NOT appear in the derivation (consistent with #11935) and structurally cannot reach this file: its corpus is the NON-RECURSIVE glob scripts/*.{mjs,mts} and this gate lives in scripts/pm/. So it neither flags the file nor reports it UNCLASSIFIED, and no control-corpus entry was owed or added. Independently: the new failure text offers NO registry-expanding remedy to mark - the remedy is 'wrap the line', the exemptions are structural predicates rather than a list, and the message says 'there is no allowlist to add a line to', which is a self-test case. (6) ESLint: repo-wide sweep left to CI; local run is a DECLARED NARROWING with all three readings - receptacle read from ESLint's own config (running it on the changed .md files returns 'File ignored because no matching configuration was supplied', so markdown is outside the population and the only judged file in this diff is scripts/pm/check-skill-line-ratchet.mjs); count read from --format json (1 file linted, 0 errors, 0 warnings, EXIT=0); invariance for untouched files quoted from eslint.config.mjs itself ('never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file'), so this diff cannot move an untouched file's verdict. (7) Ruling's rider, derived by replaying the gate script's git history and reading each CEILINGS value at each commit: EIGHT ceilinged files grew in BYTES at a perfectly flat line count since their ceilings were set - lanes/hotcrm.md +818B (+27.4%), state-machine.md +274B (+8.7%), dispatch-runbook.md +1038B (+5.3%), review-checklist.md +274B (+3.6%), AGENTS.md +1332B (+1.6%), os-dev.md +470B (+1.3%), pm-dispatch/SKILL.md +681B (+0.9%), seat-post-protocol.md +83B (+0.9%); landing-operations.md -113B; the other 16 flat. Every ceiling was set 2026-08-21..08-24, so +27.4% is a days-long window, which sharpens the reading rather than softening it. This answers the card's explicitly-unmeasured question ('whether they have been drifting the same way is unmeasured') with a YES. (8) skip-changeset applied via the additive endpoint (http=200) and CLOSED BY READ-BACK: GET .../labels returns 'size/xl, skip-changeset' - it survived the size-labeler's group write. No changeset: .claude/** + scripts/pm/** publish nothing. Published skills/ catalog NOT touched (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.", "open_questions": [], "out_of_scope_findings": [ "filed as #11947: 133 lines stay over the 120-byte budget by structural exemption (quotation x68, table x46, blockquote x10, unbreakable x6, fence x2, anchored x1) — markdown table rows are the largest remaining unmetered channel, the five longest surviving lines in the corpus are all table rows, topping out at 1,081 bytes in AGENTS.md; a table row grows by widening a cell at zero line cost and passes BOTH controls. Unassigned, finding + domain:skills, not fixed here." ] }
Generated by Claude Code
ACCEPT — skills seat (session
session_01RMTpSRF5CjMmQBFfPtPCwJ), PR #11948 at head117fc0bd, verified against the actual diff plus an independent re-measurement.- The ruled option B lands with its semantics intact: 120 bytes is the corpus's own measured upper bound (34 lines already at exactly 120B), the exemption set is CLOSED and STRUCTURAL — no registry, no per-file allowlist, the failure text says so and a self-test case pins that it says so — and the line-anchored escape hatch is anchored-only (a mid-prose
Blocked-by:mention stays RED). Every exemption class carries a red twin in a non-exempt shape, so the rule can only pass by discriminating. The re-wrap tool importingclassifyLine/wrapLinefrom the gate is the single-predicate discipline this whole references tree keeps re-learning. - The content-identity claim was independently re-verified by this seat, not taken from the report: whitespace-stripped sha256 of AGENTS.md and platform-readings.md compare IDENTICAL between
mainand the branch — only whitespace moved, exactly as the tool's own two proofs (char-sequence + code-span sequence) enforce with refusal-on-failure. - The reverse verification reproduces this card's defect on demand: on the mutated leg the LINE ratchet went green with headroom while the byte rule went red — fewer lines, more bytes, old control blind. That observed direction, plus the discarded-and-re-anchored first probe honestly recorded, is the standard the file's own doctrine demands.
- The rider answers the card's unmeasured question with a corpus-wide YES: eight ceilinged files grew in bytes at flat line counts inside a days-long window (worst +27.4%), derived by replaying the gate's git history — the drift was not one file's habit. The two judgment-call exemptions (blockquote, multi-line verbatim ruling quote) are found-by-measurement with the frame-sync red that forced the second one recorded, and both are declared reversible.
- 19/19 derived families green plus
check:ratchet-remedy-authorityrun explicitly per the [finding]check:ratchet-remedy-authoritysweepsscripts/**but declares no path, so no card that edits a gate script can derive it — the one population it exists to police #11935 warning — and its non-recursivescripts/*corpus measurably cannot reachscripts/pm/, so no classification entry was owed (a sharper [finding]check:ratchet-remedy-authoritysweepsscripts/**but declares no path, so no card that edits a gate script can derive it — the one population it exists to police #11935 datum, recorded there by implication). [finding] 133 lines stay over the 120-byte budget by structural exemption — markdown table rows are the largest remaining unmetered channel (up to 1,081 bytes on one line) #11947 correctly files the remaining unmetered channel (table rows, up to 1,081B) instead of widening this PR.
PR stays draft — governed face, human merge; joins the merge list. #11106 closes via its Fixes line at merge. The skills queue's dispatchable inventory is now EMPTY pending: #11947 triage, #11218/#11220 premise re-verification against the overhauled scan's first patrol run, and #11889-successor docs riding normal flow.
Generated by Claude Code
- The ruled option B lands with its semantics intact: 120 bytes is the corpus's own measured upper bound (34 lines already at exactly 120B), the exemption set is CLOSED and STRUCTURAL — no registry, no per-file allowlist, the failure text says so and a self-test case pins that it says so — and the line-anchored escape hatch is anchored-only (a mid-prose
Triage state repair (concentrated round, mechanical):
findingmeans awaiting first grading, and this card is already graded into another pm-state — removing the stalefindinglabel so the ungraded count stays honest. No other change.
Generated by Claude Code
Filed unassigned, recording only. Surfaced while landing the platform-readings facts family (PR 11105, five graded cards into one file); the PR declares the same numbers in its own body rather than hiding them.
Measured
.claude/skills/pm-dispatch/references/platform-readings.md, one PR, five new fact entries plus in-place compression:pnpm check:pm-skill-ratchetis green on both:platform-readings.md is 134 lines (ceiling 134; headroom 0). No ceiling was raised, no maintainer ruling was needed, and nothing in the gate saw the growth.The mechanism
scripts/pm/check-skill-line-ratchet.mjsmeasurestext.split(newline).length. The file's own wrapping convention is not uniform: older entries wrap near 100 bytes, entries added over the last week run 400 to 830 bytes on one line. So a line is not a unit of anything the reader pays for, and an author who writes in the recent dense style adds arbitrary content at zero measured cost while an author who wraps at 80 columns pays a line per 80 bytes. The incentive points at the less readable spelling.That matters here specifically because the gate's own docblock states what it is defending, verbatim:
Tokens are approximately proportional to bytes for this corpus; lines are not. So the gate's stated purpose and its actual measurement have drifted apart, silently, and the drift only shows up when someone reports it — the ratchet cannot.
Not claimed
Possible directions (triage, not a recommendation)
scripts/check-skills-token-ratchet.mjs. Extending that shape to the.claude/surface is the direction that measures what the header says it prices.The first two are real changes to a governed gate; the third is a docblock edit. Which of them is worth it is a maintainer call, not this seat's.
Generated by Claude Code