alissa-tools-github-revloop 0.23.0__tar.gz → 0.25.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.23.0/src/main/alissa_tools_github_revloop.egg-info → alissa_tools_github_revloop-0.25.0}/PKG-INFO +1 -1
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/__main__.py +12 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/alissa.py +138 -10
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/config.py +75 -1
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/loop.py +11 -1
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/prreview.py +6 -1
- alissa_tools_github_revloop-0.25.0/src/main/alissa/tools/github/revloop/version +1 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0/src/main/alissa_tools_github_revloop.egg-info}/PKG-INFO +1 -1
- alissa_tools_github_revloop-0.23.0/src/main/alissa/tools/github/revloop/version +0 -1
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/LICENSE +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/MANIFEST.in +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/NOTICE +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/README.md +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/requirements.txt +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/setup.cfg +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/setup.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/__init__.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/ghclient.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/proc.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/state.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/version.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/webui/__init__.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/webui/__main__.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/webui/auth.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/webui/page.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/webui/server.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/webui/sources.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa/tools/github/revloop/webui/sysinfo.py +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa_tools_github_revloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa_tools_github_revloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa_tools_github_revloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_revloop-0.23.0 → alissa_tools_github_revloop-0.25.0}/src/main/alissa_tools_github_revloop.egg-info/top_level.txt +0 -0
|
@@ -14,6 +14,7 @@ from .config import (
|
|
|
14
14
|
ON_MISSING_CREATE,
|
|
15
15
|
ON_MISSING_SKIP,
|
|
16
16
|
ON_MISSING_SPAWN,
|
|
17
|
+
TASK_LIST_BOW_ENV,
|
|
17
18
|
Config,
|
|
18
19
|
load_config_file,
|
|
19
20
|
resolve_config_path,
|
|
@@ -181,6 +182,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
181
182
|
help="list the sponsor-union corpus even if the config narrows it",
|
|
182
183
|
)
|
|
183
184
|
|
|
185
|
+
over.add_argument(
|
|
186
|
+
"--task-list-bow",
|
|
187
|
+
dest="task_list_bow_id",
|
|
188
|
+
metavar="BOW_ID",
|
|
189
|
+
help="scope `alissa task list` to one body of work (--bow): the Convex "
|
|
190
|
+
"_id of the BOW review tasks are created into, NOT a repo's `autodev:` "
|
|
191
|
+
"feed BOW and not a mirrorInstanceId. A review task outside it is "
|
|
192
|
+
f"invisible to this daemon. Overridden by ${TASK_LIST_BOW_ENV}",
|
|
193
|
+
)
|
|
194
|
+
|
|
184
195
|
dry = over.add_mutually_exclusive_group()
|
|
185
196
|
dry.add_argument(
|
|
186
197
|
"--dry-run",
|
|
@@ -221,6 +232,7 @@ def overrides_from(args: argparse.Namespace) -> dict:
|
|
|
221
232
|
"checks_spawn_wait_seconds": args.checks_spawn_wait_seconds,
|
|
222
233
|
"review_task_miss_ttl_polls": args.review_task_miss_ttl_polls,
|
|
223
234
|
"task_list_self_scope": args.task_list_self_scope,
|
|
235
|
+
"task_list_bow_id": args.task_list_bow_id,
|
|
224
236
|
"dry_run": args.dry_run,
|
|
225
237
|
}
|
|
226
238
|
|
|
@@ -122,6 +122,48 @@ TASK_LIST_SELF_FLAG = "--self"
|
|
|
122
122
|
TASK_LIST_VIEW_FLAG = "--view"
|
|
123
123
|
TASK_LIST_DIGEST_VIEW = "digest"
|
|
124
124
|
|
|
125
|
+
# `--bow` narrows the CANDIDATE SET rather than filtering the answer, and that
|
|
126
|
+
# is the whole reason it is worth more than the flags above (issue #100).
|
|
127
|
+
#
|
|
128
|
+
# `--status` / `--self` / `--view` all start from the operator's involvement
|
|
129
|
+
# index -- every non-terminal task they are party to -- and cut it down. A BOW
|
|
130
|
+
# scope starts from that body of work's junction rows instead, which carry a
|
|
131
|
+
# denormalized status, so a row that does not match never costs a task-document
|
|
132
|
+
# read at all. The cache story is the same shape: a BOW-scoped query is
|
|
133
|
+
# invalidated only by writes INSIDE that BOW, so the operator's unrelated task
|
|
134
|
+
# churn stops re-billing this daemon's 30-second poll.
|
|
135
|
+
#
|
|
136
|
+
# It really is a SWAP and not an intersection, and that was measured against the
|
|
137
|
+
# installed CLI on 2026-08-28 rather than assumed (PR #101 round 1): across five
|
|
138
|
+
# real BOWs, not one answer was a subset of the plain 1335-row one, and the rows
|
|
139
|
+
# outside it were all non-terminal. Two consequences the composition story has to
|
|
140
|
+
# respect, from the same probe:
|
|
141
|
+
#
|
|
142
|
+
# * `--self` is IGNORED under `--bow`. `--bow X --self` returns exactly what
|
|
143
|
+
# `--bow X` returns, and that set is not a subset of the `--self` answer. So
|
|
144
|
+
# a BOW-scoped deployment gets the body of work's whole membership, actor-
|
|
145
|
+
# owned or not -- WIDER than `--self`, never narrower.
|
|
146
|
+
# * `--include-terminal` is ignored too (a BOW holding 52 tasks, 17 of them
|
|
147
|
+
# `validated`, answers 35 either way), so the default non-terminal filter is
|
|
148
|
+
# applied server-side but the flag that lifts it is not. `--status` could not
|
|
149
|
+
# be probed -- this CLI has none -- and on that evidence should not be
|
|
150
|
+
# assumed to compose either.
|
|
151
|
+
#
|
|
152
|
+
# None of that is a correctness risk, which is why the flags are still sent: a
|
|
153
|
+
# filter the server ignores makes the answer WIDER, `is_open` still runs
|
|
154
|
+
# client-side, and the argv is one the CLI accepts. It costs bytes, not reviews.
|
|
155
|
+
# Treat the list above as a dated snapshot, not a contract: this CLI has grown
|
|
156
|
+
# flags twice now without moving off version `0.1.0`.
|
|
157
|
+
#
|
|
158
|
+
# Opt-in per deployment for the same reason `--self` is, only sharper: a review
|
|
159
|
+
# task that is not attached to the configured BOW is INVISIBLE to the daemon --
|
|
160
|
+
# and on the default `on_missing_review_task` that is not even a missed round but
|
|
161
|
+
# a round spawned untethered from its task -- and review tasks are only born into
|
|
162
|
+
# a BOW once the sessions that create them say so. A configured BOW that nothing
|
|
163
|
+
# writes into lists empty; `list_tasks` retries plain and drops the BOW alone.
|
|
164
|
+
# That is a safety net, not a licence to guess the id.
|
|
165
|
+
TASK_LIST_BOW_FLAG = "--bow"
|
|
166
|
+
|
|
125
167
|
|
|
126
168
|
@dataclass(frozen=True)
|
|
127
169
|
class TaskListFlags:
|
|
@@ -135,6 +177,7 @@ class TaskListFlags:
|
|
|
135
177
|
status: bool = False
|
|
136
178
|
self_scope: bool = False
|
|
137
179
|
digest: bool = False
|
|
180
|
+
bow: bool = False
|
|
138
181
|
|
|
139
182
|
|
|
140
183
|
# How deeply an OPTION may be indented in a commander help listing. Commander
|
|
@@ -370,7 +413,12 @@ class TaskDetail:
|
|
|
370
413
|
|
|
371
414
|
|
|
372
415
|
class Alissa:
|
|
373
|
-
def __init__(
|
|
416
|
+
def __init__(
|
|
417
|
+
self,
|
|
418
|
+
*,
|
|
419
|
+
task_list_self_scope: bool = False,
|
|
420
|
+
task_list_bow_id: "str | None" = None,
|
|
421
|
+
) -> None:
|
|
374
422
|
"""`task_list_self_scope` opts the list call into `--self`.
|
|
375
423
|
|
|
376
424
|
Default OFF, and that default is evidence, not caution -- see
|
|
@@ -378,8 +426,15 @@ class Alissa:
|
|
|
378
426
|
of a DEPLOYMENT (who creates its review tasks), not of this code, and an
|
|
379
427
|
operator who knows their review tasks are all actor-owned should be able
|
|
380
428
|
to say so.
|
|
429
|
+
|
|
430
|
+
`task_list_bow_id` opts the list call into `--bow <id>` (issue #100).
|
|
431
|
+
Unset -- the default -- is today's call shape, unconditionally. It is
|
|
432
|
+
the id ALREADY RESOLVED through the config layers (file < CLI < env);
|
|
433
|
+
this class does not read the environment, so the one place that decides
|
|
434
|
+
what the id is stays `config.env_task_list_bow_id`.
|
|
381
435
|
"""
|
|
382
436
|
self._task_list_self_scope = bool(task_list_self_scope)
|
|
437
|
+
self._task_list_bow_id = (task_list_bow_id or "").strip() or None
|
|
383
438
|
# The probe's answer, memoized for the process; None = not probed yet.
|
|
384
439
|
# A probe that FAILS is deliberately not memoized (see probe_task_list).
|
|
385
440
|
self._task_list_flags: "TaskListFlags | None" = None
|
|
@@ -389,10 +444,27 @@ class Alissa:
|
|
|
389
444
|
# wrongly is worse than a list that is large: `find_review_task` reads an
|
|
390
445
|
# empty corpus as "this PR has no review task".
|
|
391
446
|
self._task_list_narrowing_disabled = False
|
|
447
|
+
# Set when the BOW SPECIFICALLY has been disproved at runtime -- a
|
|
448
|
+
# BOW-scoped call that answered empty. Separate from the flag above
|
|
449
|
+
# because the evidence is different in kind: `--status`/`--self`/
|
|
450
|
+
# `--view` filter the same corpus, so an empty answer from them is
|
|
451
|
+
# anomalous, while `--bow` REPLACES the corpus and "this body of work
|
|
452
|
+
# holds nothing" is a legitimate answer (see `list_tasks`).
|
|
453
|
+
self._task_list_bow_disabled = False
|
|
392
454
|
# One diagnostic per client for a status filter that could not be sent
|
|
393
455
|
# (see `_warn_status_filter_dropped`); this path runs every poll pass.
|
|
394
456
|
self._status_filter_warned = False
|
|
395
457
|
|
|
458
|
+
@property
|
|
459
|
+
def task_list_bow_id(self) -> "str | None":
|
|
460
|
+
"""The BOW this client scopes its task list to, or None.
|
|
461
|
+
|
|
462
|
+
Read by the boot preflight, which must warn on the id this CLIENT holds
|
|
463
|
+
rather than on the one the config file happens to carry -- the env layer
|
|
464
|
+
wins over both file and CLI, so those two can disagree.
|
|
465
|
+
"""
|
|
466
|
+
return self._task_list_bow_id
|
|
467
|
+
|
|
396
468
|
# -- the `alissa task list` narrowing probe -----------------------------
|
|
397
469
|
|
|
398
470
|
def probe_task_list(self) -> "TaskListFlags":
|
|
@@ -433,6 +505,7 @@ class Alissa:
|
|
|
433
505
|
status=_advertises(helptext, TASK_LIST_STATUS_FLAG),
|
|
434
506
|
self_scope=_advertises(helptext, TASK_LIST_SELF_FLAG),
|
|
435
507
|
digest=_advertises(helptext, TASK_LIST_VIEW_FLAG),
|
|
508
|
+
bow=_advertises(helptext, TASK_LIST_BOW_FLAG),
|
|
436
509
|
)
|
|
437
510
|
self._task_list_flags = flags
|
|
438
511
|
return flags
|
|
@@ -472,13 +545,26 @@ class Alissa:
|
|
|
472
545
|
def task_list_argv(self, *, narrow_status: bool = True) -> list[str]:
|
|
473
546
|
"""The narrowest `alissa task list` this CLI actually supports.
|
|
474
547
|
|
|
475
|
-
Every addition is probe-gated, so
|
|
476
|
-
|
|
548
|
+
Every addition is probe-gated, so a CLI that offers none of them
|
|
549
|
+
produces exactly the call the daemon has always made. (The CLI installed
|
|
550
|
+
on 2026-08-28 offers `--self` and `--bow`, so that is no longer the
|
|
551
|
+
no-op it once was -- and it still reports version `0.1.0`, which is why
|
|
552
|
+
the gate reads the help rather than the version. See
|
|
553
|
+
TASK_LIST_BOW_FLAG.)
|
|
477
554
|
"""
|
|
478
555
|
argv = ["alissa", "task", "list", "--json"]
|
|
479
556
|
if self._task_list_narrowing_disabled:
|
|
480
557
|
return argv
|
|
481
558
|
flags = self.probe_task_list()
|
|
559
|
+
# The BOW goes FIRST because it is the only one of these that chooses a
|
|
560
|
+
# candidate set rather than filtering one, and reading the argv left to
|
|
561
|
+
# right is reading them in that order. The others are still sent: the
|
|
562
|
+
# projection (`--view digest`) genuinely applies within a BOW, and the
|
|
563
|
+
# ones the server ignores there cost bytes rather than reviews -- see
|
|
564
|
+
# TASK_LIST_BOW_FLAG for which is which, and for why that list is a
|
|
565
|
+
# dated snapshot rather than a contract.
|
|
566
|
+
if flags.bow and self._task_list_bow_id and not self._task_list_bow_disabled:
|
|
567
|
+
argv += [TASK_LIST_BOW_FLAG, self._task_list_bow_id]
|
|
482
568
|
# An empty filter is not a filter: `_status_filter` answers `""` when
|
|
483
569
|
# the open set carries a status the CLI would reject, and sending
|
|
484
570
|
# `--status ""` would be that same rejected call with an extra step.
|
|
@@ -502,8 +588,9 @@ class Alissa:
|
|
|
502
588
|
filtering. Newer CLIs offer more, so the call is now assembled from a
|
|
503
589
|
boot-time probe of the installed CLI's help (`task_list_argv`): a status
|
|
504
590
|
filter covering exactly the statuses a live review task can hold, a lean
|
|
505
|
-
`--view digest`,
|
|
506
|
-
|
|
591
|
+
`--view digest`, `--self` when the deployment says its review tasks are
|
|
592
|
+
actor-owned, and `--bow <id>` when it has named the body of work they
|
|
593
|
+
are created into. None of it is required; an absent flag is simply not
|
|
507
594
|
sent.
|
|
508
595
|
|
|
509
596
|
Narrowing is still the SECOND line of defence, not the first. Even a
|
|
@@ -520,10 +607,17 @@ class Alissa:
|
|
|
520
607
|
filter; every other narrowing still applies.
|
|
521
608
|
|
|
522
609
|
A narrowed call that FAILS, or that answers with an empty corpus, is
|
|
523
|
-
retried once unnarrowed
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
610
|
+
retried once unnarrowed. Both are how a CLI that advertises a flag its
|
|
611
|
+
API does not serve would present, and either would otherwise read as
|
|
612
|
+
"this actor has no review tasks" -- which is a skipped review, not a
|
|
613
|
+
slower one.
|
|
614
|
+
|
|
615
|
+
What the retry then turns OFF differs by which signal it was, because
|
|
616
|
+
the two carry different evidence. A FAILURE turns off all narrowing for
|
|
617
|
+
the process. An EMPTY answer does too -- unless the call was BOW-scoped,
|
|
618
|
+
in which case only the BOW is dropped and the other flags stand: `--bow`
|
|
619
|
+
replaces the corpus instead of filtering it, so an empty answer from it
|
|
620
|
+
is a legitimate result and says nothing about the rest.
|
|
527
621
|
"""
|
|
528
622
|
argv = self.task_list_argv(narrow_status=narrow_status)
|
|
529
623
|
plain = ["alissa", "task", "list", "--json"]
|
|
@@ -532,6 +626,13 @@ class Alissa:
|
|
|
532
626
|
except CommandError:
|
|
533
627
|
if argv == plain:
|
|
534
628
|
raise
|
|
629
|
+
# Deliberately blunter than the empty-answer path below, which
|
|
630
|
+
# attributes the disproof to `--bow` alone when it can. A hard
|
|
631
|
+
# failure cannot be attributed: it does not say which flag the
|
|
632
|
+
# server choked on, and a transient timeout or 5xx says nothing
|
|
633
|
+
# about the flags at all. So all narrowing goes -- wide but
|
|
634
|
+
# complete, which costs the optimization and never a review. The
|
|
635
|
+
# asymmetry is a decision, not a corner the BOW split missed.
|
|
535
636
|
log.warning(
|
|
536
637
|
"`%s` failed — retrying the plain task list and dropping the "
|
|
537
638
|
"narrowing for this process", " ".join(argv),
|
|
@@ -558,7 +659,34 @@ class Alissa:
|
|
|
558
659
|
"empty corpus from a filter this API does not serve", " ".join(argv),
|
|
559
660
|
)
|
|
560
661
|
tasks = self._tasks_from(run_json(plain, timeout=90) or [])
|
|
561
|
-
if tasks:
|
|
662
|
+
if not tasks:
|
|
663
|
+
return tasks
|
|
664
|
+
if TASK_LIST_BOW_FLAG in argv:
|
|
665
|
+
# "The plain list found rows" proves nothing about a BOW-scoped
|
|
666
|
+
# call, because the two answers are not comparable: `--bow` swaps
|
|
667
|
+
# the candidate set rather than filtering the actor's, so its answer
|
|
668
|
+
# is not a subset of the plain one (measured 2026-08-28 -- see
|
|
669
|
+
# TASK_LIST_BOW_FLAG). Condemning the other three flags on this
|
|
670
|
+
# evidence would drop the whole optimization on the state the
|
|
671
|
+
# rollout STARTS in: a review BOW nothing has been created into yet.
|
|
672
|
+
#
|
|
673
|
+
# The BOW itself is still dropped for the process, and that is not a
|
|
674
|
+
# third option so much as the only tractable one: with a swap there
|
|
675
|
+
# is no way to tell a mistyped id from an empty-but-correct one, and
|
|
676
|
+
# holding the BOW would mean paying two list calls every pass, for
|
|
677
|
+
# ever, on a typo. Wide-but-complete, the direction everything on
|
|
678
|
+
# this path degrades in.
|
|
679
|
+
self._task_list_bow_disabled = True
|
|
680
|
+
log.warning(
|
|
681
|
+
"the BOW-scoped task list answered empty while the plain list "
|
|
682
|
+
"returned %d task(s) — that is not evidence the call is broken "
|
|
683
|
+
"(a BOW-scoped list REPLACES the actor's corpus), so only the "
|
|
684
|
+
"BOW is dropped for this process and the other narrowing "
|
|
685
|
+
"stands. Either the configured id is wrong, or nothing has been "
|
|
686
|
+
"created into that body of work yet; once it is populated, "
|
|
687
|
+
"restart the daemon to scope to it again", len(tasks),
|
|
688
|
+
)
|
|
689
|
+
else:
|
|
562
690
|
self._task_list_narrowing_disabled = True
|
|
563
691
|
log.warning(
|
|
564
692
|
"the plain task list returned %d task(s) — the narrowed call is "
|
|
@@ -6,6 +6,13 @@ Settings come from three layers, later winning over earlier:
|
|
|
6
6
|
2. a JSON config file (see `resolve_config_path`)
|
|
7
7
|
3. CLI arguments
|
|
8
8
|
|
|
9
|
+
`task_list_bow_id` adds a fourth above them all — the environment
|
|
10
|
+
(`ALISSA_REVIEW_TASK_BOW`, see `env_task_list_bow_id`). It is the only key that
|
|
11
|
+
does, and the reason is that it is the only one a second entry point needs:
|
|
12
|
+
`alissa-pr-review` builds its own `Alissa` client with no config file and no
|
|
13
|
+
argv of the daemon's, so an id that lives only in the file or the flags reaches
|
|
14
|
+
the poll loop and silently misses that call site.
|
|
15
|
+
|
|
9
16
|
`workspace_root` is deliberately **not** a config key — it is a property of the
|
|
10
17
|
running process, not of the settings. That lets one config file drive several
|
|
11
18
|
daemons over different workspaces on the same machine, each pointed with
|
|
@@ -15,6 +22,7 @@ daemons over different workspaces on the same machine, each pointed with
|
|
|
15
22
|
from __future__ import annotations
|
|
16
23
|
|
|
17
24
|
import json
|
|
25
|
+
import os
|
|
18
26
|
import re
|
|
19
27
|
from dataclasses import dataclass
|
|
20
28
|
from pathlib import Path
|
|
@@ -135,9 +143,30 @@ CONFIG_KEYS = (
|
|
|
135
143
|
"checks_spawn_wait_seconds",
|
|
136
144
|
"review_task_miss_ttl_polls",
|
|
137
145
|
"task_list_self_scope",
|
|
146
|
+
"task_list_bow_id",
|
|
138
147
|
"dry_run",
|
|
139
148
|
)
|
|
140
149
|
|
|
150
|
+
# The environment variable carrying the review BOW id (issue #100). It outranks
|
|
151
|
+
# both the config file and the CLI flag -- see the module docstring for why this
|
|
152
|
+
# one key gets a layer the others do not.
|
|
153
|
+
TASK_LIST_BOW_ENV = "ALISSA_REVIEW_TASK_BOW"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def env_task_list_bow_id(environ: "Mapping[str, str] | None" = None) -> "str | None":
|
|
157
|
+
"""The review BOW id from the environment, or None when it is not set.
|
|
158
|
+
|
|
159
|
+
`environ` is passed in rather than read from `os.environ` at every call site
|
|
160
|
+
so the precedence is a pure function the tests can drive (the same shape
|
|
161
|
+
`webui.auth.require_passcode` uses). None and empty are the SAME answer: an
|
|
162
|
+
exported-but-empty variable is how a container renders "unset", and reading
|
|
163
|
+
it as an id would send `--bow ""` -- a call the CLI would take and answer
|
|
164
|
+
with nobody's tasks.
|
|
165
|
+
"""
|
|
166
|
+
raw = (os.environ if environ is None else environ).get(TASK_LIST_BOW_ENV)
|
|
167
|
+
return (raw or "").strip() or None
|
|
168
|
+
|
|
169
|
+
|
|
141
170
|
MIN_POLL_INTERVAL = 10 # the search API allows 30 req/min
|
|
142
171
|
|
|
143
172
|
# A reviewer session that has not submitted after this long is presumed dead
|
|
@@ -343,6 +372,19 @@ class Config:
|
|
|
343
372
|
# when the installed CLI does not advertise the flag.
|
|
344
373
|
task_list_self_scope: bool = False
|
|
345
374
|
|
|
375
|
+
# The Convex `_id` of the body of work review tasks are created into. Set it
|
|
376
|
+
# and `alissa task list` is scoped to that BOW's junction rows (`--bow`)
|
|
377
|
+
# instead of the operator's whole involvement index; leave it None -- the
|
|
378
|
+
# default -- and the call shape is exactly what it has always been.
|
|
379
|
+
#
|
|
380
|
+
# Off by default because a review task OUTSIDE the configured BOW is
|
|
381
|
+
# invisible to the daemon, which is a missed round rather than a slower one,
|
|
382
|
+
# and nothing creates review tasks into a BOW until the operator's review
|
|
383
|
+
# protocol says to. Ignored when the installed CLI does not advertise the
|
|
384
|
+
# flag. See alissa.TASK_LIST_BOW_FLAG, and the README for the two ways to
|
|
385
|
+
# get the id wrong (a repo's `autodev:` feed BOW; a `mirrorInstanceId`).
|
|
386
|
+
task_list_bow_id: str | None = None
|
|
387
|
+
|
|
346
388
|
dry_run: bool = False
|
|
347
389
|
|
|
348
390
|
def __post_init__(self) -> None:
|
|
@@ -395,9 +437,15 @@ class Config:
|
|
|
395
437
|
workspace_root: Path,
|
|
396
438
|
file_data: Mapping[str, Any] | None = None,
|
|
397
439
|
overrides: Mapping[str, Any] | None = None,
|
|
440
|
+
environ: Mapping[str, str] | None = None,
|
|
398
441
|
) -> "Config":
|
|
399
442
|
"""Merge the layers and validate. `overrides` entries that are None mean
|
|
400
|
-
"not specified on the CLI" and fall through to the file / defaults.
|
|
443
|
+
"not specified on the CLI" and fall through to the file / defaults.
|
|
444
|
+
|
|
445
|
+
`environ` is the fourth layer and applies to `task_list_bow_id` alone;
|
|
446
|
+
it wins over both of the others (see the module docstring). Defaults to
|
|
447
|
+
the real environment, so callers that do not care pass nothing.
|
|
448
|
+
"""
|
|
401
449
|
raw: dict[str, Any] = dict(file_data or {})
|
|
402
450
|
|
|
403
451
|
if "workspace_root" in raw:
|
|
@@ -419,6 +467,31 @@ class Config:
|
|
|
419
467
|
if value is not None:
|
|
420
468
|
raw[key] = value
|
|
421
469
|
|
|
470
|
+
# Applied AFTER the CLI overrides, which is the whole of "file < CLI <
|
|
471
|
+
# env": the variable is how a container hands the daemon an id that no
|
|
472
|
+
# file on its volume and no argv in its unit carries, so it has to be
|
|
473
|
+
# able to say something the other two do not.
|
|
474
|
+
env_bow = env_task_list_bow_id(environ)
|
|
475
|
+
if env_bow is not None:
|
|
476
|
+
raw["task_list_bow_id"] = env_bow
|
|
477
|
+
bow_id = raw.get("task_list_bow_id")
|
|
478
|
+
if bow_id is not None and not isinstance(bow_id, str):
|
|
479
|
+
# Deliberately NOT the `int(...)`/`bool(...)` coercion the numeric
|
|
480
|
+
# keys around here use. Those coerce into a value the validation
|
|
481
|
+
# below them then checks; there is nothing downstream to check an
|
|
482
|
+
# id against, so `0` would coerce to a well-formed `--bow 0` that
|
|
483
|
+
# can never resolve -- landing the deployment in the empty-answer
|
|
484
|
+
# path instead of telling it anything. A misspelled KEY already
|
|
485
|
+
# fails loudly at load; a mistyped VALUE should too.
|
|
486
|
+
raise ValueError(
|
|
487
|
+
f"task_list_bow_id must be the review BOW's Convex _id as a "
|
|
488
|
+
f"string (or null for unset), got a {type(bow_id).__name__}"
|
|
489
|
+
)
|
|
490
|
+
# Normalised the same way the env layer is, so `""` from a config file
|
|
491
|
+
# or a flag means "unset" too rather than `--bow ""` -- a call the CLI
|
|
492
|
+
# takes and answers with nobody's tasks.
|
|
493
|
+
bow_id = (bow_id or "").strip() or None
|
|
494
|
+
|
|
422
495
|
mode = raw.get("on_missing_review_task", ON_MISSING_SPAWN)
|
|
423
496
|
if mode not in _MISSING_MODES:
|
|
424
497
|
raise ValueError(
|
|
@@ -580,6 +653,7 @@ class Config:
|
|
|
580
653
|
checks_spawn_wait_seconds=spawn_wait,
|
|
581
654
|
review_task_miss_ttl_polls=miss_ttl,
|
|
582
655
|
task_list_self_scope=bool(raw.get("task_list_self_scope", False)),
|
|
656
|
+
task_list_bow_id=bow_id,
|
|
583
657
|
dry_run=bool(raw.get("dry_run", False)),
|
|
584
658
|
)
|
|
585
659
|
|
|
@@ -1425,7 +1425,8 @@ class ReviewWatcher:
|
|
|
1425
1425
|
config.reviewer_login, token_env=config.reviewer_token_env
|
|
1426
1426
|
)
|
|
1427
1427
|
self.alissa = alissa or Alissa(
|
|
1428
|
-
task_list_self_scope=config.task_list_self_scope
|
|
1428
|
+
task_list_self_scope=config.task_list_self_scope,
|
|
1429
|
+
task_list_bow_id=config.task_list_bow_id,
|
|
1429
1430
|
)
|
|
1430
1431
|
self.state = state or State(config.state_db)
|
|
1431
1432
|
# (repo, number, comment id) of every re-entry directive already
|
|
@@ -4063,6 +4064,15 @@ class ReviewWatcher:
|
|
|
4063
4064
|
"does not advertise `task list --self` — the task list is not "
|
|
4064
4065
|
"actor-scoped; upgrade the CLI or drop the key"
|
|
4065
4066
|
)
|
|
4067
|
+
# Read off the CLIENT, not the config: the env layer outranks both the
|
|
4068
|
+
# file and the flag, so an operator who set only ALISSA_REVIEW_TASK_BOW
|
|
4069
|
+
# would otherwise get no warning about the CLI that cannot serve it.
|
|
4070
|
+
if self.alissa.task_list_bow_id and not self.alissa.probe_task_list().bow:
|
|
4071
|
+
warnings.append(
|
|
4072
|
+
"a review task BOW is configured but the installed `alissa` "
|
|
4073
|
+
"CLI does not advertise `task list --bow` — the task list is "
|
|
4074
|
+
"not BOW-scoped; upgrade the CLI or drop the key"
|
|
4075
|
+
)
|
|
4066
4076
|
|
|
4067
4077
|
if not self.config.dry_run and not self.alissa.worker_running():
|
|
4068
4078
|
warnings.append(
|
|
@@ -35,6 +35,7 @@ import sys
|
|
|
35
35
|
import time
|
|
36
36
|
|
|
37
37
|
from .alissa import Alissa, VERDICT_APPROVE, VERDICT_REQUEST_CHANGES
|
|
38
|
+
from .config import env_task_list_bow_id
|
|
38
39
|
from .ghclient import GitHub
|
|
39
40
|
from .proc import CommandError, run, run_json
|
|
40
41
|
|
|
@@ -145,7 +146,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
145
146
|
)
|
|
146
147
|
|
|
147
148
|
gh = GitHub()
|
|
148
|
-
|
|
149
|
+
# The same BOW narrowing the daemon's poll uses (issue #100), from the one
|
|
150
|
+
# channel this command has: it runs in a task worktree, so there is no
|
|
151
|
+
# revloop config file under its cwd and no daemon argv to inherit. Unset ->
|
|
152
|
+
# the call shape this command has always made.
|
|
153
|
+
alissa = Alissa(task_list_bow_id=env_task_list_bow_id())
|
|
149
154
|
|
|
150
155
|
try:
|
|
151
156
|
# Identity guard: GitHub forbids requesting review from the PR author, so
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.25.0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.23.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
|