@sjawhar/pi-legion-envoy 5.5.6 → 5.5.7

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 - where the block holding it cannot be written without that paragraph, the replace is INVALID_OP and the refusal names the delete that removes it instead. ` + "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}`,
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.6",
16145
+ version: "5.5.7",
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 - where the block holding it cannot be written without that paragraph, the replace is INVALID_OP and the refusal names the delete that removes it instead. ` + "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}`,
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: {
@@ -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; where the block holding it cannot be written without that
70
- paragraph, the replace is `INVALID_OP`, and the refusal names the `delete` that removes it instead. Inside a code
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
- `***`). Use zero-based
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 where the block allows it, so a `replace` whose `with` you meant to fill
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, since the command
311
- reads only the newest comment). The review App can reply on a thread but cannot resolve it:
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). So the
314
- **implementer** runs `legion threads resolve --pr <number> --repo <owner>/<repo>` before every
315
- push that answers a review (the corrective push and the final `.legion/` deletion push) and
316
- pastes its output into the `Threads` section. The command resolves each unresolved thread
317
- whose newest comment is the opener's own `Accepted:` reply, one `resolveReviewThread` per
318
- thread, prints `resolved <url>` or `left open <url> — newest reply by <login> is not an acceptance`,
319
- and exits 1 naming the thread's URL and GitHub's message when GitHub refuses one; report that
320
- exit to the architect, which opens an ask for a human to resolve the thread by hand —
321
- never skip it silently. The merger runs the same command once more before publishing READY
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "5.5.6",
3
+ "version": "5.5.7",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [