124 lines
7.9 KiB
Python
124 lines
7.9 KiB
Python
"""Writes docs/werkrevisor-review-decisions.svg and its dark variant: where a review ends early or takes a shortcut."""
|
|
from html import escape
|
|
|
|
LIGHT = dict(bg="#ffffff", text="#141616", muted="#55615f", soft="#3e4645", lane="#f3f6f5", step="#ffffff",
|
|
step_line="#c9d3d1", dec="#e7f1f3", dec_line="#0e6b7d", arrow="#6e7c7a", label="#0e6b7d")
|
|
DARK = dict(bg="#0d1312", text="#e9efed", muted="#9fb0ad", soft="#b7c4c1", lane="#17201f", step="#1e2927",
|
|
step_line="#3a4a47", dec="#132e35", dec_line="#3aa3b8", arrow="#8a9a97", label="#5cc3d6")
|
|
OUTCOME = {"exit": "#5f6b75", "reuse": "#0e6b7d", "error": "#58616b", "red": "#c0392b", "green": "#24733e",
|
|
"yellow": "#d4a514", "orange": "#e07b2e", "hint": "#3b6fb6"}
|
|
|
|
# (kind, text lines, exit label, outcome kind, outcome lines)
|
|
ROWS = [
|
|
("1 Before the model: the pull request and the rules", [
|
|
("step", ["Werkator starts", "the review"], None, None, None),
|
|
("dec", ["Head current,", "PR open?"], "no", "exit", ["Exit 30,", "writes nothing"]),
|
|
("dec", ["Marked ready,", "\"WIP: \" removed?"], "no", "error", ["ERROR"]),
|
|
("dec", ["Head reviewed", "before?"], "yes", "reuse", ["Reuses that", "verdict"]),
|
|
("step", ["Rules on files", "and diff"], None, None, None),
|
|
("dec", ["Open question in", "a PR document?"], "yes", "red", ["RED,", "no model call"]),
|
|
("dec", ["Possible secret", "in the diff?"], "yes", "yellow", ["YELLOW, nothing", "to Jev or a model"]),
|
|
]),
|
|
("2 The shortcut, Jev, the context and the model", [
|
|
# The rules still apply, e.g. a risky path keeps its YELLOW.
|
|
("dec", ["Only mechanical,", "image or approved?"], "yes", "reuse", ["Verdict of the", "rules alone"]),
|
|
("step", ["Jev: PR documents,", "changesets, files;", "PR document check"], None, None, None),
|
|
("dec", ["Approved before,", "new blocks safe?"], "yes", "green", ["Approval", "carried over"]),
|
|
("dec", ["Classifier mode,", "no model?"], "yes", "green", ["GREEN only if", "Jev judged all files"]),
|
|
("dec", ["Prompt within", "the input limit?"], "no", "yellow", ["Excerpts, then diff;", "else YELLOW"]),
|
|
("dec", ["Budget left", "on the key?"], "no", "error", ["ERROR,", "no model call"]),
|
|
("step", ["Model review:", "advises or decides"], None, None, None),
|
|
]),
|
|
("3 After the model: findings, verdict and output", [
|
|
("dec", ["Model call", "failed?"], "yes", "hint", ["Hint if Jev judged", "all files, else YELLOW"]),
|
|
("step", ["Jev judges each", "finding"], "", "orange", ["ORANGE, YELLOW", "or a hint"]),
|
|
("step", ["Verdict: the worst", "reason wins; risk and", "finding: RED"], None, None, None),
|
|
("dec", ["GREEN without", "full review?"], "yes", "yellow", ["Becomes", "YELLOW"]),
|
|
("dec", ["RED for", "max_red_rounds?"], "yes", "red", ["RED, asks the", "human reviewers"]),
|
|
("dec", ["Head still", "the same?"], "no", "exit", ["Exit 30,", "writes nothing"]),
|
|
("step", ["Comment, status,", "approval, merge;", "usage record"], None, None, None),
|
|
]),
|
|
]
|
|
|
|
SPINES = [228, 488, 748]
|
|
NODE_W, NODE_H = 176, 84
|
|
|
|
|
|
def svg(c):
|
|
out = []
|
|
add = out.append
|
|
add('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 900" width="1600" height="900" role="img" '
|
|
'aria-label="Decisions in a werkrevisor review: where it ends early or takes a shortcut">')
|
|
add('<title>Decisions in a werkrevisor review</title>')
|
|
add(f'''<defs><style>
|
|
text {{ font-family: Inter, "IBM Plex Sans", "Segoe UI", Helvetica, Arial, sans-serif; fill: {c["text"]}; }}
|
|
.title {{ font-size: 40px; font-weight: 700; letter-spacing: -0.5px; }}
|
|
.subtitle {{ font-size: 21px; fill: {c["muted"]}; }}
|
|
.row {{ font-size: 15px; font-weight: 700; letter-spacing: 1px; fill: {c["muted"]}; }}
|
|
.node {{ font-size: 15.5px; font-weight: 600; }}
|
|
.out {{ font-size: 14.5px; font-weight: 600; fill: #ffffff; }}
|
|
.out-dark {{ fill: #141616; }}
|
|
.yes {{ font-size: 13.5px; font-weight: 700; fill: {c["label"]}; }}
|
|
.footer {{ font-size: 14px; fill: {c["muted"]}; }}
|
|
</style>
|
|
<marker id="a" viewBox="0 0 10 10" refX="8.5" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
<path d="M0,0 L10,5 L0,10 z" fill="{c["arrow"]}"/></marker></defs>''')
|
|
add(f'<rect width="1600" height="900" fill="{c["bg"]}"/>')
|
|
add('<text class="title" x="40" y="60">Decisions in a werkrevisor review</text>')
|
|
add('<text class="subtitle" x="40" y="94">Each check can end the review early or take a shortcut; '
|
|
'otherwise the review goes on to the right.</text>')
|
|
|
|
for r, (heading, nodes) in enumerate(ROWS):
|
|
spine = SPINES[r]
|
|
add(f'<rect x="30" y="{spine - 92}" width="1540" height="236" rx="16" fill="{c["lane"]}"/>')
|
|
add(f'<text class="row" x="48" y="{spine - 64}">{escape(heading.upper())}</text>')
|
|
slot = 1500 / len(nodes)
|
|
xs = [50 + slot * (i + 0.5) for i in range(len(nodes))]
|
|
for i, (kind, lines, exit_label, outcome, outcome_lines) in enumerate(nodes):
|
|
x = xs[i]
|
|
top = spine - NODE_H / 2
|
|
if kind == "dec":
|
|
w, h, d = NODE_W, NODE_H, 18
|
|
pts = (f"{x - w / 2 + d},{top} {x + w / 2 - d},{top} {x + w / 2},{spine} "
|
|
f"{x + w / 2 - d},{top + h} {x - w / 2 + d},{top + h} {x - w / 2},{spine}")
|
|
add(f'<polygon points="{pts}" fill="{c["dec"]}" stroke="{c["dec_line"]}" stroke-width="2"/>')
|
|
else:
|
|
add(f'<rect x="{x - NODE_W / 2}" y="{top}" width="{NODE_W}" height="{NODE_H}" rx="12" '
|
|
f'fill="{c["step"]}" stroke="{c["step_line"]}" stroke-width="1.5"/>')
|
|
first = spine - (len(lines) - 1) * 10 + 5
|
|
for j, line in enumerate(lines):
|
|
add(f'<text class="node" x="{x}" y="{first + j * 20}" text-anchor="middle">{escape(line)}</text>')
|
|
if i + 1 < len(nodes):
|
|
add(f'<path d="M{x + NODE_W / 2},{spine} H{xs[i + 1] - NODE_W / 2 - 2}" fill="none" '
|
|
f'stroke="{c["arrow"]}" stroke-width="2.5" marker-end="url(#a)"/>')
|
|
if kind == "dec":
|
|
opposite = "yes" if exit_label == "no" else "no"
|
|
add(f'<text class="yes" x="{x + NODE_W / 2 + 6}" y="{spine - 8}">{opposite}</text>')
|
|
if outcome_lines:
|
|
ob_top = spine + 64
|
|
add(f'<path d="M{x},{spine + NODE_H / 2} V{ob_top - 2}" fill="none" stroke="{OUTCOME[outcome]}" '
|
|
f'stroke-width="2.5" marker-end="url(#a)"/>')
|
|
add(f'<text class="yes" x="{x + 8}" y="{spine + 57}">{escape(exit_label)}</text>')
|
|
add(f'<rect x="{x - 84}" y="{ob_top}" width="168" height="{14 + 19 * len(outcome_lines)}" rx="10" '
|
|
f'fill="{OUTCOME[outcome]}"/>')
|
|
# Yellow and orange keep their bright color, so their text is dark for the contrast.
|
|
cls = "out out-dark" if outcome in ("yellow", "orange") else "out"
|
|
for j, line in enumerate(outcome_lines):
|
|
add(f'<text class="{cls}" x="{x}" y="{ob_top + 23 + j * 19}" text-anchor="middle">{escape(line)}</text>')
|
|
if r + 1 < len(ROWS):
|
|
last, nxt = xs[-1], SPINES[r + 1]
|
|
mid = spine + 158
|
|
add(f'<path d="M{last + NODE_W / 2},{spine} H{last + NODE_W / 2 + 14} V{mid} H{50 + 1500 / len(ROWS[r + 1][1]) / 2} '
|
|
f'V{nxt - NODE_H / 2 - 2}" fill="none" stroke="{c["arrow"]}" stroke-width="2.5" stroke-dasharray="7 5" '
|
|
f'marker-end="url(#a)"/>')
|
|
add('<text class="footer" x="1560" y="890" text-anchor="end">git.javagil.de/mi/werkrevisor</text>')
|
|
add('</svg>')
|
|
return "\n".join(out) + "\n"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
target = sys.argv[1] if len(sys.argv) > 1 else "docs"
|
|
open(f"{target}/werkrevisor-review-decisions.svg", "w").write(svg(LIGHT))
|
|
open(f"{target}/werkrevisor-review-decisions-dark.svg", "w").write(svg(DARK))
|