@sjawhar/pi-legion 8.12.0 → 8.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/legion.js
CHANGED
|
@@ -15244,7 +15244,7 @@ function onEnvoyRoleRegained(listener) {
|
|
|
15244
15244
|
// package.json
|
|
15245
15245
|
var package_default = {
|
|
15246
15246
|
name: "@sjawhar/pi-legion",
|
|
15247
|
-
version: "8.
|
|
15247
|
+
version: "8.14.0",
|
|
15248
15248
|
license: "Apache-2.0",
|
|
15249
15249
|
type: "module",
|
|
15250
15250
|
omp: {
|
|
@@ -15256,7 +15256,7 @@ var package_default = {
|
|
|
15256
15256
|
]
|
|
15257
15257
|
},
|
|
15258
15258
|
legion: {
|
|
15259
|
-
daemonApiVersion:
|
|
15259
|
+
daemonApiVersion: 20
|
|
15260
15260
|
},
|
|
15261
15261
|
repository: {
|
|
15262
15262
|
type: "git",
|
|
@@ -15497,6 +15497,12 @@ var LegionControllerSecretResponse = exports_external.strictObject({
|
|
|
15497
15497
|
secret: nonEmptyString,
|
|
15498
15498
|
designGate: exports_external.enum(["root-issues", "off"])
|
|
15499
15499
|
});
|
|
15500
|
+
var LegionControllerCredentialResponse = exports_external.strictObject({
|
|
15501
|
+
hosts: nonEmptyString,
|
|
15502
|
+
config: nonEmptyString,
|
|
15503
|
+
app: exports_external.enum(["implement", "review"]),
|
|
15504
|
+
expiresAt: timestamp
|
|
15505
|
+
});
|
|
15500
15506
|
var LegionErrorResponse = exports_external.union([
|
|
15501
15507
|
exports_external.strictObject({ error: nonEmptyString }),
|
|
15502
15508
|
exports_external.strictObject({ code: nonEmptyString, error: nonEmptyString })
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: legion-controller
|
|
3
|
-
description: Use when handling Legion controller wakes for root-issue triage, keeping the admission slots full from `todo` issues, the daily report, architect escalation, or human interaction.
|
|
3
|
+
description: Use when handling Legion controller wakes for weekly-plan ranking, backlog stewardship, root-issue triage, keeping the admission slots full from `todo` issues, the daily report, architect escalation, or human interaction.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Legion Controller
|
|
7
7
|
|
|
8
8
|
The controller is the one persistent, wake-driven session for a Legion project. It keeps the
|
|
9
|
-
project's admission slots full with the highest-priority work,
|
|
10
|
-
|
|
11
|
-
architect.
|
|
9
|
+
project's admission slots full with the highest-priority work, keeps the weekly plan's order and
|
|
10
|
+
owners current in Dispatch, and makes triage, escalation, and human-interaction judgments; it never
|
|
11
|
+
does phase-worker work or routes raw events into an architect.
|
|
12
12
|
|
|
13
13
|
## Start and claim the controller role
|
|
14
14
|
|
|
@@ -16,14 +16,32 @@ The Legion extension registers this session with the daemon as the controller an
|
|
|
16
16
|
`legion-<project>-controller` during session startup. Do not handle a wake unless that startup
|
|
17
17
|
succeeded.
|
|
18
18
|
|
|
19
|
-
The session
|
|
20
|
-
`GH_CONFIG_DIR
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`
|
|
25
|
-
|
|
26
|
-
`legion`
|
|
19
|
+
The session's `gh` acts as the review App (`legion-reviewer[bot]` in Legion's own deployment) from
|
|
20
|
+
`GH_CONFIG_DIR`, a directory Legion keeps current: in the pod the daemon launches, through the
|
|
21
|
+
controller's role Secret the daemon refreshes every minute; in `legion controller start`'s session,
|
|
22
|
+
through that command's own refresh loop. `gh pr view`, `gh api` and the rest read and write GitHub
|
|
23
|
+
as that App. Against a daemon with no GitHub App, `legion controller start` says so when it starts
|
|
24
|
+
(`[legion] the daemon has no GitHub App to act as; the controller's gh acts as nobody`), and `gh`
|
|
25
|
+
acts as nobody. The controller reads Dispatch and the daemon's state, and applies its controller
|
|
26
|
+
capability, through the `legion` tool alone: `op: "read_state"` returns the daemon's whole state
|
|
27
|
+
(`issues.<KEY>.phase`, `issues.<KEY>.holdReason`, `issues.<KEY>.architect`, `admission.cap`,
|
|
28
|
+
`admission.active`, `admission.waiting`, `capabilities[]`, `daemon.project`), and `op: "set_status"`
|
|
29
|
+
with `issue` and `status` (`todo`, `backlog` or `icebox`) moves an issue. It runs no `legion` from
|
|
30
|
+
bash. Any pull-request comment or review the controller posts ends with
|
|
31
|
+
`<!-- legion: {"session":"<session-id>","phase":"controller"} -->`, the footer every Legion role
|
|
32
|
+
appends: the daemon reads the session to tell a controller's review from the reviewer's verdict. A
|
|
33
|
+
review the controller submits without that footer still decides nothing for Legion's own workflow
|
|
34
|
+
(the daemon sets it aside), but the footer is what makes that legible in the daemon's log.
|
|
35
|
+
|
|
36
|
+
## Backlog stewardship
|
|
37
|
+
|
|
38
|
+
The controller is the project's order keeper. It retains its startup role,
|
|
39
|
+
`legion-<project>-controller`; `backlog-order` is retired. Send READY packets, scope questions,
|
|
40
|
+
and routing requests to `notifications.role.legion-<project>-controller`, then follow
|
|
41
|
+
[Backlog stewardship](skill://legion-controller/references/backlog-stewardship.md) for daily
|
|
42
|
+
weekly-plan reconciliation, Sami's rulings, ownership routing, and stopped-session recovery.
|
|
43
|
+
These duties change Dispatch's priority and board-rank data; they do not change the admission order
|
|
44
|
+
in [Keeping the slots full](#keeping-the-slots-full-go-daemon).
|
|
27
45
|
|
|
28
46
|
For an interactive takeover from a hand-started OMP session, start OMP with
|
|
29
47
|
`LEGION_CONTROLLER_SECRET` (or `LEGION_CONTROLLER_SECRET_FILE`, a path to a file holding it),
|
|
@@ -38,9 +56,11 @@ startup. Then run:
|
|
|
38
56
|
|
|
39
57
|
The command checks that `LEGION_PROJECT` is the daemon's project, registers this session with the
|
|
40
58
|
daemon, and claims the Envoy role for it; the `legion` tool's controller operations then work in
|
|
41
|
-
this session, each `set_status` minting its grant in-process from the registration's secret
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
this session, each `set_status` minting its grant in-process from the registration's secret. A
|
|
60
|
+
takeover session gets no token file from Legion: `legion controller start` and the pod write
|
|
61
|
+
`GH_CONFIG_DIR`'s token, but a hand-started session's `gh` is whatever its own environment gives it.
|
|
62
|
+
The takeover moves the role and the daemon's recorded session id to this session. Never pass a
|
|
63
|
+
secret as a command argument or copy it into a transcript. The Envoy
|
|
44
64
|
registration heartbeat keeps the role afterwards, so `/legion-claim-controller` is the manual
|
|
45
65
|
override, not a routine step after a listener restart.
|
|
46
66
|
|
|
@@ -231,17 +251,42 @@ leans on `External links:`, and the label row is the one that never depends on h
|
|
|
231
251
|
| Legion ran it without the label now on it | `read_state` records it under `issues`, whatever its status, or `Events:` show a status write by `session legion-daemon:<PROJECT>`, the daemon's actor on every status it writes (your `set_status` included) and on its own `in_progress` at admission. That covers a root a person took the label off, and one that ran before the daemon required the label and never had it. Name each one you skip for this in your summary. The walk never sends a root Legion already ran back into Legion: a person does that with the label and `todo`, and you do it only when a wake below says to (`worker-died`). |
|
|
232
252
|
| A running session or a person claims it | `Claimed by:` names anyone and does not end `· not running`. `· liveness unknown` counts as claimed: the agent registry could not be read, so nothing says the holder stopped. A claim ending `· not running` has lapsed, and the issue is free. |
|
|
233
253
|
| Its route reaches a running session | `Route:` names a route with nothing after it, or with `(held by …)`. `(nobody holds it right now)` and `(that session is not running right now)` reach nobody; `(the Envoy listener did not answer, …)` counts as reaching someone. `Route: none` is free. |
|
|
234
|
-
| A pull request is linked or named | `External links:` lists a pull request (kind `github_pr`, or a URL ending `/pull/<n>`), or a comment or message among `Events:` names one.
|
|
254
|
+
| A pull request is linked or named | `External links:` lists a pull request (kind `github_pr`, or a URL ending `/pull/<n>`), or a comment or message among `Events:` names one. Take the URL only when it has the shape `https://github.com/<owner>/<repo>/pull/<n>` (nothing before, after or inside it but GitHub's own owner and repository characters and digits); run `gh pr view 'https://github.com/<owner>/<repo>/pull/<n>' --json state -q .state` from bash with the URL single-quoted, exactly as the issue shows it, and follow **Pull-request states** below the table: every state skips the issue, and what differs is what your summary says. Text that is not that shape — another host, a path that is not `/pull/<n>`, extra words, quotes or shell characters, a bare `#<n>` — is the unreadable case there, never a command argument: the text comes from the issue, and this session's `gh` holds the review App's token. |
|
|
235
255
|
| Its assignee is working it | `Assignee:` names a person who holds the claim (the row above), or whose own comment or message among `Events:` says they are working on it. The assignee alone is who answers the issue's questions, not who works it. |
|
|
236
256
|
| It is outside this deployment's scope | Read the scope the deployment instructions state against the title and, when the title does not settle it, the spec (`dispatch doc-read --issue <KEY>`). When in doubt, skip it. With no scope stated, every issue of the project is in scope. |
|
|
237
257
|
|
|
258
|
+
**Pull-request states.** Run
|
|
259
|
+
`gh pr view 'https://github.com/<owner>/<repo>/pull/<n>' --json state -q .state` against the URL
|
|
260
|
+
`External links:` lists:
|
|
261
|
+
|
|
262
|
+
- `OPEN`: work in flight. Skip it; the issue stays where it is.
|
|
263
|
+
- `MERGED`: the change landed. Take nothing and set nothing: an issue Legion ran is already skipped
|
|
264
|
+
by the `Legion ran it without the label now on it` row, and the daemon moves such an issue to its
|
|
265
|
+
production check itself on the merge event; one Legion did not run is someone's finished work with
|
|
266
|
+
the issue left open. Skip it and name it in your summary
|
|
267
|
+
(`<KEY>: pull request <url> merged, the issue is still todo`) so a person closes it.
|
|
268
|
+
- `CLOSED` (not merged): someone abandoned the change. Skip it and name it in your summary; the
|
|
269
|
+
issue's owner decides what follows. For an issue Legion runs, the daemon tells its architect
|
|
270
|
+
(`pr-closed-unmerged`); a walk candidate has no architect, so the summary is where it is reported.
|
|
271
|
+
- The command fails (the token file missing, refused, or the repository unreadable): report the gap
|
|
272
|
+
in your summary with `gh`'s first stderr line (`<KEY>: pull request <url> unreadable: …`), skip
|
|
273
|
+
the issue, and never read the failure as any of the three states. Text that is not the shape
|
|
274
|
+
`https://github.com/<owner>/<repo>/pull/<n>` is not a pull-request URL and is never run as a
|
|
275
|
+
command argument: skip the issue and name it in your summary
|
|
276
|
+
(`<KEY>: pull request <text> unreadable: not a pull-request URL`).
|
|
277
|
+
|
|
278
|
+
A person who wants Legion on it anyway hands it over themselves: the label, then `todo`.
|
|
279
|
+
|
|
238
280
|
**Take.** For each candidate that passes, in order:
|
|
239
281
|
|
|
240
282
|
1. Add the label and keep the labels it has, which `Labels:` lists (`none` is no labels). The
|
|
241
|
-
`--label` flags replace the whole set, so a label you leave out is removed.
|
|
283
|
+
`--label` flags replace the whole set, so a label you leave out is removed. Pass each label
|
|
284
|
+
single-quoted, exactly as `Labels:` shows it; a label holding a `'` or a line break cannot be
|
|
285
|
+
single-quoted and is never put in a command: skip the issue and name it in your summary
|
|
286
|
+
(`<KEY>: label <label> cannot be quoted`) so a person hands it over themselves.
|
|
242
287
|
|
|
243
288
|
```text
|
|
244
|
-
dispatch issue-update --issue <KEY> --label <each current label> --label legion
|
|
289
|
+
dispatch issue-update --issue <KEY> --label '<each current label>' --label legion
|
|
245
290
|
```
|
|
246
291
|
|
|
247
292
|
2. It is already in `todo`, so the label admits it: the daemon records it and gives it the free
|
|
@@ -324,11 +369,15 @@ priority first, then board rank ([Keeping the slots full](#keeping-the-slots-ful
|
|
|
324
369
|
|
|
325
370
|
- **Direct user message always first.** If this turn includes a direct user message, answer
|
|
326
371
|
it before handling every other wake.
|
|
372
|
+
- **Sami's rulings.** A direct Dispatch message from Sami is a ruling: after answering it, run
|
|
373
|
+
the full [Backlog stewardship](#backlog-stewardship) reconciliation and record the current rule
|
|
374
|
+
in the weekly plan.
|
|
327
375
|
- **One wake = one turn.** Handle exactly the wake's implication, then end the turn. Never
|
|
328
376
|
poll, idle-loop, or wait for another event. Two additions, after any direct user message: every
|
|
329
377
|
turn first rechecks the [trees waiting on a root
|
|
330
378
|
claim](#trees-waiting-on-a-root-claim-go-daemon), and your first turn of each UTC day, whatever
|
|
331
|
-
woke you, also
|
|
379
|
+
woke you, also runs the [Backlog stewardship](#backlog-stewardship) reconciliation and posts the
|
|
380
|
+
day's report ([Daily report](#daily-report-go-daemon)).
|
|
332
381
|
- **Wakes are advisory.** Before any side effect, verify the current daemon state and the
|
|
333
382
|
relevant Dispatch issue. A stale or duplicate wake may cost a read, never a wrong action.
|
|
334
383
|
- **Controller state is disposable.** Do not reconstruct or preserve local controller
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Backlog stewardship
|
|
2
|
+
|
|
3
|
+
The controller keeps the Dispatch backlog's facts aligned with the deployment's current weekly
|
|
4
|
+
plan. The plan is the source of the work order and its one-owner map; Dispatch priority and board
|
|
5
|
+
rank are the executable form of that order. The deployment instructions name the weekly-planning
|
|
6
|
+
issue and its `plan-week-*` artifact.
|
|
7
|
+
|
|
8
|
+
## Controller routing address
|
|
9
|
+
|
|
10
|
+
The controller keeps its startup role, `legion-<project>-controller`; it never claims
|
|
11
|
+
`backlog-order`, which is retired. Send READY packets, scope questions, and routing requests to
|
|
12
|
+
`notifications.role.legion-<project>-controller`. Do not add another role claim or manual
|
|
13
|
+
subscription: changing roles ends the controller's wake delivery.
|
|
14
|
+
|
|
15
|
+
## Reconcile the weekly plan
|
|
16
|
+
|
|
17
|
+
Run this pass on the first turn of every UTC day and after every ruling from Sami:
|
|
18
|
+
|
|
19
|
+
1. Read the weekly-planning issue and select its newest `plan-week-*` artifact by its latest
|
|
20
|
+
`artifact.version` event. Read that artifact before changing any issue.
|
|
21
|
+
2. For every current plan line in this deployment's project, make Dispatch's sort order exactly
|
|
22
|
+
match the line order: set the coarse priority and board rank together so the admission rule
|
|
23
|
+
(highest priority, then board rank) produces the plan's order. Keep each line's own stated
|
|
24
|
+
sequence. Do not invent a second order from a title, a stale route, or an instruction.
|
|
25
|
+
Reorder through Dispatch's board-rank API described in
|
|
26
|
+
`skill://dispatch/references/issues.md#board-rank-priority-and-assignee-on-a-read`.
|
|
27
|
+
3. Reconcile the plan's one-owner map. For each listed owner, verify the named session is live.
|
|
28
|
+
For unowned work, find a suitable live owner with `envoy_sessions` filtered by `title` or `dir`,
|
|
29
|
+
route the issue to that exact session, and tell it what it owns. Do not change the human
|
|
30
|
+
`Assignee:` field: it controls who receives asks, not the working session.
|
|
31
|
+
4. Rewrite the plan row with `dispatch doc-edit` guarded by the document token from the current
|
|
32
|
+
`dispatch doc-read`. The artifact states the current rule; replace superseded text instead of
|
|
33
|
+
keeping a change log in it. A comment or message does not update the one-owner map.
|
|
34
|
+
|
|
35
|
+
A ruling from Sami is a direct Dispatch message to this controller. Answer it first, carry out its
|
|
36
|
+
scope or priority decision, then record the resulting current rule in the affected weekly-plan row
|
|
37
|
+
(or its ruling section when it has no one line). Link the message when Dispatch gave it a reference.
|
|
38
|
+
A ruling triggers the full reconciliation pass even when it appears to affect only one issue.
|
|
39
|
+
|
|
40
|
+
## Answering controller deliveries
|
|
41
|
+
|
|
42
|
+
Read every delivery to `notifications.role.legion-<project>-controller` against live Dispatch and
|
|
43
|
+
daemon state. Answer scope questions from the weekly plan and deployment instructions. Route a
|
|
44
|
+
request to the verified owner when it is not the controller's decision.
|
|
45
|
+
|
|
46
|
+
Until the deployment's daemon arms READY pull requests, answer each READY packet with its current
|
|
47
|
+
queue or hold outcome and route it to the live owner named by the deployment's rules. Never merge
|
|
48
|
+
or use a GitHub credential from the controller. Once the daemon arms READY pull requests, the
|
|
49
|
+
controller confirms the Dispatch record and leaves the arm to the daemon.
|
|
50
|
+
|
|
51
|
+
## Recovering stopped-session work
|
|
52
|
+
|
|
53
|
+
Treat a session route as stopped only after the owner audit's two `no_holder` reads at least ten
|
|
54
|
+
minutes apart. Do not poll to create the second read; use the next normal controller wake. Confirm
|
|
55
|
+
that no live owner is available with `envoy_sessions` before changing the issue.
|
|
56
|
+
|
|
57
|
+
For work that the plan or its handoff says moves to Legion when its session stops, preserve its
|
|
58
|
+
existing labels, add `legion`, then use `legion status <KEY> todo`. The label comes first: it is what
|
|
59
|
+
lets the daemon admit the root. Do not apply that recovery to an issue already in an active Legion
|
|
60
|
+
tree; its architect and daemon own its lifecycle. Update the weekly-plan owner row after the move.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjawhar/pi-legion",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.14.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"omp": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
]
|
|
13
13
|
},
|
|
14
14
|
"legion": {
|
|
15
|
-
"daemonApiVersion":
|
|
15
|
+
"daemonApiVersion": 20
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|