@sjawhar/opencode-legion-envoy 5.5.1 → 5.6.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/src/server.js
CHANGED
|
@@ -13962,6 +13962,7 @@ var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
|
13962
13962
|
var ASK_QUESTION_MAX = 800;
|
|
13963
13963
|
var SEARCH_QUERY_MAX = 1000;
|
|
13964
13964
|
var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passage";
|
|
13965
|
+
var SEARCH_KIND_DEPTH = 100;
|
|
13965
13966
|
var PROJECT_KEY_PATTERN = /^[A-Z][A-Z0-9]{1,9}$/;
|
|
13966
13967
|
var ISSUE_STATUSES = [
|
|
13967
13968
|
"triage",
|
|
@@ -14297,7 +14298,7 @@ var dispatchToolSpecs = [
|
|
|
14297
14298
|
{
|
|
14298
14299
|
name: "dispatch_search",
|
|
14299
14300
|
example: { query: "astrolabe" },
|
|
14300
|
-
description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR.',
|
|
14301
|
+
description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR. ' + "Each kind of content is ranked on its own and the lists are merged, so the top holds the best " + "issue, document, ask, comment and message; an issue key searched alone lists that issue first. " + `The answer names how many results match; offset pages through them, and each kind lists at most its best ${SEARCH_KIND_DEPTH}, ` + "so narrow the query or name a project to reach the rest. Paging is exact only while the " + "corpus holds still: content added, changed, or removed between two offsets can shift rows " + "across a page boundary, so one hit can come back twice and another never.",
|
|
14301
14302
|
arguments: (z2) => ({
|
|
14302
14303
|
query: z2.string({
|
|
14303
14304
|
min: 2,
|
|
@@ -14305,7 +14306,8 @@ var dispatchToolSpecs = [
|
|
|
14305
14306
|
maxHint: SEARCH_QUERY_HINT
|
|
14306
14307
|
}).describe(`Keyword, phrase, or websearch expression; 2 to ${SEARCH_QUERY_MAX} characters.`),
|
|
14307
14308
|
project: z2.string().describe("Optional project key to search within.").optional(),
|
|
14308
|
-
limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional()
|
|
14309
|
+
limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional(),
|
|
14310
|
+
offset: z2.number({ int: true, min: 0 }).describe("Results to skip before the page; nonnegative integer; default 0.").optional()
|
|
14309
14311
|
}),
|
|
14310
14312
|
validation: {
|
|
14311
14313
|
check: (value) => {
|
|
@@ -15206,6 +15208,71 @@ function dispatchChildRef(ownerRef, kind, id) {
|
|
|
15206
15208
|
return `${ownerRef}/${kind}/${id}`;
|
|
15207
15209
|
}
|
|
15208
15210
|
|
|
15211
|
+
// ../envoy-client/src/search-answer.ts
|
|
15212
|
+
function pageSummaryText(offset, count, total) {
|
|
15213
|
+
return `showing ${offset + 1}-${offset + count} of ${total}`;
|
|
15214
|
+
}
|
|
15215
|
+
function searchResultLine(result, baseUrl) {
|
|
15216
|
+
const href = new URL(result.href, baseUrl).toString();
|
|
15217
|
+
const { owner } = result;
|
|
15218
|
+
if (owner.kind === "document") {
|
|
15219
|
+
const reference = dispatchDocumentRef(owner.project, owner.slug);
|
|
15220
|
+
return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15221
|
+
}
|
|
15222
|
+
const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
|
|
15223
|
+
const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
|
|
15224
|
+
return `${label}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15225
|
+
}
|
|
15226
|
+
function searchAnswer(search, query, offset, configUrl) {
|
|
15227
|
+
const results = search.results;
|
|
15228
|
+
const count = results.length;
|
|
15229
|
+
const lines = results.map((result) => searchResultLine(result, configUrl));
|
|
15230
|
+
const noun = count === 1 ? "result" : "results";
|
|
15231
|
+
const noResults = `No results for "${query}".`;
|
|
15232
|
+
if (typeof search.total !== "number") {
|
|
15233
|
+
if (offset !== undefined && offset > 0) {
|
|
15234
|
+
throw new Error(`Dispatch answered without a total: it predates search paging and ignored offset ${offset}, so this would be its first page again.`);
|
|
15235
|
+
}
|
|
15236
|
+
return {
|
|
15237
|
+
text: count === 0 ? noResults : [`${count} ${noun} for "${query}" (${search.took_ms} ms)`, ...lines].join(`
|
|
15238
|
+
`),
|
|
15239
|
+
details: { query, results }
|
|
15240
|
+
};
|
|
15241
|
+
}
|
|
15242
|
+
const { total, reachable, offset: pageOffset } = search;
|
|
15243
|
+
const end = pageOffset + count;
|
|
15244
|
+
const cut = reachable < total ? `Each kind lists only its best ${SEARCH_KIND_DEPTH} matches, so ${reachable} of the ${total} can be paged to; narrow the query or name a project to reach the rest.` : undefined;
|
|
15245
|
+
const details = {
|
|
15246
|
+
query,
|
|
15247
|
+
results,
|
|
15248
|
+
total,
|
|
15249
|
+
reachable,
|
|
15250
|
+
offset: pageOffset,
|
|
15251
|
+
limit: search.limit
|
|
15252
|
+
};
|
|
15253
|
+
if (count === 0) {
|
|
15254
|
+
return {
|
|
15255
|
+
text: total === 0 ? noResults : [
|
|
15256
|
+
`No results for "${query}" at offset ${pageOffset}: it matches ${total}, and the pages reach the first ${reachable}.`,
|
|
15257
|
+
...cut === undefined ? [] : [cut]
|
|
15258
|
+
].join(`
|
|
15259
|
+
`),
|
|
15260
|
+
details
|
|
15261
|
+
};
|
|
15262
|
+
}
|
|
15263
|
+
const showing = pageOffset === 0 && count === total ? "" : `${pageSummaryText(pageOffset, count, total)}, `;
|
|
15264
|
+
return {
|
|
15265
|
+
text: [
|
|
15266
|
+
`${count} ${noun} for "${query}" (${showing}${search.took_ms} ms)`,
|
|
15267
|
+
...cut === undefined ? [] : [cut],
|
|
15268
|
+
...lines,
|
|
15269
|
+
...end < reachable ? [`Next page: offset ${end}.`] : []
|
|
15270
|
+
].join(`
|
|
15271
|
+
`),
|
|
15272
|
+
details
|
|
15273
|
+
};
|
|
15274
|
+
}
|
|
15275
|
+
|
|
15209
15276
|
// ../envoy-client/src/tool-input-errors.ts
|
|
15210
15277
|
class ToolInputError extends Error {
|
|
15211
15278
|
tool;
|
|
@@ -15515,17 +15582,6 @@ function duplicateCandidates(error48) {
|
|
|
15515
15582
|
throw error48;
|
|
15516
15583
|
return error48.candidates;
|
|
15517
15584
|
}
|
|
15518
|
-
function searchResultLine(result, baseUrl) {
|
|
15519
|
-
const href = new URL(result.href, baseUrl).toString();
|
|
15520
|
-
const { owner } = result;
|
|
15521
|
-
if (owner.kind === "document") {
|
|
15522
|
-
const reference = dispatchDocumentRef(owner.project, owner.slug);
|
|
15523
|
-
return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15524
|
-
}
|
|
15525
|
-
const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
|
|
15526
|
-
const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
|
|
15527
|
-
return `${label}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15528
|
-
}
|
|
15529
15585
|
function askUrgency(args) {
|
|
15530
15586
|
const value = args.urgency;
|
|
15531
15587
|
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
@@ -16721,20 +16777,13 @@ async function executeDispatchTool(input) {
|
|
|
16721
16777
|
const query = stringArg(args, "query");
|
|
16722
16778
|
const project = optionalString(args, "project");
|
|
16723
16779
|
const limit = optionalNumber(args, "limit");
|
|
16780
|
+
const offset = optionalNumber(args, "offset");
|
|
16724
16781
|
const search = await client.search(query, {
|
|
16725
16782
|
...project === undefined ? {} : { project },
|
|
16726
|
-
...limit === undefined ? {} : { limit }
|
|
16783
|
+
...limit === undefined ? {} : { limit },
|
|
16784
|
+
...offset === undefined ? {} : { offset }
|
|
16727
16785
|
});
|
|
16728
|
-
|
|
16729
|
-
const count = results.length;
|
|
16730
|
-
return {
|
|
16731
|
-
text: count === 0 ? `No results for "${query}".` : [
|
|
16732
|
-
`${count} ${count === 1 ? "result" : "results"} for "${query}" (${search.took_ms} ms)`,
|
|
16733
|
-
...results.map((result) => searchResultLine(result, configUrl))
|
|
16734
|
-
].join(`
|
|
16735
|
-
`),
|
|
16736
|
-
details: { query, results }
|
|
16737
|
-
};
|
|
16786
|
+
return searchAnswer(search, query, offset, configUrl);
|
|
16738
16787
|
}
|
|
16739
16788
|
case "dispatch_issues": {
|
|
16740
16789
|
const project = stringArg(args, "project");
|
|
@@ -16774,7 +16823,7 @@ async function executeDispatchTool(input) {
|
|
|
16774
16823
|
}));
|
|
16775
16824
|
const titles = await liveSessionTitles(client, rows.some((row) => holdsSession(row.claim)));
|
|
16776
16825
|
const isPartial = offset !== 0 || rows.length !== total;
|
|
16777
|
-
const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` :
|
|
16826
|
+
const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` : pageSummaryText(offset, rows.length, total);
|
|
16778
16827
|
return {
|
|
16779
16828
|
text: rows.length === 0 ? `No issues in ${project}.${isPartial ? ` (${showing})` : ""}` : [
|
|
16780
16829
|
`${rows.length} ${rows.length === 1 ? "issue" : "issues"} in ${project}` + (isPartial ? ` (${showing})` : ""),
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dispatch
|
|
3
|
-
description: "Use before posting a message, a status update, or a periodic status update; before asking a question that references another message, artifact, or eval; before asking a design question or brainstorming a change; and when asking
|
|
3
|
+
description: "Use before posting a message, a status update, or a periodic status update; before asking a question that references another message, artifact, or eval; before asking a design question or brainstorming a change; and when asking the human a question, updating the spec, commenting on a document, attaching an artifact, or calling a dispatch_* tool."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Dispatch
|
|
@@ -66,7 +66,7 @@ not share this session's vocabulary, and is often on a phone. Write for that per
|
|
|
66
66
|
has a human subject, even on a sentence you already simplified; a lead naming what a change does
|
|
67
67
|
inside a system leaves the reader nothing to act on. Where the judgment rule below applies, the
|
|
68
68
|
judgment leads and this rule shapes the sentence under it.
|
|
69
|
-
- Before posting, test it: could
|
|
69
|
+
- Before posting, test it: could the human, reading only this text on their phone, know what they are being
|
|
70
70
|
told or asked? If not, rewrite it. Length is not the problem; density is.
|
|
71
71
|
- When an ask or message communicates a judgment, lead with that judgment in one sentence and put the mechanism underneath it. Do not make the reader ask a second time whether the result is a win. This shapes communication only when a judgment exists; it does not pre-decide an open question or remove its genuine options.
|
|
72
72
|
- When a Dispatch message states a root cause, include the reproducing command or test in that same message. Without it, label the diagnosis a hypothesis; a diagnosis still in progress may say so plainly. This boundary applies to causal claims, not to reporting that an investigation has started.
|
|
@@ -160,15 +160,14 @@ follow [Writing a spec](#writing-a-spec).
|
|
|
160
160
|
you plan, start a design document, file an issue, ask, post a finding or start work, and what to
|
|
161
161
|
do with each hit. What it leaves out:
|
|
162
162
|
```ts
|
|
163
|
-
dispatch_search({ query, project?, limit? })
|
|
163
|
+
dispatch_search({ query, project?, limit?, offset? })
|
|
164
164
|
```
|
|
165
|
-
Websearch syntax applies: `"merge queue"`, `-daemon`, `OR`.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
"no prior issue" in the spec.
|
|
165
|
+
Websearch syntax applies: `"merge queue"`, `-daemon`, `OR`. Issues, documents, asks, comments, and messages rank separately; the
|
|
166
|
+
merged page orders each kind's best in turn — issue, document, ask, comment, message — and a bare issue key ranks first. A page
|
|
167
|
+
holds `limit` hits (20 default, 50 max); the first line gives the total (`showing 1-20 of 312`); see
|
|
168
|
+
[Search paging](skill://dispatch/references/issues.md#search-paging) for more. Queries over 1,000 characters are refused: use the
|
|
169
|
+
few words `skill://dispatch-first` names, never a pasted passage. Issue hits start with the issue key; document hits start with
|
|
170
|
+
`dispatch://PROJECT/artifact/<slug>`, then the link. Cite the hit (`dispatch://KEY` or the doc ref) or say "no prior issue".
|
|
172
171
|
|
|
173
172
|
`dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns the candidates (`POSSIBLE_DUPLICATE`).
|
|
174
173
|
Read them; reference the existing issue, or repeat the call with `force: true` when it is genuinely new work.
|
|
@@ -228,7 +227,7 @@ Every `dispatch_ask` passes four gates first:
|
|
|
228
227
|
internals are your lane's to decide where the work happens, in the plan or the code, not in the
|
|
229
228
|
spec. A contract between two lanes is settled by those two lanes over Envoy, and you open no ask
|
|
230
229
|
for it. A halt condition (a change to IAM, deletion or exposure of production data, anything
|
|
231
|
-
that reaches a customer) passes this gate: it is your own `dispatch_ask` to
|
|
230
|
+
that reaches a customer) passes this gate: it is your own `dispatch_ask` to the human on your own
|
|
232
231
|
issue.
|
|
233
232
|
2. **Is there genuine uncertainty, and have you measured what you can?** If there is none, it is
|
|
234
233
|
a plan you execute. The one legitimate ask without uncertainty is permission for an action
|
|
@@ -323,7 +322,7 @@ they must read to decide belongs in the spec in the first place — see [Artifac
|
|
|
323
322
|
|
|
324
323
|
Before saying you are waiting for human input, call `dispatch_open_asks`. With no arguments it lists this session's active asks across open issues and project documents, including whether the human or agent owes the next reply. With `dispatch_open_asks({ project })` it lists every open ask in that project — on its issues and on its documents, whoever authored them — which is how you see what a whole project is waiting on rather than just your own asks.
|
|
325
324
|
|
|
326
|
-
**Unsettled product shape needs a decision before implementation.** When a page, navigation entry, table key, customer-scoping rule, or persisted sidecar would set product shape that
|
|
325
|
+
**Unsettled product shape needs a decision before implementation.** When a page, navigation entry, table key, customer-scoping rule, or persisted sidecar would set product shape that the human has not already settled, write a decision block in the document that records the work before the first implementation commit; in a Legion tree the architect writes it, and a phase worker sends the decision to its architect. A lane's schema decision or a contract two lanes agree does not settle product shape. This does not turn a user-specified decision or routine implementation into an approval request. A control or behaviour the human asked for in words is settled by those words, together with every choice inside it that his words do not make (where it sits, its defaults, …
|
|
327
326
|
|
|
328
327
|
**Anything you are blocked on a human for is visible in Dispatch.** An agent waits on a human only
|
|
329
328
|
through an open ask. A to-do, permission, credential or grant renewal, setting only they can
|
|
@@ -359,7 +358,7 @@ Use `mode: "none"` with a concrete reason only when the work is genuinely non-ar
|
|
|
359
358
|
## Close what you opened
|
|
360
359
|
|
|
361
360
|
An ask you opened is yours until it is answered or you resolve it. When the answer arrives some
|
|
362
|
-
other way —
|
|
361
|
+
other way — the human said it live, a later comment settled it, or the question became moot because the
|
|
363
362
|
design moved — resolve it yourself with `dispatch_resolve_ask` in the same turn you learn that.
|
|
364
363
|
Never leave it for the human to clear.
|
|
365
364
|
|
|
@@ -9,13 +9,12 @@ project's architecture model, or list or audit a project's backlog.
|
|
|
9
9
|
When you finish an issue, or are told to work on the next thing, take the top ready issue of the
|
|
10
10
|
whole backlog, across every project: status `todo`, highest priority first, then board rank. There
|
|
11
11
|
are no areas: a standing role, a product owner and a lane each take the top issue like everyone
|
|
12
|
-
else
|
|
12
|
+
else. `todo` means ready: specced, unblocked, and waiting on neither a deploy nor a
|
|
13
13
|
decision. An issue that waits on one belongs in `backlog`, with what it waits on said on the issue.
|
|
14
14
|
|
|
15
15
|
Hold at most three issues in flight (`in_progress`, `testing`, `needs_review` or `retro`), of any
|
|
16
|
-
kind
|
|
17
|
-
|
|
18
|
-
next. Past three:
|
|
16
|
+
kind. The limit is per agent and has nothing to do with the week's priorities: the priorities
|
|
17
|
+
decide only what you pull next. Past three:
|
|
19
18
|
push any unfinished work, say where in one comment on the issue, move it to `backlog` and clear
|
|
20
19
|
its route. Each issue counts on its own; a child does not ride under its parent's slot.
|
|
21
20
|
In-flight issues with no owner at all go
|
|
@@ -27,7 +26,7 @@ writes on the root of a tree Legion is running is set back and the tree's archit
|
|
|
27
26
|
it; only a person in the dashboard, or `legion status`, stops that tree. On an issue under one, a
|
|
28
27
|
status that takes it out of the flow parks that issue and stops its workers.
|
|
29
28
|
|
|
30
|
-
One agent keeps the backlog's order against those priorities
|
|
29
|
+
One agent keeps the backlog's order against those priorities. Setting an
|
|
31
30
|
issue's priority stays yours ([Priority is yours to set](#priority-is-yours-to-set)); reordering
|
|
32
31
|
the board does not.
|
|
33
32
|
When the top of the backlog looks wrong, or a priority's next step is not yet a ready issue,
|
|
@@ -118,7 +117,7 @@ when work has started.
|
|
|
118
117
|
Priority is the coarse bucket a backlog is read by: `0` is P0, the highest, through `3`, P3, the
|
|
119
118
|
lowest, and `null` clears it. Agents set it (`dispatch://LEGION/artifact/issue-status-conventions-md`)
|
|
120
119
|
— on creation, and on a grooming pass over issues that have none — and say what you set and why;
|
|
121
|
-
|
|
120
|
+
the human overrides anything they disagree with from the dashboard. A closed
|
|
122
121
|
issue takes only `rank`, `components`, and a reopening `status` (any status but `done`);
|
|
123
122
|
everything else, `priority` included, waits for the reopen (`409 ISSUE_CLOSED`). So reopen it
|
|
124
123
|
first, then set the priority — the two cannot go in one call. `rank` itself is not a tool field:
|
|
@@ -194,6 +193,16 @@ The audit finds four shapes:
|
|
|
194
193
|
Run the audit as a step of a coordinator's loop, at each checkpoint, not as a habit: these shapes
|
|
195
194
|
are found by running the check, not by noticing them.
|
|
196
195
|
|
|
196
|
+
## Search paging
|
|
197
|
+
|
|
198
|
+
`skill://dispatch` sends you here when a `dispatch_search` page is not enough: more hits remain,
|
|
199
|
+
or a kind runs out before the page does.
|
|
200
|
+
|
|
201
|
+
A page holds `limit` hits (20 by default, 50 at most); the first line names how many match
|
|
202
|
+
(`showing 1-20 of 312`), and, while more can be reached, the last line names the next `offset`.
|
|
203
|
+
Each kind lists at most its best 100, so when the result says the rest cannot be paged to, narrow
|
|
204
|
+
the query or name a `project`.
|
|
205
|
+
|
|
197
206
|
## Syncing a project's architecture model
|
|
198
207
|
|
|
199
208
|
Import a project's architecture model from its configured source repository now (a human configures the source in Settings):
|
|
@@ -79,7 +79,7 @@ exercise a criterion end to end, building that path is a child issue of this tre
|
|
|
79
79
|
|
|
80
80
|
Specifications written into Dispatch follow `skill://dispatch`'s [Writing a spec](../dispatch/SKILL.md#writing-a-spec).
|
|
81
81
|
Wave releases, child closures, and your own status are visible from the issue tree and the
|
|
82
|
-
handoffs; do not narrate them into the spec or a `dispatch_message`. A to-do only
|
|
82
|
+
handoffs; do not narrate them into the spec or a `dispatch_message`. A to-do only a human can clear
|
|
83
83
|
is a `dispatch_ask`.
|
|
84
84
|
|
|
85
85
|
The issue's primary document **is** the root specification. Extend it in place: a new version
|
|
@@ -338,7 +338,7 @@ cross-tree conflict. Report those to the controller with `envoy_publish` to the
|
|
|
338
338
|
your `Legion addressing` line names. Handle everything else in the
|
|
339
339
|
tree. A product, scope, or design decision that needs the human, yours or one a worker escalated,
|
|
340
340
|
is a decision block you write (section 1 says what one does to the root spec's gate). A standalone
|
|
341
|
-
human to-do may use `dispatch_ask`; workers may reach
|
|
341
|
+
human to-do may use `dispatch_ask`; workers may reach the human directly with it the same way. Do not
|
|
342
342
|
create a wait loop for any wake source.
|
|
343
343
|
|
|
344
344
|
Never yield while waiting on a human. A human is waiting on you only where an open ask sits in
|
|
@@ -16,10 +16,10 @@ The Legion extension claims `legion-<project>-controller` and registers controll
|
|
|
16
16
|
with the daemon during session startup. Do not handle a wake unless that startup succeeded.
|
|
17
17
|
|
|
18
18
|
The daemon runs the controller as an interactive OMP terminal session in its private tmux
|
|
19
|
-
server (the pane runs plain `omp`, not `--mode rpc`, and no `legion worker-shim`;
|
|
19
|
+
server (the pane runs plain `omp`, not `--mode rpc`, and no `legion worker-shim`; the operator reaches
|
|
20
20
|
it with `tmux -L legion-<project> select-window -t <window id> \; attach -t legion-<project>`,
|
|
21
21
|
the window id being `controllerLocator.tmuxWindowId` in `legion state --json` — every window
|
|
22
|
-
opens detached, so a bare `attach` lands on whichever window is current).
|
|
22
|
+
opens detached, so a bare `attach` lands on whichever window is current). The operator may attach and
|
|
23
23
|
type into this session at any time. The pane carries no GitHub credential: its GitHub token
|
|
24
24
|
variables are emptied, and both `legion gh -- <args>` and `legion threads resolve` are refused.
|
|
25
25
|
The controller reads Dispatch and applies its controller capability with `legion status <KEY>
|
|
@@ -29,9 +29,8 @@ retrospective's durable output.
|
|
|
29
29
|
role when configured. A human merges under the repository's GitHub branch-protection and
|
|
30
30
|
CODEOWNERS requirements; GitHub's merge queue participates only when the repository enables it.
|
|
31
31
|
5. After that merge, the implementer — not the reviewer or merger — verifies the change in production
|
|
32
|
-
and records it on the PR and the issue
|
|
33
|
-
|
|
34
|
-
architect's sign-off waits for that record.
|
|
32
|
+
and records it on the PR and the issue: the agent that developed it is responsible for testing
|
|
33
|
+
in production. The architect's sign-off waits for that record.
|
|
35
34
|
The record is the pull request's `Production:` line, one pull-request comment, and a
|
|
36
35
|
`dispatch_message` on the issue, each naming what was driven, how, what was observed, and the
|
|
37
36
|
merge commit. A defect the production check finds becomes a corrective child issue of the same tree,
|