Coverage for little_loops / decisions_sync.py: 0%

20 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2026-06-16 13:08 -0500

1"""Sync decisions log active rules to .ll/ll.local.md.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6 

7from little_loops.decisions import _DEFAULT_LOG_PATH, RuleEntry, list_entries, resolve_active 

8from little_loops.file_utils import atomic_write 

9 

10 

11def _resolve_path(path: Path | None) -> Path: 

12 return path if path is not None else Path.cwd() / _DEFAULT_LOG_PATH 

13 

14 

15def sync_to_local_md(path: Path | None = None) -> None: 

16 """Write active required rules to ## Active Rules in ll.local.md. 

17 

18 `path` is the decisions YAML path (e.g. .ll/decisions.yaml). 

19 ll.local.md is resolved as path.parent / "ll.local.md". 

20 """ 

21 decisions_path = _resolve_path(path) 

22 ll_local_path = decisions_path.parent / "ll.local.md" 

23 

24 rules = [ 

25 e 

26 for e in list_entries(decisions_path, type="rule") 

27 if getattr(e, "enforcement", None) == "required" 

28 ] 

29 active_rules = resolve_active(rules) 

30 

31 rules_block = "\n".join(f"- {r.rule}" for r in active_rules if isinstance(r, RuleEntry)) 

32 section = f"## Active Rules\n\n{rules_block}\n" 

33 

34 content = ll_local_path.read_text(encoding="utf-8") if ll_local_path.exists() else "" 

35 if "## Active Rules" in content: 

36 idx = content.rfind("## Active Rules\n") 

37 end = content.find("\n##", idx + 1) 

38 content = content[:idx] + section + (content[end:] if end != -1 else "") 

39 else: 

40 content += f"\n\n{section}" 

41 atomic_write(ll_local_path, content)