feat: report --diff-only — colored terminal diff view

Shows only the changed parts of the latest report: text diffs with
ANSI colors (+green/-red/@cyan), new/missing URLs, link diffs,
hidden content, meta-refresh and inline script findings.
Colors are suppressed when stdout is not a terminal (pipes, log files).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-12 22:27:19 +02:00
commit 2c3aa60d97

View file

@ -1061,8 +1061,83 @@ def cmd_approve(args: argparse.Namespace, cfg: dict) -> int:
return 1
def _print_diff_only(report: dict) -> None:
"""Print a compact colored diff view of the latest report."""
a = report.get("assessment", {})
d = report.get("diff", {})
level = a.get("level", "green")
symbol, _ = ampel(level)
use_color = sys.stdout.isatty()
if use_color:
C = {"add": "\033[32m", "del": "\033[31m", "hdr": "\033[36m",
"url": "\033[1;34m", "rst": "\033[0m"}
else:
C = {k: "" for k in ("add", "del", "hdr", "url", "rst")}
print(f"{symbol} Level: {level.upper()} Score: {a.get('score', 0)}")
print()
for domain in d.get("new_external_domains", []):
print(f"{C['del']}+ Neue externe Domain: {domain}{C['rst']}")
for url in d.get("new_internal_urls", []):
print(f"{C['add']}+ Neue URL: {url}{C['rst']}")
for url in d.get("missing_internal_urls", []):
print(f"{C['del']}- Fehlende URL: {url}{C['rst']}")
for entry in d.get("new_broken_urls", []):
print(f"{C['del']}! Neuer kaputt Link [{entry['status']}]: {entry['url']}{C['rst']}")
has_page_diffs = False
for pd in d.get("page_diffs", []):
text_diff = pd.get("text_diff", [])
link_diff = pd.get("link_diff", {})
new_hidden = pd.get("new_hidden_content", [])
new_refresh = pd.get("new_meta_refresh", [])
new_scripts = pd.get("new_inline_scripts", [])
new_comments = pd.get("new_comment_links", [])
if not (text_diff or link_diff or new_hidden or new_refresh
or new_scripts or new_comments or pd.get("canonical_changed")):
continue
has_page_diffs = True
print(f"\n{C['url']}{'' * 4} {pd['url']} {'' * 4}{C['rst']}")
for line in text_diff:
if line.startswith("+"):
print(f"{C['add']}{line}{C['rst']}")
elif line.startswith("-"):
print(f"{C['del']}{line}{C['rst']}")
elif line.startswith("@"):
print(f"{C['hdr']}{line}{C['rst']}")
else:
print(line)
for ltype, ld in link_diff.items():
for added in ld.get("added", []):
print(f"{C['add']}+ [{ltype}] {added}{C['rst']}")
for removed in ld.get("removed", []):
print(f"{C['del']}- [{ltype}] {removed}{C['rst']}")
for h in new_hidden:
print(f"{C['del']}! Hidden Content ({h.get('type')}): "
f"{', '.join(h.get('indicators', []))}{C['rst']}")
for mr in new_refresh:
print(f"{C['del']}! Meta-Refresh → {mr}{C['rst']}")
if pd.get("canonical_changed"):
print(f"{C['del']}! Canonical: {pd.get('old_canonical')}{pd.get('new_canonical')}{C['rst']}")
for s in new_scripts:
print(f"{C['del']}! Inline-Script: {', '.join(s.get('patterns', []))}{C['rst']}")
for cl in new_comments:
print(f"{C['del']}! Kommentar-Link: {cl}{C['rst']}")
nothing = (
not d.get("new_external_domains")
and not d.get("new_internal_urls")
and not d.get("missing_internal_urls")
and not d.get("new_broken_urls")
and not has_page_diffs
)
if nothing:
print("Keine Änderungen.")
def cmd_report(args: argparse.Namespace, cfg: dict) -> int:
fmt = getattr(args, "format", "md")
rd = Path(cfg["reports_dir"])
if not rd.exists():
print("Noch keine Reports vorhanden.")
@ -1074,11 +1149,17 @@ def cmd_report(args: argparse.Namespace, cfg: dict) -> int:
return 0
latest = dirs[-1]
if fmt == "json":
p = latest / "report.json"
else:
p = latest / "report.md"
if getattr(args, "diff_only", False):
p = latest / "report.json"
if not p.exists():
print(f"Report-Datei nicht gefunden: {p}", file=sys.stderr)
return 1
_print_diff_only(json.loads(p.read_text(encoding="utf-8")))
return 0
fmt = getattr(args, "format", "md")
p = latest / ("report.json" if fmt == "json" else "report.md")
if not p.exists():
print(f"Report-Datei nicht gefunden: {p}", file=sys.stderr)
return 1
@ -1379,6 +1460,8 @@ def _build_parser() -> argparse.ArgumentParser:
rp = sub.add_parser("report", help="Letzten Report anzeigen")
rp.add_argument("--format", choices=["md", "json"], default="md")
rp.add_argument("--diff-only", action="store_true",
help="Nur Textdiffs geänderter Seiten anzeigen (farbig)")
sub.add_parser("status", help="Übersichtsstatus anzeigen")