enhance(#4261): report size-cap headroom on every run, with a reserved margin (#4418)

* enhance(#4261): report size-cap headroom on every run, with a reserved margin

The tier caps are red lines and none of them moves here. What was missing is
everything below the red line: a passing run said nothing, so a contributor
at 99.6% of a cap and one at 60% got identical feedback, and the density that
produces merge-time collisions was invisible to the people creating it.

Two levels, matching the shape execute-phase.md already carries by hand (a
hard ceiling plus a lower margin "so minor future edits don't re-trip the
gate") and which was until now the only capped file with one:

  1. a headroom census printed every run, green included, sorted
     least-headroom-first, and appended to the GitHub job summary
  2. a 95% reserved margin that names the files inside it and REPORTS
     rather than fails

The margin deliberately does not fail. A cap breach is a red line; a file at
96% is not broken, it is a file whose next contributor should extract before
adding. Failing there would create a second red line and force exactly the
+N bumps the policy forbids. Neither level asserts a count, so this adds no
snapshot to regenerate — the per-file size baseline was deleted by #2724 for
conflicting on 7 of 7 PRs that touched it.

Also deletes rather than refreshes the per-tier high-water comments in both
guard files. They were measured once and then diverged from the tree: the
LARGE line still claimed "gsd-executor 42,342 -> ~6.8 KB headroom" while the
real high-water sat at 99.6% of that cap, so the comment documenting the
margin was itself why nobody noticed the margin was gone.

As measured on next by the new census, the pressure has grown since the
issue was filed: gsd-plan-checker.md has 9 bytes of headroom, gsd-verifier.md
21, and plan-phase.md 14.

* chore(#4261): add changeset for the size-cap headroom census

* test(#4261): exercise reserved-margin boundaries

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
Michel Moreira
2026-09-07 23:04:48 -03:00
committed by GitHub
parent 95529ef153
commit 733bec3ad1
5 changed files with 407 additions and 8 deletions

View File

@@ -89,10 +89,149 @@ function measureWorkflows(dir = WORKFLOWS_DIR) {
return measureMdFiles(dir);
}
/**
* #4261: the reserved margin, as a fraction of a tier's hard cap.
*
* The hard caps are red lines and stay exactly where they are. This is the
* second, softer level the caps never had: `execute-phase.md` already carries
* a hand-rolled version of this shape (a hard `< 93600` plus a `<= 93400`
* margin whose own message says it exists "so minor future edits don't
* re-trip the gate"), and it is the only capped file that does. Everywhere
* else a file is either fine or already over, with nothing in between — so
* the first signal a contributor gets is a failure, and by then the cheap
* moment to extract has passed.
*
* 95% is deliberately not derived from anything. It is the round number the
* issue proposed, and the census below is what makes it reviewable: if it
* turns out to name too many or too few files, that is visible in one table
* rather than argued from first principles.
*/
const MARGIN_RATIO = 0.95;
/**
* The reserved-margin threshold for a hard cap, in bytes.
*
* Floor, not round: the margin must never land ON or above the cap it is
* meant to sit under, however small the cap.
*
* @param {number} cap - Tier hard cap in bytes.
* @returns {number} Margin threshold in bytes.
*/
function marginFor(cap) {
return Math.floor(cap * MARGIN_RATIO);
}
/**
* Build the headroom census for a set of measured files.
*
* Sorted by pressure (least headroom first) because that is the reading
* order that matters: the top row is the file that will break next.
*
* @param {Object<string, number>} sizes - Map of filename -> LF byte size.
* @param {function(string): {tier: string, cap: number}} capFor - Tier lookup, keyed by stem.
* @returns {Array<{name: string, tier: string, bytes: number, cap: number, margin: number, headroom: number, usedPct: number, overMargin: boolean}>}
*/
function buildHeadroomRows(sizes, capFor) {
return Object.entries(sizes)
.map(([file, bytes]) => {
const name = file.replace(/\.md$/, '');
const { tier, cap } = capFor(name);
const margin = marginFor(cap);
return {
name,
tier,
bytes,
cap,
margin,
headroom: cap - bytes,
usedPct: (bytes / cap) * 100,
overMargin: bytes > margin,
};
})
.sort((a, b) => a.headroom - b.headroom || a.name.localeCompare(b.name));
}
/**
* Render the census as a fixed-width text table for the test diagnostics.
*
* @param {ReturnType<typeof buildHeadroomRows>} rows
* @param {{limit?: number}} [opts] - How many rows to render (default: all).
* @returns {string[]} Lines, ready to hand to `t.diagnostic` one at a time.
*/
function formatHeadroomTable(rows, { limit = Infinity } = {}) {
const shown = rows.slice(0, limit);
const width = Math.max(4, ...shown.map((r) => r.name.length));
const head = `${'file'.padEnd(width)} ${'tier'.padEnd(7)} ${'bytes'.padStart(7)} ${'cap'.padStart(7)} ${'margin'.padStart(7)} ${'headroom'.padStart(8)} ${'used'.padStart(6)}`;
const body = shown.map(
(r) =>
`${r.name.padEnd(width)} ${r.tier.padEnd(7)} ${String(r.bytes).padStart(7)} ${String(r.cap).padStart(7)} ${String(r.margin).padStart(7)} ${String(r.headroom).padStart(8)} ${r.usedPct.toFixed(1).padStart(5)}%`,
);
return [head, ...body];
}
/**
* Render the census as a GitHub job-summary markdown table.
*
* Only the rows over the reserved margin: a job summary that lists all 124
* capped files is a log dump nobody reads, and every row below the margin is
* by definition not the problem. The count of the rest is still reported so
* an empty table cannot be mistaken for an unmeasured one.
*
* @param {string} title - Section heading (e.g. `'Agent size headroom'`).
* @param {ReturnType<typeof buildHeadroomRows>} rows
* @returns {string} Markdown block.
*/
function buildHeadroomSummaryMarkdown(title, rows) {
const pressured = rows.filter((r) => r.overMargin);
const lines = [`### ${title}`, ''];
if (pressured.length === 0) {
lines.push(`All ${rows.length} files are under the ${Math.round(MARGIN_RATIO * 100)}% reserved margin.`);
return `${lines.join('\n')}\n`;
}
lines.push(
`**${pressured.length} of ${rows.length}** files are over the ${Math.round(MARGIN_RATIO * 100)}% reserved margin.`,
'',
'| file | tier | bytes | cap | headroom | used |',
'| --- | --- | ---: | ---: | ---: | ---: |',
);
for (const r of pressured) {
lines.push(`| \`${r.name}\` | ${r.tier} | ${r.bytes} | ${r.cap} | ${r.headroom} | ${r.usedPct.toFixed(1)}% |`);
}
return `${lines.join('\n')}\n`;
}
/**
* Append a census block to `$GITHUB_STEP_SUMMARY` when running in CI.
*
* No-op off CI, and never throws: this is reporting, and a test suite must
* not go red because a summary file was not writable.
*
* @param {string} title - Section heading.
* @param {ReturnType<typeof buildHeadroomRows>} rows
* @param {NodeJS.ProcessEnv} [env]
* @returns {boolean} Whether anything was written.
*/
function appendHeadroomStepSummary(title, rows, env = process.env) {
if (!env.GITHUB_STEP_SUMMARY) return false;
try {
fs.appendFileSync(env.GITHUB_STEP_SUMMARY, `${buildHeadroomSummaryMarkdown(title, rows)}\n`);
return true;
} catch (err) {
process.stderr.write(`workflow-size: could not write GITHUB_STEP_SUMMARY: ${err.message}\n`);
return false;
}
}
module.exports = {
WORKFLOWS_DIR,
MARGIN_RATIO,
lfByteCount,
listWorkflowStems,
measureMdFiles,
measureWorkflows,
marginFor,
buildHeadroomRows,
formatHeadroomTable,
buildHeadroomSummaryMarkdown,
appendHeadroomStepSummary,
};