alissa-tools-github-revloop 0.30.1__tar.gz → 0.31.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.30.1/src/main/alissa_tools_github_revloop.egg-info → alissa_tools_github_revloop-0.31.0}/PKG-INFO +1 -1
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/alissa.py +102 -5
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/loop.py +75 -6
- alissa_tools_github_revloop-0.31.0/src/main/alissa/tools/github/revloop/version +1 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0/src/main/alissa_tools_github_revloop.egg-info}/PKG-INFO +1 -1
- alissa_tools_github_revloop-0.30.1/src/main/alissa/tools/github/revloop/version +0 -1
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/LICENSE +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/MANIFEST.in +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/NOTICE +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/README.md +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/requirements.txt +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/setup.cfg +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/setup.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/__init__.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/__main__.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/alissa_client.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/bows.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/config.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/fleet_vitals.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/ghclient.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/loop_events.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/proc.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/prreview.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/state.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/version.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/webui/__init__.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/webui/__main__.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/webui/auth.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/webui/page.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/webui/server.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/webui/sources.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa/tools/github/revloop/webui/sysinfo.py +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa_tools_github_revloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa_tools_github_revloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa_tools_github_revloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_revloop-0.30.1 → alissa_tools_github_revloop-0.31.0}/src/main/alissa_tools_github_revloop.egg-info/top_level.txt +0 -0
|
@@ -233,6 +233,53 @@ _VERDICT_RE = re.compile(
|
|
|
233
233
|
re.IGNORECASE,
|
|
234
234
|
)
|
|
235
235
|
|
|
236
|
+
# The envelope's merge-readiness judgment (issue #130). The alissa-code-review
|
|
237
|
+
# skill writes it as one list line in the verdict envelope:
|
|
238
|
+
# - **Merge-Readiness:** auto
|
|
239
|
+
# - **Merge-Readiness:** operator — touches convex/schema.ts
|
|
240
|
+
# Tolerant of the markdown around the label -- an optional bullet, optional
|
|
241
|
+
# `**` bold around the label (with the colon inside or outside it), optional
|
|
242
|
+
# bold around the value -- but NOT of the value's case: `Auto` is not a
|
|
243
|
+
# judgment this daemon will carry, so it reads as missing and fails closed to
|
|
244
|
+
# `operator` downstream. Line-anchored, first match wins, like the consumer's
|
|
245
|
+
# own grammar; the reason runs to the end of the line and no further.
|
|
246
|
+
READINESS_AUTO = "auto"
|
|
247
|
+
READINESS_OPERATOR = "operator"
|
|
248
|
+
_READINESS_RE = re.compile(
|
|
249
|
+
r"^[ \t]*(?:[-*+][ \t]+)?(?:\*\*)?[ \t]*Merge-Readiness[ \t]*:?[ \t]*(?:\*\*)?"
|
|
250
|
+
r"[ \t]*:?[ \t]*(?:\*\*)?(auto|operator)\b(?:\*\*)?"
|
|
251
|
+
r"(?:[ \t]*[—–-][ \t]*(.*?))?[ \t]*$",
|
|
252
|
+
re.MULTILINE,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# The reason lands in a GitHub review body (as a line-anchored trailer) and in
|
|
256
|
+
# a log line, so it is bounded and flattened at the parser: one line, no
|
|
257
|
+
# backticks (a fence would swallow the trailer and everything after it), and
|
|
258
|
+
# no more than this many characters.
|
|
259
|
+
MAX_READINESS_REASON_CHARS = 200
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def clean_readiness_reason(text: object) -> str:
|
|
263
|
+
"""Flatten a Merge-Readiness reason to one bounded, backtick-free line."""
|
|
264
|
+
if not isinstance(text, str):
|
|
265
|
+
return ""
|
|
266
|
+
flat = " ".join(text.replace("`", "").split())
|
|
267
|
+
return flat[:MAX_READINESS_REASON_CHARS].rstrip()
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def parse_readiness(blob: object) -> "tuple[str | None, str]":
|
|
271
|
+
"""`(value, reason)` from the first Merge-Readiness line in `blob`.
|
|
272
|
+
|
|
273
|
+
`value` is READINESS_AUTO / READINESS_OPERATOR, or None when no line
|
|
274
|
+
parses; `reason` is the cleaned trailing text (empty when there is none).
|
|
275
|
+
"""
|
|
276
|
+
if not isinstance(blob, str):
|
|
277
|
+
return (None, "")
|
|
278
|
+
match = _READINESS_RE.search(blob)
|
|
279
|
+
if match is None:
|
|
280
|
+
return (None, "")
|
|
281
|
+
return (match.group(1), clean_readiness_reason(match.group(2)))
|
|
282
|
+
|
|
236
283
|
|
|
237
284
|
@dataclass(frozen=True)
|
|
238
285
|
class Task:
|
|
@@ -389,6 +436,23 @@ def _task_from_row(row: object) -> "Task | None":
|
|
|
389
436
|
)
|
|
390
437
|
|
|
391
438
|
|
|
439
|
+
@dataclass(frozen=True)
|
|
440
|
+
class VerdictEnvelope:
|
|
441
|
+
"""One parsed CR6 verdict envelope: the verdict, plus the merge-readiness
|
|
442
|
+
judgment the same envelope carries (issue #130).
|
|
443
|
+
|
|
444
|
+
`readiness` is READINESS_AUTO / READINESS_OPERATOR, or None when the
|
|
445
|
+
envelope has no parseable `Merge-Readiness` line -- the case the native
|
|
446
|
+
post fails closed on. `readiness_reason` is already one bounded,
|
|
447
|
+
backtick-free line (see clean_readiness_reason); empty when the envelope
|
|
448
|
+
gave none.
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
verdict: str
|
|
452
|
+
readiness: "str | None" = None
|
|
453
|
+
readiness_reason: str = ""
|
|
454
|
+
|
|
455
|
+
|
|
392
456
|
@dataclass(frozen=True)
|
|
393
457
|
class TaskDetail:
|
|
394
458
|
"""One task as `alissa task get` sees it: the task itself, plus everything
|
|
@@ -774,16 +838,29 @@ class Alissa:
|
|
|
774
838
|
return matches[0]
|
|
775
839
|
|
|
776
840
|
def latest_verdict(self, task_ref: str) -> str | None:
|
|
777
|
-
"""The newest CR6 verdict envelope on a review task, or None.
|
|
841
|
+
"""The newest CR6 verdict envelope's verdict on a review task, or None.
|
|
778
842
|
|
|
779
843
|
Returns VERDICT_APPROVE / VERDICT_REQUEST_CHANGES. This is the verdict
|
|
780
844
|
of record: reviewers post comment-mode reviews, so the GitHub review
|
|
781
845
|
state is always COMMENTED and cannot express approval at all.
|
|
782
846
|
|
|
847
|
+
The word alone; `latest_envelope` returns the whole record (verdict
|
|
848
|
+
plus merge-readiness) off the same read for the caller that posts it.
|
|
849
|
+
|
|
783
850
|
Never raises. The daemon polls forever and this runs inside every pass,
|
|
784
851
|
so absent, empty or malformed evidence degrades to "no verdict" rather
|
|
785
852
|
than taking the loop down.
|
|
786
853
|
"""
|
|
854
|
+
envelope = self.latest_envelope(task_ref)
|
|
855
|
+
return None if envelope is None else envelope.verdict
|
|
856
|
+
|
|
857
|
+
def latest_envelope(self, task_ref: str) -> "VerdictEnvelope | None":
|
|
858
|
+
"""The newest CR6 verdict envelope on a review task, parsed, or None.
|
|
859
|
+
|
|
860
|
+
Same read and same tolerance as `latest_verdict`; this is the record
|
|
861
|
+
the native post carries onto GitHub (issue #130), so it needs the
|
|
862
|
+
envelope's Merge-Readiness line alongside the verdict word.
|
|
863
|
+
"""
|
|
787
864
|
try:
|
|
788
865
|
data = run_json(["alissa", "task", "get", task_ref, "--json"], timeout=90)
|
|
789
866
|
except CommandError as exc:
|
|
@@ -794,7 +871,7 @@ class Alissa:
|
|
|
794
871
|
return None
|
|
795
872
|
|
|
796
873
|
try:
|
|
797
|
-
return self.
|
|
874
|
+
return self._newest_envelope(data)
|
|
798
875
|
except Exception: # pragma: no cover - defence in depth
|
|
799
876
|
log.exception("could not parse verdict evidence for %s", task_ref)
|
|
800
877
|
return None
|
|
@@ -839,10 +916,23 @@ class Alissa:
|
|
|
839
916
|
|
|
840
917
|
@staticmethod
|
|
841
918
|
def _newest_verdict(payload: object) -> str | None:
|
|
842
|
-
"""
|
|
919
|
+
"""The newest parseable verdict WORD out of a task's evidence array;
|
|
920
|
+
`_newest_envelope` with the readiness dropped, for the callers that
|
|
921
|
+
only decide on the word."""
|
|
922
|
+
envelope = Alissa._newest_envelope(payload)
|
|
923
|
+
return None if envelope is None else envelope.verdict
|
|
924
|
+
|
|
925
|
+
@staticmethod
|
|
926
|
+
def _newest_envelope(payload: object) -> "VerdictEnvelope | None":
|
|
927
|
+
"""Pick the newest parseable verdict envelope out of a task's evidence
|
|
928
|
+
array, with the Merge-Readiness judgment it carries (issue #130).
|
|
843
929
|
|
|
844
930
|
Every layer is optional by design -- the payload shape is whatever the
|
|
845
931
|
CLI printed, and a task with no evidence is the normal round-1 case.
|
|
932
|
+
|
|
933
|
+
The readiness is read off the SAME evidence item as the verdict (its
|
|
934
|
+
body first, then its title), never off a neighbour: a stray line on an
|
|
935
|
+
older envelope must not decorate a newer round's verdict.
|
|
846
936
|
"""
|
|
847
937
|
if not isinstance(payload, dict):
|
|
848
938
|
return None
|
|
@@ -850,7 +940,7 @@ class Alissa:
|
|
|
850
940
|
if not isinstance(evidence, list):
|
|
851
941
|
return None
|
|
852
942
|
|
|
853
|
-
found: list[tuple[tuple[int, float], int,
|
|
943
|
+
found: list[tuple[tuple[int, float], int, VerdictEnvelope]] = []
|
|
854
944
|
for index, item in enumerate(evidence):
|
|
855
945
|
if not isinstance(item, dict):
|
|
856
946
|
continue
|
|
@@ -861,10 +951,17 @@ class Alissa:
|
|
|
861
951
|
continue
|
|
862
952
|
match = _VERDICT_RE.search(blob)
|
|
863
953
|
if match:
|
|
954
|
+
readiness, reason = parse_readiness(content)
|
|
955
|
+
if readiness is None:
|
|
956
|
+
readiness, reason = parse_readiness(title)
|
|
864
957
|
found.append(
|
|
865
958
|
(Alissa._created_key(item.get("createdAt")),
|
|
866
959
|
index,
|
|
867
|
-
|
|
960
|
+
VerdictEnvelope(
|
|
961
|
+
verdict=match.group(1).lower(),
|
|
962
|
+
readiness=readiness,
|
|
963
|
+
readiness_reason=reason,
|
|
964
|
+
))
|
|
868
965
|
)
|
|
869
966
|
break
|
|
870
967
|
|
|
@@ -22,8 +22,11 @@ from enum import Enum
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
24
|
from .alissa import (
|
|
25
|
+
READINESS_AUTO,
|
|
26
|
+
READINESS_OPERATOR,
|
|
25
27
|
VERDICT_APPROVE,
|
|
26
28
|
VERDICT_REQUEST_CHANGES,
|
|
29
|
+
VerdictEnvelope,
|
|
27
30
|
Alissa,
|
|
28
31
|
ManagedSession,
|
|
29
32
|
SessionRef,
|
|
@@ -391,10 +394,50 @@ NATIVE_VERDICT_BODY = (
|
|
|
391
394
|
"Submitted by the review daemon under the configured reviewer identity, so "
|
|
392
395
|
"this round has a verdict of record on GitHub. The round's findings and "
|
|
393
396
|
"reasoning are in the reviewer session's own comments on this PR{task_note}."
|
|
394
|
-
"{head_note}\n\n"
|
|
397
|
+
"{head_note}{readiness}\n\n"
|
|
395
398
|
"{marker}"
|
|
396
399
|
)
|
|
397
400
|
|
|
401
|
+
# The merge-readiness trailer (issue #130). orcloop's opt-in merge edge merges
|
|
402
|
+
# a PR only when the reviewer's APPROVE on the current head carries
|
|
403
|
+
# `Merge-Readiness: auto`; the judgment is the reviewer session's (the
|
|
404
|
+
# envelope's `- **Merge-Readiness:**` line) and this daemon only CARRIES it,
|
|
405
|
+
# in a grammar the consumer can parse: one plain-text line, line-anchored,
|
|
406
|
+
# the last non-empty line before the hidden verdict marker --
|
|
407
|
+
#
|
|
408
|
+
# Merge-Readiness: auto
|
|
409
|
+
# Merge-Readiness: operator — <one-line reason>
|
|
410
|
+
#
|
|
411
|
+
# The rules that make it safe to consume: it is emitted on APPROVE events
|
|
412
|
+
# ONLY (a request_changes envelope with a stray `auto` line carries nothing;
|
|
413
|
+
# an approve the checks gate downgraded carries nothing), and an envelope
|
|
414
|
+
# with no parseable line fails CLOSED to `operator`, with a reason that tells
|
|
415
|
+
# the operator why the merge waited on them.
|
|
416
|
+
READINESS_TRAILER_LABEL = "Merge-Readiness:"
|
|
417
|
+
READINESS_MISSING_REASON = "envelope carries no Merge-Readiness line"
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def readiness_trailer(envelope: "VerdictEnvelope | None") -> str:
|
|
421
|
+
"""The line the native APPROVE carries for `envelope`'s judgment.
|
|
422
|
+
|
|
423
|
+
The caller has already decided the event is an APPROVE; this only maps
|
|
424
|
+
the envelope onto the grammar. `auto` is emitted bare -- the consumer
|
|
425
|
+
keys on the value, and an operator reads a reason only when the merge is
|
|
426
|
+
theirs to make. A missing or unparseable judgment (None envelope, or one
|
|
427
|
+
whose readiness did not parse) is `operator` with a reason saying so.
|
|
428
|
+
"""
|
|
429
|
+
if envelope is not None and envelope.readiness == READINESS_AUTO:
|
|
430
|
+
return f"{READINESS_TRAILER_LABEL} {READINESS_AUTO}"
|
|
431
|
+
if envelope is not None and envelope.readiness == READINESS_OPERATOR:
|
|
432
|
+
if envelope.readiness_reason:
|
|
433
|
+
return (
|
|
434
|
+
f"{READINESS_TRAILER_LABEL} {READINESS_OPERATOR} — "
|
|
435
|
+
f"{envelope.readiness_reason}"
|
|
436
|
+
)
|
|
437
|
+
return f"{READINESS_TRAILER_LABEL} {READINESS_OPERATOR}"
|
|
438
|
+
return f"{READINESS_TRAILER_LABEL} {READINESS_OPERATOR} — {READINESS_MISSING_REASON}"
|
|
439
|
+
|
|
440
|
+
|
|
398
441
|
# Appended to the body when the round's review task is known.
|
|
399
442
|
_VERDICT_TASK_NOTE = ", and the CR6 verdict envelope is on `{task_ref}`"
|
|
400
443
|
|
|
@@ -601,6 +644,17 @@ _RECORD_THE_CAP = (
|
|
|
601
644
|
"from a stale template default. "
|
|
602
645
|
)
|
|
603
646
|
|
|
647
|
+
# The merge-readiness line (issue #130). The verdict envelope is where the
|
|
648
|
+
# reviewer's judgment lives; the daemon copies it onto the native review as a
|
|
649
|
+
# trailer the merge edge reads, and an envelope without the line posts as
|
|
650
|
+
# `operator` -- so a reviewer who skips it has silently withheld auto-merge.
|
|
651
|
+
_MERGE_READINESS_LINE = (
|
|
652
|
+
"Your verdict envelope MUST carry the skill's `- **Merge-Readiness:** auto | "
|
|
653
|
+
"operator — <reason>` line — the daemon copies it onto the native review as "
|
|
654
|
+
"the `Merge-Readiness:` trailer the merge edge reads, and an envelope "
|
|
655
|
+
"without the line posts as `operator`. "
|
|
656
|
+
)
|
|
657
|
+
|
|
604
658
|
# -- the reviewer session's own CI gate (issue #84) ---------------------------
|
|
605
659
|
#
|
|
606
660
|
# _gate_on_checks (issue #58) gates the verdict the DAEMON posts. It cannot gate the
|
|
@@ -881,6 +935,7 @@ ROUND_1_DIRECTIVE = (
|
|
|
881
935
|
"severity-tagged comments via gh pr review, record the verdict evidence, "
|
|
882
936
|
"move the task to pending_validation. "
|
|
883
937
|
+ _RECORD_THE_CAP
|
|
938
|
+
+ _MERGE_READINESS_LINE
|
|
884
939
|
+ "{credential}"
|
|
885
940
|
+ _CHECKS_BEFORE_VERDICT
|
|
886
941
|
+ "{checks}"
|
|
@@ -899,6 +954,7 @@ ROUND_K_DIRECTIVE = (
|
|
|
899
954
|
"verify the fixes, sweep the new diff with the full rubric, record a "
|
|
900
955
|
"round-{round} verdict envelope, move the task to pending_validation. "
|
|
901
956
|
+ _RECORD_THE_CAP
|
|
957
|
+
+ _MERGE_READINESS_LINE
|
|
902
958
|
+ "{credential}"
|
|
903
959
|
+ _CHECKS_BEFORE_VERDICT
|
|
904
960
|
+ "{checks}"
|
|
@@ -3169,9 +3225,10 @@ class ReviewWatcher:
|
|
|
3169
3225
|
Never raises past RateLimited: a broken post must stall this PR, not
|
|
3170
3226
|
the whole poll pass.
|
|
3171
3227
|
"""
|
|
3172
|
-
|
|
3228
|
+
envelope = self.alissa.latest_envelope(task.ref)
|
|
3229
|
+
verdict = None if envelope is None else envelope.verdict
|
|
3173
3230
|
if verdict not in (VERDICT_APPROVE, VERDICT_REQUEST_CHANGES):
|
|
3174
|
-
# count_verdicts and
|
|
3231
|
+
# count_verdicts and latest_envelope read the same envelopes with
|
|
3175
3232
|
# the same pattern, so this is nearly unreachable -- but "I know a
|
|
3176
3233
|
# round finished and cannot tell you its verdict" must never be
|
|
3177
3234
|
# resolved by guessing one onto the PR.
|
|
@@ -3266,6 +3323,11 @@ class ReviewWatcher:
|
|
|
3266
3323
|
return gate.hold
|
|
3267
3324
|
event = gate.event
|
|
3268
3325
|
|
|
3326
|
+
# The trailer follows the EVENT, not the envelope: an approve the
|
|
3327
|
+
# checks gate turned into a REQUEST_CHANGES or COMMENT carries none,
|
|
3328
|
+
# because `auto` on anything but an APPROVE is exactly what the
|
|
3329
|
+
# consumer must never see (issue #130).
|
|
3330
|
+
trailer = readiness_trailer(envelope) if event == EVENT_APPROVE else ""
|
|
3269
3331
|
body = gate.lead + NATIVE_VERDICT_BODY.format(
|
|
3270
3332
|
round=round_,
|
|
3271
3333
|
verdict=verdict,
|
|
@@ -3275,6 +3337,7 @@ class ReviewWatcher:
|
|
|
3275
3337
|
if moved
|
|
3276
3338
|
else ""
|
|
3277
3339
|
),
|
|
3340
|
+
readiness=f"\n\n{trailer}" if trailer else "",
|
|
3278
3341
|
marker=verdict_marker(round_),
|
|
3279
3342
|
)
|
|
3280
3343
|
try:
|
|
@@ -3312,14 +3375,20 @@ class ReviewWatcher:
|
|
|
3312
3375
|
self.state.record_verdict(
|
|
3313
3376
|
pr.full_name, pr.number, judged, int(time.time()), url or ""
|
|
3314
3377
|
)
|
|
3378
|
+
# The readiness carried on the post, in the same three records. Only
|
|
3379
|
+
# an APPROVE carries one, so only an APPROVE names it.
|
|
3380
|
+
readiness_value = trailer[len(READINESS_TRAILER_LABEL):].strip()
|
|
3381
|
+
readiness_note = f" readiness={readiness_value}" if trailer else ""
|
|
3315
3382
|
log.info(
|
|
3316
|
-
"%s round %d closed: native %s review submitted as %s (%s)%s",
|
|
3317
|
-
pr.slug, round_, event, self.github.login, url or "no url",
|
|
3383
|
+
"%s round %d closed: native %s review submitted as %s (%s)%s%s",
|
|
3384
|
+
pr.slug, round_, event, self.github.login, url or "no url",
|
|
3385
|
+
readiness_note, gate_note,
|
|
3318
3386
|
)
|
|
3319
3387
|
self._append_activity(
|
|
3320
3388
|
pr,
|
|
3321
3389
|
f"- {_now()} — round {round_} — native `{event}` review submitted "
|
|
3322
|
-
f"as `{self.github.login}` (verdict of record){gate_note}"
|
|
3390
|
+
f"as `{self.github.login}` (verdict of record){gate_note}"
|
|
3391
|
+
+ (f" — merge-readiness: `{readiness_value}`" if trailer else ""),
|
|
3323
3392
|
)
|
|
3324
3393
|
if event == EVENT_COMMENT:
|
|
3325
3394
|
# A degraded verdict takes the PR out of the loop (the review
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.31.0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.30.1
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|