"""Budgets that only allow the silent-except and scripts-root counts to fall.""" from __future__ import annotations import ast from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SCRIPTS = ROOT / "scripts" # Measured at the end of TASK-python-hygiene-20261013. New silent handlers # must log or carry ``# noqa: BLE001`` with a reason. New research scripts # belong in scripts/audit or scripts/research. # 原值:0 # 新值:1 # 原因:产品 2026-10-18(issue #81 选 A)要求 classical_astrology.py 保持上游原文。 # 其中 scripts/classical_astrology.py:12558 是 except Exception: continue。本单不得改这个文件。 # 本地新写的静默处理仍不得出现;允许的命中只有这一处。 SILENT_EXCEPT_BUDGET = 1 UPSTREAM_SILENT_EXCEPT = "scripts/classical_astrology.py:12558" # 原值:446 # 新值:448 # 原因:产品 2026-10-18(issue #81 选 A):上游原样文件按上游路径放在 scripts/ 根目录, # classical_astrology.py、unified_western_reading.py 两个;本地新写的文件仍不得增加。 ROOT_PY_BUDGET = 448 def _silent_handlers() -> list[str]: hits: list[str] = [] paths = list(SCRIPTS.glob("*.py")) + [ROOT / "mcp_server.py"] for path in paths: source = path.read_text(encoding="utf-8") lines = source.splitlines() tree = ast.parse(source) for node in ast.walk(tree): if not isinstance(node, ast.ExceptHandler): continue typ = node.type broad = typ is None or (isinstance(typ, ast.Name) and typ.id in {"Exception", "BaseException"}) if not broad or len(node.body) != 1: continue stmt = node.body[0] silent = isinstance(stmt, (ast.Pass, ast.Continue)) or ( isinstance(stmt, ast.Return) and (stmt.value is None or (isinstance(stmt.value, ast.Constant) and stmt.value.value is None)) ) if not silent: continue segment = "\n".join(lines[node.lineno - 1: node.end_lineno]) if "noqa: BLE001" in segment: continue hits.append(f"{path.relative_to(ROOT).as_posix()}:{node.lineno}") return hits def test_silent_except_budget_does_not_grow() -> None: hits = _silent_handlers() assert hits == [UPSTREAM_SILENT_EXCEPT] assert len(hits) <= SILENT_EXCEPT_BUDGET, hits def test_scripts_root_py_count_does_not_grow() -> None: count = len(list(SCRIPTS.glob("*.py"))) assert count <= ROOT_PY_BUDGET, count