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.
Files changed (23) hide show
  1. {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
  2. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/alissa.py +42 -0
  3. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/loop.py +25 -7
  4. alissa_tools_github_reviewloop-0.6.0/src/main/alissa/tools/github/reviewloop/version +1 -0
  5. {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
  6. alissa_tools_github_reviewloop-0.5.0/src/main/alissa/tools/github/reviewloop/version +0 -1
  7. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/MANIFEST.in +0 -0
  8. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/README.md +0 -0
  9. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/requirements.txt +0 -0
  10. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/setup.cfg +0 -0
  11. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/setup.py +0 -0
  12. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/__init__.py +0 -0
  13. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/__main__.py +0 -0
  14. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/config.py +0 -0
  15. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/ghclient.py +0 -0
  16. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/proc.py +0 -0
  17. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/prreview.py +0 -0
  18. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/state.py +0 -0
  19. {alissa_tools_github_reviewloop-0.5.0 → alissa_tools_github_reviewloop-0.6.0}/src/main/alissa/tools/github/reviewloop/version.py +0 -0
  20. {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
  21. {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
  22. {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
  23. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-reviewloop
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: ALISSA-TOOLS-GITHUB-REVIEWLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera
@@ -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 (repo, pr, round)."""
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
- completed = len(my_reviews)
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-reviewloop
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: ALISSA-TOOLS-GITHUB-REVIEWLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera