Coverage for little_loops / learning_tests / release_gate.py: 0%

53 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2026-06-29 00:54 -0500

1"""Pre-release audit gate for learning test records (ENH-2214). 

2 

3Checks whether any actively-imported packages have stale or refuted learning 

4test records. Returns 0 (pass) or 1 (block) based on the ``release_gate`` 

5config value. 

6 

7Called from ``/ll:manage-release`` before creating a git tag. 

8""" 

9 

10from __future__ import annotations 

11 

12import datetime 

13import json 

14from pathlib import Path 

15 

16from little_loops.config.core import resolve_config_path 

17from little_loops.config.features import LearningTestsConfig 

18from little_loops.learning_tests import list_records 

19from little_loops.learning_tests.gate import is_record_stale 

20from little_loops.learning_tests.import_scan import get_imported_packages 

21 

22 

23def _load_lt_config(cwd: Path) -> LearningTestsConfig: 

24 config_path = resolve_config_path(cwd) 

25 if config_path is None: 

26 return LearningTestsConfig() 

27 try: 

28 data = json.loads(config_path.read_text(encoding="utf-8")) 

29 except (OSError, json.JSONDecodeError): 

30 return LearningTestsConfig() 

31 if not isinstance(data, dict): 

32 return LearningTestsConfig() 

33 return LearningTestsConfig.from_dict(data.get("learning_tests", {})) 

34 

35 

36def run_release_gate(cwd: Path, *, base_dir: Path | None = None) -> int: 

37 """Run the learning-test pre-release audit gate. 

38 

39 Args: 

40 cwd: Project root used to load config and resolve ``scan_dirs``. 

41 base_dir: Override for the ``.ll/learning-tests`` directory (useful in tests). 

42 

43 Returns: 

44 0 if the gate passes (no issues, ``release_gate="warn"``, or ``enabled=False``). 

45 1 if ``release_gate="block"`` and stale/refuted active packages are found. 

46 """ 

47 lt_config = _load_lt_config(cwd) 

48 

49 if not lt_config.enabled: 

50 return 0 

51 

52 resolved_base = base_dir if base_dir is not None else cwd / ".ll" / "learning-tests" 

53 records = list_records(base_dir=resolved_base) 

54 

55 problem_records = [ 

56 r 

57 for r in records 

58 if r.status == "refuted" or is_record_stale(r, lt_config.stale_after_days) 

59 ] 

60 

61 if not problem_records: 

62 return 0 

63 

64 source_dirs = [cwd / d for d in lt_config.scan_dirs] 

65 imported_packages = get_imported_packages(source_dirs) 

66 

67 hits = [r for r in problem_records if r.target in imported_packages] 

68 

69 if not hits: 

70 return 0 

71 

72 today = datetime.date.today() 

73 print("") 

74 print("⚠ Learning Test Pre-Release Audit") 

75 print(f"{'Package':<30} {'Status':<10} {'Record Date':<14} Days Since Proven") 

76 print("-" * 70) 

77 for record in hits: 

78 status = "refuted" if record.status == "refuted" else "stale" 

79 try: 

80 record_date = datetime.date.fromisoformat(record.date) 

81 age = str((today - record_date).days) 

82 except (ValueError, TypeError): 

83 age = "?" 

84 print(f"{record.target:<30} {status:<10} {record.date:<14} {age}") 

85 print("") 

86 

87 if lt_config.release_gate == "block": 

88 print( 

89 "✗ Release blocked: fix or re-prove the above records, " 

90 "or set release_gate: warn to proceed." 

91 ) 

92 return 1 

93 

94 print("⚠ Continuing with warning (set release_gate: block to abort on stale/refuted records).") 

95 return 0