Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ Record the outcome and move on:
uv run --project <framework>/tools/pr-management pr-management code-review session record --session <scratch>/cr-session.json --pr <N> --outcome <DISP|skipped|…> [--reason <text>] [--adversarial yes|no]
```

`<DISP>` is the review that was posted: `APPROVE`, `REQUEST_CHANGES` or `COMMENT`, or `FIXUP` when the maintainer pushed a fixup and then approved ([`classifications/disposition-fixup.md`](classifications/disposition-fixup.md)), with or without auto-merge.

To keep wall-clock time low on a long queue, fire background analysis subagents on the next PRs while the maintainer works on this one — below.

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Each PR's outcome is recorded with `uv run --project <framework>/tools/pr-management pr-management code-review session record` as the loop goes
(Step 9 of [`review-flow.md`](review-flow.md)). On exit, print the `text` of
`uv run --project <framework>/tools/pr-management pr-management code-review session summary --session <scratch>/cr-session.json --untouched <count>`:
reviews per disposition, skips with the maintainer's reasons, PRs left
reviews per disposition (a `FIXUP` counts as a review), skips with the maintainer's reasons, PRs left
untouched, which PRs had adversarial findings folded in, time, throughput and
the GitHub calls spent. The summary is for the maintainer's records — this
skill never writes a session log anywhere else.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,20 @@
#: A pass that crosses this many calls is mis-batching: stop and fix the call pattern.
BUDGET_ALARM = 100

OUTCOMES = ("APPROVE", "REQUEST_CHANGES", "COMMENT", "skipped", "slop-comment", "slop-close", "dry-run")
#: ``FIXUP`` is a maintainer fixup pushed to the PR branch followed by an
#: ``APPROVE`` that explains it (classifications/disposition-fixup.md); it counts
#: as a review, like the three GitHub review events.
OUTCOMES = (
"APPROVE",
"REQUEST_CHANGES",
"COMMENT",
"FIXUP",
"skipped",
"slop-comment",
"slop-close",
"dry-run",
)
REVIEWED = ("APPROVE", "REQUEST_CHANGES", "COMMENT", "FIXUP")


def _load(path: Path) -> dict[str, Any]:
Expand Down Expand Up @@ -83,13 +96,13 @@ def summary(path: Path, *, untouched: int = 0, now: dt.datetime) -> dict[str, An
counts = Counter(v["outcome"] for v in prs.values())
started = dt.datetime.fromisoformat(data["started_at"]) if data.get("started_at") else now
minutes = max(1, int((now - started).total_seconds() // 60))
reviewed = sum(counts[k] for k in ("APPROVE", "REQUEST_CHANGES", "COMMENT"))
reviewed = sum(counts[k] for k in REVIEWED)
skipped = [{"pr": int(k), "reason": v.get("reason")} for k, v in prs.items() if v["outcome"] == "skipped"]
covered = sorted(int(k) for k, v in prs.items() if v.get("adversarial"))
uncovered = sorted(int(k) for k, v in prs.items() if v.get("adversarial") is False)
lines = [
f"Reviewed: {reviewed} (APPROVE {counts['APPROVE']}, REQUEST_CHANGES {counts['REQUEST_CHANGES']}, "
f"COMMENT {counts['COMMENT']})",
f"COMMENT {counts['COMMENT']}, FIXUP {counts['FIXUP']})",
f"Skipped: {len(skipped)}"
+ (
""
Expand Down
19 changes: 16 additions & 3 deletions tools/pr-management/src/pr_management/code_review/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,20 @@
#: A pass that crosses this many calls is mis-batching: stop and fix the call pattern.
BUDGET_ALARM = 100

OUTCOMES = ("APPROVE", "REQUEST_CHANGES", "COMMENT", "skipped", "slop-comment", "slop-close", "dry-run")
#: ``FIXUP`` is a maintainer fixup pushed to the PR branch followed by an
#: ``APPROVE`` that explains it (classifications/disposition-fixup.md); it counts
#: as a review, like the three GitHub review events.
OUTCOMES = (
"APPROVE",
"REQUEST_CHANGES",
"COMMENT",
"FIXUP",
"skipped",
"slop-comment",
"slop-close",
"dry-run",
)
REVIEWED = ("APPROVE", "REQUEST_CHANGES", "COMMENT", "FIXUP")


def _load(path: Path) -> dict[str, Any]:
Expand Down Expand Up @@ -83,13 +96,13 @@ def summary(path: Path, *, untouched: int = 0, now: dt.datetime) -> dict[str, An
counts = Counter(v["outcome"] for v in prs.values())
started = dt.datetime.fromisoformat(data["started_at"]) if data.get("started_at") else now
minutes = max(1, int((now - started).total_seconds() // 60))
reviewed = sum(counts[k] for k in ("APPROVE", "REQUEST_CHANGES", "COMMENT"))
reviewed = sum(counts[k] for k in REVIEWED)
skipped = [{"pr": int(k), "reason": v.get("reason")} for k, v in prs.items() if v["outcome"] == "skipped"]
covered = sorted(int(k) for k, v in prs.items() if v.get("adversarial"))
uncovered = sorted(int(k) for k, v in prs.items() if v.get("adversarial") is False)
lines = [
f"Reviewed: {reviewed} (APPROVE {counts['APPROVE']}, REQUEST_CHANGES {counts['REQUEST_CHANGES']}, "
f"COMMENT {counts['COMMENT']})",
f"COMMENT {counts['COMMENT']}, FIXUP {counts['FIXUP']})",
f"Skipped: {len(skipped)}"
+ (
""
Expand Down
37 changes: 37 additions & 0 deletions tools/pr-management/tests/code_review/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,43 @@ def test_session_summary(capsys: pytest.CaptureFixture[str], project: Path, tmp_
assert out["counts"] == {"APPROVE": 1, "COMMENT": 1, "skipped": 1} and "Reviewed: 2" in out["text"]


def test_session_records_fixup_as_a_review(
capsys: pytest.CaptureFixture[str], project: Path, tmp_path: Path
) -> None:
ses = tmp_path / "session.json"
for pr, outcome in ((1, "FIXUP"), (2, "APPROVE")):
_run(
capsys,
"--config-dir",
str(project),
"code-review",
"session",
"record",
"--session",
str(ses),
"--pr",
str(pr),
"--outcome",
outcome,
"--now",
NOW.isoformat(),
)
out = _run(
capsys,
"--config-dir",
str(project),
"code-review",
"session",
"summary",
"--session",
str(ses),
"--now",
NOW.isoformat(),
)
assert out["counts"] == {"FIXUP": 1, "APPROVE": 1}
assert "Reviewed: 2 (APPROVE 1, REQUEST_CHANGES 0, COMMENT 0, FIXUP 1)" in out["text"]


def test_slop_comment_builds_quoted_commands(
capsys: pytest.CaptureFixture[str], project: Path, tmp_path: Path
) -> None:
Expand Down
Loading