@sjawhar/opencode-legion-envoy 0.41.2 → 0.43.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
|
@@ -13619,6 +13619,8 @@ var CommentEventPayloadSchema = object({
|
|
|
13619
13619
|
var MessageEventPayloadSchema = object({
|
|
13620
13620
|
id: string2().optional(),
|
|
13621
13621
|
body: string2().optional(),
|
|
13622
|
+
reply_to: string2().nullish(),
|
|
13623
|
+
reply_body: string2().optional(),
|
|
13622
13624
|
author: object({ kind: string2(), id: string2() }).optional()
|
|
13623
13625
|
});
|
|
13624
13626
|
var ChildStatusEventPayloadSchema = object({
|
|
@@ -13683,15 +13685,17 @@ function dispatchToolSchema(spec, z, opts) {
|
|
|
13683
13685
|
}
|
|
13684
13686
|
var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference; an external reference creates its native issue in the repository's dashboard-configured project or, failing that, the default project (DISPATCH_DEFAULT_PROJECT).";
|
|
13685
13687
|
var OWNER_REFERENCE = "Exactly one of issue and project is required. An issue is a native KEY or external owner/repo#n reference; a project is a project key such as CORE and addresses an unlinked project document named by artifact.";
|
|
13686
|
-
function documentOwnerValidation(requireArtifact) {
|
|
13688
|
+
function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false) {
|
|
13687
13689
|
return {
|
|
13688
13690
|
check: (value) => {
|
|
13689
13691
|
const input = value;
|
|
13690
13692
|
const hasIssue = typeof input.issue === "string";
|
|
13691
13693
|
const hasProject = typeof input.project === "string";
|
|
13692
|
-
|
|
13694
|
+
const hasArtifact = typeof input.artifact === "string";
|
|
13695
|
+
const hasRef = typeof input.ref === "string";
|
|
13696
|
+
return (hasIssue !== hasProject || !hasIssue && !hasProject && hasRef) && (!hasProject || !requireArtifact || hasArtifact) && (!alwaysRequireArtifact || hasArtifact || hasRef);
|
|
13693
13697
|
},
|
|
13694
|
-
message: "Exactly one of issue and project is required; with project, artifact names the document."
|
|
13698
|
+
message: alwaysRequireArtifact ? "Exactly one of issue and project is required; artifact or ref must name the document." : "Exactly one of issue and project is required; with project, artifact names the document."
|
|
13695
13699
|
};
|
|
13696
13700
|
}
|
|
13697
13701
|
var SPEC_SECTIONS = [
|
|
@@ -13726,6 +13730,7 @@ var dispatchToolSpecs = [
|
|
|
13726
13730
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13727
13731
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
13728
13732
|
artifact: z.string().describe("Project document slug or id.").optional(),
|
|
13733
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
13729
13734
|
question: z.string({ max: 800 }).describe("Decision question, at most 800 characters."),
|
|
13730
13735
|
options: z.array(z.object({
|
|
13731
13736
|
label: z.string().describe("Selectable option label."),
|
|
@@ -13778,6 +13783,7 @@ var dispatchToolSpecs = [
|
|
|
13778
13783
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13779
13784
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
13780
13785
|
artifact: z.string().describe("Artifact slug or id required when quote is given.").optional(),
|
|
13786
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
13781
13787
|
quote: z.string().describe("Optional exact quoted document text.").optional(),
|
|
13782
13788
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
13783
13789
|
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
@@ -13792,29 +13798,32 @@ var dispatchToolSpecs = [
|
|
|
13792
13798
|
arguments: (z) => ({
|
|
13793
13799
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13794
13800
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
13795
|
-
artifact: z.string().describe("Artifact slug or id containing the quoted text."),
|
|
13801
|
+
artifact: z.string().describe("Artifact slug or id containing the quoted text; optional when ref names it.").optional(),
|
|
13802
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
13796
13803
|
quote: z.string().describe("Exact document text to replace."),
|
|
13797
13804
|
replace_with: z.string().describe("Replacement text."),
|
|
13798
13805
|
body: z.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
|
|
13799
13806
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
|
|
13800
13807
|
}),
|
|
13801
|
-
validation: documentOwnerValidation(true)
|
|
13808
|
+
validation: documentOwnerValidation(true, true)
|
|
13802
13809
|
},
|
|
13803
13810
|
{
|
|
13804
13811
|
name: "dispatch_message",
|
|
13805
|
-
description: "Post a note
|
|
13812
|
+
description: "Post a note humans must read now: a reply to a human's message, a deliverable that landed, or a blocker only " + "they can clear. Never progress or status updates - Dispatch is a high-signal record, not a log. Not a decision " + `(dispatch_ask) or document feedback (dispatch_comment). Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
|
|
13806
13813
|
arguments: (z) => ({
|
|
13807
13814
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13808
|
-
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters.")
|
|
13815
|
+
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
|
|
13816
|
+
reply_to: z.string().describe("Optional message id or dispatch://KEY/message/<id> reference to reply to, threading " + "this message under it so the reply stays with the original in the Conversation.").optional()
|
|
13809
13817
|
})
|
|
13810
13818
|
},
|
|
13811
13819
|
{
|
|
13812
13820
|
name: "dispatch_doc_edit",
|
|
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
|
|
13821
|
+
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 - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
13814
13822
|
arguments: (z) => ({
|
|
13815
13823
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13816
13824
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
13817
|
-
artifact: z.string().describe("Artifact slug or id for the document."),
|
|
13825
|
+
artifact: z.string().describe("Artifact slug or id for the document; optional when ref names it.").optional(),
|
|
13826
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
13818
13827
|
ops: z.array(z.object({
|
|
13819
13828
|
op: z.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
13820
13829
|
find: z.string().describe("Text to find for replace or delete.").optional(),
|
|
@@ -13826,7 +13835,7 @@ var dispatchToolSpecs = [
|
|
|
13826
13835
|
})).describe("Flat tagged edits; the server validates fields required for each operation."),
|
|
13827
13836
|
summary: z.string().describe("Optional named-version summary.").optional()
|
|
13828
13837
|
}),
|
|
13829
|
-
validation: documentOwnerValidation(true)
|
|
13838
|
+
validation: documentOwnerValidation(true, true)
|
|
13830
13839
|
},
|
|
13831
13840
|
{
|
|
13832
13841
|
name: "dispatch_doc_read",
|
|
@@ -14598,6 +14607,16 @@ class DispatchClient {
|
|
|
14598
14607
|
async message(issue, input) {
|
|
14599
14608
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
14600
14609
|
}
|
|
14610
|
+
async getMessage(issue, id) {
|
|
14611
|
+
return this.#json("GET", [
|
|
14612
|
+
"api",
|
|
14613
|
+
"v1",
|
|
14614
|
+
"issues",
|
|
14615
|
+
await this.#resolveIssue(issue),
|
|
14616
|
+
"messages",
|
|
14617
|
+
id
|
|
14618
|
+
]);
|
|
14619
|
+
}
|
|
14601
14620
|
async artifact(issue, input) {
|
|
14602
14621
|
const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"];
|
|
14603
14622
|
if ("content" in input)
|
|
@@ -14862,10 +14881,10 @@ function parseDispatchRef(ref) {
|
|
|
14862
14881
|
id: targetID
|
|
14863
14882
|
};
|
|
14864
14883
|
}
|
|
14865
|
-
const issueReference = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec)|\/(log)|\/(children)|\/artifact\/([^/@]+)(?:@v(\d+))?|\/ask\/([^/]+)|\/comment\/([^/]+))?$/);
|
|
14884
|
+
const issueReference = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec)|\/(log)|\/(children)|\/artifact\/([^/@]+)(?:@v(\d+))?|\/ask\/([^/]+)|\/comment\/([^/]+)|\/message\/([^/]+))?$/);
|
|
14866
14885
|
if (!issueReference)
|
|
14867
14886
|
return null;
|
|
14868
|
-
const [, issue, spec, log, children, artifact, version, ask, comment] = issueReference;
|
|
14887
|
+
const [, issue, spec, log, children, artifact, version, ask, comment, message] = issueReference;
|
|
14869
14888
|
if (!issue || version !== undefined && Number(version) < 1)
|
|
14870
14889
|
return null;
|
|
14871
14890
|
const owner = { kind: "issue", issue };
|
|
@@ -14887,6 +14906,8 @@ function parseDispatchRef(ref) {
|
|
|
14887
14906
|
return { owner, kind: "ask", id: ask };
|
|
14888
14907
|
if (comment)
|
|
14889
14908
|
return { owner, kind: "comment", id: comment };
|
|
14909
|
+
if (message)
|
|
14910
|
+
return { owner, kind: "message", id: message };
|
|
14890
14911
|
return { owner, kind: "issue", id: issue };
|
|
14891
14912
|
}
|
|
14892
14913
|
function askId(args) {
|
|
@@ -14899,6 +14920,16 @@ function askId(args) {
|
|
|
14899
14920
|
}
|
|
14900
14921
|
return reference.id;
|
|
14901
14922
|
}
|
|
14923
|
+
function messageReplyTo(args) {
|
|
14924
|
+
const replyTo = optionalString(args, "reply_to");
|
|
14925
|
+
if (replyTo === undefined || !replyTo.startsWith("dispatch://"))
|
|
14926
|
+
return replyTo;
|
|
14927
|
+
const reference = parseDispatchRef(replyTo);
|
|
14928
|
+
if (reference?.kind !== "message") {
|
|
14929
|
+
throw new Error("reply_to must be a bare message id or a dispatch://.../message/<id> reference");
|
|
14930
|
+
}
|
|
14931
|
+
return reference.id;
|
|
14932
|
+
}
|
|
14902
14933
|
function toolSchema(tool) {
|
|
14903
14934
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
14904
14935
|
if (!spec)
|
|
@@ -14910,7 +14941,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
14910
14941
|
return { args, ref: null, owner: null };
|
|
14911
14942
|
const refArgument = args.ref;
|
|
14912
14943
|
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
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>");
|
|
14944
|
+
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/message/<uuid>, dispatch://KEY-1/artifact/<slug>, or " + "dispatch://PROJECT/artifact/<slug>");
|
|
14914
14945
|
})() : null;
|
|
14915
14946
|
const issueArgument = args.issue;
|
|
14916
14947
|
const projectArgument = args.project;
|
|
@@ -15101,6 +15132,18 @@ function commentSummary({ comment, replies }) {
|
|
|
15101
15132
|
return ["Comment:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
15102
15133
|
`);
|
|
15103
15134
|
}
|
|
15135
|
+
function messageSummary({ message, replies }) {
|
|
15136
|
+
const root = [
|
|
15137
|
+
`${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
|
|
15138
|
+
`Body: ${message.body}`
|
|
15139
|
+
];
|
|
15140
|
+
const chain = replies.flatMap((reply) => [
|
|
15141
|
+
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
15142
|
+
`Body: ${reply.body}`
|
|
15143
|
+
]);
|
|
15144
|
+
return ["Message:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
15145
|
+
`);
|
|
15146
|
+
}
|
|
15104
15147
|
async function openArtifactMarks(client, resolved) {
|
|
15105
15148
|
const asks = resolved.owner.kind === "project" ? await client.getArtifactAsks(resolved.artifact.id) : resolved.issue?.open_asks ?? [];
|
|
15106
15149
|
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
@@ -15308,9 +15351,15 @@ async function executeDispatchTool(input) {
|
|
|
15308
15351
|
};
|
|
15309
15352
|
}
|
|
15310
15353
|
case "dispatch_message": {
|
|
15311
|
-
const
|
|
15354
|
+
const replyTo = messageReplyTo(args);
|
|
15355
|
+
const message = await client.message(issue(), {
|
|
15356
|
+
body: stringArg(args, "body"),
|
|
15357
|
+
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
15358
|
+
actor
|
|
15359
|
+
});
|
|
15360
|
+
const messageRef = `dispatch://${message.issue_key}/message/${message.id}`;
|
|
15312
15361
|
return {
|
|
15313
|
-
text: `Posted message ${message.id}`,
|
|
15362
|
+
text: `Posted message ${message.id} (${messageRef})`,
|
|
15314
15363
|
details: {
|
|
15315
15364
|
issue: message.issue_key,
|
|
15316
15365
|
topic: dispatchIssueSubject(message.issue_key, ">"),
|
|
@@ -15397,6 +15446,16 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
15397
15446
|
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: comment.comment.issue_key }
|
|
15398
15447
|
};
|
|
15399
15448
|
}
|
|
15449
|
+
if (ownerArguments.ref?.kind === "message") {
|
|
15450
|
+
if (ownerArguments.ref.owner.kind !== "issue") {
|
|
15451
|
+
throw new Error("message references are issue-scoped");
|
|
15452
|
+
}
|
|
15453
|
+
const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
|
|
15454
|
+
return {
|
|
15455
|
+
text: messageSummary(messageRead),
|
|
15456
|
+
details: { issue: messageRead.message.issue_key }
|
|
15457
|
+
};
|
|
15458
|
+
}
|
|
15400
15459
|
if (documentOwner().kind === "project") {
|
|
15401
15460
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
15402
15461
|
return {
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -5,8 +5,9 @@ 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 or project document's living spec, asks, comments, and artifacts
|
|
9
|
-
|
|
8
|
+
Dispatch is your issue's or project document's living spec, asks, comments, and artifacts — a high-signal record for the humans who
|
|
9
|
+
decide, never a log of your work. The transcript is your scratch pad; progress and status stay there. Anything meant for a human
|
|
10
|
+
goes through a `dispatch_*` tool.
|
|
10
11
|
|
|
11
12
|
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
13
|
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
|
|
@@ -121,8 +122,8 @@ resolved. A human may reply to an open or answered ask; so may you, e.g. after f
|
|
|
121
122
|
|
|
122
123
|
The spec holds requirements, design, acceptance, decisions, and rejected alternatives, structured per [Writing a spec](#writing-a-spec).
|
|
123
124
|
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
|
|
125
|
-
[
|
|
125
|
+
progress, status, timestamps, an "Update HH:MMZ" section, a PR list, or handoff notes into the spec. Progress is not a
|
|
126
|
+
Dispatch object at all: it lives in your transcript and your pull request (see [Messages](#messages)).
|
|
126
127
|
|
|
127
128
|
Read the current document before changing it:
|
|
128
129
|
|
|
@@ -163,46 +164,27 @@ and deleting a cell's quoted text removes only that text.
|
|
|
163
164
|
Use `replace` for inline continuation. Use zero-based `occurrence` for a repeated target; re-read a missing or ambiguous target before
|
|
164
165
|
retrying. Pass `summary` to name the version when recording a decision.
|
|
165
166
|
|
|
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.
|
|
187
|
-
|
|
188
167
|
## Comments and suggestions
|
|
189
168
|
|
|
190
169
|
Add feedback with:
|
|
191
170
|
|
|
192
171
|
```ts
|
|
193
|
-
dispatch_comment({ issue?, project?, artifact?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
|
|
172
|
+
dispatch_comment({ issue?, project?, artifact?, ref?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
|
|
194
173
|
```
|
|
195
174
|
|
|
196
|
-
It returns issue or project-document owner details plus `comment` and, for writes, `topic`.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
175
|
+
It returns issue or project-document owner details plus `comment` and, for writes, `topic`.
|
|
176
|
+
`ref` names the owner (an issue or project-document reference) in place of `issue`/`project`.
|
|
177
|
+
`quote` requires `artifact`; omit both for a floating issue comment. A reply (`reply_to`/`reply_to_ask`)
|
|
178
|
+
takes no `quote`; it belongs to its parent's anchor. Use `reply_to` to continue a comment thread at
|
|
179
|
+
its root; a reply to a resolved thread reopens it. Use `reply_to_ask` to reply directly under a
|
|
180
|
+
question asked with `dispatch_ask`. Comments are edited only by their author from the dashboard. A
|
|
181
|
+
delivered `comment.created` event carries the comment `id`; reply to it with
|
|
182
|
+
`dispatch_comment({ reply_to: <id> })`.
|
|
201
183
|
|
|
202
184
|
Propose an exact replacement instead of describing it:
|
|
203
185
|
|
|
204
186
|
```ts
|
|
205
|
-
dispatch_suggest({ issue?, project?, artifact, quote, replace_with, body?, occurrence? })
|
|
187
|
+
dispatch_suggest({ issue?, project?, artifact, ref?, quote, replace_with, body?, occurrence? })
|
|
206
188
|
```
|
|
207
189
|
|
|
208
190
|
It returns issue or project-document owner details plus `comment` and its write `topic`. A human accepts or rejects a suggestion.
|
|
@@ -230,15 +212,17 @@ or by its filename; the slug also arrives on `artifact.created` events.
|
|
|
230
212
|
|
|
231
213
|
## Messages
|
|
232
214
|
|
|
233
|
-
|
|
234
|
-
|
|
215
|
+
Dispatch is a high-signal record for humans, not a log of what you are doing. A message is a reply to a human's message, or a
|
|
216
|
+
change a human must know about now: a deliverable landed, a blocker only they can clear. Nothing else — no progress updates, no
|
|
217
|
+
"starting X", no "still working", no restating the spec, no status on a timer. Your transcript is where work is narrated; the
|
|
218
|
+
pull request is where it is summarised. One message that a human reads beats ten that train them to skip you.
|
|
235
219
|
|
|
236
220
|
```ts
|
|
237
221
|
dispatch_message({ issue, body })
|
|
238
222
|
```
|
|
239
223
|
|
|
240
|
-
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters.
|
|
241
|
-
|
|
224
|
+
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters. A message is not a decision
|
|
225
|
+
(`dispatch_ask`) or document feedback (`dispatch_comment`), and it does not wake anyone unless the issue is routed.
|
|
242
226
|
|
|
243
227
|
## What comes back
|
|
244
228
|
|
|
@@ -252,12 +236,17 @@ dispatch_read({ issue?, project?, artifact?, ref? })
|
|
|
252
236
|
|
|
253
237
|
With an issue ref, it returns the issue summary, open asks, references, and recent events with `details` `{ issue }`. With a project
|
|
254
238
|
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.
|
|
256
|
-
not subscribe; use `dispatch_doc_read` for document contents.
|
|
239
|
+
question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain. With a
|
|
240
|
+
message ref, it returns that message and its reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
257
241
|
|
|
258
242
|
## References
|
|
259
243
|
|
|
260
|
-
Use these in document, ask, comment, and message bodies
|
|
244
|
+
Use these in document, ask, comment, and message bodies. In the dashboard, a reference renders
|
|
245
|
+
as an inline link whose text is the target's title (an issue's title, an ask's question, a
|
|
246
|
+
comment's first line, a document's name) once it resolves; a body that is only a bare reference
|
|
247
|
+
still gets an unfurl card instead. Every `ref` argument below (and `issue`/`project`) accepts
|
|
248
|
+
either form — an issue key or a project key is never ambiguous, since a project key never
|
|
249
|
+
contains a dash:
|
|
261
250
|
|
|
262
251
|
```text
|
|
263
252
|
dispatch://KEY
|
|
@@ -265,6 +254,7 @@ dispatch://KEY/spec
|
|
|
265
254
|
dispatch://KEY/artifact/<slug>[@vN]
|
|
266
255
|
dispatch://KEY/ask/<id>
|
|
267
256
|
dispatch://KEY/comment/<id>
|
|
257
|
+
dispatch://KEY/message/<id>
|
|
268
258
|
dispatch://PROJECT/artifact/<slug>[@vN]
|
|
269
259
|
dispatch://PROJECT/artifact/<slug>/ask/<id>
|
|
270
260
|
dispatch://PROJECT/artifact/<slug>/comment/<id>
|
|
@@ -299,27 +289,18 @@ dispatch_ask({
|
|
|
299
289
|
})
|
|
300
290
|
```
|
|
301
291
|
|
|
302
|
-
Before — progress
|
|
292
|
+
Before — a progress note that nobody needs, posted where humans look for decisions:
|
|
303
293
|
|
|
304
294
|
```ts
|
|
305
295
|
dispatch_message({ issue: "LEGION-815", body: "Merged the release PR, moving to docs next." })
|
|
306
296
|
```
|
|
307
297
|
|
|
308
|
-
After —
|
|
298
|
+
After — nothing. The merge is visible on the pull request; the docs work shows up as its own deliverable. Post a message only when
|
|
299
|
+
a human must act or a deliverable is theirs to use:
|
|
309
300
|
|
|
310
301
|
```ts
|
|
311
|
-
|
|
302
|
+
dispatch_message({
|
|
312
303
|
issue: "LEGION-815",
|
|
313
|
-
|
|
314
|
-
ops: [
|
|
315
|
-
{
|
|
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.",
|
|
322
|
-
},
|
|
323
|
-
],
|
|
304
|
+
body: "Release 1.4 is live on the devbox (dispatch://LEGION-815/artifact/release-notes). Nothing needed from you.",
|
|
324
305
|
})
|
|
325
306
|
```
|
|
@@ -71,9 +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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
Wave releases, child closures, and your own status are visible from the issue tree and the
|
|
75
|
+
handoffs; do not narrate them into the spec or a `dispatch_message`. A blocker only Sami can
|
|
76
|
+
clear is a `dispatch_ask`.
|
|
77
77
|
|
|
78
78
|
Write one root specification containing the accepted scope, adoption/decomposition,
|
|
79
79
|
waves, acceptance criteria, and integration test. When the config-armed root design gate
|
|
@@ -97,9 +97,7 @@ 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
|
|
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
|
|
100
|
+
already wrote. The durable copy lives in `$LEGION_WORKSPACE/.legion/<phase>.json`. If a
|
|
103
101
|
committed handoff conflicts with memory or a prior transcript, the committed file wins: it
|
|
104
102
|
is the copy that survived.
|
|
105
103
|
|