@sjawhar/pi-legion 0.0.0 → 8.0.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.
Files changed (30) hide show
  1. package/README.md +74 -0
  2. package/agents/deep-worker.md +64 -0
  3. package/agents/oracle.md +38 -0
  4. package/agents/plan-gap-analyst.md +59 -0
  5. package/agents/plan-reviewer.md +61 -0
  6. package/agents/thermonuclear-code-quality.md +28 -0
  7. package/agents/thermonuclear-deep-review.md +28 -0
  8. package/dist/THIRD_PARTY_NOTICES +30 -0
  9. package/dist/legion.js +16807 -0
  10. package/dist/skills/ce-simplify-code/LICENSE +21 -0
  11. package/dist/skills/ce-simplify-code/SKILL.md +64 -0
  12. package/dist/skills/ce-simplify-code/references/personas/code-quality-reviewer.md +17 -0
  13. package/dist/skills/ce-simplify-code/references/personas/code-reuse-reviewer.md +7 -0
  14. package/dist/skills/ce-simplify-code/references/personas/efficiency-reviewer.md +11 -0
  15. package/dist/skills/legion-architect/SKILL.md +370 -0
  16. package/dist/skills/legion-controller/SKILL.md +419 -0
  17. package/dist/skills/legion-oracle/SKILL.md +74 -0
  18. package/dist/skills/legion-retro/SKILL.md +196 -0
  19. package/dist/skills/legion-worker/SKILL.md +482 -0
  20. package/dist/skills/legion-worker/references/cleanup-deletion.md +22 -0
  21. package/dist/skills/legion-worker/references/conflicts-and-rewrites.md +126 -0
  22. package/dist/skills/legion-worker/references/merge-gate.md +117 -0
  23. package/dist/skills/legion-worker/references/pr-body.md +146 -0
  24. package/dist/skills/legion-worker/references/review-threads.md +101 -0
  25. package/dist/skills/legion-worker/references/systematic-rename.md +19 -0
  26. package/dist/skills/thermonuclear-code-quality/LICENSE +21 -0
  27. package/dist/skills/thermonuclear-code-quality/SKILL.md +192 -0
  28. package/dist/skills/thermonuclear-deep-review/LICENSE +21 -0
  29. package/dist/skills/thermonuclear-deep-review/SKILL.md +98 -0
  30. package/package.json +43 -1
@@ -0,0 +1,482 @@
1
+ ---
2
+ name: legion-worker
3
+ description: Use when dispatched as a per-process Legion phase worker — planner, implementer, tester, reviewer, or merger — booted from the daemon's LEGION_* environment.
4
+ ---
5
+
6
+ # Legion Phase Worker
7
+
8
+ You are one phase in a shared issue workspace, running as your own OMP process — not a
9
+ `task`-spawned subagent and not a pipeline coordinator. The architect owns the tree; each
10
+ phase gets its own long-lived process against the same jj workspace, run in turn. Complete
11
+ the phase assigned to you, report its completion to the architect, and leave the durable
12
+ copy the next phase can trust.
13
+
14
+ Every path this skill cites (`packages/...`, `docs/...`, `AGENTS.md`) is in sjawhar/legion, the
15
+ Legion repository, which need not be the repository you are working in.
16
+
17
+ ## References
18
+
19
+ Each file below is part of this skill. Read it at the step beside it, through its `skill://` link
20
+ with the `read` tool: a read by filesystem path stops at 300 lines.
21
+
22
+ | When | Read |
23
+ | --- | --- |
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 to, accept, or resolve a review thread, or run `legion threads resolve` | `skill://legion-worker/references/review-threads.md` |
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
+ | You review or approve, build the READY packet, or check the merge in production | `skill://legion-worker/references/merge-gate.md` |
28
+ | The issue renames a repository, package, or URL across the codebase | `skill://legion-worker/references/systematic-rename.md` |
29
+ | The issue deletes code, a command, or documentation | `skill://legion-worker/references/cleanup-deletion.md` |
30
+
31
+ ## Identity, scope, and role
32
+
33
+ The daemon spawns you as a separate `omp --mode rpc` process (behind `legion worker-shim`,
34
+ in a tmux pane or an Agent Sandbox pod) with `LEGION_TREE`, `LEGION_ISSUE`, `LEGION_ROLE`,
35
+ `LEGION_GENERATION`, `LEGION_PROJECT`, `LEGION_BOOT_TOKEN_FILE` (the file holding your boot
36
+ token; the extension reads it for you), `LEGION_DAEMON_URL`, `LEGION_STATE_DIR`, and
37
+ `LEGION_WORKSPACE` in your environment. The extension
38
+ completes the boot handshake for you at session start — it registers with the daemon, claims
39
+ your role, and signals readiness. You never call `envoy_role_set` yourself.
40
+
41
+ Your role token is not the issue key spelled out literally. The daemon encodes it as
42
+ `legion-<project>-<key>-<role>` with the issue key lower-cased. For example, project `acme`, issue
43
+ `LEGION-41`, role `architect` encodes to `legion-acme-legion-41-architect`. Never hand-format one for another role: your own role
44
+ topic and the topic of the architect that owns your issue are in the `Legion addressing` line of
45
+ your system prompt (the daemon appends it; it names the tree root's architect, also on a child
46
+ issue), a sibling role's topic is yours with the
47
+ trailing `-<role>` replaced, and the `roleToken` helper in `@legion/contracts` computes any other
48
+ one exactly the way the daemon does — prefer a topic you've already been given before recomputing
49
+ one.
50
+
51
+ If the handshake fails (a rejected boot token, or a bootstrap failure after your role
52
+ registered), the extension logs it and exits the process outright — it does not retry, and
53
+ you do not troubleshoot it by hand. The daemon resumes this same session from its recorded
54
+ session file the next time this role is needed; that is not an instant automatic respawn.
55
+
56
+ Once ready, your assignment arrives as the first prompt in your session — you do not fetch
57
+ it. Read the current issue and its acceptance criteria before changing the workspace. Work
58
+ only on this phase's artifact.
59
+
60
+ You never start another Legion role: the daemon starts every phase worker itself, from its fixed
61
+ workflow table. You may still use ordinary `task` subagents for your own phase work; none of them
62
+ is a Legion role.
63
+ Escalate a product, scope, design, cross-phase, or lifecycle decision to the owning architect with
64
+ `envoy_publish` to its role topic (`notifications.role.` followed by its encoded token, see
65
+ above), carrying the verified facts and the decision needed. A `write` to `agent://` only reaches
66
+ agents inside your own process, not the architect's separate one. Never write a decision block
67
+ into a spec yourself: the architect decides whether the human must answer it and writes the block,
68
+ since a new version of an approved root spec closes the tree's design gate. A standalone to-do
69
+ only a human can do is a `dispatch_ask`, and its replies return to your own session.
70
+
71
+ Because the same agent is always resumed for its phase, you may receive more than one
72
+ assignment across your lifetime: once the daemon ends your phase it suspends you, and when a later
73
+ event (a review round, a red check) starts your role again it resumes this same session with a new
74
+ prompt. Treat it as a continuation — re-read the current issue and your own prior handoff, since
75
+ time has passed — never as a fresh identity.
76
+
77
+ ## Deployment instructions
78
+
79
+ Deployment instructions, when present, are the operator's standing rules for this repository —
80
+ required checks, deploy/smoke commands, code-owner expectations, standing roles you may consult,
81
+ the merge credential. They override this skill's defaults where they conflict, except four rules
82
+ they never override: no deferrals (*PR body, review, and the merge gate*, below); bringing the base
83
+ into the branch only on a real conflict or a retarget
84
+ (`skill://legion-worker/references/conflicts-and-rewrites.md#reintegrating-the-base`); the
85
+ implementer's own proof on a production-like surface at the head that merges, an applied simplify
86
+ head included (`skill://legion-worker/references/pr-body.md#what-a-proof-is`,
87
+ `skill://legion-worker/references/pr-body.md#the-rules-every-phases-evidence-follows`); and the
88
+ implementer's production check after the merge
89
+ (`skill://legion-worker/references/merge-gate.md#after-the-human-merge`).
90
+
91
+ ## Asking another role
92
+
93
+ Reach any live role on this issue the same way you reach the architect: `envoy_publish` to
94
+ `notifications.role.` followed by that role's encoded token. Use it when you need context an
95
+ earlier phase has that its handoff doesn't cover — ask the planner why a constraint was
96
+ scoped that way, ask the implementer what a commit actually did. The daemon suspends a role when
97
+ its phase ends, so a role that finished is not running to answer you: read its committed handoff
98
+ instead.
99
+
100
+ ## Workspace and handoff precedence
101
+
102
+ `LEGION_WORKSPACE` is the authoritative issue workspace. Before reading repository files or
103
+ handoffs, you **MUST** bind to that exact path with:
104
+
105
+ ```bash
106
+ cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" status
107
+ ```
108
+
109
+ Never rely on the inherited cwd. Every later repository shell command **MUST** begin
110
+ `cd -- "$LEGION_WORKSPACE" &&`; every jj command **MUST** use `-R "$LEGION_WORKSPACE"`; and
111
+ native filesystem tool paths **MUST** be absolute under that workspace. Do not create an
112
+ isolated worktree, change the workspace topology, or mix another issue's work into it.
113
+ Concurrent issues have disjoint workspaces; only the currently active phase mutates this
114
+ one. After you complete, treat `$LEGION_WORKSPACE` as read-only: a finished role is not running to
115
+ answer questions or to keep editing. Do not create new commits, run
116
+ `jj -R "$LEGION_WORKSPACE" new`, or touch tracked files once your own handoff is committed
117
+ (and, for the implementer, pushed) — a code change belongs to whichever phase is active now.
118
+
119
+ On every start, and especially after revival or re-creation, read the issue and then the
120
+ committed predecessor handoffs in lifecycle order from
121
+ `$LEGION_WORKSPACE/.legion/<issue>/`:
122
+
123
+ 1. `architect.json`
124
+ 2. `plan.json`
125
+ 3. `implement.json`
126
+ 4. `test.json`
127
+ 5. `review.json`
128
+
129
+ Read only files that precede the assigned phase. Each was held to its phase's rules when it was
130
+ written (`handoff_write`, in the completion gate below): fields the phase does not declare passed
131
+ untouched and reach the next worker. The `legion` tool's `handoff_read` returns each file as it
132
+ stands in the workspace.
133
+ Write the phase-specific fields the next phase and the architect need, consistent with what
134
+ predecessor phases already wrote. The durable copy lives in
135
+ `$LEGION_WORKSPACE/.legion/<issue>/<phase>.json`. If a committed handoff conflicts with memory or a prior
136
+ transcript, the committed file wins: it is the copy that survived.
137
+
138
+ If your system prompt begins with `Your workspace was recreated…`, read
139
+ `.legion/<issue>/workspace-recovered.json`, then your phase's committed handoff, and reconcile before any
140
+ new work.
141
+
142
+ ## jj Safety Rules
143
+
144
+ - **Always `jj -R "$LEGION_WORKSPACE" new` to create isolated commits.** Never
145
+ `jj -R "$LEGION_WORKSPACE" edit @-` to go back to a parent — this changes what `@` points
146
+ to and makes `jj abandon` dangerous.
147
+ - **Never `jj -R "$LEGION_WORKSPACE" abandon`.** If a mistake would require abandoning
148
+ work, stop and send the owning architect the `jj -R "$LEGION_WORKSPACE" log` evidence.
149
+ - **Before pushing, check ancestry:** `jj -R "$LEGION_WORKSPACE" log -r 'ancestors(@, 5)'`
150
+ — verify only your issue's commits are in the chain, not unrelated work.
151
+
152
+ **Shared operation safety:** Every Legion issue workspace is a `jj workspace` of one shared
153
+ clone, so they all share one operation log: `jj undo`, `jj abandon`, and
154
+ `jj op restore|revert|abandon|undo` rewrite it for every tree at once. The extension refuses them in every
155
+ phase-worker pane before they run — a `bash` command in any position of a pipeline or `&&`
156
+ chain, with or without `-R`, judged on the whole argument list (a supervised service's start
157
+ included); `eval` code; and stdin written to a service (a `write` to `proc://<id>`) — from your
158
+ own tool calls and from any `task` subagent you spawn (it runs in your pane, against the same
159
+ log), and a `bash` command whose quoted text merely mentions `jj`
160
+ with one of those words (a heredoc, an echo, a commit message) is refused too: write such text
161
+ with the `write` tool or say "operation-log rollback" instead. `jj restore <paths>`,
162
+ `jj op log`, and `jj op show` stay allowed. Recover forward only: a new commit
163
+ (`jj -R "$LEGION_WORKSPACE" new`) or `jj -R "$LEGION_WORKSPACE" restore <paths>` of files.
164
+ Anything else, stop and send the owning architect the `jj -R "$LEGION_WORKSPACE" log`
165
+ evidence; the architect decides, and an operator performs any operation-log restore with every
166
+ other tree paused.
167
+
168
+ ## Phase work
169
+
170
+ 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.
171
+
172
+ Follow the repository's normal engineering workflow and the assigned issue's acceptance
173
+ criteria. Your phase's own charter and the predecessor handoffs you read define the phase
174
+ artifact and its completion evidence. Do not replace architect-owned decomposition, gate
175
+ discipline, scheduling, or human communication with labels or a local status model.
176
+ An issue that renames a repository, package, or URL across the codebase also follows
177
+ `skill://legion-worker/references/systematic-rename.md`; one that deletes code, a command, or
178
+ documentation follows `skill://legion-worker/references/cleanup-deletion.md`.
179
+
180
+ Commit attribution is automatic: the extension exports a `JJ_CONFIG` overlay when your
181
+ session starts, so every jj commit you make carries an `Omp-Session: <this-session-id>`
182
+ trailer with no action from you. Do not add attribution trailers by hand.
183
+
184
+ Your pane's environment already supplies your phase's author and committer identity
185
+ (`JJ_USER`/`JJ_EMAIL` and the Git author/committer variables, set by the daemon when it opened
186
+ the pane; the daemon also re-authors the workspace's working copy for your role at each
187
+ assignment, since `jj split`/`jj describe` keep its author). Never set or override
188
+ `user.name`/`user.email` in any jj or Git scope — not `jj config set`, not `--config`, not
189
+ `git config`: `--config` outranks the pane environment and would put the wrong App back on your
190
+ commits, and the repository-scoped jj config is one file shared by every issue workspace of the
191
+ clone. Legion has two GitHub Apps, not one per role: your role's App is the **implement** App
192
+ if you are the implementer or the merger, and the **review** App if you are the planner, tester,
193
+ reviewer, or an architect (in Legion's own deployment, `legion-implementer[bot]` and
194
+ `legion-reviewer[bot]`). A planner's commits authored by the review App are right. Before a push,
195
+ check
196
+ `jj -R "$LEGION_WORKSPACE" log -r 'main@origin..@' -T 'author.email() ++ " | " ++ committer.email() ++ " " ++ description.first_line() ++ "\n"'`
197
+ shows your role's App in both columns **on every commit you made** — not on the whole list:
198
+ earlier phases' commits are legitimately authored by their own role's App. Their *committer* is
199
+ a different matter, and no longer noise to accept. Resolving a conflict rewrites nothing — it is a
200
+ forward merge (`skill://legion-worker/references/conflicts-and-rewrites.md`) — so it changes no
201
+ committer at all, and the one rewrite still open to you (*Rewriting pushed commits*, in the same
202
+ reference) resets the committer only of commits on your own chain that descend from the commit
203
+ you named, after its guard cleared. Another role's commit
204
+ carrying you as committer, which you did not rewrite that way, is evidence that something
205
+ rewrote commits it should not have. Stop and send the
206
+ architect that log; do not accept it as a side effect. A wrong identity on your own commit, the
207
+ other App or none, is a pane-environment problem to report to the architect, not something to
208
+ pin (`docs/solutions/legion/shared-main-repo-hazards-for-concurrent-issue-workspaces.md`,
209
+ Hazard 1).
210
+ Your session receives the credential capability it needs; invoke GitHub through the
211
+ credential helper:
212
+
213
+ ```bash
214
+ legion gh -- <gh args…>
215
+ ```
216
+
217
+ Four facts about `gh` in a worker pane. The `gh` on your `PATH` is a shim
218
+ (`<state_dir>/worker-bin/gh`, installed by the daemon at startup — `packages/daemon/internal/runtime/workerbin/workerbin.go`)
219
+ that execs `legion gh -- "$@"`, so `gh …` and `legion gh -- …` are the same call, and each call
220
+ redeems a fresh token from your session's grant — identity is supplied per call, never stored.
221
+ Never run `gh auth login` or `gh auth setup-git`; there is no login state to create. The shim
222
+ refuses `pr merge` (and a raw `gh api …/merge` or a GraphQL mutation) for every role: Legion never
223
+ merges. It also refuses every GitHub-issue write — the `issue`
224
+ subcommand's `comment`, `create`, `edit`, `close`, `reopen`, `delete`, `pin`, `unpin`, `transfer`,
225
+ `lock`, `unlock`, and `develop`, and any raw `gh api` call to an `/issues` path whose method is not
226
+ GET (an explicit `-X`, or the POST that `-f`/`-F`/`--input` imply; pull-request conversation
227
+ comments live on that path too, so edit them with `gh pr comment`) — printing
228
+ `Legion issues live on Dispatch; use dispatch_message or dispatch_comment on <your LEGION_ISSUE>`:
229
+ Legion never reads or writes a GitHub issue. `pr comment`, `pr review`,
230
+ `api …/pulls/…`, `api graphql`, and issue reads are unaffected. The credential reaches `legion`
231
+ through the file `$LEGION_GRANT_FILE` names, written by the extension before each of your bash
232
+ commands, each `github` tool call, and each `read`/`grep` of a `pr://` or `issue://` URL (and by
233
+ the `legion` tool before its `handoff_complete`); never `cat`, `echo`, copy, or
234
+ `export` it — `legion credential`, `legion gh`, `jj git push`, and `handoff_complete` read it
235
+ themselves. The file is the pane's, not the command's, and a grant lives 60 seconds: a `task`
236
+ subagent, an `eval` subprocess, or a background job in your pane reads the grant your last such
237
+ call wrote, and a `github` tool `run_watch` keeps polling `gh` on the one written when the call
238
+ began, so each succeeds only within 60 seconds of that call and 403s afterwards — a timing
239
+ artifact, not a broken credential. Run credentialed commands from your own bash calls, and watch a
240
+ run that may outlast a minute with `gh run watch` in bash, which redeems once and then runs on the
241
+ token it got.
242
+
243
+ **Other credentials your pod may already carry.** Before reporting that a read is unreachable,
244
+ check for them rather than assuming none exist: `AGENT_SECRETS_URL` and `AGENT_SECRETS_KEY_DIR`
245
+ are set when the deployment enrolls pods with the agent-secrets broker (`docs/kubernetes.md`,
246
+ "Operator configuration"), in which case `agent-secrets <SECRET> -- <command>` runs `<command>`
247
+ with only the secrets this pod generation's grant allows — refuses closed, naming the secret, if
248
+ the rule does not allow it. `AWS_CONFIG_FILE` is set when the deployment's `pod.volumes` carries a
249
+ further projected token beyond the model route's own; read the file it names for what profiles it
250
+ configures before assuming the AWS CLI has nothing to reach. Neither variable existing is a
251
+ guarantee the read you need is covered — a refusal from either still means what it says — but
252
+ neither should be assumed absent without checking.
253
+
254
+ ## GitHub PR comment attribution
255
+
256
+ Append this exact structured footer to **every** pull-request comment and review that this
257
+ phase posts on GitHub. It preserves session provenance on the artifact itself so work stays
258
+ attributable to the session that produced it. Dispatch comments carry session provenance
259
+ natively through their own `actor`/`origin` fields; this footer is for GitHub PR artifacts and
260
+ for the retro's Dispatch message (`skill://legion-retro`):
261
+
262
+ ```html
263
+ <!-- legion: {"session":"<session-id>","phase":"<phase>"} -->
264
+ ```
265
+
266
+ For example:
267
+
268
+ ```bash
269
+ legion gh -- pr comment <pr-number> \
270
+ --body $'Verification complete.\n\n<!-- legion: {"session":"<session-id>","phase":"<phase>"} -->' \
271
+ --repo <owner>/<repo>
272
+ ```
273
+
274
+ ## Planner artifact
275
+
276
+ The plan lives in `.legion/<issue>/plan.json` and the issue's `plan.md` document, never in the issue's
277
+ primary document, which is its spec; never commit a plan or spec file to the repository.
278
+ No `docs/plans/*`, `docs/superpowers/plans/*`, or spec markdown goes into the pull request: plan
279
+ and spec content goes into the issue, never into a PR (the root `AGENTS.md`
280
+ calls its own `docs/plans/` human-authored design history, not a Legion artifact). A skill step that says "save the plan
281
+ to a file" is satisfied by the handoff write in the completion gate below; the planner's only
282
+ commit is `plan: record handoff`.
283
+
284
+ A plan that departs from the spec's design records the departure in `plan.md` and in the required
285
+ `.legion/<issue>/plan.json` `specDepartures`: `[]` means no departure; otherwise each bounded record names
286
+ the spec, plan, evidence and outcome. The planner's role prompt defines that record. The planner
287
+ never edits the spec. Whether the spec changes is the architect's decision
288
+ (`skill://legion-architect`, section 1), and the reviewer reads the plan beside the spec.
289
+
290
+ ## Implementer push and pull request
291
+
292
+ The implementer opens the pull request. After its implementation commit and verification, it
293
+ pushes the issue branch under this exact name with the one push procedure every role uses
294
+ (*Every role pushes its own commits*, below).
295
+
296
+ The provisioned issue workspace configures `credential.helper` with the daemon's absolute
297
+ credential command, so `legion push` authenticates transparently through the same session
298
+ capability. Never handle a token.
299
+
300
+ Then open the pull request with `legion gh -- pr create`. The PR body **must** contain the
301
+ line `Dispatch: <KEY>` — the daemon's fallback link from a PR to its Dispatch issue when the
302
+ branch name alone is ambiguous. The credential helper and `legion gh` provide the GitHub
303
+ identity; never export, fetch, or replace a token. Other phases advance the existing branch
304
+ rather than creating a replacement bookmark or PR.
305
+
306
+ ## PR body, review, and the merge gate
307
+
308
+ The implementer writes the pull request body from the template when it opens the pull request,
309
+ and every later phase edits its own lines of the live body rather than replacing it. Each proof
310
+ (the implementer's `E2E (implementer)` line and `proof` array, the tester's `E2E (tester)` line
311
+ and `proof` array) is the changed behaviour exercised on a production-like surface, recorded as
312
+ its command or run id, what was observed, the head SHA, and one negative control; a unit test is
313
+ never one. **Before you write or edit any line of the PR body or any `proof` array, read
314
+ `skill://legion-worker/references/pr-body.md`**: the template (the sole definition of the CI
315
+ line), the full definition of a proof, what the tester verifies, and the simplify pass.
316
+
317
+ - **Review threads** are disposed of one by one, never in bulk, and only an `Accepted:` from the
318
+ thread's opener (or, on a bot's thread, from the Legion reviewer) closes one. The implementer
319
+ runs `legion threads resolve` after every push that answers a review, before its completion,
320
+ and the merger before READY: `skill://legion-worker/references/review-threads.md`.
321
+ - **No deferrals.** A finding that changes
322
+ behaviour, hides an error, or breaks a gate is fixed in this pull request; naming, duplication,
323
+ or wording cleanup is batched into the one `Fast-follow:` line instead of iterating per push.
324
+ - **A red CI job** that failed on its own is re-run with
325
+ `legion gh -- run rerun <run-id> --failed`, never by pushing a new commit or bringing in the base.
326
+ - **A conflict or a retarget** is the only reason to bring the base into the branch, always as a
327
+ forward merge and never `jj rebase`; the unchanged-diff fingerprint each role compares
328
+ afterwards is in the same reference: `skill://legion-worker/references/conflicts-and-rewrites.md`.
329
+ - **The merge gate**, in order: the tester's evidence green → the reviewer's approval of the head
330
+ → retro → the merger's READY → the human merge → the implementer's production check. Legion
331
+ never merges. The reviewer's submissions,
332
+ retro's commit, the merger's READY, and the production check follow
333
+ `skill://legion-worker/references/merge-gate.md`.
334
+ - **No surface reaches the changed path** is a report to the architect, never a reason to
335
+ complete the phase: `skill://legion-worker/references/pr-body.md`.
336
+
337
+ ## Completion gate: handoff write, verification, and persistence
338
+
339
+ The merger writes no handoff and pushes nothing, so this gate does not apply to it
340
+ (`packages/daemon/internal/prompts/roles/merger.md`).
341
+
342
+ A planner picking up after the issue moves back from implementing starts fresh on the remote
343
+ tip — `jj -R "$LEGION_WORKSPACE" new legion/<KEY>@origin` — since the implementer's unpushed
344
+ commits are no longer in the chain (*Every role pushes its own commits*, below).
345
+
346
+ Write the phase-specific handoff: call the `legion` tool with `op: "handoff_write"`, `phase: "<p>"`,
347
+ and `data`: a JSON object of the phase-specific fields only. It runs `legion handoff write` in
348
+ `$LEGION_WORKSPACE` and returns its output.
349
+
350
+ A handoff built from the one already on disk (a test handoff that accumulates review rounds can
351
+ pass 128 KiB) can instead be piped from bash, so you never re-emit the whole payload:
352
+ `cd -- "$LEGION_WORKSPACE" && bun -e 'const h = await Bun.file(".legion/<issue>/<phase>.json").json(); delete h.schemaVersion; delete h.phase; delete h.completed; <your edit to h>; console.log(JSON.stringify(h))' | legion handoff write --phase <phase>`.
353
+ The program is single-quoted, so strings in your edit take double quotes. It is `bun` because the
354
+ worker image a pod runs ships `bun` and not `jq`, and a devbox pane has the `bun` Legion builds
355
+ with. With `--data` omitted, `legion handoff write` reads the JSON object from stdin. The CLI adds
356
+ `schemaVersion`, `phase` and `completed` itself and refuses them in the data, hence the `delete`s.
357
+
358
+ `handoff_write` validates the payload against the phase's schema before writing: an
359
+ implement handoff without a well-formed `proof`, or a test handoff that reports no failure and
360
+ carries no `proof` of its own, exits 1 naming the field and writes nothing. Each `proof` entry, in
361
+ either phase, is an object of six non-empty strings: `criterion` (the acceptance line it proves),
362
+ `surface`, `command`, `observed`, `headSha` (the commit it ran at) and `negativeControl`.
363
+
364
+ Then verify the durable artifact exists:
365
+
366
+ ```bash
367
+ test -f "$LEGION_WORKSPACE/.legion/<issue>/<phase>.json"
368
+ ```
369
+
370
+ Then commit that exact handoff file onto the issue branch:
371
+
372
+ ```bash
373
+ cd -- "$LEGION_WORKSPACE" && \
374
+ jj -R "$LEGION_WORKSPACE" split -m "<phase>: record handoff" .legion/<issue>/<phase>.json
375
+ ```
376
+
377
+ **Every role pushes its own commits.** After the handoff commit — and, for the tester, the red
378
+ tests it wrote — push the issue branch with `legion push`, run from bash in your workspace:
379
+
380
+ ```bash
381
+ cd -- "$LEGION_WORKSPACE" && legion push
382
+ ```
383
+
384
+ It pushes `@-` through the provisioned credential helper, which authenticates as your role's App
385
+ (`appauth.AppRoleFor` in `packages/daemon/internal/appauth/identity.go`). Your system prompt says
386
+ which pushes skip CI and which commit-message keywords it refuses. While the head commit carries
387
+ `skip-checks: true`, a pull-request body edit alone starts no GitHub workflow run, so a check that
388
+ re-judges the body re-runs on that head only after a later push; on a code head the same edit
389
+ re-runs it at once. While the pull request conflicts with its base (GitHub shows it
390
+ `CONFLICTING`), no workflow triggered `on: pull_request` runs for any of its activity types, so
391
+ a body edit re-judges nothing there either; a push cures both, but only once the pull request is
392
+ mergeable, so the implementer forward-merges a conflicting one first (*Reintegrating the base* in
393
+ `skill://legion-worker/references/conflicts-and-rewrites.md`). Its ancestry check refuses
394
+ unless `@-` descends from `legion/<KEY>@origin` (or the branch is not on GitHub yet): every issue
395
+ workspace shares one clone, so another role's push moves `legion/<KEY>@origin` here at once, and
396
+ a push that did not descend from it would move the remote branch sideways onto your commit and
397
+ drop theirs. A handoff commit never sits on an implementer's unpushed chain: when the issue moves
398
+ back to planning, the implementer's unpushed commits stay off the bookmark until the implementer
399
+ returns, and the planner writes its handoff on the remote tip.
400
+
401
+ Before any rewrite of a commit you already pushed — a `jj squash --into` one, or any other
402
+ rewrite — read *Rewriting pushed commits* in
403
+ `skill://legion-worker/references/conflicts-and-rewrites.md`: it checks that no other tree's work
404
+ is built on that commit and records the pushed tip `legion push` reads, the only case in which a
405
+ push lets the remote branch move off its current tip.
406
+
407
+ Before the push, check ancestry and identity as above: the chain carries every earlier phase's
408
+ pushed commits, and pushing them with yours is expected. A refusal, and a push the remote rejects, is a
409
+ report to the architect with the output, never a force-push. The merger makes no commit and
410
+ pushes nothing.
411
+
412
+ Do not report phase completion until the write, existence check, handoff commit, and push
413
+ succeed. This is the committed copy the next phase reads after revival. No phase removes
414
+ `.legion/`: the reviewer approves a head that carries it. The daemon strips any `.legion/` still on
415
+ main from the next issue's branch before any of its roles start (dispatch://LEGION-565), so that
416
+ tree's own merge carries the removal onto the default branch; no operator sweep follows. Retro and
417
+ the post-merge production check write no `.legion/<issue>/<phase>.json`, commit no handoff, and
418
+ report with `handoff_complete` alone (below).
419
+
420
+ ## Completion: report to the architect, then stay
421
+
422
+ Report completion to the architect: call the `legion` tool with `op: "handoff_complete"` and
423
+ `summary`: two sentences for the architect. A worker never runs `legion handoff complete` from
424
+ bash, where the extension refuses it: the tool call is what the extension records, and a turn that
425
+ ends with the phase still open gets one reminder.
426
+
427
+ This publishes your phase's completion to the architect's role and clears the daemon's
428
+ record of this issue's active phase. Do not add pipeline labels, run a controller loop, or
429
+ invent a different completion protocol — this is the whole contract.
430
+
431
+ A reviewer's phase ends with its completion, not with its review; the order of a review round
432
+ (the handoff push; for an approval, CI settled green at that head; the review of that head; then
433
+ the completion) is in `skill://legion-worker/references/merge-gate.md`.
434
+
435
+ **A refused completion is information, not a retry loop.** The daemon attributes your report to
436
+ the run whose task you took, and answers with what it found. What each answer carries, and what to
437
+ do:
438
+
439
+ - `HANDOFF_STALE_GENERATION` — names the issue, the run it is on, and the run your completion
440
+ reported. The issue has moved to a newer run since your task was given, so the work you just
441
+ reported belongs to a run that is over. Nothing you can repeat changes that: stop, push nothing
442
+ further, and tell the architect what you completed and that its run has been superseded. A task
443
+ for the current run arrives in this same session if the phase still needs you. The same answer
444
+ comes when your turn started before the daemon recorded the current run's task as yours, so it
445
+ still holds you to the earlier run: that task is sent again. When a task arrives, do what it
446
+ asks; if the work it asks for is already committed, call `handoff_complete` again, and never redo
447
+ the work or write a second handoff.
448
+ - `HANDOFF_NOT_CURRENT_PHASE` — names your role, the issue, and the phase it is in now. The issue
449
+ has left your phase; report to the architect rather than completing again.
450
+ - `HANDOFF_NO_RUN` — names neither: it says this claim has taken no task, so the daemon cannot
451
+ tell which run you are reporting. Your pane is completing outside any assignment. Say so to the
452
+ architect; do not re-run the phase. The same answer comes when your turn started before your task
453
+ reached you: a notice or a message started it, and the task, refused while that turn ran, is sent
454
+ when the turn ends. When a task arrives, do what it asks; if the work it asks for is already
455
+ committed, call `handoff_complete` again, and never redo the work or write a second handoff.
456
+ - `HANDOFF_ALREADY_RECORDED` — names your role, the phase, the review round and the commit. This
457
+ exact call was received before, and its first answer stands: accepted, or a refusal the daemon
458
+ records with the call — `HANDOFF_STALE_GENERATION`, `HANDOFF_NOT_CURRENT_PHASE`,
459
+ `READY_REQUIRED` or `HANDOFF_NOT_NEW`. `HANDOFF_NO_RUN` is never that first answer, since it is
460
+ given before anything is recorded. Sending it again changes nothing; if you did not see that
461
+ first answer, tell the architect so and quote this one.
462
+
463
+ Quote the answer verbatim in what you tell the architect: with the run and phase it names, the
464
+ difference between "my work is lost" and "my work belongs to the previous run" is visible.
465
+
466
+ **Stay in this session afterward.** Your process does not exit on its own when your phase
467
+ completes: the daemon suspends it when it ends your phase, at the end of your turn, so a finished
468
+ role is not running to answer questions. When the daemon starts your role again it resumes this
469
+ same session from its session file, with a new prompt, so it is still you: you are the one resumed
470
+ if this phase's work needs to run again. Re-read `$LEGION_WORKSPACE` and your own committed handoff
471
+ then, without mutating anything until the new prompt asks for it (see Workspace and handoff
472
+ precedence above).
473
+
474
+ When blocked on a product, scope, design, lifecycle, or cross-phase decision, `envoy_publish` the
475
+ owning architect a concise message: issue, phase, verified observation, what you tried, and the
476
+ decision required.
477
+
478
+ Never yield while blocked on a decision someone else owns. Before you stop, make the block visible
479
+ where its owner will see it: a product, scope, design, lifecycle, or cross-phase decision goes to
480
+ the owning architect as above, and a standalone human to-do goes in `dispatch_ask`. Otherwise
481
+ proceed: proceeding is the default, and a phase that stops silently holds its issue until someone
482
+ notices.
@@ -0,0 +1,22 @@
1
+ # Strategy: Cleanup & Deletion PRs
2
+
3
+ When deleting deprecated code, stale docs, or consolidating references.
4
+
5
+ ## When deleting a CLI command, check all four:
6
+
7
+ 1. Implementation file(s)
8
+ 2. `package.json` / `pyproject.toml` script entry
9
+ 3. All project documentation references (AGENTS.md or equivalent — check command tables AND section headings)
10
+ 4. Wrapper scripts or CI jobs that invoke it
11
+
12
+ ## project doc headings are documentation too
13
+
14
+ When updating a command reference, grep for the section heading and update it in the same commit. Headings that reference specific paths (`## Foo (meta/bar/)`) go stale when paths change.
15
+
16
+ ## Deletion PRs should be almost entirely deletions
17
+
18
+ Resist opportunistic refactors. If the diff has significant additions, the scope has crept. The value of a cleanup PR is its tight, reviewable scope.
19
+
20
+ ## Complete the deletion chain
21
+
22
+ If a feature has implementation + CLI wrapper + package.json entry + docs, remove all of them together. Partial deletion leaves broken references.
@@ -0,0 +1,126 @@
1
+ # Conflicts, retargets, fingerprints, and rewriting pushed commits
2
+
3
+ Part of `skill://legion-worker`. Read it when GitHub reports the pull request `CONFLICTING`, the
4
+ controller asks you to resolve a conflict, the pull request is retargeted to a new base, you
5
+ compare two heads after such a merge, or you are about to rewrite a commit you already pushed.
6
+ Every path it cites is in sjawhar/legion.
7
+
8
+ ## Reintegrating the base
9
+
10
+ - **Reintegrate the base only on a real conflict, except after a base retarget — and with a
11
+ merge, never `jj rebase`.**
12
+ The implementer merges the base into the issue branch only when GitHub reports it `CONFLICTING`, the controller asks
13
+ because of a conflict, or after the pull request is retargeted to a new base. Otherwise, never reintegrate the base to
14
+ pick up `main` or refresh CI (a single failed CI job is re-run on its own: *A red CI job* in
15
+ `skill://legion-worker`). A conflict-forced rebase that leaves the branch's diff unchanged is a
16
+ confirmation, not a new round (see *The unchanged-diff check* below); that name is the event's,
17
+ kept by the rules below and the learnings that cite it, and the operation it names is always
18
+ the merge here. Before merging, record
19
+ the fingerprint at the current tip; after pushing the merged branch, record it at the new
20
+ tip; post one PR comment (Legion footer):
21
+ `rebase <old-tip-sha> → <new-tip-sha>; fingerprint <before> → <after>; unchanged|changed`.
22
+ Every issue workspace is a `jj workspace` of the same shared repository and operation log, and
23
+ jj always rebases every descendant of any commit it rewrites — a revset naming the root of your
24
+ own chain and rewriting it in place also rewrites whatever another tree has stacked on that root,
25
+ whichever selector chose it (`-s`, `-b`, and `-r` all rewrite descendants; `-r` only re-parents
26
+ them to fill the hole, which is worse).
27
+ Resolve the conflict with a forward merge instead of a rewrite — merge the branch's own
28
+ bookmark with the destination in one new commit, so nothing existing is rewritten and nothing
29
+ built on your prior commits, in this tree or another, ever moves:
30
+
31
+ ```bash
32
+ jj -R "$LEGION_WORKSPACE" new legion/<KEY> main@origin -m "merge: resolve conflict against main@origin"
33
+ ```
34
+
35
+ Merge from the bookmark, never from `@`: a handoff split leaves `@` an empty, undescribed
36
+ commit above the described one the bookmark already names, and `jj git push` refuses to push
37
+ any commit without a description — merging from `@` drags that undescribed commit into the
38
+ ancestry and the push fails (`Won't push commit … since it has no description`); the bookmark
39
+ is always on a described, already-pushed commit. If the merge conflicts, resolve it in that
40
+ 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 with the one
42
+ push procedure (*Every role pushes its own commits* in `skill://legion-worker`): the merge descends from both the
43
+ bookmark's old position and the destination, so it is a genuine fast-forward and *Rewriting
44
+ pushed commits* never applies — nothing was rewritten, so there is no tip to record first.
45
+ - **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 with the
47
+ ordinary push procedure — a genuine fast-forward, never the procedure for rewritten commits —
48
+ so the new head runs Tests against the new merge result, and cite that run in the PR body.
49
+
50
+ ## The unchanged-diff check
51
+
52
+ The fingerprint every role compares after a conflict-forced rebase (every flag and the fileset
53
+ verified on jj 0.45.1):
54
+
55
+ ```bash
56
+ cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" git fetch && \
57
+ jj -R "$LEGION_WORKSPACE" diff --from "fork_point(main@origin | <head-sha>)" --to <head-sha> \
58
+ --git --context 0 '~(.legion | docs/solutions)' \
59
+ | sed -e '/^@@/d' -e '/^index /d' | sha256sum
60
+ ```
61
+
62
+ - `<head-sha>` is a full commit SHA; a jj commit id is the git SHA GitHub shows.
63
+ - `fork_point(main@origin | <head-sha>)` is the base the branch was cut from *at that head*:
64
+ the old base for the pre-rebase head, the new base for the rebased one, so one command
65
+ serves both sides. On a stacked PR substitute its base branch for `main`
66
+ (`legion gh -- pr view <n> --json baseRefName`).
67
+ - A head the rebase hid is still addressable by its SHA in the shared workspace. A SHA the
68
+ workspace cannot resolve (`jj -R "$LEGION_WORKSPACE" log -r <sha>` errors) counts as a
69
+ changed diff — never as unchanged.
70
+ - `--context 0` drops context lines; the `sed` drops `@@` hunk headers (line positions move
71
+ on a rebase) and `index` lines (blob ids move when the base's copy of a file changed). What
72
+ is left is exactly the added and removed lines per file.
73
+ - The single fileset `'~(.legion | docs/solutions)'` leaves out the handoff ledger and retro's
74
+ learnings: process artifacts the merge gate already exempts from re-review
75
+ (`skill://legion-worker/references/merge-gate.md`), which change
76
+ between one role's verified head and the next without changing the product. This is what lets
77
+ each role compare against *its own* last verified head instead of trusting another role's
78
+ numbers. It must be one expression: jj unions positional filesets, so two separate
79
+ `'~.legion' '~docs/solutions'` arguments select every file and exclude nothing. Once
80
+ `.legion/` is gone, jj warns `No matching entries for paths: .legion` on stderr; the hash is
81
+ unaffected.
82
+
83
+ Where each role gets its two heads: the implementer — the tip before and after its own rebase;
84
+ the tester — the head its `E2E` line names and the new head; the reviewer — the `commit_id` of
85
+ its last submitted review (`legion gh -- api repos/{owner}/{repo}/pulls/{n}/reviews --jq '.[] | {commit_id, state, user: .user.login}'`)
86
+ and the new head; the merger never computes a fingerprint — it uses the `--summary` check in
87
+ `skill://legion-worker/references/merge-gate.md`.
88
+
89
+ ## Rewriting pushed commits
90
+
91
+ **Rewriting pushed commits** — a `jj squash --into` a commit already on GitHub, or any other
92
+ rewrite of a commit you already pushed — is the hazard *Reintegrating the base* describes, in a
93
+ second shape: jj rebases
94
+ every descendant of any commit it rewrites, and in the one shared repository a descendant can be
95
+ another tree's branch stacked on your pushed commit, which then moves, with its bookmark, onto a
96
+ rewritten copy. So look for a descendant outside your own chain first, and record the pushed tip
97
+ — which the rewrite leaves outside `::@-` — after a fetch and while your chain still descends
98
+ from it:
99
+
100
+ ```bash
101
+ cd -- "$LEGION_WORKSPACE" && \
102
+ jj -R "$LEGION_WORKSPACE" git fetch && \
103
+ foreign=$(jj -R "$LEGION_WORKSPACE" log --no-graph -T 'commit_id.short() ++ "\n"' \
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"
112
+ ```
113
+
114
+ `descendants(<commit>) ~ ::@` is everything built on the commit you are about to rewrite that is
115
+ not on your own chain. Non-empty means the rewrite would move work that is not yours: do not
116
+ rewrite it. Put the change in a new commit on top instead, and report the listed commits to the
117
+ architect.
118
+
119
+ Then rewrite, resolve, and push with the one push procedure (*Every role pushes its own commits*
120
+ in `skill://legion-worker`). It lets the remote branch sit on the
121
+ tip you recorded, which the rewrite replaced, and on nothing else: when another role pushed after
122
+ you recorded it, the push is refused. The push deletes the file.
123
+
124
+ Once a base is frozen for others to stack on, never rewrite it: fixes land as new commits on top,
125
+ and the PR body's `Chain` line records what is frozen.
126
+