"""Deterministic path-level three-way merge for plugin supporting files.""" from __future__ import annotations from dataclasses import dataclass, field from typing import Any @dataclass(frozen=True, slots=True) class SupportingFileDecision: path: str source: str def to_dict(self) -> dict[str, Any]: return {"path": self.path, "source": self.source} @dataclass(frozen=True, slots=True) class SupportingFileConflict: path: str reason: str def to_dict(self) -> dict[str, Any]: return {"path": self.path, "reason": self.reason} @dataclass(frozen=True, slots=True) class SupportingFileMergePlan: files: dict[str, SupportingFileDecision] = field(default_factory=dict) conflicts: list[SupportingFileConflict] = field(default_factory=list) def to_dict(self) -> dict[str, Any]: return { "files": {path: decision.to_dict() for path, decision in sorted(self.files.items())}, "conflicts": [conflict.to_dict() for conflict in self.conflicts], } def merge_supporting_file_trees( *, base: dict[str, Any], local: dict[str, Any], upstream: dict[str, Any], ) -> SupportingFileMergePlan: decisions: dict[str, SupportingFileDecision] = {} conflicts: list[SupportingFileConflict] = [] for path in sorted({*base.keys(), *local.keys(), *upstream.keys()} - {"SKILL.md"}): b = base.get(path) l = local.get(path) u = upstream.get(path) if l == u and l is not None: decisions[path] = SupportingFileDecision(path=path, source="local") elif l == b and u is not None: decisions[path] = SupportingFileDecision(path=path, source="upstream") elif u == b and l is not None: decisions[path] = SupportingFileDecision(path=path, source="local") elif b is None and l is None and u is not None: decisions[path] = SupportingFileDecision(path=path, source="upstream") elif b is None and u is None and l is not None: decisions[path] = SupportingFileDecision(path=path, source="local") elif b is not None and l is None and u is None: continue else: conflicts.append(SupportingFileConflict(path=path, reason="divergent supporting-file change")) return SupportingFileMergePlan(files=decisions, conflicts=conflicts)