ergane-cli 0.1.0__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.
- ergane_cli-0.1.0.dist-info/METADATA +148 -0
- ergane_cli-0.1.0.dist-info/RECORD +121 -0
- ergane_cli-0.1.0.dist-info/WHEEL +4 -0
- ergane_cli-0.1.0.dist-info/entry_points.txt +2 -0
- ergane_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
- ergane_cli-0.1.0.dist-info/licenses/NOTICE +27 -0
- factory/__init__.py +0 -0
- factory/activities/__init__.py +0 -0
- factory/activities/agent_activities.py +745 -0
- factory/activities/merge_activities.py +654 -0
- factory/activities/notify_activities.py +1078 -0
- factory/activities/roadmap_activities.py +577 -0
- factory/activities/usage_activities.py +593 -0
- factory/activities/verify_activities.py +617 -0
- factory/cli/__init__.py +0 -0
- factory/cli/completion.py +150 -0
- factory/cli/doctor.py +484 -0
- factory/cli/env.py +247 -0
- factory/cli/errors.py +77 -0
- factory/cli/init.py +1057 -0
- factory/cli/install.py +679 -0
- factory/cli/main.py +212 -0
- factory/cli/nouns/__init__.py +59 -0
- factory/cli/nouns/answer.py +134 -0
- factory/cli/nouns/build.py +1222 -0
- factory/cli/nouns/completion.py +13 -0
- factory/cli/nouns/doctor.py +13 -0
- factory/cli/nouns/env.py +13 -0
- factory/cli/nouns/escalations.py +93 -0
- factory/cli/nouns/findings.py +13 -0
- factory/cli/nouns/init.py +13 -0
- factory/cli/nouns/install.py +99 -0
- factory/cli/nouns/repo.py +13 -0
- factory/cli/nouns/roadmap.py +13 -0
- factory/cli/nouns/spec.py +511 -0
- factory/cli/nouns/status.py +18 -0
- factory/cli/nouns/usage.py +13 -0
- factory/cli/nouns/worker.py +69 -0
- factory/cli/repo.py +569 -0
- factory/cli/repo_export.py +282 -0
- factory/cli/roadmap.py +434 -0
- factory/cli/status.py +844 -0
- factory/cli/usage.py +129 -0
- factory/config.py +262 -0
- factory/controlplane/__init__.py +0 -0
- factory/controlplane/config.py +863 -0
- factory/controlplane/resolve.py +434 -0
- factory/controlplane/verify.py +827 -0
- factory/discovery/__init__.py +3 -0
- factory/discovery/llm_scanner.py +176 -0
- factory/doctor/__init__.py +0 -0
- factory/doctor/cli.py +392 -0
- factory/doctor/models.py +165 -0
- factory/doctor/probes.py +522 -0
- factory/doctor/scaffold.py +184 -0
- factory/doctor/store.py +449 -0
- factory/env.py +131 -0
- factory/escalation/__init__.py +0 -0
- factory/escalation/client.py +168 -0
- factory/escalation/question.py +161 -0
- factory/escalation/workflow.py +470 -0
- factory/locking.py +85 -0
- factory/mergequeue/__init__.py +21 -0
- factory/mergequeue/classify.py +133 -0
- factory/mergequeue/forge.py +401 -0
- factory/mergequeue/gh.py +484 -0
- factory/mergequeue/github_forge.py +349 -0
- factory/mergequeue/merge_queue_ruleset.json +32 -0
- factory/mergequeue/messages.py +141 -0
- factory/mergequeue/models.py +324 -0
- factory/mergequeue/onboard.py +537 -0
- factory/mergequeue/wiring.py +466 -0
- factory/notify/__init__.py +0 -0
- factory/notify/adapter.py +263 -0
- factory/notify/messages.py +474 -0
- factory/notify/service.py +809 -0
- factory/notify/webhook.py +151 -0
- factory/personas.yaml +161 -0
- factory/registry.py +583 -0
- factory/roadmap/__init__.py +8 -0
- factory/roadmap/cli.py +125 -0
- factory/roadmap/discovery.py +286 -0
- factory/roadmap/models.py +583 -0
- factory/roadmap/schedule.py +377 -0
- factory/roadmap/workflow.py +1364 -0
- factory/supervision/__init__.py +0 -0
- factory/supervision/alert.py +239 -0
- factory/supervision/probe.py +467 -0
- factory/supervision/temporal_server.py +121 -0
- factory/supervision/units.py +649 -0
- factory/usage/__init__.py +0 -0
- factory/usage/aggregate.py +113 -0
- factory/usage/cli.py +223 -0
- factory/usage/ledger.py +295 -0
- factory/usage/litellm_client.py +486 -0
- factory/usage/models.py +149 -0
- factory/verify/__init__.py +0 -0
- factory/verify/criteria.py +488 -0
- factory/verify/diffbounds.py +159 -0
- factory/verify/diffcheck.py +506 -0
- factory/verify/factory_yaml.py +842 -0
- factory/verify/gates.py +1341 -0
- factory/verify/judge.py +775 -0
- factory/verify/ladder.py +145 -0
- factory/verify/models.py +745 -0
- factory/verify/question.py +167 -0
- factory/verify/store.py +1366 -0
- factory/verify/toolchain.py +370 -0
- factory/worker.py +296 -0
- factory/workgraph/__init__.py +0 -0
- factory/workgraph/adapter.py +1333 -0
- factory/workgraph/cli.py +801 -0
- factory/workgraph/delta.py +278 -0
- factory/workgraph/derive.py +607 -0
- factory/workgraph/detector.py +509 -0
- factory/workgraph/landed.py +496 -0
- factory/workgraph/models.py +494 -0
- factory/workgraph/preflight.py +651 -0
- factory/workgraph/prompt.py +796 -0
- factory/workgraph/workflow.py +2733 -0
- factory/workgraph/worktree.py +1322 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""The merge-queue component's data model: enums, records, and the gh payload shape.
|
|
2
|
+
|
|
3
|
+
US1's whole distinction between *verified* and *merged* — FR-004 — lives in two
|
|
4
|
+
`StrEnum`s and one record. `QueueOutcome` is the closed vocabulary of what a
|
|
5
|
+
landing's queue history can say; `LandingState` is the machine the workflow
|
|
6
|
+
drives across a landing's life; `Landing` is that life as workflow state on a
|
|
7
|
+
`NodeRecord`; `PrSnapshot` is what one poll of GitHub's queue yields, and the
|
|
8
|
+
classifier's only input from the world.
|
|
9
|
+
|
|
10
|
+
All of it crosses a Temporal boundary through the default JSON converter, so the
|
|
11
|
+
discipline is the 005 models' (models.py docstring): `StrEnum`, not
|
|
12
|
+
`class X(str, Enum)` — only `StrEnum` survives the converter's *deserializer*,
|
|
13
|
+
which rebuilds a field annotated with any other str-subclass enum as a list of
|
|
14
|
+
one-character strings. And every record is frozen so a value that crossed an
|
|
15
|
+
activity boundary can never be edited in place.
|
|
16
|
+
|
|
17
|
+
`PrSnapshot.from_gh_json` is the one place the raw `gh pr view --json` payload
|
|
18
|
+
becomes a decision input, so it has to survive what GitHub actually sends: an
|
|
19
|
+
absent `autoMergeRequest` (a PR nobody enqueued is *not* queue-requested), and a
|
|
20
|
+
`statusCheckRollup` that mixes `CheckRun` entries (named checks with a
|
|
21
|
+
`conclusion`) and legacy `StatusCheckRollup` entries (named contexts with a
|
|
22
|
+
`state`). Failing required checks are what the classifier reads to distinguish
|
|
23
|
+
`CHECKS_FAILED` from `DEQUEUED_BY_HUMAN` and a pending `CLEAN` wait, so a run
|
|
24
|
+
whose conclusion or state is a failure must surface here by name.
|
|
25
|
+
|
|
26
|
+
`TargetRepoProfile` and `Finding` are US3's preflight surface (plan.md § US1,
|
|
27
|
+
T035's slice-containment note): they live in this module because they are part
|
|
28
|
+
of the merge-queue component's model, whether or not US1's slice exercises them.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from dataclasses import dataclass
|
|
34
|
+
from enum import StrEnum
|
|
35
|
+
from typing import Any, Mapping
|
|
36
|
+
|
|
37
|
+
#: 049-US3: GitHub's spelling of "the target moved under this proposal". It is
|
|
38
|
+
#: spelled once, here, at the boundary where a stored record is rebuilt — never
|
|
39
|
+
#: in the classifier, which is the whole of FR-010.
|
|
40
|
+
_LEGACY_CONFLICT = "DIRTY"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# US2: one failing check's evidence as the recovery prompt and escalation page
|
|
44
|
+
# receive it. Lives here rather than in gh.py so the model's own dataclasses can
|
|
45
|
+
# annotate tuples of it without creating a circular import (gh.py already imports
|
|
46
|
+
# PrSnapshot from this module).
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class CheckFailure:
|
|
49
|
+
"""US2: one failing check's evidence.
|
|
50
|
+
|
|
51
|
+
`log_tail` is the last of `gh run view --log-failed` for the run id parsed
|
|
52
|
+
from `url`; `note` is empty when the tail was fetched, and states why it was
|
|
53
|
+
not fetched when `log_tail` is empty.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
name: str
|
|
57
|
+
url: str
|
|
58
|
+
log_tail: str
|
|
59
|
+
note: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# FR-004's closed vocabulary ---------------------------------------------------
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class QueueOutcome(StrEnum):
|
|
66
|
+
"""What one landing's queue history can say (plan.md § Data Model).
|
|
67
|
+
|
|
68
|
+
The interpreter's distinction between verified and merged is made with these
|
|
69
|
+
exact five members; a new member would change what a PASS means, so the set
|
|
70
|
+
is closed on purpose.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
MERGED = "MERGED"
|
|
74
|
+
CHECKS_FAILED = "CHECKS_FAILED"
|
|
75
|
+
CONFLICT = "CONFLICT"
|
|
76
|
+
DEQUEUED_BY_HUMAN = "DEQUEUED_BY_HUMAN"
|
|
77
|
+
STALLED = "STALLED"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class LandingState(StrEnum):
|
|
81
|
+
"""Where one landing stands: `PR_OPEN → ENQUEUED → MERGED | REJECTED | KILLED`.
|
|
82
|
+
|
|
83
|
+
`REJECTED` is the recovery-eligible rejection (checks_failed / conflict): it
|
|
84
|
+
may return to `ENQUEUED` after a successful recovery cycle (FR-006's bounded
|
|
85
|
+
cycle). `KILLED` is terminal — operator kill, dequeue-by-human, escalation
|
|
86
|
+
default, epic kill — and nothing leaves it.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
PR_OPEN = "PR_OPEN"
|
|
90
|
+
ENQUEUED = "ENQUEUED"
|
|
91
|
+
MERGED = "MERGED"
|
|
92
|
+
REJECTED = "REJECTED"
|
|
93
|
+
KILLED = "KILLED"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# The records ------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass(frozen=True)
|
|
100
|
+
class ObservedOutcome:
|
|
101
|
+
"""One entry in a landing's queue history — what the escalation quotes.
|
|
102
|
+
|
|
103
|
+
US2: `failing_checks` carries the names of the required checks the
|
|
104
|
+
classifier saw failing when the outcome is `CHECKS_FAILED`. It defaults to
|
|
105
|
+
`()` so pre-spec histories deserialize and replay without change.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
at: str
|
|
109
|
+
outcome: QueueOutcome
|
|
110
|
+
failing_checks: tuple[str, ...] = ()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass(frozen=True)
|
|
114
|
+
class Landing:
|
|
115
|
+
"""One node's landing, as workflow state on its `NodeRecord`.
|
|
116
|
+
|
|
117
|
+
`outcomes` is the queue history — every time the poll classified a terminal
|
|
118
|
+
(or stall) outcome, in order, so a human being paged can see the sequence
|
|
119
|
+
that led here. `enqueued_at` and the PR identity are set by the landing
|
|
120
|
+
phase; `recovery_cycles` counts how many times `REJECTED` has gone back to
|
|
121
|
+
`ENQUEUED` (bounded by `LandingConfig.max_recovery_cycles`, FR-006).
|
|
122
|
+
|
|
123
|
+
US2: `check_evidence` is the fetched (name, URL, log tail, note) record for
|
|
124
|
+
the latest `CHECKS_FAILED` outcome, used by the recovery prompt and the
|
|
125
|
+
escalation page. Default `()` keeps CONFLICT and pre-spec histories intact.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
node_id: str
|
|
129
|
+
branch: str
|
|
130
|
+
pr_number: int | None = None
|
|
131
|
+
pr_url: str | None = None
|
|
132
|
+
enqueued_at: str | None = None
|
|
133
|
+
outcomes: tuple[ObservedOutcome, ...] = ()
|
|
134
|
+
recovery_cycles: int = 0
|
|
135
|
+
state: LandingState = LandingState.PR_OPEN
|
|
136
|
+
check_evidence: tuple[CheckFailure, ...] = ()
|
|
137
|
+
#: US3: the commit the branch was pushed with when it was last enqueued.
|
|
138
|
+
#: Default `None` keeps pre-spec histories replayable (FR-009).
|
|
139
|
+
enqueued_tip: str | None = None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass(frozen=True)
|
|
143
|
+
class PrSnapshot:
|
|
144
|
+
"""What one poll of GitHub's queue saw — the classifier's only input from the world.
|
|
145
|
+
|
|
146
|
+
`state` is GitHub's PR state (`OPEN|CLOSED|MERGED`), kept as the string GitHub
|
|
147
|
+
sent rather than re-derived, because the classifier reads it verbatim (a
|
|
148
|
+
merged PR is GitHub's word, never inferred). `failing_required_checks` is the
|
|
149
|
+
subset of the rollup whose conclusion/state was a failure, by name; an empty
|
|
150
|
+
tuple is a rollup with nothing failing. `observed_at` is the workflow's clock
|
|
151
|
+
at the poll, so a stall is measured on the interpreter's time, not the
|
|
152
|
+
subprocess's.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
state: str
|
|
156
|
+
is_draft: bool
|
|
157
|
+
auto_merge_requested: bool
|
|
158
|
+
merge_state_status: str
|
|
159
|
+
merged_at: str | None
|
|
160
|
+
closed_at: str | None
|
|
161
|
+
failing_required_checks: tuple[str, ...]
|
|
162
|
+
observed_at: str
|
|
163
|
+
#: 049-US3: the conflict fact in the *factory's* vocabulary — the target has
|
|
164
|
+
#: moved under this proposal and it no longer applies as it stands.
|
|
165
|
+
#: `merge_state_status` stays as GitHub's spelling of the same observation,
|
|
166
|
+
#: kept as evidence an operator can read; nothing decides from it any more
|
|
167
|
+
#: (FR-010). Defaulted so a pre-spec history deserializes — `__post_init__`.
|
|
168
|
+
in_conflict: bool = False
|
|
169
|
+
|
|
170
|
+
def __post_init__(self) -> None:
|
|
171
|
+
"""Carry a pre-049 history's conflict across, once, at the boundary.
|
|
172
|
+
|
|
173
|
+
A record written before this spec recorded the conflict in the only
|
|
174
|
+
spelling there was, and histories holding one are replayed: a replay
|
|
175
|
+
reaching "keep polling" where the original run reached `CONFLICT` would
|
|
176
|
+
diverge from its own history — the defect class 032, 038 and 039 each
|
|
177
|
+
shipped. So the legacy spelling is read here, in the constructor, which
|
|
178
|
+
is also where `from_gh_json` lands every GitHub observation. The
|
|
179
|
+
classifier never sees it, and a forge that never heard of `DIRTY` is
|
|
180
|
+
unaffected.
|
|
181
|
+
"""
|
|
182
|
+
if not self.in_conflict and self.merge_state_status == _LEGACY_CONFLICT:
|
|
183
|
+
object.__setattr__(self, "in_conflict", True)
|
|
184
|
+
|
|
185
|
+
@classmethod
|
|
186
|
+
def from_gh_json(cls, payload: Mapping[str, Any], *, observed_at: str) -> "PrSnapshot":
|
|
187
|
+
"""One `gh pr view --json` payload, turned into a decision input.
|
|
188
|
+
|
|
189
|
+
`autoMergeRequest` is null for a PR nobody enqueued, and that must read as
|
|
190
|
+
"not requested", not as an error or as an unreadable field. The rollup
|
|
191
|
+
mixes named check runs and legacy status contexts; a failing one surfaces
|
|
192
|
+
by name, a passing or absent one does not. No value that is missing is
|
|
193
|
+
invented — a PR with no `mergedAt` simply has `merged_at=None`.
|
|
194
|
+
"""
|
|
195
|
+
auto_merge = payload.get("autoMergeRequest")
|
|
196
|
+
failing = tuple(
|
|
197
|
+
name
|
|
198
|
+
for name in _failing_check_names(payload.get("statusCheckRollup") or [])
|
|
199
|
+
)
|
|
200
|
+
return cls(
|
|
201
|
+
state=str(payload.get("state") or ""),
|
|
202
|
+
is_draft=bool(payload.get("isDraft")),
|
|
203
|
+
auto_merge_requested=auto_merge is not None,
|
|
204
|
+
merge_state_status=str(payload.get("mergeStateStatus") or ""),
|
|
205
|
+
merged_at=_nullable_str(payload.get("mergedAt")),
|
|
206
|
+
closed_at=_nullable_str(payload.get("closedAt")),
|
|
207
|
+
failing_required_checks=failing,
|
|
208
|
+
observed_at=observed_at,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@dataclass(frozen=True)
|
|
213
|
+
class TargetRepoProfile:
|
|
214
|
+
"""US3's preflight read of the target repo (plan.md § US1, T035).
|
|
215
|
+
|
|
216
|
+
`repo` is the slug exactly as `gh` reports it; `required_checks` is what the
|
|
217
|
+
queue will demand of a PR, `declared_gates` what the repo's own `factory.yaml`
|
|
218
|
+
names — the preflight compares them to decide whether a landing can ever be
|
|
219
|
+
enqueued. `findings` is the actionable list, and `passed` is their
|
|
220
|
+
conjunction: a profile that fails any finding cannot land.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
repo: str
|
|
224
|
+
default_branch: str
|
|
225
|
+
visibility: str
|
|
226
|
+
queue_enabled: bool
|
|
227
|
+
required_checks: tuple[str, ...]
|
|
228
|
+
declared_gates: tuple[str, ...]
|
|
229
|
+
findings: tuple["Finding", ...]
|
|
230
|
+
passed: bool
|
|
231
|
+
|
|
232
|
+
@classmethod
|
|
233
|
+
def from_readiness(
|
|
234
|
+
cls,
|
|
235
|
+
*,
|
|
236
|
+
repo: str,
|
|
237
|
+
reading: Any,
|
|
238
|
+
policy: Any,
|
|
239
|
+
declared_gates: tuple[str, ...],
|
|
240
|
+
findings: tuple["Finding", ...],
|
|
241
|
+
passed: bool,
|
|
242
|
+
) -> "TargetRepoProfile":
|
|
243
|
+
"""Build the report from a forge's two readings and a decided verdict.
|
|
244
|
+
|
|
245
|
+
The three descriptive fields above keep this record's own spelling — an
|
|
246
|
+
operator's report and every stored payload already read that way — so
|
|
247
|
+
the mapping onto them belongs with the record that owns them, not in a
|
|
248
|
+
judgment that may never spell them (049 US2, FR-006). `reading` and
|
|
249
|
+
`policy` are structural: `forge.py` imports `Finding` from here.
|
|
250
|
+
"""
|
|
251
|
+
return cls(
|
|
252
|
+
repo=repo,
|
|
253
|
+
default_branch=reading.default_branch,
|
|
254
|
+
visibility=reading.visibility,
|
|
255
|
+
queue_enabled=policy.gates_on_named_checks,
|
|
256
|
+
required_checks=tuple(policy.required_checks),
|
|
257
|
+
declared_gates=declared_gates,
|
|
258
|
+
findings=findings,
|
|
259
|
+
passed=passed,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@dataclass(frozen=True)
|
|
264
|
+
class Finding:
|
|
265
|
+
"""One preflight finding (US3): which check, whether it passed, and how to fix it.
|
|
266
|
+
|
|
267
|
+
`detail` is actionable — it names what to change, not just what is wrong — so
|
|
268
|
+
the operator reading a preflight report can go and change it without re-deriving
|
|
269
|
+
the problem from a slug.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
check: str
|
|
273
|
+
passed: bool
|
|
274
|
+
detail: str
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@dataclass(frozen=True)
|
|
278
|
+
class LandingConfig:
|
|
279
|
+
"""The operator's landing knobs — `EpicInput`'s new field (plan.md § US1).
|
|
280
|
+
|
|
281
|
+
Defaults are code defaults the operator overrides per epic; none of these is
|
|
282
|
+
a constant buried in the workflow. `merge_method` is passed verbatim to
|
|
283
|
+
`gh pr merge --auto --<method>` and must match a method the repo allows.
|
|
284
|
+
`stall_after_s` bounds how long a landing may sit queue-requested but
|
|
285
|
+
unanswered before the classifier calls it `STALLED`; `max_recovery_cycles` is
|
|
286
|
+
FR-006's "one bounded cycle".
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
merge_method: str = "squash"
|
|
290
|
+
poll_interval_s: int = 60
|
|
291
|
+
stall_after_s: int = 7200
|
|
292
|
+
max_recovery_cycles: int = 1
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# Helpers ----------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _nullable_str(value: Any) -> str | None:
|
|
299
|
+
if value is None:
|
|
300
|
+
return None
|
|
301
|
+
return str(value)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _failing_check_names(rollup: list[Any]) -> list[str]:
|
|
305
|
+
"""Names of rollup entries whose outcome is a failure, whatever their shape.
|
|
306
|
+
|
|
307
|
+
`gh pr view --json statusCheckRollup` returns a heterogeneous list: `CheckRun`
|
|
308
|
+
entries carry a `conclusion` (and `name`); legacy `StatusCheckRollup` entries
|
|
309
|
+
carry a `state` (and `context`). A run that is still in progress is not a
|
|
310
|
+
failure, and an entry with no failing signal is not named.
|
|
311
|
+
"""
|
|
312
|
+
names: list[str] = []
|
|
313
|
+
for entry in rollup:
|
|
314
|
+
if not isinstance(entry, dict):
|
|
315
|
+
continue
|
|
316
|
+
typename = entry.get("__typename")
|
|
317
|
+
if typename == "CheckRun":
|
|
318
|
+
if entry.get("conclusion") == "FAILURE":
|
|
319
|
+
names.append(str(entry.get("name") or ""))
|
|
320
|
+
elif typename in ("StatusCheckRollup", "StatusContext"):
|
|
321
|
+
if entry.get("state") == "FAILURE":
|
|
322
|
+
names.append(str(entry.get("context") or ""))
|
|
323
|
+
# A run whose name came back empty carries nothing the classifier can quote.
|
|
324
|
+
return [name for name in names if name]
|