Files
cleveragents-core/robot/helper_diff_review.py
2026-02-25 10:02:38 +00:00

201 lines
5.5 KiB
Python

"""Robot Framework helper for diff review artifact smoke tests.
Provides a CLI-style interface for Robot to invoke diff review
model creation, serialization, and builder operations.
Exit code 0 = success, 1 = failure.
Usage:
python robot/helper_diff_review.py build-empty
python robot/helper_diff_review.py build-create
python robot/helper_diff_review.py build-rename
python robot/helper_diff_review.py serialize-all
python robot/helper_diff_review.py truncation
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from typing import Any
# Ensure the src directory is on the import path.
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
from cleveragents.domain.models.core.change import ( # noqa: E402
ChangeEntry,
ChangeOperation,
SpecChangeSet,
)
from cleveragents.domain.models.core.diff_review import ( # noqa: E402
DiffBuilder,
DiffSerializer,
)
def _make_entry(
plan_id: str,
resource_id: str,
operation: str,
path: str,
*,
before_hash: str | None = None,
after_hash: str | None = None,
) -> ChangeEntry:
"""Build a ChangeEntry with sensible defaults."""
kwargs: dict[str, Any] = {
"plan_id": plan_id,
"resource_id": resource_id,
"tool_name": "builtin/test-tool",
"operation": ChangeOperation(operation),
"path": path,
}
if before_hash is not None:
kwargs["before_hash"] = before_hash
if after_hash is not None:
kwargs["after_hash"] = after_hash
return ChangeEntry(**kwargs)
def _cmd_build_empty() -> int:
"""Build an artifact from an empty changeset."""
cs = SpecChangeSet(plan_id="PLAN001")
builder = DiffBuilder()
artifact = builder.build(cs)
assert artifact.total_changes == 0
assert len(artifact.groups) == 0
assert not artifact.truncated
assert artifact.plan_id == "PLAN001"
print("build-empty-ok")
return 0
def _cmd_build_create() -> int:
"""Build an artifact with a CREATE change."""
cs = SpecChangeSet(plan_id="PLAN001")
entry = _make_entry("PLAN001", "RES001", "create", "src/main.py")
cs.add_change(entry)
builder = DiffBuilder()
artifact = builder.build(
cs,
after_contents={"src/main.py": "print('hello')\n"},
)
assert artifact.total_changes == 1
assert len(artifact.groups) == 1
group = artifact.groups[0]
assert group.resource_id == "RES001"
assert len(group.entries) == 1
assert group.entries[0].change_type == "create"
assert group.entries[0].path == "src/main.py"
assert "+++" in group.entries[0].diff_text
print("build-create-ok")
return 0
def _cmd_build_rename() -> int:
"""Build an artifact with rename detection."""
cs = SpecChangeSet(plan_id="PLAN001")
del_entry = _make_entry(
"PLAN001", "RES001", "delete", "old/file.py", before_hash="abc123"
)
cre_entry = _make_entry(
"PLAN001", "RES001", "create", "new/file.py", after_hash="abc123"
)
cs.add_change(del_entry)
cs.add_change(cre_entry)
builder = DiffBuilder()
artifact = builder.build(cs)
assert len(artifact.groups) == 1
group = artifact.groups[0]
assert len(group.entries) == 1
assert group.entries[0].is_rename
assert group.entries[0].rename_from == "old/file.py"
assert group.entries[0].rename_to == "new/file.py"
print("build-rename-ok")
return 0
def _cmd_serialize_all() -> int:
"""Build an artifact and serialize to all formats."""
cs = SpecChangeSet(plan_id="PLAN001")
entry = _make_entry("PLAN001", "RES001", "create", "new.py")
cs.add_change(entry)
builder = DiffBuilder()
artifact = builder.build(
cs,
after_contents={"new.py": "hello\n"},
resource_names={"RES001": "my-resource"},
)
# Plain text
plain = DiffSerializer.to_plain(artifact)
assert "my-resource" in plain
assert "Total changes: 1" in plain
# JSON
json_text = DiffSerializer.to_json(artifact)
data = json.loads(json_text)
assert "total_changes" in data
assert "groups" in data
assert data["total_changes"] == 1
# Rich
rich = DiffSerializer.to_rich(artifact)
assert "[bold]" in rich
assert "new.py" in rich
print("serialize-all-ok")
return 0
def _cmd_truncation() -> int:
"""Verify truncation with a small max_diff_size."""
cs = SpecChangeSet(plan_id="PLAN001")
entry = _make_entry("PLAN001", "RES001", "create", "big.py")
cs.add_change(entry)
builder = DiffBuilder(max_diff_size=50)
artifact = builder.build(
cs,
after_contents={"big.py": "x" * 10000},
)
assert artifact.truncated
assert artifact.groups[0].entries[0].truncated
print("truncation-ok")
return 0
def main() -> int:
"""Entry point called by Robot Framework ``Run Process``."""
if len(sys.argv) < 2:
print(
"Usage: helper_diff_review.py "
"<build-empty|build-create|build-rename|serialize-all|truncation>"
)
return 1
command = sys.argv[1]
if command == "build-empty":
return _cmd_build_empty()
if command == "build-create":
return _cmd_build_create()
if command == "build-rename":
return _cmd_build_rename()
if command == "serialize-all":
return _cmd_serialize_all()
if command == "truncation":
return _cmd_truncation()
print(f"Unknown command: {command}")
return 1
if __name__ == "__main__":
sys.exit(main())