alissa-tools-github-devloop 0.8.59__py3-none-any.whl → 0.8.61__py3-none-any.whl
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/loop.py +63 -14
- alissa/tools/github/devloop/state.py +48 -0
- alissa/tools/github/devloop/version +1 -1
- {alissa_tools_github_devloop-0.8.59.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/METADATA +1 -1
- {alissa_tools_github_devloop-0.8.59.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/RECORD +10 -10
- {alissa_tools_github_devloop-0.8.59.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/WHEEL +0 -0
- {alissa_tools_github_devloop-0.8.59.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/entry_points.txt +0 -0
- {alissa_tools_github_devloop-0.8.59.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/licenses/LICENSE +0 -0
- {alissa_tools_github_devloop-0.8.59.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/licenses/NOTICE +0 -0
- {alissa_tools_github_devloop-0.8.59.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/top_level.txt +0 -0
|
@@ -547,11 +547,16 @@ _CRITERIA_RULE = (
|
|
|
547
547
|
)
|
|
548
548
|
|
|
549
549
|
# The adopt half: a resumed or fix-round session inherits a task some other
|
|
550
|
-
# session typed, possibly before this rule existed.
|
|
550
|
+
# session typed, possibly before this rule existed. No worker surface can
|
|
551
|
+
# edit, retype or add a criterion on an existing task (the REST API only
|
|
552
|
+
# creates tasks with criteria and satisfies them; the CLI has neither), so
|
|
553
|
+
# the clause asks for the one thing a worker can do: put each breach in front
|
|
554
|
+
# of the validator, who reads the delivery note.
|
|
551
555
|
_CRITERIA_ADOPT = (
|
|
552
|
-
" On a task you ADOPT,
|
|
553
|
-
"
|
|
554
|
-
"
|
|
556
|
+
" On a task you ADOPT, leave its criteria as they are — no tool you have "
|
|
557
|
+
"can edit, retype or add one — and list every criterion that breaks this "
|
|
558
|
+
"rule, with the type it should carry (a fact typed `human` should be "
|
|
559
|
+
"`evidence`), under the delivery note's Judgment calls."
|
|
555
560
|
)
|
|
556
561
|
|
|
557
562
|
# The origin-task instruction is split in two because the downstream rule is
|
|
@@ -2269,7 +2274,9 @@ ESCALATION_LIVE_STALLED = "live-stalled"
|
|
|
2269
2274
|
# assignment. Once-ever like ESCALATION_ASSIGNMENT -- the remedy (unassign,
|
|
2270
2275
|
# or leave it to whoever is working it by hand) does not change per poll.
|
|
2271
2276
|
# ASSIGNED_OUTSIDE_SEEN is the episode's durable first-seen clock, kept in the
|
|
2272
|
-
# activity_log ledger so a daemon restart does not restart the wait
|
|
2277
|
+
# activity_log ledger so a daemon restart does not restart the wait, and
|
|
2278
|
+
# dropped by the first issue pass that no longer reaches the skip (issue
|
|
2279
|
+
# #204), so the next episode on the same issue starts a clock of its own.
|
|
2273
2280
|
ESCALATION_ASSIGNED_OUTSIDE = "assigned-outside"
|
|
2274
2281
|
ASSIGNED_OUTSIDE_SEEN = "assigned-outside-seen"
|
|
2275
2282
|
|
|
@@ -4732,6 +4739,10 @@ class DevWatcher:
|
|
|
4732
4739
|
self._shell_streaks: "dict[str, tuple[int, int]]" = {}
|
|
4733
4740
|
# The process reaper's own clock (janitor cadence, issue #186).
|
|
4734
4741
|
self._orphans_at: "float | None" = None
|
|
4742
|
+
# The issues whose assigned-outside episode this issue pass saw
|
|
4743
|
+
# still running (issue #204): `_assigned_outside` adds to it, and the
|
|
4744
|
+
# end of `_poll_issues` ends every other episode's first-seen clock.
|
|
4745
|
+
self._outside_seen: "set[tuple[str, int]]" = set()
|
|
4735
4746
|
|
|
4736
4747
|
# -- per-issue decision ------------------------------------------------
|
|
4737
4748
|
|
|
@@ -4851,6 +4862,13 @@ class DevWatcher:
|
|
|
4851
4862
|
if claim is not None:
|
|
4852
4863
|
return self._claim_deferral(claim, attempts)
|
|
4853
4864
|
|
|
4865
|
+
# What adoption put on the ledger, carried by hand for the reads below
|
|
4866
|
+
# that would otherwise go back to the ledger for it: a dry run writes
|
|
4867
|
+
# no row, so without these the dry pass judged the attempt cap
|
|
4868
|
+
# against 0 billed attempts and liveness against no session name at
|
|
4869
|
+
# all, and could SPAWN where the real pass ESCALATES (issue #204).
|
|
4870
|
+
adopted = 0
|
|
4871
|
+
adopted_session: "str | None" = None
|
|
4854
4872
|
if login in issue.assignees and age is None:
|
|
4855
4873
|
# Self-assigned but the ledger never spawned. Issue #200: when the
|
|
4856
4874
|
# issue's own activity comment carries an EXPIRED spawn line by
|
|
@@ -4862,6 +4880,7 @@ class DevWatcher:
|
|
|
4862
4880
|
orphans = self._orphaned_spawns(issue, session_name(issue, 1))
|
|
4863
4881
|
if orphans:
|
|
4864
4882
|
attempts, age = self._adopt_orphaned_spawns(issue, orphans)
|
|
4883
|
+
adopted, adopted_session = attempts, orphans[-1].session
|
|
4865
4884
|
else:
|
|
4866
4885
|
# No spawn line at all: assigned outside this daemon (by
|
|
4867
4886
|
# hand, or under another workspace's ledger). Not ours to
|
|
@@ -4926,8 +4945,9 @@ class DevWatcher:
|
|
|
4926
4945
|
)
|
|
4927
4946
|
linked = [url for url, state in links if state == PR_OPEN]
|
|
4928
4947
|
if linked:
|
|
4929
|
-
row_session =
|
|
4930
|
-
issue.full_name, number
|
|
4948
|
+
row_session = (
|
|
4949
|
+
self.state.newest_spawn_session(issue.full_name, number)
|
|
4950
|
+
or adopted_session
|
|
4931
4951
|
)
|
|
4932
4952
|
liveness = self.session_liveness(row_session, roster)
|
|
4933
4953
|
if liveness is Liveness.ALIVE and row_session:
|
|
@@ -5062,9 +5082,11 @@ class DevWatcher:
|
|
|
5062
5082
|
# rows resolve INDETERMINATE otherwise, and since they respawn
|
|
5063
5083
|
# unconditionally TODAY, deferring them forever would be a
|
|
5064
5084
|
# regression dressed as caution.
|
|
5065
|
-
live_session =
|
|
5066
|
-
issue.full_name, number
|
|
5067
|
-
|
|
5085
|
+
live_session = (
|
|
5086
|
+
self.state.newest_spawn_session(issue.full_name, number)
|
|
5087
|
+
or adopted_session
|
|
5088
|
+
or session_name(issue, attempts)
|
|
5089
|
+
)
|
|
5068
5090
|
liveness = self.session_liveness(live_session, roster)
|
|
5069
5091
|
# Falling through means DEAD, and DEAD here is CONFIRMED (a
|
|
5070
5092
|
# successful listing without this name) -- so the retry below
|
|
@@ -5176,7 +5198,13 @@ class DevWatcher:
|
|
|
5176
5198
|
# Billable attempts only (issue #142): an attempt refunded by a
|
|
5177
5199
|
# usage limit never counts toward the cap; the numbering below still
|
|
5178
5200
|
# counts it, so session names stay monotonic.
|
|
5179
|
-
|
|
5201
|
+
# An adopted orphan is billable by construction (its row never carries
|
|
5202
|
+
# a limit stamp), and on a dry run it has no row to count: `max` lets
|
|
5203
|
+
# the dry pass reach the cap decision the real one does (issue #204).
|
|
5204
|
+
billed = max(
|
|
5205
|
+
self.state.attempt_count(issue.full_name, number, billable=True),
|
|
5206
|
+
adopted,
|
|
5207
|
+
)
|
|
5180
5208
|
if billed >= self.config.attempt_cap:
|
|
5181
5209
|
if self.state.escalated(issue.full_name, number, ESCALATION_CAP):
|
|
5182
5210
|
return Decision(Action.CAPPED, "already escalated", attempts)
|
|
@@ -10258,8 +10286,10 @@ class DevWatcher:
|
|
|
10258
10286
|
Rows are numbered 1..k in line order (the ledger has none for this
|
|
10259
10287
|
issue), keeping the session name the line carries; numbering by the
|
|
10260
10288
|
name's own suffix could REPLACE a row when a line is missing. Not
|
|
10261
|
-
written on a dry run (no ledger write is)
|
|
10262
|
-
|
|
10289
|
+
written on a dry run (no ledger write is); `evaluate` carries the
|
|
10290
|
+
returned count and the newest line's session name into the reads
|
|
10291
|
+
that would otherwise look for these rows (the attempt cap, the
|
|
10292
|
+
lane's liveness), so the dry pass reaches the same decision."""
|
|
10263
10293
|
for attempt, orphan in enumerate(orphans, start=1):
|
|
10264
10294
|
log.warning(
|
|
10265
10295
|
"%s: adopted orphaned attempt %d from activity log (`%s`, "
|
|
@@ -10289,8 +10319,15 @@ class DevWatcher:
|
|
|
10289
10319
|
Not silent forever (issue #200): once the skip has persisted for
|
|
10290
10320
|
STALLED_DEFER_MULTIPLE stale windows (clock: the episode's durable
|
|
10291
10321
|
first-seen row) it posts ONE operator comment, recorded only after
|
|
10292
|
-
it lands so a failed post retries next poll.
|
|
10322
|
+
it lands so a failed post retries next poll.
|
|
10323
|
+
|
|
10324
|
+
The clock is per EPISODE (issue #204): the issue pass ends it the
|
|
10325
|
+
first time it does not reach this skip for the issue -- unassigned,
|
|
10326
|
+
adopted, spawned, closed or unlabeled -- so a hand assignment weeks
|
|
10327
|
+
later waits its own full window instead of paging on its first
|
|
10328
|
+
poll. The page itself stays once-ever."""
|
|
10293
10329
|
full_name, number = issue.full_name, issue.number
|
|
10330
|
+
self._outside_seen.add((full_name, number))
|
|
10294
10331
|
reason = (
|
|
10295
10332
|
f"assigned to {login} outside this daemon (no spawn on the "
|
|
10296
10333
|
f"ledger) — unassign the issue to let the loop take it"
|
|
@@ -13798,6 +13835,7 @@ class DevWatcher:
|
|
|
13798
13835
|
)
|
|
13799
13836
|
|
|
13800
13837
|
results = []
|
|
13838
|
+
self._outside_seen = set()
|
|
13801
13839
|
for owner, repo, number, assignees in _oldest_first(found):
|
|
13802
13840
|
slug = f"{owner}/{repo}#{number}"
|
|
13803
13841
|
# Defense in depth: the search is already repo-scoped, but nothing
|
|
@@ -13832,10 +13870,21 @@ class DevWatcher:
|
|
|
13832
13870
|
log.error("%s: %s", slug, exc)
|
|
13833
13871
|
# Owed: an evaluation that failed is unknown, never settled.
|
|
13834
13872
|
decision = Decision(Action.SKIPPED, str(exc), owed=True)
|
|
13873
|
+
# Unknown is not ended either: a transient failure must
|
|
13874
|
+
# not restart an assigned-outside wait.
|
|
13875
|
+
self._outside_seen.add((f"{owner}/{repo}", number))
|
|
13835
13876
|
|
|
13836
13877
|
level = logging.INFO if decision.action != Action.SKIPPED else logging.DEBUG
|
|
13837
13878
|
log.log(level, "%s → %s (%s)", slug, decision.action.value, decision.reason)
|
|
13838
13879
|
results.append((slug, decision))
|
|
13880
|
+
# Issue #204: an assigned-outside episode lasts exactly as long as
|
|
13881
|
+
# the skip keeps answering. Every first-seen clock this pass did not
|
|
13882
|
+
# reach the skip for has ended; drop it, or a later hand assignment
|
|
13883
|
+
# of the same issue inherits a wait that is weeks old and pages on
|
|
13884
|
+
# its first poll. Not reached when the pass raised (RateLimited), and
|
|
13885
|
+
# never on a dry run, which writes nothing.
|
|
13886
|
+
if not self.config.dry_run:
|
|
13887
|
+
self.state.prune_activity(ASSIGNED_OUTSIDE_SEEN, self._outside_seen)
|
|
13839
13888
|
return results
|
|
13840
13889
|
|
|
13841
13890
|
def _poll_prs(
|
|
@@ -1289,6 +1289,54 @@ class State:
|
|
|
1289
1289
|
f"record_activity({repo_slug}#{number}, {kind})", write),
|
|
1290
1290
|
)
|
|
1291
1291
|
|
|
1292
|
+
def prune_activity(
|
|
1293
|
+
self, kind: str, keep: "set[tuple[str, int]]"
|
|
1294
|
+
) -> int:
|
|
1295
|
+
"""Drop every activity_log row of exactly this KIND whose
|
|
1296
|
+
(repo_slug, number) is not in `keep`. Returns how many rows were
|
|
1297
|
+
dropped (0 when the store refused the write).
|
|
1298
|
+
|
|
1299
|
+
For a kind whose row is an EPISODE's first-seen clock rather than a
|
|
1300
|
+
posted line's dedupe (issue #204: `assigned-outside-seen`): the
|
|
1301
|
+
caller passes the subjects whose episode it saw still running this
|
|
1302
|
+
pass, and every other clock of the kind ends, so a later episode on
|
|
1303
|
+
the same item starts its own wait instead of inheriting this one's.
|
|
1304
|
+
|
|
1305
|
+
BEST-EFFORT with the SELECT inside the closure, and a pass with
|
|
1306
|
+
nothing to drop reports `False`, for the reasons `prune_escalations`
|
|
1307
|
+
gives. A held (unwritten) row of the kind is released alongside."""
|
|
1308
|
+
dropped: "list[tuple[str, int]]" = []
|
|
1309
|
+
|
|
1310
|
+
def write() -> "bool | None":
|
|
1311
|
+
del dropped[:]
|
|
1312
|
+
rows = self._db.execute(
|
|
1313
|
+
"SELECT repo_slug, number FROM activity_log WHERE kind=?",
|
|
1314
|
+
(kind,),
|
|
1315
|
+
).fetchall()
|
|
1316
|
+
dropped.extend(
|
|
1317
|
+
(row["repo_slug"], int(row["number"])) for row in rows
|
|
1318
|
+
if (row["repo_slug"], int(row["number"])) not in keep
|
|
1319
|
+
)
|
|
1320
|
+
if not dropped:
|
|
1321
|
+
return False
|
|
1322
|
+
for repo_slug, number in dropped:
|
|
1323
|
+
self._db.execute(
|
|
1324
|
+
"DELETE FROM activity_log WHERE repo_slug=? AND number=? "
|
|
1325
|
+
"AND kind=?",
|
|
1326
|
+
(repo_slug, number, kind),
|
|
1327
|
+
)
|
|
1328
|
+
self._db.commit()
|
|
1329
|
+
return True
|
|
1330
|
+
|
|
1331
|
+
wrote = self._best_effort(f"prune_activity({kind})", write)
|
|
1332
|
+
for key in [
|
|
1333
|
+
key for key in self._pending_gates
|
|
1334
|
+
if key[0] == "activity_log" and key[3] == kind
|
|
1335
|
+
and (key[1], key[2]) not in keep
|
|
1336
|
+
]:
|
|
1337
|
+
self._pending_gates.pop(key, None)
|
|
1338
|
+
return len(dropped) if wrote else 0
|
|
1339
|
+
|
|
1292
1340
|
# -- janitor holdouts (issue #99) ---------------------------------------
|
|
1293
1341
|
#
|
|
1294
1342
|
# The workspace janitor's once-per-episode memory. Every method here is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.8.
|
|
1
|
+
0.8.61
|
{alissa_tools_github_devloop-0.8.59.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/RECORD
RENAMED
|
@@ -10,13 +10,13 @@ alissa/tools/github/devloop/hubs.py,sha256=yjnsnSHLG7GNNP8X2hILeAMV6P5n4JGc_IaAx
|
|
|
10
10
|
alissa/tools/github/devloop/hungtests.py,sha256=TozIDydUe_Kn1m5EtGeGtYt_BrLqGznrNjikLYhxH-Q,24621
|
|
11
11
|
alissa/tools/github/devloop/integration_check.py,sha256=JkFwz3JqOQBIvzszmiql-IMVGSH3lDers6mJUHh9zMM,17928
|
|
12
12
|
alissa/tools/github/devloop/janitor.py,sha256=R8REoHBeY2cfW7fYJVhKr-F5z9Mjlc-vmv-z4KtvBZc,27360
|
|
13
|
-
alissa/tools/github/devloop/loop.py,sha256=
|
|
13
|
+
alissa/tools/github/devloop/loop.py,sha256=0GhXYt-Ou6b4hCaVlW3MwBZojRpvtxgA7uTkXKMnknU,707186
|
|
14
14
|
alissa/tools/github/devloop/loop_events.py,sha256=B8wq4_MMXbf1-od3pk9OHVo6F6W2lzzUZmU6GsKcCkM,38113
|
|
15
15
|
alissa/tools/github/devloop/parking.py,sha256=7h3Gg2XQ0eqlvn0M6K6NeEg9xbqnlUPrZNs9gRofzxc,12410
|
|
16
16
|
alissa/tools/github/devloop/proc.py,sha256=nzfsxJSHFWRQcrXmfv588KazFysv8dVQrhMPH1q5eRk,3134
|
|
17
17
|
alissa/tools/github/devloop/prompts.py,sha256=VzN6ec5eF2wwg7QSCRg52WLcxo-tGbGRgQbc4HSahUc,55169
|
|
18
|
-
alissa/tools/github/devloop/state.py,sha256=
|
|
19
|
-
alissa/tools/github/devloop/version,sha256=
|
|
18
|
+
alissa/tools/github/devloop/state.py,sha256=mZcKMtWKdwjRk4up74WVI_QZaOJF4JLjGWFMhx1k2FA,91591
|
|
19
|
+
alissa/tools/github/devloop/version,sha256=G3rr6PpxmjA80IOCaXyiYP6ce9bJP9MLMtfPh8hyo00,7
|
|
20
20
|
alissa/tools/github/devloop/version.py,sha256=znuuOaitXGrBpbskNy78_rlVpFdtmKmk_3k1ACxZrl0,1418
|
|
21
21
|
alissa/tools/github/devloop/webui/__init__.py,sha256=vdXYoyGYzD7BrJ5lV6XdH8Bpw_pej8-RK0hjfxop7-8,1513
|
|
22
22
|
alissa/tools/github/devloop/webui/__main__.py,sha256=wORyW5AkgsniK0TZcrOrvm_P7Q4Mr-fH-4S6qj9XcgA,6137
|
|
@@ -25,10 +25,10 @@ alissa/tools/github/devloop/webui/page.py,sha256=4OD3e01ByvVM4Wo04AZyFuyOYTFEMT2
|
|
|
25
25
|
alissa/tools/github/devloop/webui/server.py,sha256=3aGYGesjUSrPrgcpfy-oYDefBNKnAj3lGfRzwsRkv_I,27031
|
|
26
26
|
alissa/tools/github/devloop/webui/sources.py,sha256=Uw_2B5QGSmW6f66WfVbQb6yLLYmSv6uuF7HNJ5OSr_I,39314
|
|
27
27
|
alissa/tools/github/devloop/webui/sysinfo.py,sha256=ptlogH9alFv0vZ-pQfyUaA3w-K7LQS3tQHGv0d8Tdlk,9649
|
|
28
|
-
alissa_tools_github_devloop-0.8.
|
|
29
|
-
alissa_tools_github_devloop-0.8.
|
|
30
|
-
alissa_tools_github_devloop-0.8.
|
|
31
|
-
alissa_tools_github_devloop-0.8.
|
|
32
|
-
alissa_tools_github_devloop-0.8.
|
|
33
|
-
alissa_tools_github_devloop-0.8.
|
|
34
|
-
alissa_tools_github_devloop-0.8.
|
|
28
|
+
alissa_tools_github_devloop-0.8.61.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
29
|
+
alissa_tools_github_devloop-0.8.61.dist-info/licenses/NOTICE,sha256=FKstNs3uw_tQod4_1XcFLD1FUTJQXf4FC0oXJAOA_HE,260
|
|
30
|
+
alissa_tools_github_devloop-0.8.61.dist-info/METADATA,sha256=0xoJnkXt8Xv4qVsf_C7lFVHdGVm05j410cO-eRY29-Q,15973
|
|
31
|
+
alissa_tools_github_devloop-0.8.61.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
32
|
+
alissa_tools_github_devloop-0.8.61.dist-info/entry_points.txt,sha256=8aC7Ajzx4bu9ZRR37a40IDfuStacvZP5KsAxY1dbFKU,145
|
|
33
|
+
alissa_tools_github_devloop-0.8.61.dist-info/top_level.txt,sha256=DodjDg-l-TWQnlxG-Vuc3G5sB4O7cWGQwe6XrVx3u-A,7
|
|
34
|
+
alissa_tools_github_devloop-0.8.61.dist-info/RECORD,,
|
{alissa_tools_github_devloop-0.8.59.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|