Skip to content

Commit 44e2d50

Browse files
fix(skills-ratchet): leave the whole release-stamp line out of the token count and re-pin the stamped ceilings so headroom stays 0 and 1 (#23199)
Fixes #23192 Clause-②: no ## What this does `scripts/check-skills-token-ratchet.mjs` no longer prices the release bot's frontmatter `metadata.version` line. For each file the version script stamps (`stampedSkillPaths()`, the ten `skills/NAME/SKILL.md`), the count leaves out that whole line and its newline. This holds on every tree and for every stamp width, so a stamped file's count no longer moves when the stamp changes. The ten stamped `CEILINGS` rows are each lowered by exactly the tokens taken out (5), so every file's headroom on `main` is the same as before. That includes `objectstack-data` at 0 and `objectstack-automation` at 1. No ceiling minus its count grows. The route-A code (`stampPrerelease()` / `STAMP_PRERELEASE_RE`, which zero-priced only the `-next.N` segment) is **replaced**, not kept alongside. Its only consumers were inside this file, and `git grep` finds none elsewhere. ## The ruling this implements (`6139629628`, item 2, quoted verbatim) > **A2′ (replaces A2): no tolerance band.** `scripts/check-skills-token-ratchet.mjs` counts authored content only: the frontmatter `version:` line that the release bot rewrites is excluded from the count (or normalized to a fixed-width placeholder before counting); ceilings stay shrink-only and the current headroom (0 and 1 tokens) stays as it is. This is also #23176's structural fix; whatever one-token trim is in flight there is the stop-gap, not the route. The gate marks loosening a ceiling as ⛔ MAINTAINER-ONLY. This PR loosens nothing: every changed row moves **down**, by exactly what its count moved. The ruling is quoted here because it is the authority for changing what the gate counts. `pnpm check:ratchet-remedy-authority` is green on the final head (output under Verification). ## Why exclude the whole line, not normalize to a placeholder The ruling allows either. I chose exclusion because it keeps the re-pin simplest and the hand audit easiest: - **Every re-pin is exactly 5, whatever the file's length.** On the basis tree every stamp line is two spaces, `version: "17.7.0"` and a newline, which is 20 bytes or exactly 5 units. `ceil((B - 20) / 4) == ceil(B / 4) - 5` holds for every B, so each count falls by exactly 5 and each ceiling drops by exactly 5. No per-file rounding is involved. A placeholder of any width other than 6 would re-pin each row by 0 or 1 depending on the file's length mod 4. A width-6 placeholder would need no re-pin, but it would build today's stamp width into the convention as a magic constant and keep pricing bytes no author wrote. - **The hand audit takes one extra term.** For a stamped SKILL.md: `ceil(($(cat FILE | wc -c) - $(grep -m1 -E '^[[:space:]]+version:' FILE | wc -c)) / 4)`. For every other file it is unchanged: `ceil($(cat FILE | wc -c) / 4)`. The self-test checks this recipe against every real stamped file. A placeholder audit would need a `sed` rewrite before `wc`. - **It matches the ruling's main wording:** "counts authored content only". **Finding the line without a second parser.** The header already said the stamp is read only through the stamp-owning script. Line exclusion needs the line itself, and `extractMetadataVersion` returns only its value. So `stampLine()` asks the **writer**, `deriveFrontmatter()` (the `--fix` leg of `scripts/check-skill-compatibility-version.mjs`), to restamp the file with a probe value, and takes the one line that changed. That is literally "the line the release bot rewrites". The probe has no numeric major, so the writer leaves every `compatibility:` pin alone. If the writer ever rewrites more than one line for the probe, the gate throws instead of guessing (ablation 4 below). `scripts/check-skill-compatibility-version.mjs` is not edited. ## Per-file headroom on `main`, before and after Measured at `d4aaeaf0e` (before) and at this branch (after). All 54 priced files were compared mechanically: 0 headroom differences, 44 rows unchanged, and the 10 rows below moved. | file | count before → after | ceiling before → after | headroom before → after | |---|---|---|---| | objectstack-ai/SKILL.md | 5541 → 5536 | 6806 → 6801 | 1265 → 1265 | | objectstack-api/SKILL.md | 4759 → 4754 | 6319 → 6314 | 1560 → 1560 | | objectstack-automation/SKILL.md | 5784 → 5779 | 5785 → 5780 | **1 → 1** | | objectstack-data/SKILL.md | 6128 → 6123 | 6128 → 6123 | **0 → 0** | | objectstack-formula/SKILL.md | 5404 → 5399 | 6002 → 5997 | 598 → 598 | | objectstack-i18n/SKILL.md | 5033 → 5028 | 6338 → 6333 | 1305 → 1305 | | objectstack-platform/SKILL.md | 5826 → 5821 | 5833 → 5828 | 7 → 7 | | objectstack-query/SKILL.md | 4129 → 4124 | 5552 → 5547 | 1423 → 1423 | | objectstack-ui/SKILL.md | 3848 → 3843 | 3856 → 3851 | 8 → 8 | | objectstack-upgrade/SKILL.md | 6185 → 6180 | 6193 → 6188 | 8 → 8 | The ratcheted subtotal goes from 144142 / 154938 (-10796) to 144092 / 154888 (-10796), so the net headroom is unchanged. ## Self-test (79 cases, roster floor 75 → 79) I replaced route A's 11 batteries with 14. The new pins: - **Stamp width does not change the count.** Stamps `17.7.0`, `18.0.0-next.0`, `18.9.0`, `18.10.0`, `18.10.0-next.12` and `100.0.0` all give one count. A zero-headroom fixture stays green at every one of those widths. - **A wider stamp still reds when the line is priced.** With the line priced as before, the widest stamp reds the same fixture, so the rows above can fail. - **One authored byte over a zero-headroom ceiling is red at every stamp width.** - **Version-shaped text outside the stamp is priced:** prose naming `18.10.0-next.12`, and a `version:` line inside a yaml code block. - **Files without a stamp are priced whole:** files the version script does not stamp, and stamped files where the writer finds no stamp line. - **The writer's probe changes only the stamp line,** never a `compatibility:` pin. - **On every real stamped file:** - the excluded line is its `metadata.version` stamp, cross-checked against `extractMetadataVersion`; - the header's hand-audit recipe gives the gate's count; - **headroom before equals after.** The old reading (whole file priced, restamped at the basis `17.7.0`) minus the new reading equals the recorded re-pin of 5. The ceilings moved by exactly that much, so the headroom is the same number. Every unstamped file reads exactly `countTokens`. - **The re-pin record is pinned:** `STAMP_LINE_REPIN = { basis: 'd4aaeaf0e', stamp: '17.7.0', tokens: 5 }`. `SELF_TEST_BATTERY_FLOOR` was 75 against a 76-row roster. It is now 79, equal to the roster. ## Verification All of the following ran on the final head `0f5c195b1`. - `node scripts/check-skills-token-ratchet.mjs --self-test` printed `✓ check-skills-token-ratchet self-test: 79 cases pass.` (exit 0) - `node scripts/check-skills-token-ratchet.mjs` printed `✓ check-skills-token-ratchet: 54 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted.` (exit 0) - `pnpm check:ratchet-remedy-authority` printed `OK check-ratchet-remedy-authority: 275 scripts swept …; 14 mark the expanding remedy ⛔ MAINTAINER-ONLY …` (exit 0) - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` derived 31 families from the 1-path change set. I ran all 31: the 30 direct commands each exited 0, and `pnpm check:pm-dispatch-gates` printed `✓ dispatch-gates self-test (fast tier): 1833 cases pass`. The `--ran` reconciliation reported 31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN. - Narrowed eslint run: `npx eslint --no-inline-config --format json scripts/check-skills-token-ratchet.mjs` reported 1 file linted, 0 errors and 0 warnings. Three facts show this narrowing leaves nothing out: 1. The file is inside the linted population: the `eslint.config.mjs` glob `**/*.{ts,…,mjs,…}` covers it, and `--print-config` resolves it. 2. The count of files linted (1) comes from eslint's own JSON output. 3. No other file's result can change. This config never enables type-aware linting: the resolved `parserOptions` is `{"ecmaVersion":"latest","sourceType":"module"}`, with no `project`. A diff to this file therefore cannot move the result for any untouched file. - Not run locally: the full `pnpm lint`, which CI owns. The derivation also names some families that it does not place by path; CI runs those. **Ablations.** Each ablation was a one-time run through `node scripts/ablation-replace.mjs`. Every mutation was confirmed on disk (anchor count 1 → 0, blob changed), and every restore was confirmed (blob equal to HEAD `5c98de59e`, `git diff HEAD` empty). 1. **No exclusion** (`unpriced = 0`): the self-test fails 4 cases (width invariance, which got `[32,34,33,35]`; zero-headroom green; hand audit; the real-bundle re-pin). The gate reds `objectstack-automation` (over by 4) and `objectstack-data` (over by 5). 2. **Route A restored** (prerelease segment only): the self-test fails the same 4 cases. Width invariance got `[32,33]`: the 1-byte stable change `18.9.0` → `18.10.0` moves the count, which is the gap this card describes. The gate reds the same two files. 3. **Over-broad exclusion** (every indented `version:` line): the self-test fails 3 cases. "a version-shaped string outside the stamp is priced" got 13 where 18 was expected. The hand audit and the real-bundle re-pin both name `objectstack-platform/SKILL.md`, which carries a second indented `version:` line in its body. The real-bundle pins therefore catch over-exclusion on the real tree, not only on fixtures. 4. **A numeric probe** (`1.0.0`, which also repins `compatibility:`): the self-test and the gate both throw `the stamp writer rewrote 2 lines for one probe stamp … refusing to guess which bytes to leave unpriced.` **The Version Packages tree.** I made a one-time shallow sparse clone of `changeset-release/main` at `0b5641e55` (stamps `"18.0.0-next.0"`, 27-byte lines) in a scratch directory and deleted it afterwards. - Its own route-A gate exited 0. With this script copied in, the gate exited 0 and the self-test passed (79 cases). All 54 files' headroom was identical to `main`'s under this script. - I then restamped the clone's ten stamps to `18.0.0-next.0`, `18.10.0`, `18.10.10-next.12`, `18.100.10` and `100.100.100`. `objectstack-data` read 6123/6123 (headroom 0) under this script at every width. Under route A it read 6128 up to `18.100.10` and went red at `100.100.100` (6129 over 6128). The 4-byte rounding hid the narrower stable growth on that file's current byte count. ## Acceptance notes - **The compatibility pins are still priced.** `deriveFrontmatter` also rewrites the `compatibility:` pins (`@objectstack/spec 17.x`), and those stay priced: A2′ names only the `version:` line. Their width changes only when a major gains a digit (the next case is 99 → 100), so I left this as a note and filed nothing. - **An old reproducibility line is left as history.** The `objectstack-ui` row still has its old "Independently reproducible … ceil(15424 / 4)" line. It already described the 3856 pin's history rather than the file today, and the new `3856 -> 3851` line after it gives the current state. - **Every changed row records why it moved.** Each of the ten rows now has an `N -> N-5: A2′` line, or a trailing `then -5 A2′ (was N)` on the rows whose history is a trailing comment, so no ceiling changed without a recorded reason. - **No changeset.** `scripts/` belongs to the private root package (`"private": true`), so nothing here is published. The label is `skip-changeset`. --- _Generated by [Claude Code](https://claude.ai/code/session_012zwsLMxQBwHUYJgPyrpEvx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d889448 commit 44e2d50

1 file changed

Lines changed: 208 additions & 95 deletions

File tree

0 commit comments

Comments
 (0)