Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 41 additions & 63 deletions scripts/check-file-sizes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,21 @@
past these caps and decomposing them mechanically would hurt readability.

Files already over the hard cap on day one are listed in
``scripts/file-size-allowlist.yaml`` with their line and byte baselines.
The lint allows allowlisted files to stay over the cap, but rejects further
growth past the recorded baseline -- decomposition follow-ups land as the
file shrinks. Removing a file from the allowlist (or letting it grow under
the cap) is encouraged as cleanup proceeds.
``scripts/file-size-allowlist.yaml``. The lint allows allowlisted files to
stay over the cap regardless of size; per-file size baselines were dropped
because every unrelated PR that touched one of these files needed a
baseline bump in the allowlist, conflicting with every other in-flight PR.
Removing a file from the allowlist (or letting it drop under the cap) is
encouraged as decomposition proceeds.

Usage:
scripts/check-file-sizes.py
scripts/check-file-sizes.py --update-allowlist # rewrite baselines
scripts/check-file-sizes.py --update-allowlist # add over-cap files
scripts/check-file-sizes.py --list # report all files

Exit codes:
0 no violations
1 one or more files exceed caps or grew past their allowlist baseline
1 one or more non-allowlisted files exceed the hard cap
"""

from __future__ import annotations
Expand Down Expand Up @@ -62,13 +63,6 @@ class Caps:
soft_bytes: int


@dataclass(frozen=True)
class Baseline:
lines: int
bytes: int
issue: str | None = None


@dataclass(frozen=True)
class FileStats:
path: Path
Expand All @@ -79,7 +73,9 @@ class FileStats:
@dataclass
class Config:
caps: Caps
baselines: dict[str, Baseline]
# path -> optional tracking issue. Membership is what gates the lint;
# the issue field is documentation only.
allowlist: dict[str, str | None]


def load_config(path: Path | None = None) -> Config:
Expand All @@ -95,15 +91,14 @@ def load_config(path: Path | None = None) -> Config:
soft_bytes=int(caps_raw["soft_bytes"]),
)
files_raw: dict[str, Any] = raw.get("files") or {}
baselines = {
rel: Baseline(
lines=int(entry["lines"]),
bytes=int(entry["bytes"]),
issue=(str(entry["issue"]) if entry.get("issue") is not None else None),
)
for rel, entry in files_raw.items()
}
return Config(caps=caps, baselines=baselines)
allowlist: dict[str, str | None] = {}
for rel, entry in files_raw.items():
if isinstance(entry, dict):
issue = entry.get("issue")
allowlist[rel] = str(issue) if issue is not None else None
else:
allowlist[rel] = None
return Config(caps=caps, allowlist=allowlist)


def is_test_file(rel: Path) -> bool:
Expand Down Expand Up @@ -146,37 +141,24 @@ def evaluate(
errors: list[str] = []
warnings: list[str] = []
caps = config.caps
baseline = config.baselines.get(rel)
in_allowlist = rel in config.allowlist

over_hard_lines = stats.lines > caps.hard_lines
over_hard_bytes = stats.bytes > caps.hard_bytes

if over_hard_lines or over_hard_bytes:
if baseline is None:
if not in_allowlist:
errors.append(
f"{rel}: {stats.lines} lines / {stats.bytes} bytes exceeds hard cap "
f"({caps.hard_lines} lines / {caps.hard_bytes} bytes). "
"Decompose the file or, if you cannot in this PR, add it to "
"scripts/file-size-allowlist.yaml with a tracking issue."
)
else:
if stats.lines > baseline.lines:
errors.append(
f"{rel}: {stats.lines} lines exceeds allowlist baseline "
f"({baseline.lines}). Reduce the file or open a separate PR "
"raising the baseline with justification."
)
if stats.bytes > baseline.bytes:
errors.append(
f"{rel}: {stats.bytes} bytes exceeds allowlist baseline "
f"({baseline.bytes}). Reduce the file or open a separate PR "
"raising the baseline with justification."
)
return errors, warnings

# Soft warnings are skipped for allowlisted files -- they're already
# tracked for decomposition.
if baseline is not None:
if in_allowlist:
return errors, warnings

if stats.lines > caps.soft_lines:
Expand Down Expand Up @@ -207,18 +189,17 @@ def check_all(repo_root: Path = REPO_ROOT) -> tuple[list[str], list[str], list[s
errors.extend(file_errors)
warnings.extend(file_warnings)

stale = sorted(rel for rel in config.baselines if rel not in seen)
stale = sorted(rel for rel in config.allowlist if rel not in seen)
return errors, warnings, stale


def write_allowlist(config: Config, baselines: dict[str, Baseline]) -> None:
"""Rewrite the allowlist file preserving caps + sorted baselines."""
def write_allowlist(config: Config, allowlist: dict[str, str | None]) -> None:
"""Rewrite the allowlist file preserving caps + sorted entries."""

def _entry(b: Baseline) -> dict[str, Any]:
out: dict[str, Any] = {"lines": b.lines, "bytes": b.bytes}
if b.issue is not None:
out["issue"] = b.issue
return out
def _entry(issue: str | None) -> Any:
if issue is None:
return None
return {"issue": issue}

payload: dict[str, Any] = {
"caps": {
Expand All @@ -227,32 +208,29 @@ def _entry(b: Baseline) -> dict[str, Any]:
"soft_lines": config.caps.soft_lines,
"soft_bytes": config.caps.soft_bytes,
},
"files": {rel: _entry(b) for rel, b in sorted(baselines.items())},
"files": {rel: _entry(issue) for rel, issue in sorted(allowlist.items())},
}
ALLOWLIST_PATH.write_text(yaml.safe_dump(payload, sort_keys=False))


def update_allowlist(repo_root: Path = REPO_ROOT) -> int:
"""Refresh allowlist baselines from current file sizes.
"""Sync the allowlist with the current set of over-cap files.

The ``issue:`` tracking field on each existing entry is carried forward;
losing it would drop the link between the file and its decomposition
follow-up.
Adds over-cap files not yet listed (with no issue link) and drops
entries for files that have shrunk under the cap or no longer exist.
The ``issue:`` tracking field on each existing entry is carried
forward; losing it would drop the link between the file and its
decomposition follow-up.
"""
config = load_config()
new_baselines: dict[str, Baseline] = {}
new_allowlist: dict[str, str | None] = {}
for path in iter_source_files(repo_root):
rel = str(path.relative_to(repo_root))
stats = measure(path)
if stats.lines > config.caps.hard_lines or stats.bytes > config.caps.hard_bytes:
existing = config.baselines.get(rel)
new_baselines[rel] = Baseline(
lines=stats.lines,
bytes=stats.bytes,
issue=existing.issue if existing is not None else None,
)
write_allowlist(config, new_baselines)
print(f"Wrote {len(new_baselines)} entries to {ALLOWLIST_PATH.name}")
new_allowlist[rel] = config.allowlist.get(rel)
write_allowlist(config, new_allowlist)
print(f"Wrote {len(new_allowlist)} entries to {ALLOWLIST_PATH.name}")
return 0


Expand All @@ -274,7 +252,7 @@ def main(argv: list[str] | None = None) -> int:
parser.add_argument(
"--update-allowlist",
action="store_true",
help="Rewrite scripts/file-size-allowlist.yaml from current file sizes.",
help="Sync scripts/file-size-allowlist.yaml with current over-cap files.",
)
parser.add_argument(
"--list",
Expand Down
47 changes: 9 additions & 38 deletions scripts/file-size-allowlist.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# Allowlist for scripts/check-file-sizes.py.
#
# Each entry grandfathers a Python source file at the line and byte count it
# had when added here. The lint allows files in this list to remain over the
# global cap, but rejects any further growth past the recorded baselines.
# Each entry grandfathers a Python source file so the lint allows it to
# remain over the global cap. Allowlisted files may grow freely -- the
# allowlist's only role is to say "this file is exempt from the global
# size cap." Decompose listed files in follow-up PRs and remove them from
# the allowlist when they drop back under the global cap.
#
# Decompose listed files in follow-up PRs and remove them from the allowlist
# when they drop back under the global cap. New files are NOT eligible — add
# only files that pre-date the lint or whose decomposition is already tracked
# as a separate issue.
# Per-file size baselines were dropped because every unrelated PR that
# touched one of these files needed a baseline bump here, which conflicted
# with every other in-flight PR.
#
# Schema:
# caps: { hard_lines, hard_bytes, soft_lines, soft_bytes }
# files: { <repo-relative path>: { lines: int, bytes: int, issue: str|null } }
# files: { <repo-relative path>: null | { issue: str } }
caps:
hard_lines: 1500
hard_bytes: 100000
Expand All @@ -20,62 +21,32 @@ caps:

files:
orchestrator/routes/pipelines.py:
lines: 15593
bytes: 681405
issue: "2248"
gateway/gateway.py:
lines: 9754
bytes: 373121
issue: "2248"
sandbox/egg_lib/orch_cli.py:
lines: 3512
bytes: 127924
issue: "2248"
orchestrator/mcp_tools.py:
lines: 2817
bytes: 118448
issue: "2248"
orchestrator/gateway_client.py:
lines: 2392
bytes: 88655
issue: "2248"
shared/egg_contracts/checkpoint_cli.py:
lines: 2233
bytes: 81972
issue: "2248"
sandbox/entrypoint.py:
lines: 2109
bytes: 85051
issue: "2248"
gateway/worktree_manager.py:
lines: 2090
bytes: 83664
issue: "2248"
gateway/git_client.py:
lines: 2032
bytes: 66936
issue: "2248"
orchestrator/overseer/monitor.py:
lines: 2005
bytes: 83921
issue: "2248"
orchestrator/peer_consensus.py:
lines: 2003
bytes: 85965
issue: "2248"
orchestrator/routes/signals.py:
lines: 1986
bytes: 74042
issue: "2248"
gateway/checkpoint_handler.py:
lines: 1655
bytes: 61597
issue: "2248"
scripts/select_tests.py:
lines: 1875
bytes: 75206
issue: "2248"
orchestrator/routes/deployment.py:
lines: 1604
bytes: 56130
issue: "2248"
Loading
Loading