@sjawhar/opencode-legion-envoy 6.10.1 → 6.12.0
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.
- package/package.json +1 -1
- package/skills/AGENTS.md +1 -1
- package/skills/dispatch/references/issues.md +1 -1
- package/skills/legion-architect/SKILL.md +27 -22
- package/skills/legion-controller/SKILL.md +48 -47
- package/skills/legion-retro/SKILL.md +15 -9
- package/skills/legion-worker/SKILL.md +151 -154
- package/skills/legion-worker/references/conflicts-and-rewrites.md +33 -31
- package/skills/legion-worker/references/merge-gate.md +60 -24
- package/skills/legion-worker/references/pr-body.md +21 -18
- package/skills/legion-worker/references/review-threads.md +68 -92
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# The merge gate: review, retro, READY, and the production check
|
|
2
2
|
|
|
3
3
|
Part of `skill://legion-worker`. Read it when you are the reviewer submitting a review or an
|
|
4
|
-
approval, the implementer recording the production check, or the
|
|
5
|
-
path it cites is in sjawhar/legion.
|
|
4
|
+
approval, the implementer disarming a withdrawn READY or recording the production check, or the
|
|
5
|
+
merger building READY and submitting the merge. Every path it cites is in sjawhar/legion.
|
|
6
6
|
|
|
7
7
|
The order, in full: the tester's evidence green → the reviewer's approval of the head →
|
|
8
|
-
retro → the merger's READY → the
|
|
8
|
+
retro → the merger's READY → the merge → the implementer's production check. The approved
|
|
9
9
|
head carries the issue's handoffs, `.legion/<issue>/`; retro's last commit removes them from the
|
|
10
|
-
head
|
|
10
|
+
head that merges (dispatch://LEGION-605), since a squash merge commits that head merged into
|
|
11
11
|
the default branch and nothing there reads a handoff, and READY refuses a head that still
|
|
12
12
|
carries them. After the approval, only retro's commits leave it standing on their own: those that
|
|
13
13
|
change only `docs/solutions/`, and that removal (*Retro*, below). A conflict-forced merge goes back
|
|
@@ -25,7 +25,7 @@ the head voids it.
|
|
|
25
25
|
Skip the `Thermo` line entirely on a docs-only PR. Submit **one review per round** —
|
|
26
26
|
`REQUEST_CHANGES` when any correctness finding stands, otherwise `APPROVE` of the head you
|
|
27
27
|
reviewed — always named by SHA — carrying every inline comment in that single
|
|
28
|
-
call: `
|
|
28
|
+
call: `gh api --method POST repos/{owner}/{repo}/pulls/{number}/reviews --input body.json`
|
|
29
29
|
with `commit_id`, `event` (`REQUEST_CHANGES` or `APPROVE`), `body` (with the
|
|
30
30
|
Legion footer), and a `comments[]` array of `{path, line, side, body}`, one entry per
|
|
31
31
|
finding — never one `pr review` call per finding (each submission fires a `pr-review` wake).
|
|
@@ -48,9 +48,10 @@ before you submit it, since an approval stands only on green checks and GitHub c
|
|
|
48
48
|
once the head moves, and a verdict that settles red there makes the round's decision a request for
|
|
49
49
|
changes naming the failing checks, unless only review workflows the project declares
|
|
50
50
|
(`projects.<KEY>.review_workflows`) are red on their own findings: then you answer their threads,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
name the ones you accepted to the implementer, who resolves them with its own `gh` as the pull
|
|
52
|
+
request's author, and re-run the failed run once they are resolved, as your role prompt says, and
|
|
53
|
+
approve once it passes. Any other red required workflow is a failing check like any other. A
|
|
54
|
+
request for changes does not wait, since it stands whatever CI says and the issue leaves
|
|
54
55
|
reviewing with it. The verdict is of the checks and workflows the base branch requires, the set
|
|
55
56
|
READY checks: red when one of them failed, and never red for a check the base branch does not
|
|
56
57
|
require. A required check that was cancelled, or that the head's checks
|
|
@@ -73,37 +74,72 @@ review posted without a completion leaves the issue in reviewing until you finis
|
|
|
73
74
|
- **Retro brings the PR body's path-derived content up to date before its push.** Whatever the
|
|
74
75
|
repository's instructions derive from the pull request's changed paths (a checklist named for
|
|
75
76
|
each class of path, read by a required check), retro recomputes for the whole diff at its head
|
|
76
|
-
and writes into the live body before
|
|
77
|
+
and writes into the live body before its push (`skill://legion-retro`), since the merger
|
|
77
78
|
reports a stale body rather than rewriting it. A body edit changes no commit, so the approval
|
|
78
79
|
stands.
|
|
79
80
|
|
|
80
81
|
## The merger
|
|
81
82
|
|
|
82
|
-
- The merger
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
exits 1 (report the thread to the architect instead), then proves that rule with two commands.
|
|
86
|
-
First `cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" git fetch && jj -R
|
|
83
|
+
- The merger resolves no review thread: the reviewer's approval already judged them, and READY
|
|
84
|
+
requires no thread state beyond it. It proves the head is the approved head plus retro's commits
|
|
85
|
+
with two commands. First `cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" git fetch && jj -R
|
|
87
86
|
"$LEGION_WORKSPACE" diff --from <approved-sha> --to <tip-sha> --summary`, whose output is quoted
|
|
88
87
|
in READY; then the same with the one fileset `'~(docs/solutions | .legion/<issue>)'` appended,
|
|
89
88
|
which must print nothing (jj unions separate path arguments, so two of them leave nothing out).
|
|
90
|
-
READY
|
|
91
|
-
architect, which has it move the issue back to `retro`), and publishes a pull request a person
|
|
92
|
-
already merged unread (`packages/daemon/internal/prompts/go/merger.md`). *The READY packet* is
|
|
89
|
+
*The READY packet* is
|
|
93
90
|
`READY #<n> at <current sha> (approved at <approved sha>) for <KEY> (<pr url>)`
|
|
94
91
|
(the shape `packages/daemon/internal/prompts/roles/merger.md` defines), then the PR body's
|
|
95
92
|
`Outcome:` line and its `Not proven / risk:` value — every bullet under that label joined with
|
|
96
93
|
`; ` on the one READY line, or `none` — quoted from the `## For the reviewer` block at that same
|
|
97
94
|
head (or one line saying the body carries no brief — the packet still goes out), then the
|
|
98
|
-
`--summary` output and the PR body's gate facts. The
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
`--summary` output and the PR body's gate facts. The READY packet names both the implementer's
|
|
96
|
+
and tester's `E2E` lines; a missing one is reported to the architect instead of completing.
|
|
97
|
+
- READY is `handoff_complete` with `ready: true` and the packet as its `summary`; the merger runs
|
|
98
|
+
no pre-flight of its own, and its completion reports the commit the workspace stands on. READY's
|
|
99
|
+
checks run in the daemon, which reads the pull request's head on GitHub and refuses, before
|
|
100
|
+
recording anything: `READY_HEAD_CARRIES_HANDOFFS` while the head still carries `.legion/<issue>/`
|
|
101
|
+
(tell the architect, which has you move the issue back to `retro` with `request_backward_move`,
|
|
102
|
+
so the implementer's retro removes it; a refusal saying GitHub's read of the directory failed is
|
|
103
|
+
GitHub's failure, not the head's: complete again); `READY_HEAD_CONFLICTS` while the pull
|
|
104
|
+
request conflicts with its base, named — GitHub can neither merge it nor start pull_request CI
|
|
105
|
+
for it, so the implementer brings the base into the branch with a forward merge; and
|
|
106
|
+
`READY_CHECKS_NOT_GREEN` unless every check the base branch requires has succeeded at that head
|
|
107
|
+
and every workflow its rulesets require has a passing run there — no result for a check, or no
|
|
108
|
+
run of a workflow, means that head's push skipped CI when it should not have; a check still
|
|
109
|
+
running means wait; a failed one is a finding. Report the refusal to the architect; never
|
|
110
|
+
push a commit to make CI run. On success the daemon posts the packet as a `dispatch message` on
|
|
111
|
+
the issue, publishes it to the project's merge queue role when one is set, and says on the issue
|
|
112
|
+
when that role has no live holder; the answer's `note` says when READY was published on a pull
|
|
113
|
+
request a person already merged, whose head can no longer change, or on a base that requires no
|
|
114
|
+
check, which the packet itself also says.
|
|
115
|
+
- **The merger submits the merge the moment READY is accepted.** In the same turn, before anything
|
|
116
|
+
else, it runs `gh pr merge <n> -R <owner>/<repo> --auto --squash --match-head-commit <head>` with
|
|
117
|
+
its pane's plain `gh`: `<n>` the pull request number, `<head>` the full sha of the current head
|
|
118
|
+
READY named. A repository with a merge queue enqueues the pull request; one without arms
|
|
119
|
+
auto-merge; one whose rules are already satisfied merges at once. `--match-head-commit` has
|
|
120
|
+
GitHub refuse the request when the head has moved since READY read it. The repository's required
|
|
121
|
+
reviews and checks decide when it lands: the merger makes no merge by hand and bypasses no
|
|
122
|
+
rule. It then tells the architect with `envoy_publish` to its encoded role token what the
|
|
123
|
+
command answered — enqueued, auto-merge enabled, or merged — and the head. On any READY refusal
|
|
124
|
+
it submits nothing, since an armed request would merge through the gate the refusal names; a
|
|
125
|
+
READY the design gate refused is submitted later, on the architect's word, once the gate opens,
|
|
126
|
+
and that turn runs only `gh`. A `gh pr merge` GitHub refuses is reported to the architect with
|
|
127
|
+
its message; a person can still merge after READY. The packet's second line records the step,
|
|
128
|
+
`Merge: submitted on acceptance with gh pr merge <n> -R <owner>/<repo> --auto --squash --match-head-commit <head>`.
|
|
129
|
+
- **A withdrawn READY is disarmed by the implementer.** When READY is withdrawn (the head's CI
|
|
130
|
+
turned red, or it conflicts with its base, after READY, which sends the issue back to
|
|
131
|
+
`implementing`), a merge queue drops the pull request on the red check, but auto-merge stays
|
|
132
|
+
enabled across pushes, and `--match-head-commit` was checked only when it was enabled. So the
|
|
133
|
+
implementer, at the start of that implementing round, runs
|
|
134
|
+
`gh pr merge <n> -R <owner>/<repo> --disable-auto` before any push. A pull request still in a
|
|
135
|
+
merge queue is removed with `gh api graphql -f query='mutation($id:ID!){dequeuePullRequest(input:{id:$id}){mergeQueueEntry{id}}}' -F id=<pull request node id>`
|
|
136
|
+
(`--disable-auto` dequeues nothing). When nothing is armed — the merger's submission was refused,
|
|
137
|
+
or a queue already dropped it — the command exits 1 with "Can't disable auto-merge for this pull
|
|
138
|
+
request.": run it anyway and take that answer as nothing to disarm.
|
|
103
139
|
|
|
104
|
-
## After the
|
|
140
|
+
## After the merge
|
|
105
141
|
|
|
106
|
-
- **After
|
|
142
|
+
- **After the merge, the implementer verifies in production.**
|
|
107
143
|
The daemon starts the implementer again once the merge lands; the implementer watches the
|
|
108
144
|
deploy slot that carries the merge to `production-apply` (or the equivalent publish step),
|
|
109
145
|
drives the changed path in production through the user's own access path, and records the
|
|
@@ -26,15 +26,11 @@ later phase keeps it current rather than replacing it:
|
|
|
26
26
|
|
|
27
27
|
**CI:** `Tests` run <run-id> — jobs lint, typecheck, test all success at <head-sha>; `PR Title` run <run-id> — job pr-title success at <head-sha>.
|
|
28
28
|
|
|
29
|
-
**Threads:** <n> resolved, 0 unresolved. Each
|
|
30
|
-
- Thread <id>: fixed in <commit-sha> — <one line>.
|
|
31
|
-
- Thread <id>: not a defect — <reason>.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
resolved <thread URL> — the Legion reviewer's acceptance of a bot's thread
|
|
35
|
-
left open <thread URL> — newest reply by <login> is not an acceptance
|
|
36
|
-
left open <thread URL> — newest reply by <login> is an unsubmitted draft in a pending review
|
|
37
|
-
left open <thread URL> — newest reply by <login> is not its opener's or the Legion reviewer's acceptance
|
|
29
|
+
**Threads:** <n> resolved, 0 unresolved. Each answered and resolved individually by the implementer, the pull request's author, never in bulk (the reviewer names the bot threads it accepted; it resolves none itself):
|
|
30
|
+
- Thread <node id>: fixed in <commit-sha> — <one line>; resolved at <head-sha>.
|
|
31
|
+
- Thread <node id>: not a defect — <reason>; resolved at <head-sha>.
|
|
32
|
+
- Thread <node id>: accepted by the reviewer — <its reason, one line>; resolved at <head-sha>.
|
|
33
|
+
- Thread <node id>: still open — <what remains>; unresolved at <head-sha>.
|
|
38
34
|
|
|
39
35
|
**Thermo:** `ce-simplify-code` once at <head-sha>: <0 applied | applied → new head <sha>>; thermonuclear pair at the reviewed head <sha>:
|
|
40
36
|
<verdict>. (omitted entirely on a docs-only PR — there is no code for either pass, so neither runs)
|
|
@@ -61,7 +57,7 @@ ledger the reviewer and merger check against GitHub. The implementer writes `Out
|
|
|
61
57
|
`Change`, `Proven by`, and `Size` when the pull request opens, and keeps them true after every
|
|
62
58
|
push; `Outcome` is a sentence a user of the repository would recognise, never "fix bug" or a file
|
|
63
59
|
name, and `Why` ends with the Dispatch key, never a URL. The reviewer writes `Look at first` and
|
|
64
|
-
`Not proven / risk` at each round, into the live body (`
|
|
60
|
+
`Not proven / risk` at each round, into the live body (`gh api
|
|
65
61
|
repos/{owner}/{repo}/pulls/{number} --jq .body`, edit, then `--method PATCH ... -F body=@body.md`);
|
|
66
62
|
`Not proven / risk` copies every claim recorded as unproven before READY — the tester's
|
|
67
63
|
`failures`, the reviewer's own review, any proof-check comment already on the pull request —
|
|
@@ -88,19 +84,26 @@ and the tester's proof below are both this proof.
|
|
|
88
84
|
|
|
89
85
|
- **The implementer proves the change before its phase completes, and writes the `E2E (implementer)` line when the pull request opens.**
|
|
90
86
|
The proof is the one defined above. It goes into `.legion/<issue>/implement.json` as the required `proof`
|
|
91
|
-
array
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
array, and into the PR body, because the reviewer and the merger verify facts on GitHub and never
|
|
88
|
+
from a handoff. Nothing checks the handoff's shape when you complete — `handoff_complete` reports
|
|
89
|
+
the pushed commit carrying the file, and the daemon reads nothing in it — so an implement handoff
|
|
90
|
+
without a `proof` array, or with a blank or whitespace-only field in one, reaches the tester as it
|
|
91
|
+
is, and the tester records it as a failure against you.
|
|
94
92
|
- **The tester verifies the implementer's proof and adds its own `E2E (tester)` line.** It re-runs
|
|
95
93
|
the implementer's command or drives the same surface independently, and records the verdict in
|
|
96
|
-
`.legion/<issue>/test.json` as `implementerProof` (`{verdict, how}`).
|
|
94
|
+
`.legion/<issue>/test.json` as `implementerProof` (`{verdict, how}`). The tester runs the
|
|
95
|
+
application, not CI again. Try to break the changed surface — wrong and boundary inputs, the
|
|
96
|
+
second and the concurrent run, the party it must refuse — judge whether the implementer's tests
|
|
97
|
+
would catch the ways it breaks, writing the red test for one that none would, and
|
|
98
|
+
read CI for everything static: lint, types and the unit and integration suites are CI's, cited
|
|
99
|
+
from the `CI` line, and no CI lane is rerun as evidence.
|
|
97
100
|
A test handoff whose predecessor carried no proof is a test failure, not a gap for the tester to fill:
|
|
98
101
|
record it in `failures` with `implementerProof.verdict: "rejected"`, complete the phase with
|
|
99
102
|
`verdict: "fail"`, and the daemon returns the issue to the implementer — the agent that developed the change owns
|
|
100
|
-
proving it (
|
|
101
|
-
|
|
102
|
-
above — as the `E2E (tester)` line and the `proof` array
|
|
103
|
-
|
|
103
|
+
proving it (a rejected verdict, or `failed > 0`, always comes with the failure recorded in
|
|
104
|
+
`failures`). Otherwise, add your own proof before completing — a proof as defined
|
|
105
|
+
above — as the `E2E (tester)` line and the `proof` array a test handoff that reports no failure
|
|
106
|
+
carries. A code path whose first execution is after merge — a
|
|
104
107
|
deploy workflow's inline step, a post-merge helper, a production-only resource — is untested
|
|
105
108
|
until the implementer has executed it against a devN stack; if no surface can reach it, the
|
|
106
109
|
tester names that missing surface as the blocker instead of passing the phase. Environment or
|
|
@@ -1,101 +1,77 @@
|
|
|
1
1
|
# Review threads
|
|
2
2
|
|
|
3
|
-
Part of `skill://legion-worker`. Read it when you reply
|
|
4
|
-
or
|
|
5
|
-
|
|
6
|
-
only when a review workflow the project declares is red on a bot's findings.
|
|
7
|
-
Every path it cites is in sjawhar/legion.
|
|
3
|
+
Part of `skill://legion-worker`. Read it when you reply on or resolve a review thread, list a pull
|
|
4
|
+
request's threads, or adjudicate a bot's finding: the implementer after every push that answers a
|
|
5
|
+
review, and the reviewer on every re-review. Every path it cites is in sjawhar/legion.
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
thread gets its own line naming the fixing commit or the reason it isn't a defect. The
|
|
11
|
-
reviewer answers each thread it opened, and each thread a bot opened that is none of Legion's
|
|
12
|
-
role Apps, with exactly one of `Accepted: fixed in <commit> — <one line>`,
|
|
13
|
-
`Accepted: not a defect — <reason>`, or `Still open: <what remains>`; nothing else is an
|
|
14
|
-
acceptance, and nobody replies after an `Accepted:` (any later reply that is not itself an
|
|
15
|
-
`Accepted:` — the opener's own follow-up included — leaves the thread open, because resolution
|
|
16
|
-
considers only the newest comment). The review App can reply on a thread but cannot resolve it:
|
|
17
|
-
GitHub grants resolving a review thread to the pull request's author, and the implementer opens
|
|
18
|
-
every Legion pull request (`docs/site/src/content/docs/legion/running-legion.md`, "The two
|
|
19
|
-
GitHub Apps"). In the reviewer's pane (`LEGION_ROLE=reviewer`), `legion threads resolve` asks
|
|
20
|
-
the daemon instead (`POST /legion/v1/threads/resolve`), which resolves as the implementer's App
|
|
21
|
-
only the threads a bot outside Legion's role Apps opened whose newest submitted comment is the
|
|
22
|
-
reviewer's `Accepted:`, on the pull request of the reviewer's own issue, and prints the same
|
|
23
|
-
lines; the reviewer never holds the implementer's token. A thread whose newest comment is a
|
|
24
|
-
draft in the implement App's pending review (the implementer's or the merger's: GitHub shows it
|
|
25
|
-
to that App alone, as which the daemon reads) is left open and never named: the command prints
|
|
26
|
-
only how many there are (`<n> unresolved threads hold the implement App's pending draft and
|
|
27
|
-
were left open`) and exits 1, as it does on a refusal. Report that count to the architect before
|
|
28
|
-
you spend your one re-run of the failed workflow; the architect sends the issue back so the
|
|
29
|
-
implementer submits or discards its pending review. Once the review is submitted, the
|
|
30
|
-
implementer's reply is the thread's newest comment, which leaves it open: answer the thread
|
|
31
|
-
again, then run the command again. Once the review is discarded, your `Accepted:` is the newest
|
|
32
|
-
comment again, and running the command again closes the thread.
|
|
33
|
-
When `LEGION_GRANT_FILE` or `LEGION_GRANT` is set, use `legion threads resolve --pr <number> --repo <owner>/<repo>`.
|
|
34
|
-
When neither is set, add `--gh` to that command, which applies the fallback's rule below through
|
|
35
|
-
your own `gh`; where no `legion` command is installed, use `gh api graphql` with the session's
|
|
36
|
-
GitHub credential and the fallback below.
|
|
37
|
-
In a Legion pane, the **implementer** runs the command after every push that answers a review
|
|
38
|
-
and before its `handoff_complete`, and pastes its output, stamped with the head it just pushed, into
|
|
39
|
-
the `Threads` section. The output is then recorded against the head the reviewer will read, and
|
|
40
|
-
nothing reads thread state before the implementer's completion. The command resolves each
|
|
41
|
-
unresolved thread whose newest submitted comment is the opener's own `Accepted:` reply. On a
|
|
42
|
-
thread a bot account opened that is none of Legion's role Apps (the daemon names them, keyed by
|
|
43
|
-
App role), the Legion reviewer's `Accepted:` also closes it. GitHub cannot tell a CI bot, which
|
|
44
|
-
never accepts, from a person whose `gh` is routed to an App, so the reviewer adjudicates such a
|
|
45
|
-
finding, and it may accept one an App-routed person raised. The subject of a finding never
|
|
46
|
-
closes it: the implementer's `Fixed in <commit>: …` or `Declined: …` answers a thread and closes
|
|
47
|
-
none. A thread either Legion App opened, a reviewer's finding included, still needs its opener's
|
|
48
|
-
`Accepted:`. It makes one `resolveReviewThread` per
|
|
49
|
-
thread, prints `resolved <url> — <whose acceptance>` (its opener's, or the Legion reviewer's on a
|
|
50
|
-
bot's thread, so the ledger shows which) or `left open <url> — newest reply by <login> is …`
|
|
51
|
-
naming why, and exits 1 naming the thread's URL and GitHub's message when GitHub refuses one.
|
|
7
|
+
Three facts govern every thread, whoever opened it.
|
|
52
8
|
|
|
53
|
-
|
|
54
|
-
|
|
9
|
+
- **Reply on each thread you answer, one disposition reply per thread.** Every open review thread
|
|
10
|
+
gets its own reply naming the fixing commit or the reason it isn't a defect: the implementer's
|
|
11
|
+
`Fixed in <commit>: <one line>` or `Declined: <reason>`; the reviewer's reasons in its own words —
|
|
12
|
+
the commit that fixed it, why it is not a defect, or what still stands. No word in a reply
|
|
13
|
+
resolves anything, and nothing reads a reply for a magic form: a thread is resolved only by the
|
|
14
|
+
resolution below, after its reply.
|
|
15
|
+
- **Resolve each thread you have answered, one thread per call, never in bulk, never a thread you
|
|
16
|
+
have not read.** GitHub grants `resolveReviewThread` to the pull request's author, and the
|
|
17
|
+
implementer opens every Legion pull request (`docs/site/src/content/docs/legion/running-legion.md`,
|
|
18
|
+
"The two GitHub Apps"), so the implementer resolves every thread, its own and the ones the
|
|
19
|
+
reviewer accepted:
|
|
20
|
+
- The **implementer**, after every push that answers a review and before its `handoff_complete`,
|
|
21
|
+
resolves each thread it answered with its own `gh`, one thread per call:
|
|
55
22
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
nodes {
|
|
60
|
-
id isResolved
|
|
61
|
-
opener: comments(first: 1) { nodes { author { __typename login } } }
|
|
62
|
-
newest: comments(last: 1) { nodes { author { __typename login } body state } }
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
```
|
|
23
|
+
```bash
|
|
24
|
+
gh api graphql -f query='mutation($id:ID!){resolveReviewThread(input:{threadId:$id}){thread{isResolved}}}' -F id=<thread node id>
|
|
25
|
+
```
|
|
66
26
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
27
|
+
A thread a bot opened (a CI bot's, or an App-routed person's) it answers the same way; the
|
|
28
|
+
reviewer adjudicates the finding. The bot threads the reviewer accepted, which the reviewer
|
|
29
|
+
names to the implementer by node id (below), the implementer resolves with the same call, one
|
|
30
|
+
per thread, as soon as the message reaches it or as part of answering that review, and records
|
|
31
|
+
in the PR body's `Threads` section too; never one the reviewer did not name. When GitHub refuses
|
|
32
|
+
a resolution, report the thread and GitHub's message to the architect with `envoy_publish`,
|
|
33
|
+
which opens a `dispatch ask` for a human to resolve it by hand; never skip it silently.
|
|
34
|
+
- The **reviewer** cannot resolve a thread as its own App: GitHub refuses the review App
|
|
35
|
+
`resolveReviewThread` on the implementer's pull request, so it replies, and names the threads
|
|
36
|
+
it accepted to the implementer, who resolves them. List the pull request's threads:
|
|
73
37
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
```
|
|
38
|
+
```bash
|
|
39
|
+
gh api graphql -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100){nodes{id isResolved comments(last:1){nodes{author{login} body}}}}}}}' -F o=<owner> -F r=<repo> -F n=<number>
|
|
40
|
+
```
|
|
79
41
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
42
|
+
Reply on each thread you answer, then tell the implementer the node ids of exactly the threads
|
|
43
|
+
you accepted — a bot's findings you adjudicated — never one you have not read: an
|
|
44
|
+
`envoy_publish` to the implementer's role topic (it is live on the issue and answers while you
|
|
45
|
+
wait; `skill://legion-worker` "Asking another role" has the topic), or your review body, which
|
|
46
|
+
it reads at its next round. The implementer resolves exactly those with its own `gh`. GitHub
|
|
47
|
+
cannot tell a CI bot from a person whose `gh` is routed to an App, so such a thread may be a
|
|
48
|
+
person's finding: accept it only when the finding itself is settled (a genuine Minor is settled
|
|
49
|
+
by being judged Minor), since the resolution says you judged it.
|
|
50
|
+
- **A reviewer who disagrees with a resolution replies and unresolves it.** Resolution is a
|
|
51
|
+
judgment, not a ledger entry: a thread the implementer resolved whose finding still stands gets
|
|
52
|
+
the reviewer's reply saying what stands and is unresolved, and a finding the reviewer cannot
|
|
53
|
+
accept becomes its own, with a `REQUEST_CHANGES` naming it.
|
|
86
54
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
55
|
+
## The PR body's `Threads` section
|
|
56
|
+
|
|
57
|
+
The implementer records each thread it answered in the PR body's `Threads` section
|
|
58
|
+
(`skill://legion-worker/references/pr-body.md`), stamped with the head it just pushed: the thread's
|
|
59
|
+
id, its disposition (`fixed in <commit-sha> — <one line>` or `not a defect — <reason>`), and the
|
|
60
|
+
head that answered it. The reviewer verifies thread state in `gh api graphql`, never from that
|
|
61
|
+
section: it is the implementer's record, and GitHub is the fact.
|
|
62
|
+
|
|
63
|
+
## The reviewer, on a re-review
|
|
64
|
+
|
|
65
|
+
When you re-review after a corrective push, answer every thread you opened, and every thread a bot
|
|
66
|
+
opened that is none of Legion's role Apps, with one reply in your own words. A bot's finding you
|
|
67
|
+
cannot accept becomes your own: say what stands and request changes. Approve once each of those
|
|
68
|
+
threads carries your answer and no finding stands, read in `gh api graphql` — quote those answers
|
|
69
|
+
in the approval. Your approval never waits on a resolution: resolution is the pull request author's
|
|
70
|
+
App's, except when a review workflow the project declares (`projects.<KEY>.review_workflows`) is
|
|
71
|
+
red on its findings. Such a workflow starts again only on a push, so neither a reply nor a
|
|
72
|
+
resolution re-runs it, and it passes on a re-run only once its threads are resolved: answer its
|
|
73
|
+
threads, name the ones you accepted to the implementer with `envoy_publish` to its role topic, and
|
|
74
|
+
read the listing above until each shows `isResolved` true before you push your handoff, so the run
|
|
75
|
+
that push starts reads the threads as the implementer left them; then re-run the failed run once,
|
|
76
|
+
as your role prompt says, before you approve. The merger resolves nothing: READY requires no thread
|
|
77
|
+
state beyond what your approval already judged.
|