Skip to content

Commit 92f2beb

Browse files
feat(cli): --format {table,json,md} y --output; Markdown report (#9)
1 parent 300f419 commit 92f2beb

File tree

3 files changed

+99
-46
lines changed

3 files changed

+99
-46
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@ name: CI / build
22
on:
33
pull_request:
44
push:
5-
branches: [main]
5+
branches: [ main ]
66
jobs:
77
python:
8+
name: python (${{ matrix.python-version }})
89
runs-on: ubuntu-latest
910
strategy:
10-
matrix:
11-
python-version: ["3.11","3.12"]
11+
matrix: { python-version: ["3.11","3.12"] }
1212
steps:
1313
- uses: actions/checkout@v5
1414
- uses: actions/setup-python@v5
15-
with:
16-
python-version: ${{ matrix.python-version }}
15+
with: { python-version: ${{ matrix.python-version }} }
1716
- run: python -m pip install -U pip
18-
- run: pip install poetry
19-
- run: poetry install --no-interaction
20-
- run: poetry run ruff check .
21-
- run: poetry run black --check .
22-
- run: PYTHONPATH=src poetry run pytest -q
23-
- run: poetry run mypy src
17+
- run: pip install pytest mypy ruff black
18+
- run: ruff check .
19+
- run: black --check .
20+
- run: pytest -q
21+
- name: mypy (solo 3.12)
22+
if: matrix.python-version == '3.12'
23+
run: mypy src

src/diff_risk_dashboard/cli.py

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,59 @@
33
import argparse
44
import json
55
import sys
6+
from collections.abc import Mapping
67
from pathlib import Path
7-
8-
from .core import Summary, summarize
9-
10-
11-
def _print_table(summary: Summary) -> None:
12-
bs = summary["by_severity"]
13-
rows = [
14-
("CRITICAL", bs["CRITICAL"]),
15-
("HIGH", bs["HIGH"]),
16-
("MEDIUM", bs["MEDIUM"]),
17-
("LOW", bs["LOW"]),
18-
("INFO", bs["INFO"]),
19-
]
20-
print("\n=== Diff Risk Summary ===")
21-
print(f"Total findings: {summary['total']}")
22-
print("Severity counts:")
23-
w = max(len(r[0]) for r in rows)
24-
for name, cnt in rows:
25-
print(f" {name:<{w}} : {cnt}")
26-
print(f"Worst severity : {summary['worst']}")
27-
print(f"Risk level : {summary['risk_level']}\n")
28-
29-
30-
def main(argv: list[str] | None = None) -> int:
31-
p = argparse.ArgumentParser(description="Diff Risk Dashboard (APV JSON -> summary)")
32-
p.add_argument("apv_json", help="Path to ai-patch-verifier JSON")
33-
args = p.parse_args(argv)
34-
data = json.loads(Path(args.apv_json).read_text(encoding="utf-8"))
35-
sm = summarize(data)
36-
_print_table(sm)
37-
return 2 if sm["risk_level"] == "red" else (1 if sm["risk_level"] == "yellow" else 0)
38-
39-
40-
if __name__ == "__main__":
41-
sys.exit(main())
8+
from typing import Any
9+
10+
from .core import summarize_apv_json
11+
from .report import to_markdown
12+
13+
14+
def _exit_code(risk: str) -> int:
15+
return {"green": 0, "yellow": 1, "red": 2}.get(risk, 0)
16+
17+
18+
def _print_table(summary: Mapping[str, Any]) -> None:
19+
by = summary.get("by_severity", {}) or {}
20+
print("Severity\tCount")
21+
for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]:
22+
print(f"{sev}\t{by.get(sev, by.get(sev.lower(), 0))}")
23+
print(f"TOTAL\t{summary.get('total', 0)}")
24+
25+
26+
def main() -> int:
27+
p = argparse.ArgumentParser(
28+
prog="diff_risk_dashboard", description="Diff Risk Dashboard (APV JSON -> summary)"
29+
)
30+
p.add_argument("input", help="Path o texto JSON de ai-patch-verifier")
31+
p.add_argument(
32+
"-f", "--format", choices=["table", "json", "md"], default="table", help="Formato de salida"
33+
)
34+
p.add_argument("-o", "--output", default="-", help="Archivo de salida; '-' = stdout")
35+
p.add_argument(
36+
"--no-exit-by-risk", action="store_true", help="No ajustar el exit code por nivel de riesgo"
37+
)
38+
args = p.parse_args()
39+
40+
summary: Mapping[str, Any] = summarize_apv_json(args.input)
41+
out: str | None = None
42+
if args.format == "json":
43+
out = json.dumps(summary, indent=2)
44+
elif args.format == "md":
45+
out = to_markdown(summary)
46+
else:
47+
_print_table(summary)
48+
49+
if out is not None:
50+
if args.output == "-":
51+
print(out)
52+
else:
53+
Path(args.output).write_text(out, encoding="utf-8")
54+
print(f"Wrote {args.output}", file=sys.stderr)
55+
56+
risk = str(summary.get("risk", summary.get("risk_level", "green")))
57+
return 0 if args.no_exit_by_risk else _exit_code(risk)
58+
59+
60+
if __name__ == "__main__": # pragma: no cover
61+
raise SystemExit(main())

src/diff_risk_dashboard/report.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Mapping
4+
from typing import Any
5+
6+
_SEVERITIES = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]
7+
8+
9+
def _get_counts(summary: Mapping[str, Any]) -> dict[str, int]:
10+
counts: dict[str, int] = {}
11+
by = summary.get("by_severity", {}) or {}
12+
if isinstance(by, Mapping):
13+
for sev in _SEVERITIES:
14+
counts[sev] = int(by.get(sev, by.get(sev.lower(), 0)) or 0)
15+
return counts
16+
17+
18+
def to_markdown(summary: Mapping[str, Any]) -> str:
19+
counts = _get_counts(summary)
20+
total = int(summary.get("total", 0) or 0)
21+
worst = str(summary.get("worst", "INFO") or "INFO").upper()
22+
risk = str(summary.get("risk", summary.get("risk_level", "green")) or "green").lower()
23+
emoji = {"red": "🔴", "yellow": "🟡", "green": "🟢"}.get(risk, "🟢")
24+
lines = [
25+
f"# Diff Risk Dashboard {emoji} — Worst: **{worst}**",
26+
"",
27+
"| Severity | Count |",
28+
"|---|---:|",
29+
]
30+
for sev in _SEVERITIES:
31+
lines.append(f"| {sev} | {counts.get(sev, 0)} |")
32+
lines += [f"| **TOTAL** | **{total}** |", "", "> Generated by diff-risk-dashboard CLI"]
33+
return "\n".join(lines)

0 commit comments

Comments
 (0)