'use strict'; /** * @file workflow-size.cjs * * Single source of truth for measuring workflow `.md` file sizes in bytes. * * Shared by `tests/workflow-size-budget.test.cjs` (the CI guard) and * `scripts/update-size-baseline.cjs` (the baseline generator) so the two can * never disagree on HOW a file is measured. A divergence between the generator * and the guard would silently mis-record the baseline (issue #1074). */ const fs = require('fs'); const path = require('path'); const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows'); /** * Byte size of a file, counted as on an LF (Unix) checkout. * * The size budget is calibrated against `wc -c` on a Unix (LF) checkout, but * these `.md` files have no `eol=lf` in `.gitattributes`, so Windows checks * them out as CRLF. Counting raw on-disk bytes there adds one byte per line, * a Windows-only false positive that diverges from the LF calibration basis * (issue #683). Stripping CR yields the same LF byte count on every platform. * This is still a raw byte count (not a trailing-newline-stripping line count). * * @param {string} filePath - Absolute or relative path to the file. * @returns {number} LF-normalized byte length. */ function lfByteCount(filePath) { const content = fs.readFileSync(filePath, 'utf-8'); return Buffer.byteLength(content.replace(/\r\n/g, '\n'), 'utf-8'); } /** * List top-level workflow stems (filenames without the `.md` extension), sorted. * Non-recursive by design: per-mode bodies under `workflows//modes/` and * templates are NOT measured — only the always-loaded top-level workflows. * * @param {string} [dir] - Workflows directory (defaults to the canonical one). * @returns {string[]} Sorted stems, e.g. `['autonomous', 'plan-phase', ...]`. */ function listWorkflowStems(dir = WORKFLOWS_DIR) { return fs .readdirSync(dir) .filter((f) => f.endsWith('.md')) .map((f) => f.replace(/\.md$/, '')) .sort(); } /** * Measure every top-level workflow file, keyed by filename (`.md`). * * @param {string} [dir] - Workflows directory (defaults to the canonical one). * @returns {Object} Map of `.md` → LF byte size, with * keys inserted in sorted order. */ function measureWorkflows(dir = WORKFLOWS_DIR) { const out = {}; for (const stem of listWorkflowStems(dir)) { out[`${stem}.md`] = lfByteCount(path.join(dir, `${stem}.md`)); } return out; } module.exports = { WORKFLOWS_DIR, lfByteCount, listWorkflowStems, measureWorkflows };