from __future__ import annotations import json from collections.abc import Iterable from dataclasses import dataclass @dataclass(frozen=True) class Finding: file: str hunk: str snippet: str message: str label: str def key(self) -> tuple[str, str, str, str, str]: return (self.file, self.hunk, self.snippet, self.message, self.label) def dedupe_findings(findings: Iterable[Finding]) -> list[Finding]: seen = set() unique: list[Finding] = [] for item in findings: key = item.key() if key in seen: continue seen.add(key) unique.append(item) return unique def render_json(findings: Iterable[Finding]) -> str: payload = {"findings": [item.__dict__ for item in findings]} return json.dumps(payload, indent=2, sort_keys=True) + "\n" def render_markdown(findings: Iterable[Finding]) -> str: items = list(findings) if not items: return "# Review Report\n\nNo findings.\n" lines: list[str] = ["# Review Report", ""] for idx, item in enumerate(items, start=1): lines.append(f"## Finding {idx}") lines.append(f"- File: {item.file}") lines.append(f"- Hunk: {item.hunk}") lines.append(f"- Label: {item.label}") lines.append("- Snippet:") lines.append("```") lines.append(item.snippet.rstrip()) lines.append("```") lines.append("") lines.append(item.message) lines.append("") return "\n".join(lines).rstrip() + "\n"