Skip to content

Commit b15dfd7

Browse files
committed
feat: add structured compliance policy report to job summary
Replace the bare pass/fail job summary with a structured 'Commit Check Policy Report' that shows per-check results as a Markdown table with pass/fail indicators. The report leverages commit-check's --format json output to determine per-check pass/fail status without changing the existing validation flow or PR comment behavior. Key changes: - Add CheckResult dataclass and report-building functions - On pass: shortcuts to all-pass results (no extra work) - On failure: runs per-check JSON lookups to identify which exact checks failed - Raw output preserved in a collapsible <details> section - Backward compatible: run_commit_check() and add_pr_comments() are unchanged
1 parent a294f54 commit b15dfd7

2 files changed

Lines changed: 370 additions & 6 deletions

File tree

‎main.py‎

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import re
55
import subprocess
66
import sys
7+
from dataclasses import dataclass
78
from typing import TextIO
89

910
# Constants for message titles
@@ -12,6 +13,14 @@
1213
COMMIT_MESSAGE_DELIMITER = "\x00"
1314
COMMIT_SECTION_SEPARATOR = "\n---\n"
1415

16+
# Map CLI flags to human-readable check labels for the policy report
17+
CHECK_LABEL_MAP = {
18+
"--message": "Commit message",
19+
"--branch": "Branch naming",
20+
"--author-name": "Author name",
21+
"--author-email": "Author email",
22+
}
23+
1524
# Environment variables
1625
MESSAGE = os.getenv("MESSAGE", "false")
1726
BRANCH = os.getenv("BRANCH", "false")
@@ -232,17 +241,155 @@ def build_result_body(result_text: str | None) -> str:
232241
return f"{FAILURE_TITLE}\n```\n{result_text}\n```"
233242

234243

244+
@dataclass
245+
class CheckResult:
246+
"""Per-check result for the policy report."""
247+
248+
label: str
249+
passed: bool
250+
251+
252+
def run_check_json(flag: str, input_text: str | None = None) -> tuple[int, dict]:
253+
"""Run commit-check with --format json and return (returncode, parsed_json)."""
254+
command = ["commit-check", flag, "--format", "json", "--no-banner"]
255+
result = subprocess.run(
256+
command,
257+
input=input_text,
258+
stdout=subprocess.PIPE,
259+
stderr=subprocess.STDOUT,
260+
text=True,
261+
check=False,
262+
)
263+
try:
264+
data = (
265+
json.loads(result.stdout)
266+
if result.stdout.strip()
267+
else {"status": "pass", "checks": []}
268+
)
269+
except json.JSONDecodeError:
270+
data = {"status": "fail", "checks": []}
271+
return result.returncode, data
272+
273+
274+
def get_policy_file() -> str:
275+
"""Detect which commit-check config file exists in the repository."""
276+
for filename in ["commit-check.toml", "cchk.toml", ".commit-check.yml"]:
277+
if os.path.exists(filename):
278+
return filename
279+
return "commit-check.toml (default)"
280+
281+
282+
def _run_all_message_checks(pr_messages: list[str]) -> bool:
283+
"""Check all PR commit messages individually via JSON.
284+
285+
Returns True if all messages pass, False if any fails.
286+
"""
287+
for msg in pr_messages:
288+
rc, _ = run_check_json("--message", input_text=msg)
289+
if rc != 0:
290+
return False
291+
return True
292+
293+
294+
def determine_check_results() -> list[CheckResult]:
295+
"""Run each enabled check with --format json to determine per-check pass/fail."""
296+
results: list[CheckResult] = []
297+
298+
if MESSAGE_ENABLED:
299+
pr_messages = get_pr_commit_messages()
300+
if pr_messages:
301+
passed = _run_all_message_checks(pr_messages)
302+
else:
303+
rc, _ = run_check_json("--message")
304+
passed = rc == 0
305+
results.append(CheckResult("Commit message", passed))
306+
307+
if BRANCH_ENABLED:
308+
rc, _ = run_check_json("--branch")
309+
results.append(CheckResult("Branch naming", rc == 0))
310+
311+
if AUTHOR_NAME_ENABLED:
312+
rc, _ = run_check_json("--author-name")
313+
results.append(CheckResult("Author name", rc == 0))
314+
315+
if AUTHOR_EMAIL_ENABLED:
316+
rc, _ = run_check_json("--author-email")
317+
results.append(CheckResult("Author email", rc == 0))
318+
319+
return results
320+
321+
322+
def build_pass_results() -> list[CheckResult]:
323+
"""Build all-pass results for enabled checks (shortcut for success case)."""
324+
results: list[CheckResult] = []
325+
if MESSAGE_ENABLED:
326+
results.append(CheckResult("Commit message", True))
327+
if BRANCH_ENABLED:
328+
results.append(CheckResult("Branch naming", True))
329+
if AUTHOR_NAME_ENABLED:
330+
results.append(CheckResult("Author name", True))
331+
if AUTHOR_EMAIL_ENABLED:
332+
results.append(CheckResult("Author email", True))
333+
return results
334+
335+
336+
def build_policy_report(results: list[CheckResult]) -> str:
337+
"""Build a structured compliance report in Markdown format."""
338+
repo = os.getenv("GITHUB_REPOSITORY", "unknown")
339+
policy_file = get_policy_file()
340+
event = os.getenv("GITHUB_EVENT_NAME", "unknown")
341+
342+
lines = [
343+
"# Commit Check Policy Report",
344+
"",
345+
f"**Repository**: `{repo}` ",
346+
f"**Policy file**: `{policy_file}` ",
347+
f"**Trigger**: `{event}` ",
348+
"",
349+
"| Check | Result |",
350+
"|---|---|",
351+
]
352+
353+
all_passed = True
354+
for r in results:
355+
status = "✅ Pass" if r.passed else "❌ Fail"
356+
if not r.passed:
357+
all_passed = False
358+
lines.append(f"| {r.label} | {status} |")
359+
360+
lines.append("")
361+
if all_passed:
362+
lines.append("### ✅ All checks passed")
363+
else:
364+
lines.append("### ❌ Some checks failed")
365+
366+
return "\n".join(lines)
367+
368+
235369
def add_job_summary() -> int:
236370
"""Adds the commit check result to the GitHub job summary."""
237371
if not JOB_SUMMARY_ENABLED:
238372
return 0
239373

240374
result_text = read_result_file()
375+
overall_pass = result_text is None
376+
377+
if overall_pass:
378+
report_results = build_pass_results()
379+
else:
380+
report_results = determine_check_results()
381+
382+
report = build_policy_report(report_results)
241383

242384
with open(GITHUB_STEP_SUMMARY, "a") as summary_file:
243-
summary_file.write(build_result_body(result_text))
385+
summary_file.write(report)
386+
if result_text:
387+
summary_file.write(
388+
"\n\n<details>\n<summary>Details</summary>\n\n```\n"
389+
f"{result_text}\n```\n</details>\n"
390+
)
244391

245-
return 0 if result_text is None else 1
392+
return 0 if overall_pass else 1
246393

247394

248395
def is_fork_pr() -> bool:

0 commit comments

Comments
 (0)