@sjawhar/claude-envoy 1.11.3 → 1.12.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.
@@ -2,5 +2,5 @@
2
2
  "name": "claude-envoy",
3
3
  "description": "Deliver Envoy events as Claude Code channel notifications and expose Envoy MCP tools.",
4
4
  "mcpServers": "./.mcp.json",
5
- "version": "1.11.3"
5
+ "version": "1.12.0"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-envoy",
3
- "version": "1.11.3",
3
+ "version": "1.12.0",
4
4
  "description": "Deliver Envoy events as Claude Code channel notifications and expose Envoy MCP tools.",
5
5
  "mcpServers": {}
6
6
  }
package/dist/dispatch.js CHANGED
@@ -16849,6 +16849,7 @@ var LEGION_ROLES = [
16849
16849
  "reviewer",
16850
16850
  "merger"
16851
16851
  ];
16852
+ var GATED_STEPS = ["decisions", "criteria", "spec"];
16852
16853
  var PROJECT_TOKEN = /^[a-z0-9]+$/;
16853
16854
  var DISPATCH_KEY_PATTERN = /^([A-Z][A-Z0-9]*)-([0-9]+)$/;
16854
16855
  function roleForPhase(phase) {
@@ -38498,6 +38498,7 @@ var LEGION_ROLES = [
38498
38498
  "reviewer",
38499
38499
  "merger"
38500
38500
  ];
38501
+ var GATED_STEPS = ["decisions", "criteria", "spec"];
38501
38502
  var PROJECT_TOKEN = /^[a-z0-9]+$/;
38502
38503
  var DISPATCH_KEY_PATTERN = /^([A-Z][A-Z0-9]*)-([0-9]+)$/;
38503
38504
  function roleForPhase(phase) {
@@ -52032,6 +52033,15 @@ var legionGateView = object({
52032
52033
  currentVersion: number2().int().positive(),
52033
52034
  approvedVersion: number2().int().positive().nullable()
52034
52035
  });
52036
+ var legionDesignStep = _enum2(GATED_STEPS);
52037
+ var legionGatedStepView = object({
52038
+ step: legionDesignStep,
52039
+ artifactId: nonEmptyString.optional(),
52040
+ currentVersion: number2().int().positive().optional(),
52041
+ approvedVersion: number2().int().positive().nullable(),
52042
+ status: _enum2(["unregistered", "waiting", "approved", "changes_requested"]),
52043
+ changesReason: nonEmptyString.optional()
52044
+ });
52035
52045
  var legionSlotView = object({
52036
52046
  index: number2().int().nonnegative(),
52037
52047
  admittedAt: timestamp,
@@ -52119,6 +52129,7 @@ var legionIssue = object({
52119
52129
  workers: partialRecord(string2(), legionPhaseView),
52120
52130
  pullRequest: legionPullRequestView.optional(),
52121
52131
  designGate: legionGateView.optional(),
52132
+ gatedSteps: array(legionGatedStepView).optional(),
52122
52133
  slot: legionSlotView.optional(),
52123
52134
  waitingOn: legionWaitingOn.optional(),
52124
52135
  check: legionCheckView.optional(),
@@ -52301,7 +52312,7 @@ var LegionControllerRegisterResponse = object({
52301
52312
  });
52302
52313
  var LegionControllerSecretResponse = object({
52303
52314
  secret: nonEmptyString,
52304
- designGate: _enum2(["root-issues", "off"])
52315
+ designGateSteps: array(legionDesignStep)
52305
52316
  });
52306
52317
  var LegionControllerCredentialResponse = object({
52307
52318
  owners: record(nonEmptyString, object({
@@ -52405,7 +52416,8 @@ var LegionGateRegisterRequest = strictObject({
52405
52416
  grantId: nonEmptyString,
52406
52417
  issue: nonEmptyString,
52407
52418
  artifactId: uuid2(),
52408
- version: number2().int().positive()
52419
+ version: number2().int().positive(),
52420
+ step: legionDesignStep.optional()
52409
52421
  });
52410
52422
  var LegionWaveReleaseRequest = strictObject({
52411
52423
  grantId: nonEmptyString,
@@ -55133,9 +55145,9 @@ var import_nats2 = __toESM(require_mod4(), 1);
55133
55145
 
55134
55146
  // package.json
55135
55147
  var name = "@sjawhar/claude-envoy";
55136
- var version2 = "1.11.3";
55148
+ var version2 = "1.12.0";
55137
55149
  var legion = {
55138
- daemonApiVersion: 54
55150
+ daemonApiVersion: 55
55139
55151
  };
55140
55152
 
55141
55153
  // src/channel-forwarder.ts
@@ -15848,6 +15848,7 @@ var LEGION_ROLES = [
15848
15848
  "reviewer",
15849
15849
  "merger"
15850
15850
  ];
15851
+ var GATED_STEPS = ["decisions", "criteria", "spec"];
15851
15852
  var PROJECT_TOKEN = /^[a-z0-9]+$/;
15852
15853
  var DISPATCH_KEY_PATTERN = /^([A-Z][A-Z0-9]*)-([0-9]+)$/;
15853
15854
  function roleForPhase(phase) {
@@ -29,9 +29,9 @@ You own one issue tree from its first decision to the merge that closes it. The
29
29
  - A child issue is a separately deliverable change: its own pull request in its own repository, or its own deploy. Never use a child to track a to-do, a fix found in review, a proof of the root's acceptance, or a part of your own work: do it in this tree. Each child costs a workspace, an admission wait and a review loop. Before any `dispatch issue` that creates a child, and before `release_children`, run the red-team check in `skill://legion-architect/references/decomposition.md`.
30
30
  - Every child names the running system its criteria are proven on (the command, service or page the tester drives) and the repository skill that drives it. If the repository cannot exercise a criterion end to end, building that path is work of this tree.
31
31
  - The issue's primary document is the root spec: extend it in place, because a second "spec" artifact replaces the human's document. Mark each requirement of it as a `requirement` block, and write the issue's Verification document (`verification.md`) beside it: one `check` block per thing that must be proved, each with `serves` naming the requirements it serves, its `surface` (`browser`, `cli` or `api`), its `run`, its `when` (`before_merge`, `after_merge` or `default_branch`) and its `negative_control`. The checks are the gate every later role reads: the implementer and the tester record a result against each, and the reviewer approves only once every `before_merge` check of the issue has a passing result current at the head. The waves and the plan's tasks go in the children and the planner's `plan` handoff, not the root spec.
32
- - Read `skill://legion-architect/references/design-gate.md` before you write the root spec and before any edit to it after approval, because any new version closes the gate. It also says which gate runs, and that a sub-architect runs none.
33
- - File every child of your decomposition before `dispatch request-approval`, and before `register_gate` under `gates.design: off`: the gate's opening starts your root's own first phase unless Dispatch shows an open child under it, so a child filed after it opens leaves the root working beside its children.
34
- - A root architect then ends its turn and releases no wave until a delivered wake shows `design-approved`: the daemon starts no phase in the tree before it. A root that waited on a person's answer gets no `design-approved` notice: a resume task saying "this resume is your design-approved wake" is that wake. A sub-architect waits for none and releases its children with `release_children`.
32
+ - Read `skill://legion-architect/references/design-gate.md` before you write the root spec and before any edit to it after approval, because any new version closes the gate. It also names the design gate steps your `Legion addressing` line declares, the document each step records, and says that a sub-architect runs none.
33
+ - File every child of your decomposition before `dispatch request-approval`, and before `register_gate` where your line gates no step: the gate's opening starts your root's own first phase unless Dispatch shows an open child under it, so a child filed after it opens leaves the root working beside its children.
34
+ - A root architect then ends its turn and releases no wave until every gated step is open: a delivered wake shows `design-approved` on the root, and under more than one step `gatedSteps` in your record (the `legion` tool's `read_record`) shows each step `approved`. The daemon starts no phase in the tree before that. A root that waited on a person's answer gets no `design-approved` notice: a resume task saying "this resume is your design-approved wake" is that wake. A sub-architect waits for none and releases its children with `release_children`.
35
35
 
36
36
  ## 2. Children in flight
37
37
 
@@ -30,21 +30,35 @@ answer, Slack message or direct message of a `Filed for <person> at their reques
30
30
  evidence of a controller's `Filed for Legion from <role> on <issue>:` line
31
31
  ([The `Filed for` line](skill://legion-controller/references/finding-asks.md#the-filed-for-line)).
32
32
 
33
- ## Which gate runs
33
+ ## Which steps are gated
34
34
 
35
- The approval step runs only when the `Design gate policy` sentence of your `Legion addressing` line
36
- says `gates.design: root-issues`. When it says `gates.design: off`, write the spec and register it
37
- with `register_gate` at its current version, with no approval step: do not request approval and do
38
- not wait for `design-approved`; the gate opens at registration. File every child of your
39
- decomposition before that registration, since the gate's opening starts the root's own first phase
40
- unless Dispatch shows an open child under it. A sub-architect on a child issue
41
- has no policy line and never runs the gate.
35
+ The `Design gate steps` sentence of your `Legion addressing` line names the steps this deployment
36
+ gates, in the order it gates them, each in backticks: ``Design gate steps: `spec`.``,
37
+ ``Design gate steps: `decisions`, `criteria`.``, or `Design gate steps: none.` A sub-architect on
38
+ a child issue has no such sentence and never runs the gate.
39
+
40
+ Each step is one document of the root issue, and the daemon keeps one gate record for each step:
41
+
42
+ | Step | The document it records |
43
+ | --- | --- |
44
+ | `decisions` | the issue's decisions document (`decisions.md`) |
45
+ | `criteria` | its Verification document (`verification.md`) |
46
+ | `spec` | its primary document |
47
+
48
+ The tree's work starts when every step the sentence names is open, so run the armed sequence below
49
+ once for each of them, each on that step's own document. Under `Design gate steps: none.` write
50
+ the spec and register it with `register_gate` at its current version, with no `step` and no
51
+ approval step: do not request approval and do not wait for `design-approved`; the gate opens at
52
+ registration. File every child of your decomposition before that registration, since the gate's
53
+ opening starts the root's own first phase unless Dispatch shows an open child under it.
42
54
 
43
55
  ## The armed gate
44
56
 
45
- When the gate is armed, run this exact sequence before the tree's work starts, with every child of
46
- your decomposition filed before step 4, since the gate's opening starts the root's own first phase
47
- unless Dispatch shows an open child under it:
57
+ Run this exact sequence for each gated step, before the tree's work starts, with every child of
58
+ your decomposition filed before step 4, since the gate's opening starts the root's own first
59
+ phase unless Dispatch shows an open child under it. One approval request covers the `criteria`
60
+ and the `spec` step together (step 4), and each of them is registered on its own document
61
+ (step 5); the `decisions` step is a request of its own.
48
62
 
49
63
  1. Extend the primary document in place:
50
64
 
@@ -74,22 +88,31 @@ unless Dispatch shows an open child under it:
74
88
  ```
75
89
 
76
90
  `--include-verification` asks for the spec and the Verification document in one request, each
77
- at its own current settled version, and the human's answer records a review on each. An issue
78
- with no `verification.md` is refused `NO_VERIFICATION_DOCUMENT`, and one whose Verification
79
- document has no settled version `NO_VERSION`.
91
+ at its own current settled version, and the human's answer records a review on each: that one
92
+ request covers the `criteria` and the `spec` step together. An issue with no `verification.md`
93
+ is refused `NO_VERIFICATION_DOCUMENT`, and one whose Verification document has no settled
94
+ version `NO_VERSION`.
95
+
96
+ The `decisions` step is a request of its own, since `--include-verification` is for the issue's
97
+ spec alone and any other document is refused `NOT_SPEC_DOCUMENT`:
98
+
99
+ ```bash
100
+ dispatch request-approval --issue <root issue> --artifact decisions.md --summary '<what the human is approving> Reviews: <gap result>, <claim result>; <what they do not show>.'
101
+ ```
80
102
 
81
103
  `--summary` says in one to three sentences what the human is approving and nothing else (no
82
104
  commentary and no open question), then one sentence with both reviews' results and what they do
83
105
  not show, for example `Reviews: no open gap, 14 of 14 claims verified; neither shows that the
84
106
  design works once built.` The primary document is the default; the
85
107
  command prints `Approval requested for spec.md (document id <UUID>) at version <N> (ask <id>)`.
86
- 5. Register the spec's version with the daemon:
108
+ 5. Register each gated step's document and version with the daemon, one call for each step:
87
109
 
88
110
  ```ts
89
111
  legion({
90
112
  op: "register_gate",
91
113
  issue: "<root issue>",
92
- artifactId: "<UUID>", // the spec's document id, which the request printed
114
+ step: "<decisions|criteria|spec>", // the step this document is for
115
+ artifactId: "<UUID>", // that step's document id, which the request printed
93
116
  version: <N>, // the version the human is asked to approve
94
117
  })
95
118
  ```
@@ -99,10 +122,12 @@ unless Dispatch shows an open child under it:
99
122
  document's approval events by that id alone; the `legion` tool looks a slug or file name up in
100
123
  Dispatch and hands the daemon the id.
101
124
 
102
- The gate records one document. A request naming the spec and the Verification document sends
103
- an `artifact.approved` event for each, and the gate opens on the spec's alone, so register the
104
- spec's id and version and never the Verification document's: a Verification-only version
105
- closes and opens nothing. INFRA-175 is where that changes.
125
+ Each step records its own document, so register the spec's id and version under `step: "spec"`
126
+ and the Verification document's under `step: "criteria"`: the request that named both sent an
127
+ `artifact.approved` event for each, and each step opens on its own document's alone. Leave
128
+ `step` out only where your line names one step, which is the whole list a one-step deployment
129
+ gates. Register no step your line does not name: a step the deployment does not gate is
130
+ refused, and a record of it would hold nothing open.
106
131
 
107
132
  Facts about the request:
108
133
 
@@ -124,18 +149,38 @@ Facts about the request:
124
149
  latest version Dispatch holds for that document (the message names both), and a version Dispatch
125
150
  never wrote is no registration: the gate is as it was. Read the document's latest version, check
126
151
  it is the spec the human is asked to approve, and `register_gate` that version.
127
-
128
- Then end your turn. The registration hands the spec to the human: when nothing else of the tree is
129
- at work, the root returns its admission slot and waits on its design gate (`awaiting_person`), as a
130
- root question handed to the human does ("Every turn" in `skill://legion-architect/SKILL.md`). Stay
131
- live to read the human's comments and replies. Their approval or changes request delivers your next
132
- task, and so does a newer spec version once Dispatch shows the request back on your turn. While the
133
- root waits on a question instead, the human's approval or changes request of the spec answers that
134
- wait too, and the task says how the spec stands. Release no wave until a delivered wake shows
135
- `design-approved` on the root, or a task says its design gate is open: the daemon sends a root
136
- waiting on a person no `design-approved` notice, and "this resume is your design-approved wake" is
137
- that wake. On `design-changes-requested`: revise the spec as the human's reason asks, run step 3's
138
- two reviews on the new version, resolve every finding, request approval again and end your turn. A
152
+ - `register_gate` refused 400 `GATE_STEP_REQUIRED` means this deployment gates more than one step
153
+ and your call named none; the message names the steps it gates. Call again with `step` naming
154
+ the one that document is for.
155
+ - `register_gate` refused 409 `GATE_STEP_NOT_CONFIGURED` means the `step` you named is not one
156
+ this deployment gates; the message names the steps it does gate. Register those and no other.
157
+ - `register_gate` refused 409 `GATE_STEP_DOCUMENT_MISMATCH` means the document you named is not
158
+ that step's document of that issue; the message names the step and the document it records.
159
+ Register that step's own document, as the table in "Which steps are gated" says.
160
+
161
+ Each of those four refusals is given before anything is recorded, so the gate is as it was.
162
+
163
+ A `design-approved` or `design-changes-requested` notice carries a version and a reason and names
164
+ no step. Read your own issue record with the `legion` tool's `read_record` to see which step the
165
+ review was of: `gatedSteps` carries one entry for each gated step, with its `artifactId`, its
166
+ `currentVersion`, its `approvedVersion`, its `status` (`unregistered`, `waiting`, `approved` or
167
+ `changes_requested`) and the `changesReason` of a step whose reviewer asked for changes. That
168
+ list, and not the notice, says which steps are open and which still hold the tree's work.
169
+
170
+ Then end your turn. The registrations hand those documents to the human: when nothing else of the
171
+ tree is at work, the root returns its admission slot and waits on its design gate
172
+ (`awaiting_person`), as a root question handed to the human does ("Every turn" in
173
+ `skill://legion-architect/SKILL.md`). Stay live to read the human's comments and replies. Their
174
+ approval or changes request delivers your next task, and so does a newer spec version once
175
+ Dispatch shows the request back on your turn. While the root waits on a question instead, the
176
+ human's approval or changes request of the spec answers that wait too, and the task says how the
177
+ spec stands. Release no wave until every gated step is open: a delivered wake shows
178
+ `design-approved` on the root, or a task says its design gate is open, and under more than one
179
+ step `gatedSteps` in your record shows each one `approved`. The daemon sends a root waiting on a
180
+ person no `design-approved` notice, and "this resume is your design-approved wake" is that wake.
181
+ On `design-changes-requested`: revise the document that step records as the human's reason asks,
182
+ run step 3's two reviews on the new version, resolve every finding, request approval again and
183
+ end your turn. A
139
184
  `design-changes-requested` may name a version below the latest, or arrive after another for the
140
185
  same version: it is a request the human made that reached the daemon late, and no approval came
141
186
  after it. Each request arrives once. The gate is as the newest review left it; still read the
@@ -143,9 +188,11 @@ reason, and answer it in the spec if the latest version does not already.
143
188
 
144
189
  ## After approval
145
190
 
146
- Approval is pinned to the spec version: any new version closes the gate again, and no
147
- `design-approved`-type wake says so; you know from your own edit, or from the `artifact.version`
148
- event on your issue. The exceptions are the version a human's own answer to a decision block
191
+ Each step's approval is pinned to the version of the document that step records: a new version of
192
+ that document closes that step again, and no `design-approved`-type wake says so; you know from
193
+ your own edit, or from the `artifact.version` event on your issue. One step closing holds the
194
+ tree's work whatever the other steps stand at. The exceptions are the version a human's own
195
+ answer to a decision block
149
196
  writes when that answer is the whole change since the version they approved, and a version whose
150
197
  text is exactly the text of the version they approved, with no decision block in it decided
151
198
  since, such as an accidental edit undone exactly: for either, Dispatch records an `approved`
@@ -18,14 +18,14 @@ and 7 (a conflict, the hand-over, the close) is in
18
18
 
19
19
  | Wake | Procedure |
20
20
  | --- | --- |
21
- | `carried-gate-opened` | Only a root whose catch-up said `carried` gets it. Its `version` and `reason`: a human approved the old root's spec document at `version`, which opened the gate this root carried; the daemon has released any READY it refused and started any child waiting on it. It is not `design-approved`: do not proceed to section 2 or release a wave because of it, and start nothing. |
21
+ | `carried-gate-opened` | Only a root whose catch-up said `carried` gets it. Its `version` and `reason`: a human approved the old root's spec document at `version`, which opened the gate this root carried; the daemon has released any READY it refused and started any child waiting on it. It names no step: the carry brings the old root's gate records, and the catch-up reports every gated step the carry did not bring as unregistered, so register each of those on this issue's own document for that step (`skill://legion-architect/references/design-gate.md`). It is not `design-approved`: do not proceed to section 2 or release a wave because of it, and start nothing. |
22
22
  | `child-status` | A child of your tree left the workflow or re-entered it, or joined your tree from the admission queue; the reason names the child and its new status, or reads `<KEY> joined the tree of <ROOT> from the admission queue`. `todo` (a human's move, your `release_children`, or your `rerun_child`) means it runs again under your tree from planning, and the daemon starts it; you start nothing. A child held for a blocker starts the same way once nothing holds it, and the reason, naming it and its root, says what cleared the hold: its last blocker's close, naming the issues it waited on, now done, or a person or you taking its blocked-by links off; you start nothing for it either. `backlog`, `icebox` or `triage` (a human's move, or your `park_child`) means the daemon has suspended its workers, and it advances no further until it is set back to `todo`. Joined from the admission queue means a root that waited for a slot and never ran, which a person made a child of your tree or your `release_children` adopted, now runs in your tree with no architect of its own and takes an admission slot of its own when its work starts, from planning when it is `todo` and parked otherwise; one that waits on an unfinished blocked-by issue is held for that blocker like any other child, and starts when nothing holds it; you start nothing. A reason saying an issue is beneath an ancestor whose reviewed head the daemon has already accepted is a person's `todo` the daemon recorded and holds at `admitted`: nothing starts it until that ancestor closes, and a `request_backward_move` of the ancestor is what runs it under the ancestor now. What the rest of the tree does is your decision. When a park leaves no child that an issue in `accepting` waits on, run that issue's last round (`skill://legion-architect/references/acceptance.md`). |
23
23
  | `child-closed` | Read the child completion and remaining open children. Re-scope open work, park obsolete work for good with `park_child` (first take its link off each sibling that waits on it, or park that sibling too, as section 2 says), and file any new child the result calls for, after the red-team check in `skill://legion-architect/references/decomposition.md`, and release it at once with `release_children`, blocked by what it builds on (section 2). 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). When it was the last child an issue in `accepting` waits on, run that issue's last round (`skill://legion-architect/references/acceptance.md`). |
24
24
  | `child-release-stalled` | Its `reason` names a child your `release_children` released that your tree has not recorded, its parent, and why, one of three: the parent's release has not reached the daemon after three tries; or GitHub did not answer the App installation read of a repository on the child's parent chain, and the wave left the child unrecorded because a parent on that chain is not `todo`, or did not write the child `todo` because its parent is not recorded; or the daemon's recovery of such a repository, once GitHub answered, still left children unrecorded (one notice per tree lists them all, each with its parent). Read the parent's record with the `legion` tool's `read_record` (`issue` the parent); release the parent first when the `reason` says to, then run `release_children` for the child again once the parent is recorded in your tree, and tell the controller if it never is. |
25
25
  | `child-not-recorded` | Its `reason` names a child your tree did not record and why: its repository resolves to none (neither it nor an ancestor names one, and the project has no default), or to one not mapped to the project, or to one the daemon leaves out for a missing GitHub App. The daemon said why on the child too. Set the child's repository to one of the project's (`dispatch issue-update --repo owner/name`), then release it again with `release_children`; tell the controller when the repository it needs is not served. |
26
26
  | `children-first` | An issue of your tree starts no phase of its own while Dispatch shows an open child under it, a child you filed and have not released included. The reason names the parent and each open child with the Dispatch status that holds it, and each cycle it found (a child `blocked_by` an issue above it on its own parent chain, which only a person taking that link off ends: open a `dispatch ask`). It comes once per issue and run of its tree. Release every child that should run, and close, `park_child` or re-parent the rest; the daemon starts the parent's first phase at its next admission slot once the last open child has left the workflow. You start nothing. |
27
- | `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, "After approval" in `skill://legion-architect/references/design-gate.md`), opens nothing new: carry on, and release no wave because of it. A root that was waiting on a question in `awaiting_person` gets no such notice: the task that resumes it says "Your spec is approved at version N and the design gate is open: this resume is your design-approved wake", and that resume is this wake — proceed to section 2 on it. |
28
- | `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 "The armed gate" in `skill://legion-architect/references/design-gate.md` says; end your turn; the gate is closed. On a root that was waiting on a question in `awaiting_person`, a review the human gave after the wait began comes in the resume task, not in this notice, whether it answered the wait or came after something else did: the task names the review with its version and reason, unless a later approval superseded it. A `version` below the latest, or a second request for one version, is a request that reached the daemon late: read it as the same section's last paragraph says. Each request arrives once. |
27
+ | `design-approved` | Its `version` is the approved one, and it names no step: under more than one gated step, read `gatedSteps` in your own record (the `legion` tool's `read_record`) to see which step that version opened and whether another still holds the tree. A human approved the document of one gated step at its current version; that step is open. Proceed to section 2 once every gated step is `approved` — 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, "After approval" in `skill://legion-architect/references/design-gate.md`), opens nothing new: carry on, and release no wave because of it. A root that was waiting on a question in `awaiting_person` gets no such notice: the task that resumes it says "Your spec is approved at version N and the design gate is open: this resume is your design-approved wake", and that resume is this wake — proceed to section 2 on it. |
28
+ | `design-changes-requested` | Its `version` and `reason`, and no step: under more than one gated step, read `gatedSteps` in your own record (the `legion` tool's `read_record`) and take the step whose `status` is `changes_requested`. A human asked for changes to that step's document at `version`, for `reason`. Revise that document as the reason asks and request approval again as "The armed gate" in `skill://legion-architect/references/design-gate.md` says; end your turn; that step is closed. On a root that was waiting on a question in `awaiting_person`, a review the human gave after the wait began comes in the resume task, not in this notice, whether it answered the wait or came after something else did: the task names the review with its version and reason, unless a later approval superseded it. A `version` below the latest, or a second request for one version, is a request that reached the daemon late: read it as the same section's last paragraph says. Each request arrives once. |
29
29
  | `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 finishing phase's handoff on the Dispatch issue (`dispatch handoff-read --issue <issue> --phase <word>`); 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 the full condition in "After approval" of `skill://legion-architect/references/design-gate.md`: when the approved Summary, the requirements, the checks, the scope and the settled decisions still hold, the plan is the record; otherwise change the root spec and request approval again as that section 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 issue is in `accepting` and the acceptance is yours (section 6): read the evidence, then `sign_off` (a parent only after its last round, "The last round" in `skill://legion-architect/references/acceptance.md`) or send it back with `request_backward_move`. A `tester` notice with `verdict: "fail"` — its handoff carries `implementerProof.verdict: "rejected"`, or a failure naming a check with no current passing result — has gone back to the **implementer** by the daemon's table; never record that check's result 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: build the surface in this tree: a child issue (infrastructure, tooling, or a skill) after the red-team check in `skill://legion-architect/references/decomposition.md`, or, when the red team folds it, tell the waiting worker to move the issue back to `implementing` with `request_backward_move`, naming the surface. When it is a child, tell the waiting worker to continue with `envoy_publish` to its role topic once that child's `child-closed` arrives. That report is never a reason to advance the phase. |
30
30
  | `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: no handoff is a commit, so every such push counts except a push by the review App (a planner's, tester's, reviewer's or architect's), and the head pushed onto a red the tester's red tests earned (a planned red) does not count either. 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 or on a settled head whose base branch requires no check and no workflow, the tester's red tests onto the held red included) sends the work back from `testing`, `reviewing`, `accepting`, `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, or onto a settled head whose base branch requires no check and no workflow, 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. In `accepting`, where no worker holds a phase, the move is yours: `request_backward_move` with `issue`, naming the failing checks. 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. 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 a conflict (`the head conflicts with <base>`), the required checks that failed (`CI is red at <head>`), or the merge queue's failed merge and its failing run (`the merge queue took pull request #<n> out at READY's 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; your own send-back from `accepting` by the reason you gave. 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, or when the merge queue fails the merge) are the rounds the cap is for. Each `review_round_cap` notice is a decision you make in the turn it wakes, never one left for the next round to make: read what every counted round found, choose one of the three below, act on it in the same turn, and post it as a `dispatch comment` on the issue naming the rounds you read and the choice. **Narrow**: the change itself is sound but each round finds more around it; tell the implementer and the reviewer (`envoy_publish` to each one's role topic) which checks of the issue the next round answers; a correctness finding is still fixed in this pull request, and so is a wording nit or any clear fix in a file it touches, so the `Fast-follow:` line carries only cleanup in files this pull request does not touch. **Split**: part of the work cannot settle in this pull request and is a separately deliverable change (`skill://legion-architect`, section 1). On a child, run the red-team check in `skill://legion-architect/references/decomposition.md`, then file that part as a child of this issue's parent, never of this one (this issue closes at its own merge, and the split part usually builds on this pull request's unmerged code): `dispatch issue --project <project> --parent <parent> --title '<outcome>' --blocked-by <this issue> --spec-file -`. Release it now with `release_children`: Legion holds it on that `--blocked-by` link until this issue closes, and starts it then. On the root, narrow it to what settles here and open a `dispatch ask` for the rest (`skill://legion-worker/references/findings.md`): a person decides whether that becomes an issue. Tell the implementer and the reviewer that this pull request no longer carries it. **Accept the rounds**: each round found less than the one before it, and the last found no defect in the change itself (a stale line, a comment that states an old default); let the loop run and say why in the comment. A later notice is a new decision: accept again only when its round found less than the round you last accepted, and otherwise narrow or split. A finding that changes what the spec requires or what a check proves is a decision block in the root spec ("After approval" in `skill://legion-architect/references/design-gate.md`), and `dispatch ask` is only for a decision that needs a human's authority; neither replaces the choice. Judge a worker's backward move by the reason that worker gives. A move you ordered for a conflict found before `awaiting_merge` ("A conflicting head" in `skill://legion-architect/references/acceptance.md`) is a conflict return. The merger's move back to `reviewing` for a READY refusal (`READY_UNREVIEWED_CHANGE`, or `READY_NOT_APPROVED` when its task names no approved head), and the reviewer's no-fix request for changes that follows it so the tester runs the head, are not the work going in circles either. A move that sends back code found wrong counts with the tester fails. |
31
31
  | `pr-merged` | The pull request merged and the issue did not close; its `reason` says why. Merged before the issue reached `awaiting_merge`: the workflow runs on, and the merge closes the issue the moment it gets there, your acceptance and retro first. Merged while the rest of the tree still runs: the reason names the pull request and every issue the close waits for, and the root waits in `awaiting_merge`, its workers kept and live and no admission slot held, until the last of them is done or parked ("The close" in `skill://legion-architect/references/acceptance.md`). Either way you start nothing and no further notice follows. |
@@ -39,7 +39,7 @@ and 7 (a conflict, the hand-over, the close) is in
39
39
  | 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. |
40
40
  | `worker-died` | Its `role` and `phase`. That role's claim failed: its launches or prompts ran out. For a phase worker the daemon holds the issue (phase `held`) and starts nothing more on it. Reassess the work, then decide with `retry_or_escalate`: `retry` when the failure looks agent-specific or transient, `escalate` to hand the held issue to the controller when it looks environmental. |
41
41
  | `held` | Its `role` and `phase` (the phase the issue left), or `phase` with `reason: "escalated"`. Without `reason`, that phase's worker ran out of launches or prompts: the issue is held (phase `held`), the `worker-died` that comes with it is yours to answer with `retry_or_escalate` as that row says, and the controller hears of the hold too. With `reason: "escalated"`, it records your own `escalate`: the controller has the issue now, and you start nothing for it. |
42
- | `ready-refused` | Its `version` and `reason`. The merger's READY was refused, and its packet is kept with the completion so no second READY is ever needed. With `version` greater than 0, `reason` reads `READY refused: approve design version <N> before requesting READY.`: the root's spec is already registered at that version, so get it approved (`skill://legion-architect/references/design-gate.md`), and the daemon advances the merge and posts the kept packet the moment it is. With `version` 0, `reason` reads `READY refused: no design version is approved; register and approve the tree's spec before requesting READY.`: the root has no spec registered yet, so register it and get it approved (`skill://legion-architect/references/design-gate.md`) exactly as above; the daemon releases the kept packet the same way — at registration itself when that opens the gate (`gates.design: off`, or Dispatch already shows the version approved), otherwise the moment a human approves the version you registered. Either way, nothing else is needed once the gate opens. On a root whose catch-up said `carried`, the gate is a copy on the old root's spec document: run nothing of `skill://legion-architect/references/design-gate.md`; tell the controller the READY waits on a human's approval of that document, and `carried-gate-opened` follows the approval. A `reason` starting `READY_PACKET_MISSING` names a READY refused before the daemon kept packets: that READY is void and the issue stays in merging, so start it over (`park_child` then `rerun_child`, for a child) or end it. |
42
+ | `ready-refused` | Its `version` and `reason`, and the `reason` names the gated step the READY waits on. The merger's READY was refused, and its packet is kept with the completion so no second READY is ever needed. With `version` greater than 0, `reason` reads `READY refused: approve design version <N> of the tree's <step> document before requesting READY.`: that step's document is already registered at that version, so get it approved (`skill://legion-architect/references/design-gate.md`), and the daemon advances the merge and posts the kept packet the moment every gated step is open. With `version` 0, `reason` reads `READY refused: no design version of the tree's <step> document is approved; register and approve it before requesting READY.`: that step is not registered yet, so register it and get it approved (`skill://legion-architect/references/design-gate.md`) exactly as above; the daemon releases the kept packet the same way — at registration itself when that opens the step (a deployment that gates no step, or Dispatch already shows the version approved), otherwise the moment a human approves the version you registered. The refusal names the first closed step, in the order the deployment gates them, and no second READY is asked for however many steps were closed: read `gatedSteps` in your own record (the `legion` tool's `read_record`) for every step still closed, and open each one. Nothing else is needed once every gated step is open. On a root whose catch-up said `carried`, the gate is a copy on the old root's spec document: run nothing of `skill://legion-architect/references/design-gate.md` for that step; tell the controller the READY waits on a human's approval of that document, and `carried-gate-opened` follows the approval. A `reason` starting `READY_PACKET_MISSING` names a READY refused before the daemon kept packets: that READY is void and the issue stays in merging, so start it over (`park_child` then `rerun_child`, for a child) or end it. |
43
43
 
44
44
  ## A root that joins your tree, and a refused release
45
45
 
@@ -21,7 +21,7 @@ except a clear defect a role reports ([Issues handed to Legion](#issues-handed-t
21
21
  - Keep no notes between turns, because a restarted session has only `read_state` and Dispatch.
22
22
  - Reach the daemon only through the `legion` tool: `read_state`, and `set_status` to `todo`, `backlog` or `icebox`. Run no `legion` from bash, because the tool holds the controller grant.
23
23
  - Never edit a repository, push a commit or call `handoff_complete`, and never forward a raw event to an architect, because the daemon runs the phases and raw events only fill the architect's context.
24
- - Never open a design gate, because only a person's approval of the root's spec in Dispatch opens it.
24
+ - Never open a design gate, because only a person's approval in Dispatch opens one: the deployment gates the steps your `Design gate steps:` sentence names, each step records one document of the root issue (`decisions.md`, `verification.md` or the primary document), and the tree's work starts when a person has approved every one of them.
25
25
  - When a `dispatch` command fails, or a `curl` read of Dispatch fails or prints no JSON array, act on nothing it would decide and name its first line in your summary (the turn's last message), because an empty read is no evidence. A Slack read answers a JSON object, and `skill://legion-controller/references/slack-mentions.md` holds its own success test.
26
26
  - Before you say that a service or an API is out of reach, run the call through the secrets broker's client (`skill://agent-secrets`), and report it unreachable only with the command you ran and its output.
27
27
  - Write every Dispatch comment, message and ask as `skill://dispatch-writing-specs`'s "Rules for the text" say.
@@ -97,7 +97,10 @@ an approval of a document that is not the root's primary one.
97
97
  on the issue where the decision was made, has judged it: do nothing.
98
98
  4. **Judge the words, not the kind.** Read the decision (`dispatch read --ref <ref>` for an ask or
99
99
  a reply; for a request for changes, the notice's `reason` or the issue's `Spec approval:` line;
100
- for an approval, `issues.<root>.designGate`). An approval or a request for changes is two
100
+ for an approval, `issues.<root>.designGate` and `issues.<root>.gatedSteps` beside it, which
101
+ carries one entry for each design gate step the deployment gates, with that step's
102
+ `artifactId`, `currentVersion`, `approvedVersion` and `status`). An approval or a request for
103
+ changes is two
101
104
  events at one time, the approval request's `ask.answered` and `artifact.approved` or
102
105
  `artifact.changes_requested`: judge them as one decision, whose `ref` is `dispatch://<KEY>/spec`.
103
106
  - It gives the work a next step: an approval of the spec, a request for changes that says what
@@ -105,6 +108,9 @@ an approval of a document that is not the root's primary one.
105
108
  the agent to go on. An approval counts when `issues.<root>.designGate.approvedVersion` equals
106
109
  `issues.<root>.designGate.currentVersion`, and also when it is lower: then the architect's
107
110
  start task names both versions, and the architect requests approval of the current one.
111
+ Where `gatedSteps` carries more than one entry, read each one the same way: the tree runs
112
+ again only once every entry is `approved`, and an entry still `waiting`, `unregistered` or
113
+ `changes_requested` is work the architect holds.
108
114
  - It is your own hold (step 2): set the root to `todo` (`set_status`); admission queues it and
109
115
  its architect resumes. Post one comment on the issue where the decision was made
110
116
  (`dispatch comment --issue <KEY> --body-file -`): `Parked decision read: <ref> by <login>
@@ -68,9 +68,9 @@ Read this on a `triage` wake, at every start, and when a person hands you an iss
68
68
  who will get this tree's questions and its design approval`, or, when `Assignee:` says
69
69
  `unassigned`, `Unassigned — nobody's Inbox shows this tree's questions or its design approval
70
70
  until someone takes it from the issue header (Assignee, beside Priority)`. Promise the design
71
- approval only when your system prompt's `Design gate policy:` line says
72
- `gates.design: root-issues`; under `gates.design: off` nobody approves a design, so drop "and
73
- its design approval" (and "or its design approval"). Take the assignee from the read, never
71
+ approval only when your system prompt's `Design gate steps:` sentence names at least one step;
72
+ when it reads `Design gate steps: none.` nobody approves a design, so drop "and its design
73
+ approval" (and "or its design approval"). Take the assignee from the read, never
74
74
  from how the issue was created, because an issue filed with no assignee still lands on the
75
75
  project's default assignee where a person set one. An unassigned root still runs, but its
76
76
  architect's asks reach only the Inbox's `Unassigned` filter.
@@ -132,7 +132,9 @@ Send any other to the owning architect: find its role in `read_state` and `envoy
132
132
  mention's URL, its text and the issue key you checked. Never build a role token from a partial
133
133
  issue reference, because a wrong token reaches nobody.
134
134
 
135
- A person who asks how to let a root past its design gate approves the root's spec document in
136
- Dispatch: the `Approve` control in the document's header, or the approval question the architect's
137
- request opened in the Inbox. No operator command opens a gate; a project that wants no gate sets
138
- `gates.design: off` in `legion.yaml`.
135
+ A person who asks how to let a root past its design gate approves, in Dispatch, the document of
136
+ each step the deployment gates, which your system prompt's `Design gate steps:` sentence names:
137
+ the `Approve` control in that document's header, or the approval question the architect's request
138
+ opened in the Inbox. The root's work starts when every one of those steps is approved. No
139
+ operator command opens a gate; a project that wants no gate sets `gates.design` to the empty list
140
+ (`design: []`) in `legion.yaml`.
@@ -145,7 +145,9 @@ reviewing until you finish.
145
145
  the architect as a gap the tester and the reviewer named and neither refused, so the
146
146
  architect's acceptance is the only gate on it: the acceptance comment names that gap and
147
147
  decides there, accepting the issue or sending it back with the checks the architect writes,
148
- which closes no design gate while dispatch://INFRA-175 has not shipped. The architect also
148
+ which closes the root's `criteria` step where the deployment gates that step, since that step
149
+ records the root's Verification document and each new version of a step's document closes it;
150
+ a child's Verification document is gated by nothing. The architect also
149
151
  reads the reviewer's own prose commits here, with the rest of the evidence, and that read is no
150
152
  new round. It does not
151
153
  accept while the issue holds a finding ask no controller has taken (`findings.md`, "When no
@@ -259,8 +261,10 @@ reviewing until you finish.
259
261
  ordinary arm cannot merge the person-only head. Ordinary READY does not wait for the disarm. The
260
262
  daemon tells that READY with the note at its head and the packet's `Merge:` line replaced, so the
261
263
  issue and the merge queue role are never asked to arm it. A READY the design gate refused is
262
- submitted by the daemon when the approval that opens the gate moves the issue to
263
- `awaiting_merge`. A push that moves the head past a READY the gate holds voids it before then:
264
+ submitted by the daemon once every gated step of the root's design gate is open, when the
265
+ approval that opens the last closed one moves the issue to `awaiting_merge`; the refusal named
266
+ one step and the version to approve, and no second READY is asked for however many steps held
267
+ it. A push that moves the head past a READY the gate holds voids it before then:
264
268
  the architect is told `READY_HEAD_MOVED` and has the merger build a new READY at the new head.
265
269
  The packet's second line records the step,
266
270
  `Merge: the daemon submits pull request #<n> at <head> once READY is accepted`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/claude-envoy",
3
- "version": "1.11.3",
3
+ "version": "1.12.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Claude Code channel plugin for Legion's Envoy bus",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  "AGENTS.md"
20
20
  ],
21
21
  "legion": {
22
- "daemonApiVersion": 54
22
+ "daemonApiVersion": 55
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
@@ -33,7 +33,7 @@ report, a disclosure, and a reply to a maintainer who wrote first.
33
33
 
34
34
  Hold an extremely high bar. Optimise every upstream report, patch or public post for a human
35
35
  reader and for being received well, so each one builds the company's reputation rather than a
36
- name for slop.
36
+ reputation for low-quality or careless submissions.
37
37
 
38
38
  ## The four steps
39
39
 
@@ -95,7 +95,9 @@ review batch, never a per-message "can I send this" ask. Write its entry — wha
95
95
  serious; who receives it and what they do; the exact text or patch; its evidence; a non-author
96
96
  reviewer's APPROVE under your stream's reviewer-model rule; what the operator decides; its owner
97
97
  — into the batch, add it under the batch's Waiting list, and tell the batch owner. Your stream's
98
- brief names the batch and its owner.
98
+ brief names the batch and its owner. Where your stream's brief names no batch, ask the operator
99
+ for one, or hold the entry until there is a batch; a missing batch is not a reason to send a
100
+ per-message ask.
99
101
 
100
102
  ## What this skill does not cover
101
103