Skip to content

pm-ratchet: meter table-row bytes per file, close the Han+ASCII-punct soft breaks, cover compile-surfaces.md - #12263

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-11947-table-row-byte-pins
Aug 25, 2026
Merged

pm-ratchet: meter table-row bytes per file, close the Han+ASCII-punct soft breaks, cover compile-surfaces.md#12263
hotlong merged 2 commits into
mainfrom
claude/issue-11947-table-row-byte-pins

Conversation

@claude

@claudeclaudeBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Closes#11947, closes#12098, closes#12081

Three cards against one serial unit — scripts/pm/check-skill-line-ratchet.mjs
— which is why they are one PR. Final commit for every reading below:
acf9bedfc.


#11947 — per-file max-table-row-bytes pins

Maintainer ruling 2026-08-25 (issue #11947, comment 5406811814), verbatim and
untranslated:

「同意」

accepting the skills seat's option 1 (A-ratchet): a per-file
max-table-row-bytes pin over the CEILINGS corpus, shrink-only, seeded at
today's widest table row per file, no invented constants. Options 2 (whole-file
byte ceiling on the two table-heavy files) and 3 (nothing) are not taken. The
other five structural exemption classes — quotation / blockquote / unbreakable
/ anchored / fence — are untouched.

Seeds, measured with the script's own scanTableRows, not by hand. Five of
the 27 ceilinged files carry a table row today:

filepin (bytes)widest row at
AGENTS.md1081L703
.claude/skills/pm-dispatch/SKILL.md765L271
.claude/skills/pm-dispatch/references/compile-surfaces.md352L16
.claude/skills/spec-property-retirement/SKILL.md328L81
.claude/skills/checklist-test/SKILL.md221L50

Re-measured on this branch rather than copied from the grading comment: the
AGENTS.md row is at L703, not L702 — it shifted by one line when #11819
landed. The byte figure (1081) is unchanged.

The remaining 22 files are pinned at 0, which is a measurement (their
widest table row is 0 bytes), not a disabled row. An uncovered file is the one
place a row could widen unmetered again, which is the whole defect, so every
CEILINGS key carries a pin and the self-test holds the two maps in step —
enforcement cannot, because a missing pin is simply never consulted.

Ablation — the card demonstrated in one run. Widening the corpus's widest
cell by 11 bytes (a marker injected into AGENTS.md L703, mutation confirmed on
disk by grep count 0 → 1 and by the line growing 1081 → 1092 bytes):

✓ check-skill-line-ratchet: AGENTS.md is 1150 lines (ceiling 1150; headroom 0).
✗ check-skill-line-ratchet: AGENTS.md has a 1092-byte table row at L703; the
max-table-row-bytes pin is 1081. …

The line ceiling stays green — that is exactly the unmetered channel the card
measured, and the new pin is what sees it. The mutation script carried a
trap … EXIT INT TERM restore; restoration verified (marker count back to 0,
gate exit 0).

#12098 — a CEILINGS row for references/compile-surfaces.md

Added at 26, read from the ratchet's own run per the card's convention (row
inserted at a deliberately low ceiling of 1, gate printed
compile-surfaces.md is 26 lines, then set), headroom 0.

⚠️One honest deviation from the card's re-check. The card asked to confirm
the file carries no line over the 120-byte rule. It carries seven — and all
seven are table rows, structurally exempt and not re-wrappable (a wrapped
| row is a different table). The card's remedy for that case ("re-wrap before
the row, never raise") does not apply, because there is no legal wrap to
perform. They are instead metered by this PR's other half: the file's 352-byte
pin above. No ceiling was raised.

#12081 — closing the Han+ASCII-punct soft breaks, and no longer offering them

⭐ Read this before reading the diff — the card's self-concealment mechanism,
quoted verbatim:

⭐ And it is self-concealing under re-wrap: re-flowing such a paragraph and
re-wrapping it faithfully must preserve the space (it is what the text
renders as), so the space migrates from a line break into a literal ", " in
the source. It looks like the re-wrap introduced it. It did not — it only made
an existing render visible. Anyone auditing a future re-wrap by eye will reach
the wrong conclusion here, which is why the mechanism is written down.

Measured on this diff: it did not fire. Literal Han+mark+space occurrences
across the 15 touched files went 11 → 11, delta 0 — because this PR merges
the break (deleting the stray space, which is the defect) rather than re-wrapping
it faithfully (which would have preserved it). So no reviewer should read a
migrated ", " here; the mechanism is quoted because the next re-wrap of this
corpus will hit it, and it is now recorded in the script header too.

It has, however, already fired once — measured, and filed as #12265.
platform-readings.md was re-wrapped by #12091 earlier the same day #12081 was
filed, and running both predicates across that rewrite shows all seven of its
breaks converting into literal ", ":

BEFORE #12091 (ef786a51): line-end breaks = 7 | literal in-source = 0
NOW (eeec62a98): line-end breaks = 0 | literal in-source = 7

That is why #12081's own file table no longer reproduces for that file, and it
is the reason its count is 34 here rather than 45. The literal population (14
across 3 files) is out of scope for this PR — the dispatch scoped #12081 to "the
measured lines" — and is recorded in #12265 with its triage caveat.

The population was re-derived on this tree, and it is 34, not the card's 45.
Two reasons, both worth stating:

  • The corpus moved. The card was filed at 08:10 today; platform-readings.md
    was rewritten by refs(pm-dispatch): qualify the REST prescriptions as per-session, and bank four platform readings #12091 since, taking its 7 with it (it now measures 0).
  • Six of the card's hits sit inside multi-line 「…」 verbatim maintainer
    rulings
    — the quotation exemption class the 2026-08-25 ruling explicitly
    leaves untouched, and which AGENTS.md 语言规则 requires be carried verbatim and
    unrewritten. They are excluded, not fixed. A blunt pass over the card's raw
    predicate would have edited six maintainer rulings.

The 34 are closed in place across 15 files. Line counts move only downward:
decision-analysis.md 48 → 46, lowered in the map (shrink-only, always
legitimate); every other file's count is unchanged, so no other row moved and no
ceiling was raised. ⛔ No corpus-wide re-wrap: the re-wrap window is the smallest
run of lines starting at the measured break and growing downward inside the same
paragraph
that does not cost a line. Paragraphs with no measured break are not
touched.

Render-level before/after, two of the samples (the whole point of the card is
that this is invisible in the source and visible only in the render):

--- .claude/skills/pm-dispatch/SKILL.md:316
RENDER before: 行)。无心跳, 活性惰性判定;竞态三招 —— 动手前重 fetch 正文、…
RENDER after : 行)。无心跳,活性惰性判定;竞态三招 —— 动手前重 fetch 正文、…
spaces in render before: 5 -> after: 4 content identical ignoring spaces: true
--- .claude/skills/pm-dispatch/SKILL.md:408
RENDER before: 拆分; 父单已有子结构的,父单队列标签即可(sub-issue 自动成为候选),…
RENDER after : 拆分;父单已有子结构的,父单队列标签即可(sub-issue 自动成为候选),…
spaces in render before: 2 -> after: 1 content identical ignoring spaces: true

② The wrapper no longer offers the break.hanAsciiPunctTail refuses a break
after an ASCII ,;: that follows a Han character. It is a separate test
rather than three more characters in NO_BREAK_AFTER, because those marks must
stay legal break points in ASCII prose — the self-test pins both directions, and
the ASCII twin case exists so the Han case cannot pass for being over-broad.

Why only one shape ever leaked, now pinned in the self-test: with no space
after the mark there was never a legal break there anyway (neither side is wide),
so the defect could only enter where the author had written ", " and the
wrapper spent that space on a newline.

Reverse-verification, and it changed a case. Ablating the guard
(hanAsciiPunctTail call removed from breakLegal; mutation confirmed on disk,
guard count 1 → 0, marker count 0 → 1) first turned only one case red. The
breakLegal case was passing for an unrelated reason — its atom pair had no
space after the mark, so the break was already illegal. It was rewritten to use a
spaced pair; the ablation now turns 2 of 111 red, and the fix is a separate
commit so the sequence is auditable. Restore trapped and verified.

Inertness on today's corpus, measured. Comparing classifyLine and
wrapLine old-vs-new over all 4,953 lines of the corpus: 0 classification
diffs, 0 wrap-output diffs
. The rule removes a break option, which could in
principle turn a RED over-long line into an exempt unbreakable one — a real
loosening. It does not, on any line in this corpus.


Verification

Self-test 71 → 111 cases, all green. (The dispatch brief said "currently
1336+ cases"; the measured baseline on origin/main is 71 — recorded here
because the figure will otherwise be quoted again.)

✓ check-skill-line-ratchet self-test: 111 cases pass.

Gate families derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (16 paths vs merge base eeec62a98; 19 families,
re-derived on the final tree with no new family appearing). Each ran at
acf9bedfc with its exit code captured before any pipe; every one green:

check:agent-model-declared · check:agent-test-spelling ·
check:cross-package-test-inputs · check:doc-authoring ·
check:doc-formula-expressions · check:entry-guard · check:nul-bytes ·
check:parse-guard · check:pm-governed-merges · check:pm-governed-prose ·
check:pm-skill-id-lint · check:pm-skill-ratchet ·
check:pnpm-filter-targets · check:required-contexts ·
check:skill-frame-sync · check-ci-filter-parity

Verdict lines from the two that speak to this change:

check-nul-bytes: OK (scanned 6766 text file(s) -- 6766 tracked, 0 untracked-not-ignored;
skipped 7 binary; no raw ASCII control bytes).
✓ check-skill-line-ratchet: AGENTS.md: widest table row is 1081 bytes (pin 1081; headroom 0).

check-governed-queue-guard is the one family that cannot run locally — it reads
GITHUB_EVENT_PATH and correctly refuses to exit 0 without it. CI owns it.

ESLint — a narrowed run, declared. Repo-wide pnpm lint is CI's; this ran
eslint --no-inline-config over the 16 changed paths, 0 errors. The
narrowing excludes nothing, on three readings: (1) the population comes from
eslint's own config, which reports 15 of the 16 paths as "File ignored because
no matching configuration was supplied"
— the linted population of this diff is
exactly scripts/pm/check-skill-line-ratchet.mjs; (2) the count is read from
--format json (16 entries, 0 errors, 15 unconfigured); (3) this repo's
eslint.config.mjs never enables type-aware linting for ANY file (no
parserOptions.project, no typed @typescript-eslint rules — stated and
measured with a positive control at eslint.config.mjs:324-334), so this diff
cannot move the verdict of any file it does not touch.

No changeset: internal agent tooling plus .claude/ instruction files. This
publishes nothing. skip-changeset applied.

⛔ Draft only — the diff touches .claude/**, a governed surface. Not flipped
ready, no auto-merge armed. Review requested from @os-zhuang.

Generated by Claude Code


Generated by Claude Code

… soft breaks, cover compile-surfaces.md
Three cards against one serial unit, scripts/pm/check-skill-line-ratchet.mjs.
#11947 — per-file max-table-row-bytes pins, shrink-only, seeded at each file's
own widest table row read from the script's own scanner. Closes the largest
unmetered channel the 120-byte line rule leaves open: a table row grows by
widening a cell, which costs zero lines and is exempt by shape.
#12098 — a CEILINGS row for references/compile-surfaces.md, the last uncovered
pm-dispatch references file. Count read from the ratchet's own run (26).
#12081 — the 34 measured soft breaks sitting after an ASCII , ; : between Han
characters are merged (each rendered as a stray mid-sentence space), and
wrapLine no longer offers that break at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMTpSRF5CjMmQBFfPtPCwJ
The case used an atom pair with no space after the mark, where the break was
already illegal for an unrelated reason — it passed under ablation of the very
guard it names. Pinned both shapes instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMTpSRF5CjMmQBFfPtPCwJ
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude