1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//! `tm pr …` — deterministic pull-request open and merge-queue gates (#6653).
//!
//! Why: two procedures `version-control` runs on every delivery are written
//! down only as prose in `tm-workflow.md`, and both are mechanical. Opening a
//! PR means checking the nine-field body contract, the exact attribution
//! footer, the shipped `--assignee @me --label trusty-mpm --label
//! ws/<session>` defaults, and whether the diff owes a changelog fragment —
//! four judgments an agent re-derives each time and can silently skip.
//! Merging means walking a fixed stop-condition table over three `gh` reads,
//! where skipping one under time pressure is the recorded failure mode. A
//! command that returns an exit code removes both.
//!
//! What: [`run`] dispatches the three verbs to [`open`], [`merge`] and
//! [`queue_check`].
//! Every `gh` call in this module goes through [`GhRunner`], whose production
//! implementation is [`RealGhRunner`] over `trusty_common::gh::GhCommand` —
//! this workspace's single `gh` entry point (#5475), carrying the
//! project-scoped identity binding. A second `Command::new("gh")` here would
//! be a defect. The trait is also the test seam: every test in the sibling
//! `tests.rs` drives a scripted fake, so nothing in this module needs a
//! network or a live `gh`.
//!
//! Exit codes are the verb surface:
//!
//! | verb | 0 | 1 | 2 | 3 |
//! |---|---|---|---|---|
//! | `open` | PR created (or `--dry-run` printed the argv) | — | a pre-flight check failed; `gh` was never called | the PR EXISTS; some metadata could not be applied (#7869) |
//! | `merge` | squash-merged, or auto-merge armed | a hold signal or a missing attribution footer refused it; `gh pr merge` was never called | usage or `gh` error | — |
//! | `queue-check` | every listed PR is mergeable | at least one is blocked | usage or `gh` error | — |
//!
//! Test: the sibling `tests.rs`; `cli_parses_pr_*` in `tests.rs`.
pub
pub
pub
pub
pub
pub
pub
use Context as _;
use cratePrCmd;
/// Exit code: the verb's contract holds.
pub const EXIT_OK: i32 = 0;
/// Exit code: `queue-check` found at least one blocked PR.
pub const EXIT_BLOCKED: i32 = 1;
/// Exit code: a pre-flight check failed, or the invocation was wrong.
pub const EXIT_CHECK_FAILED: i32 = 2;
/// Exit code: the PR EXISTS but some of its metadata could not be applied.
///
/// Why (#7869): 2 means "a check failed and `gh` was never called", which is
/// the opposite of what a partial apply is — `tm pr open` reported exit 2 for
/// PR #7918 after creating it, so the caller read it as "no PR" and went
/// hunting with `gh pr list --head`. A code of its own keeps "nothing
/// happened" and "the PR is open but incomplete" distinguishable.
/// Test: `pr_7646_a_failed_edit_names_the_field_and_retries_per_field`,
/// `pr_7869_a_create_retry_that_also_fails_exits_non_zero`.
pub const EXIT_PARTIAL: i32 = 3;
/// One completed `gh` invocation, as this module needs it.
///
/// Why: `gh pr view --json` on a missing PR and `gh pr create` on a rejected
/// push both exit non-zero, and the two want different handling, so the
/// runner hands back the triple rather than deciding.
/// What: exit success, stdout, stderr.
/// Test: `FakeGh` in `tests.rs` constructs these directly.
pub
/// The seam every `gh` call in `tm pr` goes through.
///
/// Why: the verdicts these verbs compute are pure functions of `gh` output,
/// so hiding the spawn behind a trait makes the whole decision table testable
/// against canned JSON — no network, no live `gh`, no PR to create.
/// What: one method taking a fully-formed argv (without the `gh` itself).
/// Test: `FakeGh` in `tests.rs` scripts responses by argv prefix.
pub
/// Production [`GhRunner`] over `trusty_common::gh::GhCommand`.
///
/// Why: `GhCommand` is this workspace's single `gh` entry point (#5475) — it
/// decides which binary, renders the argv for errors, classifies a missing
/// binary, and never goes through a shell. Spawning `gh` here directly would
/// be a second implementation of that capability, which the repo's "common
/// entry point" rule exists to prevent.
/// What: overlays the active project's GitHub identity (`tm`'s `gh_identity`,
/// #1265) onto the child, then runs blocking and maps the outcome to
/// [`GhRun`].
/// Test: exercised live (see the `tm pr queue-check` evidence on the PR); the
/// logic it feeds is covered against [`GhRunner`] fakes.
pub
/// Let the shared cleanup engine spawn `gh` through this module's runner
/// (#7275).
///
/// Why: `trusty_mpm::core::pr_cleanup` defines its own `gh` seam so the daemon
/// can drive it too, and `tm pr` already has a runner carrying the active
/// project's GitHub identity. Bridging the two is one impl; building a second
/// identity-bound runner for the CLI would be the duplication the
/// common-entry-point rule forbids.
/// Test: the engine's decisions are covered against fakes in
/// `core::pr_cleanup::tests`; this bridge is exercised live.
/// Resolve `owner/repo` for the verbs that need the REST API.
///
/// Why: branch-protection lives at `repos/<owner>/<repo>/branches/<base>/
/// protection`, which needs the pair spelled out — unlike `gh pr view`, which
/// infers it from the cwd's remote. Asking `gh` for it keeps the answer
/// consistent with whichever remote every other call in the run resolved.
/// What: returns `--repo` verbatim when given, else `gh repo view --json
/// nameWithOwner --jq .nameWithOwner`.
/// Test: `repo_slug_prefers_the_flag`, `repo_slug_falls_back_to_gh`.
pub
/// Build an owned argv from string slices.
pub
/// Drive one `tm pr` invocation to its exit code.
///
/// Why: both verbs answer with an exit code a caller branches on, so — like
/// `tm wait` (#5843) — exiting here rather than returning a `Result` to
/// `main` is what makes that guarantee hold for the error paths too.
/// What: builds the production `gh` runner, runs the requested verb, and
/// exits. An error from either verb prints to stderr and exits
/// [`EXIT_CHECK_FAILED`].
/// Test: the verb bodies are unit-tested against [`GhRunner`] fakes; this is
/// the exit-code wrapper.
pub async !
/// The fallible body of [`run`], split out so every error leaves one way.
async