"""IMP-#91 u14 — idempotent status-board marker updater. Reads a pytest-json-report artifact emitted by the IMP-91 CI workflow and rewrites paired ``...`` markers inside the Phase Z status board with a single-character outcome symbol. Pure functions (``parse_outcomes`` / ``update_board_text``) are exposed so ``tests/scripts/test_update_status_board.py`` can exercise the contract without invoking pytest. The CLI just wires file IO around them so the GitHub Actions step in u15 can call it deterministically. The updater is additive: untouched markers stay; missing outcomes render ``?`` so a collection failure is loud, not silent. [[feedback_auto_pipeline_first]] [[feedback_artifact_status_naming]] """ from __future__ import annotations import argparse import json import re from pathlib import Path from typing import Dict, Mapping, Tuple AXIS_FROM_TEST = { "test_normalize_snapshot_matches": "F0", "test_v4_ranking_snapshot_matches": "F1", "test_slot_payload_snapshot_matches": "F2", "test_ai_classifier_snapshot_matches": "F3", "test_layout_snapshot_matches": "F4", "test_final_html_snapshot_matches": "F5", } SYMBOL = {"passed": "PASS", "failed": "FAIL", "error": "ERR", "skipped": "SKIP"} NODEID_RE = re.compile(r"::(test_[a-z0-9_]+)\[(\d{2})\]$") MARKER_RE = re.compile( r"()(.*?)()", re.DOTALL ) def parse_outcomes(report: Mapping[str, object]) -> Dict[Tuple[str, str], str]: out: Dict[Tuple[str, str], str] = {} for test in report.get("tests", []) or []: m = NODEID_RE.search(str(test.get("nodeid", ""))) if not m: continue axis = AXIS_FROM_TEST.get(m.group(1)) if not axis: continue out[(axis, m.group(2))] = SYMBOL.get(str(test.get("outcome")), "?") return out def update_board_text(board: str, outcomes: Mapping[Tuple[str, str], str]) -> str: def repl(match: "re.Match[str]") -> str: key = (match.group(2), match.group(3)) symbol = outcomes.get(key, "?") return f"{match.group(1)}{symbol}{match.group(5)}" return MARKER_RE.sub(repl, board) def main() -> int: parser = argparse.ArgumentParser(description="IMP-91 status-board updater") parser.add_argument("--report", required=True, type=Path) parser.add_argument("--board", required=True, type=Path) args = parser.parse_args() report = json.loads(args.report.read_text(encoding="utf-8")) outcomes = parse_outcomes(report) args.board.write_text( update_board_text(args.board.read_text(encoding="utf-8"), outcomes), encoding="utf-8", ) return 0 if __name__ == "__main__": raise SystemExit(main())