"""把 finding 渲染成 GitCode 行级评论(远程他检出口)。
两条远程语义(见设计 §1):
- 同一文件同一规则超过 FOLD_THRESHOLD 条 → 折叠成一条,避免刷屏
- 评论用纯文本、无 markdown 语法、语气像同事,不写"审查通过"类正面确认
python3 render_comments.py <findings.json> [--fold N]
"""
from __future__ import annotations
import argparse
import json
import sys
from collections import defaultdict
FOLD_THRESHOLD = 5
SEV_ICON = {"high": "🔴", "mid": "🟡", "low": "🟢"}
SEV_ORDER = {"high": 0, "mid": 1, "low": 2}
CONSEQUENCE_OF = {
"op_graph": ["G6-proto-not-found"],
}
def _merge_consequences(findings: list[dict]) -> list[dict]:
missing = {f["file"].rsplit("/", 1)[-1] for f in findings
if f["rule"] == "G9-deliverable-missing"}
dropped = {r for k, rules in CONSEQUENCE_OF.items() if k in missing for r in rules}
if not dropped:
return findings
kept = []
for f in findings:
if f["rule"] in dropped:
continue
if f["rule"] == "G9-deliverable-missing" and f["file"].rsplit("/", 1)[-1] in CONSEQUENCE_OF:
f = {**f, "_consequence": True}
kept.append(f)
return kept
def build(findings: list[dict], fold: int) -> list[dict]:
dropped = [f for f in findings if f.get("out_of_diff")]
findings = [f for f in findings if not f.get("out_of_diff")]
if dropped:
import collections
import sys as _sys
cnt = collections.Counter(f["rule"] for f in dropped)
print(f"[render_comments] 锚点在 diff 外、未生成评论 {len(dropped)} 条:"
+ "、".join(f"{k}×{v}" for k, v in cnt.most_common()), file=_sys.stderr)
findings = _merge_consequences(findings)
buckets: dict[tuple[str, str], list[dict]] = defaultdict(list)
for f in findings:
buckets[(f["file"], f["rule"])].append(f)
comments: list[dict] = []
for (path, rule), group in buckets.items():
group.sort(key=lambda x: x["line"])
if len(group) > fold:
first = group[0]
rest = [g["line"] for g in group[1:]]
lines = "、".join(str(n) for n in rest[:15])
more = "……" if len(rest) > 15 else ""
fix_hint = ("建议一起改掉" if first["group"] == 12 else
"本地跑一遍 pre-commit 能统一处理")
comments.append({
"path": path,
"line": first["line"],
"severity": first["severity"],
"group": first["group"],
"rule": rule,
"folded": len(group),
"body": f"{first['message']}这个文件里同类的还有 {len(group) - 1} 处,"
f"行号 {lines}{more},{fix_hint},就不逐行标了。",
})
else:
for f in group:
suffix = ""
if f.get("needs_human"):
suffix = "(这条是静态扫描给的提示,如果上界确实有别处保证,忽略即可)"
comments.append({
"path": path,
"line": f["line"],
"severity": f["severity"],
"group": f["group"],
"rule": rule,
"folded": 0,
"body": f["message"] + suffix,
})
comments.sort(key=lambda c: (SEV_ORDER[c["severity"]], c["path"], c["line"]))
return comments
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("findings")
ap.add_argument("--fold", type=int, default=FOLD_THRESHOLD)
ap.add_argument("--json", action="store_true")
a = ap.parse_args()
data = json.load(open(a.findings, encoding="utf-8"))
comments = build(data["findings"], a.fold)
if a.json:
print(json.dumps(comments, ensure_ascii=False, indent=2))
return 0
print(f"共 {len(comments)} 条评论(原始 finding {len(data['findings'])} 条,"
f"折叠 {sum(1 for c in comments if c['folded'])} 组)\n")
print("| # | 严重 | 文件 | 行号 | 评论内容(提交内容) |")
print("|---|---|---|---|---|")
for i, c in enumerate(comments, 1):
body = c["body"].replace("|", "\\|")
print(f"| {i} | {SEV_ICON[c['severity']]} | {c['path'].split('/')[-1]} | {c['line']} | {body} |")
return 0
if __name__ == "__main__":
sys.exit(main())