Files
msd-core/scripts/update-size-baseline.cjs
Rezolv e4f0910d62 test(#1074): agent-size-budget per-file baseline + line→byte rebase (PR 3/3) (#1097)
Extends the #1074 scheme to tests/agent-size-budget.test.cjs, which used the
same assertTightCeiling tier ratchet but was still line-based (never rebased in
#717). Completes the migration — the last part of the #1074 epic.

- Rebase agent sizing from lines to LF-normalized bytes (#717/#683).
- Delete the 'SIZE: tier anti-creep' describe (3 assertTightCeiling tests);
  add a per-agent baseline (tests/agent-size-baseline.json) as the primary
  anti-creep, and byte hard caps (XL 56 KiB / LARGE 48 KiB / DEFAULT 24 KiB),
  each above its tier high-water with real headroom. No separate new-file cap:
  a net-new agent is DEFAULT-tier, already bounded by the DEFAULT cap.
- Keep the agent-classification tests verbatim.
- scripts/workflow-size.cjs: add generic measureMdFiles(dir, predicate)
  (workflows + agents share one byte-measurement path); measureWorkflows now
  delegates to it.
- scripts/update-size-baseline.cjs: one 'npm run size:baseline' now regenerates
  BOTH the workflow and agent baselines (gsd-* filter for agents).

Rebased onto next after PR 2/3 (#1096) merged: replicate the
scripts/lib/workflow-size.cjs -> scripts/workflow-size.cjs move (PR 1/3) across
the generator and the agent test's require; regenerate the agent baseline
against current agents (a uniform +170 B preamble drift on all 33 since
authoring).

Addresses the #1097 review (trek-e):
- BLOCKER (acceptance criterion 5): document the agent contract in CONTEXT.md.
  Adds RULESET.AGENT_SIZE_BUDGET (caps 57344/49152/24576, per-agent baseline,
  dual size:baseline, shared measureMdFiles seam) and disambiguates it from the
  separate DEFECT.AGENT-FILE-SIZE-CAP-BREACH 45K-CHAR guard (two units, two
  purposes).
- Docs: now that #1096's docs/TESTING-SUITES.md "Workflow size budget" section
  is in next, fold in the agent coverage here (renamed to "Workflow & agent
  size budget"): agent caps + per-agent baseline + the how-to + reference rows,
  and the disambiguation from the 45K-char guard.
- Minor (negative proof): add a boundary-fixture test exercising the hard-cap
  comparison at cap-1/cap/cap+1 through the real lfByteCount path, so a future
  threshold/operator edit can't silently neuter a cap.
- Nit: align the tier test name wording ("stays within") with the <= operator.

Negative proof on a real tracked agent (gsd-planner): baseline catches +10 B;
XL hard cap catches 57,516 > 57,344 with the baseline current.

Closes #1095 (PR 3/3 child); landing this completes the #1074 epic.
2026-06-12 09:58:44 -04:00

69 lines
2.5 KiB
JavaScript

#!/usr/bin/env node
'use strict';
/**
* @file update-size-baseline.cjs
*
* Regenerates the committed per-file workflow size baseline
* (`tests/workflow-size-baseline.json`) from the current workflow files.
*
* Run via `npm run size:baseline` whenever a workflow file legitimately grows
* or shrinks. Growth must still be justified in the PR; this script only
* records the new reality so the CI guard (issue #1074) can diff against it.
*
* Idempotent: running it twice with no file changes produces no diff.
*/
const fs = require('fs');
const path = require('path');
const { measureMdFiles, WORKFLOWS_DIR } = require('./workflow-size.cjs');
const BASELINE_PATH = path.join(__dirname, '..', 'tests', 'workflow-size-baseline.json');
const AGENTS_DIR = path.join(__dirname, '..', 'agents');
const AGENT_BASELINE_PATH = path.join(__dirname, '..', 'tests', 'agent-size-baseline.json');
const isGsdAgent = (f) => f.startsWith('gsd-');
/**
* Serialize a size map to the on-disk baseline format: keys sorted, 2-space
* indent, trailing newline (so the file is a stable, minimal-diff artifact).
*
* @param {Object<string, number>} sizes
* @returns {string}
*/
function serializeBaseline(sizes) {
const sorted = {};
for (const key of Object.keys(sizes).sort()) sorted[key] = sizes[key];
return JSON.stringify(sorted, null, 2) + '\n';
}
/**
* Write a baseline file from the measured `.md` sizes in a directory.
*
* @param {object} [opts]
* @param {string} [opts.dir] - Directory to measure (default: workflows).
* @param {string} [opts.outPath] - Baseline file to write (default: workflow).
* @param {function(string): boolean} [opts.predicate] - Filename filter.
* @returns {{ outPath: string, count: number, content: string }}
*/
function generateBaseline({ dir = WORKFLOWS_DIR, outPath = BASELINE_PATH, predicate } = {}) {
const sizes = measureMdFiles(dir, predicate);
const content = serializeBaseline(sizes);
fs.writeFileSync(outPath, content);
return { outPath, count: Object.keys(sizes).length, content };
}
if (require.main === module) {
const targets = [
{ label: 'workflow', dir: WORKFLOWS_DIR, outPath: BASELINE_PATH },
{ label: 'agent', dir: AGENTS_DIR, outPath: AGENT_BASELINE_PATH, predicate: isGsdAgent },
];
for (const t of targets) {
const { outPath, count } = generateBaseline(t);
process.stdout.write(
`Wrote ${count} ${t.label} sizes to ${path.relative(process.cwd(), outPath)}\n`
);
}
}
module.exports = { generateBaseline, serializeBaseline, BASELINE_PATH, AGENT_BASELINE_PATH };