generated from coulomb/repo-seed
feat(coordination): patch rendering (unified diff) (WP-0008 T3)
render_patch(overlay, base_body) → Patch (pure difflib unified diff, target- labelled); is_empty when unchanged. 3 tests green, pyflakes clean. (ADR-05) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ from shard_wiki.coordination.decision_log import (
|
|||||||
EventType,
|
EventType,
|
||||||
)
|
)
|
||||||
from shard_wiki.coordination.overlay import Overlay, OverlayEngine
|
from shard_wiki.coordination.overlay import Overlay, OverlayEngine
|
||||||
|
from shard_wiki.coordination.patch import Patch, render_patch
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"DecisionLog",
|
"DecisionLog",
|
||||||
@@ -15,4 +16,6 @@ __all__ = [
|
|||||||
"CoordinationState",
|
"CoordinationState",
|
||||||
"Overlay",
|
"Overlay",
|
||||||
"OverlayEngine",
|
"OverlayEngine",
|
||||||
|
"Patch",
|
||||||
|
"render_patch",
|
||||||
]
|
]
|
||||||
|
|||||||
38
src/shard_wiki/coordination/patch.py
Normal file
38
src/shard_wiki/coordination/patch.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
"""Patch rendering — an overlay as a reviewable diff (FederationRequirements ADR-05).
|
||||||
|
|
||||||
|
A pure function over (base body, overlay body): the auditable change proposal that an overlay
|
||||||
|
becomes before it is applied. Markdown/native text in this slice (lossless); a lossy
|
||||||
|
native-syntax-with-fidelity-report variant is later (TSD §A.6).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import difflib
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from shard_wiki.coordination.overlay import Overlay
|
||||||
|
from shard_wiki.model import Identity
|
||||||
|
|
||||||
|
__all__ = ["Patch", "render_patch"]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class Patch:
|
||||||
|
target: Identity
|
||||||
|
diff: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_empty(self) -> bool:
|
||||||
|
return self.diff == ""
|
||||||
|
|
||||||
|
|
||||||
|
def render_patch(overlay: Overlay, base_body: str) -> Patch:
|
||||||
|
"""Render ``base_body`` → ``overlay.body`` as a unified diff against the overlay target."""
|
||||||
|
label = str(overlay.target)
|
||||||
|
lines = difflib.unified_diff(
|
||||||
|
base_body.splitlines(keepends=True),
|
||||||
|
overlay.body.splitlines(keepends=True),
|
||||||
|
fromfile=f"a/{label}",
|
||||||
|
tofile=f"b/{label}",
|
||||||
|
)
|
||||||
|
return Patch(target=overlay.target, diff="".join(lines))
|
||||||
29
tests/test_patch.py
Normal file
29
tests/test_patch.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"""Tests for patch rendering (SHARD-WP-0008 T3)."""
|
||||||
|
|
||||||
|
from shard_wiki.coordination import Overlay, Patch, render_patch
|
||||||
|
from shard_wiki.model import Identity
|
||||||
|
|
||||||
|
|
||||||
|
def _overlay(body: str) -> Overlay:
|
||||||
|
return Overlay("ov1", Identity("shardA", "Home"), base_rev="r1", body=body)
|
||||||
|
|
||||||
|
|
||||||
|
def test_patch_shows_the_change():
|
||||||
|
patch = render_patch(_overlay("line one\nline TWO\n"), "line one\nline two\n")
|
||||||
|
assert isinstance(patch, Patch)
|
||||||
|
assert not patch.is_empty
|
||||||
|
assert "-line two" in patch.diff
|
||||||
|
assert "+line TWO" in patch.diff
|
||||||
|
assert patch.target == Identity("shardA", "Home")
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_patch_when_unchanged():
|
||||||
|
patch = render_patch(_overlay("same\n"), "same\n")
|
||||||
|
assert patch.is_empty
|
||||||
|
assert patch.diff == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_patch_labels_name_the_target():
|
||||||
|
patch = render_patch(_overlay("new\n"), "old\n")
|
||||||
|
assert "a/shardA:Home" in patch.diff
|
||||||
|
assert "b/shardA:Home" in patch.diff
|
||||||
@@ -72,7 +72,7 @@ overlays. Tests: draft recorded + retrievable via fold; overlay id stable.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: SHARD-WP-0008-T3
|
id: SHARD-WP-0008-T3
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "90d98c16-ed3b-414f-802c-b0400eca6ede"
|
state_hub_task_id: "90d98c16-ed3b-414f-802c-b0400eca6ede"
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user