alissa-tools-github-devloop 0.4.1__tar.gz → 0.5.1__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_devloop-0.4.1/src/main/alissa_tools_github_devloop.egg-info → alissa_tools_github_devloop-0.5.1}/PKG-INFO +1 -1
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/setup.py +1 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/__main__.py +13 -2
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/config.py +19 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/ghclient.py +61 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/loop.py +615 -54
- alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/state.py +593 -0
- alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/version +1 -0
- alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/__init__.py +28 -0
- alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/__main__.py +131 -0
- alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/auth.py +195 -0
- alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/page.py +567 -0
- alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/server.py +324 -0
- alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/sources.py +477 -0
- alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/sysinfo.py +163 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1/src/main/alissa_tools_github_devloop.egg-info}/PKG-INFO +1 -1
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa_tools_github_devloop.egg-info/SOURCES.txt +7 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa_tools_github_devloop.egg-info/entry_points.txt +1 -0
- alissa_tools_github_devloop-0.4.1/src/main/alissa/tools/github/devloop/state.py +0 -290
- alissa_tools_github_devloop-0.4.1/src/main/alissa/tools/github/devloop/version +0 -1
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/MANIFEST.in +0 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/README.md +0 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/requirements.txt +0 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/setup.cfg +0 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/__init__.py +0 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/alissa.py +0 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/proc.py +0 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/version.py +0 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa_tools_github_devloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa_tools_github_devloop.egg-info/top_level.txt +0 -0
|
@@ -92,6 +92,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
92
92
|
over.add_argument("--state-path", type=Path, metavar="PATH")
|
|
93
93
|
over.add_argument("--attempt-cap", type=int, metavar="N", help="dev attempts per issue")
|
|
94
94
|
over.add_argument("--stale-minutes", type=int, metavar="MINUTES")
|
|
95
|
+
over.add_argument(
|
|
96
|
+
"--max-sessions",
|
|
97
|
+
type=int,
|
|
98
|
+
metavar="N",
|
|
99
|
+
help="max concurrent worker sessions (develop-*/fix-*/maintain-*) "
|
|
100
|
+
"before actionable items defer to the next poll; 0 = unlimited "
|
|
101
|
+
"(the default)",
|
|
102
|
+
)
|
|
95
103
|
over.add_argument(
|
|
96
104
|
"--on-missing-origin-task",
|
|
97
105
|
choices=[ON_MISSING_WARN, ON_MISSING_SPAWN, ON_MISSING_SKIP],
|
|
@@ -170,6 +178,7 @@ def overrides_from(args: argparse.Namespace) -> dict:
|
|
|
170
178
|
"state_path": args.state_path,
|
|
171
179
|
"attempt_cap": args.attempt_cap,
|
|
172
180
|
"stale_minutes": args.stale_minutes,
|
|
181
|
+
"max_sessions": args.max_sessions,
|
|
173
182
|
"on_missing_origin_task": args.on_missing_origin_task,
|
|
174
183
|
"on_missing_hub": args.on_missing_hub,
|
|
175
184
|
"fix_rounds_enabled": args.fix_rounds_enabled,
|
|
@@ -206,9 +215,11 @@ def log_effective_config(config: Config, login: str) -> None:
|
|
|
206
215
|
log.info("reviewers: %s", ", ".join(config.reviewers) or "none")
|
|
207
216
|
log.info(
|
|
208
217
|
"poll every %ss; dry_run=%s; attempt_cap=%s; stale after %s min; "
|
|
209
|
-
"fix_rounds=%s; maintain=%s (label %r)",
|
|
218
|
+
"max_sessions=%s; fix_rounds=%s; maintain=%s (label %r)",
|
|
210
219
|
config.poll_interval, config.dry_run, config.attempt_cap,
|
|
211
|
-
config.stale_minutes,
|
|
220
|
+
config.stale_minutes,
|
|
221
|
+
config.max_sessions or "unlimited",
|
|
222
|
+
config.fix_rounds_enabled,
|
|
212
223
|
config.maintain_enabled, config.maintain_label,
|
|
213
224
|
)
|
|
214
225
|
log.debug("hub_template: %s", config.hub_template)
|
|
@@ -56,6 +56,7 @@ CONFIG_KEYS = (
|
|
|
56
56
|
"state_path",
|
|
57
57
|
"attempt_cap",
|
|
58
58
|
"stale_minutes",
|
|
59
|
+
"max_sessions",
|
|
59
60
|
"on_missing_origin_task",
|
|
60
61
|
"on_missing_hub",
|
|
61
62
|
"fix_rounds_enabled",
|
|
@@ -133,6 +134,15 @@ class Config:
|
|
|
133
134
|
attempt_cap: int = 10
|
|
134
135
|
stale_minutes: int = 20
|
|
135
136
|
|
|
137
|
+
# Backpressure: the maximum number of concurrent worker sessions
|
|
138
|
+
# (`develop-*`/`fix-*`/`maintain-*`) the daemon will keep in flight at
|
|
139
|
+
# once. 0 means UNLIMITED -- today's behavior, preserved as the default so
|
|
140
|
+
# nothing changes until an operator opts in. At the cap, actionable items
|
|
141
|
+
# defer cleanly (no self-assign, no ledger row) and retry next poll; see
|
|
142
|
+
# DevWatcher.poll_once. Counted from the reaper's own `alissa tmux ls`
|
|
143
|
+
# listing, so enabling it costs no extra GitHub or CLI calls.
|
|
144
|
+
max_sessions: int = 0
|
|
145
|
+
|
|
136
146
|
on_missing_origin_task: str = ON_MISSING_WARN
|
|
137
147
|
on_missing_hub: str = HUB_SKIP
|
|
138
148
|
|
|
@@ -269,6 +279,14 @@ class Config:
|
|
|
269
279
|
if stale < 1:
|
|
270
280
|
raise ValueError(f"stale_minutes must be >= 1, got {stale}")
|
|
271
281
|
|
|
282
|
+
# 0 is the sentinel for "unlimited" (the default), so the floor is 0,
|
|
283
|
+
# not 1 -- a negative cap is the only invalid value.
|
|
284
|
+
max_sessions = int(raw.get("max_sessions", cls.max_sessions))
|
|
285
|
+
if max_sessions < 0:
|
|
286
|
+
raise ValueError(
|
|
287
|
+
f"max_sessions must be >= 0 (0 = unlimited), got {max_sessions}"
|
|
288
|
+
)
|
|
289
|
+
|
|
272
290
|
interval = int(raw.get("poll_interval", cls.poll_interval))
|
|
273
291
|
if interval < MIN_POLL_INTERVAL:
|
|
274
292
|
raise ValueError(
|
|
@@ -288,6 +306,7 @@ class Config:
|
|
|
288
306
|
state_path=Path(state_path).expanduser() if state_path else None,
|
|
289
307
|
attempt_cap=cap,
|
|
290
308
|
stale_minutes=stale,
|
|
309
|
+
max_sessions=max_sessions,
|
|
291
310
|
on_missing_origin_task=mode,
|
|
292
311
|
on_missing_hub=hub_mode,
|
|
293
312
|
fix_rounds_enabled=bool(
|
|
@@ -147,6 +147,20 @@ class Review:
|
|
|
147
147
|
return bool(self.body.strip())
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class IssueComment:
|
|
152
|
+
"""An issue comment on an issue OR a PR (a PR is an issue on the issues
|
|
153
|
+
endpoints). Distinct from Review: issue comments live on the
|
|
154
|
+
`/issues/{n}/comments` endpoints and never create review records, so
|
|
155
|
+
posting or PATCHing one can never disturb the fix edge's round counting
|
|
156
|
+
or its author-activity liveness probe -- the non-interference the
|
|
157
|
+
mechanical activity comment leans on."""
|
|
158
|
+
|
|
159
|
+
id: int
|
|
160
|
+
author: str
|
|
161
|
+
body: str
|
|
162
|
+
|
|
163
|
+
|
|
150
164
|
class RateLimited(RuntimeError):
|
|
151
165
|
pass
|
|
152
166
|
|
|
@@ -739,3 +753,50 @@ class GitHub:
|
|
|
739
753
|
"-f",
|
|
740
754
|
f"body={body}",
|
|
741
755
|
)
|
|
756
|
+
|
|
757
|
+
def issue_comments(
|
|
758
|
+
self, owner: str, repo: str, number: int
|
|
759
|
+
) -> list[IssueComment]:
|
|
760
|
+
"""Issue comments on an issue or PR (find-or-create reads these to
|
|
761
|
+
locate THE activity comment). per_page=100 is a documented single-page
|
|
762
|
+
window like the searches -- the activity comment is authored by the
|
|
763
|
+
daemon itself, so it lands on the first page unless a hundred other
|
|
764
|
+
comments were filed after it, and even then find-or-create degrades to
|
|
765
|
+
creating a second activity comment, never to touching a foreign one.
|
|
766
|
+
Malformed entries are tolerated (never fatal): one odd comment must not
|
|
767
|
+
wedge a spawn's telemetry."""
|
|
768
|
+
data = (
|
|
769
|
+
self._api(
|
|
770
|
+
"-X",
|
|
771
|
+
"GET",
|
|
772
|
+
f"repos/{owner}/{repo}/issues/{number}/comments",
|
|
773
|
+
"-f",
|
|
774
|
+
"per_page=100",
|
|
775
|
+
)
|
|
776
|
+
or []
|
|
777
|
+
)
|
|
778
|
+
if not isinstance(data, list):
|
|
779
|
+
return []
|
|
780
|
+
return [
|
|
781
|
+
IssueComment(
|
|
782
|
+
id=int(c.get("id") or 0),
|
|
783
|
+
author=(c.get("user") or {}).get("login", ""),
|
|
784
|
+
body=c.get("body") or "",
|
|
785
|
+
)
|
|
786
|
+
for c in data
|
|
787
|
+
if isinstance(c, dict)
|
|
788
|
+
]
|
|
789
|
+
|
|
790
|
+
def update_comment(
|
|
791
|
+
self, owner: str, repo: str, comment_id: int, body: str
|
|
792
|
+
) -> None:
|
|
793
|
+
"""PATCH an existing issue comment's body -- the append half of the
|
|
794
|
+
activity comment's find-or-create. Keyed on the comment id, so it only
|
|
795
|
+
ever rewrites a comment the caller already located and authored."""
|
|
796
|
+
self._api(
|
|
797
|
+
"-X",
|
|
798
|
+
"PATCH",
|
|
799
|
+
f"repos/{owner}/{repo}/issues/comments/{comment_id}",
|
|
800
|
+
"-f",
|
|
801
|
+
f"body={body}",
|
|
802
|
+
)
|