Coverage for little_loops / decisions_sync.py: 0%
20 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-06-08 15:34 -0500
« prev ^ index » next coverage.py v7.12.0, created at 2026-06-08 15:34 -0500
1"""Sync decisions log active rules to .ll/ll.local.md."""
3from __future__ import annotations
5from pathlib import Path
7from little_loops.decisions import _DEFAULT_LOG_PATH, RuleEntry, list_entries, resolve_active
8from little_loops.file_utils import atomic_write
11def _resolve_path(path: Path | None) -> Path:
12 return path if path is not None else Path.cwd() / _DEFAULT_LOG_PATH
15def sync_to_local_md(path: Path | None = None) -> None:
16 """Write active required rules to ## Active Rules in ll.local.md.
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"
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)
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"
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)