Coverage for src/ai_jury/incremental.py: 100%

26 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-26 23:18 +0000

1"""Incremental review mode for updated PRs (issue #9). 

2 

3Re-reviewing a large PR's full diff on every push is slow and costly. Incremental 

4mode reviews only what changed since the jury last ran: it records the 

5reviewed head SHA in a hidden marker on its summary comment, and on the next run 

6compares that SHA to the current head to fetch just the new range. 

7 

8This module holds the PURE, network-free core — embedding/parsing the marker and 

9deciding the review scope — so it is fully unit-testable. The thin GitHub calls 

10(fetch head SHA, fetch comment bodies, fetch the range diff) live in 

11``github.py`` and the CLI; this module never touches the network. 

12""" 

13 

14from __future__ import annotations 

15 

16import re 

17 

18# Hidden marker embedded in the jury summary comment recording the SHA that 

19# was reviewed, so a later run can compute the incremental range. 

20_MARKER_RE = re.compile(r"<!--\s*arc-reviewed-sha:([0-9a-fA-F]{7,40})\s*-->") 

21 

22MODE_FULL = "full" 

23MODE_INCREMENTAL = "incremental" 

24 

25 

26def reviewed_sha_marker(sha: str) -> str: 

27 """Return the hidden HTML-comment marker recording the reviewed head SHA.""" 

28 return f"<!-- arc-reviewed-sha:{sha} -->" 

29 

30 

31def parse_reviewed_sha(comment_bodies) -> str | None: 

32 """Return the most recent reviewed SHA across jury comment bodies, or None. 

33 

34 Scans every body for the marker and returns the LAST match found (later 

35 comments override earlier ones), so a fresh re-review marker wins. 

36 """ 

37 last: str | None = None 

38 for body in comment_bodies or []: 

39 for m in _MARKER_RE.finditer(body or ""): 

40 last = m.group(1) 

41 return last 

42 

43 

44def decide_review(prev_sha: str | None, head_sha: str | None) -> tuple[str, str]: 

45 """Decide review scope from the previous reviewed SHA and the current head. 

46 

47 Returns ``(mode, reason)`` where ``mode`` is ``"full"`` or ``"incremental"``. 

48 Falls back to a full review whenever incremental is not safely possible: no 

49 prior marker, unknown head, or an unchanged head (nothing new to review). 

50 """ 

51 if not prev_sha: 

52 return MODE_FULL, "no prior jury marker found — full review" 

53 if not head_sha: 

54 return MODE_FULL, "current head SHA unavailable — full review" 

55 if prev_sha == head_sha: 

56 return MODE_FULL, "head unchanged since last review — full review" 

57 return ( 

58 MODE_INCREMENTAL, 

59 f"incremental: reviewing {prev_sha[:7]}..{head_sha[:7]}", 

60 ) 

61 

62 

63def compare_range(prev_sha: str, head_sha: str) -> str: 

64 """Return the ``base...head`` range spec for the GitHub compare API.""" 

65 return f"{prev_sha}...{head_sha}" 

66 

67 

68def scope_note(mode: str, reason: str) -> str: 

69 """Render the user-facing review-scope line for the report (issue #9).""" 

70 label = "Incremental" if mode == MODE_INCREMENTAL else "Full" 

71 return f"**Review scope:** {label}{reason}"