import { type ChangedFiles } from './file-size-baseline.js'; /** * Paths exempt from the file-size cap. The cap is Karpathy's "Simplicity * First": it exists to make a human *simplify* an over-long file. A path is * therefore exempt exactly when no such simplification exists — which is two * kinds of file, both listed below so the reason is visible next to the rule. * * Tool output (whole-cloth generated, nobody maintains it by hand): * `.peaks/**` is Peaks-Loop's own state store and holds three derived indexes * already over the cap (`memory/index.json`, `lint/baseline.json`, * `retrospective/index.json`); exempting only `memory/` would leave the other * two false positives intact. No source module lives there, and its largest * hand-authored file is under 500 lines. Lockfiles are regenerated on every * install. * * Append-only records: `CHANGELOG.md` is history, so its length is a function * of how long the project has existed, not of anyone's design choices — the * only way to "fix" a violation would be to delete the record. Left checked, * this gate is reliably red on every release, precisely when it cannot be * acted on, which trains people to ignore it. A nested changelog under * `packages/` is the same kind of record. * * Declared once, here — do not add special-cases in the scan loop. */ export declare const SIZE_CAP_EXEMPT_PATTERNS: readonly string[]; export declare function isSizeCapExempt(file: string): boolean; export type FileSizeViolation = { file: string; lines: number; /** The cap this file was measured against, resolved per path by the policy * module (300 raw lines; 500 under root `tests/`), or the caller's override. */ cap: number; /** * The file's raw-line count at `baseRef`, or `null` when the file did not * exist there — i.e. THIS change CREATED it over the cap. * * A changed file that is over its cap but NOT larger than its own state at * `baseRef` is not a violation: the whole-tree `fileSizeOverCap` ratchet * already accepts it as a standing quantity, so touching it must stay legal. * Only growth travels here — including growth that a rename carried along, * which is measured against the pre-rename path. */ previousLines: number | null; }; export type FileSizeScanResult = { ok: boolean; /** The caller's `threshold` override, or `null` when every file was measured * against its own policy cap. One number cannot describe a two-cap policy, * so the number that decided a file travels on that file's violation. */ threshold: number | null; checkedFiles: number; /** Paths skipped by SIZE_CAP_EXEMPT_PATTERNS (tool output + append-only * records). Reported so the exemption is auditable rather than a silent * skip. */ exemptFiles: string[]; /** Files that appeared in `git diff` but no longer exist on disk. Pre-#015 * the scan crashed on these via ENOENT; now they are reported, not counted. */ deletedFiles: string[]; /** * Changed files that ARE over their cap right now but this change did not * enlarge (they were already that large at `baseRef`, or larger). They * are NOT violations — the ratchet accepts them — but they are named here * rather than dropped, so "0 violations" cannot mean "nothing was over the * cap"; the same reason `exemptFiles` and `outOfScopeFiles` exist. */ acceptedOverCapFiles: string[]; /** Changed files the file-size policy does not measure at all: outside its * four scope directories, or with an extension it does not count. Reported * rather than silently skipped, for the same reason `exemptFiles` is. * * the scan checked used to be measured against `fileSizeCapFor`, whose * else-branch is 300 — a number that is only defined for `src`/`packages`/ * `scripts`. So an out-of-scope path (a `.md`, a file under `.husky/`) could * redden `request transition` at 301 lines while contributing nothing to the * `fileSizeOverCap` row that ratchets the policy: no ceiling to descend, no * split campaign with a duty to clear it, and no verdict a contributor could * act on. Enforcing the cap wider than the declared scope is not a stricter * gate, it is an unauditable one. * * A caller that names `threshold` explicitly opts out of the scope rule — * "apply this number to every changed file" is the documented meaning of the * flag — and then nothing is out of scope. */ outOfScopeFiles: string[]; violations: FileSizeViolation[]; }; export type FileSizeScanOptions = { projectRoot: string; /** Compare working tree against this ref. Default 'HEAD'. */ baseRef?: string; /** * Line count threshold applied to EVERY file. When omitted, each file is * measured against the cap `file-size-policy.ts` defines for its directory — * the policy is the default, this option only overrides it. */ threshold?: number; /** * The caller's own list, when it has one — the commit hook is handed the STAGED * paths by lint-staged. It RESTRICTS the git change set (a path git does not * report as changed is skipped, never measured) and never widens it; omitted, * git alone decides the population. */ files?: readonly string[]; /** * A change set the caller already read (the transition gate shares one read * with the declared-type rule). When present, NO git enumeration runs here; * `files` restriction is the caller's responsibility. */ changedFiles?: ChangedFiles; }; export declare function scanFileSize(options: FileSizeScanOptions): FileSizeScanResult;