@sjawhar/pi-legion-envoy 1.47.0 → 1.49.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/README.md +3 -3
- package/dist/envoy.js +89 -7
- package/dist/legion.js +23 -5
- package/dist/skills/dispatch/SKILL.md +17 -0
- package/dist/skills/legion-architect/SKILL.md +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,10 +85,10 @@ extension files it does not contain.
|
|
|
85
85
|
|
|
86
86
|
## Native Dispatch tools
|
|
87
87
|
|
|
88
|
-
The extension registers
|
|
88
|
+
The extension registers twenty native Dispatch tools: `dispatch_issue`, `dispatch_issue_update`, `dispatch_ask`, `dispatch_edit_ask`,
|
|
89
89
|
`dispatch_resolve_ask`, `dispatch_resolve_comment`, `dispatch_follow`, `dispatch_comment`, `dispatch_suggest`,
|
|
90
90
|
`dispatch_message`, `dispatch_doc_edit`, `dispatch_doc_read`, `dispatch_request_approval`, `dispatch_artifact`,
|
|
91
|
-
`dispatch_read`, `dispatch_search`, `dispatch_issues`, `dispatch_open_asks`, and `dispatch_whoami`, when Dispatch configuration resolves both a base URL and bearer token.
|
|
91
|
+
`dispatch_read`, `dispatch_search`, `dispatch_issues`, `dispatch_architecture_sync`, `dispatch_open_asks`, and `dispatch_whoami`, when Dispatch configuration resolves both a base URL and bearer token.
|
|
92
92
|
|
|
93
93
|
Configure the shared `envoy.json` with:
|
|
94
94
|
|
|
@@ -112,7 +112,7 @@ whose trimmed contents are the token — how the Legion daemon delivers it to a
|
|
|
112
112
|
pane) wins over every other token source and never falls back when unreadable.
|
|
113
113
|
Omitting `dispatch.serverUrl` while `dispatch.enabled` is true targets
|
|
114
114
|
`http://localhost:8766`, the Go server's listen address. Invalid configuration,
|
|
115
|
-
an invalid URL, or an empty token leaves the
|
|
115
|
+
an invalid URL, or an empty token leaves the twenty tools unavailable and
|
|
116
116
|
reports the source of the error.
|
|
117
117
|
|
|
118
118
|
Owner-scoped calls use either an issue (a native `KEY` or external `owner/repo#n` reference) or
|
package/dist/envoy.js
CHANGED
|
@@ -29863,6 +29863,14 @@ var commentValidation = (() => {
|
|
|
29863
29863
|
message: `${owner.message} turn requires reply_to_ask.`
|
|
29864
29864
|
};
|
|
29865
29865
|
})();
|
|
29866
|
+
var ISSUE_COMPONENTS_MODES = ["inherit", "explicit", "none"];
|
|
29867
|
+
function componentsArgument(z) {
|
|
29868
|
+
return z.object({
|
|
29869
|
+
mode: z.enum(ISSUE_COMPONENTS_MODES).describe("inherit: take the parent chain's attachment (the default; deletes this issue's own). explicit: attach to ids. none: not architectural work, with reason."),
|
|
29870
|
+
ids: z.array(z.string({ min: 1 }), { min: 1, max: 50 }).describe("For mode explicit: bare component ids from the project's architecture model (web, dispatch-server), not external ones.").optional(),
|
|
29871
|
+
reason: z.string({ min: 1 }).describe("For mode none: why this issue is not architectural (process, hiring, ops).").optional()
|
|
29872
|
+
}).describe("Attach the issue to architecture components. Attach the root before decomposing it; children inherit unless they choose.");
|
|
29873
|
+
}
|
|
29866
29874
|
var SPEC_SECTIONS = [
|
|
29867
29875
|
"Summary",
|
|
29868
29876
|
"Decisions needed",
|
|
@@ -29901,12 +29909,13 @@ var dispatchToolSpecs = [
|
|
|
29901
29909
|
spec: z.string().describe(`Optional initial primary-document markdown. ${SPEC_WRITING_GUIDANCE}`).optional(),
|
|
29902
29910
|
labels: z.array(z.string({ min: 1, max: 40 }), { max: 20 }).describe("Optional initial labels, at most 20 labels of up to 40 characters.").optional(),
|
|
29903
29911
|
priority: z.number({ int: true, min: 0, max: 3 }).describe("Optional coarse priority: P0 is highest and P3 is lowest.").optional(),
|
|
29904
|
-
assignee: z.string().describe("GitHub login of the human who answers this issue's asks; defaults to your owner when you act for a person, else the parent's assignee, else unassigned.").optional()
|
|
29912
|
+
assignee: z.string().describe("GitHub login of the human who answers this issue's asks; defaults to your owner when you act for a person, else the parent's assignee, else unassigned.").optional(),
|
|
29913
|
+
components: componentsArgument(z).optional()
|
|
29905
29914
|
})
|
|
29906
29915
|
},
|
|
29907
29916
|
{
|
|
29908
29917
|
name: "dispatch_issue_update",
|
|
29909
|
-
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, link a URL " + "(the pull request that delivers it, a run, a document), set its route,
|
|
29918
|
+
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, link a URL " + "(the pull request that delivers it, a run, a document), set its route, set or clear its parent, " + "or attach it to architecture components. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. external_links are " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link. components replaces the issue's own attachment and is allowed on a " + "closed issue. Priority is the human's and is not settable here. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
|
|
29910
29919
|
arguments: (z) => ({
|
|
29911
29920
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29912
29921
|
status: z.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
|
|
@@ -29914,14 +29923,15 @@ var dispatchToolSpecs = [
|
|
|
29914
29923
|
labels: z.array(z.string({ min: 1, max: 40 }), { max: 20 }).describe("Replacement label set, at most 20 labels of up to 40 characters; replaces every existing label.").optional(),
|
|
29915
29924
|
external_links: z.array(z.string({ min: 1 })).describe("URLs to link; merged into the issue's existing external links by URL.").optional(),
|
|
29916
29925
|
route: z.string().describe("Route the issue to role:<name> or session:<id>; an empty string clears it.").optional(),
|
|
29917
|
-
parent: z.string().describe("Parent issue key in the same project; an empty string clears the parent.").optional()
|
|
29926
|
+
parent: z.string().describe("Parent issue key in the same project; an empty string clears the parent.").optional(),
|
|
29927
|
+
components: componentsArgument(z).optional()
|
|
29918
29928
|
}),
|
|
29919
29929
|
validation: {
|
|
29920
29930
|
check: (value) => {
|
|
29921
29931
|
const input = value;
|
|
29922
|
-
return typeof input.status === "string" || typeof input.title === "string" || Array.isArray(input.labels) || Array.isArray(input.external_links) || typeof input.route === "string" || typeof input.parent === "string";
|
|
29932
|
+
return typeof input.status === "string" || typeof input.title === "string" || Array.isArray(input.labels) || Array.isArray(input.external_links) || typeof input.route === "string" || typeof input.parent === "string" || typeof input.components === "object" && input.components !== null;
|
|
29923
29933
|
},
|
|
29924
|
-
message: "Issue update requires at least one field besides issue: status, title, labels, external_links, route, or
|
|
29934
|
+
message: "Issue update requires at least one field besides issue: status, title, labels, external_links, route, parent, or components."
|
|
29925
29935
|
},
|
|
29926
29936
|
strict: true
|
|
29927
29937
|
},
|
|
@@ -30133,6 +30143,14 @@ var dispatchToolSpecs = [
|
|
|
30133
30143
|
limit: z.number({ int: true, min: 1, max: 250 }).describe("Maximum rows, 1-250; default 50.").optional()
|
|
30134
30144
|
})
|
|
30135
30145
|
},
|
|
30146
|
+
{
|
|
30147
|
+
name: "dispatch_architecture_sync",
|
|
30148
|
+
description: "Import a project's architecture model from its configured source repository now, instead of " + "waiting for the server's five-minute schedule. Returns the imported commit and component " + "count, or the recorded error when the model was rejected (the previous model stays up). " + "The source itself is configured by a human in Settings; 404 SOURCE_NOT_FOUND without one.",
|
|
30149
|
+
arguments: (z) => ({
|
|
30150
|
+
project: z.string().describe("Project key whose architecture source to sync, such as CORE.")
|
|
30151
|
+
}),
|
|
30152
|
+
strict: true
|
|
30153
|
+
},
|
|
30136
30154
|
{
|
|
30137
30155
|
name: "dispatch_open_asks",
|
|
30138
30156
|
description: "List active unanswered asks, oldest first, with age and whose reply is needed. Omit project to " + "see only this session's own authored asks (call before saying you are waiting for human input); " + "supply project to see every open ask across that project's issues and documents, whoever authored " + "them.",
|
|
@@ -31939,6 +31957,9 @@ class DispatchClient {
|
|
|
31939
31957
|
async whoami() {
|
|
31940
31958
|
return this.#json("GET", ["api", "v1", "whoami"]);
|
|
31941
31959
|
}
|
|
31960
|
+
async syncArchitectureSource(project) {
|
|
31961
|
+
return this.#json("POST", ["api", "v1", "projects", project, "architecture-source", "sync"]);
|
|
31962
|
+
}
|
|
31942
31963
|
async resolveAsk(id, input) {
|
|
31943
31964
|
return this.#json("POST", ["api", "v1", "asks", id, "resolve"], input);
|
|
31944
31965
|
}
|
|
@@ -32354,7 +32375,8 @@ var issueFreeTools = {
|
|
|
32354
32375
|
dispatch_search: true,
|
|
32355
32376
|
dispatch_issues: true,
|
|
32356
32377
|
dispatch_open_asks: true,
|
|
32357
|
-
dispatch_whoami: true
|
|
32378
|
+
dispatch_whoami: true,
|
|
32379
|
+
dispatch_architecture_sync: true
|
|
32358
32380
|
};
|
|
32359
32381
|
function canonicalExternalIssueRef(value) {
|
|
32360
32382
|
const match = value.trim().match(externalIssueRefPattern);
|
|
@@ -32378,6 +32400,27 @@ function optionalNumber(args, name) {
|
|
|
32378
32400
|
const value = args[name];
|
|
32379
32401
|
return typeof value === "number" ? value : undefined;
|
|
32380
32402
|
}
|
|
32403
|
+
function optionalComponents(args, name) {
|
|
32404
|
+
const value = args[name];
|
|
32405
|
+
if (typeof value !== "object" || value === null)
|
|
32406
|
+
return;
|
|
32407
|
+
const input = value;
|
|
32408
|
+
return {
|
|
32409
|
+
mode: input.mode,
|
|
32410
|
+
...input.ids === undefined ? {} : { ids: input.ids },
|
|
32411
|
+
...input.reason === undefined ? {} : { reason: input.reason }
|
|
32412
|
+
};
|
|
32413
|
+
}
|
|
32414
|
+
function componentsChange(input, after) {
|
|
32415
|
+
switch (input.mode) {
|
|
32416
|
+
case "explicit":
|
|
32417
|
+
return `components -> explicit [${after.ids.join(", ")}]`;
|
|
32418
|
+
case "none":
|
|
32419
|
+
return `components -> none (${after.reason ?? input.reason ?? ""})`;
|
|
32420
|
+
case "inherit":
|
|
32421
|
+
return "components -> inherit";
|
|
32422
|
+
}
|
|
32423
|
+
}
|
|
32381
32424
|
function isDuplicateCandidate(value) {
|
|
32382
32425
|
if (typeof value !== "object" || value === null)
|
|
32383
32426
|
return false;
|
|
@@ -32761,6 +32804,18 @@ function approvalLine(artifact) {
|
|
|
32761
32804
|
return `Approval: changes requested on v${approval.version} by ${approval.by?.id ?? "unknown"}: ${approval.reason ?? ""}`;
|
|
32762
32805
|
}
|
|
32763
32806
|
}
|
|
32807
|
+
function componentsLine(components) {
|
|
32808
|
+
const inherited = components.inherited_from === null ? "" : ` (inherited from ${components.inherited_from})`;
|
|
32809
|
+
const retired = components.unknown.length === 0 ? "" : ` (retired: ${components.unknown.join(", ")})`;
|
|
32810
|
+
switch (components.mode) {
|
|
32811
|
+
case "explicit":
|
|
32812
|
+
return `Components: ${components.ids.length === 0 ? "none live" : components.ids.join(", ")}${inherited}${retired}`;
|
|
32813
|
+
case "none":
|
|
32814
|
+
return `Components: none \u2014 ${components.reason ?? ""}${inherited}`;
|
|
32815
|
+
case "inherit":
|
|
32816
|
+
return "Components: unassigned";
|
|
32817
|
+
}
|
|
32818
|
+
}
|
|
32764
32819
|
function issueSummary(issue, events, references, graph) {
|
|
32765
32820
|
const asks = issue.open_asks;
|
|
32766
32821
|
const spec = issue.artifacts?.find((artifact) => artifact.primary);
|
|
@@ -32769,6 +32824,8 @@ function issueSummary(issue, events, references, graph) {
|
|
|
32769
32824
|
throw new Error("Dispatch issue is missing priority");
|
|
32770
32825
|
if (issue.assignee === undefined)
|
|
32771
32826
|
throw new Error("Dispatch issue is missing assignee");
|
|
32827
|
+
if (issue.components === undefined)
|
|
32828
|
+
throw new Error("Dispatch issue is missing components");
|
|
32772
32829
|
return [
|
|
32773
32830
|
`Title: ${issue.title}`,
|
|
32774
32831
|
`Key: ${issue.key}`,
|
|
@@ -32776,6 +32833,7 @@ function issueSummary(issue, events, references, graph) {
|
|
|
32776
32833
|
`Assignee: ${issue.assignee ?? "unassigned"}`,
|
|
32777
32834
|
...issue.priority === null ? [] : [`Priority: P${issue.priority}`],
|
|
32778
32835
|
`Labels: ${issue.labels.length === 0 ? "none" : issue.labels.join(", ")}`,
|
|
32836
|
+
componentsLine(issue.components),
|
|
32779
32837
|
`Route: ${issue.route ?? "none"}`,
|
|
32780
32838
|
...specApproval === undefined ? [] : [`Spec ${specApproval.replace(/^Approval/, "approval")}`],
|
|
32781
32839
|
"Open asks:",
|
|
@@ -33065,6 +33123,7 @@ async function executeDispatchTool(input) {
|
|
|
33065
33123
|
const spec = optionalString(args, "spec");
|
|
33066
33124
|
const priority = optionalNumber(args, "priority");
|
|
33067
33125
|
const assignee = optionalString(args, "assignee");
|
|
33126
|
+
const components = optionalComponents(args, "components");
|
|
33068
33127
|
const labels = args.labels;
|
|
33069
33128
|
try {
|
|
33070
33129
|
const created = await client.issue({
|
|
@@ -33076,6 +33135,7 @@ async function executeDispatchTool(input) {
|
|
|
33076
33135
|
...spec === undefined ? {} : { spec },
|
|
33077
33136
|
...priority === undefined ? {} : { priority },
|
|
33078
33137
|
...assignee === undefined ? {} : { assignee },
|
|
33138
|
+
...components === undefined ? {} : { components },
|
|
33079
33139
|
...Array.isArray(labels) ? { labels } : {},
|
|
33080
33140
|
actor
|
|
33081
33141
|
});
|
|
@@ -33108,6 +33168,7 @@ async function executeDispatchTool(input) {
|
|
|
33108
33168
|
const title = optionalString(args, "title");
|
|
33109
33169
|
const route = optionalString(args, "route");
|
|
33110
33170
|
const parent = optionalString(args, "parent");
|
|
33171
|
+
const components = optionalComponents(args, "components");
|
|
33111
33172
|
const labels = Array.isArray(args.labels) ? args.labels : undefined;
|
|
33112
33173
|
const requestedLinks = Array.isArray(args.external_links) ? [...new Set(args.external_links)] : undefined;
|
|
33113
33174
|
let newLinks = [];
|
|
@@ -33121,6 +33182,7 @@ async function executeDispatchTool(input) {
|
|
|
33121
33182
|
...labels === undefined ? {} : { labels },
|
|
33122
33183
|
...route === undefined ? {} : { route },
|
|
33123
33184
|
...parent === undefined ? {} : { parent: parent === "" ? null : parent },
|
|
33185
|
+
...components === undefined ? {} : { components },
|
|
33124
33186
|
...requestedLinks === undefined ? {} : { external_links: [...before.external_links, ...newLinks.map((url) => ({ url }))] },
|
|
33125
33187
|
actor
|
|
33126
33188
|
});
|
|
@@ -33133,7 +33195,8 @@ async function executeDispatchTool(input) {
|
|
|
33133
33195
|
newLinks.length === 0 ? `already linked ${requestedLinks.join(", ")} ${linkCount}` : `linked ${newLinks.join(", ")} ${linkCount}`
|
|
33134
33196
|
],
|
|
33135
33197
|
...route === undefined ? [] : [after.route === null ? "route cleared" : `route ${after.route}`],
|
|
33136
|
-
...parent === undefined ? [] : [after.parent === null ? "parent cleared" : `parent -> ${after.parent}`]
|
|
33198
|
+
...parent === undefined ? [] : [after.parent === null ? "parent cleared" : `parent -> ${after.parent}`],
|
|
33199
|
+
...components === undefined ? [] : [componentsChange(components, after.components)]
|
|
33137
33200
|
];
|
|
33138
33201
|
return {
|
|
33139
33202
|
text: `${after.key}: ${changes.join("; ")} ${notSubscribed(issueTopic(after.key))}`,
|
|
@@ -33202,6 +33265,25 @@ async function executeDispatchTool(input) {
|
|
|
33202
33265
|
details: { issues: rows }
|
|
33203
33266
|
};
|
|
33204
33267
|
}
|
|
33268
|
+
case "dispatch_architecture_sync": {
|
|
33269
|
+
const project = stringArg(args, "project");
|
|
33270
|
+
const source = await client.syncArchitectureSource(project);
|
|
33271
|
+
const at = source.last_sync_at ?? "unknown time";
|
|
33272
|
+
return {
|
|
33273
|
+
text: source.last_error === null ? `Synced ${project} architecture from ${source.repo}@${source.branch}: commit ${source.last_commit ?? "unknown"} (${at}).` : [
|
|
33274
|
+
`Sync failed for ${project} (${source.repo}@${source.branch}): ${source.last_error}`,
|
|
33275
|
+
source.last_commit === null ? "No model has ever imported for this project." : `The previous model stays up (commit ${source.last_commit}).`
|
|
33276
|
+
].join(`
|
|
33277
|
+
`),
|
|
33278
|
+
details: {
|
|
33279
|
+
project,
|
|
33280
|
+
repo: source.repo,
|
|
33281
|
+
branch: source.branch,
|
|
33282
|
+
commit: source.last_commit,
|
|
33283
|
+
error: source.last_error
|
|
33284
|
+
}
|
|
33285
|
+
};
|
|
33286
|
+
}
|
|
33205
33287
|
case "dispatch_resolve_ask": {
|
|
33206
33288
|
const kind = stringArg(args, "kind");
|
|
33207
33289
|
const ask = await client.resolveAsk(stringArg(args, "ask"), {
|
package/dist/legion.js
CHANGED
|
@@ -29137,6 +29137,14 @@ var commentValidation = (() => {
|
|
|
29137
29137
|
message: `${owner.message} turn requires reply_to_ask.`
|
|
29138
29138
|
};
|
|
29139
29139
|
})();
|
|
29140
|
+
var ISSUE_COMPONENTS_MODES = ["inherit", "explicit", "none"];
|
|
29141
|
+
function componentsArgument(z) {
|
|
29142
|
+
return z.object({
|
|
29143
|
+
mode: z.enum(ISSUE_COMPONENTS_MODES).describe("inherit: take the parent chain's attachment (the default; deletes this issue's own). explicit: attach to ids. none: not architectural work, with reason."),
|
|
29144
|
+
ids: z.array(z.string({ min: 1 }), { min: 1, max: 50 }).describe("For mode explicit: bare component ids from the project's architecture model (web, dispatch-server), not external ones.").optional(),
|
|
29145
|
+
reason: z.string({ min: 1 }).describe("For mode none: why this issue is not architectural (process, hiring, ops).").optional()
|
|
29146
|
+
}).describe("Attach the issue to architecture components. Attach the root before decomposing it; children inherit unless they choose.");
|
|
29147
|
+
}
|
|
29140
29148
|
var SPEC_SECTIONS = [
|
|
29141
29149
|
"Summary",
|
|
29142
29150
|
"Decisions needed",
|
|
@@ -29175,12 +29183,13 @@ var dispatchToolSpecs = [
|
|
|
29175
29183
|
spec: z.string().describe(`Optional initial primary-document markdown. ${SPEC_WRITING_GUIDANCE}`).optional(),
|
|
29176
29184
|
labels: z.array(z.string({ min: 1, max: 40 }), { max: 20 }).describe("Optional initial labels, at most 20 labels of up to 40 characters.").optional(),
|
|
29177
29185
|
priority: z.number({ int: true, min: 0, max: 3 }).describe("Optional coarse priority: P0 is highest and P3 is lowest.").optional(),
|
|
29178
|
-
assignee: z.string().describe("GitHub login of the human who answers this issue's asks; defaults to your owner when you act for a person, else the parent's assignee, else unassigned.").optional()
|
|
29186
|
+
assignee: z.string().describe("GitHub login of the human who answers this issue's asks; defaults to your owner when you act for a person, else the parent's assignee, else unassigned.").optional(),
|
|
29187
|
+
components: componentsArgument(z).optional()
|
|
29179
29188
|
})
|
|
29180
29189
|
},
|
|
29181
29190
|
{
|
|
29182
29191
|
name: "dispatch_issue_update",
|
|
29183
|
-
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, link a URL " + "(the pull request that delivers it, a run, a document), set its route,
|
|
29192
|
+
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, link a URL " + "(the pull request that delivers it, a run, a document), set its route, set or clear its parent, " + "or attach it to architecture components. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. external_links are " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link. components replaces the issue's own attachment and is allowed on a " + "closed issue. Priority is the human's and is not settable here. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
|
|
29184
29193
|
arguments: (z) => ({
|
|
29185
29194
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29186
29195
|
status: z.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
|
|
@@ -29188,14 +29197,15 @@ var dispatchToolSpecs = [
|
|
|
29188
29197
|
labels: z.array(z.string({ min: 1, max: 40 }), { max: 20 }).describe("Replacement label set, at most 20 labels of up to 40 characters; replaces every existing label.").optional(),
|
|
29189
29198
|
external_links: z.array(z.string({ min: 1 })).describe("URLs to link; merged into the issue's existing external links by URL.").optional(),
|
|
29190
29199
|
route: z.string().describe("Route the issue to role:<name> or session:<id>; an empty string clears it.").optional(),
|
|
29191
|
-
parent: z.string().describe("Parent issue key in the same project; an empty string clears the parent.").optional()
|
|
29200
|
+
parent: z.string().describe("Parent issue key in the same project; an empty string clears the parent.").optional(),
|
|
29201
|
+
components: componentsArgument(z).optional()
|
|
29192
29202
|
}),
|
|
29193
29203
|
validation: {
|
|
29194
29204
|
check: (value) => {
|
|
29195
29205
|
const input = value;
|
|
29196
|
-
return typeof input.status === "string" || typeof input.title === "string" || Array.isArray(input.labels) || Array.isArray(input.external_links) || typeof input.route === "string" || typeof input.parent === "string";
|
|
29206
|
+
return typeof input.status === "string" || typeof input.title === "string" || Array.isArray(input.labels) || Array.isArray(input.external_links) || typeof input.route === "string" || typeof input.parent === "string" || typeof input.components === "object" && input.components !== null;
|
|
29197
29207
|
},
|
|
29198
|
-
message: "Issue update requires at least one field besides issue: status, title, labels, external_links, route, or
|
|
29208
|
+
message: "Issue update requires at least one field besides issue: status, title, labels, external_links, route, parent, or components."
|
|
29199
29209
|
},
|
|
29200
29210
|
strict: true
|
|
29201
29211
|
},
|
|
@@ -29407,6 +29417,14 @@ var dispatchToolSpecs = [
|
|
|
29407
29417
|
limit: z.number({ int: true, min: 1, max: 250 }).describe("Maximum rows, 1-250; default 50.").optional()
|
|
29408
29418
|
})
|
|
29409
29419
|
},
|
|
29420
|
+
{
|
|
29421
|
+
name: "dispatch_architecture_sync",
|
|
29422
|
+
description: "Import a project's architecture model from its configured source repository now, instead of " + "waiting for the server's five-minute schedule. Returns the imported commit and component " + "count, or the recorded error when the model was rejected (the previous model stays up). " + "The source itself is configured by a human in Settings; 404 SOURCE_NOT_FOUND without one.",
|
|
29423
|
+
arguments: (z) => ({
|
|
29424
|
+
project: z.string().describe("Project key whose architecture source to sync, such as CORE.")
|
|
29425
|
+
}),
|
|
29426
|
+
strict: true
|
|
29427
|
+
},
|
|
29410
29428
|
{
|
|
29411
29429
|
name: "dispatch_open_asks",
|
|
29412
29430
|
description: "List active unanswered asks, oldest first, with age and whose reply is needed. Omit project to " + "see only this session's own authored asks (call before saying you are waiting for human input); " + "supply project to see every open ask across that project's issues and documents, whoever authored " + "them.",
|
|
@@ -248,6 +248,23 @@ that follow from it:
|
|
|
248
248
|
the question carries `dispatch://KEY/artifact/<slug>` (or `ref`), never just its filename. Text
|
|
249
249
|
they must read to decide belongs in the spec in the first place — see [Artifacts](#artifacts).
|
|
250
250
|
|
|
251
|
+
Attach an issue to the architecture components it changes, before decomposing it — children inherit the parent's attachment unless they choose their own, so attaching the root once classifies the whole tree:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
dispatch_issue({ project: "CORE", title: "...", components: { mode: "explicit", ids: ["dispatch-server", "web"] } })
|
|
255
|
+
dispatch_issue_update({ issue: "CORE-12", components: { mode: "explicit", ids: ["web"] } }) // this issue's own set, replacing what it inherited
|
|
256
|
+
dispatch_issue_update({ issue: "CORE-13", components: { mode: "none", reason: "hiring, not code" } })
|
|
257
|
+
dispatch_issue_update({ issue: "CORE-14", components: { mode: "inherit" } }) // back to the parent chain's attachment
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Component ids are the file names under the repository's `.dispatch/architecture/` (`web.md` → `web`); an id the model lacks, or an `external` component, is refused with `COMPONENTS_INPUT`. A closed issue can be classified without reopening. Two rules: declare and attach before decomposing work, and change code and its architecture description (`.dispatch/architecture/<id>.md`) in the same review — the tree at `GET /api/v1/projects/{key}/architecture` counts every issue whose effective set names a component or anything it contains.
|
|
261
|
+
|
|
262
|
+
Import a project's architecture model from its configured source repository now (a human configures the source in Settings):
|
|
263
|
+
```ts
|
|
264
|
+
dispatch_architecture_sync({ project: "CORE" })
|
|
265
|
+
```
|
|
266
|
+
It returns the imported commit, or the recorded error when the model was rejected — the previous model stays up. Without a configured source it answers 404 `SOURCE_NOT_FOUND`.
|
|
267
|
+
|
|
251
268
|
Before saying you are waiting for human input, call `dispatch_open_asks`. With no arguments it lists this session's active asks across open issues and project documents, including whether the human or agent owes the next reply. With `dispatch_open_asks({ project })` it lists every open ask in that project — on its issues and on its documents, whoever authored them — which is how you audit what a whole project is waiting on rather than just your own asks.
|
|
252
269
|
|
|
253
270
|
**Anything that needs the human is an ask, or it does not exist.** An approval, a credential,
|
|
@@ -331,3 +331,7 @@ capacity, and cross-tree conflict. Use the Legion escalation operation for those
|
|
|
331
331
|
everything else in the tree, or use `dispatch_ask` for a human question; workers may reach
|
|
332
332
|
Sami directly with `dispatch_ask` the same way. Do not create a wait loop for any wake
|
|
333
333
|
source.
|
|
334
|
+
|
|
335
|
+
## Architecture components
|
|
336
|
+
|
|
337
|
+
Bootstrap on a root whose project has an architecture source: in the root's first PR (the implement worker pushes it), write `.dispatch/architecture/<id>.md` files for the planned components — front matter `title`, `parent`, `depends_on`, `external`; no `paths` yet. The importer reads the source branch's head, so the sync and the attach below work only once that PR has merged to the source branch: until then leave the root on `inherit` (the attach would answer `400 COMPONENTS_INPUT`, the component does not exist yet). After the merge, `dispatch_architecture_sync({ project })` and attach the root with `dispatch_issue_update({ issue, components: { mode: "explicit", ids: [...] } })`. Children inherit the root's attachment; give a child its own `components` only when it changes a narrower set, and `{ mode: "none", reason }` when it is not architectural work. Attach before decomposing, and require every implementer to change the component file beside the code it describes in the same review.
|