@sjawhar/opencode-legion-envoy 6.10.1 → 6.11.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
|
@@ -22,7 +22,7 @@ with the `read` tool: a read by filesystem path stops at 300 lines.
|
|
|
22
22
|
| When | Read |
|
|
23
23
|
| --- | --- |
|
|
24
24
|
| You write or edit the PR body, record a proof (an `E2E` line or a handoff `proof` array), verify another phase's proof, or run the simplify pass | `skill://legion-worker/references/pr-body.md` |
|
|
25
|
-
| You reply
|
|
25
|
+
| You reply on or resolve a review thread, list a pull request's threads, or adjudicate a bot's finding | `skill://legion-worker/references/review-threads.md` |
|
|
26
26
|
| GitHub reports a conflict, the pull request is retargeted, you compare heads after a conflict merge, or you would rewrite a pushed commit | `skill://legion-worker/references/conflicts-and-rewrites.md` |
|
|
27
27
|
| You review or approve, build the READY packet, or check the merge in production | `skill://legion-worker/references/merge-gate.md` |
|
|
28
28
|
| The issue renames a repository, package, or URL across the codebase | `skill://legion-worker/references/systematic-rename.md` |
|
|
@@ -60,10 +60,8 @@ only on this phase's artifact.
|
|
|
60
60
|
You never start another Legion role: the daemon starts every phase worker itself, from its fixed
|
|
61
61
|
workflow table. You may still use ordinary `task` subagents for your own phase work; none of them
|
|
62
62
|
is a Legion role. A subagent runs in your pane as you, with every host tool but the `legion` tool
|
|
63
|
-
(it is never offered one), so
|
|
64
|
-
|
|
65
|
-
your own last credentialed call wrote, within its 60 seconds (*Four facts about `gh`*, below);
|
|
66
|
-
LEGION-631 (dispatch://LEGION-631) replaces that grant with the role's mounted token file.
|
|
63
|
+
(it is never offered one), so completing your phase is yours alone: give a subagent no completion
|
|
64
|
+
step.
|
|
67
65
|
Escalate a product, scope, design, cross-phase, or lifecycle decision to the owning architect with
|
|
68
66
|
`envoy_publish` to its role topic (`notifications.role.` followed by its encoded token, see
|
|
69
67
|
above), carrying the verified facts and the decision needed. A `write` to `agent://` only reaches
|
|
@@ -90,7 +88,7 @@ implementer's own proof on a production-like surface at the head that merges, an
|
|
|
90
88
|
head included (`skill://legion-worker/references/pr-body.md#what-a-proof-is`,
|
|
91
89
|
`skill://legion-worker/references/pr-body.md#the-rules-every-phases-evidence-follows`); and the
|
|
92
90
|
implementer's production check after the merge
|
|
93
|
-
(`skill://legion-worker/references/merge-gate.md#after-the-
|
|
91
|
+
(`skill://legion-worker/references/merge-gate.md#after-the-merge`).
|
|
94
92
|
|
|
95
93
|
## Asking another role
|
|
96
94
|
|
|
@@ -118,25 +116,24 @@ Concurrent issues have disjoint workspaces; only the currently active phase muta
|
|
|
118
116
|
one. After you complete and go idle, treat `$LEGION_WORKSPACE` as read-only in every later
|
|
119
117
|
turn, including one an Envoy question starts: you are kept alive to answer questions, not to keep
|
|
120
118
|
editing. Do not create new commits, run `jj -R "$LEGION_WORKSPACE" new`, or touch tracked files
|
|
121
|
-
once your own handoff is committed
|
|
119
|
+
once your own handoff is committed and pushed — a code change belongs to
|
|
122
120
|
whichever phase is active now. The same holds once the Go daemon has taken your phase back without
|
|
123
121
|
your completion: CI settling red while you tested, or reviewed a round you had not completed,
|
|
124
122
|
moves the issue to `implementing` and interrupts your turn, and the implementer starts only once
|
|
125
123
|
that turn has ended. Do not resume the interrupted work afterward.
|
|
126
124
|
|
|
127
125
|
On every start, and especially after revival or re-creation, read the issue and then the
|
|
128
|
-
committed predecessor handoffs in lifecycle order, with the `
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
Read only files that precede the assigned phase. Each was
|
|
137
|
-
|
|
138
|
-
untouched and reach the next worker.
|
|
139
|
-
description says where it reads each one from.
|
|
126
|
+
committed predecessor handoffs in lifecycle order, with the `read` tool from
|
|
127
|
+
`$LEGION_WORKSPACE/.legion/<issue>/`:
|
|
128
|
+
|
|
129
|
+
1. `plan.json`
|
|
130
|
+
2. `implement.json`
|
|
131
|
+
3. `test.json`
|
|
132
|
+
4. `review.json`
|
|
133
|
+
|
|
134
|
+
Read only files that precede the assigned phase. Each was checked against its phase's rules when
|
|
135
|
+
its phase completed (the completion gate below): fields the phase does not declare passed
|
|
136
|
+
untouched and reach the next worker.
|
|
140
137
|
Write the phase-specific fields the next phase and the architect need, consistent with what
|
|
141
138
|
predecessor phases already wrote. The durable copy lives in
|
|
142
139
|
`$LEGION_WORKSPACE/.legion/<issue>/<phase>.json`. If a committed handoff conflicts with memory or a prior
|
|
@@ -146,32 +143,6 @@ If your task, prompt, or a message since your last turn begins `Your workspace w
|
|
|
146
143
|
your phase's committed handoff and any `.legion/<issue>/workspace-recovered.json`, and reconcile
|
|
147
144
|
before new work: anything unpushed is gone. That message stays in history; reconciled, it is done.
|
|
148
145
|
|
|
149
|
-
## jj Safety Rules
|
|
150
|
-
|
|
151
|
-
- **Always `jj -R "$LEGION_WORKSPACE" new` to create isolated commits.** Never
|
|
152
|
-
`jj -R "$LEGION_WORKSPACE" edit @-` to go back to a parent — this changes what `@` points
|
|
153
|
-
to and makes `jj abandon` dangerous.
|
|
154
|
-
- **Never `jj -R "$LEGION_WORKSPACE" abandon`.** If a mistake would require abandoning
|
|
155
|
-
work, stop and send the owning architect the `jj -R "$LEGION_WORKSPACE" log` evidence.
|
|
156
|
-
- **Before pushing, check ancestry:** `jj -R "$LEGION_WORKSPACE" log -r 'ancestors(@, 5)'`
|
|
157
|
-
— verify only your issue's commits are in the chain, not unrelated work.
|
|
158
|
-
|
|
159
|
-
**Shared operation safety:** Under tmux every issue workspace is a `jj workspace` of one shared
|
|
160
|
-
clone and operation log; under Kubernetes each issue pod has its own, shared by that issue's roles
|
|
161
|
-
alone. Either way `jj undo`, `jj abandon`, and `jj op restore|revert|abandon|undo` rewrite a log
|
|
162
|
-
other agents work in. The extension refuses them in every phase-worker pane before they run — a
|
|
163
|
-
`bash` command in any position of a pipeline or `&&` chain, with or without `-R`, judged on the
|
|
164
|
-
whole argument list (a supervised service's start included); `eval` code; and stdin written to a
|
|
165
|
-
service (a `write` to `proc://<id>`) — from your own tool calls and from any `task` subagent you
|
|
166
|
-
spawn (it runs in your pane, against the same log), and a `bash` command whose quoted text merely
|
|
167
|
-
mentions `jj` with one of those words (a heredoc, an echo, a commit message) is refused too: write
|
|
168
|
-
such text with the `write` tool or say "operation-log rollback" instead. `jj restore <paths>`,
|
|
169
|
-
`jj op log`, and `jj op show` stay allowed. Recover forward only: a new commit
|
|
170
|
-
(`jj -R "$LEGION_WORKSPACE" new`) or `jj -R "$LEGION_WORKSPACE" restore <paths>` of files.
|
|
171
|
-
Anything else, stop and send the owning architect the `jj -R "$LEGION_WORKSPACE" log`
|
|
172
|
-
evidence; the architect decides, and an operator performs any operation-log restore with every
|
|
173
|
-
other tree paused.
|
|
174
|
-
|
|
175
146
|
## Phase work
|
|
176
147
|
|
|
177
148
|
Specifications written into Dispatch follow `skill://dispatch`'s [Writing a spec](skill://dispatch/SKILL.md#writing-a-spec), except that a phase worker writes no decision block: it sends an open product, scope or design decision to its architect, which writes the block.
|
|
@@ -184,6 +155,9 @@ An issue that renames a repository, package, or URL across the codebase also fol
|
|
|
184
155
|
`skill://legion-worker/references/systematic-rename.md`; one that deletes code, a command, or
|
|
185
156
|
documentation follows `skill://legion-worker/references/cleanup-deletion.md`.
|
|
186
157
|
|
|
158
|
+
Create isolated commits with `jj -R "$LEGION_WORKSPACE" new` and reviewable ones with
|
|
159
|
+
`jj -R "$LEGION_WORKSPACE" split -m "<message>" <paths…>`, each holding only your phase's paths.
|
|
160
|
+
|
|
187
161
|
Commit attribution is automatic: the extension exports a `JJ_CONFIG` overlay when your
|
|
188
162
|
session starts, so every jj commit you make carries an `Omp-Session: <this-session-id>`
|
|
189
163
|
trailer with no action from you. Do not add attribution trailers by hand.
|
|
@@ -214,38 +188,28 @@ architect that log; do not accept it as a side effect. A wrong identity on your
|
|
|
214
188
|
other App or none, is a pane-environment problem to report to the architect, not something to
|
|
215
189
|
pin (`docs/solutions/legion/shared-main-repo-hazards-for-concurrent-issue-workspaces.md`,
|
|
216
190
|
Hazard 1).
|
|
217
|
-
Your
|
|
218
|
-
|
|
191
|
+
Your pane's `gh` and `git` already carry your role's GitHub identity; invoke GitHub with plain
|
|
192
|
+
`gh`:
|
|
219
193
|
|
|
220
194
|
```bash
|
|
221
|
-
|
|
195
|
+
gh <gh args…>
|
|
222
196
|
```
|
|
223
197
|
|
|
224
|
-
|
|
225
|
-
(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
`
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
commands, each `github` tool call, and each `read`/`grep` of a `pr://` or `issue://` URL (and by
|
|
240
|
-
the `legion` tool before its `handoff_complete`); never `cat`, `echo`, copy, or
|
|
241
|
-
`export` it — `legion credential`, `legion gh`, `jj git push`, and `handoff_complete` read it
|
|
242
|
-
themselves. The file is the pane's, not the command's, and a grant lives 60 seconds: a `task`
|
|
243
|
-
subagent, an `eval` subprocess, or a background job in your pane reads the grant your last such
|
|
244
|
-
call wrote, and a `github` tool `run_watch` keeps polling `gh` on the one written when the call
|
|
245
|
-
began, so each succeeds only within 60 seconds of that call and 403s afterwards — a timing
|
|
246
|
-
artifact, not a broken credential. Run credentialed commands from your own bash calls, and watch a
|
|
247
|
-
run that may outlast a minute with `gh run watch` in bash, which redeems once and then runs on the
|
|
248
|
-
token it got.
|
|
198
|
+
How `gh` works in a worker pane. `GH_CONFIG_DIR` names a read-only directory holding gh's own
|
|
199
|
+
`hosts.yml` (your role's GitHub App installation token, user `x-access-token`) and `config.yml`,
|
|
200
|
+
which the daemon rewrites in place from its cached lease so it never expires under you; the `gh`
|
|
201
|
+
on your `PATH` is the ordinary binary and reads it on every call, `git` reads the same file
|
|
202
|
+
through the clone's `credential.helper` (`!gh auth git-credential`), and `GH_TOKEN`,
|
|
203
|
+
`GITHUB_TOKEN` and `GH_HOST` are set empty so nothing outranks it. Never run `gh auth login`,
|
|
204
|
+
`gh auth setup-git`, `gh config set` or `gh alias set`: there is no login state to create, and the
|
|
205
|
+
directory is read-only. One rule nothing enforces: **Legion's issues live on Dispatch, never on
|
|
206
|
+
GitHub issues** — no `gh issue` write and no non-GET `gh api` call to an `/issues` path
|
|
207
|
+
(pull-request conversation comments live on that path too; edit them with `gh pr comment`); use
|
|
208
|
+
`dispatch message` or `dispatch comment` on your `LEGION_ISSUE`. The `legion` tool's own daemon
|
|
209
|
+
calls carry a credential of their own, minted in-process; `gh` and `git` never see it, and you
|
|
210
|
+
never `cat`, `echo`, copy, or `export` a token. A `task` subagent, an `eval` subprocess, or a
|
|
211
|
+
background job in your pane inherits the same `GH_CONFIG_DIR` and so the same credential, for as
|
|
212
|
+
long as it runs.
|
|
249
213
|
|
|
250
214
|
**Other credentials your pod may already carry.** Before reporting that a read is unreachable,
|
|
251
215
|
check for them rather than assuming none exist: `AGENT_SECRETS_URL` and `AGENT_SECRETS_KEY_DIR`
|
|
@@ -273,7 +237,7 @@ for the retro's Dispatch message (`skill://legion-retro`):
|
|
|
273
237
|
For example:
|
|
274
238
|
|
|
275
239
|
```bash
|
|
276
|
-
|
|
240
|
+
gh pr comment <pr-number> \
|
|
277
241
|
--body $'Verification complete.\n\n<!-- legion: {"session":"<session-id>","phase":"<phase>"} -->' \
|
|
278
242
|
--repo <owner>/<repo>
|
|
279
243
|
```
|
|
@@ -300,13 +264,12 @@ The implementer opens the pull request. After its implementation commit and veri
|
|
|
300
264
|
pushes the issue branch under this exact name with the one push procedure every role uses
|
|
301
265
|
(*Every role pushes its own commits*, below).
|
|
302
266
|
|
|
303
|
-
The
|
|
304
|
-
|
|
305
|
-
capability. Never handle a token.
|
|
267
|
+
The shared clone's `credential.helper` is `!gh auth git-credential`, so `jj git push`
|
|
268
|
+
authenticates as your role's App from the same file your `gh` reads. Never handle a token.
|
|
306
269
|
|
|
307
|
-
Then open the pull request with `
|
|
270
|
+
Then open the pull request with `gh pr create`. The PR body **must** contain the
|
|
308
271
|
line `Dispatch: <KEY>` — the daemon's fallback link from a PR to its Dispatch issue when the
|
|
309
|
-
branch name alone is ambiguous.
|
|
272
|
+
branch name alone is ambiguous. Your pane's `gh` and `git` provide the GitHub
|
|
310
273
|
identity; never export, fetch, or replace a token. Other phases advance the existing branch
|
|
311
274
|
rather than creating a replacement bookmark or PR.
|
|
312
275
|
|
|
@@ -321,23 +284,32 @@ never one. **Before you write or edit any line of the PR body or any `proof` arr
|
|
|
321
284
|
`skill://legion-worker/references/pr-body.md`**: the template (the sole definition of the CI
|
|
322
285
|
line), the full definition of a proof, what the tester verifies, and the simplify pass.
|
|
323
286
|
|
|
324
|
-
- **
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
287
|
+
- **The tester runs the application, not CI again.** It drives the changed surface for real and
|
|
288
|
+
tries to break it, judges whether the implementer's tests would catch what it broke, records what
|
|
289
|
+
it ran and saw, and must read CI for everything static — lint, types and the unit suites are
|
|
290
|
+
CI's work, rerun by no tester: `skill://legion-worker/references/pr-body.md`.
|
|
291
|
+
- **Review threads** are answered and resolved one by one, never in bulk, and never one you have
|
|
292
|
+
not read: one disposition reply per thread, then that thread's own resolution by the
|
|
293
|
+
implementer, the pull request's author — its `gh api graphql` mutation after every push that
|
|
294
|
+
answers a review, before its completion, for the threads it answered and for the bot threads the
|
|
295
|
+
reviewer names to it as accepted (the review App cannot resolve one):
|
|
296
|
+
`skill://legion-worker/references/review-threads.md`.
|
|
328
297
|
- **No deferrals.** A finding that changes
|
|
329
298
|
behaviour, hides an error, or breaks a gate is fixed in this pull request; naming, duplication,
|
|
330
299
|
or wording cleanup is batched into the one `Fast-follow:` line instead of iterating per push.
|
|
331
300
|
- **A red CI job** that failed on its own is re-run with
|
|
332
|
-
`
|
|
301
|
+
`gh run rerun <run-id> --failed`, never by pushing a new commit or bringing in the base.
|
|
333
302
|
- **A conflict or a retarget** is the only reason to bring the base into the branch, always as a
|
|
334
303
|
forward merge and never `jj rebase`; the unchanged-diff fingerprint each role compares
|
|
335
304
|
afterwards is in the same reference: `skill://legion-worker/references/conflicts-and-rewrites.md`.
|
|
336
305
|
- **The merge gate**, in order: the tester's evidence green → the reviewer's approval of the head
|
|
337
|
-
→ retro → the merger's READY → the
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
306
|
+
→ retro → the merger's READY → the merge → the implementer's production check. On READY's
|
|
307
|
+
acceptance the merger submits the pull request with
|
|
308
|
+
`gh pr merge <n> -R <owner>/<repo> --auto --squash --match-head-commit <head>`, and the
|
|
309
|
+
repository's required reviews and checks decide when it lands
|
|
310
|
+
(`skill://legion-worker/references/merge-gate.md`). The reviewer's submissions, retro's commit,
|
|
311
|
+
the merger's READY and its submission, the implementer's disarming of a withdrawn READY, and the
|
|
312
|
+
production check follow the same reference.
|
|
341
313
|
- **No surface reaches the changed path** is a report to the architect, never a reason to
|
|
342
314
|
complete the phase: `skill://legion-worker/references/pr-body.md`.
|
|
343
315
|
|
|
@@ -350,29 +322,17 @@ A planner picking up after the issue moves back from implementing starts fresh o
|
|
|
350
322
|
tip — `jj -R "$LEGION_WORKSPACE" new legion/<KEY>@origin` — since the implementer's unpushed
|
|
351
323
|
commits are no longer in the chain (*Every role pushes its own commits*, below).
|
|
352
324
|
|
|
353
|
-
Write the
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
`
|
|
364
|
-
|
|
365
|
-
`handoff_write` validates the payload against the phase's schema before writing: an
|
|
366
|
-
implement handoff without a well-formed `proof`, or a test handoff that reports no failure and
|
|
367
|
-
carries no `proof` of its own, exits 1 naming the field and writes nothing. Each `proof` entry, in
|
|
368
|
-
either phase, is an object of six non-empty strings: `criterion` (the acceptance line it proves),
|
|
369
|
-
`surface`, `command`, `observed`, `headSha` (the commit it ran at) and `negativeControl`.
|
|
370
|
-
|
|
371
|
-
Then verify the durable artifact exists:
|
|
372
|
-
|
|
373
|
-
```bash
|
|
374
|
-
test -f "$LEGION_WORKSPACE/.legion/<issue>/<phase>.json"
|
|
375
|
-
```
|
|
325
|
+
Write the handoff, `$LEGION_WORKSPACE/.legion/<issue>/<phase>.json` (`plan`, `implement`, `test`
|
|
326
|
+
or `review`), with the ordinary `write` tool: a JSON object with `schemaVersion: 1`,
|
|
327
|
+
`phase: "<phase>"`, `issue: "<KEY>"`, `completed: "<RFC 3339 UTC time of writing>"`, and the
|
|
328
|
+
phase's own fields exactly as your role prompt spells them — the planner's `requiredSkills`,
|
|
329
|
+
`gapAnalysis`, `planReview` and `specDepartures` records; the implementer's `proof` array; the
|
|
330
|
+
tester's `implementerProof`, `failures` and, whenever it reports no failure, its own `proof`; the
|
|
331
|
+
reviewer's `verdict`, counts and `keyFindings`. Nothing stamps the first four for you. Each `proof`
|
|
332
|
+
entry, in either phase, is an object of six non-empty strings: `criterion` (the acceptance line it
|
|
333
|
+
proves), `surface`, `command`, `observed`, `headSha` (the commit it ran at) and `negativeControl`.
|
|
334
|
+
A handoff built from the one already on disk (a test handoff that accumulates review rounds) is
|
|
335
|
+
read with `read`, edited, and written whole with `write`, its `completed` set to now.
|
|
376
336
|
|
|
377
337
|
Then commit that exact handoff file onto the issue branch:
|
|
378
338
|
|
|
@@ -381,63 +341,100 @@ cd -- "$LEGION_WORKSPACE" && \
|
|
|
381
341
|
jj -R "$LEGION_WORKSPACE" split -m "<phase>: record handoff" .legion/<issue>/<phase>.json
|
|
382
342
|
```
|
|
383
343
|
|
|
344
|
+
Then push (below) and report completion: call the `legion` tool with `op: "handoff_complete"` and
|
|
345
|
+
`summary` (two sentences for the architect; the tester adds `verdict: "pass"` or `"fail"`, the
|
|
346
|
+
merger `ready: true`). **Push before you complete is a hard rule.** `handoff_complete` finds, with
|
|
347
|
+
your pane's jj, the newest commit on the issue branch that carries `.legion/<KEY>/<phase>.json`
|
|
348
|
+
and reports it to the daemon, and refuses — posting nothing — while:
|
|
349
|
+
|
|
350
|
+
- the file has a change still in the working copy: commit it, then complete again;
|
|
351
|
+
- the file is missing from the workspace: write it, then commit and push it;
|
|
352
|
+
- the file is not committed on this branch (only the base carries it): write and commit it;
|
|
353
|
+
- the carrying commit was authored by another App (every role of an issue shares the workspace):
|
|
354
|
+
run `jj -R "$LEGION_WORKSPACE" new`, then write and commit this phase's handoff again;
|
|
355
|
+
- the carrying commit is not yet on `legion/<KEY>@origin`: push the issue branch, then complete
|
|
356
|
+
again.
|
|
357
|
+
|
|
358
|
+
Nothing checks the file's shape: the fields your role prompt spells are what the next role reads.
|
|
359
|
+
The daemon reads no handoff file and no branch head; it refuses, before recording anything:
|
|
360
|
+
|
|
361
|
+
- `HANDOFF_NOT_NEW` — the commit is the one you reported for your previous phase: write and
|
|
362
|
+
commit this phase's handoff before completing.
|
|
363
|
+
- `READY_HEAD_CARRIES_HANDOFFS`, `READY_HEAD_CONFLICTS` and `READY_CHECKS_NOT_GREEN` — the
|
|
364
|
+
merger's READY, in `skill://legion-worker/references/merge-gate.md`.
|
|
365
|
+
- `GITHUB_READ_FAILED` — GitHub's failure, not the head's, reading READY's checks: complete again.
|
|
366
|
+
|
|
367
|
+
Such a refusal records nothing, so the corrected call at the same commit is applied. The answer
|
|
368
|
+
may carry a `note` (READY published on an already-merged pull request, or a base requiring no
|
|
369
|
+
check).
|
|
370
|
+
|
|
371
|
+
Retro's last commit removes `.legion/<issue>/` from the head that merges (`skill://legion-retro`);
|
|
372
|
+
a round after it, such as one a withdrawn READY sends back, writes and commits its own handoff
|
|
373
|
+
again, since `handoff_complete` reports the commit carrying the file. Retro, the merger and the
|
|
374
|
+
post-merge production check write no `.legion/<issue>/<phase>.json`, commit no handoff, and report
|
|
375
|
+
with `handoff_complete` alone, which reports the commit the workspace stands on.
|
|
376
|
+
|
|
384
377
|
**Every role pushes its own commits.** After the handoff commit — and, for the tester, the red
|
|
385
|
-
tests it wrote — push the issue branch with `
|
|
378
|
+
tests it wrote — push the issue branch with the plain `jj` of your pane, from bash in your
|
|
379
|
+
workspace; `<KEY>` is your `LEGION_ISSUE`:
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" bookmark set legion/<KEY> -r @- && jj -R "$LEGION_WORKSPACE" git push --bookmark legion/<KEY>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
A push whose commits touch only `.legion/` (a handoff-only push) ends its head's commit message
|
|
386
|
+
with `skip-checks: true` as the last line, preceded by two empty lines, so GitHub starts no
|
|
387
|
+
workflow run for it and the daemon carries the code head's verdict to it. GitHub honours the
|
|
388
|
+
trailer only in that shape — the last line, after two empty lines; one empty line is not enough,
|
|
389
|
+
and GitHub then starts every workflow as if the line were not there (a planner's handoff push
|
|
390
|
+
did exactly that in the first live run of this rule). jj keeps the two lines as written, where
|
|
391
|
+
`git commit`'s default cleanup would collapse them. Write the line with trailers off for the one
|
|
392
|
+
describe, since the pane's jj overlay would otherwise append `Omp-Session:` after it:
|
|
386
393
|
|
|
387
394
|
```bash
|
|
388
|
-
|
|
395
|
+
jj -R "$LEGION_WORKSPACE" describe -r @- --config 'templates.commit_trailers=""' -m "$(jj -R "$LEGION_WORKSPACE" log -r @- --no-graph -T description)"$'\n\n\nskip-checks: true'
|
|
389
396
|
```
|
|
390
397
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
398
|
+
Every other push — code, red tests, `docs/solutions/`, retro's removal of `.legion/<KEY>/`, a
|
|
399
|
+
forward merge, a rewrite — carries no such line and none of GitHub's bracket keywords
|
|
400
|
+
(`[skip ci]`, `[ci skip]`, `[no ci]`, `[skip actions]`, `[actions skip]`), which start no workflow
|
|
401
|
+
wherever they stand. The daemon classifies a push by the paths its commits touch, never by the
|
|
402
|
+
trailer: a wrong trailer stalls the issue (a trailered code push gets no verdict carried; a head
|
|
403
|
+
with no CI is refused at READY) and never passes it. While the head commit carries
|
|
394
404
|
`skip-checks: true`, a pull-request body edit alone starts no GitHub workflow run, so a check that
|
|
395
405
|
re-judges the body re-runs on that head only after a later push; on a code head the same edit
|
|
396
406
|
re-runs it at once. While the pull request conflicts with its base (GitHub shows it
|
|
397
407
|
`CONFLICTING`), no workflow triggered `on: pull_request` runs for any of its activity types, so
|
|
398
408
|
a body edit re-judges nothing there either; a push cures both, but only once the pull request is
|
|
399
409
|
mergeable, so the implementer forward-merges a conflicting one first (*Reintegrating the base* in
|
|
400
|
-
`skill://legion-worker/references/conflicts-and-rewrites.md`).
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
`
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
Before the push, check ancestry and identity as above: the chain carries every earlier phase's
|
|
415
|
-
pushed commits, and pushing them with yours is expected. A refusal, and a push the remote rejects, is a
|
|
416
|
-
report to the architect with the output, never a force-push. The merger makes no commit and
|
|
417
|
-
pushes nothing.
|
|
418
|
-
|
|
419
|
-
Do not report phase completion until the write, existence check, handoff commit, and push
|
|
420
|
-
succeed. This is the committed copy the next phase reads after revival. Retro's last commit
|
|
421
|
-
removes `.legion/<issue>/` from the head a human merges (`skill://legion-retro`); a round after
|
|
422
|
-
it, such as one a withdrawn READY sends back, writes and commits its own handoff again, since
|
|
423
|
-
`handoff_complete` refuses a handoff that commit removed. Retro and the post-merge production
|
|
424
|
-
check write no `.legion/<issue>/<phase>.json`, commit no handoff, and report with
|
|
425
|
-
`handoff_complete` alone (below).
|
|
410
|
+
`skill://legion-worker/references/conflicts-and-rewrites.md`).
|
|
411
|
+
|
|
412
|
+
Before the push, inspect `jj -R "$LEGION_WORKSPACE" log -r 'ancestors(@, 5)'` and run the
|
|
413
|
+
identity check above: the chain carries every earlier phase's pushed commits, and pushing them
|
|
414
|
+
with yours is expected. Your issue's roles share one clone (under tmux every issue does), so
|
|
415
|
+
another role's push moves `legion/<KEY>@origin` here at once, and `jj git push` refuses a remote
|
|
416
|
+
branch that moved: report the refusal to the architect with its output, never force. A handoff commit never
|
|
417
|
+
sits on an implementer's unpushed chain: when the issue moves back to planning, the implementer's
|
|
418
|
+
unpushed commits stay off the bookmark until the implementer returns, and the planner writes its
|
|
419
|
+
handoff on the remote tip. The one sanctioned non-fast-forward move is the rewrite of a commit you
|
|
420
|
+
already pushed — a `jj squash --into` one, or any other rewrite — and before it, read *Rewriting
|
|
421
|
+
pushed commits* in `skill://legion-worker/references/conflicts-and-rewrites.md`: its guard checks
|
|
422
|
+
that no other tree's work is built on that commit, and only after it clears is the bookmark moved
|
|
423
|
+
with `--allow-backwards`. The merger makes no commit and pushes nothing.
|
|
426
424
|
|
|
427
425
|
## Completion: report to the architect, then stay
|
|
428
426
|
|
|
429
|
-
Report completion to the architect
|
|
430
|
-
`summary
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
extension's phase stall
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
whole contract.
|
|
427
|
+
Report completion to the architect: call the `legion` tool with `op: "handoff_complete"` and
|
|
428
|
+
`summary`: two sentences for the architect. The tool is your only way to the daemon
|
|
429
|
+
(`handoff_complete`; `read_record`, your issue's record, which is what "re-read your issue
|
|
430
|
+
record" means; and `request_backward_move`), and nothing of yours runs `legion` from bash. The
|
|
431
|
+
tool call is what the extension's phase stall records, and a turn that ends with the phase still
|
|
432
|
+
open gets one reminder; do not complete again on that reminder when your first completion was
|
|
433
|
+
accepted — it stands, and a second is refused because the issue has already left your phase.
|
|
434
|
+
|
|
435
|
+
This publishes your phase's completion to the architect's role and clears the daemon's
|
|
436
|
+
record of this issue's active phase. Do not add pipeline labels, run a controller loop, or
|
|
437
|
+
invent a different completion protocol — this is the whole contract.
|
|
441
438
|
|
|
442
439
|
A reviewer's phase ends with its completion, not with its review; the order of a review round
|
|
443
440
|
(the handoff push; for an approval, CI settled green at that head; the review of that head; then
|
|
@@ -19,11 +19,13 @@ Every path it cites is in sjawhar/legion.
|
|
|
19
19
|
the fingerprint at the current tip; after pushing the merged branch, record it at the new
|
|
20
20
|
tip; post one PR comment (Legion footer):
|
|
21
21
|
`rebase <old-tip-sha> → <new-tip-sha>; fingerprint <before> → <after>; unchanged|changed`.
|
|
22
|
-
|
|
23
|
-
jj
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
On the tmux runtime, where issue workspaces are still jj workspaces of one shared clone,
|
|
23
|
+
`jj undo`, `jj abandon` and `jj op restore` rewrite the shared operation log, so a mistake there
|
|
24
|
+
is recovered forward (`jj -R "$LEGION_WORKSPACE" new`, or `jj -R "$LEGION_WORKSPACE" restore
|
|
25
|
+
<paths>` of files). In that shared repository jj always rebases every descendant of any commit it
|
|
26
|
+
rewrites — a revset naming the root of your own chain and rewriting it in place also rewrites
|
|
27
|
+
whatever another tree has stacked on that root, whichever selector chose it (`-s`, `-b`, and
|
|
28
|
+
`-r` all rewrite descendants; `-r` only re-parents them to fill the hole, which is worse).
|
|
27
29
|
Resolve the conflict with a forward merge instead of a rewrite — merge the branch's own
|
|
28
30
|
bookmark with the destination in one new commit, so nothing existing is rewritten and nothing
|
|
29
31
|
built on your prior commits, in this tree or another, ever moves:
|
|
@@ -38,14 +40,14 @@ Every path it cites is in sjawhar/legion.
|
|
|
38
40
|
ancestry and the push fails (`Won't push commit … since it has no description`); the bookmark
|
|
39
41
|
is always on a described, already-pushed commit. If the merge conflicts, resolve it in that
|
|
40
42
|
one commit — edit the markers directly; there is nothing to squash, since the merge is the
|
|
41
|
-
only new commit. Then `jj -R "$LEGION_WORKSPACE" new` to move off it, and push
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
only new commit. Then `jj -R "$LEGION_WORKSPACE" new` to move off it, and push it (*Every role
|
|
44
|
+
pushes its own commits* in `skill://legion-worker`; a merge is a code push, so its head carries
|
|
45
|
+
no `skip-checks: true` line): the merge descends from both the bookmark's old position and the
|
|
46
|
+
destination, so it is a genuine fast-forward and *Rewriting pushed commits* never applies.
|
|
45
47
|
- **After a retarget.** Retargeting a pull request to a new base does not re-run Tests. Merge the
|
|
46
|
-
bookmark onto the new base (`jj new legion/<KEY> <new base> -m "<message>"`) and push
|
|
47
|
-
ordinary
|
|
48
|
-
|
|
48
|
+
bookmark onto the new base (`jj new legion/<KEY> <new base> -m "<message>"`) and push it the
|
|
49
|
+
ordinary way — a genuine fast-forward, never `--allow-backwards` — so the new head runs Tests
|
|
50
|
+
against the new merge result, and cite that run in the PR body.
|
|
49
51
|
|
|
50
52
|
## The unchanged-diff check
|
|
51
53
|
|
|
@@ -63,7 +65,7 @@ cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" git fetch && \
|
|
|
63
65
|
- `fork_point(main@origin | <head-sha>)` is the base the branch was cut from *at that head*:
|
|
64
66
|
the old base for the pre-rebase head, the new base for the rebased one, so one command
|
|
65
67
|
serves both sides. On a stacked PR substitute its base branch for `main`
|
|
66
|
-
(`
|
|
68
|
+
(`gh pr view <n> --json baseRefName`).
|
|
67
69
|
- A head the rebase hid is still addressable by its SHA in the shared workspace. A SHA the
|
|
68
70
|
workspace cannot resolve (`jj -R "$LEGION_WORKSPACE" log -r <sha>` errors) counts as a
|
|
69
71
|
changed diff — never as unchanged.
|
|
@@ -82,7 +84,7 @@ cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" git fetch && \
|
|
|
82
84
|
|
|
83
85
|
Where each role gets its two heads: the implementer — the tip before and after its own rebase;
|
|
84
86
|
the tester — the head its `E2E` line names and the new head; the reviewer — the `commit_id` of
|
|
85
|
-
its last submitted review (`
|
|
87
|
+
its last submitted review (`gh api repos/{owner}/{repo}/pulls/{n}/reviews --jq '.[] | {commit_id, state, user: .user.login}'`)
|
|
86
88
|
and the new head; the merger never computes a fingerprint — it uses the `--summary` check in
|
|
87
89
|
`skill://legion-worker/references/merge-gate.md`.
|
|
88
90
|
|
|
@@ -90,25 +92,17 @@ and the new head; the merger never computes a fingerprint — it uses the `--sum
|
|
|
90
92
|
|
|
91
93
|
**Rewriting pushed commits** — a `jj squash --into` a commit already on GitHub, or any other
|
|
92
94
|
rewrite of a commit you already pushed — is the hazard *Reintegrating the base* describes, in a
|
|
93
|
-
second shape: jj rebases
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
— which the rewrite leaves outside `::@-` — after a fetch and while your chain still descends
|
|
98
|
-
from it:
|
|
95
|
+
second shape: jj rebases every descendant of any commit it rewrites, and in the one shared
|
|
96
|
+
repository a descendant can be another tree's branch stacked on your pushed commit, which then
|
|
97
|
+
moves, with its bookmark, onto a rewritten copy. So look for a descendant outside your own chain
|
|
98
|
+
first, after a fetch and while your chain still descends from the pushed tip:
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
101
|
cd -- "$LEGION_WORKSPACE" && \
|
|
102
102
|
jj -R "$LEGION_WORKSPACE" git fetch && \
|
|
103
103
|
foreign=$(jj -R "$LEGION_WORKSPACE" log --no-graph -T 'commit_id.short() ++ "\n"' \
|
|
104
104
|
-r 'descendants(<the commit you are about to rewrite>) ~ ::@') && \
|
|
105
|
-
{ [ -z "$foreign" ] || { echo "not mine, and descends from the commit to rewrite: $foreign" >&2; false; }; }
|
|
106
|
-
behind=$(jj -R "$LEGION_WORKSPACE" log --no-graph -T 'commit_id.short() ++ "\n"' \
|
|
107
|
-
-r 'remote_bookmarks(exact:"legion/<KEY>", exact:"origin") ~ ::@-') && \
|
|
108
|
-
{ [ -z "$behind" ] || { echo "legion/<KEY>@origin is at $behind, which @- does not descend from" >&2; false; }; } && \
|
|
109
|
-
jj -R "$LEGION_WORKSPACE" log --no-graph -T 'commit_id' \
|
|
110
|
-
-r 'remote_bookmarks(exact:"legion/<KEY>", exact:"origin")' \
|
|
111
|
-
>"${TMPDIR:-/tmp}/legion-<KEY>-$LEGION_ROLE-rewritten-tip"
|
|
105
|
+
{ [ -z "$foreign" ] || { echo "not mine, and descends from the commit to rewrite: $foreign" >&2; false; }; }
|
|
112
106
|
```
|
|
113
107
|
|
|
114
108
|
`descendants(<commit>) ~ ::@` is everything built on the commit you are about to rewrite that is
|
|
@@ -116,10 +110,18 @@ not on your own chain. Non-empty means the rewrite would move work that is not y
|
|
|
116
110
|
rewrite it. Put the change in a new commit on top instead, and report the listed commits to the
|
|
117
111
|
architect.
|
|
118
112
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
Once the guard has cleared, rewrite, resolve, and push once, moving the bookmark backwards onto
|
|
114
|
+
the rewritten head — the one sanctioned non-fast-forward move, and the only place
|
|
115
|
+
`--allow-backwards` is ever used:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" bookmark set legion/<KEY> -r @- --allow-backwards && jj -R "$LEGION_WORKSPACE" git push --bookmark legion/<KEY>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
A rewrite head is a code push and never carries the `skip-checks: true` line: when the rewrite
|
|
122
|
+
carried one onto it (a squash of a trailered handoff commit into a code commit), describe the head
|
|
123
|
+
again without that line before you push. `jj git push` still refuses a remote branch another role
|
|
124
|
+
moved since your fetch: report the refusal to the architect with its output, never force.
|
|
123
125
|
|
|
124
126
|
Once a base is frozen for others to stack on, never rewrite it: fixes land as new commits on top,
|
|
125
127
|
and the PR body's `Chain` line records what is frozen.
|