From d20ad1e989b8c59ca51bd67fa2a3bf193afed4e1 Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Tue, 9 Jun 2026 02:42:05 +0000 Subject: [PATCH] =?UTF-8?q?feat(card):=20show=20skipped=20rungs=20as=20row?= =?UTF-8?q?s=20=E2=80=94=20INTENTIONAL=20SKIP=20(green)=20with=20reason=20?= =?UTF-8?q?below?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per operator: intentional skips now render like a pass row but labelled 'INTENTIONAL SKIP' (muted green) with the declared reason on the line beneath; unintentional skips render amber 'UNINTENTIONAL SKIP' with a prompt to add a test or declare them. The cap line is back to just the level-cap reason (the per-rung reason now lives in the rows). Labelled, so it never reads as a PASS. Co-Authored-By: Claude Opus 4.8 --- runner/harness/card.py | 49 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/runner/harness/card.py b/runner/harness/card.py index e57d517..21418dc 100644 --- a/runner/harness/card.py +++ b/runner/harness/card.py @@ -141,6 +141,43 @@ def _stage_rows(stages: list[dict]) -> str: return "\n".join(rows) or 'no stages' +# Friendly rung labels for the skip rows. +RUNG_LABEL = { + "install": "install", + "upgrade": "upgrade", + "backup_restore": "backup/restore", + "functional": "functional", + "integration": "integration", + "recipe_local": "recipe-local", +} +SKIP_GREEN = "#57ab5a" # muted green — an intentional skip reads like a pass (but labelled, never inflating) + + +def _skip_rows(skips: dict) -> str: + """Render SKIPPED rungs as stage-like rows. An intentional (declared) skip looks like a pass row + but its status says 'INTENTIONAL SKIP' (muted green) with the declared reason on the line below; + an unintentional skip is amber 'UNINTENTIONAL SKIP' with a prompt to add a test or declare it.""" + rows = [] + for rung, reason in (skips.get("intentional") or {}).items(): + rows.append( + f'' + f'{html.escape(RUNG_LABEL.get(rung, rung))}' + f'intentional skip' + ) + rows.append(f'{html.escape(reason)}') + for rung in skips.get("unintentional") or []: + rows.append( + f'' + f'{html.escape(RUNG_LABEL.get(rung, rung))}' + f'unintentional skip' + ) + rows.append( + 'not declared in EXPECTED_NA — add the ' + "missing test/label, or declare the skip with a reason" + ) + return "\n".join(rows) + + def render_card_html(data: dict, screenshot_rel: str | None = "screenshot.png") -> str: """Build the summary-card HTML from a results.json dict. `screenshot_rel` is the relative path to the screenshot PNG (same dir as the card) — omitted from the card if None / absent. @@ -151,15 +188,8 @@ def render_card_html(data: dict, screenshot_rel: str | None = "screenshot.png") version = html.escape(str(data.get("version") or data.get("ref") or "")) level = int(data.get("level", 0)) cap_reason = str(data.get("level_cap_reason") or "") - # Annotate the cap line by whether the capping rung was an intentional skip (declared, with its - # reason) or an unintentional one (skipped but not declared). - capped = data.get("level_cap_rung") - sk = data.get("skips", {}) or {} - if capped and capped in (sk.get("intentional") or {}): - cap_reason += f" · intentional: {sk['intentional'][capped]}" - elif capped and capped in (sk.get("unintentional") or []): - cap_reason += " · unintentional skip (no EXPECTED_NA — add a test or declare it)" cap = html.escape(cap_reason) + sk = data.get("skips", {}) or {} color = level_color(level) flags = data.get("flags", {}) or {} flag_bits = [] @@ -175,7 +205,7 @@ def render_card_html(data: dict, screenshot_rel: str | None = "screenshot.png") if show_shot else '
no screenshot
' ) - rows = _stage_rows(data.get("stages", [])) + rows = _stage_rows(data.get("stages", [])) + "\n" + _skip_rows(sk) return f"""