"""Reconcile plan-detected and diff-detected resources into a catalog. Schema: one entry per (source_dir, local_address). Each entry carries a list of CatalogInstance (plan_dir, address_at_plan, action) so a module reviewed at N callsites yields one entry, not N. """ from __future__ import annotations from dataclasses import dataclass from scripts.manifest import CatalogEntry, CatalogInstance, ModuleGraphEntry @dataclass(frozen=True) class PlanHit: address: str type: str action: str @dataclass(frozen=True) class DiffHit: source_dir: str local_address: str type: str def _strip_module_prefix(address: str) -> tuple[list[str], str]: parts = address.split(".") locals_: list[str] = [] while len(parts) >= 2 and parts[0] == "module": locals_.append(parts[1]) parts = parts[2:] return locals_, ".".join(parts) def _plan_source_dir(plan_dir: str, address: str, module_graph: dict[str, ModuleGraphEntry]) -> str: module_locals, _ = _strip_module_prefix(address) if not module_locals: return plan_dir current_dir = plan_dir for module_local in module_locals: next_dir = None has_explicit_mapping = False for entry in module_graph.values(): local_names = entry.callsite_local_names.get(current_dir) if local_names is None: continue has_explicit_mapping = True if module_local in local_names: next_dir = local_names[module_local] break if next_dir is None: if has_explicit_mapping: return current_dir unique_candidates = sorted( mod_dir for mod_dir, entry in module_graph.items() if current_dir in entry.callsites ) if len(unique_candidates) != 1: return current_dir next_dir = unique_candidates[0] current_dir = next_dir return current_dir def build_catalog( plan_hits: dict[str, list[PlanHit]], diff_hits: list[DiffHit], module_graph: dict[str, ModuleGraphEntry], ) -> list[CatalogEntry]: by_key: dict[tuple[str, str], CatalogEntry] = {} def _get(source_dir: str, local_addr: str, type_: str) -> CatalogEntry: k = (source_dir, local_addr) if k not in by_key: by_key[k] = CatalogEntry( source_dir=source_dir, local_address=local_addr, type=type_, source="plan", instances=[], ) return by_key[k] for plan_dir, hits in plan_hits.items(): for h in hits: source_dir = _plan_source_dir(plan_dir, h.address, module_graph) _, local_addr = _strip_module_prefix(h.address) entry = _get(source_dir, local_addr, h.type) entry.instances.append(CatalogInstance( plan_dir=plan_dir, address_at_plan=h.address, action=h.action, )) for d in diff_hits: k = (d.source_dir, d.local_address) if k in by_key: existing = by_key[k] by_key[k] = CatalogEntry( source_dir=existing.source_dir, local_address=existing.local_address, type=existing.type, source="both", instances=list(existing.instances), ) continue graph_entry = module_graph.get(d.source_dir) if graph_entry and graph_entry.callsites: instances = [ CatalogInstance( plan_dir=cs, address_at_plan=f"module.<{d.source_dir}>.{d.local_address}", action="unknown", ) for cs in graph_entry.callsites ] else: instances = [CatalogInstance( plan_dir=d.source_dir, address_at_plan=d.local_address, action="unknown", )] by_key[k] = CatalogEntry( source_dir=d.source_dir, local_address=d.local_address, type=d.type, source="diff", instances=instances, ) return sorted(by_key.values(), key=lambda e: (e.source_dir, e.local_address))