@sjawhar/pi-legion-envoy 1.53.5 → 1.55.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/envoy.js +55 -8
- package/dist/legion.js +46 -7
- package/dist/skills/AGENTS.md +14 -41
- package/dist/skills/dispatch/SKILL.md +43 -20
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -29913,6 +29913,20 @@ function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false)
|
|
|
29913
29913
|
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."
|
|
29914
29914
|
};
|
|
29915
29915
|
}
|
|
29916
|
+
var documentEditValidation = {
|
|
29917
|
+
check: (value) => {
|
|
29918
|
+
if (!documentOwnerValidation(true, true).check(value))
|
|
29919
|
+
return false;
|
|
29920
|
+
const input = value;
|
|
29921
|
+
if (input.precondition === undefined)
|
|
29922
|
+
return true;
|
|
29923
|
+
if (typeof input.precondition !== "object" || input.precondition === null)
|
|
29924
|
+
return false;
|
|
29925
|
+
const precondition = input.precondition;
|
|
29926
|
+
return typeof precondition.document === "string" !== Array.isArray(precondition.blocks);
|
|
29927
|
+
},
|
|
29928
|
+
message: "Exactly one of issue and project is required; artifact or ref must name the document. A precondition selects exactly one of document or blocks."
|
|
29929
|
+
};
|
|
29916
29930
|
var commentOwner = documentOwnerValidation(true);
|
|
29917
29931
|
var commentValidation = {
|
|
29918
29932
|
check: (value) => {
|
|
@@ -29957,7 +29971,15 @@ var ISSUE_STATUSES = [
|
|
|
29957
29971
|
function isIssueStatus(value) {
|
|
29958
29972
|
return ISSUE_STATUSES.includes(value);
|
|
29959
29973
|
}
|
|
29960
|
-
var DOC_EDIT_OPS = [
|
|
29974
|
+
var DOC_EDIT_OPS = [
|
|
29975
|
+
"replace",
|
|
29976
|
+
"delete",
|
|
29977
|
+
"insert",
|
|
29978
|
+
"retype",
|
|
29979
|
+
"move",
|
|
29980
|
+
"delete_row",
|
|
29981
|
+
"delete_column"
|
|
29982
|
+
];
|
|
29961
29983
|
var dispatchToolSpecs = [
|
|
29962
29984
|
{
|
|
29963
29985
|
name: "dispatch_issue",
|
|
@@ -30135,9 +30157,10 @@ var dispatchToolSpecs = [
|
|
|
30135
30157
|
example: {
|
|
30136
30158
|
issue: "DSP-1",
|
|
30137
30159
|
artifact: "spec",
|
|
30138
|
-
ops: [{ op: "
|
|
30160
|
+
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
30161
|
+
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
30139
30162
|
},
|
|
30140
|
-
description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block,
|
|
30163
|
+
description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, delete or move a whole block by its id, or delete a table row or column in place. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + "For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, `code`) is tolerated; a leading '# ' matches a heading. replace is inline: with is the new text of the matched span, so a leading list or heading marker stays literal text. " + "A delete whose find is a block's entire text removes the block (a list emptied of its items goes too); delete with block removes any block by id, and move with block relocates one. delete_row and delete_column take a table block and a zero-based index, preserving the table block id and refusing to remove cells with open asks or unresolved comments. " + 'Insert and move anchors also accept "start", "end", "heading:<exact heading text>", and "block:<id>"; block ids and their tokens come from GET /api/v1/artifacts/{artifact UUID}/blocks (the route takes the artifact UUID, not its slug). ' + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch_doc_read, or block {id, token} values from /blocks. A block guard must include every block the batch changes; Dispatch resolves quote targets and rejects an uncovered batch rather than applying it. Use a document token for insert or move, which depend on document order. Prefer block tokens when the covered content blocks are independent sections. Tokens include inline marks, so a fresh human comment also makes a stale edit fail. PRECONDITION_FAILED means re-read; EDIT_QUEUE_FULL means back off before retrying. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
30141
30164
|
arguments: (z) => ({
|
|
30142
30165
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
30143
30166
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -30151,18 +30174,26 @@ var dispatchToolSpecs = [
|
|
|
30151
30174
|
markdown: z.string().describe("Markdown to insert.").optional(),
|
|
30152
30175
|
after: z.string().describe(`Insert or move after this anchor: a quote of the neighbouring block's text, or one of "start", "end", "heading:<exact heading text>", "block:<id>".`).optional(),
|
|
30153
30176
|
before: z.string().describe(`Insert or move before this anchor: a quote of the neighbouring block's text, or one of "start", "end", "heading:<exact heading text>", "block:<id>".`).optional(),
|
|
30154
|
-
block: z.string().describe("Block id for retype, delete, or
|
|
30177
|
+
block: z.string().describe("Block id for retype, delete, move, delete_row, or delete_column: the #id of a typed block, or an id from GET /api/v1/artifacts/{id}/blocks.").optional(),
|
|
30178
|
+
index: z.number({ int: true, min: 0 }).describe("Zero-based row or column index for delete_row or delete_column.").optional(),
|
|
30155
30179
|
type: z.string().describe("Typed block name for retype.").optional(),
|
|
30156
30180
|
attributes: z.unknown().describe("Typed block attributes for retype.").optional()
|
|
30157
30181
|
})).describe("Flat tagged edits; the server validates fields required for each operation."),
|
|
30182
|
+
precondition: z.object({
|
|
30183
|
+
document: z.string({ min: 1 }).describe("Token for the exact canonical document returned by dispatch_doc_read.").optional(),
|
|
30184
|
+
blocks: z.array(z.object({
|
|
30185
|
+
id: z.string({ min: 1 }).describe("Stable block id from GET /api/v1/artifacts/{id}/blocks."),
|
|
30186
|
+
token: z.string({ min: 1 }).describe("That block's full-state token, including inline marks.")
|
|
30187
|
+
}), { min: 1 }).describe("Every content block this batch changes, each with the token returned by /blocks.").optional()
|
|
30188
|
+
}).describe("Optional optimistic-concurrency guard; select exactly one of document or blocks.").optional(),
|
|
30158
30189
|
summary: z.string().describe("Optional named-version summary.").optional()
|
|
30159
30190
|
}),
|
|
30160
|
-
validation:
|
|
30191
|
+
validation: documentEditValidation
|
|
30161
30192
|
},
|
|
30162
30193
|
{
|
|
30163
30194
|
name: "dispatch_doc_read",
|
|
30164
30195
|
example: { issue: "DSP-1" },
|
|
30165
|
-
description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + OWNER_REFERENCE,
|
|
30196
|
+
description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch_doc_edit precondition; use /blocks for per-block tokens. " + OWNER_REFERENCE,
|
|
30166
30197
|
arguments: (z) => ({
|
|
30167
30198
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
30168
30199
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -30387,6 +30418,14 @@ var reviewSchema = baseHandoffSchema.extend({
|
|
|
30387
30418
|
verdict: _enum2(["approved", "changes_requested"]).optional(),
|
|
30388
30419
|
keyFindings: array(object({ severity: string2(), file: string2(), description: string2() }).passthrough()).optional()
|
|
30389
30420
|
});
|
|
30421
|
+
var nonEmptySkillList = array(string2().trim().min(1)).min(1);
|
|
30422
|
+
var planWriteSchema = planSchema.extend({
|
|
30423
|
+
requiredSkills: object({
|
|
30424
|
+
implement: nonEmptySkillList,
|
|
30425
|
+
test: nonEmptySkillList,
|
|
30426
|
+
review: nonEmptySkillList
|
|
30427
|
+
}).passthrough()
|
|
30428
|
+
});
|
|
30390
30429
|
var phaseHandoffSchema = discriminatedUnion("phase", [
|
|
30391
30430
|
architectSchema,
|
|
30392
30431
|
planSchema,
|
|
@@ -32064,12 +32103,16 @@ class DispatchServiceError extends Error {
|
|
|
32064
32103
|
code;
|
|
32065
32104
|
status;
|
|
32066
32105
|
candidates;
|
|
32106
|
+
current;
|
|
32107
|
+
mismatches;
|
|
32067
32108
|
name = "DispatchServiceError";
|
|
32068
|
-
constructor(code, status, message, candidates) {
|
|
32109
|
+
constructor(code, status, message, candidates, current, mismatches) {
|
|
32069
32110
|
super(message);
|
|
32070
32111
|
this.code = code;
|
|
32071
32112
|
this.status = status;
|
|
32072
32113
|
this.candidates = candidates;
|
|
32114
|
+
this.current = current;
|
|
32115
|
+
this.mismatches = mismatches;
|
|
32073
32116
|
}
|
|
32074
32117
|
}
|
|
32075
32118
|
function asErrorShape(value) {
|
|
@@ -32381,7 +32424,7 @@ class DispatchClient {
|
|
|
32381
32424
|
}
|
|
32382
32425
|
if (!response.ok) {
|
|
32383
32426
|
const error = asErrorShape(payload);
|
|
32384
|
-
throw new DispatchServiceError(error.code ?? `HTTP_${response.status}`, response.status, error.error ?? (typeof payload === "string" && payload ? payload : response.statusText), error.candidates);
|
|
32427
|
+
throw new DispatchServiceError(error.code ?? `HTTP_${response.status}`, response.status, error.error ?? (typeof payload === "string" && payload ? payload : response.statusText), error.candidates, error.current, error.mismatches);
|
|
32385
32428
|
}
|
|
32386
32429
|
return payload;
|
|
32387
32430
|
}
|
|
@@ -33708,9 +33751,12 @@ ${followsAsk(askOwner)}`,
|
|
|
33708
33751
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
33709
33752
|
const ops = args.ops;
|
|
33710
33753
|
const summary = optionalString(args, "summary");
|
|
33754
|
+
const { precondition: rawPrecondition } = args;
|
|
33755
|
+
const precondition = rawPrecondition;
|
|
33711
33756
|
const edited = await client.docEdit(resolved.artifact.id, {
|
|
33712
33757
|
ops,
|
|
33713
33758
|
...summary === undefined ? {} : { summary },
|
|
33759
|
+
...precondition === undefined ? {} : { precondition },
|
|
33714
33760
|
actor
|
|
33715
33761
|
});
|
|
33716
33762
|
const retyped = ops.filter((operation) => operation.op === "retype").length;
|
|
@@ -33738,6 +33784,7 @@ ${followsAsk(askOwner)}`,
|
|
|
33738
33784
|
const marks = marksResult.value;
|
|
33739
33785
|
const approval = approvalLine(resolved.artifact);
|
|
33740
33786
|
const trailer = [
|
|
33787
|
+
..."token" in document && document.token !== undefined ? [`Document token: ${document.token}`] : [],
|
|
33741
33788
|
...marks.length === 0 ? [] : [`Open anchored asks/comments: ${marks.join(", ")}`],
|
|
33742
33789
|
...approval === undefined ? [] : [approval]
|
|
33743
33790
|
];
|
package/dist/legion.js
CHANGED
|
@@ -16145,7 +16145,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
16145
16145
|
// package.json
|
|
16146
16146
|
var package_default = {
|
|
16147
16147
|
name: "@sjawhar/pi-legion-envoy",
|
|
16148
|
-
version: "1.
|
|
16148
|
+
version: "1.55.0",
|
|
16149
16149
|
type: "module",
|
|
16150
16150
|
omp: {
|
|
16151
16151
|
extensions: [
|
|
@@ -29232,6 +29232,20 @@ function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false)
|
|
|
29232
29232
|
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."
|
|
29233
29233
|
};
|
|
29234
29234
|
}
|
|
29235
|
+
var documentEditValidation = {
|
|
29236
|
+
check: (value) => {
|
|
29237
|
+
if (!documentOwnerValidation(true, true).check(value))
|
|
29238
|
+
return false;
|
|
29239
|
+
const input = value;
|
|
29240
|
+
if (input.precondition === undefined)
|
|
29241
|
+
return true;
|
|
29242
|
+
if (typeof input.precondition !== "object" || input.precondition === null)
|
|
29243
|
+
return false;
|
|
29244
|
+
const precondition = input.precondition;
|
|
29245
|
+
return typeof precondition.document === "string" !== Array.isArray(precondition.blocks);
|
|
29246
|
+
},
|
|
29247
|
+
message: "Exactly one of issue and project is required; artifact or ref must name the document. A precondition selects exactly one of document or blocks."
|
|
29248
|
+
};
|
|
29235
29249
|
var commentOwner = documentOwnerValidation(true);
|
|
29236
29250
|
var commentValidation = {
|
|
29237
29251
|
check: (value) => {
|
|
@@ -29276,7 +29290,15 @@ var ISSUE_STATUSES = [
|
|
|
29276
29290
|
function isIssueStatus(value) {
|
|
29277
29291
|
return ISSUE_STATUSES.includes(value);
|
|
29278
29292
|
}
|
|
29279
|
-
var DOC_EDIT_OPS = [
|
|
29293
|
+
var DOC_EDIT_OPS = [
|
|
29294
|
+
"replace",
|
|
29295
|
+
"delete",
|
|
29296
|
+
"insert",
|
|
29297
|
+
"retype",
|
|
29298
|
+
"move",
|
|
29299
|
+
"delete_row",
|
|
29300
|
+
"delete_column"
|
|
29301
|
+
];
|
|
29280
29302
|
var dispatchToolSpecs = [
|
|
29281
29303
|
{
|
|
29282
29304
|
name: "dispatch_issue",
|
|
@@ -29454,9 +29476,10 @@ var dispatchToolSpecs = [
|
|
|
29454
29476
|
example: {
|
|
29455
29477
|
issue: "DSP-1",
|
|
29456
29478
|
artifact: "spec",
|
|
29457
|
-
ops: [{ op: "
|
|
29479
|
+
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
29480
|
+
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
29458
29481
|
},
|
|
29459
|
-
description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block,
|
|
29482
|
+
description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, delete or move a whole block by its id, or delete a table row or column in place. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + "For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, `code`) is tolerated; a leading '# ' matches a heading. replace is inline: with is the new text of the matched span, so a leading list or heading marker stays literal text. " + "A delete whose find is a block's entire text removes the block (a list emptied of its items goes too); delete with block removes any block by id, and move with block relocates one. delete_row and delete_column take a table block and a zero-based index, preserving the table block id and refusing to remove cells with open asks or unresolved comments. " + 'Insert and move anchors also accept "start", "end", "heading:<exact heading text>", and "block:<id>"; block ids and their tokens come from GET /api/v1/artifacts/{artifact UUID}/blocks (the route takes the artifact UUID, not its slug). ' + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch_doc_read, or block {id, token} values from /blocks. A block guard must include every block the batch changes; Dispatch resolves quote targets and rejects an uncovered batch rather than applying it. Use a document token for insert or move, which depend on document order. Prefer block tokens when the covered content blocks are independent sections. Tokens include inline marks, so a fresh human comment also makes a stale edit fail. PRECONDITION_FAILED means re-read; EDIT_QUEUE_FULL means back off before retrying. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
29460
29483
|
arguments: (z) => ({
|
|
29461
29484
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29462
29485
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -29470,18 +29493,26 @@ var dispatchToolSpecs = [
|
|
|
29470
29493
|
markdown: z.string().describe("Markdown to insert.").optional(),
|
|
29471
29494
|
after: z.string().describe(`Insert or move after this anchor: a quote of the neighbouring block's text, or one of "start", "end", "heading:<exact heading text>", "block:<id>".`).optional(),
|
|
29472
29495
|
before: z.string().describe(`Insert or move before this anchor: a quote of the neighbouring block's text, or one of "start", "end", "heading:<exact heading text>", "block:<id>".`).optional(),
|
|
29473
|
-
block: z.string().describe("Block id for retype, delete, or
|
|
29496
|
+
block: z.string().describe("Block id for retype, delete, move, delete_row, or delete_column: the #id of a typed block, or an id from GET /api/v1/artifacts/{id}/blocks.").optional(),
|
|
29497
|
+
index: z.number({ int: true, min: 0 }).describe("Zero-based row or column index for delete_row or delete_column.").optional(),
|
|
29474
29498
|
type: z.string().describe("Typed block name for retype.").optional(),
|
|
29475
29499
|
attributes: z.unknown().describe("Typed block attributes for retype.").optional()
|
|
29476
29500
|
})).describe("Flat tagged edits; the server validates fields required for each operation."),
|
|
29501
|
+
precondition: z.object({
|
|
29502
|
+
document: z.string({ min: 1 }).describe("Token for the exact canonical document returned by dispatch_doc_read.").optional(),
|
|
29503
|
+
blocks: z.array(z.object({
|
|
29504
|
+
id: z.string({ min: 1 }).describe("Stable block id from GET /api/v1/artifacts/{id}/blocks."),
|
|
29505
|
+
token: z.string({ min: 1 }).describe("That block's full-state token, including inline marks.")
|
|
29506
|
+
}), { min: 1 }).describe("Every content block this batch changes, each with the token returned by /blocks.").optional()
|
|
29507
|
+
}).describe("Optional optimistic-concurrency guard; select exactly one of document or blocks.").optional(),
|
|
29477
29508
|
summary: z.string().describe("Optional named-version summary.").optional()
|
|
29478
29509
|
}),
|
|
29479
|
-
validation:
|
|
29510
|
+
validation: documentEditValidation
|
|
29480
29511
|
},
|
|
29481
29512
|
{
|
|
29482
29513
|
name: "dispatch_doc_read",
|
|
29483
29514
|
example: { issue: "DSP-1" },
|
|
29484
|
-
description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + OWNER_REFERENCE,
|
|
29515
|
+
description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch_doc_edit precondition; use /blocks for per-block tokens. " + OWNER_REFERENCE,
|
|
29485
29516
|
arguments: (z) => ({
|
|
29486
29517
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29487
29518
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -29706,6 +29737,14 @@ var reviewSchema = baseHandoffSchema.extend({
|
|
|
29706
29737
|
verdict: _enum2(["approved", "changes_requested"]).optional(),
|
|
29707
29738
|
keyFindings: array(object({ severity: string2(), file: string2(), description: string2() }).passthrough()).optional()
|
|
29708
29739
|
});
|
|
29740
|
+
var nonEmptySkillList = array(string2().trim().min(1)).min(1);
|
|
29741
|
+
var planWriteSchema = planSchema.extend({
|
|
29742
|
+
requiredSkills: object({
|
|
29743
|
+
implement: nonEmptySkillList,
|
|
29744
|
+
test: nonEmptySkillList,
|
|
29745
|
+
review: nonEmptySkillList
|
|
29746
|
+
}).passthrough()
|
|
29747
|
+
});
|
|
29709
29748
|
var phaseHandoffSchema = discriminatedUnion("phase", [
|
|
29710
29749
|
architectSchema,
|
|
29711
29750
|
planSchema,
|
package/dist/skills/AGENTS.md
CHANGED
|
@@ -4,44 +4,17 @@ Legion skills guide the architect and its sequential phase workers in a shared i
|
|
|
4
4
|
They are Markdown instructions loaded by Oh My Pi sessions; the daemon and OMP extension own
|
|
5
5
|
event intake, process lifecycle, credentials, and role delivery.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
The extension supplies a phase worker with its issue, workspace, role token, and structured
|
|
23
|
-
output schema. The worker claims its supplied role, works only on its phase artifact, and
|
|
24
|
-
returns that schema to the architect. It writes the same phase-specific payload to
|
|
25
|
-
`.legion/<phase>.json`, verifies it exists, and commits the handoff before reporting completion.
|
|
26
|
-
The committed predecessor handoff wins after revival or re-creation.
|
|
27
|
-
|
|
28
|
-
Workers do not run a controller loop or mutate lifecycle labels. Workers coordinate
|
|
29
|
-
lifecycle, scope, and cross-phase decisions with the owning architect by `envoy_publish` to its
|
|
30
|
-
role topic, sending the verified observation and decision needed (`hub` reaches only subagents
|
|
31
|
-
inside the worker's own process). A worker may call the native `dispatch_*` tools directly for a
|
|
32
|
-
durable human question; replies come back to the worker's own session.
|
|
33
|
-
|
|
34
|
-
## Durable artifacts
|
|
35
|
-
|
|
36
|
-
Phase handoffs are committed in lifecycle order: architect, plan, implement, test, and review.
|
|
37
|
-
Only the implementer pushes them: it and the merger act as the code-writing GitHub App, while
|
|
38
|
-
the planner, tester, reviewer, and architects act as the review App (`appRoleForLegionRole`,
|
|
39
|
-
`packages/daemon/src/daemon/github-apps.ts`), which holds no `contents` permission — their
|
|
40
|
-
handoff commits stay on the shared workspace's issue branch and ride the implementer's next push.
|
|
41
|
-
A clean review ends with the `.legion/` deletion pushed by the implementer at the reviewer's
|
|
42
|
-
direction, which the reviewer then approves; retro records its learning in
|
|
43
|
-
`docs/solutions/` and writes no handoff. GitHub comments and reviews carry the required Legion
|
|
44
|
-
footer so the daemon can attribute artifacts to their worker session.
|
|
45
|
-
The implement handoff carries the implementer's own production-like proof and the test handoff the
|
|
46
|
-
tester's verdict on it plus the tester's own; `legion handoff write` refuses a payload the phase's
|
|
47
|
-
schema rejects and names the field. Retro's message goes to the Dispatch issue (`dispatch_message`), never a GitHub issue.
|
|
7
|
+
| Skill | Who reads it | What it owns |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `dispatch/` | every role, and any session writing to Dispatch | specs, asks, comments, artifacts, and messages on native Dispatch |
|
|
10
|
+
| `envoy/` | every role | subscriptions, agent-to-agent messages, and topic formats |
|
|
11
|
+
| `legion-architect/` | root and sub-architects | tree ownership, decomposition, waves, gates, integration, sign-off |
|
|
12
|
+
| `legion-controller/` | the controller root process | wake routing, backlog admission, escalation |
|
|
13
|
+
| `legion-oracle/` | any role doing research | repository-grounded research |
|
|
14
|
+
| `legion-retro/` | the implementer, at retro | the pre-merge retrospective and its Dispatch message |
|
|
15
|
+
| `legion-worker/` | planner, implementer, tester, reviewer, merger | the phase contracts: handoffs, GitHub identity, PR body and READY discipline, the merge-gate order |
|
|
16
|
+
|
|
17
|
+
The owning skill above is where each contract is defined; a role prompt that needs a contract from its own seat points there or restates only its own step. This file lists and does not restate.
|
|
18
|
+
The text a worker boots with (its role prompt) lives in `packages/pi-envoy/roles/` and is
|
|
19
|
+
composed per role in `packages/daemon/src/daemon/processes.ts`; `packages/pi-envoy/roles/roles.test.ts`
|
|
20
|
+
holds the structural rules for those parts.
|
|
@@ -381,18 +381,19 @@ Read the current document before changing it:
|
|
|
381
381
|
```ts
|
|
382
382
|
dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
|
|
383
383
|
```
|
|
384
|
-
It returns live or versioned markdown with open marks.
|
|
385
|
-
`artifact
|
|
384
|
+
It returns live or versioned markdown with open marks. A live read ends with a document token; `issue` with an
|
|
385
|
+
omitted `artifact` reads the issue specification; a project needs `artifact`; and a
|
|
386
|
+
`dispatch://PROJECT/artifact/<document-ref>` ref supplies both, where `document-ref` is the id, slug, or filename.
|
|
386
387
|
|
|
387
388
|
```ts
|
|
388
|
-
dispatch_doc_edit({ issue?, project?, artifact, ops, summary? })
|
|
389
|
+
dispatch_doc_edit({ issue?, project?, artifact, ops, precondition?, summary? })
|
|
389
390
|
```
|
|
390
391
|
It returns issue or project-document owner details plus `applied` and optional `version`. `ops` is an array of this
|
|
391
392
|
exact `EditOp` shape:
|
|
392
393
|
|
|
393
394
|
```ts
|
|
394
395
|
type EditOp = {
|
|
395
|
-
op: "replace" | "delete" | "insert" | "retype" | "move";
|
|
396
|
+
op: "replace" | "delete" | "insert" | "retype" | "move" | "delete_row" | "delete_column";
|
|
396
397
|
find?: string;
|
|
397
398
|
with?: string;
|
|
398
399
|
occurrence?: number;
|
|
@@ -400,6 +401,7 @@ type EditOp = {
|
|
|
400
401
|
after?: string;
|
|
401
402
|
before?: string;
|
|
402
403
|
block?: string;
|
|
404
|
+
index?: number;
|
|
403
405
|
type?: string;
|
|
404
406
|
attributes?: Record<string, unknown>;
|
|
405
407
|
};
|
|
@@ -411,18 +413,24 @@ anchor is its cell text. Quote code-block contents without their Markdown fences
|
|
|
411
413
|
within one textblock; split changes that span separate blocks into separate operations.
|
|
412
414
|
|
|
413
415
|
`replace` requires `find` and `with`; `delete` requires `find` or `block`; `insert` requires `markdown` and exactly one of `after` or
|
|
414
|
-
`before`; `move` requires `block` and exactly one of `after` or `before
|
|
415
|
-
`
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
`
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
416
|
+
`before`; `move` requires `block` and exactly one of `after` or `before`; and `delete_row` / `delete_column` each require a table
|
|
417
|
+
`block` plus a zero-based `index`. An insert or move anchor is a quote, `"start"`, `"end"`, `"heading:Title"`, or `"block:<id>"`.
|
|
418
|
+
Ordinary inserts create a sibling block before or after the quote, heading, or block's enclosing document block, and a move lands the
|
|
419
|
+
block at that same boundary; `"start"` and `"end"` select the document edges. At a table-cell quote, a body-row fragment (no header or
|
|
420
|
+
delimiter rows) extends that table before or after the matched row instead; short rows are padded, wider rows are rejected, and deleting
|
|
421
|
+
a cell's quoted text removes only that text. `delete_row` / `delete_column` instead mutate their named table in place, keeping the
|
|
422
|
+
table's block id. A row index includes the header: row `0` is the header and its deletion promotes the first body row. The last body
|
|
423
|
+
row and any row's last column cannot be deleted. An index is required. A missing, non-integer, negative, or out-of-range index is
|
|
424
|
+
`INVALID_OP` on `index`, naming the supplied value and the table's actual dimensions before making any change. Markdown parsing
|
|
425
|
+
canonicalizes short ragged rows by padding missing cells, so column deletion preserves every non-selected cell in the canonical table.
|
|
426
|
+
`GET /api/v1/artifacts/<artifact UUID>/blocks` reports a table's own references plus its descendant cell anchors. A row or column
|
|
427
|
+
deletion that would remove an open ask or unresolved comment anchor is `INVALID_OP` on `index`, naming the axis and anchor ids;
|
|
428
|
+
answered asks and resolved comments are history and do not block it. A `find` or quote anchor tolerates inline Markdown
|
|
429
|
+
(`**bold**`, `` `code` ``) and a leading `# ` selects a heading by its text; a miss names the three nearest blocks so the next quote
|
|
430
|
+
lands. `replace` is inline: `with` is the new text of the matched span inside its block, so a leading list or heading
|
|
431
|
+
marker (`4. Design`, `# Title`) stays literal text and never turns the block into a list or heading; `with` that forms more than one
|
|
432
|
+
paragraph is rejected (`INVALID_OP` on `with`) — delete the block and insert new blocks instead. Use zero-based `occurrence` for a
|
|
433
|
+
repeated target; re-read a missing or ambiguous target before retrying. Pass `summary` to name the version when recording a decision.
|
|
426
434
|
|
|
427
435
|
A `delete` whose `find` is a block's entire text removes the block itself — the bullet, paragraph, or heading, not just its words — and
|
|
428
436
|
a list emptied of every item disappears with it; a partial match keeps the block with its remaining text. Deleting the text of a bullet
|
|
@@ -430,10 +438,25 @@ that holds a nested list hoists that list's items into the bullet's place (as an
|
|
|
430
438
|
(paragraphs, code, tables) is refused with `INVALID_OP` naming `delete {block:"<item id>"}`, which removes the item with its content.
|
|
431
439
|
`delete` with `block` removes any block by id (paragraph, heading, list, list item, table, or typed block; deleting an open `ask` block
|
|
432
440
|
retracts its ask, while an answered one keeps its answer as the record), and `move` with `block` relocates one, keeping its id and
|
|
433
|
-
attributes — a moved `ask` keeps its ask and answer. Block ids are the `#id` a typed block renders
|
|
434
|
-
every block including untyped ones, the `id` rows
|
|
435
|
-
each with its `type` and byte range
|
|
436
|
-
|
|
441
|
+
attributes — a moved `ask` keeps its ask and answer. Block ids are the `#id` a typed block renders
|
|
442
|
+
(`:::ask{#5467e5ce-…}`) and, for every block including untyped ones, the `id` rows from
|
|
443
|
+
`GET /api/v1/artifacts/<artifact UUID>/blocks` (or `/api/v1/issues/{key}/artifacts/{slug}/blocks`), each with its `type` and byte range
|
|
444
|
+
in canonical markdown; the UUID route does not accept a slug. A later operation in the same atomic batch that names a block removed by
|
|
445
|
+
an earlier `delete {block}` fails as `INVALID_OP` naming the earlier operation and the parent block that cascaded the removal. A move
|
|
446
|
+
whose anchor lies inside the moved block, or a delete that would leave a typed block without the body its content rule requires, is
|
|
447
|
+
`INVALID_OP` naming the field and the rule.
|
|
448
|
+
|
|
449
|
+
`GET /api/v1/artifacts/<artifact UUID>/blocks` includes a full-state `token` on every block, including
|
|
450
|
+
inline marks. To reject a stale edit, pass `precondition` with exactly one of
|
|
451
|
+
`{ document: "<token from dispatch_doc_read>" }` or
|
|
452
|
+
`{ blocks: [{ id: "<block id>", token: "<block token>" }] }`. The server resolves the whole batch before
|
|
453
|
+
mutation: a block guard must cover every content block it changes, or Dispatch returns
|
|
454
|
+
`400 INVALID_PRECONDITION` without applying anything. Use a document token for insert and move because they
|
|
455
|
+
depend on document order. A block token lets other sections change concurrently; a new anchored ask or comment
|
|
456
|
+
changes the relevant token. A stale guard returns `409 PRECONDITION_FAILED` with each mismatch and current
|
|
457
|
+
token; Dispatch applies no part of that batch. It is the hashline `#TAG` property applied to stable block ids,
|
|
458
|
+
not line numbers: canonical Markdown lines shift under concurrent edits and rendering changes, while block ids
|
|
459
|
+
survive moves and retyping.
|
|
437
460
|
|
|
438
461
|
`retype` turns the paragraph or typed block with `block` into the named typed `type` in place. It keeps the
|
|
439
462
|
block id, keeps a typed block's body, and uses `attributes` for client-owned typed attributes. Use it when
|