@sjawhar/opencode-legion-envoy 3.8.2 → 3.10.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 +57 -34
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +45 -4
package/dist/src/server.js
CHANGED
|
@@ -14255,13 +14255,14 @@ var dispatchToolSpecs = [
|
|
|
14255
14255
|
},
|
|
14256
14256
|
{
|
|
14257
14257
|
name: "dispatch_issues",
|
|
14258
|
-
example: { project: "AGENTC" },
|
|
14259
|
-
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, and open-ask count, so you can see backlog shape without " + "opening every issue. Optionally filter by status, parent, label, or how recently it changed
|
|
14258
|
+
example: { project: "AGENTC", priority: [0, 1] },
|
|
14259
|
+
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, and open-ask count, so you can see backlog shape without " + "opening every issue. Optionally filter by status, parent, label, priority, or how recently it " + "changed; priority takes one or more of 0-3 (P0-P3) and null for an issue with no priority, so " + "an owner's P0/P1 audit is priority [0, 1]. Do not use it to search by keyword or phrase; " + "dispatch_search remains the keyword surface. Rows are capped at limit (default 50, max 250), " + "applied to the response here, not by the server.",
|
|
14260
14260
|
arguments: (z2) => ({
|
|
14261
14261
|
project: z2.string().describe("Project key to list issues from."),
|
|
14262
14262
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
14263
14263
|
parent: z2.string().describe("Optional parent issue key filter.").optional(),
|
|
14264
14264
|
label: z2.string().describe("Optional label filter.").optional(),
|
|
14265
|
+
priority: z2.array(z2.number({ int: true, min: 0, max: 3 }).nullable(), { min: 1, max: 5 }).describe("Optional priority filter: one or more of 0 (P0, highest) through 3 (P3, lowest), and null " + "for an issue with no priority; an issue matching any listed value is returned.").optional(),
|
|
14265
14266
|
updated_since: z2.string().describe("Optional RFC3339 timestamp; only issues updated at or after it.").optional(),
|
|
14266
14267
|
limit: z2.number({ int: true, min: 1, max: 250 }).describe("Maximum rows, 1-250; default 50.").optional()
|
|
14267
14268
|
})
|
|
@@ -15323,6 +15324,9 @@ class DispatchClient {
|
|
|
15323
15324
|
for (const [name, value] of Object.entries(query)) {
|
|
15324
15325
|
if (typeof value === "string" || typeof value === "number") {
|
|
15325
15326
|
url2.searchParams.set(name, String(value));
|
|
15327
|
+
} else if (Array.isArray(value)) {
|
|
15328
|
+
for (const item of value)
|
|
15329
|
+
url2.searchParams.append(name, String(item));
|
|
15326
15330
|
}
|
|
15327
15331
|
}
|
|
15328
15332
|
}
|
|
@@ -15623,6 +15627,10 @@ function optionalPriority(args, name) {
|
|
|
15623
15627
|
return null;
|
|
15624
15628
|
return typeof value === "number" ? value : undefined;
|
|
15625
15629
|
}
|
|
15630
|
+
function optionalPriorityFilter(args, name) {
|
|
15631
|
+
const value = args[name];
|
|
15632
|
+
return Array.isArray(value) ? value.map((item) => item ?? "none") : undefined;
|
|
15633
|
+
}
|
|
15626
15634
|
function optionalComponents(args, name) {
|
|
15627
15635
|
const value = args[name];
|
|
15628
15636
|
if (typeof value !== "object" || value === null)
|
|
@@ -16033,13 +16041,23 @@ async function resolveOwnerArguments(tool, input, cwd, env, exec, serverUrl, pro
|
|
|
16033
16041
|
const issue2 = `${repo2}#${legionIssue}`;
|
|
16034
16042
|
return { args: { ...args, issue: issue2 }, ref, owner: { kind: "issue", issue: issue2 } };
|
|
16035
16043
|
}
|
|
16036
|
-
function artifactByReference(artifacts, artifactReference, owner) {
|
|
16037
|
-
const
|
|
16038
|
-
if (
|
|
16039
|
-
return
|
|
16044
|
+
function artifactByReference(artifacts, artifactReference, owner, canonical) {
|
|
16045
|
+
const bySlug = artifacts.find((candidate) => candidate.slug === artifactReference);
|
|
16046
|
+
if (canonical && bySlug !== undefined)
|
|
16047
|
+
return bySlug;
|
|
16048
|
+
const byId = artifacts.find((candidate) => candidate.id === artifactReference);
|
|
16049
|
+
if (byId !== undefined)
|
|
16050
|
+
return byId;
|
|
16040
16051
|
const byName = artifacts.filter((candidate) => candidate.name === artifactReference);
|
|
16052
|
+
if (bySlug !== undefined) {
|
|
16053
|
+
const named = byName.filter((candidate) => candidate.id !== bySlug.id && candidate.kind === "doc");
|
|
16054
|
+
if (named.length > 0) {
|
|
16055
|
+
throw new Error(documentReferenceProblem(artifactReference, [bySlug, ...named], owner, "id"));
|
|
16056
|
+
}
|
|
16057
|
+
return bySlug;
|
|
16058
|
+
}
|
|
16041
16059
|
if (byName.length > 1) {
|
|
16042
|
-
throw new Error(documentReferenceProblem(artifactReference, byName, owner,
|
|
16060
|
+
throw new Error(documentReferenceProblem(artifactReference, byName, owner, "slug"));
|
|
16043
16061
|
}
|
|
16044
16062
|
const [artifact] = byName;
|
|
16045
16063
|
if (artifact === undefined) {
|
|
@@ -16047,32 +16065,31 @@ function artifactByReference(artifacts, artifactReference, owner) {
|
|
|
16047
16065
|
}
|
|
16048
16066
|
return artifact;
|
|
16049
16067
|
}
|
|
16050
|
-
async function resolveArtifact(client, owner, artifactReference) {
|
|
16068
|
+
async function resolveArtifact(client, owner, artifactReference, { canonical = false } = {}) {
|
|
16051
16069
|
if (owner.kind === "project") {
|
|
16052
16070
|
if (artifactReference === undefined) {
|
|
16053
16071
|
throw new Error("artifact is required for a project document");
|
|
16054
16072
|
}
|
|
16055
|
-
|
|
16056
|
-
return {
|
|
16057
|
-
owner,
|
|
16058
|
-
artifact: await client.getProjectArtifact(owner.project, artifactReference)
|
|
16059
|
-
};
|
|
16060
|
-
} catch (error48) {
|
|
16073
|
+
const routed = await client.getProjectArtifact(owner.project, artifactReference).catch((error48) => {
|
|
16061
16074
|
if (!(error48 instanceof DispatchServiceError) || error48.status !== 404)
|
|
16062
16075
|
throw error48;
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16066
|
-
|
|
16067
|
-
|
|
16068
|
-
|
|
16076
|
+
return;
|
|
16077
|
+
});
|
|
16078
|
+
if (canonical && routed !== undefined)
|
|
16079
|
+
return { owner, artifact: routed };
|
|
16080
|
+
const unlinked = await client.listProjectArtifacts(owner.project, true);
|
|
16081
|
+
const artifacts = routed === undefined ? unlinked : [routed, ...unlinked.filter((candidate) => candidate.id !== routed.id)];
|
|
16082
|
+
return {
|
|
16083
|
+
owner,
|
|
16084
|
+
artifact: artifactByReference(artifacts, artifactReference, "project", canonical)
|
|
16085
|
+
};
|
|
16069
16086
|
}
|
|
16070
16087
|
const issue2 = await client.getIssue(owner.issue);
|
|
16071
16088
|
let artifact;
|
|
16072
16089
|
if (artifactReference === undefined || artifactReference === "spec") {
|
|
16073
16090
|
artifact = issue2.artifacts.find((candidate) => candidate.primary || candidate.id === issue2.primary_artifact_id);
|
|
16074
16091
|
} else {
|
|
16075
|
-
artifact = artifactByReference(issue2.artifacts, artifactReference, "issue");
|
|
16092
|
+
artifact = artifactByReference(issue2.artifacts, artifactReference, "issue", canonical);
|
|
16076
16093
|
}
|
|
16077
16094
|
if (!artifact) {
|
|
16078
16095
|
throw new Error(documentReferenceProblem(artifactReference ?? "spec", issue2.artifacts, "issue"));
|
|
@@ -16080,10 +16097,10 @@ async function resolveArtifact(client, owner, artifactReference) {
|
|
|
16080
16097
|
return { owner, issue: issue2, artifact };
|
|
16081
16098
|
}
|
|
16082
16099
|
var documentHintLimit = 8;
|
|
16083
|
-
function documentReferenceProblem(reference, documents, owner,
|
|
16084
|
-
const hints = documents.slice(0, documentHintLimit).map((document) => `${document.slug} (${document.name})`);
|
|
16100
|
+
function documentReferenceProblem(reference, documents, owner, use) {
|
|
16101
|
+
const hints = documents.slice(0, documentHintLimit).map((document) => use === "id" ? `${document.id} (${document.slug}, ${document.name})` : `${document.slug} (${document.name})`);
|
|
16085
16102
|
const list = hints.length === 0 ? "none" : hints.join(", ");
|
|
16086
|
-
return
|
|
16103
|
+
return use === undefined ? `document "${reference}" not found by slug; this ${owner}'s documents: ${list}` : `"${reference}" names ${documents.length} documents on this ${owner}; use ${use === "id" ? "the id" : "a slug"}: ${list}`;
|
|
16087
16104
|
}
|
|
16088
16105
|
var askHintLimit = 8;
|
|
16089
16106
|
function askIDInputProblem(asks, scope) {
|
|
@@ -16473,6 +16490,10 @@ async function executeDispatchTool(input) {
|
|
|
16473
16490
|
const args = parsed.success ? parsed.data : ownerArguments.args;
|
|
16474
16491
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
16475
16492
|
const client = dispatchClient();
|
|
16493
|
+
const refDocument = ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : ownerArguments.ref?.artifact;
|
|
16494
|
+
const resolveDocument = (documentOwner2, reference) => resolveArtifact(client, documentOwner2, reference, {
|
|
16495
|
+
canonical: reference !== undefined && reference === refDocument
|
|
16496
|
+
});
|
|
16476
16497
|
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
16477
16498
|
kind: "issue",
|
|
16478
16499
|
issue: await resolveExistingIssue(client, ownerArguments.owner.issue)
|
|
@@ -16688,6 +16709,7 @@ async function executeDispatchTool(input) {
|
|
|
16688
16709
|
const status = optionalString(args, "status");
|
|
16689
16710
|
const parent = optionalString(args, "parent");
|
|
16690
16711
|
const label = optionalString(args, "label");
|
|
16712
|
+
const priority = optionalPriorityFilter(args, "priority");
|
|
16691
16713
|
const updatedSince = optionalString(args, "updated_since");
|
|
16692
16714
|
const limit = Math.min(Math.max(optionalNumber(args, "limit") ?? 50, 1), 250);
|
|
16693
16715
|
const issues = await client.listIssues({
|
|
@@ -16695,6 +16717,7 @@ async function executeDispatchTool(input) {
|
|
|
16695
16717
|
...status === undefined ? {} : { status },
|
|
16696
16718
|
...parent === undefined ? {} : { parent },
|
|
16697
16719
|
...label === undefined ? {} : { label },
|
|
16720
|
+
...priority === undefined ? {} : { priority },
|
|
16698
16721
|
...updatedSince === undefined ? {} : { updated_since: updatedSince }
|
|
16699
16722
|
});
|
|
16700
16723
|
const rows = issues.slice(0, limit).map((row) => ({
|
|
@@ -16761,7 +16784,7 @@ async function executeDispatchTool(input) {
|
|
|
16761
16784
|
const issueKey = ref.owner.issue;
|
|
16762
16785
|
id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getComments(issueKey));
|
|
16763
16786
|
} else if (ref !== null) {
|
|
16764
|
-
const artifact = (await resolveArtifact(client, ref.owner, ref.artifact)).artifact;
|
|
16787
|
+
const artifact = (await resolveArtifact(client, ref.owner, ref.artifact, { canonical: true })).artifact;
|
|
16765
16788
|
document = artifact;
|
|
16766
16789
|
id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getArtifactComments(artifact.id));
|
|
16767
16790
|
}
|
|
@@ -16773,7 +16796,7 @@ async function executeDispatchTool(input) {
|
|
|
16773
16796
|
const anchorArgs = asObject(args.anchor);
|
|
16774
16797
|
const owner2 = documentOwner();
|
|
16775
16798
|
const artifactReference = optionalString(args, "artifact") ?? (anchorArgs === null ? undefined : optionalString(anchorArgs, "artifact"));
|
|
16776
|
-
const resolved = owner2.kind === "project" || artifactReference === undefined ? owner2.kind === "project" ? await
|
|
16799
|
+
const resolved = owner2.kind === "project" || artifactReference === undefined ? owner2.kind === "project" ? await resolveDocument(owner2, artifactReference) : undefined : await resolveDocument(owner2, artifactReference);
|
|
16777
16800
|
const options = args.options;
|
|
16778
16801
|
const multiple = optionalBoolean(args, "multiple");
|
|
16779
16802
|
const urgency = askUrgency(args);
|
|
@@ -16823,7 +16846,7 @@ ${followsAsk(askOwner)}`,
|
|
|
16823
16846
|
case "dispatch_comment": {
|
|
16824
16847
|
const artifactReference = optionalString(args, "artifact");
|
|
16825
16848
|
const owner2 = documentOwner();
|
|
16826
|
-
const resolved = owner2.kind === "project" || artifactReference === undefined ? owner2.kind === "project" ? await
|
|
16849
|
+
const resolved = owner2.kind === "project" || artifactReference === undefined ? owner2.kind === "project" ? await resolveDocument(owner2, artifactReference) : undefined : await resolveDocument(owner2, artifactReference);
|
|
16827
16850
|
const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
|
|
16828
16851
|
const replyTo = optionalString(args, "reply_to");
|
|
16829
16852
|
const replyToAskReference = optionalString(args, "reply_to_ask");
|
|
@@ -16876,7 +16899,7 @@ ${followsAsk(askOwner)}`,
|
|
|
16876
16899
|
};
|
|
16877
16900
|
}
|
|
16878
16901
|
case "dispatch_suggest": {
|
|
16879
|
-
const resolved = await
|
|
16902
|
+
const resolved = await resolveDocument(documentOwner(), stringArg(args, "artifact"));
|
|
16880
16903
|
const anchored = anchor(resolved.artifact, args);
|
|
16881
16904
|
if (anchored === undefined)
|
|
16882
16905
|
throw new Error("quote is required");
|
|
@@ -16931,7 +16954,7 @@ ${followsAsk(askOwner)}`,
|
|
|
16931
16954
|
};
|
|
16932
16955
|
}
|
|
16933
16956
|
case "dispatch_doc_edit": {
|
|
16934
|
-
const resolved = await
|
|
16957
|
+
const resolved = await resolveDocument(documentOwner(), stringArg(args, "artifact"));
|
|
16935
16958
|
const ops = args.ops;
|
|
16936
16959
|
const summary = optionalString(args, "summary");
|
|
16937
16960
|
const { precondition: rawPrecondition } = args;
|
|
@@ -16973,7 +16996,7 @@ ${followsAsk(askOwner)}`,
|
|
|
16973
16996
|
}
|
|
16974
16997
|
case "dispatch_doc_read": {
|
|
16975
16998
|
const artifactReference = optionalString(args, "artifact") ?? (ownerArguments.ref?.kind === "spec" || ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : undefined);
|
|
16976
|
-
const resolved = await
|
|
16999
|
+
const resolved = await resolveDocument(documentOwner(), artifactReference);
|
|
16977
17000
|
const version2 = optionalNumber(args, "version") ?? ownerArguments.ref?.version;
|
|
16978
17001
|
const documentPromise = client.docRead(resolved.artifact.id, version2);
|
|
16979
17002
|
const marksPromise = openArtifactMarks(client, resolved);
|
|
@@ -17002,7 +17025,7 @@ ${trailer.join(`
|
|
|
17002
17025
|
}
|
|
17003
17026
|
case "dispatch_request_approval": {
|
|
17004
17027
|
const artifactReference = optionalString(args, "artifact") ?? (ownerArguments.ref?.kind === "spec" || ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : undefined);
|
|
17005
|
-
const resolved = await
|
|
17028
|
+
const resolved = await resolveDocument(documentOwner(), artifactReference);
|
|
17006
17029
|
const result = await client.requestApproval(resolved.artifact.id, { actor });
|
|
17007
17030
|
if (result.ask === null) {
|
|
17008
17031
|
return {
|
|
@@ -17080,7 +17103,7 @@ ${trailer.join(`
|
|
|
17080
17103
|
case "dispatch_read": {
|
|
17081
17104
|
if (ownerArguments.ref?.kind === "ask") {
|
|
17082
17105
|
const ref = ownerArguments.ref;
|
|
17083
|
-
const id = await resolveIdPrefix(input.tool, "ask", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await
|
|
17106
|
+
const id = await resolveIdPrefix(input.tool, "ask", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveDocument(ref.owner, ref.artifact)).artifact.id, "all"));
|
|
17084
17107
|
const askRead = await client.getAsk(id);
|
|
17085
17108
|
const askRef = refTarget(ref, "ask", id);
|
|
17086
17109
|
return {
|
|
@@ -17090,7 +17113,7 @@ ${trailer.join(`
|
|
|
17090
17113
|
}
|
|
17091
17114
|
if (ownerArguments.ref?.kind === "comment") {
|
|
17092
17115
|
const ref = ownerArguments.ref;
|
|
17093
|
-
const id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await
|
|
17116
|
+
const id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveDocument(ref.owner, ref.artifact)).artifact.id));
|
|
17094
17117
|
const comment = await client.getComment(id);
|
|
17095
17118
|
const commentRef = refTarget(ref, "comment", id);
|
|
17096
17119
|
return {
|
|
@@ -17110,7 +17133,7 @@ ${trailer.join(`
|
|
|
17110
17133
|
};
|
|
17111
17134
|
}
|
|
17112
17135
|
if (documentOwner().kind === "project") {
|
|
17113
|
-
const resolved = await
|
|
17136
|
+
const resolved = await resolveDocument(documentOwner(), stringArg(args, "artifact"));
|
|
17114
17137
|
const documentRef = dispatchDocumentRef(resolved.artifact.project, resolved.artifact.slug);
|
|
17115
17138
|
return {
|
|
17116
17139
|
text: [
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -286,16 +286,54 @@ distinction from the named issue, citing it (`dispatch://KEY`), for whoever read
|
|
|
286
286
|
|
|
287
287
|
To see the shape of a project rather than find a phrase, list its issues:
|
|
288
288
|
```ts
|
|
289
|
-
dispatch_issues({ project, status?, parent?, label?, updated_since?, limit? })
|
|
289
|
+
dispatch_issues({ project, status?, parent?, label?, priority?, updated_since?, limit? })
|
|
290
290
|
```
|
|
291
291
|
Each row carries the issue key, title, status, priority, parent, labels, its open-ask count, and
|
|
292
292
|
when it last changed — a roadmap or backlog pass without opening every issue. Filter with `status`
|
|
293
|
-
(a lifecycle status), `parent` (one issue's children), `label`,
|
|
293
|
+
(a lifecycle status), `parent` (one issue's children), `label`, `priority` (a list of `0`–`3`, with
|
|
294
|
+
`null` for an issue with no priority: `[0, 1]` is every P0 and P1), or `updated_since` (an RFC3339
|
|
294
295
|
timestamp, for "what moved this week"). `limit` caps the rows at 50 by default and 250 at most.
|
|
295
296
|
|
|
296
297
|
This is not search: it matches no text. Use `dispatch_search` for a keyword or phrase, and
|
|
297
298
|
`dispatch_issues` when you want every issue in a project and its current state.
|
|
298
299
|
|
|
300
|
+
### The owner audit
|
|
301
|
+
|
|
302
|
+
As the owner of a surface, list your area's P0 and P1 issues and staff or close each one nobody
|
|
303
|
+
has started:
|
|
304
|
+
```ts
|
|
305
|
+
dispatch_issues({ project, priority: [0, 1], limit: 250 })
|
|
306
|
+
```
|
|
307
|
+
Every unclaimed row in `triage`, `icebox`, `backlog` or `todo` is a decision: someone takes it and
|
|
308
|
+
builds it, or it closes. A row in `in_progress`, `testing`, `needs_review` or `retro`, or one that
|
|
309
|
+
carries a claim, is work under way ([Issue status is yours to move](#issue-status-is-yours-to-move))
|
|
310
|
+
and is not re-staffed. A todo with a finished spec reads as queued work that nobody is doing
|
|
311
|
+
(LEGION-173 sat in todo for two weeks with a complete spec; AGENTC-1010's v4 plan sat in backlog
|
|
312
|
+
with nobody building it).
|
|
313
|
+
|
|
314
|
+
The audit finds three shapes:
|
|
315
|
+
|
|
316
|
+
- **Unstaffed work.** A plan or measurement exists, and no one is building it.
|
|
317
|
+
- **Unrecorded delivery.** An issue not yet in `testing` or `done`, claimed or not, has a merged PR
|
|
318
|
+
naming it. Check the change live, then move the issue (AGENTC-1033 sat at `triage` after its fix,
|
|
319
|
+
agent-c #20367, merged).
|
|
320
|
+
- **Unrecorded practice.** Someone does the issue's work by hand, more than once, while the issue
|
|
321
|
+
sits in backlog (OPS-132, done by hand on every migration merge). It leaves no plan and no PR to
|
|
322
|
+
find; the tell is your own messages. Doing something by hand more than once means an issue is
|
|
323
|
+
wearing the wrong status.
|
|
324
|
+
|
|
325
|
+
Run the audit as a step of a coordinator's loop, at each checkpoint, not as a habit: these shapes
|
|
326
|
+
are found by running the check, not by noticing them.
|
|
327
|
+
|
|
328
|
+
### Symptom versus cause
|
|
329
|
+
|
|
330
|
+
When a symptom and its cause sit on different issues, the work accrues to the cause's issue, and
|
|
331
|
+
the symptom's issue carries a pointer to it. Before posting a measurement or finding, search
|
|
332
|
+
Dispatch for the failing identity's or component's name, and post on the issue whose title names
|
|
333
|
+
the fix, not the one naming the symptom. A symptom issue gathering messages with no human response
|
|
334
|
+
is the tell. (The production freeze was iterated on AGENTC-546, the failing gate, while its cause
|
|
335
|
+
and answer sat on AGENTC-1010.)
|
|
336
|
+
|
|
299
337
|
## Asking
|
|
300
338
|
|
|
301
339
|
### Before you ask
|
|
@@ -565,7 +603,8 @@ dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
|
|
|
565
603
|
```
|
|
566
604
|
It returns live or versioned markdown with open marks. A live read ends with a document token; `issue` with an
|
|
567
605
|
omitted `artifact` reads the issue specification; a project needs `artifact`; and a
|
|
568
|
-
`dispatch://PROJECT/artifact/<document-ref>` ref supplies both, where `document-ref` is the id
|
|
606
|
+
`dispatch://PROJECT/artifact/<document-ref>` ref supplies both, where `document-ref` is the slug (an id or a
|
|
607
|
+
filename resolves when no document has that slug).
|
|
569
608
|
|
|
570
609
|
Editing one is [Editing a document](references/document-edits.md): the shape of `dispatch_doc_edit`,
|
|
571
610
|
how to quote the text you mean, one `replace` per paragraph, preconditions against a stale edit, and
|
|
@@ -703,7 +742,9 @@ Exactly one of `path` and `content` is required. It returns issue or project-doc
|
|
|
703
742
|
Uploading the same `name` creates its next version — so uploading `spec.md` **replaces the issue's own specification**
|
|
704
743
|
with your text. Never do that: the spec is edited in place with `dispatch_doc_edit` (see [Editing a document](references/document-edits.md)). Address an existing
|
|
705
744
|
artifact by the slug shown in the upload result or by its filename, and a project document by its artifact id, slug, or filename; the
|
|
706
|
-
slug also arrives on `artifact.created` events.
|
|
745
|
+
slug also arrives on `artifact.created` events. Dispatch suffixes a slug two documents would share, so one document's
|
|
746
|
+
filename can be another's slug (`plan v2` takes `plan-v2`, then a document named `plan-v2` takes `plan-v2-2`): a bare
|
|
747
|
+
`artifact` that names both is refused with each one's id, while a `dispatch://` reference's document part is always the slug.
|
|
707
748
|
|
|
708
749
|
**Where a deliverable goes.** Text the human must read to decide — a draft message, a proposal,
|
|
709
750
|
a summary — goes in the spec as a section: the spec is the one document they open. A separate
|