@sjawhar/opencode-legion-envoy 1.18.0 → 1.19.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/dist/src/server.js +25 -10
- package/package.json +1 -1
- package/skills/AGENTS.md +8 -2
- package/skills/dispatch/SKILL.md +9 -3
- package/skills/legion-architect/SKILL.md +26 -11
- package/skills/legion-retro/SKILL.md +34 -17
- package/skills/legion-worker/SKILL.md +129 -38
package/dist/src/server.js
CHANGED
|
@@ -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(),
|
|
@@ -14006,6 +14006,15 @@ var HANDOFF_SCHEMA_VERSION = 1;
|
|
|
14006
14006
|
var HANDOFF_PHASES = ["architect", "plan", "implement", "test", "review"];
|
|
14007
14007
|
var isoTimestamp = string2().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
|
14008
14008
|
var handoffPhase = _enum2(HANDOFF_PHASES);
|
|
14009
|
+
var nonEmpty = string2().min(1);
|
|
14010
|
+
var proofSchema = object({
|
|
14011
|
+
criterion: nonEmpty,
|
|
14012
|
+
surface: nonEmpty,
|
|
14013
|
+
command: nonEmpty,
|
|
14014
|
+
observed: nonEmpty,
|
|
14015
|
+
headSha: nonEmpty,
|
|
14016
|
+
negativeControl: nonEmpty
|
|
14017
|
+
}).passthrough();
|
|
14009
14018
|
var routingHintsSchema = object({
|
|
14010
14019
|
skipArchitect: boolean2().optional(),
|
|
14011
14020
|
complexity: _enum2(["trivial", "small", "medium", "large"]).optional(),
|
|
@@ -14025,7 +14034,7 @@ var architectSchema = baseHandoffSchema.extend({
|
|
|
14025
14034
|
subIssues: array(string2()).optional(),
|
|
14026
14035
|
routingHints: routingHintsSchema,
|
|
14027
14036
|
concerns: array(string2()).optional()
|
|
14028
|
-
});
|
|
14037
|
+
}).passthrough();
|
|
14029
14038
|
var requiredSkillsSchema = object({
|
|
14030
14039
|
implement: array(string2()).optional(),
|
|
14031
14040
|
test: array(string2()).optional(),
|
|
@@ -14039,24 +14048,30 @@ var planSchema = baseHandoffSchema.extend({
|
|
|
14039
14048
|
concerns: array(string2()).optional(),
|
|
14040
14049
|
workflowRecommendation: string2().optional(),
|
|
14041
14050
|
requiredSkills: requiredSkillsSchema
|
|
14042
|
-
});
|
|
14051
|
+
}).passthrough();
|
|
14043
14052
|
var implementSchema = baseHandoffSchema.extend({
|
|
14044
14053
|
phase: literal("implement"),
|
|
14045
14054
|
filesChanged: array(string2()).optional(),
|
|
14055
|
+
proof: array(proofSchema).min(1),
|
|
14046
14056
|
trickyParts: array(string2()).optional(),
|
|
14047
14057
|
deviations: array(string2()).optional(),
|
|
14048
14058
|
openQuestions: array(string2()).optional(),
|
|
14049
14059
|
subPlanningNeeded: boolean2().optional(),
|
|
14050
14060
|
discoveredComplexity: array(string2()).optional(),
|
|
14051
14061
|
suggestedSubWorkers: number2().optional()
|
|
14052
|
-
});
|
|
14062
|
+
}).passthrough();
|
|
14053
14063
|
var testSchema = baseHandoffSchema.extend({
|
|
14054
14064
|
phase: literal("test"),
|
|
14055
14065
|
passed: number2().optional(),
|
|
14056
14066
|
failed: number2().optional(),
|
|
14057
14067
|
failures: array(object({ criterion: string2(), evidence: string2() }).passthrough()).optional(),
|
|
14068
|
+
implementerProof: object({ verdict: _enum2(["verified", "rejected"]), how: nonEmpty }).passthrough(),
|
|
14069
|
+
proof: array(proofSchema).min(1).optional(),
|
|
14058
14070
|
documentationFeedback: string2().optional(),
|
|
14059
14071
|
observations: array(string2()).optional()
|
|
14072
|
+
}).passthrough().refine((handoff) => (handoff.failures?.length ?? 0) > 0 || (handoff.failed ?? 0) > 0 || (handoff.proof?.length ?? 0) > 0, {
|
|
14073
|
+
path: ["proof"],
|
|
14074
|
+
message: "a passing test handoff needs the tester's own production-like proof"
|
|
14060
14075
|
});
|
|
14061
14076
|
var reviewSchema = baseHandoffSchema.extend({
|
|
14062
14077
|
phase: literal("review"),
|
|
@@ -14065,13 +14080,13 @@ var reviewSchema = baseHandoffSchema.extend({
|
|
|
14065
14080
|
minor: number2().optional(),
|
|
14066
14081
|
verdict: _enum2(["approved", "changes_requested"]).optional(),
|
|
14067
14082
|
keyFindings: array(object({ severity: string2(), file: string2(), description: string2() }).passthrough()).optional()
|
|
14068
|
-
});
|
|
14083
|
+
}).passthrough();
|
|
14069
14084
|
var phaseHandoffSchema = discriminatedUnion("phase", [
|
|
14070
|
-
architectSchema
|
|
14071
|
-
planSchema
|
|
14072
|
-
implementSchema
|
|
14073
|
-
testSchema
|
|
14074
|
-
reviewSchema
|
|
14085
|
+
architectSchema,
|
|
14086
|
+
planSchema,
|
|
14087
|
+
implementSchema,
|
|
14088
|
+
testSchema,
|
|
14089
|
+
reviewSchema
|
|
14075
14090
|
]);
|
|
14076
14091
|
var handoffMessageSchema = object({
|
|
14077
14092
|
from: handoffPhase,
|
package/package.json
CHANGED
package/skills/AGENTS.md
CHANGED
|
@@ -34,8 +34,14 @@ 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
|
|
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.
|
|
45
|
+
The implement handoff carries the implementer's own production-like proof and the test handoff the
|
|
46
|
+
tester's verdict on it plus the tester's own; `legion handoff write` refuses a payload the phase's
|
|
47
|
+
schema rejects and names the field. Retro's message goes to the Dispatch issue (`dispatch_message`), never a GitHub issue.
|
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -243,9 +243,10 @@ type EditOp = {
|
|
|
243
243
|
};
|
|
244
244
|
```
|
|
245
245
|
|
|
246
|
-
Target `replace` and
|
|
247
|
-
|
|
248
|
-
|
|
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,19 +210,21 @@ 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
|
|
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
|
|
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
|
|
|
217
223
|
Sign off only when scope is fully met, integration evidence is current, corrective work
|
|
218
224
|
is complete, review is clean, retro completed, and no necessary work was silently
|
|
219
|
-
deferred. Make the sign-off comment explicit about that evidence.
|
|
225
|
+
deferred. Make the sign-off comment explicit about that evidence. Sign-off also requires the
|
|
226
|
+
implementer's production report: a `Production:` line that names what was driven, how, what was
|
|
227
|
+
observed, and the merge commit — never a `pending` one, and never a staging pass.
|
|
220
228
|
|
|
221
229
|
Preserve this order exactly:
|
|
222
230
|
|
|
@@ -236,6 +244,11 @@ Preserve this order exactly:
|
|
|
236
244
|
merges. The merge queue merges under its own authority and the repository's own rules
|
|
237
245
|
(branch protection, CODEOWNERS); whether a human must approve first is that repository's
|
|
238
246
|
setting, not Legion's, and you never ask for or wait on such an approval.
|
|
247
|
+
5. the merge queue merges; you then `spawn_worker` the **implementer** once more with the
|
|
248
|
+
production-check task. It drives the changed path in production through the user's own access
|
|
249
|
+
path and records what it saw on the pull request and on this issue. Close only after the implementer's production report exists.
|
|
250
|
+
A defect it finds is a corrective child issue of this tree, not a note on a closed one; a deploy
|
|
251
|
+
the implementer cannot perform is its action ask, and the issue waits for it.
|
|
239
252
|
|
|
240
253
|
What returns the tree to review: a changed diff — a commit above the approved head that
|
|
241
254
|
touches anything outside `docs/solutions/`, or a rebase whose fingerprint (the `legion-worker`
|
|
@@ -258,11 +271,11 @@ and the implementer's and merger's runs of the command close every accepted one.
|
|
|
258
271
|
|
|
259
272
|
## 7. Close
|
|
260
273
|
|
|
261
|
-
After the merge result and sign-off are recorded, post the
|
|
262
|
-
through the Legion write surface:
|
|
274
|
+
After the merge result, the implementer's production report, and sign-off are recorded, post the
|
|
275
|
+
sign-off and close this issue through the Legion write surface:
|
|
263
276
|
|
|
264
277
|
```text
|
|
265
|
-
dispatch_comment({ issue: "LEGION-40", body: "<sign-off: scope, integration evidence, review, retro, and
|
|
278
|
+
dispatch_comment({ issue: "LEGION-40", body: "<sign-off: scope, integration evidence, review, retro, merge, and the implementer's production report>" })
|
|
266
279
|
legion({ op: "set_status", issue: "LEGION-40", status: "done" })
|
|
267
280
|
```
|
|
268
281
|
|
|
@@ -276,21 +289,23 @@ corresponding lifecycle procedure.
|
|
|
276
289
|
|
|
277
290
|
| Wake | Procedure |
|
|
278
291
|
| --- | --- |
|
|
292
|
+
| `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. |
|
|
293
|
+
| `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
294
|
| `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
295
|
| `children-complete` | Execute steps 3–4: parent integration verification; failures become a new child wave, success advances to review and retro. |
|
|
281
296
|
| `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
297
|
| `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
298
|
| `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
|
-
| `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}`.
|
|
299
|
+
| `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. An `implementer` completion that follows the merge is its production report: read the record on the pull request and the issue, then run step 7 — the issue is already at `retro`, the daemon writes no status for this completion, and you set `done` yourself. A `tester` completion whose handoff carries `implementerProof.verdict: "rejected"`, or a failure naming the production-like proof, goes back to the **implementer** with that finding — never forward to the reviewer, and never by supplying the proof from another role. A worker that reports no surface reaches the changed path gets a child issue in this tree (infrastructure, tooling, or a skill) and a resume once it lands; that report is never a reason to advance the phase. |
|
|
300
|
+
| `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
301
|
| `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
302
|
| `pr-ready` | Verify the live PR head, green status, and review state. Continue the review/retro/merger order only for that current head. |
|
|
288
303
|
| `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. |
|
|
289
304
|
| `pr-blocked` | Payload `{type:"pr-blocked", pr, attempts}`. `attempts` counts heads pushed onto a red verdict that changed something outside `.legion/` — handoff-only pushes (`.legion/` paths only) never count; a push the daemon cannot classify (a listener without `changed_paths`, a list capped at 100, a push listing no commits) does. Published once per exhausted count, not on every later red verdict for that count. Read the failed CI evidence and recovery attempts. Assign a focused implementer or corrective child, then return it through testing and review; do not treat the blocked PR as final. |
|
|
290
|
-
| `pr-merged` | Payload `{type:"pr-merged", pr, mergeCommitSha}`. The merge queue landed the PR.
|
|
305
|
+
| `pr-merged` | Payload `{type:"pr-merged", pr, mergeCommitSha}`. The merge queue landed the PR. `spawn_worker` the **implementer** with the production-check task naming that merge commit (it resumes the same agent; a retired role has no live holder, so never `envoy_publish` for this). Its `phase-complete` is what brings you to step 7: verify the record on the pull request and this issue first, then sign off naming it and set the issue `done`. A merge is not the close. |
|
|
291
306
|
| `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
307
|
| `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. |
|
|
308
|
+
| `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
309
|
| `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
310
|
| `reopened` | Reopen the root lifecycle: inspect the reason and current artifacts, reassess scope and children, and resume at the first applicable numbered step. |
|
|
296
311
|
|
|
@@ -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
|
|
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 —
|
|
@@ -27,6 +28,10 @@ retrospective's durable output.
|
|
|
27
28
|
the change in production and records it on the PR and the issue (Sami, 2026-09-13, verbatim:
|
|
28
29
|
"the agent that developed it should be responsible for testing in production"). The
|
|
29
30
|
architect's sign-off waits for that record.
|
|
31
|
+
The record is the pull request's `Production:` line, one pull-request comment, and a
|
|
32
|
+
`dispatch_message` on the issue, each naming what was driven, how, what was observed, and the
|
|
33
|
+
merge commit. A defect the production check finds becomes a corrective child issue of the same tree,
|
|
34
|
+
owned by the architect and implemented by the same implementer; the parent stays open until it lands.
|
|
30
35
|
|
|
31
36
|
Retro's commit sits above the reviewer's approved head and the approval stands: a commit that
|
|
32
37
|
changes only `docs/solutions/` does not void it, and the tree goes from retro to the merger —
|
|
@@ -39,10 +44,12 @@ before step 3. The design gate is not a substitute for review and retro.
|
|
|
39
44
|
## Two perspectives
|
|
40
45
|
|
|
41
46
|
1. Re-read the issue, its acceptance criteria, the PR, test evidence, and review evidence.
|
|
42
|
-
Confirm the PR's `E2E` line
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
Confirm the PR carries both proofs: the implementer's own `E2E (implementer)` line and the tester's `E2E (tester)` line,
|
|
48
|
+
each naming a production-like surface (a scratch daemon, a smoke rig, a sandbox repository, a devN
|
|
49
|
+
stack, staging, or a local stack with real migrations), a command or run id, an observation, a head
|
|
50
|
+
SHA, and a negative control. If either is missing, or links only a unit suite, the retro's first
|
|
51
|
+
durable learning is that gap and the issue goes back — to the implementer for its own proof, to the
|
|
52
|
+
tester for the tester's — before `READY`.
|
|
46
53
|
Do not rebase or create a new branch; work on the existing issue branch.
|
|
47
54
|
2. Spawn one fresh-eyes subagent. Give it the issue and PR, ask it to inspect the diff and
|
|
48
55
|
return concrete reusable learnings, and require it to return analysis rather than edit files.
|
|
@@ -66,18 +73,23 @@ date: YYYY-MM-DD
|
|
|
66
73
|
status: active
|
|
67
74
|
module: affected-module
|
|
68
75
|
related_issues:
|
|
69
|
-
- "
|
|
76
|
+
- "LEGION-123" # the Dispatch issue
|
|
77
|
+
- "owner/repo#456" # the pull request
|
|
70
78
|
---
|
|
71
79
|
```
|
|
72
80
|
|
|
73
81
|
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
|
|
75
|
-
the
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
that branch. Do not create a replacement branch or bookmark. Then post one Dispatch message on
|
|
83
|
+
the issue — `issue` is your `LEGION_ISSUE`; Legion issues live on Dispatch, never on a GitHub
|
|
84
|
+
issue, and the `gh` shim refuses every GitHub-issue write — naming the documents, the
|
|
85
|
+
one-to-three most useful takeaways, the two proofs you read, and the production check that
|
|
86
|
+
follows the merge. The message must carry this revived implementer's structured
|
|
87
|
+
attribution footer with `phase` set to `retro`; the body is capped at 2,000 characters:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
dispatch_message({
|
|
91
|
+
issue: "<KEY>",
|
|
92
|
+
body: `## Retro Complete
|
|
81
93
|
|
|
82
94
|
**Learnings documented in:**
|
|
83
95
|
- docs/solutions/<path>.md
|
|
@@ -85,11 +97,15 @@ legion gh -- issue comment <issue-number> \
|
|
|
85
97
|
**Key takeaways:**
|
|
86
98
|
- <reusable lesson>
|
|
87
99
|
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
**Proofs read:** implementer <surface/command>, tester <surface/command>.
|
|
101
|
+
|
|
102
|
+
**Production check:** <what the implementer will drive after the merge, or the action ask it opened>
|
|
103
|
+
|
|
104
|
+
<!-- legion: {"session":"<session-id>","phase":"retro"} -->`,
|
|
105
|
+
})
|
|
90
106
|
```
|
|
91
107
|
|
|
92
|
-
The
|
|
108
|
+
The Dispatch message and the `docs/solutions/` commit are the only retro outputs. Never write a
|
|
93
109
|
handoff, phase artifact, local feedback log, or completion label; `.legion/` was deleted before
|
|
94
110
|
retro and nothing recreates it. Report completion with `legion handoff complete` alone (its
|
|
95
111
|
summary: two sentences for the architect) — no `legion handoff write`.
|
|
@@ -100,7 +116,8 @@ Before returning, verify all of the following:
|
|
|
100
116
|
|
|
101
117
|
- The reviewer cleanup commit remains below the retro documentation commit, and the reviewer's
|
|
102
118
|
approval of that cleanup head stands: the merger accepts the approved head plus this commit.
|
|
103
|
-
- The learning documents and
|
|
119
|
+
- The learning documents and the Dispatch message both exist (never a GitHub issue comment).
|
|
120
|
+
- Both proofs were read, and any gap in either is recorded as a learning.
|
|
104
121
|
- No `.legion` file was created or modified by retro.
|
|
105
122
|
- The fresh-eyes analysis was considered alongside the implementer's context.
|
|
106
123
|
- 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:**
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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.
|
|
156
|
-
|
|
157
|
-
`
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
170
|
-
|
|
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>"} -->
|
|
@@ -232,9 +263,16 @@ left open <thread URL> — newest reply by <login> is not an acceptance
|
|
|
232
263
|
**Thermo:** thermonuclear-deep-review + thermonuclear-code-quality run once at <head-sha>:
|
|
233
264
|
<verdict>. (omitted entirely on a docs-only PR — no thermo pass runs)
|
|
234
265
|
|
|
235
|
-
**E2E:** <surface> — ran `<command or run id>`, observed <result>, at head <sha>.
|
|
266
|
+
**E2E (implementer):** <surface> — ran `<command or run id>`, observed <result>, at head <sha>.
|
|
236
267
|
Negative control: <deliberately broken input> → <refusal or failure observed>.
|
|
237
268
|
|
|
269
|
+
**E2E (tester):** <surface> — ran `<command or run id>`, observed <result>, at head <sha>.
|
|
270
|
+
Negative control: <deliberately broken input> → <refusal or failure observed>.
|
|
271
|
+
Verified the implementer's proof by <re-running its command | driving the same surface independently>.
|
|
272
|
+
|
|
273
|
+
**Production:** <what was checked in production, how, what was observed> — merge commit <sha>.
|
|
274
|
+
(written by the implementer after the merge lands; `pending <what is missing>` until then)
|
|
275
|
+
|
|
238
276
|
**Fast-follow:** <one named cleanup item and where it will land>, or "none".
|
|
239
277
|
|
|
240
278
|
**Chain:** stacked on <base bookmark> frozen at <sha> / not stacked.
|
|
@@ -278,12 +316,26 @@ Negative control: <deliberately broken input> → <refusal or failure observed>.
|
|
|
278
316
|
- **No deferrals.** Sami, 2026-09-11, verbatim: "My rule is no deferrals." The `Fast-follow:`
|
|
279
317
|
field names naming, duplication, or wording cleanup only; anything that changes behaviour,
|
|
280
318
|
hides an error, or breaks a gate lands in this PR.
|
|
281
|
-
- The
|
|
319
|
+
- **The implementer proves the change before its phase completes, and writes the `E2E (implementer)` line when the pull request opens.**
|
|
320
|
+
The proof is the changed behaviour exercised on the surface a user reaches it through — a
|
|
321
|
+
scratch daemon, a smoke rig, a sandbox repository, a real browser, a devN stack, a local stack
|
|
322
|
+
with real migrations — with the exact command or run id, what was observed, the head SHA, and
|
|
323
|
+
one negative control. The same proof goes into `.legion/implement.json` as its required `proof`
|
|
324
|
+
array (`legion handoff write --phase implement` refuses a payload without one and names the
|
|
325
|
+
field), and into the PR body, because the reviewer and the merger verify facts on GitHub and
|
|
326
|
+
never from a handoff. A unit or integration test is a regression lock, never proof of a
|
|
327
|
+
criterion.
|
|
328
|
+
- **The tester verifies the implementer's proof and adds its own `E2E (tester)` line.** It re-runs
|
|
329
|
+
the implementer's command or drives the same surface independently, records the verdict in
|
|
330
|
+
`.legion/test.json` as `implementerProof` (`{verdict, how}`), and records its own proof beside
|
|
331
|
+
it. A test handoff whose predecessor carried no proof is a test failure, not a gap for the tester to fill:
|
|
332
|
+
record it in `failures` with `implementerProof.verdict: "rejected"`, complete the phase, and let
|
|
333
|
+
the architect return the issue to the implementer — the agent that developed the change owns
|
|
334
|
+
proving it. The tester's own proof names the real surface a user reaches the criterion
|
|
282
335
|
through, the exact command or run id, what was observed, the head SHA, and one negative
|
|
283
|
-
control — a deliberately broken input and the refusal or failure it produced.
|
|
284
|
-
integration test is a regression lock, never proof of a criterion. The surface is
|
|
336
|
+
control — a deliberately broken input and the refusal or failure it produced. The surface is
|
|
285
337
|
**production-like** — a devN stack, staging, or a local stack with real migrations, one that
|
|
286
|
-
has the resource the change touches — and
|
|
338
|
+
has the resource the change touches — and each `E2E` line carries a **link** to that run,
|
|
287
339
|
screenshot, or e2e; the merge queue does not approve a user-facing change without it, and a
|
|
288
340
|
green unit suite is not it. Sami, 2026-09-13, verbatim: "They need to test everything in a
|
|
289
341
|
production-like environment before merging, and it is the agent that develops the feature
|
|
@@ -309,6 +361,7 @@ Negative control: <deliberately broken input> → <refusal or failure observed>.
|
|
|
309
361
|
- The reviewer verifies the `CI`, `Threads`, and `E2E` facts against GitHub directly —
|
|
310
362
|
never from a handoff — then runs `task(agent="thermonuclear-deep-review")` and
|
|
311
363
|
`task(agent="thermonuclear-code-quality")` once at that head and records the verdict.
|
|
364
|
+
Approval is refused while either `E2E (implementer)` or `E2E (tester)` is missing: `REQUEST_CHANGES` naming the missing line.
|
|
312
365
|
Skip the `Thermo` line entirely on a docs-only PR. Submit **one review per round** —
|
|
313
366
|
`REQUEST_CHANGES` when any correctness finding stands, otherwise `COMMENT` while the head
|
|
314
367
|
still carries `.legion/`; `APPROVE` only for a head that carries no `.legion/` — the head
|
|
@@ -350,8 +403,9 @@ Negative control: <deliberately broken input> → <refusal or failure observed>.
|
|
|
350
403
|
`no file changes above the approved head`); then the same with `'~docs/solutions'` appended,
|
|
351
404
|
which must print nothing. Then it publishes `READY #<n> at <tip-sha>` naming the approved
|
|
352
405
|
head, the tip, and that summary, plus the PR body's gate facts, to the merge queue's role
|
|
353
|
-
(`notifications.role.pr-queue`) with `envoy_publish`. The
|
|
354
|
-
|
|
406
|
+
(`notifications.role.pr-queue`) with `envoy_publish`. The READY packet names both the
|
|
407
|
+
implementer's and the tester's `E2E` lines; a missing one is reported to the architect instead
|
|
408
|
+
of published. The merger never merges; the queue merges under its own authority.
|
|
355
409
|
- **After the queue merges, the implementer verifies in production.** Sami, 2026-09-13,
|
|
356
410
|
verbatim: "the agent that developed it should be responsible for testing in production."
|
|
357
411
|
The architect sends the implementer back once the merge lands; the implementer watches the
|
|
@@ -361,6 +415,29 @@ Negative control: <deliberately broken input> → <refusal or failure observed>.
|
|
|
361
415
|
this: on 2026-09-12 a slot's entire staging gate passed at 00:02Z and its production-apply
|
|
362
416
|
failed at 00:12Z on a resource staging never runs. If the slot fails on the change, the
|
|
363
417
|
implementer owns the fix and the next slot.
|
|
418
|
+
The record has three places: the PR body's `Production:` line, one pull-request comment
|
|
419
|
+
carrying the Legion footer, and a `dispatch_message` on the issue — the reviewer and merger
|
|
420
|
+
read GitHub, the architect reads the issue. When the deploy that carries the merge has not
|
|
421
|
+
happened (a shared profile still holding the previous plugin release, a daemon still running
|
|
422
|
+
the previous commit, a slot nobody has run), open an action ask — `dispatch_ask` with
|
|
423
|
+
`kind: "action"` — naming the exact install or restart step, keep the `Production:` line at
|
|
424
|
+
`pending <what is missing>`, and complete the check once the human answers Done. Never record
|
|
425
|
+
a staging pass as the production check, and never let the architect sign off on a `pending`
|
|
426
|
+
line.
|
|
427
|
+
|
|
428
|
+
## When no surface reaches the changed path
|
|
429
|
+
|
|
430
|
+
No surface reaches the changed path is a report to the architect, never a reason to complete the phase.
|
|
431
|
+
Say which surface is missing and what it would have to do — a rig that can spawn the role, a
|
|
432
|
+
sandbox that holds the resource, a credential, a command that does not exist yet — and send it to
|
|
433
|
+
the architect with `envoy_publish` to its role topic. The architect creates a child issue in this
|
|
434
|
+
tree to build it (infrastructure, tooling, or a skill) and resumes you once it lands. Sami,
|
|
435
|
+
2026-09-13, verbatim: "If there's anything blocking that, we need to fix it: if it's
|
|
436
|
+
infrastructure, we need to fix it; if it's tooling, we need to develop it; if it's skills, we need
|
|
437
|
+
to fix the skills." A code path whose first execution would be after the merge — a deploy
|
|
438
|
+
workflow's inline step, a post-merge helper, a production-only resource — is untested until you
|
|
439
|
+
have executed it somewhere production-like; completing with a unit-test-only handoff is the
|
|
440
|
+
failure this rule exists to stop.
|
|
364
441
|
|
|
365
442
|
## The unchanged-diff check
|
|
366
443
|
|
|
@@ -409,6 +486,10 @@ cd -- "$LEGION_WORKSPACE" && \
|
|
|
409
486
|
legion handoff write --phase <p> --data '<JSON object of phase-specific fields only>'
|
|
410
487
|
```
|
|
411
488
|
|
|
489
|
+
`legion handoff write` validates the payload against the phase's schema before writing: an
|
|
490
|
+
implement handoff without a well-formed `proof`, or a test handoff that reports no failure and
|
|
491
|
+
carries no `proof` of its own, exits 1 naming the field and writes nothing.
|
|
492
|
+
|
|
412
493
|
Then verify the durable artifact exists:
|
|
413
494
|
|
|
414
495
|
```bash
|
|
@@ -422,9 +503,12 @@ cd -- "$LEGION_WORKSPACE" && \
|
|
|
422
503
|
jj -R "$LEGION_WORKSPACE" split -m "<phase>: record handoff" .legion/<phase>.json
|
|
423
504
|
```
|
|
424
505
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
506
|
+
**Only the implementer pushes the issue branch.** It acts as the code-writing App
|
|
507
|
+
(`legion-implementer[bot]`, `appRoleForLegionRole` in `packages/daemon/src/daemon/github-apps.ts`),
|
|
508
|
+
the one App with `contents` permission (the merger acts as the same App but pushes nothing: it
|
|
509
|
+
verifies and publishes READY). If you are the implementer, advance the issue bookmark and push it
|
|
510
|
+
with the provisioned credential helper. `--bookmark` also publishes the locally provisioned
|
|
511
|
+
bookmark on its first push — a bookmark not yet tracking a remote one is tracked automatically:
|
|
428
512
|
|
|
429
513
|
```bash
|
|
430
514
|
cd -- "$LEGION_WORKSPACE" && \
|
|
@@ -432,16 +516,23 @@ cd -- "$LEGION_WORKSPACE" && \
|
|
|
432
516
|
jj -R "$LEGION_WORKSPACE" git push --bookmark legion/<KEY>
|
|
433
517
|
```
|
|
434
518
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
519
|
+
Every other role — planner, tester, reviewer, architects — acts as the review App
|
|
520
|
+
(`legion-reviewer[bot]`), which cannot push: the `split` above is your last step, and the commit
|
|
521
|
+
rides the implementer's next push (the corrective push after a review, or the final `.legion/`
|
|
522
|
+
deletion). A push from one of those roles is refused — over git it reads
|
|
523
|
+
`remote: Repository not found.`; the REST API's form of the same refusal is
|
|
524
|
+
`Resource not accessible by integration` — and that refusal is expected, not a failure to report
|
|
525
|
+
or retry.
|
|
526
|
+
|
|
527
|
+
Do not report phase completion until the write, existence check, and handoff commit succeed —
|
|
528
|
+
and, for the implementer, until the push has too. This is the committed copy the next phase
|
|
529
|
+
reads after revival. It is removed once, at the end of a clean review: the implementer pushes
|
|
530
|
+
that deletion at the reviewer's direction. No other phase removes it — and once it is gone
|
|
531
|
+
(`jj -R "$LEGION_WORKSPACE" file list -r @- .legion` prints nothing on stdout; jj warns on
|
|
532
|
+
stderr), this gate no longer applies: a later rebase, bare-gate re-check, confirmation, or retro
|
|
533
|
+
writes no `.legion/<phase>.json`, commits no handoff, and reports with `legion handoff complete`
|
|
534
|
+
alone (below). Recreating `.legion/` after its deletion changes the approved head and restarts
|
|
535
|
+
the review loop this rule exists to end.
|
|
445
536
|
|
|
446
537
|
## Completion: report to the architect, then stay
|
|
447
538
|
|