@sjawhar/opencode-legion-envoy 8.0.0 → 8.2.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/dispatch.js +7 -7
- package/dist/src/server.js +2 -2
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +24 -24
- package/skills/dispatch/references/documents.md +21 -1
- package/skills/legion-architect/SKILL.md +23 -9
- package/skills/legion-worker/SKILL.md +8 -5
- package/skills/legion-worker/references/merge-gate.md +38 -20
- package/skills/legion-worker/references/review-threads.md +5 -8
package/dist/dispatch.js
CHANGED
|
@@ -14290,12 +14290,12 @@ var dispatchToolSpecs = [
|
|
|
14290
14290
|
{
|
|
14291
14291
|
name: "dispatch_request_approval",
|
|
14292
14292
|
example: { issue: "DSP-1", summary: "A live sync replaces the nightly export." },
|
|
14293
|
-
description: "Ask a human to approve a document at its current version. Before calling, have a fresh read-only reader check the spec against the code and the human's words, then resolve every finding; a Legion architect uses `plan-gap-analyst`. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. An open request follows the document: a " + "later version moves it to that version and leaves it waiting on you, as a human's reply in " + "its thread does. Only the first move since the request was opened or handed back sends an " + "event, and never to the session whose version made it; dispatch doc-read shows whom it " + "waits on. Once the revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", call this again to hand that same Inbox row back. The request carries nothing new. A " + "call while it already waits on the human hands nothing back: the same summary changes " + "nothing, and a different one is refused, since it would rewrite the card the human is " + "reading. Approve and Request changes each close the request, so the next call opens a new " + "one. Call it once per revision, when the revision is complete, never after each edit. An " + "approval goes stale when the document changes after it: request approval again once that " + "
|
|
14293
|
+
description: "Ask a human to approve a document at its current version. Before calling, have a fresh read-only reader check the spec against the code and the human's words, then resolve every finding; a Legion architect uses `plan-gap-analyst`. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. An open request follows the document: a " + "later version moves it to that version and leaves it waiting on you, as a human's reply in " + "its thread does. Only the first move since the request was opened or handed back sends an " + "event, and never to the session whose version made it; dispatch doc-read shows whom it " + "waits on. Once the revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", call this again to hand that same Inbox row back. The request carries nothing new. A " + "call while it already waits on the human hands nothing back: the same summary changes " + "nothing, and a different one is refused, since it would rewrite the card the human is " + "reading. Approve and Request changes each close the request, so the next call opens a new " + "one. Call it once per revision, when the revision is complete, never after each edit. An " + "approval goes stale when the document changes outside its decision blocks after it: " + "request approval again once that revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", with a one-sentence summary naming what changed since the earlier approval: the card " + "names that approval (version, date, reviewer) and its Show diff control carries the detail. " + "A human's own answer to a decision block carries the approval to the version it writes when " + "that answer is the whole change since the approved version, so nothing is requested for " + "it. A new version of a Legion root spec closes its armed design gate until a human " + "approves it; the carried approval reopens it too. " + "Refused, with nothing sent, while the document holds an open decision block, even when a " + "human asked for approval: the refusal names each block; ask the human to answer or waive " + "it first. " + OWNER_REFERENCE,
|
|
14294
14294
|
arguments: (z2) => ({
|
|
14295
14295
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14296
14296
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
14297
14297
|
artifact: z2.string().describe("Project document artifact id, slug, or filename; primary document by default for an issue.").optional(),
|
|
14298
|
-
summary: z2.string({ min: 1 }).describe(`What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question. Request approval only once ${HUMAN_AGREED_TO_DOCUMENT}.`)
|
|
14298
|
+
summary: z2.string({ min: 1 }).describe(`What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question; after an earlier approval, one sentence naming what changed since it, since the card shows that approval and a diff. Request approval only once ${HUMAN_AGREED_TO_DOCUMENT}.`)
|
|
14299
14299
|
}),
|
|
14300
14300
|
validation: documentOwnerValidation(true)
|
|
14301
14301
|
},
|
|
@@ -16785,7 +16785,7 @@ function approvalLine(artifact) {
|
|
|
16785
16785
|
case "approved":
|
|
16786
16786
|
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}`;
|
|
16787
16787
|
case "stale":
|
|
16788
|
-
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"},
|
|
16788
|
+
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}, changed since (now v${approval.latest_version}) - the human's own answer to a decision block carries the approval when that answer is the whole change since v${approval.version}; for any other change, request approval again once the human has agreed to every point in this version, with a --summary naming what changed since v${approval.version}`;
|
|
16789
16789
|
case "changes_requested":
|
|
16790
16790
|
return `Approval: changes requested on v${approval.version} by ${approval.by?.id ?? "unknown"}: ${approval.reason ?? ""}`;
|
|
16791
16791
|
}
|
|
@@ -17234,7 +17234,7 @@ async function refuseOpenDecisionBlocks(client, tool, resolved) {
|
|
|
17234
17234
|
const asks = new Map(documentAsks.map((ask) => [ask.block_id, ask]));
|
|
17235
17235
|
const lines = version2.markdown.split(`
|
|
17236
17236
|
`);
|
|
17237
|
-
const stillOpen = (named, state) => state === "answered" ? `${named}, answered but still open in version ${latest}: it settles into a version on its own in about two seconds; wait
|
|
17237
|
+
const stillOpen = (named, state) => state === "answered" ? `${named}, answered but still open in version ${latest}: it settles into a version on its own in about two seconds; wait for that version` : `${named}, ${state} but still open in version ${latest}: write the decision into the text with dispatch doc-edit, which writes a version that carries it`;
|
|
17238
17238
|
const open = blocks.flatMap((block) => {
|
|
17239
17239
|
const ask = asks.get(block.id);
|
|
17240
17240
|
const openers = lines.filter((line) => line.includes(`ask{#${block.id} `) || line.includes(`ask{#${block.id}}`));
|
|
@@ -17263,7 +17263,7 @@ async function refuseOpenDecisionBlocks(client, tool, resolved) {
|
|
|
17263
17263
|
throw new Error([
|
|
17264
17264
|
`dispatch ${commandName(tool)} was not called: ${artifact.name} (version ${latest}) has ${count}. Answering one writes a new version, which would move this request to that version and leave it waiting on you.`,
|
|
17265
17265
|
...open.map((line) => `- ${line}`),
|
|
17266
|
-
"Do not request approval over an open block, even when a human asked for it. Tell the human which block is open and ask them to answer it or to waive it. Once it is answered, it settles into a version on its own in about two seconds
|
|
17266
|
+
"Do not request approval over an open block, even when a human asked for it. Tell the human which block is open and ask them to answer it or to waive it. Once it is answered, it settles into a version on its own in about two seconds. When the human approved an earlier version and their answer is the whole change since it, Dispatch carries the approval to the new version in their name: request nothing. When the answer changes design text outside the block, rewrite that text (never to restate the answer), then request approval again once the human has agreed to every point in the new version, with a one-sentence --summary naming what changed since the earlier approval. On a document nobody has approved, wait for that version, then request. If they waive it, close the block with dispatch resolve-ask (--kind resolved, their words as the --reason), write their decision into the text with dispatch doc-edit, then request approval again once the human has agreed to every point in the new version. The call opens the request, or hands an open one back to the human."
|
|
17267
17267
|
].join(`
|
|
17268
17268
|
`));
|
|
17269
17269
|
}
|
|
@@ -18054,7 +18054,7 @@ ${trailer.join(`
|
|
|
18054
18054
|
});
|
|
18055
18055
|
if (result.ask === null) {
|
|
18056
18056
|
return {
|
|
18057
|
-
text: `${resolved.artifact.name} (document id ${resolved.artifact.id}) is already approved at version ${result.version} by ${result.approval.by?.id ?? "unknown"}; no new request was opened.
|
|
18057
|
+
text: `${resolved.artifact.name} (document id ${resolved.artifact.id}) is already approved at version ${result.version} by ${result.approval.by?.id ?? "unknown"}; no new request was opened. A change outside the decision blocks after approval makes it stale, so request again only for such a version, once the human has agreed to every point in it, with a --summary naming what changed since version ${result.version}; the human's own answer to a decision block carries the approval when that answer is the whole change.`,
|
|
18058
18058
|
details: {
|
|
18059
18059
|
...resolved.owner.kind === "project" ? documentResultDetails(resolved.artifact) : { issue: resolved.issue?.key },
|
|
18060
18060
|
artifact: resolved.artifact.id,
|
|
@@ -18065,7 +18065,7 @@ ${trailer.join(`
|
|
|
18065
18065
|
const details = await followedAskDetails(client, result.ask, resolved.artifact);
|
|
18066
18066
|
const outcome = result.recorded ? `Approval requested for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}).` : `The approval request for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}) already waits on the human, so this call changed nothing: nothing since it last reached the human (a newer version, a human's reply in its thread, or your progress note) left it waiting on you.`;
|
|
18067
18067
|
return {
|
|
18068
|
-
text: `${outcome} The human's Inbox asks: ${JSON.stringify(result.ask.question)}. The answer arrives as artifact.approved or artifact.changes_requested. An edit before the answer moves this request to the new version and leaves it waiting on you, and
|
|
18068
|
+
text: `${outcome} The human's Inbox asks: ${JSON.stringify(result.ask.question)}. The answer arrives as artifact.approved or artifact.changes_requested. An edit before the answer moves this request to the new version and leaves it waiting on you, and a change outside the decision blocks after approval makes the approval stale: either way, request again for the new version once the human has agreed to every point in it, which hands this request back or opens a new one, with a --summary naming what changed since the earlier approval. The human's own answer to a decision block carries the approval when that answer is the whole change since the approved version, and answers an open request Approve in their name: request nothing then.`,
|
|
18069
18069
|
details: { ...details, artifact: resolved.artifact.id, version: result.version }
|
|
18070
18070
|
};
|
|
18071
18071
|
}
|
package/dist/src/server.js
CHANGED
|
@@ -14147,12 +14147,12 @@ var dispatchToolSpecs = [
|
|
|
14147
14147
|
{
|
|
14148
14148
|
name: "dispatch_request_approval",
|
|
14149
14149
|
example: { issue: "DSP-1", summary: "A live sync replaces the nightly export." },
|
|
14150
|
-
description: "Ask a human to approve a document at its current version. Before calling, have a fresh read-only reader check the spec against the code and the human's words, then resolve every finding; a Legion architect uses `plan-gap-analyst`. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. An open request follows the document: a " + "later version moves it to that version and leaves it waiting on you, as a human's reply in " + "its thread does. Only the first move since the request was opened or handed back sends an " + "event, and never to the session whose version made it; dispatch doc-read shows whom it " + "waits on. Once the revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", call this again to hand that same Inbox row back. The request carries nothing new. A " + "call while it already waits on the human hands nothing back: the same summary changes " + "nothing, and a different one is refused, since it would rewrite the card the human is " + "reading. Approve and Request changes each close the request, so the next call opens a new " + "one. Call it once per revision, when the revision is complete, never after each edit. An " + "approval goes stale when the document changes after it: request approval again once that " + "
|
|
14150
|
+
description: "Ask a human to approve a document at its current version. Before calling, have a fresh read-only reader check the spec against the code and the human's words, then resolve every finding; a Legion architect uses `plan-gap-analyst`. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. An open request follows the document: a " + "later version moves it to that version and leaves it waiting on you, as a human's reply in " + "its thread does. Only the first move since the request was opened or handed back sends an " + "event, and never to the session whose version made it; dispatch doc-read shows whom it " + "waits on. Once the revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", call this again to hand that same Inbox row back. The request carries nothing new. A " + "call while it already waits on the human hands nothing back: the same summary changes " + "nothing, and a different one is refused, since it would rewrite the card the human is " + "reading. Approve and Request changes each close the request, so the next call opens a new " + "one. Call it once per revision, when the revision is complete, never after each edit. An " + "approval goes stale when the document changes outside its decision blocks after it: " + "request approval again once that revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", with a one-sentence summary naming what changed since the earlier approval: the card " + "names that approval (version, date, reviewer) and its Show diff control carries the detail. " + "A human's own answer to a decision block carries the approval to the version it writes when " + "that answer is the whole change since the approved version, so nothing is requested for " + "it. A new version of a Legion root spec closes its armed design gate until a human " + "approves it; the carried approval reopens it too. " + "Refused, with nothing sent, while the document holds an open decision block, even when a " + "human asked for approval: the refusal names each block; ask the human to answer or waive " + "it first. " + OWNER_REFERENCE,
|
|
14151
14151
|
arguments: (z2) => ({
|
|
14152
14152
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14153
14153
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
14154
14154
|
artifact: z2.string().describe("Project document artifact id, slug, or filename; primary document by default for an issue.").optional(),
|
|
14155
|
-
summary: z2.string({ min: 1 }).describe(`What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question. Request approval only once ${HUMAN_AGREED_TO_DOCUMENT}.`)
|
|
14155
|
+
summary: z2.string({ min: 1 }).describe(`What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question; after an earlier approval, one sentence naming what changed since it, since the card shows that approval and a diff. Request approval only once ${HUMAN_AGREED_TO_DOCUMENT}.`)
|
|
14156
14156
|
}),
|
|
14157
14157
|
validation: documentOwnerValidation(true)
|
|
14158
14158
|
},
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -213,11 +213,11 @@ field, and choosing your next issue are in [Working an issue](skill://dispatch/r
|
|
|
213
213
|
## Asking
|
|
214
214
|
|
|
215
215
|
Two kinds of question reach a human. A decision about the design a document records is a decision
|
|
216
|
-
block in that document ([Decision blocks](#decision-blocks)),
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
the block (`skill://legion-worker`). A to-do or permission only a human can give, or
|
|
220
|
-
with no document to live in, is a `dispatch ask`. The gates below apply to both.
|
|
216
|
+
block in that document ([Decision blocks](#decision-blocks)), at every phase, approved or not: a
|
|
217
|
+
block in an approved document leaves the approval stale until the answer, which carries it
|
|
218
|
+
forward when it is the whole change (below). A Legion phase worker sends it to its architect,
|
|
219
|
+
which writes the block (`skill://legion-worker`). A to-do or permission only a human can give, or
|
|
220
|
+
a decision with no document to live in, is a `dispatch ask`. The gates below apply to both.
|
|
221
221
|
|
|
222
222
|
### Before you ask
|
|
223
223
|
|
|
@@ -368,33 +368,33 @@ See [Following](#following) for why you receive what happens to asks you open an
|
|
|
368
368
|
## Approval of a spec
|
|
369
369
|
|
|
370
370
|
Approval is a property of a document, not a question you phrase: a human approves a specific
|
|
371
|
-
version, the way a pull-request review approves a commit, and
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
371
|
+
version, the way a pull-request review approves a commit, and a later version leaves that approval
|
|
372
|
+
stale, unless the human's own answer to a decision block is the whole change since the version
|
|
373
|
+
they approved; then Dispatch records an `approved` review in their name at that version and emits
|
|
374
|
+
`artifact.approved`, so request nothing. When the answer changes text outside its block, rewrite
|
|
375
|
+
that text, run the fresh reader below on it and request again. A later version also moves an open
|
|
376
|
+
request with it, waiting on you until you request again. Request only when all three hold: every
|
|
377
|
+
decision block is settled (answered or waived, below); the human has agreed to every point in the
|
|
378
|
+
spec; and the current version has not been approved. A Legion root spec under an armed design gate
|
|
379
|
+
always goes to approval once all three hold (`skill://legion-architect`).
|
|
379
380
|
|
|
380
381
|
Before calling `dispatch request-approval`, have a fresh read-only reader check the current spec
|
|
381
|
-
against the code and the human's words, then resolve every finding. A Legion architect uses
|
|
382
|
-
`plan-gap-analyst
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
`--reason`), then write their decision into the text in their words and request.
|
|
389
|
-
`dispatch request-approval` refuses while any block is open.
|
|
382
|
+
against the code and the human's words, then resolve every finding. A Legion architect uses
|
|
383
|
+
`plan-gap-analyst`, another session the read-only reader its host supplies. When all three hold,
|
|
384
|
+
request in the pass that finishes the spec: a design waiting with nothing in the human's Inbox
|
|
385
|
+
waits on nobody. When a human asks for approval while a block is open, neither request it nor hold
|
|
386
|
+
it silently: name each open block and ask them to answer or waive it. For a waiver, close it with
|
|
387
|
+
`dispatch resolve-ask --kind resolved` (their words as `--reason`), write their decision into the
|
|
388
|
+
text in their words and request. `dispatch request-approval` refuses while any block is open.
|
|
390
389
|
|
|
391
390
|
An approval request carries nothing new: a point the human has not agreed to gets its own decision
|
|
392
391
|
block first, with your recommendation, or, when it is a choice you can make yourself
|
|
393
392
|
([Before you ask](#before-you-ask), gate 1) and the human will not want a say in it, comes out of
|
|
394
393
|
the spec and is made where the work happens; an inference you cannot defend in a decision block
|
|
395
394
|
comes out of the spec. `--summary` says in one to three sentences what the human is approving and
|
|
396
|
-
nothing else: no commentary on itself or on the conversation, and no open question
|
|
397
|
-
|
|
395
|
+
nothing else: no commentary on itself or on the conversation, and no open question; after an
|
|
396
|
+
earlier approval, one sentence naming what changed since it, since the card shows that approval
|
|
397
|
+
and its `Show diff` control carries the detail. Never write "Approve" options into an ordinary
|
|
398
398
|
`dispatch ask`; only humans approve. The call, its result and its answer:
|
|
399
399
|
[Approval requests](skill://dispatch/references/documents.md#approval-requests).
|
|
400
400
|
|
|
@@ -226,7 +226,27 @@ nothing, and a different `--summary` is refused, since it would rewrite the card
|
|
|
226
226
|
reading. The answer reaches you as `artifact.approved` or `artifact.changes_requested` with the
|
|
227
227
|
pinned `version` and closes the request, so the next call opens a new one; `changes_requested`
|
|
228
228
|
carries the reason, which is your next piece of work. Those reads show the document's approval
|
|
229
|
-
state; `stale` means it was approved and
|
|
229
|
+
state; `stale` means it was approved and changed since, other than as the next paragraph says.
|
|
230
|
+
|
|
231
|
+
A human's own answer to a decision block in a document they approved can carry the approval to
|
|
232
|
+
the version it writes. Dispatch compares that version with the version they approved, every
|
|
233
|
+
decision block cut out of both; when the text outside the blocks is the same and every block added or
|
|
234
|
+
answered since has their answer, it records an `approved` review in their name pinned to the new version and
|
|
235
|
+
emits `artifact.approved`, in the transaction that writes the version, and answers an approval
|
|
236
|
+
request open on the document `Approve` in their name. The answer was the whole change since the
|
|
237
|
+
version they approved, so request nothing: `dispatch doc-read` shows `Approval: approved vN` at
|
|
238
|
+
the new version. An edit inside the block before the answer does not stop this. A change
|
|
239
|
+
outside the blocks, whoever wrote it (an upload or a typed edit included), a block added since the
|
|
240
|
+
approval that the human has not answered (open or waived), a block the approved version held that
|
|
241
|
+
someone else answered or that was waived since, a decided block whose question or options were
|
|
242
|
+
changed (its answer decided the question it was given on), a removed block, a request for changes
|
|
243
|
+
newer than the approval, or a document they never approved leaves the approval stale, and the next
|
|
244
|
+
request shows them the diff: a request on a document a human approved earlier carries
|
|
245
|
+
`approved` (`{version, at, by}`) in `ask.approval`, and its Inbox card puts
|
|
246
|
+
`Approved at version N on <date> by <who>.` and a `Show diff` control under the question, which
|
|
247
|
+
shows what changed between the two versions with every decision block cut out. So that request's
|
|
248
|
+
`--summary` is one sentence naming what changed since that approval, and repeats nothing the diff
|
|
249
|
+
shows.
|
|
230
250
|
|
|
231
251
|
## A document that is reloading
|
|
232
252
|
|
|
@@ -166,13 +166,27 @@ the code and command output it relies on, and the human's words, then resolve ev
|
|
|
166
166
|
**After approval, the root spec changes only when what the tree delivers, or a decision a human
|
|
167
167
|
settled, changes.** Approval is pinned to the spec version: any new version of the root spec closes
|
|
168
168
|
the gate again with no wake (you made the edit, or the `artifact.version` event on your issue tells
|
|
169
|
-
you)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
you), except the version a human's own answer to a decision block writes when that answer is the
|
|
170
|
+
whole change since the version they approved: Dispatch then records an `approved` review in their
|
|
171
|
+
name pinned to the new version and emits `artifact.approved`, and the daemon reopens the gate and
|
|
172
|
+
sends `design-approved`. So edit an approved root spec only when its Summary, its Acceptance, the
|
|
173
|
+
tree's scope, or a decision a human settled in one of its decision blocks changes. Such a change is
|
|
174
|
+
a point the human has not agreed to: put the problem behind it to them as its own decision block,
|
|
175
|
+
with its evidence, at the end of the section it changes, and add nothing else to the spec, since
|
|
176
|
+
the question and its evidence go inside the block. An edit inside the block before the answer
|
|
177
|
+
closes the gate like any version and does not stop the answer carrying the approval. Once they have
|
|
178
|
+
answered, one of two follows. When the answer is the whole change, request nothing: the document's
|
|
179
|
+
chip reads Approved at the new version, its reviews dialog shows the review in their name, and the
|
|
180
|
+
`design-approved` the daemon sent opens nothing new — carry on, and release no wave because of it.
|
|
181
|
+
When the answer changes the Summary, the Acceptance or the tree's scope, rewrite that text and
|
|
182
|
+
repeat steps 3 through 5 on the new version, with a one-sentence `--summary` naming what changed
|
|
183
|
+
since the earlier approval (the card shows that approval, `Approved at version N on <date> by
|
|
184
|
+
<who>.`, and its `Show diff` control carries the detail); the rewrite closes the gate until they
|
|
185
|
+
approve, and what the daemon released at the answer stands. Release no new wave on the
|
|
186
|
+
`design-approved` the answer brought when a rewrite follows, and none until the next
|
|
187
|
+
`design-approved` after the rewrite — work already in flight continues. Every merger's `READY` in
|
|
188
|
+
the tree is refused until a human approves the latest version. A settled decision is the human's.
|
|
189
|
+
A plan that would overturn one goes back to
|
|
176
190
|
the planner with the decision kept, which asks the human nothing, unless the planner brings
|
|
177
191
|
evidence the human did not weigh that would change the decision, such as a measurement showing the
|
|
178
192
|
settled choice cannot meet the Acceptance; then that decision block names the decision and that
|
|
@@ -384,10 +398,10 @@ active phase worker.
|
|
|
384
398
|
| `child-status` | A child of your tree left the workflow or re-entered it; the notice's reason names the child and its new status. `todo` (a human's move, your `release_children`, or your `rerun_child`) means the child runs again under your tree from planning, and the daemon starts it; you start nothing. `backlog`, `icebox` or `triage` (a human's move, or your `park_child`) means the daemon has suspended the child's workers, and it advances no further until it is set back to `todo`. What the rest of the tree does is your decision. |
|
|
385
399
|
| `child-closed` | Read the child completion and remaining open children. Re-scope open work, park obsolete work with `park_child`, and release an appropriate next wave with `release_children`. When a worker waits on this child for a missing surface (see `phase-finished`), tell it to continue with `envoy_publish` to its role topic. The last child's close is not the end-game (section 3). |
|
|
386
400
|
| `child-release-stalled` | Its `reason` names a child your `release_children` released while Dispatch already showed it `todo`, and that child's parent, whose release has not reached the daemon after three tries. Check the parent's record in `legion state`; run `release_children` for the child again once the parent is recorded in your tree, and tell the controller if it never is. |
|
|
387
|
-
| `design-approved` | Its `version` is the approved one. A human approved the root spec document at its current version; the gate is open. Proceed to section 2 — except on a root whose catch-up said `handed_over`: its workers already run their phases, so start nothing; the approval releases any READY the gate refused. |
|
|
401
|
+
| `design-approved` | Its `version` is the approved one. A human approved the root spec document at its current version; the gate is open. Proceed to section 2 — except on a root whose catch-up said `handed_over`: its workers already run their phases, so start nothing; the approval releases any READY the gate refused. One whose `version` is the version a human's own answer to a decision block wrote, after an earlier approval (Dispatch carried that approval to it, section 1), opens nothing new: carry on, and release no wave because of it. |
|
|
388
402
|
| `design-changes-requested` | Its `version` and `reason`. A human asked for changes to the root spec at `version`, for `reason`. Revise the spec as the reason asks and request approval again as section 1 says; stay parked; the gate is closed. |
|
|
389
403
|
| `phase-finished` | The daemon has already moved the issue to its next phase by its fixed table and started that phase's role; you start nothing. Read the committed handoff for the finishing phase; if it shows unresolved gaps, tell the role now working the issue (`envoy_publish` to its role topic). A `planner` notice that names a departure from the spec's design defers to section 1's full condition: when the approved Summary, Acceptance, scope and settled decisions still hold, the plan is the record; otherwise change the root spec and request approval again as section 1 says. A `reviewer` notice whose GitHub review is `CHANGES_REQUESTED` needs nothing from you: the daemon has returned the issue to `implementing` (Dispatch `in_progress`) and started the **implementer**, whose correction goes through the tester and the reviewer again, never straight to retro. A `reviewer` notice with an `APPROVED` review means the daemon has started retro (step 5). An `implementer` notice for `production_check` is its production report: read the record on the pull request and the issue, then run step 7. A `tester` notice with `verdict: "fail"` — its handoff carries `implementerProof.verdict: "rejected"`, or a failure naming the production-like proof — has gone back to the **implementer** by the daemon's table; never supply the proof from another role. A worker that reports no surface reaches the changed path sends that report instead of completing its phase, so the daemon starts nothing more on that issue and the worker stays idle in its session, not suspended: file a child issue in this tree to build the surface (infrastructure, tooling, or a skill), and when that child's `child-closed` arrives, tell the waiting worker to continue with `envoy_publish` to its role topic. That report is never a reason to advance the phase. |
|
|
390
|
-
| `pr-blocked` | Its `reason` names the pull request and the `max_fix_attempts` it reached, or reads `Issue reached review_round_cap=<N>.` (the end of this row). The count is of heads pushed onto a red verdict that changed something outside `.legion/` — handoff-only pushes (`.legion/` paths only) never count, a push by the review App (a planner's, tester's, reviewer's or architect's) never counts, and the head after a red the tester's red tests earned (a review-App push that changed a path outside `.legion/`, however many handoff-only pushes follow it) does not count either — so after the tester's handoff-only push onto the implementer's red, the implementer's next push does count; a push the daemon cannot classify (a listener without `changed_paths`, a list the listener stopped at 100 paths or 32,768 runes of text, a push listing no commits) does. Published once per exhausted count, not on every later red verdict for that count. Read the failed CI evidence and recovery attempts. The notice moves nothing, and no later CI red within the attempt that ran the count out (the counted head, and every head pushed on top of it until a push lands on a head CI judged green, the tester's red tests and handoff onto the held red included) sends the work back from `testing` or `
|
|
404
|
+
| `pr-blocked` | Its `reason` names the pull request and the `max_fix_attempts` it reached, or reads `Issue reached review_round_cap=<N>.` (the end of this row). The count is of heads pushed onto a red verdict that changed something outside `.legion/` — handoff-only pushes (`.legion/` paths only) never count, a push by the review App (a planner's, tester's, reviewer's or architect's) never counts, and the head after a red the tester's red tests earned (a review-App push that changed a path outside `.legion/`, however many handoff-only pushes follow it) does not count either — so after the tester's handoff-only push onto the implementer's red, the implementer's next push does count; a push the daemon cannot classify (a listener without `changed_paths`, a list the listener stopped at 100 paths or 32,768 runes of text, a push listing no commits) does. Published once per exhausted count, not on every later red verdict for that count. Read the failed CI evidence and recovery attempts. The notice moves nothing, and no later CI red within the attempt that ran the count out (the counted head, and every head pushed on top of it until a push lands on a head CI judged green, the tester's red tests and handoff onto the held red included) sends the work back from `testing`, `reviewing`, `retro` or `merging` either: the issue stays in its phase until you move it, or a worker's own decision does (a tester's `fail`, a reviewer's request for changes). A head pushed onto green CI leaves that attempt, so its red still comes as a `checks-red`. In `implementing`, give the implementer the failing checks (`envoy_publish` to its role topic). In any later phase a worker holds, tell that worker (`envoy_publish` to its role topic) to move the issue back to `implementing` with `request_backward_move`, naming the failing checks, and the daemon starts the implementer; or file a corrective child. In `awaiting_merge`, where no worker holds a phase and a backward move is refused, open a `dispatch ask` naming the failing checks for the human who merges, as section 6 does for a conflict there. Do not treat the blocked PR as final. A `review_round_cap` reason is the implementer's round count instead: every move back to an earlier phase counts one, and the notice comes again with each round past the cap. It moves nothing either, since the round that reached the cap has already started its worker. Tell the rounds apart by what announced each: a tester's fail by its `phase-finished` with `verdict: "fail"`; a request for changes by the reviewer's `phase-finished` over a `CHANGES_REQUESTED` review; the daemon's own move by a `checks-red` notice, whose reason names either a conflict (`the head conflicts with <base>`) or the required checks that failed (`CI is red at <head>`); a worker's `request_backward_move` by a `phase-finished` from the role that asked, whose reason reaches only the task of the worker the move started, so ask that worker. Judge the cap by the rounds that are not conflicts. Conflict returns need nothing from you beyond the next round. Tester fails, requests for changes and CI-red returns (a `checks-red` notice is raised for CI only when a required check fails on code the branch pushed) are the work going in circles: narrow it, re-plan it with the responsible worker, or ask a human with `dispatch ask`. Judge a worker's backward move by the reason that worker gives. A move you ordered for a conflict found before `awaiting_merge` (section 6) is a conflict return. The merger's move back to `reviewing` because its task names no approved head is not the work going in circles either. A move that sends back code found wrong counts with the tester fails. |
|
|
391
405
|
| `pr-merged` | Its `reason` names the pull request. The PR merged, under the repository's rules, before the issue reached `awaiting_merge`. The workflow runs on and asks no one to merge it: the daemon starts the **implementer** on the production check once the issue gets there, and you start nothing. Its `phase-finished` for `production_check` is what brings you to step 7: verify the record on the pull request and this issue first, then sign off naming it with `sign_off`. A merge is not the close. |
|
|
392
406
|
| `pr-closed-unmerged` | Decide from current scope whether the work is reopened, started over (`park_child` then `rerun_child`, for a child), or ended with a reason. Delegate the repository action to the responsible phase worker and keep ownership. |
|
|
393
407
|
| A reply on an ask you follow | Interpret the reply in the issue's design context. Answer in its thread (`dispatch comment --reply-to <comment id>`; under an open ask whose next move is yours, such as the approval request you must revise or hand back, `--reply-to-ask <ask id> --turn agent`, since a default-turn reply hands that request back to the human and a corrected `--summary` is then refused), then adjust the plan or relay it via `envoy_publish` to the responsible worker's role token; scope and product decisions remain with you. |
|
|
@@ -118,9 +118,12 @@ turn, including one an Envoy question starts: you are kept alive to answer quest
|
|
|
118
118
|
editing. Do not create new commits, run `jj -R "$LEGION_WORKSPACE" new`, or touch tracked files
|
|
119
119
|
once your own handoff is committed and pushed — a code change belongs to
|
|
120
120
|
whichever phase is active now. The same holds once the Go daemon has taken your phase back without
|
|
121
|
-
your completion: CI settling red while you tested,
|
|
122
|
-
moves the issue to `implementing` and interrupts your turn, and the implementer starts
|
|
123
|
-
that turn has ended. Do not resume the interrupted work afterward.
|
|
121
|
+
your completion: CI settling red while you tested, reviewed a round you had not completed, or
|
|
122
|
+
built READY, moves the issue to `implementing` and interrupts your turn, and the implementer starts
|
|
123
|
+
only once that turn has ended. Do not resume the interrupted work afterward. From retro the
|
|
124
|
+
issue moves the same way with nothing interrupted: you are the implementer, handed the implementing
|
|
125
|
+
task once your turn ends; a retro completion started after the move is refused, and one in flight
|
|
126
|
+
when the red lands can be taken as your implementing completion, caught by the tester's CI read.
|
|
124
127
|
|
|
125
128
|
On every start, and especially after revival or re-creation, read the issue and then the
|
|
126
129
|
committed predecessor handoffs in lifecycle order, with the `read` tool from
|
|
@@ -437,8 +440,8 @@ record of this issue's active phase. Do not add pipeline labels, run a controlle
|
|
|
437
440
|
invent a different completion protocol — this is the whole contract.
|
|
438
441
|
|
|
439
442
|
A reviewer's phase ends with its completion, not with its review; the order of a review round
|
|
440
|
-
(the
|
|
441
|
-
the completion) is in `skill://legion-worker/references/merge-gate.md`.
|
|
443
|
+
(the read of the code commit's checks on GitHub; the handoff push; the review of the head that
|
|
444
|
+
push made; then the completion) is in `skill://legion-worker/references/merge-gate.md`.
|
|
442
445
|
|
|
443
446
|
**A refused completion is information, not a retry loop.** The daemon attributes your report to
|
|
444
447
|
the run whose task you took, and answers with what it found. What each answer carries, and what to
|
|
@@ -20,7 +20,33 @@ the head voids it.
|
|
|
20
20
|
- The reviewer verifies the `CI`, `Threads`, and `E2E` facts against GitHub directly —
|
|
21
21
|
never from a handoff — then runs `task(agent="thermonuclear-deep-review")` and
|
|
22
22
|
`task(agent="thermonuclear-code-quality")` once at that head — the head the round reviews —
|
|
23
|
-
and records the verdict.
|
|
23
|
+
and records the verdict. The `CI` fact is the checks that stand for the code under review:
|
|
24
|
+
those GitHub ran for the newest push of the branch that started `pull_request` workflows, when
|
|
25
|
+
every commit above that push's head changes only `.legion/` (a push that changes only
|
|
26
|
+
`.legion/` carries `skip-checks: true`, so GitHub runs no workflow for it). With `<o>/<r>`,
|
|
27
|
+
`<n>`, `<base>`, `<headRefName>` and `<headRefOid>` from
|
|
28
|
+
`gh pr view <n> --json baseRefName,headRefOid,headRefName`, four reads. The required set, from
|
|
29
|
+
the base branch's rulesets
|
|
30
|
+
(`gh api repos/<o>/<r>/rules/branches/<base> --jq '.[] | select(.type=="required_status_checks") | .parameters.required_status_checks[].context'`)
|
|
31
|
+
and its branch protection, when it has any
|
|
32
|
+
(`gh api repos/<o>/<r>/branches/<base> --jq '.protection.required_status_checks.checks[].context'`);
|
|
33
|
+
a read GitHub refuses is reported to the architect, never guessed. The CI head, the head of the
|
|
34
|
+
branch's newest `pull_request` run
|
|
35
|
+
(`gh api 'repos/<o>/<r>/actions/runs?branch=<headRefName>&event=pull_request&per_page=30' --jq '.workflow_runs[0].head_sha'`,
|
|
36
|
+
newest first); the PR body's `CI:` line names runs at that same commit
|
|
37
|
+
(`gh run view <run-id> --json headSha,status,conclusion`), or the line is stale and the review
|
|
38
|
+
says so. That commit's check-runs
|
|
39
|
+
(`gh api repos/<o>/<r>/commits/<ci head>/check-runs --paginate --jq '.check_runs[] | [.name, .status, .conclusion] | @tsv'`):
|
|
40
|
+
every required name present, `completed` and `success` (GitHub counts `neutral` and `skipped`
|
|
41
|
+
as passing for a required check). And the compare proving only `.legion/` changed since
|
|
42
|
+
(`gh api repos/<o>/<r>/compare/<ci head>...<headRefOid> --jq '[.files[].filename] | all(startswith(".legion/"))'`
|
|
43
|
+
prints `true`; `false` means code was pushed since CI ran, so wait for the new push's runs and
|
|
44
|
+
read the CI head again). A required check `queued` or `in_progress`: wait. One `completed` as
|
|
45
|
+
`cancelled`, or absent: `gh run rerun <run-id>` once, then wait; when nothing will run it (a
|
|
46
|
+
check only a commit status reports, a workflow another repository defines), tell the architect
|
|
47
|
+
and do not approve. `gh pr checks <n>` is not the read: it reads only the current head, which
|
|
48
|
+
at a handoff head shows only the `pr-title` runs the review events start (measured on
|
|
49
|
+
sjawhar/legion#1944 at 0220b94fc2 on 2026-10-14).
|
|
24
50
|
Approval is refused while either `E2E (implementer)` or `E2E (tester)` is missing: `REQUEST_CHANGES` naming the missing line.
|
|
25
51
|
Skip the `Thermo` line entirely on a docs-only PR. Submit **one review per round** —
|
|
26
52
|
`REQUEST_CHANGES` when any correctness finding stands, otherwise `APPROVE` of the head you
|
|
@@ -42,25 +68,17 @@ the head voids it.
|
|
|
42
68
|
when every thread is settled enough to approve, and resolving one never gates your approval.
|
|
43
69
|
|
|
44
70
|
A reviewer's phase ends with its completion, not with its review. Every round writes a handoff
|
|
45
|
-
and takes this order: write, commit and push the handoff; submit the review of
|
|
46
|
-
made, by its SHA; then complete.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
reviewing
|
|
56
|
-
READY checks: red when one of them failed, and never red for a check the base branch does not
|
|
57
|
-
require. A required check that was cancelled, or that the head's checks
|
|
58
|
-
settled without, leaves no verdict until a later settlement decides it, since a run can be
|
|
59
|
-
cancelled or not yet queued when the head settles; a required workflow's run on the head that is
|
|
60
|
-
still going or has not happened leaves none either. A review of a head the handoff push
|
|
61
|
-
then replaces names a head the pull request no longer has. The daemon moves the issue
|
|
62
|
-
once both are in — the decision GitHub reports and your completion, in either order — so a
|
|
63
|
-
review posted without a completion leaves the issue in reviewing until you finish.
|
|
71
|
+
and takes this order: read CI as above; write, commit and push the handoff; submit the review of
|
|
72
|
+
the head that push made, by its SHA; then complete. A red at the code is a `REQUEST_CHANGES`
|
|
73
|
+
naming the check, never an approval. A request for changes does not wait, since it stands
|
|
74
|
+
whatever CI says and the issue leaves reviewing with it. The approval waits on nothing at the
|
|
75
|
+
handoff commit either: it ends the round whatever CI says there, since the round reads no CI
|
|
76
|
+
verdict, and a red the daemon reads later — at that commit or any above it — is the code's red
|
|
77
|
+
and resumes the implementer from wherever the issue is (reviewing, retro or merging) without the
|
|
78
|
+
reviewer. A review of a head the handoff push then replaces names a head the pull request no
|
|
79
|
+
longer has. The daemon moves the issue once both are in — the decision GitHub reports and your
|
|
80
|
+
completion, in either order — so a review posted without a completion leaves the issue in
|
|
81
|
+
reviewing until you finish.
|
|
64
82
|
|
|
65
83
|
## Retro
|
|
66
84
|
|
|
@@ -67,11 +67,8 @@ opened that is none of Legion's role Apps, with one reply in your own words. A b
|
|
|
67
67
|
cannot accept becomes your own: say what stands and request changes. Approve once each of those
|
|
68
68
|
threads carries your answer and no finding stands, read in `gh api graphql` — quote those answers
|
|
69
69
|
in the approval. Your approval never waits on a resolution: resolution is the pull request author's
|
|
70
|
-
App's
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
that push starts reads the threads as the implementer left them; then re-run the failed run once,
|
|
76
|
-
as your role prompt says, before you approve. The merger resolves nothing: READY requires no thread
|
|
77
|
-
state beyond what your approval already judged.
|
|
70
|
+
App's. A review bot that is red while a thread it opened still stands is CI like any other, and
|
|
71
|
+
you re-run nothing: when the base branch requires its check, that red sends the work back to the
|
|
72
|
+
implementer, who answers or fixes each of the bot's threads, resolves them with its own `gh` (the
|
|
73
|
+
ones you named to it, above, and its own), and pushes, and the bot runs again on that push. The
|
|
74
|
+
merger resolves nothing: READY requires no thread state beyond what your approval already judged.
|