"""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 " "" ) 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())