Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 61 additions & 31 deletions src/diff_risk_dashboard/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,69 @@
import argparse
import json
import sys
from collections.abc import Mapping
from pathlib import Path
from typing import Any, Literal, TypedDict

from .core import Summary, summarize


def _print_table(summary: Summary) -> None:
bs = summary["by_severity"]
rows = [
("CRITICAL", bs["CRITICAL"]),
("HIGH", bs["HIGH"]),
("MEDIUM", bs["MEDIUM"]),
("LOW", bs["LOW"]),
("INFO", bs["INFO"]),
]
print("\n=== Diff Risk Summary ===")
print(f"Total findings: {summary['total']}")
print("Severity counts:")
w = max(len(r[0]) for r in rows)
for name, cnt in rows:
print(f" {name:<{w}} : {cnt}")
print(f"Worst severity : {summary['worst']}")
print(f"Risk level : {summary['risk_level']}\n")


def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description="Diff Risk Dashboard (APV JSON -> summary)")
p.add_argument("apv_json", help="Path to ai-patch-verifier JSON")
args = p.parse_args(argv)
data = json.loads(Path(args.apv_json).read_text(encoding="utf-8"))
sm = summarize(data)
_print_table(sm)
return 2 if sm["risk_level"] == "red" else (1 if sm["risk_level"] == "yellow" else 0)
from .core import summarize_apv_json
from .report import to_markdown


class Summary(TypedDict):
total: int
worst: str
risk: Literal["red", "yellow", "green"]
by_severity: dict


def _exit_code(risk: str) -> int:
return {"green": 0, "yellow": 1, "red": 2}.get(risk, 0)


def _print_table(summary: Mapping[str, Any]) -> None:
by = summary.get("by_severity", {})
print("Severity\tCount")
for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]:
print(f"{sev}\t{by.get(sev, by.get(sev.lower(), 0))}")
print(f"TOTAL\t{summary.get('total',0)}")


def main() -> int:
p = argparse.ArgumentParser(
prog="diff_risk_dashboard", description="Diff Risk Dashboard (APV JSON -> summary)"
)
p.add_argument("input", help="Path o texto JSON de ai-patch-verifier")
p.add_argument(
"-f", "--format", choices=["table", "json", "md"], default="table", help="Formato de salida"
)
p.add_argument("-o", "--output", default="-", help="Archivo de salida; '-' = stdout")
p.add_argument(
"--no-exit-by-risk", action="store_true", help="No ajustar el exit code por nivel de riesgo"
)
args = p.parse_args()

summary: Summary = summarize_apv_json(args.input) # acepta path o texto
if args.format == "json":
out = json.dumps(summary, indent=2)
elif args.format == "md":
out = to_markdown(summary)
else:
_print_table(summary)
out = None

if out is not None:
if args.output == "-":
print(out)
else:
Path(args.output).write_text(out, encoding="utf-8")
print(f"Wrote {args.output}", file=sys.stderr)

# compat: summary puede traer 'risk' o 'risk_level'
risk = str(summary.get("risk", summary.get("risk_level", "green")))
if not args.no_exit_by_risk:
return _exit_code(risk)
return 0


if __name__ == "__main__":
sys.exit(main())
raise SystemExit(main())
34 changes: 34 additions & 0 deletions src/diff_risk_dashboard/report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

from collections.abc import Mapping
from typing import Any

_SEVERITIES = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]


def _get_counts(summary: Mapping[str, Any]) -> dict[str, int]:
counts: dict[str, int] = {}
by = summary.get("by_severity", {}) or {}
if isinstance(by, Mapping):
for sev in _SEVERITIES:
counts[sev] = int(by.get(sev, by.get(sev.lower(), 0)) or 0)
return counts


def to_markdown(summary: Mapping[str, Any]) -> str:
counts = _get_counts(summary)
total = int(summary.get("total", 0) or 0)
worst = str(summary.get("worst", "INFO") or "INFO").upper()
risk = str(summary.get("risk", summary.get("risk_level", "green")) or "green").lower()
risk_emoji = {"red": "🔴", "yellow": "🟡", "green": "🟢"}.get(risk, "🟢")
lines = []
lines.append(f"# Diff Risk Dashboard {risk_emoji} — Worst: **{worst}**")
lines.append("")
lines.append("| Severity | Count |")
lines.append("|---|---:|")
for sev in _SEVERITIES:
lines.append(f"| {sev} | {counts.get(sev, 0)} |")
lines.append(f"| **TOTAL** | **{total}** |")
lines.append("")
lines.append("> Generated by diff-risk-dashboard CLI")
return "\n".join(lines)
13 changes: 13 additions & 0 deletions tests/test_report_md.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from diff_risk_dashboard.report import to_markdown


def test_md_total():
md = to_markdown(
{
"total": 3,
"worst": "HIGH",
"risk": "yellow",
"by_severity": {"HIGH": 1, "MEDIUM": 1, "LOW": 1},
}
)
assert "| **TOTAL** | **3** |" in md
Loading