@sjawhar/opencode-legion-envoy 1.18.0 → 1.18.1

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.
@@ -13875,7 +13875,7 @@ var dispatchToolSpecs = [
13875
13875
  },
13876
13876
  {
13877
13877
  name: "dispatch_doc_edit",
13878
- description: "Apply deterministic document edits, including retyping an identified paragraph into a schema-declared typed block. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
13878
+ description: "Apply deterministic document edits, including retyping an identified paragraph into a schema-declared typed block. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + "For replace, delete, and quote insert anchors, find text exactly as rendered: omit Markdown markers such as backticks or asterisks. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
13879
13879
  arguments: (z) => ({
13880
13880
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
13881
13881
  project: z.string().describe("Project key owning the document.").optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "1.18.0",
3
+ "version": "1.18.1",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
package/skills/AGENTS.md CHANGED
@@ -34,8 +34,11 @@ durable human question; replies come back to the worker's own session.
34
34
  ## Durable artifacts
35
35
 
36
36
  Phase handoffs are committed in lifecycle order: architect, plan, implement, test, and review.
37
+ Only the implementer pushes them: it and the merger act as the code-writing GitHub App, while
38
+ the planner, tester, reviewer, and architects act as the review App (`appRoleForLegionRole`,
39
+ `packages/daemon/src/daemon/github-apps.ts`), which holds no `contents` permission — their
40
+ handoff commits stay on the shared workspace's issue branch and ride the implementer's next push.
37
41
  A clean review ends with the `.legion/` deletion pushed by the implementer at the reviewer's
38
- direction (the review App holds no `contents` permission and cannot push), which the reviewer
39
- then approves; retro records its learning in
42
+ direction, which the reviewer then approves; retro records its learning in
40
43
  `docs/solutions/` and writes no handoff. GitHub comments and reviews carry the required Legion
41
44
  footer so the daemon can attribute artifacts to their worker session.
@@ -243,9 +243,10 @@ type EditOp = {
243
243
  };
244
244
  ```
245
245
 
246
- Target `replace` and `delete` by the document's plain text: inline-code and link text match without Markdown syntax, and a table-cell
247
- anchor is its cell text. Quote code-block contents without their Markdown fences. A quote must stay within one textblock; split changes
248
- that span separate blocks into separate operations.
246
+ Target `replace`, `delete`, and quote insert anchors by a block's text as rendered: write inline
247
+ code without backticks, bold without asterisks, and link text without link syntax. A table-cell
248
+ anchor is its cell text. Quote code-block contents without their Markdown fences. A quote must stay
249
+ within one textblock; split changes that span separate blocks into separate operations.
249
250
 
250
251
  `replace` requires `find` and `with`; `delete` requires `find`; `insert` requires `markdown` and exactly one of `after` or `before`. An
251
252
  insert anchor is a quote, `"start"`, `"end"`, or `"heading:Title"`. Ordinary inserts create a sibling block before or after the quote or
@@ -286,6 +287,11 @@ Should we ship the migration?
286
287
  :::
287
288
  ```
288
289
 
290
+ When a human answers a decision written as an ask block, the answer lives on that ask. Use
291
+ `dispatch_resolve_ask` when the decision is resolved without a human response, or preserve the
292
+ human's answer; never rewrite the question into its answer or blank its options. An edit that leaves
293
+ an ask block without a question or with a blank option is rejected with `INVALID_ASK_BLOCK`.
294
+
289
295
  ## Comments and suggestions
290
296
 
291
297
  Add feedback with:
@@ -163,6 +163,12 @@ in flight. On each child closure, re-scope open work, close obsolete work with a
163
163
  release the next wave only when it now makes sense. There is no inter-child dependency
164
164
  mechanism to encode.
165
165
 
166
+ Release admits nothing. A child never takes an admission slot or becomes a root tree of its
167
+ own: the daemon ignores a child's `todo` while your tree is live, and this `spawn_worker` is
168
+ what starts the child — the daemon writes its Dispatch status `in_progress` on the first
169
+ sub-architect spawn while the child is at `todo`. A released child with no sub-architect stays
170
+ at `todo` until you spawn one.
171
+
166
172
  ## 3. Children complete
167
173
 
168
174
  Treat `children-complete` as the edge into the end-game, not as a reason to close the
@@ -204,13 +210,13 @@ legion({
204
210
  op: "spawn_worker",
205
211
  issue: "LEGION-40",
206
212
  role: "implementer",
207
- task: "Run the legion-retro skill now. Capture durable learnings and post the issue comment; do not create a .legion handoff file."
213
+ task: "Run the legion-retro skill now. Capture durable learnings and post the retro message on the Dispatch issue with dispatch_message; do not create a .legion handoff file."
208
214
  })
209
215
  ```
210
216
 
211
217
  Wait for the implementer to report its durable retro result. Retro output is
212
- `docs/solutions/` plus an issue comment; it must not create a `.legion` file or rewrite
213
- the reviewer-approved head after cleanup.
218
+ `docs/solutions/` plus one `dispatch_message` on the issue; it must not create a `.legion`
219
+ file or rewrite the reviewer-approved head after cleanup.
214
220
 
215
221
  ## 6. Architect sign-off and merge
216
222
 
@@ -276,13 +282,15 @@ corresponding lifecycle procedure.
276
282
 
277
283
  | Wake | Procedure |
278
284
  | --- | --- |
285
+ | `child-adopted` | Payload `{type:"child-adopted", child, remaining}`. A child is now in your tree — one created under this issue (by you or a human), or one a daemon upgrade moved back into your tree from a root tree of its own (LEGION-57). If Dispatch shows it released **and open** — `todo` through `retro`, never `done`; `remaining` counts exactly those — and `legion state` shows no `roles` entry with `issue` = the child and `role: "architect"`, `spawn_worker` its architect now. An unreleased child waits for its wave; a `done` child is finished and gets nothing, whatever stray tree of its own `legion state` may still show. |
286
+ | `child-status` | Payload `{type:"child-status", child, from, to}`. Your child's Dispatch status changed. `to: "todo"` with no architect claim for the child (`legion state`) means it is released and unowned — your own `release_wave` echo, or a human's move — so `spawn_worker` its architect. `to: "backlog"` or `"icebox"` means the child was de-prioritised (a human's move, or your own `set_status`): a child has no tree of its own, so the daemon stops nothing on that move — tell its sub-architect (`envoy_publish` to its role topic) to finish the step in flight and park, or re-scope it; its finished workers idle-retire, and it resumes from its session on your next `spawn_worker` once the child is released again. Any other transition is information for re-scoping. |
279
287
  | `child-closed` | Read the child completion and remaining open children. Re-scope or close obsolete open work; release an appropriate next wave, or await `children-complete`. |
280
288
  | `children-complete` | Execute steps 3–4: parent integration verification; failures become a new child wave, success advances to review and retro. |
281
289
  | `child-reopened` | Treat the completion edge as reset. Reassess the reopened child and return the tree to children-in-flight; do not continue an already-started end-game. |
282
290
  | `design-approved` | Payload `{type:"design-approved"}`. A human approved the root spec document at its current version; the gate is open. Proceed to section 2. |
283
291
  | `design-changes-requested` | Payload `{type:"design-changes-requested", version, reason, author?}`. A human asked for changes to the root spec at `version`, for `reason`. Revise the spec, call `dispatch_request_approval` again, and stay parked; the gate is closed. |
284
292
  | `phase-complete` | Payload `{type:"phase-complete", issue, role, summary}`. May arrive live or via `catchup-overseer`'s `phaseCompletions`. Read the committed handoff for that phase, then spawn the next phase's owner, or `spawn_worker` on the same role again to resume it with corrections if the handoff shows unresolved gaps. A `reviewer` completion whose GitHub review is `CHANGES_REQUESTED` (the daemon returns the issue's Dispatch status to `in_progress` for this, on the reviewer's completion and again when you spawn the corrective implementer unless the daemon already knows the issue is `in_progress`) means `spawn_worker` the **implementer** again with the review findings — thread URLs and blocking items — as its task, then route back through tester and reviewer in order; never `spawn_worker` the reviewer directly off this wake and never proceed to retro on this verdict. A reviewer completion with an `APPROVED` review proceeds to retro (step 5). A `reviewer` completion after a conflict-forced rebase whose review body names an unchanged fingerprint is a confirmation, not a round: if retro already completed, `spawn_worker` the merger; otherwise resume the step you were on. |
285
- | `worker-queued` | Payload `{type:"worker-queued", issue, role}`. The deployment's worker cap is full; this role's spawn is queued. Do not respawn or retry — wait for `worker-started`. |
293
+ | `worker-queued` | Payload `{type:"worker-queued", issue, role}`. This role's task is queued for promotion — either the deployment's worker cap is full, or the live worker acknowledged the task without starting a turn and the daemon is retrying it (counted; the worker is replaced after three such failures, still with the same task). Do not respawn or retry — wait for `worker-started`. |
286
294
  | `worker-started` | Payload `{type:"worker-started", issue, role}`. A previously queued role has been promoted and is now running. Treat it exactly as a normal spawn: resume tracking that role's live session. |
287
295
  | `pr-ready` | Verify the live PR head, green status, and review state. Continue the review/retro/merger order only for that current head. |
288
296
  | `pr-review` | Payload `{type:"pr-review", state, author, body}`. Delivered to whichever role is currently active for the issue, falling back to you when no worker phase is active. Follows the same verdict rule as a reviewer's `phase-complete`: `state: "changes_requested"` sends the implementer back in with the review findings, then tester, then reviewer — never the reviewer again and never retro; that `spawn_worker` returns the issue to `in_progress` on its own (the daemon writes it for a corrective implementer whenever the PR's latest recorded review is changes requested, a human's after approval included), so you set nothing by hand; `state: "approved"` proceeds toward retro (step 5) once the step 6 integration/merge-gate conditions are met. `state: "approved"` on a rebased head whose body names an unchanged fingerprint is that confirmation: proceed to retro if it has not run, otherwise to the merger — never to a second retro or test round. |
@@ -290,7 +298,7 @@ corresponding lifecycle procedure.
290
298
  | `pr-merged` | Payload `{type:"pr-merged", pr, mergeCommitSha}`. The merge queue landed the PR. This is your cue for step 7: post the sign-off comment naming that merge commit and set the issue `done`. Nothing else follows a merge. |
291
299
  | `pr-closed-unmerged` | Decide from current scope whether to reopen the work, send a fresh implementer, or cancel it with a reason. Delegate the repository action to the responsible phase worker and keep ownership. |
292
300
  | `issue-comment` | Interpret the comment in the issue's design context. Answer it, adjust the plan, or relay it via `envoy_publish` to the responsible worker's role token; scope and product decisions remain with you. |
293
- | `catchup-overseer` | Verify its gates, child counts, and PR verdicts against current artifacts, then resume the applicable numbered lifecycle step. It is a current-state snapshot, not a raw-event replay. `gates[LEGION_TREE].open` is the design gate's current state: `true` means the root spec is approved at its current version and you may spawn; `false` (or no `open` key, meaning no gate is registered) means the sequence in section 1 still applies. For each entry in its `phaseCompletions` (`{issue, role, summary, at}`, phases that completed while you were not live), handle it exactly as a `phase-complete` wake. |
301
+ | `catchup-overseer` | Verify its gates, child counts, and PR verdicts against current artifacts, then resume the applicable numbered lifecycle step. It is a current-state snapshot, not a raw-event replay. `gates[LEGION_TREE].open` is the design gate's current state: `true` means the root spec is approved at its current version and you may spawn; `false` (or no `open` key, meaning no gate is registered) means the sequence in section 1 still applies. For each entry in its `phaseCompletions` (`{issue, role, summary, at}`, phases that completed while you were not live), handle it exactly as a `phase-complete` wake. Then compare `childCounts[LEGION_ISSUE].open` (the children not `done`) with `legion state` and Dispatch: any **open** released child — `todo` through `retro` — with no architect role claim gets `spawn_worker` for its architect, a `child-adopted` or `child-status` wake you missed while not live; a `done` child gets nothing, whether or not a lingering legacy tree of its own still shows in `legion state`. |
294
302
  | `worker-died` | Payload `{type:"worker-died", issue, role}`. The daemon probed and retried this role's worker through `MAX_LAUNCH_FAILURES` attempts and could not confirm a boot — never a raw-event replay or a silent revive. Reassess the work and `spawn_worker` again for the role (it resumes the same agent via `--resume` if a session file survived) or reassign it if the failure looks environmental, not agent-specific. |
295
303
  | `reopened` | Reopen the root lifecycle: inspect the reason and current artifacts, reassess scope and children, and resume at the first applicable numbered step. |
296
304
 
@@ -17,7 +17,8 @@ retrospective's durable output.
17
17
  1. Tester green and all code-review cycles finish.
18
18
  2. The implementer pushes the `.legion/` deletion at the reviewer's direction, and the reviewer
19
19
  approves that head.
20
- 3. Run this retro: commit durable learnings to `docs/solutions/` and post the issue comment.
20
+ 3. Run this retro: commit durable learnings to `docs/solutions/` and post the retro message on
21
+ the Dispatch issue.
21
22
  Retro writes **no `.legion` file**, so it never re-dirties the cleaned handoff tree.
22
23
  4. The merger verifies the tip is the approved head plus commits that change only
23
24
  `docs/solutions/` — `jj diff --from <approved-sha> --to <tip-sha> --summary`, quoted in READY —
@@ -66,18 +67,22 @@ date: YYYY-MM-DD
66
67
  status: active
67
68
  module: affected-module
68
69
  related_issues:
69
- - "owner/repo#123"
70
+ - "LEGION-123" # the Dispatch issue
71
+ - "owner/repo#456" # the pull request
70
72
  ---
71
73
  ```
72
74
 
73
75
  Commit the documentation on the existing issue branch, advance its existing bookmark, and push
74
- that branch. Do not create a replacement branch or bookmark. Then post an issue comment naming
75
- the documents and the one-to-three most useful takeaways. The comment must carry this revived
76
- implementer's structured attribution footer with `phase` set to `retro`:
76
+ that branch. Do not create a replacement branch or bookmark. Then post one Dispatch message on
77
+ the issue `issue` is your `LEGION_ISSUE`; Legion issues live on Dispatch, never on a GitHub
78
+ issue, and the `gh` shim refuses every GitHub-issue write — naming the documents and the
79
+ one-to-three most useful takeaways. The message must carry this revived implementer's structured
80
+ attribution footer with `phase` set to `retro`; the body is capped at 2,000 characters:
77
81
 
78
- ```bash
79
- legion gh -- issue comment <issue-number> \
80
- --body $'## Retro Complete
82
+ ```ts
83
+ dispatch_message({
84
+ issue: "<KEY>",
85
+ body: `## Retro Complete
81
86
 
82
87
  **Learnings documented in:**
83
88
  - docs/solutions/<path>.md
@@ -85,11 +90,11 @@ legion gh -- issue comment <issue-number> \
85
90
  **Key takeaways:**
86
91
  - <reusable lesson>
87
92
 
88
- <!-- legion: {"session":"<session-id>","phase":"retro"} -->' \
89
- --repo <owner>/<repo>
93
+ <!-- legion: {"session":"<session-id>","phase":"retro"} -->`,
94
+ })
90
95
  ```
91
96
 
92
- The issue comment and `docs/solutions/` commit are the only retro outputs. Never write a
97
+ The Dispatch message and the `docs/solutions/` commit are the only retro outputs. Never write a
93
98
  handoff, phase artifact, local feedback log, or completion label; `.legion/` was deleted before
94
99
  retro and nothing recreates it. Report completion with `legion handoff complete` alone (its
95
100
  summary: two sentences for the architect) — no `legion handoff write`.
@@ -100,7 +105,7 @@ Before returning, verify all of the following:
100
105
 
101
106
  - The reviewer cleanup commit remains below the retro documentation commit, and the reviewer's
102
107
  approval of that cleanup head stands: the merger accepts the approved head plus this commit.
103
- - The learning documents and issue comment both exist.
108
+ - The learning documents and the Dispatch message both exist.
104
109
  - No `.legion` file was created or modified by retro.
105
110
  - The fresh-eyes analysis was considered alongside the implementer's context.
106
111
  - The merger remains a subsequent step, not work performed by retro.
@@ -89,7 +89,7 @@ Concurrent issues have disjoint workspaces; only the currently active phase muta
89
89
  one. After you complete and go idle, treat `$LEGION_WORKSPACE` as read-only: you are kept
90
90
  alive to answer questions, not to keep editing. Do not create new commits, run
91
91
  `jj -R "$LEGION_WORKSPACE" new`, or touch tracked files once your own handoff is committed
92
- and pushed — a code change belongs to whichever phase is active now.
92
+ (and, for the implementer, pushed) — a code change belongs to whichever phase is active now.
93
93
 
94
94
  On every start, and especially after revival or re-creation, read the issue and then the
95
95
  committed predecessor handoffs in lifecycle order from `$LEGION_WORKSPACE/.legion/`:
@@ -120,10 +120,21 @@ transcript, the committed file wins: it is the copy that survived.
120
120
  - **Before pushing, check ancestry:** `jj -R "$LEGION_WORKSPACE" log -r 'ancestors(@, 5)'`
121
121
  — verify only your issue's commits are in the chain, not unrelated work.
122
122
 
123
- **Shared operation safety:** Never run `jj op restore` in a Legion workspace. It rewrites
124
- the shared operation log. If a mistake reaches that point, stop and send the owning
125
- architect the `jj -R "$LEGION_WORKSPACE" log` evidence; recover only through the approved,
126
- path-scoped workflow.
123
+ **Shared operation safety:** Every Legion issue workspace is a `jj workspace` of one shared
124
+ clone, so they all share one operation log: `jj undo`, `jj abandon`, and
125
+ `jj op restore|revert|abandon|undo` rewrite it for every tree at once (on 2026-09-12 one
126
+ worker's `jj undo` rewrote nine of another tree's commits). The extension refuses them in every
127
+ phase-worker pane before they run — a `bash` command in any position of a pipeline or `&&`
128
+ chain, with or without `-R`, judged on the whole argument list; `eval` code; and a `hub`
129
+ process start — from your own tool calls and from any `task` subagent you spawn (it runs in
130
+ your pane, against the same log), and a `bash` command whose quoted text merely mentions `jj`
131
+ with one of those words (a heredoc, an echo, a commit message) is refused too: write such text
132
+ with the `write` tool or say "operation-log rollback" instead. `jj restore <paths>`,
133
+ `jj op log`, and `jj op show` stay allowed. Recover forward only: a new commit
134
+ (`jj -R "$LEGION_WORKSPACE" new`) or `jj -R "$LEGION_WORKSPACE" restore <paths>` of files.
135
+ Anything else, stop and send the owning architect the `jj -R "$LEGION_WORKSPACE" log`
136
+ evidence; the architect decides, and an operator performs any operation-log restore with every
137
+ other tree paused.
127
138
 
128
139
  ## Phase work
129
140
 
@@ -138,9 +149,22 @@ Commit attribution is automatic: the extension exports a `JJ_CONFIG` overlay whe
138
149
  session starts, so every jj commit you make carries an `Omp-Session: <this-session-id>`
139
150
  trailer with no action from you. Do not add attribution trailers by hand.
140
151
 
141
- The jj configuration already supplies your phase's plus-addressed author and committer
142
- identity. Do not override Git identity configuration. Your session receives the credential
143
- capability it needs; invoke GitHub through the credential helper:
152
+ Your pane's environment already supplies your phase's author and committer identity
153
+ (`JJ_USER`/`JJ_EMAIL` and the Git author/committer variables, set by the daemon when it opened
154
+ the pane; the daemon also re-authors the workspace's working copy for your role at each
155
+ assignment, since `jj split`/`jj describe` keep its author). Never set or override
156
+ `user.name`/`user.email` in any jj or Git scope — not `jj config set`, not `--config`, not
157
+ `git config`: `--config` outranks the pane environment and would put the wrong App back on your
158
+ commits, and the repository-scoped jj config is one file shared by every issue workspace of the
159
+ clone. Before a push, check
160
+ `jj -R "$LEGION_WORKSPACE" log -r 'main@origin..@' -T 'author.email() ++ " | " ++ committer.email() ++ " " ++ description.first_line() ++ "\n"'`
161
+ shows your role's App in both columns **on every commit you made** — not on the whole list:
162
+ earlier phases' commits are legitimately authored by their own role's App, and a conflict-forced
163
+ rebase legitimately sets the committer of every rebased commit, other roles' included, to the
164
+ rebaser. A wrong identity on your own commit is a pane-environment problem to report to the
165
+ architect, not something to pin (`docs/solutions/legion/shared-main-repo-hazards-for-concurrent-issue-workspaces.md`,
166
+ Hazard 1). Your session receives the credential capability it needs; invoke GitHub through the
167
+ credential helper:
144
168
 
145
169
  ```bash
146
170
  legion gh -- <gh args…>
@@ -152,22 +176,29 @@ that execs `legion gh -- "$@"`, so `gh …` and `legion gh -- …` are the same
152
176
  redeems a fresh token from your session's grant — identity is supplied per call, never stored.
153
177
  Never run `gh auth login` or `gh auth setup-git`; there is no login state to create. The shim
154
178
  refuses `pr merge` (and a raw `gh api …/merge`): no worker role merges a pull request — the merge
155
- queue does, under its own authority. The credential reaches `legion` through the file
156
- `$LEGION_GRANT_FILE` names, written before each of your bash commands by the extension; never
157
- `cat`, `echo`, copy, or `export` it `legion credential`, `legion gh`, `jj git push`, and
158
- `legion handoff complete` read it themselves. The file is the pane's, not the command's: a `task`
159
- subagent, an `eval` subprocess, or a background job in your pane reads the grant your last bash
160
- command minted, so its `legion gh` or `jj git push` succeeds only within 60 seconds of that call
161
- and 403s afterwards a timing artifact, not a broken credential; run credentialed commands from
162
- your own bash calls.
179
+ queue does, under its own authority. It also refuses every GitHub-issue write — the `issue`
180
+ subcommand's `comment`, `create`, `edit`, `close`, `reopen`, `delete`, `pin`, `unpin`, `transfer`,
181
+ `lock`, `unlock`, and `develop`, and any raw `gh api` call to an `/issues` path whose method is not
182
+ GET (an explicit `-X`, or the POST that `-f`/`-F`/`--input` imply; pull-request conversation
183
+ comments live on that path too, so edit them with `gh pr comment`) printing
184
+ `Legion issues live on Dispatch; use dispatch_message or dispatch_comment on <your LEGION_ISSUE>`:
185
+ Legion never reads or writes a GitHub issue (LEGION-78). `pr comment`, `pr review`,
186
+ `api …/pulls/…`, `api graphql`, and issue reads are unaffected. The credential reaches `legion`
187
+ through the file `$LEGION_GRANT_FILE` names, written before each of your bash commands by the
188
+ extension; never `cat`, `echo`, copy, or `export` it — `legion credential`, `legion gh`,
189
+ `jj git push`, and `legion handoff complete` read it themselves. The file is the pane's, not the
190
+ command's: a `task` subagent, an `eval` subprocess, or a background job in your pane reads the
191
+ grant your last bash command minted, so its `legion gh` or `jj git push` succeeds only within 60
192
+ seconds of that call and 403s afterwards — a timing artifact, not a broken credential; run
193
+ credentialed commands from your own bash calls.
163
194
 
164
195
  ## GitHub PR comment attribution
165
196
 
166
197
  Append this exact structured footer to **every** pull-request comment and review that this
167
198
  phase posts on GitHub. It preserves session provenance on the artifact itself so work stays
168
199
  attributable to the session that produced it. Dispatch comments carry session provenance
169
- natively through their own `actor`/`origin` fields; this footer is only for GitHub PR
170
- artifacts:
200
+ natively through their own `actor`/`origin` fields; this footer is for GitHub PR artifacts and
201
+ for the retro's Dispatch message (`skills/legion-retro`):
171
202
 
172
203
  ```html
173
204
  <!-- legion: {"session":"<session-id>","phase":"<phase>"} -->
@@ -422,9 +453,12 @@ cd -- "$LEGION_WORKSPACE" && \
422
453
  jj -R "$LEGION_WORKSPACE" split -m "<phase>: record handoff" .legion/<phase>.json
423
454
  ```
424
455
 
425
- If the issue bookmark exists locally, advance it and push it with the provisioned
426
- credential helper. `--bookmark` also publishes the locally provisioned bookmark on its
427
- first push a bookmark not yet tracking a remote one is tracked automatically:
456
+ **Only the implementer pushes the issue branch.** It acts as the code-writing App
457
+ (`legion-implementer[bot]`, `appRoleForLegionRole` in `packages/daemon/src/daemon/github-apps.ts`),
458
+ the one App with `contents` permission (the merger acts as the same App but pushes nothing: it
459
+ verifies and publishes READY). If you are the implementer, advance the issue bookmark and push it
460
+ with the provisioned credential helper. `--bookmark` also publishes the locally provisioned
461
+ bookmark on its first push — a bookmark not yet tracking a remote one is tracked automatically:
428
462
 
429
463
  ```bash
430
464
  cd -- "$LEGION_WORKSPACE" && \
@@ -432,16 +466,23 @@ cd -- "$LEGION_WORKSPACE" && \
432
466
  jj -R "$LEGION_WORKSPACE" git push --bookmark legion/<KEY>
433
467
  ```
434
468
 
435
- Do not report phase completion until the write, existence check, and handoff commit
436
- succeed; when an issue branch exists, its push is also required. This is the committed
437
- copy the next phase reads after revival. It is removed once, at the end of a clean review: the
438
- implementer pushes that deletion at the reviewer's direction. No other phase removes it — and
439
- once it is gone (`jj -R "$LEGION_WORKSPACE" file list -r @- .legion` prints nothing on stdout;
440
- jj warns on stderr), this
441
- gate no longer applies: a later rebase, bare-gate re-check, confirmation, or retro writes no
442
- `.legion/<phase>.json`, commits no handoff, and reports with `legion handoff complete` alone
443
- (below). Recreating `.legion/` after its deletion changes the approved head and restarts the
444
- review loop this rule exists to end.
469
+ Every other role planner, tester, reviewer, architects acts as the review App
470
+ (`legion-reviewer[bot]`), which cannot push: the `split` above is your last step, and the commit
471
+ rides the implementer's next push (the corrective push after a review, or the final `.legion/`
472
+ deletion). A push from one of those roles is refused over git it reads
473
+ `remote: Repository not found.`; the REST API's form of the same refusal is
474
+ `Resource not accessible by integration` — and that refusal is expected, not a failure to report
475
+ or retry.
476
+
477
+ Do not report phase completion until the write, existence check, and handoff commit succeed —
478
+ and, for the implementer, until the push has too. This is the committed copy the next phase
479
+ reads after revival. It is removed once, at the end of a clean review: the implementer pushes
480
+ that deletion at the reviewer's direction. No other phase removes it — and once it is gone
481
+ (`jj -R "$LEGION_WORKSPACE" file list -r @- .legion` prints nothing on stdout; jj warns on
482
+ stderr), this gate no longer applies: a later rebase, bare-gate re-check, confirmation, or retro
483
+ writes no `.legion/<phase>.json`, commits no handoff, and reports with `legion handoff complete`
484
+ alone (below). Recreating `.legion/` after its deletion changes the approved head and restarts
485
+ the review loop this rule exists to end.
445
486
 
446
487
  ## Completion: report to the architect, then stay
447
488