alissa-tools-github-reviewloop 0.5.0__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {alissa_tools_github_reviewloop-0.5.0/src/main/alissa_tools_github_reviewloop.egg-info → alissa_tools_github_reviewloop-0.6.0}/PKG-INFO +1 -1
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/alissa.py +42 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/loop.py +25 -7
- alissa_tools_github_reviewloop-0.6.0/src/main/alissa/tools/github/reviewloop/version +1 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0/src/main/alissa_tools_github_reviewloop.egg-info}/PKG-INFO +1 -1
- alissa_tools_github_reviewloop-0.5.0/src/main/alissa/tools/github/reviewloop/version +0 -1
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/MANIFEST.in +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/README.md +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/requirements.txt +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/setup.cfg +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/setup.py +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/__init__.py +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/__main__.py +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/config.py +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/ghclient.py +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/proc.py +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/prreview.py +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/state.py +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/version.py +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa_tools_github_reviewloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa_tools_github_reviewloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa_tools_github_reviewloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa_tools_github_reviewloop.egg-info/top_level.txt +0 -0
|
@@ -154,6 +154,48 @@ class Alissa:
|
|
|
154
154
|
# lost its timestamp.
|
|
155
155
|
return max(found, key=lambda pair: pair[0])[1]
|
|
156
156
|
|
|
157
|
+
def count_verdicts(self, task_ref: str) -> int:
|
|
158
|
+
"""How many CR6 verdict envelopes are on the review task.
|
|
159
|
+
|
|
160
|
+
One envelope per completed round (CR7, append-only), so this is the
|
|
161
|
+
authoritative round count -- unlike the GitHub review count it cannot be
|
|
162
|
+
thrown off by an empty-bodied review or two reviews in one cycle. Never
|
|
163
|
+
raises: absent, empty, or malformed evidence degrades to 0 (round 1)
|
|
164
|
+
rather than taking the loop down.
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
data = run_json(["alissa", "task", "get", task_ref, "--json"], timeout=90)
|
|
168
|
+
except CommandError as exc:
|
|
169
|
+
log.warning("could not read verdict evidence for %s: %s", task_ref, exc)
|
|
170
|
+
return 0
|
|
171
|
+
except Exception: # pragma: no cover - defence in depth
|
|
172
|
+
log.exception("unexpected failure reading verdict evidence for %s", task_ref)
|
|
173
|
+
return 0
|
|
174
|
+
try:
|
|
175
|
+
return self._count_verdicts(data)
|
|
176
|
+
except Exception: # pragma: no cover - defence in depth
|
|
177
|
+
log.exception("could not count verdict evidence for %s", task_ref)
|
|
178
|
+
return 0
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def _count_verdicts(payload: object) -> int:
|
|
182
|
+
"""Count evidence items carrying a verdict envelope. Mirrors
|
|
183
|
+
`_newest_verdict`'s tolerant parsing -- one match per item at most."""
|
|
184
|
+
if not isinstance(payload, dict):
|
|
185
|
+
return 0
|
|
186
|
+
evidence = payload.get("evidence")
|
|
187
|
+
if not isinstance(evidence, list):
|
|
188
|
+
return 0
|
|
189
|
+
count = 0
|
|
190
|
+
for item in evidence:
|
|
191
|
+
if not isinstance(item, dict):
|
|
192
|
+
continue
|
|
193
|
+
for blob in (item.get("title"), item.get("markdownContent")):
|
|
194
|
+
if isinstance(blob, str) and _VERDICT_RE.search(blob):
|
|
195
|
+
count += 1
|
|
196
|
+
break
|
|
197
|
+
return count
|
|
198
|
+
|
|
157
199
|
def enqueue_reviewer(
|
|
158
200
|
self,
|
|
159
201
|
*,
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import logging
|
|
14
14
|
import re
|
|
15
|
+
import secrets
|
|
15
16
|
import time
|
|
16
17
|
from dataclasses import dataclass
|
|
17
18
|
from enum import Enum
|
|
@@ -105,9 +106,17 @@ class Decision:
|
|
|
105
106
|
|
|
106
107
|
|
|
107
108
|
def session_name(pr: PullRequest, round_: int) -> str:
|
|
108
|
-
"""tmux-safe, unique per
|
|
109
|
+
"""A tmux-safe reviewer session name, unique per spawn.
|
|
110
|
+
|
|
111
|
+
The `review-<repo>-pr<n>-r<round>` prefix stays human-readable, but a short
|
|
112
|
+
random nonce is appended so a re-used or miscounted round number can never
|
|
113
|
+
collide with a still-live session (a collision wedges the worker -- the
|
|
114
|
+
original 'stuck' failure). Safe to be non-deterministic: the generated name
|
|
115
|
+
is recorded in the spawn ledger and is what gets reaped / self-killed, so the
|
|
116
|
+
daemon never re-derives it.
|
|
117
|
+
"""
|
|
109
118
|
repo = re.sub(r"[^A-Za-z0-9-]", "-", pr.repo).strip("-").lower()
|
|
110
|
-
return f"review-{repo}-pr{pr.number}-r{round_}"
|
|
119
|
+
return f"review-{repo}-pr{pr.number}-r{round_}-{secrets.token_hex(3)}"
|
|
111
120
|
|
|
112
121
|
|
|
113
122
|
class ReviewWatcher:
|
|
@@ -143,17 +152,26 @@ class ReviewWatcher:
|
|
|
143
152
|
)
|
|
144
153
|
|
|
145
154
|
my_reviews = self.github.my_reviews(owner, repo, number)
|
|
146
|
-
|
|
155
|
+
|
|
156
|
+
# The review task (CR2) is the round record: one verdict envelope per
|
|
157
|
+
# round (CR7), so counting envelopes is the authoritative "rounds
|
|
158
|
+
# completed" -- immune to the GitHub heuristics that drift. A round whose
|
|
159
|
+
# review has an empty body undercounts (round_ repeats -> the session name
|
|
160
|
+
# collides -> the worker wedges); two reviews in one cycle overcount.
|
|
161
|
+
# Fall back to the substantive-review count only before the review task
|
|
162
|
+
# exists (round 1). Looked up here (not in _spawn) because both the count
|
|
163
|
+
# and convergence need it.
|
|
164
|
+
task = self.alissa.find_review_task(owner, repo, number)
|
|
165
|
+
completed = (
|
|
166
|
+
self.alissa.count_verdicts(task.ref) if task is not None
|
|
167
|
+
else len(my_reviews)
|
|
168
|
+
)
|
|
147
169
|
|
|
148
170
|
# Backstop: reap the sessions of rounds that have already landed a review
|
|
149
171
|
# (the fast path is the reviewer self-killing, but it may forget). A round
|
|
150
172
|
# <= completed is done; its session, if we still have it, can be killed.
|
|
151
173
|
self._reap_finished(pr.full_name, number, completed)
|
|
152
174
|
|
|
153
|
-
# Looked up here rather than inside _spawn: convergence needs the ref
|
|
154
|
-
# too. _spawn still handles `task is None` exactly as before.
|
|
155
|
-
task = self.alissa.find_review_task(owner, repo, number)
|
|
156
|
-
|
|
157
175
|
converged = self._convergence_reason(my_reviews, task)
|
|
158
176
|
if converged is not None:
|
|
159
177
|
return Decision(Action.CONVERGED, converged, completed)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.6.0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.5.0
|
|
File without changes
|
|
File without changes
|
{alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/requirements.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|