@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.
- package/README.md +74 -0
- package/agents/deep-worker.md +64 -0
- package/agents/oracle.md +38 -0
- package/agents/plan-gap-analyst.md +59 -0
- package/agents/plan-reviewer.md +61 -0
- package/agents/thermonuclear-code-quality.md +28 -0
- package/agents/thermonuclear-deep-review.md +28 -0
- package/dist/THIRD_PARTY_NOTICES +30 -0
- package/dist/legion.js +16807 -0
- package/dist/skills/ce-simplify-code/LICENSE +21 -0
- package/dist/skills/ce-simplify-code/SKILL.md +64 -0
- package/dist/skills/ce-simplify-code/references/personas/code-quality-reviewer.md +17 -0
- package/dist/skills/ce-simplify-code/references/personas/code-reuse-reviewer.md +7 -0
- package/dist/skills/ce-simplify-code/references/personas/efficiency-reviewer.md +11 -0
- package/dist/skills/legion-architect/SKILL.md +370 -0
- package/dist/skills/legion-controller/SKILL.md +419 -0
- package/dist/skills/legion-oracle/SKILL.md +74 -0
- package/dist/skills/legion-retro/SKILL.md +196 -0
- package/dist/skills/legion-worker/SKILL.md +482 -0
- package/dist/skills/legion-worker/references/cleanup-deletion.md +22 -0
- package/dist/skills/legion-worker/references/conflicts-and-rewrites.md +126 -0
- package/dist/skills/legion-worker/references/merge-gate.md +117 -0
- package/dist/skills/legion-worker/references/pr-body.md +146 -0
- package/dist/skills/legion-worker/references/review-threads.md +101 -0
- package/dist/skills/legion-worker/references/systematic-rename.md +19 -0
- package/dist/skills/thermonuclear-code-quality/LICENSE +21 -0
- package/dist/skills/thermonuclear-code-quality/SKILL.md +192 -0
- package/dist/skills/thermonuclear-deep-review/LICENSE +21 -0
- package/dist/skills/thermonuclear-deep-review/SKILL.md +98 -0
- 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
|
+
|