Address PR review feedback (#1698)

- Import URL from node:url to clear ESLint no-undef (coderabbit)
- Throw on unreadable tracked files instead of silently skipping, so a
  file can't evade the cap and false-pass (coderabbit)
- Emit the "baseline can be tightened" note for deleted-only stale entries
  too, with a correct count (greptile)
- Add FNXC:CI comments to the guard and its test per AGENTS.md (greptile)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-21 00:05:41 -07:00
parent 2d6ba99caf
commit b7182da349
2 changed files with 32 additions and 10 deletions

View File

@@ -1,3 +1,12 @@
/*
FNXC:CI 2026-06-21-00:04:
Tests for the line-count guardrail. Pin the behavior the guard promises: a new
file at exactly MAX_LINES passes but one line over fails; grandfathered files may
sit at or below their recorded ceiling but a single line of growth is a failure;
and shrunk, under-cap, or deleted baseline entries surface as tightenable so the
ratchet can only move down. Line counting must agree whether or not the file ends
in a trailing newline.
*/
import test from "node:test";
import assert from "node:assert/strict";
import {

View File

@@ -1,4 +1,15 @@
#!/usr/bin/env node
/*
FNXC:CI 2026-06-21-00:04:
New source files must not be born as god-files. This guard caps new files under
packages/scripts/plugins at MAX_LINES (2000) lines to keep the codebase
splittable and reviewable. The 106 files already over the cap can't be
refactored in one PR, so they are grandfathered through a ratchet baseline
(line-count-baseline.json): each is pinned to its current count and may shrink
but never grow, applying steady downward pressure without blocking unrelated
work. Generated/lock/locale/.d.ts files are out of scope so the guard only
governs hand-written source.
*/
// Repo-wide guard: hand-written source files may not exceed a hard line-count
// cap (MAX_LINES). This stops the next god-file from being born while leaving
// today's known offenders to be refactored down over time.
@@ -18,7 +29,7 @@
// after an intentional, reviewed change to the set of oversized files.
import { readFileSync, writeFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { fileURLToPath, URL } from "node:url";
export const MAX_LINES = 2000;
@@ -91,11 +102,13 @@ export function evaluate(counts, baseline = loadBaseline()) {
export function collectCounts(files = listTrackedSources()) {
const counts = {};
for (const filePath of files) {
// A tracked file that can't be read must fail the guard, not be skipped:
// silently continuing would let an unreadable file evade the cap (false pass).
let content;
try {
content = readFileSync(filePath, "utf8");
} catch {
continue;
} catch (error) {
throw new Error(`[check-file-line-count] failed to read tracked file ${filePath}: ${error.message}`);
}
counts[filePath] = countLines(content);
}
@@ -143,14 +156,14 @@ export function main(argv = process.argv.slice(2)) {
const { violations, staleBaseline } = evaluate(counts);
// Any stale entry — shrunk, dropped under the cap, or deleted — means the
// baseline can ratchet down. Deleted-only entries must trigger this note too,
// otherwise removed files sit in the baseline forever with no prompt to prune.
if (staleBaseline.length > 0) {
const shrunk = staleBaseline.filter((s) => s.reason !== "deleted");
if (shrunk.length > 0) {
console.error(
`[check-file-line-count] note: ${staleBaseline.length} baseline entr(ies) can be tightened ` +
"(files shrank or were removed). Run with --update to ratchet the baseline down.",
);
}
console.error(
`[check-file-line-count] note: ${staleBaseline.length} baseline entr(ies) can be tightened ` +
"(files shrank, dropped under the cap, or were deleted). Run with --update to ratchet the baseline down.",
);
}
if (violations.length === 0) return 0;