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.
Files changed (30) hide show
  1. {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
  2. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/setup.py +1 -0
  3. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/__main__.py +13 -2
  4. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/config.py +19 -0
  5. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/ghclient.py +61 -0
  6. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/loop.py +615 -54
  7. alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/state.py +593 -0
  8. alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/version +1 -0
  9. alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/__init__.py +28 -0
  10. alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/__main__.py +131 -0
  11. alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/auth.py +195 -0
  12. alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/page.py +567 -0
  13. alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/server.py +324 -0
  14. alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/sources.py +477 -0
  15. alissa_tools_github_devloop-0.5.1/src/main/alissa/tools/github/devloop/webui/sysinfo.py +163 -0
  16. {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
  17. {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
  18. {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
  19. alissa_tools_github_devloop-0.4.1/src/main/alissa/tools/github/devloop/state.py +0 -290
  20. alissa_tools_github_devloop-0.4.1/src/main/alissa/tools/github/devloop/version +0 -1
  21. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/MANIFEST.in +0 -0
  22. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/README.md +0 -0
  23. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/requirements.txt +0 -0
  24. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/setup.cfg +0 -0
  25. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/__init__.py +0 -0
  26. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/alissa.py +0 -0
  27. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/proc.py +0 -0
  28. {alissa_tools_github_devloop-0.4.1 → alissa_tools_github_devloop-0.5.1}/src/main/alissa/tools/github/devloop/version.py +0 -0
  29. {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
  30. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-devloop
3
- Version: 0.4.1
3
+ Version: 0.5.1
4
4
  Summary: ALISSA-TOOLS-GITHUB-DEVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera
@@ -49,6 +49,7 @@ setup(
49
49
  entry_points={
50
50
  "console_scripts": [
51
51
  "alissa-devloop=alissa.tools.github.devloop.__main__:main",
52
+ "alissa-devloop-ui=alissa.tools.github.devloop.webui.__main__:main",
52
53
  ]
53
54
  },
54
55
  install_requires=requirements,
@@ -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, config.fix_rounds_enabled,
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
+ )