alissa-tools-github-reviewloop 0.3.0__tar.gz → 0.5.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.3.0/src/main/alissa_tools_github_reviewloop.egg-info → alissa_tools_github_reviewloop-0.5.0}/PKG-INFO +1 -1
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa/tools/github/reviewloop/alissa.py +21 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa/tools/github/reviewloop/loop.py +62 -4
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa/tools/github/reviewloop/state.py +18 -0
- alissa_tools_github_reviewloop-0.5.0/src/main/alissa/tools/github/reviewloop/version +1 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0/src/main/alissa_tools_github_reviewloop.egg-info}/PKG-INFO +1 -1
- alissa_tools_github_reviewloop-0.3.0/src/main/alissa/tools/github/reviewloop/version +0 -1
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/MANIFEST.in +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/README.md +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/requirements.txt +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/setup.cfg +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/setup.py +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa/tools/github/reviewloop/__init__.py +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa/tools/github/reviewloop/__main__.py +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa/tools/github/reviewloop/config.py +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa/tools/github/reviewloop/ghclient.py +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa/tools/github/reviewloop/proc.py +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa/tools/github/reviewloop/prreview.py +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa/tools/github/reviewloop/version.py +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa_tools_github_reviewloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa_tools_github_reviewloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa_tools_github_reviewloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.0}/src/main/alissa_tools_github_reviewloop.egg-info/top_level.txt +0 -0
|
@@ -185,6 +185,27 @@ class Alissa:
|
|
|
185
185
|
|
|
186
186
|
run(argv, timeout=60)
|
|
187
187
|
|
|
188
|
+
# Reviewers are one-shot per round (CR3): once the session finishes and is
|
|
189
|
+
# reaped, it must never be respawned. Make that explicit so a self-kill or
|
|
190
|
+
# a daemon reap can't trigger a respawn loop. Best-effort — an older CLI
|
|
191
|
+
# without `queue set` should not fail the enqueue.
|
|
192
|
+
try:
|
|
193
|
+
run(["alissa", "tmux", "queue", "set", session, "respawn", "off"],
|
|
194
|
+
timeout=30, check=False)
|
|
195
|
+
except CommandError: # pragma: no cover - defence in depth
|
|
196
|
+
log.warning("could not set respawn off for %s", session)
|
|
197
|
+
|
|
198
|
+
def kill_session(self, session: str, *, dry_run: bool = False) -> None:
|
|
199
|
+
"""Kill a finished reviewer's managed session to free its worker slot.
|
|
200
|
+
|
|
201
|
+
Best-effort and idempotent-friendly: the session may already be gone (the
|
|
202
|
+
reviewer self-killed), so a non-zero exit is not an error here.
|
|
203
|
+
"""
|
|
204
|
+
if dry_run:
|
|
205
|
+
log.info("[dry-run] would kill session %s", session)
|
|
206
|
+
return
|
|
207
|
+
run(["alissa", "tmux", "kill", session], timeout=30, check=False)
|
|
208
|
+
|
|
188
209
|
def add_repo_to_workspace(
|
|
189
210
|
self, owner: str, repo: str, workspace_root: Path, *, dry_run: bool = False
|
|
190
211
|
) -> None:
|
|
@@ -29,14 +29,40 @@ log = logging.getLogger(__name__)
|
|
|
29
29
|
# (skill failure mode: "reviewer session stalls"). The round is re-enqueued.
|
|
30
30
|
STALE_ROUND_SECONDS = 90 * 60
|
|
31
31
|
|
|
32
|
+
# The closing contract is spelled out in both directives (not just the skill)
|
|
33
|
+
# because it is the reviewer's most-skipped step: on re-review, sessions produce
|
|
34
|
+
# findings but never register the review on the PR, or stop without a verdict.
|
|
35
|
+
_CLOSE_THE_ROUND = (
|
|
36
|
+
"CLOSE THE ROUND — both are mandatory or the round does not count: "
|
|
37
|
+
"(1) SUBMIT your review so it lands as one registered review record ON the "
|
|
38
|
+
"PR (gh pr review / the reviews API) and confirm it with "
|
|
39
|
+
"`gh api repos/<org>/<repo>/pulls/<n>/reviews` — findings left only in your "
|
|
40
|
+
"session do not exist; (2) end with a decisive verdict — approve OR "
|
|
41
|
+
"request_changes, never neither, never comment-only. You are read-only: "
|
|
42
|
+
"never commit or fix, even a one-character typo — a needed fix IS "
|
|
43
|
+
"request_changes. "
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Reviewers are one-shot per round (CR3), so a finished session should not linger
|
|
47
|
+
# holding a worker slot. The daemon reaps it as a backstop, but the fast path is
|
|
48
|
+
# the reviewer releasing its own slot as its very last action. {session} is the
|
|
49
|
+
# reviewer's own managed session name, injected at spawn.
|
|
50
|
+
_RELEASE_SLOT = (
|
|
51
|
+
"FINALLY, and only once the round is fully closed above (review registered "
|
|
52
|
+
"AND verdict recorded), release your worker slot as your last action: run "
|
|
53
|
+
"`alissa tmux kill {session}`. Do nothing after it."
|
|
54
|
+
)
|
|
55
|
+
|
|
32
56
|
ROUND_1_DIRECTIVE = (
|
|
33
57
|
"You are a PR REVIEWER, not an implementer. {assignment} "
|
|
34
58
|
"Load the alissa-code-review skill and follow procedures/review-a-pr.md: "
|
|
35
59
|
"hydrate the task and the PR it names, review per the rubric, post "
|
|
36
60
|
"severity-tagged comments via gh pr review, record the verdict evidence, "
|
|
37
61
|
"move the task to pending_validation. "
|
|
62
|
+
+ _CLOSE_THE_ROUND +
|
|
38
63
|
"NEVER push commits, merge, or change PR state. "
|
|
39
|
-
"Do NOT create further ali-* sessions."
|
|
64
|
+
"Do NOT create further ali-* sessions. "
|
|
65
|
+
+ _RELEASE_SLOT
|
|
40
66
|
)
|
|
41
67
|
|
|
42
68
|
ROUND_K_DIRECTIVE = (
|
|
@@ -46,8 +72,10 @@ ROUND_K_DIRECTIVE = (
|
|
|
46
72
|
"including its round-k section: verify the triage of every prior finding, "
|
|
47
73
|
"verify the fixes, sweep the new diff with the full rubric, record a "
|
|
48
74
|
"round-{round} verdict envelope, move the task to pending_validation. "
|
|
75
|
+
+ _CLOSE_THE_ROUND +
|
|
49
76
|
"NEVER push commits, merge, or change PR state. "
|
|
50
|
-
"Do NOT create further ali-* sessions."
|
|
77
|
+
"Do NOT create further ali-* sessions. "
|
|
78
|
+
+ _RELEASE_SLOT
|
|
51
79
|
)
|
|
52
80
|
|
|
53
81
|
ESCALATION_COMMENT = (
|
|
@@ -117,6 +145,11 @@ class ReviewWatcher:
|
|
|
117
145
|
my_reviews = self.github.my_reviews(owner, repo, number)
|
|
118
146
|
completed = len(my_reviews)
|
|
119
147
|
|
|
148
|
+
# Backstop: reap the sessions of rounds that have already landed a review
|
|
149
|
+
# (the fast path is the reviewer self-killing, but it may forget). A round
|
|
150
|
+
# <= completed is done; its session, if we still have it, can be killed.
|
|
151
|
+
self._reap_finished(pr.full_name, number, completed)
|
|
152
|
+
|
|
120
153
|
# Looked up here rather than inside _spawn: convergence needs the ref
|
|
121
154
|
# too. _spawn still handles `task is None` exactly as before.
|
|
122
155
|
task = self.alissa.find_review_task(owner, repo, number)
|
|
@@ -173,6 +206,31 @@ class ReviewWatcher:
|
|
|
173
206
|
|
|
174
207
|
return None
|
|
175
208
|
|
|
209
|
+
def _reap_finished(self, repo: str, number: int, completed: int) -> None:
|
|
210
|
+
"""Kill the managed session of every round whose review has landed.
|
|
211
|
+
|
|
212
|
+
A round `<= completed` is done (its review was submitted), so its session
|
|
213
|
+
is finished and should not hold a worker slot. The reviewer self-kills as
|
|
214
|
+
the fast path; this is the backstop for when it forgets. Idempotent — each
|
|
215
|
+
session is killed at most once (recorded in the ledger) — never runs in
|
|
216
|
+
dry-run, and best-effort: a reap failure (or an already-gone session) must
|
|
217
|
+
not take the poll down.
|
|
218
|
+
"""
|
|
219
|
+
if self.config.dry_run or completed < 1:
|
|
220
|
+
return
|
|
221
|
+
for round_ in range(1, completed + 1):
|
|
222
|
+
row = self.state.get_spawn(repo, number, round_)
|
|
223
|
+
if row is None or self.state.is_reaped(row["session"]):
|
|
224
|
+
continue
|
|
225
|
+
session = row["session"]
|
|
226
|
+
try:
|
|
227
|
+
self.alissa.kill_session(session)
|
|
228
|
+
except Exception: # pragma: no cover - defence in depth
|
|
229
|
+
log.exception("failed to reap session %s", session)
|
|
230
|
+
continue
|
|
231
|
+
self.state.record_reap(session)
|
|
232
|
+
log.info("reaped finished reviewer session %s (round %d done)", session, round_)
|
|
233
|
+
|
|
176
234
|
# -- actions -----------------------------------------------------------
|
|
177
235
|
|
|
178
236
|
def _spawn(self, pr: PullRequest, round_: int, task: Task | None) -> Decision:
|
|
@@ -194,16 +252,16 @@ class ReviewWatcher:
|
|
|
194
252
|
else:
|
|
195
253
|
assignment = f"You've been assigned Alissa review task {task.ref}."
|
|
196
254
|
|
|
255
|
+
name = session_name(pr, round_)
|
|
197
256
|
template = ROUND_1_DIRECTIVE if round_ == 1 else ROUND_K_DIRECTIVE
|
|
198
257
|
directive = template.format(
|
|
199
|
-
assignment=assignment, round=round_, cap=self.config.round_cap
|
|
258
|
+
assignment=assignment, round=round_, cap=self.config.round_cap, session=name
|
|
200
259
|
)
|
|
201
260
|
|
|
202
261
|
hub, problem = self._ensure_hub(pr)
|
|
203
262
|
if problem is not None:
|
|
204
263
|
return Decision(Action.SKIPPED, problem, round_)
|
|
205
264
|
|
|
206
|
-
name = session_name(pr, round_)
|
|
207
265
|
self.alissa.enqueue_reviewer(
|
|
208
266
|
session=name,
|
|
209
267
|
directive=directive,
|
|
@@ -31,6 +31,11 @@ CREATE TABLE IF NOT EXISTS escalations (
|
|
|
31
31
|
escalated_at INTEGER NOT NULL,
|
|
32
32
|
PRIMARY KEY (repo, number, head_sha)
|
|
33
33
|
);
|
|
34
|
+
|
|
35
|
+
CREATE TABLE IF NOT EXISTS reaps (
|
|
36
|
+
session TEXT NOT NULL PRIMARY KEY,
|
|
37
|
+
reaped_at INTEGER NOT NULL
|
|
38
|
+
);
|
|
34
39
|
"""
|
|
35
40
|
|
|
36
41
|
|
|
@@ -81,6 +86,19 @@ class State:
|
|
|
81
86
|
)
|
|
82
87
|
self._db.commit()
|
|
83
88
|
|
|
89
|
+
def is_reaped(self, session: str) -> bool:
|
|
90
|
+
row = self._db.execute(
|
|
91
|
+
"SELECT 1 FROM reaps WHERE session=?", (session,)
|
|
92
|
+
).fetchone()
|
|
93
|
+
return row is not None
|
|
94
|
+
|
|
95
|
+
def record_reap(self, session: str) -> None:
|
|
96
|
+
self._db.execute(
|
|
97
|
+
"INSERT OR REPLACE INTO reaps (session, reaped_at) VALUES (?,?)",
|
|
98
|
+
(session, int(time.time())),
|
|
99
|
+
)
|
|
100
|
+
self._db.commit()
|
|
101
|
+
|
|
84
102
|
def escalated(self, repo: str, number: int, head_sha: str) -> bool:
|
|
85
103
|
row = self._db.execute(
|
|
86
104
|
"SELECT 1 FROM escalations WHERE repo=? AND number=? AND head_sha=?",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.5.0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.3.0
|
|
File without changes
|
|
File without changes
|
{alissa_tools_github_reviewloop-0.3.0 → alissa_tools_github_reviewloop-0.5.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
|