"""Map changed dirs to plan units, preserving the direct trigger directories.""" from __future__ import annotations from pathlib import Path from scripts.manifest import ModuleGraphEntry from scripts.plan_unit import classify_dir, DirKind def resolve_plan_units( repo_root: Path | str, changed_dirs: set[str], module_graph: dict[str, ModuleGraphEntry], ) -> tuple[dict[str, list[str]], list[str]]: """Return (plan_units, orphan_modules). plan_units maps plan_dir -> sorted-unique list of changed dirs that triggered it. Direct Terragrunt or root-module changes remain in the trigger set so later manifest assembly can attach per-plan-unit change context. orphan_modules is a list of changed module dirs with zero callsites. """ plan_units: dict[str, set[str]] = {} orphans: list[str] = [] for d in sorted(changed_dirs): kind = classify_dir(repo_root, d) if kind is DirKind.PLAN_UNIT: plan_units.setdefault(d, set()).add(d) elif kind is DirKind.MODULE: entry = module_graph.get(d) callsites = list(entry.callsites) if entry else [] if not callsites: orphans.append(d) continue for cs in callsites: plan_units.setdefault(cs, set()).add(d) return ( {k: sorted(v) for k, v in sorted(plan_units.items())}, sorted(orphans), )