@sjawhar/opencode-legion-envoy 0.41.1 → 0.42.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 +73 -14
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +96 -139
- package/skills/legion-architect/SKILL.md +3 -0
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(),
|
|
@@ -13611,13 +13613,15 @@ var CommentEventPayloadSchema = object({
|
|
|
13611
13613
|
ask_question: string2().optional(),
|
|
13612
13614
|
anchor: object({ quote: string2().optional() }).nullish(),
|
|
13613
13615
|
suggestion: object({ replace_with: string2().optional() }).nullish(),
|
|
13614
|
-
author: object({ kind: string2(), id: string2()
|
|
13616
|
+
author: object({ kind: string2(), id: string2() }).optional(),
|
|
13615
13617
|
created_at: string2().optional()
|
|
13616
13618
|
});
|
|
13617
13619
|
var MessageEventPayloadSchema = object({
|
|
13618
13620
|
id: string2().optional(),
|
|
13619
13621
|
body: string2().optional(),
|
|
13620
|
-
|
|
13622
|
+
reply_to: string2().nullish(),
|
|
13623
|
+
reply_body: string2().optional(),
|
|
13624
|
+
author: object({ kind: string2(), id: string2() }).optional()
|
|
13621
13625
|
});
|
|
13622
13626
|
var ChildStatusEventPayloadSchema = object({
|
|
13623
13627
|
child_key: string2().optional(),
|
|
@@ -13719,7 +13723,7 @@ var dispatchToolSpecs = [
|
|
|
13719
13723
|
},
|
|
13720
13724
|
{
|
|
13721
13725
|
name: "dispatch_ask",
|
|
13722
|
-
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " +
|
|
13726
|
+
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}`,
|
|
13723
13727
|
arguments: (z) => ({
|
|
13724
13728
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13725
13729
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -13800,15 +13804,16 @@ var dispatchToolSpecs = [
|
|
|
13800
13804
|
},
|
|
13801
13805
|
{
|
|
13802
13806
|
name: "dispatch_message",
|
|
13803
|
-
description: "Post a
|
|
13807
|
+
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}`,
|
|
13804
13808
|
arguments: (z) => ({
|
|
13805
13809
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13806
|
-
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters.")
|
|
13810
|
+
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
|
|
13811
|
+
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()
|
|
13807
13812
|
})
|
|
13808
13813
|
},
|
|
13809
13814
|
{
|
|
13810
13815
|
name: "dispatch_doc_edit",
|
|
13811
|
-
description: "Apply deterministic text edits to an issue or project document. Do not use it for review feedback or for reading; use " +
|
|
13816
|
+
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}`,
|
|
13812
13817
|
arguments: (z) => ({
|
|
13813
13818
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13814
13819
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -14596,6 +14601,16 @@ class DispatchClient {
|
|
|
14596
14601
|
async message(issue, input) {
|
|
14597
14602
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
14598
14603
|
}
|
|
14604
|
+
async getMessage(issue, id) {
|
|
14605
|
+
return this.#json("GET", [
|
|
14606
|
+
"api",
|
|
14607
|
+
"v1",
|
|
14608
|
+
"issues",
|
|
14609
|
+
await this.#resolveIssue(issue),
|
|
14610
|
+
"messages",
|
|
14611
|
+
id
|
|
14612
|
+
]);
|
|
14613
|
+
}
|
|
14599
14614
|
async artifact(issue, input) {
|
|
14600
14615
|
const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"];
|
|
14601
14616
|
if ("content" in input)
|
|
@@ -14860,10 +14875,10 @@ function parseDispatchRef(ref) {
|
|
|
14860
14875
|
id: targetID
|
|
14861
14876
|
};
|
|
14862
14877
|
}
|
|
14863
|
-
const issueReference = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec)|\/(log)|\/(children)|\/artifact\/([^/@]+)(?:@v(\d+))?|\/ask\/([^/]+)|\/comment\/([^/]+))?$/);
|
|
14878
|
+
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\/([^/]+))?$/);
|
|
14864
14879
|
if (!issueReference)
|
|
14865
14880
|
return null;
|
|
14866
|
-
const [, issue, spec, log, children, artifact, version, ask, comment] = issueReference;
|
|
14881
|
+
const [, issue, spec, log, children, artifact, version, ask, comment, message] = issueReference;
|
|
14867
14882
|
if (!issue || version !== undefined && Number(version) < 1)
|
|
14868
14883
|
return null;
|
|
14869
14884
|
const owner = { kind: "issue", issue };
|
|
@@ -14885,6 +14900,8 @@ function parseDispatchRef(ref) {
|
|
|
14885
14900
|
return { owner, kind: "ask", id: ask };
|
|
14886
14901
|
if (comment)
|
|
14887
14902
|
return { owner, kind: "comment", id: comment };
|
|
14903
|
+
if (message)
|
|
14904
|
+
return { owner, kind: "message", id: message };
|
|
14888
14905
|
return { owner, kind: "issue", id: issue };
|
|
14889
14906
|
}
|
|
14890
14907
|
function askId(args) {
|
|
@@ -14897,6 +14914,16 @@ function askId(args) {
|
|
|
14897
14914
|
}
|
|
14898
14915
|
return reference.id;
|
|
14899
14916
|
}
|
|
14917
|
+
function messageReplyTo(args) {
|
|
14918
|
+
const replyTo = optionalString(args, "reply_to");
|
|
14919
|
+
if (replyTo === undefined || !replyTo.startsWith("dispatch://"))
|
|
14920
|
+
return replyTo;
|
|
14921
|
+
const reference = parseDispatchRef(replyTo);
|
|
14922
|
+
if (reference?.kind !== "message") {
|
|
14923
|
+
throw new Error("reply_to must be a bare message id or a dispatch://.../message/<id> reference");
|
|
14924
|
+
}
|
|
14925
|
+
return reference.id;
|
|
14926
|
+
}
|
|
14900
14927
|
function toolSchema(tool) {
|
|
14901
14928
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
14902
14929
|
if (!spec)
|
|
@@ -14908,7 +14935,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
14908
14935
|
return { args, ref: null, owner: null };
|
|
14909
14936
|
const refArgument = args.ref;
|
|
14910
14937
|
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
14911
|
-
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>");
|
|
14938
|
+
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>");
|
|
14912
14939
|
})() : null;
|
|
14913
14940
|
const issueArgument = args.issue;
|
|
14914
14941
|
const projectArgument = args.project;
|
|
@@ -14988,8 +15015,12 @@ async function resolveArtifact(client, owner, artifactReference) {
|
|
|
14988
15015
|
} catch (error) {
|
|
14989
15016
|
if (!(error instanceof DispatchServiceError) || error.status !== 404)
|
|
14990
15017
|
throw error;
|
|
14991
|
-
const artifacts = await client.listProjectArtifacts(owner.project);
|
|
14992
|
-
const
|
|
15018
|
+
const artifacts = await client.listProjectArtifacts(owner.project, true);
|
|
15019
|
+
const matches = artifacts.filter((candidate) => candidate.name === artifactReference);
|
|
15020
|
+
if (matches.length > 1) {
|
|
15021
|
+
throw new Error(`artifact name ${artifactReference} is ambiguous in project ${owner.project}; ` + `${matches.length} documents share it \u2014 use its slug instead`);
|
|
15022
|
+
}
|
|
15023
|
+
const artifact = matches[0];
|
|
14993
15024
|
if (!artifact)
|
|
14994
15025
|
throw error;
|
|
14995
15026
|
return { owner, artifact };
|
|
@@ -15095,6 +15126,18 @@ function commentSummary({ comment, replies }) {
|
|
|
15095
15126
|
return ["Comment:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
15096
15127
|
`);
|
|
15097
15128
|
}
|
|
15129
|
+
function messageSummary({ message, replies }) {
|
|
15130
|
+
const root = [
|
|
15131
|
+
`${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
|
|
15132
|
+
`Body: ${message.body}`
|
|
15133
|
+
];
|
|
15134
|
+
const chain = replies.flatMap((reply) => [
|
|
15135
|
+
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
15136
|
+
`Body: ${reply.body}`
|
|
15137
|
+
]);
|
|
15138
|
+
return ["Message:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
15139
|
+
`);
|
|
15140
|
+
}
|
|
15098
15141
|
async function openArtifactMarks(client, resolved) {
|
|
15099
15142
|
const asks = resolved.owner.kind === "project" ? await client.getArtifactAsks(resolved.artifact.id) : resolved.issue?.open_asks ?? [];
|
|
15100
15143
|
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
@@ -15302,9 +15345,15 @@ async function executeDispatchTool(input) {
|
|
|
15302
15345
|
};
|
|
15303
15346
|
}
|
|
15304
15347
|
case "dispatch_message": {
|
|
15305
|
-
const
|
|
15348
|
+
const replyTo = messageReplyTo(args);
|
|
15349
|
+
const message = await client.message(issue(), {
|
|
15350
|
+
body: stringArg(args, "body"),
|
|
15351
|
+
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
15352
|
+
actor
|
|
15353
|
+
});
|
|
15354
|
+
const messageRef = `dispatch://${message.issue_key}/message/${message.id}`;
|
|
15306
15355
|
return {
|
|
15307
|
-
text: `Posted message ${message.id}`,
|
|
15356
|
+
text: `Posted message ${message.id} (${messageRef})`,
|
|
15308
15357
|
details: {
|
|
15309
15358
|
issue: message.issue_key,
|
|
15310
15359
|
topic: dispatchIssueSubject(message.issue_key, ">"),
|
|
@@ -15391,6 +15440,16 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
15391
15440
|
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: comment.comment.issue_key }
|
|
15392
15441
|
};
|
|
15393
15442
|
}
|
|
15443
|
+
if (ownerArguments.ref?.kind === "message") {
|
|
15444
|
+
if (ownerArguments.ref.owner.kind !== "issue") {
|
|
15445
|
+
throw new Error("message references are issue-scoped");
|
|
15446
|
+
}
|
|
15447
|
+
const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
|
|
15448
|
+
return {
|
|
15449
|
+
text: messageSummary(messageRead),
|
|
15450
|
+
details: { issue: messageRead.message.issue_key }
|
|
15451
|
+
};
|
|
15452
|
+
}
|
|
15394
15453
|
if (documentOwner().kind === "project") {
|
|
15395
15454
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
15396
15455
|
return {
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -5,17 +5,18 @@ 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
|
-
transcript is your scratch pad. Anything meant for a human
|
|
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
|
-
The server enforces high signal: an ask question is at most 800 characters with at most eight
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
14
|
+
longer exist.
|
|
14
15
|
|
|
15
16
|
## Writing a spec
|
|
16
17
|
|
|
17
|
-
A spec is a decision record for the human who decides and the implementer who builds, not a
|
|
18
|
-
|
|
18
|
+
A spec is a decision record for the human who decides and the implementer who builds, not a transcript of your thinking. Use exactly
|
|
19
|
+
these document headings in this order.
|
|
19
20
|
|
|
20
21
|
| Section | Required content | Form |
|
|
21
22
|
| --- | --- | --- |
|
|
@@ -35,32 +36,23 @@ transcript of your thinking. Use exactly these document headings in this order.
|
|
|
35
36
|
- Do not hedge with “might” or “could consider.”
|
|
36
37
|
- Do not use TBD, TODO, or placeholders; an open item is a Decision needed.
|
|
37
38
|
- 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.
|
|
39
|
+
- Update the spec in place as decisions land: the spec is the record, comments are the discussion.
|
|
40
|
+
- Before sending it: no sections conflict, and every requirement has exactly one reading.
|
|
46
41
|
|
|
47
42
|
## Your owner
|
|
48
43
|
|
|
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`.
|
|
44
|
+
Every session works on an issue or project document. Legion pre-fills `issue` from `LEGION_ISSUE`: use a native issue key such as
|
|
45
|
+
`LEGION-3`, an external `owner/repo#n` reference, or a bare positive number (resolved against the cwd repository). Otherwise pass
|
|
46
|
+
exactly one owner to every owner-scoped tool: `issue` for an issue, or `project` and `artifact` for an unlinked project document (see
|
|
47
|
+
[References](#references) for the resulting ref shape). On first use, an external issue reference creates its native issue in the
|
|
48
|
+
project configured for that repository in Dispatch Settings, then falls back to `DISPATCH_DEFAULT_PROJECT`.
|
|
57
49
|
|
|
58
50
|
Architects create newly tracked child work with:
|
|
59
51
|
```ts
|
|
60
52
|
dispatch_issue({ project, title, parent?, external?, spec?, force? })
|
|
61
53
|
```
|
|
62
|
-
It returns `details` `{ issue, topic }`. Use `dispatch_issue` only to create an issue; never use
|
|
63
|
-
|
|
54
|
+
It returns `details` `{ issue, topic }`. Use `dispatch_issue` only to create an issue; never use it to park a question. When `spec` is
|
|
55
|
+
supplied, follow [Writing a spec](#writing-a-spec).
|
|
64
56
|
|
|
65
57
|
## Search first
|
|
66
58
|
|
|
@@ -68,13 +60,12 @@ Before you create an issue or start a design document, search:
|
|
|
68
60
|
```ts
|
|
69
61
|
dispatch_search({ query, project?, limit? })
|
|
70
62
|
```
|
|
71
|
-
It returns every issue, document, comment, ask, and message that contains the words, with the
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
It returns every issue, document, comment, ask, and message that contains the words, with the issue key and a link. Cite the hit you
|
|
64
|
+
build on (`dispatch://KEY` or the document reference), or state "no prior issue" in the spec. Websearch syntax applies: `"merge queue"`,
|
|
65
|
+
`-daemon`, `OR`.
|
|
74
66
|
|
|
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.
|
|
67
|
+
`dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns the candidates (`POSSIBLE_DUPLICATE`).
|
|
68
|
+
Read them; reference the existing issue, or repeat the call with `force: true` when it is genuinely new work.
|
|
78
69
|
|
|
79
70
|
## Asking
|
|
80
71
|
|
|
@@ -91,14 +82,14 @@ dispatch_ask({
|
|
|
91
82
|
anchor?: { artifact, quote, occurrence? },
|
|
92
83
|
})
|
|
93
84
|
```
|
|
94
|
-
It returns `details` `{ issue, topic, ask }` for an issue or `{ project, artifact, document,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
85
|
+
It returns `details` `{ issue, topic, ask }` for an issue or `{ project, artifact, document, topic, ask }` for a project document.
|
|
86
|
+
Options are buttons: never enumerate choices in prose. Put the recommendation in `question`, and put each selectable choice in
|
|
87
|
+
`options`. Anchor a document question with `anchor: { artifact, quote, occurrence? }`; `occurrence` is zero-based and selects a repeated
|
|
88
|
+
quote, and an anchor whose quote later disappears becomes orphaned but stays readable against its original document version.
|
|
89
|
+
|
|
90
|
+
An ask must be answerable from its own text and its anchor alone. Anchor a question about a document passage with `anchor`; thread one
|
|
91
|
+
about a comment with `reply_to`; thread a follow-up on your own ask with `reply_to_ask`; cite anything else with a `dispatch://`
|
|
92
|
+
reference (see [References](#references)). Never write "see above", "the message above", or "as attached".
|
|
102
93
|
|
|
103
94
|
Correct or refine an open ask in place instead of opening a second question:
|
|
104
95
|
```ts
|
|
@@ -110,12 +101,11 @@ dispatch_edit_ask({
|
|
|
110
101
|
urgency?,
|
|
111
102
|
})
|
|
112
103
|
```
|
|
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.
|
|
104
|
+
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
|
|
105
|
+
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
106
|
|
|
117
|
-
An ask stays open until a human answers, unless its question no longer needs that answer. Retract a
|
|
118
|
-
|
|
107
|
+
An ask stays open until a human answers, unless its question no longer needs that answer. Retract a moot or superseded question, or
|
|
108
|
+
self-resolve one after finding the answer:
|
|
119
109
|
```ts
|
|
120
110
|
dispatch_resolve_ask({
|
|
121
111
|
ask,
|
|
@@ -123,32 +113,31 @@ dispatch_resolve_ask({
|
|
|
123
113
|
reason: "A newer ask supersedes this question.",
|
|
124
114
|
})
|
|
125
115
|
```
|
|
126
|
-
Use `retracted` when the question is obsolete and `resolved` when you found the answer. Include the
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
Use `retracted` when the question is obsolete and `resolved` when you found the answer. Include the reason because the question remains
|
|
117
|
+
in its Conversation card and reply thread. Resolution is not an answer: it never records a human decision, and an answered ask cannot be
|
|
118
|
+
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
|
|
119
|
+
`dispatch_comment` (mutually exclusive with `reply_to`).
|
|
129
120
|
|
|
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`.
|
|
121
|
+
## The Spec
|
|
134
122
|
|
|
135
|
-
|
|
136
|
-
|
|
123
|
+
The spec holds requirements, design, acceptance, decisions, and rejected alternatives, structured per [Writing a spec](#writing-a-spec).
|
|
124
|
+
It changes only when a decision or requirement changes, and every version that records one is named with `summary`. Never write
|
|
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)).
|
|
137
127
|
|
|
138
128
|
Read the current document before changing it:
|
|
139
129
|
|
|
140
130
|
```ts
|
|
141
131
|
dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
|
|
142
132
|
```
|
|
143
|
-
It returns live or versioned markdown with open marks. `issue` with an omitted `artifact`
|
|
144
|
-
|
|
145
|
-
`dispatch://PROJECT/artifact/<slug>` ref supplies both. Then write narrative with:
|
|
133
|
+
It returns live or versioned markdown with open marks. `issue` with an omitted `artifact` reads the issue specification; a project needs
|
|
134
|
+
`artifact`; and a `dispatch://PROJECT/artifact/<slug>` ref supplies both. Then write with:
|
|
146
135
|
|
|
147
136
|
```ts
|
|
148
137
|
dispatch_doc_edit({ issue?, project?, artifact, ops, summary? })
|
|
149
138
|
```
|
|
150
|
-
It returns issue or project-document owner details plus `applied`, optional `version`, and its
|
|
151
|
-
|
|
139
|
+
It returns issue or project-document owner details plus `applied`, optional `version`, and its write `topic`. `ops` is an array of this
|
|
140
|
+
exact `EditOp` shape:
|
|
152
141
|
|
|
153
142
|
```ts
|
|
154
143
|
type EditOp = {
|
|
@@ -162,23 +151,18 @@ type EditOp = {
|
|
|
162
151
|
};
|
|
163
152
|
```
|
|
164
153
|
|
|
165
|
-
Target `replace` and `delete` by the document's plain text: inline-code and link text match
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
span separate blocks into separate operations.
|
|
154
|
+
Target `replace` and `delete` by the document's plain text: inline-code and link text match without Markdown syntax, and a table-cell
|
|
155
|
+
anchor is its cell text. Quote code-block contents without their Markdown fences. A quote must stay within one textblock; split changes
|
|
156
|
+
that span separate blocks into separate operations.
|
|
169
157
|
|
|
170
|
-
`replace` requires `find` and `with`; `delete` requires `find`; `insert` requires `markdown`
|
|
171
|
-
|
|
172
|
-
`"
|
|
173
|
-
|
|
158
|
+
`replace` requires `find` and `with`; `delete` requires `find`; `insert` requires `markdown` and exactly one of `after` or `before`. An
|
|
159
|
+
insert anchor is a quote, `"start"`, `"end"`, or `"heading:Title"`. Ordinary inserts create a sibling block before or after the quote or
|
|
160
|
+
heading's enclosing document block; `"start"` and `"end"` select the document edges. At a table-cell quote, a body-row fragment (no
|
|
161
|
+
header or delimiter rows) extends that table before or after the matched row instead; short rows are padded, wider rows are rejected,
|
|
162
|
+
and deleting a cell's quoted text removes only that text.
|
|
174
163
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
rows wider than the table are rejected.
|
|
178
|
-
Deleting a cell's quoted text removes that text, not the surrounding row or table.
|
|
179
|
-
Use `replace` for inline continuation. Use zero-based `occurrence` for a repeated target;
|
|
180
|
-
re-read a missing or ambiguous target before retrying. Pass `summary` to name the version
|
|
181
|
-
when recording a decision. Never paste progress into a message.
|
|
164
|
+
Use `replace` for inline continuation. Use zero-based `occurrence` for a repeated target; re-read a missing or ambiguous target before
|
|
165
|
+
retrying. Pass `summary` to name the version when recording a decision.
|
|
182
166
|
|
|
183
167
|
## Comments and suggestions
|
|
184
168
|
|
|
@@ -188,14 +172,11 @@ Add feedback with:
|
|
|
188
172
|
dispatch_comment({ issue?, project?, artifact?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
|
|
189
173
|
```
|
|
190
174
|
|
|
191
|
-
It returns issue or project-document owner details plus `comment` and, for writes, `topic`.
|
|
192
|
-
`
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
`
|
|
196
|
-
mutually exclusive. Comments are edited only by their author from the dashboard. A delivered
|
|
197
|
-
`comment.created` event carries the comment `id`; reply to it with
|
|
198
|
-
`dispatch_comment({ reply_to: <id> })`.
|
|
175
|
+
It returns issue or project-document owner details plus `comment` and, for writes, `topic`. `quote` requires `artifact`; omit both for a
|
|
176
|
+
floating issue comment. A reply (`reply_to`/`reply_to_ask`) takes no `quote`; it belongs to its parent's anchor. Use `reply_to` to
|
|
177
|
+
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
|
|
178
|
+
asked with `dispatch_ask`. Comments are edited only by their author from the dashboard. A delivered `comment.created` event carries the
|
|
179
|
+
comment `id`; reply to it with `dispatch_comment({ reply_to: <id> })`.
|
|
199
180
|
|
|
200
181
|
Propose an exact replacement instead of describing it:
|
|
201
182
|
|
|
@@ -203,15 +184,9 @@ Propose an exact replacement instead of describing it:
|
|
|
203
184
|
dispatch_suggest({ issue?, project?, artifact, quote, replace_with, body?, occurrence? })
|
|
204
185
|
```
|
|
205
186
|
|
|
206
|
-
It returns issue or project-document owner details plus `comment` and its write `topic`. A
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
before retrying. `INVALID_ANCHOR` requires exactly one nonempty anchor `quote` or `mark_id`;
|
|
210
|
-
`ANCHOR_MISSING` means a browser mark was not observed in the live tree, and
|
|
211
|
-
`ANCHOR_ORPHANED` means its marked text no longer exists. `INVALID_MARKDOWN` and `DOC_SCHEMA`
|
|
212
|
-
reject Markdown or a live tree outside the Proof schema. `INVALID_OP` names a malformed edit;
|
|
213
|
-
`CAP_EXCEEDED` never truncates; `ISSUE_CLOSED` rejects a write. `ACTOR_KIND` and `ROUTE_INVALID`
|
|
214
|
-
reject an invalid actor or route.
|
|
187
|
+
It returns issue or project-document owner details plus `comment` and its write `topic`. A human accepts or rejects a suggestion.
|
|
188
|
+
Errors: `TARGET_AMBIGUOUS` (add `occurrence`), `TARGET_NOT_FOUND` (re-read first), `INVALID_ANCHOR`/`ANCHOR_MISSING`/`ANCHOR_ORPHANED`
|
|
189
|
+
(bad, unwritten, or stale quote), `INVALID_MARKDOWN`/`DOC_SCHEMA` (malformed content), `CAP_EXCEEDED`, `ISSUE_CLOSED`.
|
|
215
190
|
|
|
216
191
|
## Artifacts
|
|
217
192
|
|
|
@@ -227,40 +202,39 @@ Or, when the text is already in the call, post a Markdown document directly:
|
|
|
227
202
|
dispatch_artifact({ issue?, project?, name: "spec.md", content: "# Design\n..." })
|
|
228
203
|
```
|
|
229
204
|
|
|
230
|
-
Exactly one of `issue` and `project` is required. A project upload creates an unlinked project
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
same `name` creates its next version. Use `content` when the text is already in the call.
|
|
235
|
-
Address an existing artifact by the slug shown in the upload result or by its filename.
|
|
205
|
+
Exactly one of `issue` and `project` is required. A project upload creates an unlinked project document; it must not include `artifact`.
|
|
206
|
+
Exactly one of `path` and `content` is required. It returns issue or project-document owner details plus `artifact`, `version`, and its
|
|
207
|
+
write `topic`. Uploading the same `name` creates its next version. Address an existing artifact by the slug shown in the upload result
|
|
208
|
+
or by its filename; the slug also arrives on `artifact.created` events.
|
|
236
209
|
|
|
237
210
|
## Messages
|
|
238
211
|
|
|
239
|
-
|
|
212
|
+
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
|
|
213
|
+
change a human must know about now: a deliverable landed, a blocker only they can clear. Nothing else — no progress updates, no
|
|
214
|
+
"starting X", no "still working", no restating the spec, no status on a timer. Your transcript is where work is narrated; the
|
|
215
|
+
pull request is where it is summarised. One message that a human reads beats ten that train them to skip you.
|
|
240
216
|
|
|
241
217
|
```ts
|
|
242
218
|
dispatch_message({ issue, body })
|
|
243
219
|
```
|
|
244
220
|
|
|
245
|
-
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters.
|
|
246
|
-
|
|
221
|
+
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters. A message is not a decision
|
|
222
|
+
(`dispatch_ask`) or document feedback (`dispatch_comment`), and it does not wake anyone unless the issue is routed.
|
|
247
223
|
|
|
248
224
|
## What comes back
|
|
249
225
|
|
|
250
|
-
A write result's `details.topic` subscribes the host to its owner. Issue writes use
|
|
251
|
-
`notifications.dispatch.
|
|
252
|
-
|
|
253
|
-
event; `notify` only controls agent wake and routed delivery. After a restart, catch up with:
|
|
226
|
+
A write result's `details.topic` subscribes the host to its owner. Issue writes use `notifications.dispatch.issue.<KEY>.>`;
|
|
227
|
+
project-document writes use `notifications.dispatch.document.<PROJECT>.<SLUG>.>`. The owner topic carries every Dispatch event; `notify`
|
|
228
|
+
only controls agent wake and routed delivery. After a restart, catch up with:
|
|
254
229
|
|
|
255
230
|
```ts
|
|
256
231
|
dispatch_read({ issue?, project?, artifact?, ref? })
|
|
257
232
|
```
|
|
258
233
|
|
|
259
|
-
With an issue ref, it returns the issue summary, open asks, references, and recent events with
|
|
260
|
-
`details` `{
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
234
|
+
With an issue ref, it returns the issue summary, open asks, references, and recent events with `details` `{ issue }`. With a project
|
|
235
|
+
document owner or ref, it returns a document summary with `details` `{ project, document }`. With an ask ref, it returns that ask's
|
|
236
|
+
question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain. With a
|
|
237
|
+
message ref, it returns that message and its reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
264
238
|
|
|
265
239
|
## References
|
|
266
240
|
|
|
@@ -272,11 +246,14 @@ dispatch://KEY/spec
|
|
|
272
246
|
dispatch://KEY/artifact/<slug>[@vN]
|
|
273
247
|
dispatch://KEY/ask/<id>
|
|
274
248
|
dispatch://KEY/comment/<id>
|
|
249
|
+
dispatch://KEY/message/<id>
|
|
275
250
|
dispatch://PROJECT/artifact/<slug>[@vN]
|
|
276
251
|
dispatch://PROJECT/artifact/<slug>/ask/<id>
|
|
277
252
|
dispatch://PROJECT/artifact/<slug>/comment/<id>
|
|
278
253
|
```
|
|
279
254
|
|
|
255
|
+
A bare UUID or `KEY#seq` is not a reference; the `dispatch://` form is what Dispatch links and records.
|
|
256
|
+
|
|
280
257
|
## Before / after
|
|
281
258
|
|
|
282
259
|
Before — a wall of text hides the decision and makes the choices unclickable:
|
|
@@ -294,48 +271,28 @@ After — anchor the decision and make each option a button:
|
|
|
294
271
|
dispatch_ask({
|
|
295
272
|
issue: "LEGION-815",
|
|
296
273
|
question:
|
|
297
|
-
"Choose the release gate. Recommendation: ship after release-note review
|
|
274
|
+
"Choose the release gate. Recommendation: ship after release-note review, since the tested deployment is otherwise ready.",
|
|
298
275
|
options: [
|
|
299
|
-
{
|
|
300
|
-
|
|
301
|
-
description: "Keeps the tested release intact and publishes reviewed instructions.",
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
label: "Ship now",
|
|
305
|
-
description: "Meets the demo deadline; release notes follow separately.",
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
label: "Remove dashboard changes",
|
|
309
|
-
description: "Narrows the release but requires another deployment test.",
|
|
310
|
-
},
|
|
276
|
+
{ label: "Review notes, then ship", description: "Keeps the release intact and reviewed." },
|
|
277
|
+
{ label: "Ship now", description: "Meets the demo deadline; release notes follow later." },
|
|
311
278
|
],
|
|
312
279
|
urgency: "high",
|
|
313
|
-
anchor: {
|
|
314
|
-
artifact: "spec",
|
|
315
|
-
quote: "Release requires reviewed operator instructions before deployment.",
|
|
316
|
-
},
|
|
280
|
+
anchor: { artifact: "spec", quote: "Release requires reviewed operator instructions before deployment." },
|
|
317
281
|
})
|
|
318
282
|
```
|
|
319
283
|
|
|
320
|
-
Before — a
|
|
284
|
+
Before — a progress note that nobody needs, posted where humans look for decisions:
|
|
321
285
|
|
|
322
|
-
```
|
|
323
|
-
|
|
286
|
+
```ts
|
|
287
|
+
dispatch_message({ issue: "LEGION-815", body: "Merged the release PR, moving to docs next." })
|
|
324
288
|
```
|
|
325
289
|
|
|
326
|
-
After —
|
|
290
|
+
After — nothing. The merge is visible on the pull request; the docs work shows up as its own deliverable. Post a message only when
|
|
291
|
+
a human must act or a deliverable is theirs to use:
|
|
327
292
|
|
|
328
293
|
```ts
|
|
329
|
-
|
|
294
|
+
dispatch_message({
|
|
330
295
|
issue: "LEGION-815",
|
|
331
|
-
|
|
332
|
-
ops: [
|
|
333
|
-
{
|
|
334
|
-
op: "replace",
|
|
335
|
-
find: "Release pending.",
|
|
336
|
-
with: "Release merged and ready for deployment.",
|
|
337
|
-
},
|
|
338
|
-
],
|
|
339
|
-
summary: "Recorded merged release",
|
|
296
|
+
body: "Release 1.4 is live on the devbox (dispatch://LEGION-815/artifact/release-notes). Nothing needed from you.",
|
|
340
297
|
})
|
|
341
298
|
```
|
|
@@ -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
|
+
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`.
|
|
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
|