@sjawhar/opencode-legion-envoy 0.41.0 → 0.41.2
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 +37 -13
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +121 -126
- package/skills/legion-architect/SKILL.md +3 -0
- package/skills/legion-worker/SKILL.md +3 -1
package/dist/src/server.js
CHANGED
|
@@ -13566,14 +13566,16 @@ var IssueEventPayloadSchema = object({
|
|
|
13566
13566
|
route: string2().nullish()
|
|
13567
13567
|
});
|
|
13568
13568
|
var ArtifactCreatedEventPayloadSchema = object({
|
|
13569
|
-
artifact: object({ name: string2().optional() }).optional()
|
|
13569
|
+
artifact: object({ id: string2().optional(), slug: string2().optional(), name: string2().optional() }).optional()
|
|
13570
13570
|
});
|
|
13571
13571
|
var ArtifactVersionEventPayloadSchema = object({
|
|
13572
|
+
artifact_id: string2().optional(),
|
|
13572
13573
|
name: string2().optional(),
|
|
13573
13574
|
version: object({ number: number2().optional(), summary: string2().nullish() }).optional(),
|
|
13574
13575
|
diff: string2().optional()
|
|
13575
13576
|
});
|
|
13576
13577
|
var askEventPayloadFields = {
|
|
13578
|
+
id: string2().optional(),
|
|
13577
13579
|
opened_event_id: number2().int().positive(),
|
|
13578
13580
|
question: string2().optional(),
|
|
13579
13581
|
options: array(object({ label: string2().optional() })).nullish(),
|
|
@@ -13603,15 +13605,22 @@ var AskEditedEventPayloadSchema = object({
|
|
|
13603
13605
|
edited_by: object({ kind: string2(), id: string2() }).passthrough()
|
|
13604
13606
|
});
|
|
13605
13607
|
var CommentEventPayloadSchema = object({
|
|
13608
|
+
id: string2().optional(),
|
|
13606
13609
|
artifact_name: string2().optional(),
|
|
13607
13610
|
body: string2().optional(),
|
|
13608
13611
|
reply_to: string2().nullish(),
|
|
13609
13612
|
ask_id: string2().nullish(),
|
|
13610
13613
|
ask_question: string2().optional(),
|
|
13611
13614
|
anchor: object({ quote: string2().optional() }).nullish(),
|
|
13612
|
-
suggestion: object({ replace_with: string2().optional() }).nullish()
|
|
13615
|
+
suggestion: object({ replace_with: string2().optional() }).nullish(),
|
|
13616
|
+
author: object({ kind: string2(), id: string2() }).optional(),
|
|
13617
|
+
created_at: string2().optional()
|
|
13618
|
+
});
|
|
13619
|
+
var MessageEventPayloadSchema = object({
|
|
13620
|
+
id: string2().optional(),
|
|
13621
|
+
body: string2().optional(),
|
|
13622
|
+
author: object({ kind: string2(), id: string2() }).optional()
|
|
13613
13623
|
});
|
|
13614
|
-
var MessageEventPayloadSchema = object({ body: string2().optional() });
|
|
13615
13624
|
var ChildStatusEventPayloadSchema = object({
|
|
13616
13625
|
child_key: string2().optional(),
|
|
13617
13626
|
from: string2().optional(),
|
|
@@ -13712,7 +13721,7 @@ var dispatchToolSpecs = [
|
|
|
13712
13721
|
},
|
|
13713
13722
|
{
|
|
13714
13723
|
name: "dispatch_ask",
|
|
13715
|
-
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " +
|
|
13724
|
+
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". Question is at most 800 ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
13716
13725
|
arguments: (z) => ({
|
|
13717
13726
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13718
13727
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -13793,7 +13802,7 @@ var dispatchToolSpecs = [
|
|
|
13793
13802
|
},
|
|
13794
13803
|
{
|
|
13795
13804
|
name: "dispatch_message",
|
|
13796
|
-
description: "Post a
|
|
13805
|
+
description: "Post a note to the issue's Conversation for humans: a status they should see now, or a reply to a human's message. " + "Not a progress ledger (the issue's progress.md artifact), a decision (dispatch_ask), or document feedback " + `(dispatch_comment). Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
|
|
13797
13806
|
arguments: (z) => ({
|
|
13798
13807
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13799
13808
|
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters.")
|
|
@@ -13801,7 +13810,7 @@ var dispatchToolSpecs = [
|
|
|
13801
13810
|
},
|
|
13802
13811
|
{
|
|
13803
13812
|
name: "dispatch_doc_edit",
|
|
13804
|
-
description: "Apply deterministic text edits to an issue or project document. Do not use it for review feedback or for reading; use " +
|
|
13813
|
+
description: "Apply deterministic text edits to an issue or project document. Do not use it for review feedback or for reading; use " + "dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. The spec (or any document) holds requirements, " + `design, and decisions \u2014 record progress in the issue's progress.md artifact instead. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
13805
13814
|
arguments: (z) => ({
|
|
13806
13815
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13807
13816
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -14901,7 +14910,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
14901
14910
|
return { args, ref: null, owner: null };
|
|
14902
14911
|
const refArgument = args.ref;
|
|
14903
14912
|
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
14904
|
-
throw new Error("ref must be a valid dispatch:// reference");
|
|
14913
|
+
throw new Error("ref must be a valid dispatch:// reference such as dispatch://KEY-1, " + "dispatch://KEY-1/ask/<uuid>, dispatch://KEY-1/comment/<uuid>, " + "dispatch://KEY-1/artifact/<slug>, or dispatch://PROJECT/artifact/<slug>");
|
|
14905
14914
|
})() : null;
|
|
14906
14915
|
const issueArgument = args.issue;
|
|
14907
14916
|
const projectArgument = args.project;
|
|
@@ -14973,13 +14982,27 @@ async function resolveArtifact(client, owner, artifactReference) {
|
|
|
14973
14982
|
if (artifactReference === undefined) {
|
|
14974
14983
|
throw new Error("artifact is required for a project document");
|
|
14975
14984
|
}
|
|
14976
|
-
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
14985
|
+
try {
|
|
14986
|
+
return {
|
|
14987
|
+
owner,
|
|
14988
|
+
artifact: await client.getProjectArtifact(owner.project, artifactReference)
|
|
14989
|
+
};
|
|
14990
|
+
} catch (error) {
|
|
14991
|
+
if (!(error instanceof DispatchServiceError) || error.status !== 404)
|
|
14992
|
+
throw error;
|
|
14993
|
+
const artifacts = await client.listProjectArtifacts(owner.project, true);
|
|
14994
|
+
const matches = artifacts.filter((candidate) => candidate.name === artifactReference);
|
|
14995
|
+
if (matches.length > 1) {
|
|
14996
|
+
throw new Error(`artifact name ${artifactReference} is ambiguous in project ${owner.project}; ` + `${matches.length} documents share it \u2014 use its slug instead`);
|
|
14997
|
+
}
|
|
14998
|
+
const artifact = matches[0];
|
|
14999
|
+
if (!artifact)
|
|
15000
|
+
throw error;
|
|
15001
|
+
return { owner, artifact };
|
|
15002
|
+
}
|
|
14980
15003
|
}
|
|
14981
15004
|
const issue = await client.getIssue(owner.issue);
|
|
14982
|
-
const artifact = artifactReference === undefined || artifactReference === "spec" ? issue.artifacts.find((candidate) => candidate.primary || candidate.id === issue.primary_artifact_id) : issue.artifacts.find((candidate) => candidate.id === artifactReference || candidate.slug === artifactReference);
|
|
15005
|
+
const artifact = artifactReference === undefined || artifactReference === "spec" ? issue.artifacts.find((candidate) => candidate.primary || candidate.id === issue.primary_artifact_id) : issue.artifacts.find((candidate) => candidate.id === artifactReference || candidate.slug === artifactReference || candidate.name === artifactReference);
|
|
14983
15006
|
if (!artifact) {
|
|
14984
15007
|
throw new Error(`artifact ${artifactReference ?? "spec"} was not found on issue ${issue.key}`);
|
|
14985
15008
|
}
|
|
@@ -15345,8 +15368,9 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
15345
15368
|
};
|
|
15346
15369
|
const artifactOwner = documentOwner();
|
|
15347
15370
|
const result = artifactOwner.kind === "project" ? await client.projectArtifact(artifactOwner.project, artifactInput) : await client.artifact(issue(), artifactInput);
|
|
15371
|
+
const artifactRef = artifactOwner.kind === "project" ? `dispatch://${artifactOwner.project}/artifact/${result.artifact.slug}` : `dispatch://${issue()}/artifact/${result.artifact.slug}`;
|
|
15348
15372
|
return {
|
|
15349
|
-
text: `Uploaded ${result.artifact.name} as version ${result.version.number}`,
|
|
15373
|
+
text: `Uploaded ${result.artifact.name} as version ${result.version.number} (artifact slug ${result.artifact.slug}; ${artifactRef})`,
|
|
15350
15374
|
details: artifactOwner.kind === "project" ? {
|
|
15351
15375
|
...documentResultDetails(result.artifact),
|
|
15352
15376
|
version: result.version.number
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -5,17 +5,17 @@ description: "Use when asking Sami a question, updating the spec, commenting on
|
|
|
5
5
|
|
|
6
6
|
# Dispatch
|
|
7
7
|
|
|
8
|
-
Dispatch is your issue's living spec, asks, comments, and artifacts. The transcript is your
|
|
9
|
-
|
|
8
|
+
Dispatch is your issue's or project document's living spec, asks, comments, and artifacts. The transcript is your scratch pad. Anything
|
|
9
|
+
meant for a human goes through a `dispatch_*` tool.
|
|
10
10
|
|
|
11
|
-
The server enforces high signal: an ask question is at most 800 characters with at most eight
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
The server enforces high signal: an ask question is at most 800 characters with at most eight options; comment and message bodies are at
|
|
12
|
+
most 2,000 characters; an artifact is at most 25 MiB. It refuses over-limit input; it never truncates it. GitHub threads and markers no
|
|
13
|
+
longer exist.
|
|
14
14
|
|
|
15
15
|
## Writing a spec
|
|
16
16
|
|
|
17
|
-
A spec is a decision record for the human who decides and the implementer who builds, not a
|
|
18
|
-
|
|
17
|
+
A spec is a decision record for the human who decides and the implementer who builds, not a transcript of your thinking. Use exactly
|
|
18
|
+
these document headings in this order.
|
|
19
19
|
|
|
20
20
|
| Section | Required content | Form |
|
|
21
21
|
| --- | --- | --- |
|
|
@@ -35,32 +35,23 @@ transcript of your thinking. Use exactly these document headings in this order.
|
|
|
35
35
|
- Do not hedge with “might” or “could consider.”
|
|
36
36
|
- Do not use TBD, TODO, or placeholders; an open item is a Decision needed.
|
|
37
37
|
- Keep each section to one screen; work that exceeds one screen per section is two specs.
|
|
38
|
-
- Update the spec in place as decisions land
|
|
39
|
-
|
|
40
|
-
### Self-review
|
|
41
|
-
|
|
42
|
-
- [ ] No placeholders remain.
|
|
43
|
-
- [ ] No sections conflict.
|
|
44
|
-
- [ ] The spec covers one implementation plan's worth of work.
|
|
45
|
-
- [ ] Every requirement has exactly one reading.
|
|
38
|
+
- Update the spec in place as decisions land: the spec is the record, comments are the discussion.
|
|
39
|
+
- Before sending it: no sections conflict, and every requirement has exactly one reading.
|
|
46
40
|
|
|
47
41
|
## Your owner
|
|
48
42
|
|
|
49
|
-
Every session works on an issue or project document. Legion pre-fills `issue` from
|
|
50
|
-
`
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`design-notes` identifies `dispatch://CORE/artifact/design-notes`. On first use, an external
|
|
55
|
-
issue reference creates its native issue in the project configured for that repository in
|
|
56
|
-
Dispatch Settings, then falls back to `DISPATCH_DEFAULT_PROJECT`.
|
|
43
|
+
Every session works on an issue or project document. Legion pre-fills `issue` from `LEGION_ISSUE`: use a native issue key such as
|
|
44
|
+
`LEGION-3`, an external `owner/repo#n` reference, or a bare positive number (resolved against the cwd repository). Otherwise pass
|
|
45
|
+
exactly one owner to every owner-scoped tool: `issue` for an issue, or `project` and `artifact` for an unlinked project document (see
|
|
46
|
+
[References](#references) for the resulting ref shape). On first use, an external issue reference creates its native issue in the
|
|
47
|
+
project configured for that repository in Dispatch Settings, then falls back to `DISPATCH_DEFAULT_PROJECT`.
|
|
57
48
|
|
|
58
49
|
Architects create newly tracked child work with:
|
|
59
50
|
```ts
|
|
60
51
|
dispatch_issue({ project, title, parent?, external?, spec?, force? })
|
|
61
52
|
```
|
|
62
|
-
It returns `details` `{ issue, topic }`. Use `dispatch_issue` only to create an issue; never use
|
|
63
|
-
|
|
53
|
+
It returns `details` `{ issue, topic }`. Use `dispatch_issue` only to create an issue; never use it to park a question. When `spec` is
|
|
54
|
+
supplied, follow [Writing a spec](#writing-a-spec).
|
|
64
55
|
|
|
65
56
|
## Search first
|
|
66
57
|
|
|
@@ -68,13 +59,12 @@ Before you create an issue or start a design document, search:
|
|
|
68
59
|
```ts
|
|
69
60
|
dispatch_search({ query, project?, limit? })
|
|
70
61
|
```
|
|
71
|
-
It returns every issue, document, comment, ask, and message that contains the words, with the
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
It returns every issue, document, comment, ask, and message that contains the words, with the issue key and a link. Cite the hit you
|
|
63
|
+
build on (`dispatch://KEY` or the document reference), or state "no prior issue" in the spec. Websearch syntax applies: `"merge queue"`,
|
|
64
|
+
`-daemon`, `OR`.
|
|
74
65
|
|
|
75
|
-
`dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns
|
|
76
|
-
|
|
77
|
-
call with `force: true` when it is genuinely new work.
|
|
66
|
+
`dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns the candidates (`POSSIBLE_DUPLICATE`).
|
|
67
|
+
Read them; reference the existing issue, or repeat the call with `force: true` when it is genuinely new work.
|
|
78
68
|
|
|
79
69
|
## Asking
|
|
80
70
|
|
|
@@ -91,14 +81,14 @@ dispatch_ask({
|
|
|
91
81
|
anchor?: { artifact, quote, occurrence? },
|
|
92
82
|
})
|
|
93
83
|
```
|
|
94
|
-
It returns `details` `{ issue, topic, ask }` for an issue or `{ project, artifact, document,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
84
|
+
It returns `details` `{ issue, topic, ask }` for an issue or `{ project, artifact, document, topic, ask }` for a project document.
|
|
85
|
+
Options are buttons: never enumerate choices in prose. Put the recommendation in `question`, and put each selectable choice in
|
|
86
|
+
`options`. Anchor a document question with `anchor: { artifact, quote, occurrence? }`; `occurrence` is zero-based and selects a repeated
|
|
87
|
+
quote, and an anchor whose quote later disappears becomes orphaned but stays readable against its original document version.
|
|
88
|
+
|
|
89
|
+
An ask must be answerable from its own text and its anchor alone. Anchor a question about a document passage with `anchor`; thread one
|
|
90
|
+
about a comment with `reply_to`; thread a follow-up on your own ask with `reply_to_ask`; cite anything else with a `dispatch://`
|
|
91
|
+
reference (see [References](#references)). Never write "see above", "the message above", or "as attached".
|
|
102
92
|
|
|
103
93
|
Correct or refine an open ask in place instead of opening a second question:
|
|
104
94
|
```ts
|
|
@@ -110,12 +100,11 @@ dispatch_edit_ask({
|
|
|
110
100
|
urgency?,
|
|
111
101
|
})
|
|
112
102
|
```
|
|
113
|
-
At least one field besides `ask` is required. Use this only while the same decision remains
|
|
114
|
-
|
|
115
|
-
If the decision is moot or superseded, retract the old ask and open a new one.
|
|
103
|
+
At least one field besides `ask` is required. Use this only while the same decision remains open: it keeps the prior text in the event
|
|
104
|
+
log. An answered or resolved ask cannot be edited. If the decision is moot or superseded, retract the old ask and open a new one.
|
|
116
105
|
|
|
117
|
-
An ask stays open until a human answers, unless its question no longer needs that answer. Retract a
|
|
118
|
-
|
|
106
|
+
An ask stays open until a human answers, unless its question no longer needs that answer. Retract a moot or superseded question, or
|
|
107
|
+
self-resolve one after finding the answer:
|
|
119
108
|
```ts
|
|
120
109
|
dispatch_resolve_ask({
|
|
121
110
|
ask,
|
|
@@ -123,31 +112,31 @@ dispatch_resolve_ask({
|
|
|
123
112
|
reason: "A newer ask supersedes this question.",
|
|
124
113
|
})
|
|
125
114
|
```
|
|
126
|
-
Use `retracted` when the question is obsolete and `resolved` when you found the answer. Include the
|
|
127
|
-
|
|
128
|
-
|
|
115
|
+
Use `retracted` when the question is obsolete and `resolved` when you found the answer. Include the reason because the question remains
|
|
116
|
+
in its Conversation card and reply thread. Resolution is not an answer: it never records a human decision, and an answered ask cannot be
|
|
117
|
+
resolved. A human may reply to an open or answered ask; so may you, e.g. after finding the answer — use `reply_to_ask` on
|
|
118
|
+
`dispatch_comment` (mutually exclusive with `reply_to`).
|
|
129
119
|
|
|
130
|
-
|
|
131
|
-
(the asker) can reply too — e.g. acknowledging a clarifying question, or following up after the
|
|
132
|
-
answer. Use `reply_to_ask` on `dispatch_comment` to reply under your own ask; it is mutually
|
|
133
|
-
exclusive with `reply_to`.
|
|
120
|
+
## The Spec
|
|
134
121
|
|
|
135
|
-
|
|
136
|
-
|
|
122
|
+
The spec holds requirements, design, acceptance, decisions, and rejected alternatives, structured per [Writing a spec](#writing-a-spec).
|
|
123
|
+
It changes only when a decision or requirement changes, and every version that records one is named with `summary`. Never write
|
|
124
|
+
progress, status, timestamps, an "Update HH:MMZ" section, a PR list, or handoff notes into the spec — that belongs in
|
|
125
|
+
[Progress](#progress) instead.
|
|
137
126
|
|
|
138
127
|
Read the current document before changing it:
|
|
139
128
|
|
|
140
129
|
```ts
|
|
141
130
|
dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
|
|
142
131
|
```
|
|
143
|
-
It returns live or versioned markdown with open marks. `issue` with an omitted `artifact`
|
|
144
|
-
|
|
145
|
-
|
|
132
|
+
It returns live or versioned markdown with open marks. `issue` with an omitted `artifact` reads the issue specification; a project needs
|
|
133
|
+
`artifact`; and a `dispatch://PROJECT/artifact/<slug>` ref supplies both. Then write with:
|
|
134
|
+
|
|
146
135
|
```ts
|
|
147
136
|
dispatch_doc_edit({ issue?, project?, artifact, ops, summary? })
|
|
148
137
|
```
|
|
149
|
-
It returns issue or project-document owner details plus `applied`, optional `version`, and its
|
|
150
|
-
|
|
138
|
+
It returns issue or project-document owner details plus `applied`, optional `version`, and its write `topic`. `ops` is an array of this
|
|
139
|
+
exact `EditOp` shape:
|
|
151
140
|
|
|
152
141
|
```ts
|
|
153
142
|
type EditOp = {
|
|
@@ -161,16 +150,40 @@ type EditOp = {
|
|
|
161
150
|
};
|
|
162
151
|
```
|
|
163
152
|
|
|
164
|
-
Target `replace` and `delete` by the document's plain text: inline-code and link text match
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
153
|
+
Target `replace` and `delete` by the document's plain text: inline-code and link text match without Markdown syntax, and a table-cell
|
|
154
|
+
anchor is its cell text. Quote code-block contents without their Markdown fences. A quote must stay within one textblock; split changes
|
|
155
|
+
that span separate blocks into separate operations.
|
|
156
|
+
|
|
157
|
+
`replace` requires `find` and `with`; `delete` requires `find`; `insert` requires `markdown` and exactly one of `after` or `before`. An
|
|
158
|
+
insert anchor is a quote, `"start"`, `"end"`, or `"heading:Title"`. Ordinary inserts create a sibling block before or after the quote or
|
|
159
|
+
heading's enclosing document block; `"start"` and `"end"` select the document edges. At a table-cell quote, a body-row fragment (no
|
|
160
|
+
header or delimiter rows) extends that table before or after the matched row instead; short rows are padded, wider rows are rejected,
|
|
161
|
+
and deleting a cell's quoted text removes only that text.
|
|
162
|
+
|
|
163
|
+
Use `replace` for inline continuation. Use zero-based `occurrence` for a repeated target; re-read a missing or ambiguous target before
|
|
164
|
+
retrying. Pass `summary` to name the version when recording a decision.
|
|
165
|
+
|
|
166
|
+
## Progress
|
|
167
|
+
|
|
168
|
+
Every issue you work has one progress artifact, `progress.md` — for humans reading later and for your own successor after compaction. It
|
|
169
|
+
is never a wake signal.
|
|
170
|
+
|
|
171
|
+
Create it once, on first use:
|
|
172
|
+
```ts
|
|
173
|
+
dispatch_artifact({ issue, name: "progress.md", content: "### 2026-09-11 15:00Z - Started\n..." })
|
|
174
|
+
```
|
|
175
|
+
The server slugs `progress.md` to `progress-md`; address every later edit with that slug. Append — never edit or remove an earlier
|
|
176
|
+
entry:
|
|
177
|
+
```ts
|
|
178
|
+
dispatch_doc_edit({
|
|
179
|
+
issue,
|
|
180
|
+
artifact: "progress-md",
|
|
181
|
+
ops: [{ op: "insert", after: "end", markdown: "### 2026-09-11 16:10Z - Blocked\n..." }],
|
|
182
|
+
})
|
|
183
|
+
```
|
|
184
|
+
Newest entry last. Each entry is `### <UTC time> - <headline>` followed by 1-5 lines: what changed (cite `dispatch://` refs or PR
|
|
185
|
+
links), what is blocked and on whom, and what is next. `.legion/<phase>.json` is the durable machine handoff between phases;
|
|
186
|
+
`progress.md` is the human-readable narrative for the same work — keep both, never conflate one for the other.
|
|
174
187
|
|
|
175
188
|
## Comments and suggestions
|
|
176
189
|
|
|
@@ -180,12 +193,11 @@ Add feedback with:
|
|
|
180
193
|
dispatch_comment({ issue?, project?, artifact?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
|
|
181
194
|
```
|
|
182
195
|
|
|
183
|
-
It returns issue or project-document owner details plus `comment` and, for writes, `topic`.
|
|
184
|
-
`
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
`
|
|
188
|
-
mutually exclusive. Comments are edited only by their author from the dashboard.
|
|
196
|
+
It returns issue or project-document owner details plus `comment` and, for writes, `topic`. `quote` requires `artifact`; omit both for a
|
|
197
|
+
floating issue comment. A reply (`reply_to`/`reply_to_ask`) takes no `quote`; it belongs to its parent's anchor. Use `reply_to` to
|
|
198
|
+
continue a comment thread at its root; a reply to a resolved thread reopens it. Use `reply_to_ask` to reply directly under a question
|
|
199
|
+
asked with `dispatch_ask`. Comments are edited only by their author from the dashboard. A delivered `comment.created` event carries the
|
|
200
|
+
comment `id`; reply to it with `dispatch_comment({ reply_to: <id> })`.
|
|
189
201
|
|
|
190
202
|
Propose an exact replacement instead of describing it:
|
|
191
203
|
|
|
@@ -193,15 +205,9 @@ Propose an exact replacement instead of describing it:
|
|
|
193
205
|
dispatch_suggest({ issue?, project?, artifact, quote, replace_with, body?, occurrence? })
|
|
194
206
|
```
|
|
195
207
|
|
|
196
|
-
It returns issue or project-document owner details plus `comment` and its write `topic`. A
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
before retrying. `INVALID_ANCHOR` requires exactly one nonempty anchor `quote` or `mark_id`;
|
|
200
|
-
`ANCHOR_MISSING` means a browser mark was not observed in the live tree, and
|
|
201
|
-
`ANCHOR_ORPHANED` means its marked text no longer exists. `INVALID_MARKDOWN` and `DOC_SCHEMA`
|
|
202
|
-
reject Markdown or a live tree outside the Proof schema. `INVALID_OP` names a malformed edit;
|
|
203
|
-
`CAP_EXCEEDED` never truncates; `ISSUE_CLOSED` rejects a write. `ACTOR_KIND` and `ROUTE_INVALID`
|
|
204
|
-
reject an invalid actor or route.
|
|
208
|
+
It returns issue or project-document owner details plus `comment` and its write `topic`. A human accepts or rejects a suggestion.
|
|
209
|
+
Errors: `TARGET_AMBIGUOUS` (add `occurrence`), `TARGET_NOT_FOUND` (re-read first), `INVALID_ANCHOR`/`ANCHOR_MISSING`/`ANCHOR_ORPHANED`
|
|
210
|
+
(bad, unwritten, or stale quote), `INVALID_MARKDOWN`/`DOC_SCHEMA` (malformed content), `CAP_EXCEEDED`, `ISSUE_CLOSED`.
|
|
205
211
|
|
|
206
212
|
## Artifacts
|
|
207
213
|
|
|
@@ -217,39 +223,37 @@ Or, when the text is already in the call, post a Markdown document directly:
|
|
|
217
223
|
dispatch_artifact({ issue?, project?, name: "spec.md", content: "# Design\n..." })
|
|
218
224
|
```
|
|
219
225
|
|
|
220
|
-
Exactly one of `issue` and `project` is required. A project upload creates an unlinked project
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
same `name` creates its next version. Use `content` when the text is already in the call.
|
|
226
|
+
Exactly one of `issue` and `project` is required. A project upload creates an unlinked project document; it must not include `artifact`.
|
|
227
|
+
Exactly one of `path` and `content` is required. It returns issue or project-document owner details plus `artifact`, `version`, and its
|
|
228
|
+
write `topic`. Uploading the same `name` creates its next version. Address an existing artifact by the slug shown in the upload result
|
|
229
|
+
or by its filename; the slug also arrives on `artifact.created` events.
|
|
225
230
|
|
|
226
231
|
## Messages
|
|
227
232
|
|
|
228
|
-
|
|
233
|
+
Post a note to the issue's Conversation for humans: a short status they should see now (a deploy landed, a blocker appeared), or a reply
|
|
234
|
+
to a human's message.
|
|
229
235
|
|
|
230
236
|
```ts
|
|
231
237
|
dispatch_message({ issue, body })
|
|
232
238
|
```
|
|
233
239
|
|
|
234
|
-
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters.
|
|
235
|
-
|
|
240
|
+
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters. It is not a progress ledger (`progress.md`), a
|
|
241
|
+
decision (`dispatch_ask`), or document feedback (`dispatch_comment`). Your message does not wake anyone unless the issue is routed.
|
|
236
242
|
|
|
237
243
|
## What comes back
|
|
238
244
|
|
|
239
|
-
A write result's `details.topic` subscribes the host to its owner. Issue writes use
|
|
240
|
-
`notifications.dispatch.
|
|
241
|
-
|
|
242
|
-
event; `notify` only controls agent wake and routed delivery. After a restart, catch up with:
|
|
245
|
+
A write result's `details.topic` subscribes the host to its owner. Issue writes use `notifications.dispatch.issue.<KEY>.>`;
|
|
246
|
+
project-document writes use `notifications.dispatch.document.<PROJECT>.<SLUG>.>`. The owner topic carries every Dispatch event; `notify`
|
|
247
|
+
only controls agent wake and routed delivery. After a restart, catch up with:
|
|
243
248
|
|
|
244
249
|
```ts
|
|
245
250
|
dispatch_read({ issue?, project?, artifact?, ref? })
|
|
246
251
|
```
|
|
247
252
|
|
|
248
|
-
With an issue ref, it returns the issue summary, open asks, references, and recent events with
|
|
249
|
-
`details` `{
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
253
|
+
With an issue ref, it returns the issue summary, open asks, references, and recent events with `details` `{ issue }`. With a project
|
|
254
|
+
document owner or ref, it returns a document summary with `details` `{ project, document }`. With an ask ref, it returns that ask's
|
|
255
|
+
question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain. Reads do
|
|
256
|
+
not subscribe; use `dispatch_doc_read` for document contents.
|
|
253
257
|
|
|
254
258
|
## References
|
|
255
259
|
|
|
@@ -266,6 +270,8 @@ dispatch://PROJECT/artifact/<slug>/ask/<id>
|
|
|
266
270
|
dispatch://PROJECT/artifact/<slug>/comment/<id>
|
|
267
271
|
```
|
|
268
272
|
|
|
273
|
+
A bare UUID or `KEY#seq` is not a reference; the `dispatch://` form is what Dispatch links and records.
|
|
274
|
+
|
|
269
275
|
## Before / after
|
|
270
276
|
|
|
271
277
|
Before — a wall of text hides the decision and makes the choices unclickable:
|
|
@@ -283,48 +289,37 @@ After — anchor the decision and make each option a button:
|
|
|
283
289
|
dispatch_ask({
|
|
284
290
|
issue: "LEGION-815",
|
|
285
291
|
question:
|
|
286
|
-
"Choose the release gate. Recommendation: ship after release-note review
|
|
292
|
+
"Choose the release gate. Recommendation: ship after release-note review, since the tested deployment is otherwise ready.",
|
|
287
293
|
options: [
|
|
288
|
-
{
|
|
289
|
-
|
|
290
|
-
description: "Keeps the tested release intact and publishes reviewed instructions.",
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
label: "Ship now",
|
|
294
|
-
description: "Meets the demo deadline; release notes follow separately.",
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
label: "Remove dashboard changes",
|
|
298
|
-
description: "Narrows the release but requires another deployment test.",
|
|
299
|
-
},
|
|
294
|
+
{ label: "Review notes, then ship", description: "Keeps the release intact and reviewed." },
|
|
295
|
+
{ label: "Ship now", description: "Meets the demo deadline; release notes follow later." },
|
|
300
296
|
],
|
|
301
297
|
urgency: "high",
|
|
302
|
-
anchor: {
|
|
303
|
-
artifact: "spec",
|
|
304
|
-
quote: "Release requires reviewed operator instructions before deployment.",
|
|
305
|
-
},
|
|
298
|
+
anchor: { artifact: "spec", quote: "Release requires reviewed operator instructions before deployment." },
|
|
306
299
|
})
|
|
307
300
|
```
|
|
308
301
|
|
|
309
|
-
Before — a
|
|
302
|
+
Before — progress typed once into a message, gone once compaction drops it from context:
|
|
310
303
|
|
|
311
|
-
```
|
|
312
|
-
|
|
304
|
+
```ts
|
|
305
|
+
dispatch_message({ issue: "LEGION-815", body: "Merged the release PR, moving to docs next." })
|
|
313
306
|
```
|
|
314
307
|
|
|
315
|
-
After —
|
|
308
|
+
After — append it to the progress artifact, where the record survives:
|
|
316
309
|
|
|
317
310
|
```ts
|
|
318
311
|
dispatch_doc_edit({
|
|
319
312
|
issue: "LEGION-815",
|
|
320
|
-
artifact: "
|
|
313
|
+
artifact: "progress-md",
|
|
321
314
|
ops: [
|
|
322
315
|
{
|
|
323
|
-
op: "
|
|
324
|
-
|
|
325
|
-
|
|
316
|
+
op: "insert",
|
|
317
|
+
after: "end",
|
|
318
|
+
markdown:
|
|
319
|
+
"### 2026-09-11 15:40Z - Release PR merged\n" +
|
|
320
|
+
"- dispatch://LEGION-815/artifact/spec stays unchanged; this is progress, not a decision.\n" +
|
|
321
|
+
"- Next: docs review.",
|
|
326
322
|
},
|
|
327
323
|
],
|
|
328
|
-
summary: "Recorded merged release",
|
|
329
324
|
})
|
|
330
325
|
```
|
|
@@ -71,6 +71,9 @@ exercise a criterion end to end, building that path is a child issue of this tre
|
|
|
71
71
|
inert until released.
|
|
72
72
|
|
|
73
73
|
Specifications written into Dispatch follow [`skills/dispatch`'s Writing a spec](../dispatch/SKILL.md#writing-a-spec).
|
|
74
|
+
Record ongoing status — wave releases, child closures, blockers — in the issue's `progress.md`
|
|
75
|
+
artifact (see [`skills/dispatch`'s Progress](../dispatch/SKILL.md#progress)), never in the spec
|
|
76
|
+
or a `dispatch_message`.
|
|
74
77
|
|
|
75
78
|
Write one root specification containing the accepted scope, adoption/decomposition,
|
|
76
79
|
waves, acceptance criteria, and integration test. When the config-armed root design gate
|
|
@@ -97,7 +97,9 @@ committed predecessor handoffs in lifecycle order from `$LEGION_WORKSPACE/.legio
|
|
|
97
97
|
Read only files that precede the assigned phase. There is no handoff schema (rejected
|
|
98
98
|
design — no schema validation runs anywhere in this pipeline): write the phase-specific
|
|
99
99
|
fields the next phase and the architect need, consistent with what predecessor phases
|
|
100
|
-
already wrote. The durable copy lives in `$LEGION_WORKSPACE/.legion/<phase>.json
|
|
100
|
+
already wrote. The durable copy lives in `$LEGION_WORKSPACE/.legion/<phase>.json` — the machine
|
|
101
|
+
handoff between phases, not a human-readable status; post that to the issue's `progress.md`
|
|
102
|
+
artifact instead (see [`skills/dispatch`'s Progress](../dispatch/SKILL.md#progress)). If a
|
|
101
103
|
committed handoff conflicts with memory or a prior transcript, the committed file wins: it
|
|
102
104
|
is the copy that survived.
|
|
103
105
|
|