Files
gstack/scripts/size-ledger.ts
T
Garry Tanandcapy-ai[bot] 398618259d v1.91.286.0 feat(ship-queue): the ship size cap composes across a merge train; verify refuses a stale all-merged train (#3519) (#3522)
* feat(ship-queue): the ship size cap composes across a merge train; verify refuses a stale all-merged train (#3519 b, c)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (runnable clear-time NEXT, stale exit only on a fetched main, open-slot freeze control, LF size-ledger fixture)

- verify's clear-time NEXT names gh pr view <n> --json state,headRefOid per unreadable slot instead of a pasteable #n@sha that views the current branch's PR
- QUEUE_TRAIN_STALE needs a fetched main (never under --no-fetch) and reports a freeze restored from the notes ref instead of saying the queue is not frozen
- freezeQueue docstring matches the unreadable field
- new test: a train with a slot still OPEN freezes even when another train verified main's sha
- test/size-ledger.test.ts fixture repo sets core.autocrlf false (Windows Free Tests shard 4 counted CRLF bytes)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: CHANGELOG [Unreleased] entry for the ship size ledger and QUEUE_TRAIN_STALE

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: run scripts/size-ledger.ts (check on this tree, refusals write nothing)

The remote pregate refused 82f160c with PREGATE_NO_LANE: no test named scripts/size-ledger.ts. The new case runs the CLI itself.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: spawn scripts/size-ledger.ts with bunIsolationArgs() so native Windows Bun starts it

The CLI test passed --config=/dev/null, which native Bun on Windows cannot open
(#3415); Windows Free Tests shard 6 failed at 4143373 with exit 1 and empty stdout.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* FILES: CHANGELOG.md VERSION agents-digest/gstack-AGENTS.md package.json
MERGE_CONDITION: sha=1b7c8b3af32a093924205f4894cba6c5d224630a
next: NEW_VERSION=1.91.286.0; commit and push only the stamp diff above (FILES), with no edit of your own: the train's verify accepts the CHANGELOG only as the base's plus this PR's entry re-headed; merge with the expected head 1b7c8b3af3 and recompute on a stale queue.
GSTACK_RESULT: skill=restamp status=complete run=/workspace/gstack/.gstack/tmp/restamp-journal.json

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-28 00:27:22 +00:00

70 lines
3.6 KiB
TypeScript
Executable File

#!/usr/bin/env -S bun --no-env-file --config=/dev/null
/**
* Size ledger for a carved skill's union bytes (GSTA-132; format in
* test/helpers/size-ledger.ts).
*
* size-ledger.ts record <skill> --why <text> [--branch <name>]
* set this branch's entry to what this tree's union bytes differ from the
* base plus every other entry; a zero difference deletes the entry
* size-ledger.ts check <skill>
* print measured, expected and each entry; exit 1 on a mismatch
* size-ledger.ts fold <skill> --measured-at <text>
* move the entries' sum into base.json and delete them (a tidy-up PR,
* never a stamp commit); refuses on a mismatch
*/
import { spawnSync } from 'child_process';
import { CARVE_GUARDS } from '../test/helpers/carve-guards';
import { readSkillForParity } from '../test/helpers/parity-harness';
import { foldLedger, ledgerExpected, ledgerFailure, readSizeLedger, recordEntry } from '../test/helpers/size-ledger';
const USAGE = 'usage: size-ledger.ts record <skill> --why <text> [--branch <name>] | check <skill> | fold <skill> --measured-at <text>';
function fail(msg: string, code = 2): never {
console.error(`size-ledger: ${msg}`);
process.exit(code);
}
function flag(args: string[], name: string): string | undefined {
const i = args.indexOf(name);
if (i < 0) return undefined;
const v = args[i + 1];
if (!v || v.startsWith('--')) fail(`${name} needs a value`);
return v;
}
const [cmd, skill, ...rest] = process.argv.slice(2);
if (!cmd || !skill || !['record', 'check', 'fold'].includes(cmd)) fail(USAGE);
const root = spawnSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf-8', timeout: 10_000 }).stdout?.trim() || process.cwd();
const dir = CARVE_GUARDS[skill]?.sizeLedger;
if (!dir) fail(`${skill} has no sizeLedger in test/helpers/carve-guards.ts`);
const measured = readSkillForParity(root, skill, true).unionBytes;
const ledger = readSizeLedger(root, dir);
if ('error' in ledger) fail(ledger.error, 1);
if (cmd === 'check') {
console.log(`base ${ledger.base.union_bytes} (${ledger.base.measured_at})`);
for (const e of ledger.entries) console.log(`${e.bytes > 0 ? '+' : ''}${e.bytes} ${e.file}: ${e.why}`);
console.log(`expected ${ledgerExpected(ledger)} measured ${measured}`);
const failure = ledgerFailure(skill, measured, ledger);
if (failure) fail(failure, 1);
console.log('SIZE_LEDGER: ok');
} else if (cmd === 'record') {
const why = flag(rest, '--why');
if (!why?.trim()) fail('record needs --why "<what changed>"');
const branch = flag(rest, '--branch') ?? spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: root, encoding: 'utf-8', timeout: 10_000 }).stdout?.trim();
if (!branch || branch === 'HEAD') fail('detached HEAD: pass --branch <name>');
if (branch === 'main' || branch === 'master') fail(`refusing to record on ${branch}; record on the branch that made the change`);
let r: { file: string; bytes: number };
try { r = recordEntry(root, ledger, measured, branch, why); } catch (err) { fail((err as Error).message); }
console.log(r.bytes === 0
? `SIZE_LEDGER: ${dir}/${r.file} not needed (this tree matches the ledger without it); expected ${measured}`
: `SIZE_LEDGER: ${dir}/${r.file} ${r.bytes > 0 ? '+' : ''}${r.bytes}; expected ${measured}`);
} else {
const at = flag(rest, '--measured-at');
if (!at?.trim()) fail('fold needs --measured-at "<tree it was measured on>"');
const failure = ledgerFailure(skill, measured, ledger);
if (failure) fail(`fold refused: ${failure}`, 1);
foldLedger(root, ledger, at);
console.log(`SIZE_LEDGER: folded ${ledger.entries.length} entries into ${dir}/base.json = ${measured}`);
}