From 8adb982f36d8e232bb25f928a3974b24b071fa30 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Sun, 14 Jun 2026 11:10:06 +0000 Subject: [PATCH] ci: add a small per-file slack to the type-check gate Allow each file to drift PER_FILE_SLACK (5) errors past its recorded count before failing, so a basedpyright inference ripple in an unrelated file doesn't break the build over a couple of errors. Budgets still record exact counts; the tolerance is applied at check time. --- scripts/type_check_gate.py | 15 +++++++++++---- tests/test_litellm/test_type_check_gate.py | 16 +++++++++++++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/scripts/type_check_gate.py b/scripts/type_check_gate.py index 31c5ae66eec..fbd3b836949 100644 --- a/scripts/type_check_gate.py +++ b/scripts/type_check_gate.py @@ -24,6 +24,11 @@ PATTERNS: Mapping[str, re.Pattern[str]] = { "basedpyright": re.compile(r"^\s*(?P.+?):\d+:\d+ - error:"), } +# Headroom per file on top of its recorded count, so an inference ripple in an +# unrelated file (basedpyright especially) does not fail the build over a couple +# of errors. Small on purpose: a file still fails once it drifts past this. +PER_FILE_SLACK = 5 + class Breach(NamedTuple): file: str @@ -52,11 +57,13 @@ def count_errors(lines: Iterable[str], pattern: re.Pattern[str]) -> dict[str, in return dict(counts) -def evaluate(counts: Mapping[str, int], budget: Mapping[str, int]) -> list[Breach]: +def evaluate( + counts: Mapping[str, int], budget: Mapping[str, int], slack: int +) -> list[Breach]: return sorted( - Breach(file, count, budget.get(file, 0)) + Breach(file, count, budget.get(file, 0) + slack) for file, count in counts.items() - if count > budget.get(file, 0) + if count > budget.get(file, 0) + slack ) @@ -74,7 +81,7 @@ def cmd_update(tool: str, counts: Mapping[str, int]) -> None: def cmd_check(tool: str, counts: Mapping[str, int]) -> None: budget = json.loads(budget_path(tool).read_text()) - breaches = evaluate(counts, budget) + breaches = evaluate(counts, budget, PER_FILE_SLACK) if not breaches: print( f"OK: every file is within its {tool} ceiling ({sum(counts.values())} errors total)" diff --git a/tests/test_litellm/test_type_check_gate.py b/tests/test_litellm/test_type_check_gate.py index a7fd33c2b5a..e12c9df0081 100644 --- a/tests/test_litellm/test_type_check_gate.py +++ b/tests/test_litellm/test_type_check_gate.py @@ -41,14 +41,24 @@ def test_paths_outside_repo_are_skipped(): def test_at_or_under_ceiling_passes(): - assert gate.evaluate({"a.py": 5, "b.py": 3}, {"a.py": 5, "b.py": 10}) == [] + assert gate.evaluate({"a.py": 5, "b.py": 3}, {"a.py": 5, "b.py": 10}, 0) == [] def test_one_more_error_than_ceiling_fails(): - breaches = gate.evaluate({"a.py": 6}, {"a.py": 5}) + breaches = gate.evaluate({"a.py": 6}, {"a.py": 5}, 0) assert breaches == [gate.Breach("a.py", 6, 5)] def test_file_absent_from_budget_has_zero_ceiling(): - breaches = gate.evaluate({"new.py": 1}, {}) + breaches = gate.evaluate({"new.py": 1}, {}, 0) assert breaches == [gate.Breach("new.py", 1, 0)] + + +def test_slack_absorbs_small_increase_then_fails_past_it(): + assert gate.evaluate({"a.py": 10}, {"a.py": 5}, 5) == [] + assert gate.evaluate({"a.py": 11}, {"a.py": 5}, 5) == [gate.Breach("a.py", 11, 10)] + + +def test_slack_applies_to_new_files_too(): + assert gate.evaluate({"new.py": 5}, {}, 5) == [] + assert gate.evaluate({"new.py": 6}, {}, 5) == [gate.Breach("new.py", 6, 5)]