feat(skill-learning): merge plugin skill updates
This commit is contained in:
65
app-instance/backend/beaver/plugins/tree_merge.py
Normal file
65
app-instance/backend/beaver/plugins/tree_merge.py
Normal file
@ -0,0 +1,65 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user