CtrlK
BlogDocsLog inGet started
Tessl Logo

canonical/mason

Agent kit for working on canonical/chisel-releases. Cross-agent skills + scripts for authoring and reviewing chisel slice definition files.

81

Quality

85%

Does it follow best practices?

Impact

75%

Average score across 5 eval scenarios

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

review-diff.pyskills/chisel-slice-reviewer/scripts/

#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10"
# dependencies = ["pyyaml"]
# ///
"""review-diff: run every deterministic check over a chisel-releases PR diff.

one command for a PR-review bot. given the branch a PR targets, it finds the
changed SDFs and runs the three checkers over them:

  - check-slice   static conventions (sorting, naming, copyright, clutter, ...)
  - check-test    binary test coverage
  - check-diff    append-only regressions (removed SDF / slice / path)

then prints one report grouped by severity, with a verdict, and exits non-zero
if anything `block`s (1) or a check is unavailable (2).

Usage:
  review-diff.py --base <ref>

Run from the chisel-releases checkout root (the checkers read chisel.yaml for
the format version). pyyaml is declared so the checkers, invoked via this same
interpreter, can import it.
"""

from __future__ import annotations

import subprocess
import sys
from pathlib import Path

HERE = Path(__file__).resolve().parent
SEVERITIES = ("block", "warn", "info", "unavailable")


def git(args: list[str]) -> str | None:
    try:
        r = subprocess.run(["git", *args], capture_output=True, text=True, check=False)
    except OSError:
        return None
    return r.stdout if r.returncode == 0 else None


def run_check(script: str, *args: str) -> list[str]:
    try:
        r = subprocess.run(
            [sys.executable, str(HERE / script), *args],
            capture_output=True,
            text=True,
            check=False,
        )
    except OSError as e:
        return [f"unavailable  {script}: cannot run checker ({e})"]
    lines = (r.stdout + r.stderr).splitlines()
    findings = [ln for ln in lines if ln.split() and ln.split()[0] in SEVERITIES]
    unavailable = any(ln.startswith("unavailable") for ln in findings)
    expected_failure = r.returncode == 1 and any(
        ln.startswith("block") for ln in findings
    )
    if "Traceback" in r.stderr or (
        r.returncode != 0 and not unavailable and not expected_failure
    ):
        tail = (
            r.stderr.strip().splitlines()[-1]
            if r.stderr.strip()
            else f"exit {r.returncode}"
        )
        findings.append(
            f"unavailable  {script} {' '.join(args)}: checker did not complete ({tail})"
        )
    return findings


def changed_slices(base: str) -> list[str] | None:
    status = git(["diff", "--name-status", base, "--", "slices/*.yaml"])
    if status is None:
        return None
    out = []
    for line in status.splitlines():
        parts = line.split("\t")
        # added or modified (skip deletions -- check-diff reports those).
        if (
            len(parts) >= 2
            and parts[0][:1] in ("A", "M", "R")
            and Path(parts[-1]).exists()
        ):
            out.append(parts[-1])
    return out


def main(argv: list[str]) -> int:
    if argv and argv[0] in ("-h", "--help"):
        print(__doc__)
        return 0
    if len(argv) < 2 or argv[0] != "--base":
        print("usage: review-diff.py --base <ref>", file=sys.stderr)
        return 2
    base = argv[1]

    changed = changed_slices(base)
    if changed is None:
        print(
            f"unavailable  {base}: git diff failed -- is this a checkout, and is the ref present?"
        )
        print("verdict: incomplete")
        return 2

    findings: list[str] = []
    if changed:
        findings += run_check("_check-slice.py", *changed)
        for sdf in changed:
            findings += run_check("_check-test.py", sdf)
    findings += run_check("_check-diff.py", "--base", base)

    rank = {s: i for i, s in enumerate(SEVERITIES)}
    findings.sort(key=lambda ln: rank.get(ln.split()[0], 9))
    blocks = sum(ln.startswith("block") for ln in findings)
    warns = sum(ln.startswith("warn") for ln in findings)
    unavailable = sum(ln.startswith("unavailable") for ln in findings)
    infos = sum(ln.startswith("info") for ln in findings)

    print(
        f"reviewed {len(changed)} changed SDF(s) against {base}: "
        f"{blocks} block, {warns} warn, {infos} info, {unavailable} unavailable"
    )
    for ln in findings:
        print(ln)
    verdict = (
        "request-changes"
        if blocks
        else "incomplete"
        if unavailable
        else "comment"
        if warns
        else "approve"
    )
    print(f"verdict: {verdict}")
    return 2 if unavailable else 1 if blocks else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

README.md

tile.json