alissa-tools-github-revloop 0.20.0__tar.gz → 0.21.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_revloop-0.20.0/src/main/alissa_tools_github_revloop.egg-info → alissa_tools_github_revloop-0.21.0}/PKG-INFO +1 -1
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/__main__.py +10 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/config.py +39 -2
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/loop.py +37 -0
- alissa_tools_github_revloop-0.21.0/src/main/alissa/tools/github/revloop/version +1 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0/src/main/alissa_tools_github_revloop.egg-info}/PKG-INFO +1 -1
- alissa_tools_github_revloop-0.20.0/src/main/alissa/tools/github/revloop/version +0 -1
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/LICENSE +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/MANIFEST.in +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/NOTICE +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/README.md +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/requirements.txt +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/setup.cfg +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/setup.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/__init__.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/alissa.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/ghclient.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/proc.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/prreview.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/state.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/version.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/webui/__init__.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/webui/__main__.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/webui/auth.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/webui/page.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/webui/server.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/webui/sources.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa/tools/github/revloop/webui/sysinfo.py +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa_tools_github_revloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa_tools_github_revloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa_tools_github_revloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_revloop-0.20.0 → alissa_tools_github_revloop-0.21.0}/src/main/alissa_tools_github_revloop.egg-info/top_level.txt +0 -0
|
@@ -80,6 +80,15 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
80
80
|
metavar="OWNER/REPO",
|
|
81
81
|
help="only watch this repo; repeatable. Replaces the config list entirely.",
|
|
82
82
|
)
|
|
83
|
+
over.add_argument(
|
|
84
|
+
"--author",
|
|
85
|
+
dest="authors",
|
|
86
|
+
action="append",
|
|
87
|
+
metavar="LOGIN",
|
|
88
|
+
help="only review PRs opened by this GitHub login; repeatable. "
|
|
89
|
+
"Replaces the config list entirely. Empty = every author is served "
|
|
90
|
+
"(a scope filter, not a grant); the self-review skip still applies.",
|
|
91
|
+
)
|
|
83
92
|
over.add_argument(
|
|
84
93
|
"--operator",
|
|
85
94
|
dest="operators",
|
|
@@ -194,6 +203,7 @@ def overrides_from(args: argparse.Namespace) -> dict:
|
|
|
194
203
|
through. `repos` becomes a tuple so it matches the config-file form."""
|
|
195
204
|
return {
|
|
196
205
|
"repos": tuple(args.repos) if args.repos else None,
|
|
206
|
+
"authors": tuple(args.authors) if args.authors else None,
|
|
197
207
|
"operators": tuple(args.operators) if args.operators else None,
|
|
198
208
|
"poll_interval": args.poll_interval,
|
|
199
209
|
"round_cap": args.round_cap,
|
|
@@ -120,6 +120,7 @@ CONFIG_KEYS = (
|
|
|
120
120
|
"poll_interval",
|
|
121
121
|
"round_cap",
|
|
122
122
|
"repos",
|
|
123
|
+
"authors",
|
|
123
124
|
"operators",
|
|
124
125
|
"agent_profile",
|
|
125
126
|
"reviewer_login",
|
|
@@ -262,6 +263,21 @@ class Config:
|
|
|
262
263
|
# Empty tuple means "every repo that requests a review from me".
|
|
263
264
|
repos: tuple[str, ...] = ()
|
|
264
265
|
|
|
266
|
+
# GitHub logins whose PRs this loop will spend rounds on. A SCOPE FILTER,
|
|
267
|
+
# not a capability grant -- so, like `repos` and unlike `operators`, empty
|
|
268
|
+
# (the default) means EVERY author, and an existing deployment with no
|
|
269
|
+
# `authors` key behaves exactly as it did before the key existed.
|
|
270
|
+
#
|
|
271
|
+
# The distinction is worth keeping straight: `operators` empty means NOBODY
|
|
272
|
+
# because honouring an ack is a power being handed out, and a grant that
|
|
273
|
+
# defaults to everyone is a hole. Serving a review request is not a power
|
|
274
|
+
# the daemon hands to the author -- summoning the loop already costs repo
|
|
275
|
+
# write access (to request the reviewer) plus a place on the `repos`
|
|
276
|
+
# allowlist. This key only narrows which of those already-authorised PRs are
|
|
277
|
+
# worth the rounds: skip dependabot/renovate, or a teammate who does not
|
|
278
|
+
# want agent reviews.
|
|
279
|
+
authors: tuple[str, ...] = ()
|
|
280
|
+
|
|
265
281
|
# GitHub logins whose re-entry ack may raise a capped PR's effective cap
|
|
266
282
|
# (loop.parse_reentry_ack). Empty -- the default -- means NO ack is ever
|
|
267
283
|
# honoured: the lever fails closed, because anyone who can comment on a PR
|
|
@@ -356,6 +372,23 @@ class Config:
|
|
|
356
372
|
def watches(self, full_name: str) -> bool:
|
|
357
373
|
return not self.repos or full_name in self.repos
|
|
358
374
|
|
|
375
|
+
def serves_author(self, login: str) -> bool:
|
|
376
|
+
"""Whether a PR by this login is in scope for the loop.
|
|
377
|
+
|
|
378
|
+
Empty list = every author (the filter pattern, see the field). Otherwise
|
|
379
|
+
membership case-insensitively, because GitHub logins are: a config
|
|
380
|
+
saying `Alissa-App` must match the `alissa-app` the API returns, or the
|
|
381
|
+
allowlist silently serves nobody -- the same failure mode `_string_list`
|
|
382
|
+
exists to prevent. Mirrors `loop.ReviewWatcher._is_operator`.
|
|
383
|
+
|
|
384
|
+
This is not the self-review guard and cannot stand in for it: an author
|
|
385
|
+
listed here is still refused if it is the reviewer identity, because
|
|
386
|
+
that check runs first and independently (see `loop.evaluate`).
|
|
387
|
+
"""
|
|
388
|
+
if not self.authors:
|
|
389
|
+
return True
|
|
390
|
+
return login.lower() in {a.lower() for a in self.authors}
|
|
391
|
+
|
|
359
392
|
@classmethod
|
|
360
393
|
def build(
|
|
361
394
|
cls,
|
|
@@ -399,6 +432,9 @@ class Config:
|
|
|
399
432
|
)
|
|
400
433
|
|
|
401
434
|
repos = _string_list(raw.get("repos", ()), "repos", "owner/repo entries")
|
|
435
|
+
authors = _string_list(
|
|
436
|
+
raw.get("authors", ()), "authors", "GitHub logins"
|
|
437
|
+
)
|
|
402
438
|
operators = _string_list(
|
|
403
439
|
raw.get("operators", ()), "operators", "GitHub logins"
|
|
404
440
|
)
|
|
@@ -529,6 +565,7 @@ class Config:
|
|
|
529
565
|
poll_interval=interval,
|
|
530
566
|
round_cap=cap,
|
|
531
567
|
repos=repos,
|
|
568
|
+
authors=authors,
|
|
532
569
|
operators=operators,
|
|
533
570
|
agent_profile=raw.get("agent_profile", "claude"),
|
|
534
571
|
reviewer_login=raw.get("reviewer_login"),
|
|
@@ -553,8 +590,8 @@ def _string_list(value: Any, key: str, what: str) -> tuple[str, ...]:
|
|
|
553
590
|
The guard is the point: JSON makes `"repos": "org/repo"` an easy typo, and
|
|
554
591
|
Python would iterate it into single CHARACTERS -- an allowlist of 30-odd
|
|
555
592
|
one-character names, which `watches()` then matches against nothing and the
|
|
556
|
-
daemon quietly reviews no PR at all.
|
|
557
|
-
|
|
593
|
+
daemon quietly reviews no PR at all. Every list key goes through here so
|
|
594
|
+
none of them can grow the footgun back.
|
|
558
595
|
"""
|
|
559
596
|
if isinstance(value, str):
|
|
560
597
|
raise ValueError(
|
|
@@ -1695,6 +1695,13 @@ class ReviewWatcher:
|
|
|
1695
1695
|
if pr.author == self.github.login:
|
|
1696
1696
|
# GitHub rejects a self review-request, so this should be
|
|
1697
1697
|
# unreachable -- but a shared bot identity would land here.
|
|
1698
|
+
#
|
|
1699
|
+
# FIRST, and deliberately above the authors allowlist (further
|
|
1700
|
+
# down): the allowlist is a scope filter an operator writes, and no
|
|
1701
|
+
# entry in it may buy back a review GitHub itself forbids. Listing
|
|
1702
|
+
# the reviewer login in `authors` therefore narrows the loop to a PR
|
|
1703
|
+
# it will then refuse -- which is the correct reading of "only serve
|
|
1704
|
+
# this author" for an author that cannot be served.
|
|
1698
1705
|
return Decision(
|
|
1699
1706
|
Action.SKIPPED,
|
|
1700
1707
|
f"PR author is the reviewer identity ({pr.author}); "
|
|
@@ -1755,6 +1762,36 @@ class ReviewWatcher:
|
|
|
1755
1762
|
self._clear_own_review_request(pr, my_reviews, converged)
|
|
1756
1763
|
return Decision(Action.CONVERGED, converged, completed)
|
|
1757
1764
|
|
|
1765
|
+
# THE AUTHORS SCOPE FILTER (issue #93), and its position is the whole
|
|
1766
|
+
# of its contract: it gates STARTING a round and nothing else.
|
|
1767
|
+
#
|
|
1768
|
+
# Client-side, because the discovery search is deliberately untouched --
|
|
1769
|
+
# the author is only in hand once the PR detail is fetched. Below
|
|
1770
|
+
# convergence rather than up beside the self-review skip, because every
|
|
1771
|
+
# branch above finishes a round that was already started: a verdict
|
|
1772
|
+
# envelope ahead of its native review still becomes a review of record
|
|
1773
|
+
# (_close_round_natively), and a converged PR still gets its dangling
|
|
1774
|
+
# review request withdrawn (_clear_own_review_request). An operator who
|
|
1775
|
+
# narrows `authors` mid-round must not strand the round that is already
|
|
1776
|
+
# running, and stopping one step short of its verdict of record is the
|
|
1777
|
+
# studio #298 failure this module refuses everywhere else (PR #94 round
|
|
1778
|
+
# 1). It costs a filtered PR one my_reviews fetch and the review-task
|
|
1779
|
+
# lookup per poll -- the negative cache bounds the corpus search -- and
|
|
1780
|
+
# that is the price of not stranding a round.
|
|
1781
|
+
#
|
|
1782
|
+
# Above THIS line, therefore, and everything the issue's checklist asks
|
|
1783
|
+
# for follows from where it sits: the effective cap and its ack
|
|
1784
|
+
# announcement, the cap-out escalation (the only branch below that would
|
|
1785
|
+
# COMMENT on the PR), `round_ = completed + 1`, the stale-round probe and
|
|
1786
|
+
# the spawn are all downstream. A filtered PR burns no round number,
|
|
1787
|
+
# records no attempt, holds no stale-round slot and never says a word on
|
|
1788
|
+
# the PR -- it gets silence, exactly as an unwatched repo does.
|
|
1789
|
+
if not self.config.serves_author(pr.author):
|
|
1790
|
+
return Decision(
|
|
1791
|
+
Action.SKIPPED,
|
|
1792
|
+
f"PR author {pr.author} is not in the authors allowlist",
|
|
1793
|
+
)
|
|
1794
|
+
|
|
1758
1795
|
# The effective cap is the configured one plus every re-entry an
|
|
1759
1796
|
# operator has explicitly acked on THIS PR (issue #42). The sum is a
|
|
1760
1797
|
# local read, so it costs nothing on the common path; the GitHub scan
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.21.0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.20.0
|
|
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
|
|
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
|