@sjawhar/pi-legion-envoy 5.5.6 → 5.5.8
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
CHANGED
|
@@ -30215,7 +30215,7 @@ var dispatchToolSpecs = [
|
|
|
30215
30215
|
artifact: z2.string().describe("Project document artifact id, slug, or filename containing the quoted text; optional when ref names it.").optional(),
|
|
30216
30216
|
ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
30217
30217
|
quote: z2.string().describe("Exact document text to replace."),
|
|
30218
|
-
replace_with: z2.string().describe("Replacement text."),
|
|
30218
|
+
replace_with: z2.string().describe("Replacement text; a CR LF or a lone carriage return in it is written as a line feed."),
|
|
30219
30219
|
body: z2.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
|
|
30220
30220
|
occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
|
|
30221
30221
|
}),
|
|
@@ -30240,7 +30240,7 @@ var dispatchToolSpecs = [
|
|
|
30240
30240
|
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
30241
30241
|
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
30242
30242
|
},
|
|
30243
|
-
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 and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose -
|
|
30243
|
+
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 and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose - a list item, quote, typed block or footnote definition left holding only the emptied paragraph keeps it. ` + "with cannot open a new block: after a hard line break inside with (two trailing spaces, or a backslash, before the newline) a heading, bullet, '1.'/'1)' ordered, or '>' blockquote marker is INVALID_OP too, since that line would stay escaped text inside the matched block - use insert, plus delete for what it replaces, to add the block. A hard break in with is itself INVALID_OP when the matched text is in a heading or a table cell, which are written on one line. " + "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 result carries the document token this edit produced, so a chain of guarded edits passes each result's token as the next edit's precondition with no dispatch_doc_read between them. " + "A batch that leaves the document exactly as it was mints no version, named or not, and the result says nothing changed and names each operation that did nothing. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
30244
30244
|
arguments: (z2) => ({
|
|
30245
30245
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
30246
30246
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -30249,15 +30249,15 @@ var dispatchToolSpecs = [
|
|
|
30249
30249
|
ops: z2.array(z2.object({
|
|
30250
30250
|
op: z2.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
30251
30251
|
find: z2.string().describe("Text of the target as rendered, for replace or delete; inline markdown (**bold**, `code`) is tolerated and must be balanced; a leading '# ' matches a heading. A delete of a block's entire text removes the block.").optional(),
|
|
30252
|
-
with: z2.string().describe("Replacement text for replace: inside a code block, the code's literal text as sent (line breaks at its end do not survive a read); text that would read as block syntax at a line start, such as '---' over a paragraph, is stored escaped and reads back as those characters, so a rule is added with insert beside the paragraph; elsewhere parsed as inline markdown within the matched block; a marker of a different kind from the block's own is literal text, one of the same kind is refused unless it is a heading rename (where a level named by find is what lets with change it), a backslash escape keeps prose that merely looks like a marker, a block marker after a hard line break is refused because replace cannot open a new block, and any non-empty value that renders to no text is refused - only an empty value deletes the match.").optional(),
|
|
30252
|
+
with: z2.string().describe("Replacement text for replace: inside a code block, the code's literal text as sent (line breaks at its end do not survive a read); text that would read as block syntax at a line start, such as '---' over a paragraph, is stored escaped and reads back as those characters, so a rule is added with insert beside the paragraph; elsewhere parsed as inline markdown within the matched block; a marker of a different kind from the block's own is literal text, one of the same kind is refused unless it is a heading rename (where a level named by find is what lets with change it), a backslash escape keeps prose that merely looks like a marker, a block marker after a hard line break is refused because replace cannot open a new block, and any non-empty value that renders to no text is refused - only an empty value deletes the match. A CR LF or a lone carriage return in it is written as a line feed.").optional(),
|
|
30253
30253
|
occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based match occurrence.").optional(),
|
|
30254
|
-
markdown: z2.string().describe("Markdown to insert.").optional(),
|
|
30254
|
+
markdown: z2.string().describe("Markdown to insert; a CR LF or a lone carriage return in it is written as a line feed.").optional(),
|
|
30255
30255
|
after: z2.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(),
|
|
30256
30256
|
before: z2.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(),
|
|
30257
30257
|
block: z2.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(),
|
|
30258
30258
|
index: z2.number({ int: true, min: 0 }).describe("Zero-based row or column index for delete_row or delete_column.").optional(),
|
|
30259
30259
|
type: z2.string().describe("Typed block name for retype.").optional(),
|
|
30260
|
-
attributes: z2.unknown().describe("Typed block attributes for retype.").optional()
|
|
30260
|
+
attributes: z2.unknown().describe("Typed block attributes for retype; a CR LF or a lone carriage return in a string value is written as a line feed.").optional()
|
|
30261
30261
|
}, { strict: true })).describe("Flat tagged edits; an operation takes only the keys below, a misspelled one is refused rather than ignored, and the server validates the fields its op requires."),
|
|
30262
30262
|
precondition: z2.object({
|
|
30263
30263
|
document: z2.string({ min: 1 }).describe("Token for the exact canonical document returned by dispatch_doc_read.").optional(),
|
|
@@ -30304,7 +30304,7 @@ var dispatchToolSpecs = [
|
|
|
30304
30304
|
project: z2.string().describe("Project key for an unlinked document.").optional(),
|
|
30305
30305
|
name: z2.string().describe("Artifact filename shown in Dispatch."),
|
|
30306
30306
|
path: z2.string().describe("Local path to the file to upload.").optional(),
|
|
30307
|
-
content: z2.string().describe("Inline text to store as a Markdown document.").optional(),
|
|
30307
|
+
content: z2.string().describe("Inline text to store as a Markdown document; a CR LF or a lone carriage return in it is stored as a line feed.").optional(),
|
|
30308
30308
|
summary: z2.string().describe("Optional version summary.").optional()
|
|
30309
30309
|
}),
|
|
30310
30310
|
validation: {
|
package/dist/legion.js
CHANGED
|
@@ -16142,7 +16142,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
16142
16142
|
// package.json
|
|
16143
16143
|
var package_default = {
|
|
16144
16144
|
name: "@sjawhar/pi-legion-envoy",
|
|
16145
|
-
version: "5.5.
|
|
16145
|
+
version: "5.5.8",
|
|
16146
16146
|
type: "module",
|
|
16147
16147
|
omp: {
|
|
16148
16148
|
extensions: [
|
|
@@ -30288,7 +30288,7 @@ var dispatchToolSpecs = [
|
|
|
30288
30288
|
artifact: z2.string().describe("Project document artifact id, slug, or filename containing the quoted text; optional when ref names it.").optional(),
|
|
30289
30289
|
ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
30290
30290
|
quote: z2.string().describe("Exact document text to replace."),
|
|
30291
|
-
replace_with: z2.string().describe("Replacement text."),
|
|
30291
|
+
replace_with: z2.string().describe("Replacement text; a CR LF or a lone carriage return in it is written as a line feed."),
|
|
30292
30292
|
body: z2.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
|
|
30293
30293
|
occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
|
|
30294
30294
|
}),
|
|
@@ -30313,7 +30313,7 @@ var dispatchToolSpecs = [
|
|
|
30313
30313
|
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
30314
30314
|
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
30315
30315
|
},
|
|
30316
|
-
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 and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose -
|
|
30316
|
+
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 and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose - a list item, quote, typed block or footnote definition left holding only the emptied paragraph keeps it. ` + "with cannot open a new block: after a hard line break inside with (two trailing spaces, or a backslash, before the newline) a heading, bullet, '1.'/'1)' ordered, or '>' blockquote marker is INVALID_OP too, since that line would stay escaped text inside the matched block - use insert, plus delete for what it replaces, to add the block. A hard break in with is itself INVALID_OP when the matched text is in a heading or a table cell, which are written on one line. " + "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 result carries the document token this edit produced, so a chain of guarded edits passes each result's token as the next edit's precondition with no dispatch_doc_read between them. " + "A batch that leaves the document exactly as it was mints no version, named or not, and the result says nothing changed and names each operation that did nothing. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
30317
30317
|
arguments: (z2) => ({
|
|
30318
30318
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
30319
30319
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -30322,15 +30322,15 @@ var dispatchToolSpecs = [
|
|
|
30322
30322
|
ops: z2.array(z2.object({
|
|
30323
30323
|
op: z2.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
30324
30324
|
find: z2.string().describe("Text of the target as rendered, for replace or delete; inline markdown (**bold**, `code`) is tolerated and must be balanced; a leading '# ' matches a heading. A delete of a block's entire text removes the block.").optional(),
|
|
30325
|
-
with: z2.string().describe("Replacement text for replace: inside a code block, the code's literal text as sent (line breaks at its end do not survive a read); text that would read as block syntax at a line start, such as '---' over a paragraph, is stored escaped and reads back as those characters, so a rule is added with insert beside the paragraph; elsewhere parsed as inline markdown within the matched block; a marker of a different kind from the block's own is literal text, one of the same kind is refused unless it is a heading rename (where a level named by find is what lets with change it), a backslash escape keeps prose that merely looks like a marker, a block marker after a hard line break is refused because replace cannot open a new block, and any non-empty value that renders to no text is refused - only an empty value deletes the match.").optional(),
|
|
30325
|
+
with: z2.string().describe("Replacement text for replace: inside a code block, the code's literal text as sent (line breaks at its end do not survive a read); text that would read as block syntax at a line start, such as '---' over a paragraph, is stored escaped and reads back as those characters, so a rule is added with insert beside the paragraph; elsewhere parsed as inline markdown within the matched block; a marker of a different kind from the block's own is literal text, one of the same kind is refused unless it is a heading rename (where a level named by find is what lets with change it), a backslash escape keeps prose that merely looks like a marker, a block marker after a hard line break is refused because replace cannot open a new block, and any non-empty value that renders to no text is refused - only an empty value deletes the match. A CR LF or a lone carriage return in it is written as a line feed.").optional(),
|
|
30326
30326
|
occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based match occurrence.").optional(),
|
|
30327
|
-
markdown: z2.string().describe("Markdown to insert.").optional(),
|
|
30327
|
+
markdown: z2.string().describe("Markdown to insert; a CR LF or a lone carriage return in it is written as a line feed.").optional(),
|
|
30328
30328
|
after: z2.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(),
|
|
30329
30329
|
before: z2.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(),
|
|
30330
30330
|
block: z2.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(),
|
|
30331
30331
|
index: z2.number({ int: true, min: 0 }).describe("Zero-based row or column index for delete_row or delete_column.").optional(),
|
|
30332
30332
|
type: z2.string().describe("Typed block name for retype.").optional(),
|
|
30333
|
-
attributes: z2.unknown().describe("Typed block attributes for retype.").optional()
|
|
30333
|
+
attributes: z2.unknown().describe("Typed block attributes for retype; a CR LF or a lone carriage return in a string value is written as a line feed.").optional()
|
|
30334
30334
|
}, { strict: true })).describe("Flat tagged edits; an operation takes only the keys below, a misspelled one is refused rather than ignored, and the server validates the fields its op requires."),
|
|
30335
30335
|
precondition: z2.object({
|
|
30336
30336
|
document: z2.string({ min: 1 }).describe("Token for the exact canonical document returned by dispatch_doc_read.").optional(),
|
|
@@ -30377,7 +30377,7 @@ var dispatchToolSpecs = [
|
|
|
30377
30377
|
project: z2.string().describe("Project key for an unlinked document.").optional(),
|
|
30378
30378
|
name: z2.string().describe("Artifact filename shown in Dispatch."),
|
|
30379
30379
|
path: z2.string().describe("Local path to the file to upload.").optional(),
|
|
30380
|
-
content: z2.string().describe("Inline text to store as a Markdown document.").optional(),
|
|
30380
|
+
content: z2.string().describe("Inline text to store as a Markdown document; a CR LF or a lone carriage return in it is stored as a line feed.").optional(),
|
|
30381
30381
|
summary: z2.string().describe("Optional version summary.").optional()
|
|
30382
30382
|
}),
|
|
30383
30383
|
validation: {
|
|
@@ -571,6 +571,10 @@ nothing and the document is intact. Nothing retries it for you: the Dispatch cli
|
|
|
571
571
|
Wait a few seconds and make the same call again. A second refusal in a row is worth telling your human about,
|
|
572
572
|
with the document's reference.
|
|
573
573
|
|
|
574
|
+
`dispatch_doc_read` can answer it too, though it writes nothing: a read never opens a live room, and waits out
|
|
575
|
+
a room that is reloading, so it is refused only when the document's durable copy cannot be read or decoded.
|
|
576
|
+
Retry it the same way.
|
|
577
|
+
|
|
574
578
|
## Typed blocks
|
|
575
579
|
|
|
576
580
|
The server declares typed document blocks at `GET /api/v1/schema/blocks`. Write one only with the
|
|
@@ -66,15 +66,17 @@ heading's actual level — `find="## Old"`, `with="### New"` retitles and makes
|
|
|
66
66
|
so `find="# Old"` renames the text and keeps whatever level it selected. `with` that forms more than one
|
|
67
67
|
paragraph is rejected (`INVALID_OP` on `with`) — see the recipe for a multi-paragraph rewrite below; so is any non-empty `with` that
|
|
68
68
|
renders to no text, which a line indented four spaces or a tab does (markdown reads that as a code block), as does whitespace
|
|
69
|
-
alone. An empty `with` deletes the matched text on purpose;
|
|
70
|
-
|
|
69
|
+
alone. An empty `with` deletes the matched text on purpose; a list item, quote, typed block or footnote definition left
|
|
70
|
+
holding only the emptied paragraph keeps it, and reads back holding it. Emptying a task item's first paragraph while
|
|
71
|
+
another block follows it in the item is `INVALID_OP`, since the browser editor reads no such item as a task. Inside a code
|
|
71
72
|
block none of this applies: `with` is the code's literal text, written as sent, whitespace, markdown syntax and
|
|
72
73
|
references included, except that line breaks at the end of the code's text, and a line holding only whitespace in a
|
|
73
74
|
list item's code, do not survive the next read. A line of colons in code inside a typed block is kept: Dispatch writes
|
|
74
75
|
that typed block's fence longer than any such line the browser editor would end it at. Text a `replace` writes that
|
|
75
76
|
would read as block syntax at a line start is stored escaped and reads back as the characters you sent: `---` over a
|
|
76
77
|
paragraph is stored `\---`, not a rule, so to add a rule, `insert` it beside the paragraph (`insert` with markdown
|
|
77
|
-
`***`).
|
|
78
|
+
`***`). A CR LF or a lone carriage return in any text you write - an insert's markdown, a `with` in text or in code,
|
|
79
|
+
a suggestion, an upload or a spec - is stored as a line feed, as the browser editor reads both. Use zero-based
|
|
78
80
|
`occurrence` for a
|
|
79
81
|
repeated target; re-read a missing or ambiguous target before retrying. Pass `summary` to name the version when recording a decision.
|
|
80
82
|
|
|
@@ -115,7 +117,7 @@ block before or after it. The delete is what costs the id (below); a typed block
|
|
|
115
117
|
carries it, which works only in that order, because an insert carrying an id the document still holds is refused.
|
|
116
118
|
Then read the document back with
|
|
117
119
|
`dispatch_doc_read` and read the passage and its neighbours, not a grep for the words you added: an empty
|
|
118
|
-
`with` deletes the matched text on purpose
|
|
120
|
+
`with` deletes the matched text on purpose, so a `replace` whose `with` you meant to fill
|
|
119
121
|
empties that paragraph — the block and its id stay, holding nothing — and only a read shows what the document now says.
|
|
120
122
|
|
|
121
123
|
A batch that leaves the document's semantic identity unchanged — including its inline anchor marks, so an edit that only orphans a
|
|
@@ -307,18 +307,46 @@ this proof.
|
|
|
307
307
|
reviewer answers each thread it opened with exactly one of `Accepted: fixed in <commit> — <one line>`,
|
|
308
308
|
`Accepted: not a defect — <reason>`, or `Still open: <what remains>`; nothing else is an
|
|
309
309
|
acceptance, and nobody replies after an `Accepted:` (any later reply that is not itself an
|
|
310
|
-
`Accepted:` — the opener's own follow-up included — leaves the thread open,
|
|
311
|
-
|
|
310
|
+
`Accepted:` — the opener's own follow-up included — leaves the thread open, because resolution
|
|
311
|
+
considers only the newest comment). The review App can reply on a thread but cannot resolve it:
|
|
312
312
|
GitHub grants resolving a review thread to the pull request's author, and the implementer opens
|
|
313
|
-
every Legion pull request (`packages/daemon/src/daemon/AGENTS.md`, GitHub Apps).
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
313
|
+
every Legion pull request (`packages/daemon/src/daemon/AGENTS.md`, GitHub Apps).
|
|
314
|
+
When `LEGION_GRANT_FILE` or `LEGION_GRANT` is set, use `legion threads resolve --pr <number> --repo <owner>/<repo>`; when neither is set, use `gh api graphql`
|
|
315
|
+
with the session's GitHub credential and the fallback below.
|
|
316
|
+
In a Legion pane, the **implementer** runs the command before every push that answers a review
|
|
317
|
+
(the corrective push and the final `.legion/` deletion push) and pastes its output into the
|
|
318
|
+
`Threads` section. The command resolves each unresolved thread whose newest submitted comment is
|
|
319
|
+
the opener's own `Accepted:` reply, one `resolveReviewThread` per thread, prints `resolved <url>`
|
|
320
|
+
or `left open <url> — newest reply by <login> is not an acceptance`, and exits 1 naming the
|
|
321
|
+
thread's URL and GitHub's message when GitHub refuses one.
|
|
322
|
+
|
|
323
|
+
Without a grant, page through `reviewThreads`, skip `isResolved: true`, and compare the opener
|
|
324
|
+
with the newest comment. Query shape, inside `repository { pullRequest { … } }`:
|
|
325
|
+
|
|
326
|
+
```graphql
|
|
327
|
+
reviewThreads(first: 100, after: $after) {
|
|
328
|
+
pageInfo { hasNextPage endCursor }
|
|
329
|
+
nodes {
|
|
330
|
+
id isResolved
|
|
331
|
+
opener: comments(first: 1) { nodes { author { login } } }
|
|
332
|
+
newest: comments(last: 1) { nodes { author { login } body state } }
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Resolve only when the newest comment is submitted, its `author { login }` equals the opener's,
|
|
338
|
+
and its `body`, after removing leading spaces, tabs, CR, and LF, begins `Accepted:`. For each
|
|
339
|
+
such thread:
|
|
340
|
+
|
|
341
|
+
```graphql
|
|
342
|
+
mutation($threadId: ID!) {
|
|
343
|
+
resolveReviewThread(input: { threadId: $threadId }) { thread { isResolved } }
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Re-read `reviewThreads` and confirm that thread's `isResolved` is true. In either route, report
|
|
348
|
+
a refused resolution to the architect, which opens an ask for a human to resolve the thread by
|
|
349
|
+
hand — never skip it silently. The merger runs the command once more before publishing READY
|
|
322
350
|
and does not publish while any `left open` line remains.
|
|
323
351
|
- **Correctness fixes land in this PR; cleanup is one named fast-follow.** A finding that
|
|
324
352
|
changes behavior, hides an error, or breaks a gate is fixed here — never deferred.
|