@sjawhar/pi-legion-envoy 5.3.3 → 5.3.5
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/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.3.
|
|
16145
|
+
version: "5.3.5",
|
|
16146
16146
|
type: "module",
|
|
16147
16147
|
omp: {
|
|
16148
16148
|
extensions: [
|
|
@@ -298,10 +298,22 @@ production import). Every `dispatch_ask` passes four gates first:
|
|
|
298
298
|
deletion or exposure of production data, anything that reaches a customer). The PO takes those
|
|
299
299
|
to Sami as a Dispatch ask; you do not open one yourself, even as a permission ask under gate 2
|
|
300
300
|
(Sami, 2026-09-25, AGENTC-34 §12).
|
|
301
|
-
2. **Is there genuine uncertainty
|
|
302
|
-
|
|
303
|
-
write, an external send, a console action — and then
|
|
304
|
-
sentence, with options that name its outcomes (below).
|
|
301
|
+
2. **Is there genuine uncertainty, and have you measured what you can?** If there is none, it is
|
|
302
|
+
a plan you execute. The one legitimate ask without uncertainty is permission for an action
|
|
303
|
+
only a human can authorise — a production write, an external send, a console action — and then
|
|
304
|
+
the question is that action in one sentence, with options that name its outcomes (below).
|
|
305
|
+
Measure before you write: how many are affected, whether anything reaches the path, what the
|
|
306
|
+
current state already is. The measurement decides whether a human is needed at all, and when
|
|
307
|
+
one is, it turns a research request he cannot answer into a decision he can — an ask whose
|
|
308
|
+
lead was "accept this or build a workaround", with nobody knowing whether anyone was affected,
|
|
309
|
+
was unanswerable until a measurement showed the usage could not be observed at all; the same
|
|
310
|
+
ask, carrying that and the size of the affected population, was answered at once, and another
|
|
311
|
+
lost an option outright when the measurement showed it could not repair most of the affected.
|
|
312
|
+
Report what the measurement could **not** establish, with its own control: "I found no
|
|
313
|
+
evidence" and "there is no evidence to find" read alike and mean opposite things, and a
|
|
314
|
+
control that shares the query's blind spot proves neither. Before you say you are waiting on
|
|
315
|
+
him, run `dispatch_open_asks` (below) — and the test for a new ask is not whether you asked on
|
|
316
|
+
this issue before, but whether it asks him to re-report something he has already answered.
|
|
305
317
|
3. **Can someone who has not read the code answer it on a phone?** Write it as
|
|
306
318
|
[Writing for the human](#writing-for-the-human) says — who can do what today and what changes
|
|
307
319
|
for them, then two options with what each costs and your recommendation — and no slice or
|
|
@@ -315,10 +327,10 @@ production import). Every `dispatch_ask` passes four gates first:
|
|
|
315
327
|
it nor build it — fixing the cause of the thing he named is delivering what he asked for, and
|
|
316
328
|
is not what this forbids; changing something else is. An ask that turns his complaint about
|
|
317
329
|
one control into a choice about another does not address what he asked, and changing that
|
|
318
|
-
other control is a change he never asked for.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
330
|
+
other control is a change he never asked for. What is still an ask the moment you know it,
|
|
331
|
+
even before delivery, is anything "Anything that needs the human is an ask" (further down)
|
|
332
|
+
lists that the delivery waits on — including a conflict between what he asked for and another
|
|
333
|
+
of his rules, which this gate would otherwise bury as settled.
|
|
322
334
|
|
|
323
335
|
The platform PO audits open asks. One that fails a gate — or that points at another message in
|
|
324
336
|
prose instead of carrying its content (below) — is retracted, with the PO's answer as the record.
|
|
@@ -541,124 +553,11 @@ It returns live or versioned markdown with open marks. A live read ends with a d
|
|
|
541
553
|
omitted `artifact` reads the issue specification; a project needs `artifact`; and a
|
|
542
554
|
`dispatch://PROJECT/artifact/<document-ref>` ref supplies both, where `document-ref` is the id, slug, or filename.
|
|
543
555
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
It returns issue or project-document owner details plus `applied`, optional `version`, `changed`, and
|
|
548
|
-
`unchanged_ops`. `ops` is an array of this exact `EditOp` shape:
|
|
549
|
-
|
|
550
|
-
```ts
|
|
551
|
-
type EditOp = {
|
|
552
|
-
op: "replace" | "delete" | "insert" | "retype" | "move" | "delete_row" | "delete_column";
|
|
553
|
-
find?: string;
|
|
554
|
-
with?: string;
|
|
555
|
-
occurrence?: number;
|
|
556
|
-
markdown?: string;
|
|
557
|
-
after?: string;
|
|
558
|
-
before?: string;
|
|
559
|
-
block?: string;
|
|
560
|
-
index?: number;
|
|
561
|
-
type?: string;
|
|
562
|
-
attributes?: Record<string, unknown>;
|
|
563
|
-
};
|
|
564
|
-
```
|
|
556
|
+
Editing one is [Editing a document](references/document-edits.md): the shape of `dispatch_doc_edit`,
|
|
557
|
+
how to quote the text you mean, one `replace` per paragraph, preconditions against a stale edit, and
|
|
558
|
+
what each operation costs a block's id and its anchors.
|
|
565
559
|
|
|
566
|
-
|
|
567
|
-
your process, naming the operation and its keys, because every key but `op` is optional: a
|
|
568
|
-
misspelled `with` would otherwise be dropped and the `replace` would delete the text you meant to
|
|
569
|
-
rewrite.
|
|
570
|
-
|
|
571
|
-
Target `replace`, `delete`, and quote insert anchors by a block's text as rendered: write inline
|
|
572
|
-
code without backticks, bold without asterisks, and link text without link syntax. A table-cell
|
|
573
|
-
anchor is its cell text. Quote code-block contents without their Markdown fences. A quote must stay
|
|
574
|
-
within one textblock; split changes that span separate blocks into separate operations.
|
|
575
|
-
|
|
576
|
-
`replace` requires `find` and `with`; `delete` requires `find` or `block`; `insert` requires `markdown` and exactly one of `after` or
|
|
577
|
-
`before`; `move` requires `block` and exactly one of `after` or `before`; and `delete_row` / `delete_column` each require a table
|
|
578
|
-
`block` plus a zero-based `index`. An insert or move anchor is a quote, `"start"`, `"end"`, `"heading:Title"`, or `"block:<id>"`.
|
|
579
|
-
Ordinary inserts create a sibling block before or after the quote, heading, or block's enclosing document block, and a move lands the
|
|
580
|
-
block at that same boundary; `"start"` and `"end"` select the document edges. At a table-cell quote, a body-row fragment (no header or
|
|
581
|
-
delimiter rows) extends that table before or after the matched row instead; short rows are padded, wider rows are rejected, and deleting
|
|
582
|
-
a cell's quoted text removes only that text. `delete_row` / `delete_column` instead mutate their named table in place, keeping the
|
|
583
|
-
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
|
|
584
|
-
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
|
|
585
|
-
`INVALID_OP` on `index`, naming the supplied value and the table's actual dimensions before making any change. Markdown parsing
|
|
586
|
-
canonicalizes short ragged rows by padding missing cells, so column deletion preserves every non-selected cell in the canonical table.
|
|
587
|
-
`GET /api/v1/artifacts/<artifact UUID>/blocks` reports a table's own references plus its descendant cell anchors. A row or column
|
|
588
|
-
deletion that would remove an open ask or unresolved comment anchor is `INVALID_OP` on `index`, naming the axis and anchor ids;
|
|
589
|
-
answered asks and resolved comments are history and do not block it. A `find` or quote anchor tolerates inline Markdown
|
|
590
|
-
(`**bold**`, `` `code` ``) and a leading `# ` selects a heading by its text; a miss names the quote and the three nearest blocks so
|
|
591
|
-
the next quote lands, and a `find` cut before a closing `**` or `` ` `` is refused as an unbalanced inline mark rather than reported
|
|
592
|
-
as a miss. A `heading:` anchor matches the whole heading text exactly — a prefix of a longer heading is a miss, naming the anchor and
|
|
593
|
-
the nearest headings. `replace` is inline: `with` is the new text of the matched span inside its block, so a marker of a *different*
|
|
594
|
-
kind from the block's own (`4. Design` written into a heading, `# Title` into a paragraph) stays literal text and never turns the
|
|
595
|
-
block into a list or heading. A `with` that opens with a marker of the *same* kind as the matched block's own would write it twice and
|
|
596
|
-
is rejected (`INVALID_OP` on `with`) — including prose that merely looks like a marker (`1999. was a year` into an ordered item),
|
|
597
|
-
which is written as text with a backslash escape (`1999\. was a year`) — omit the marker to replace the block's text, or use `insert`
|
|
598
|
-
plus `delete` to change the block's kind, level or number. The one exception is a heading rename whose `find` carried a heading
|
|
599
|
-
marker: `replace(find="## Old", with="## New")` gives `## New`. A different level in `with` applies only when `find` named the
|
|
600
|
-
heading's actual level — `find="## Old"`, `with="### New"` retitles and makes it an h3 — because `# ` is the level-blind selector,
|
|
601
|
-
so `find="# Old"` renames the text and keeps whatever level it selected. `with` that forms more than one
|
|
602
|
-
paragraph is rejected (`INVALID_OP` on `with`) — see the recipe for a multi-paragraph rewrite below; so is any non-empty `with` that
|
|
603
|
-
renders to no text, which a line indented four spaces or a tab does (markdown reads that as a code block), as does whitespace
|
|
604
|
-
alone. An empty `with` is the one that deletes the matched text on purpose. Use zero-based `occurrence` for a
|
|
605
|
-
repeated target; re-read a missing or ambiguous target before retrying. Pass `summary` to name the version when recording a decision.
|
|
606
|
-
|
|
607
|
-
**Rewriting several paragraphs is one `replace` per paragraph, then a read-back.** `replace` is inline:
|
|
608
|
-
each `with` is the new text of one paragraph, and a `with` that forms two paragraphs is refused whatever the
|
|
609
|
-
text says. Give each paragraph you rewrite its own `replace`, which keeps that paragraph's block id and every
|
|
610
|
-
anchor outside the text you rewrite. A comment or ask anchored to the text you rewrite loses its quote but keeps
|
|
611
|
-
its pin to the block, so the dashboard still shows it beside that paragraph; a delete (below) loses both. When
|
|
612
|
-
the new text has more paragraphs than the old, `insert` the extra ones
|
|
613
|
-
with `after` quoting the last paragraph you rewrote exactly as it now reads (the operations in one batch
|
|
614
|
-
apply in order); they land after the top-level block that holds the quote, so beside a paragraph inside a
|
|
615
|
-
list item or a typed block they go after the whole list or block. When it has fewer, `delete` each leftover
|
|
616
|
-
paragraph, with its whole text as `find` or its id as `block`. All of that holds while the new text is
|
|
617
|
-
paragraphs: `replace` keeps a block's kind, so a heading, list, table or code fence cannot be replaced into place —
|
|
618
|
-
a heading, list or table marker is written as literal text, and a code fence becomes an inline code span with the
|
|
619
|
-
fence gone. When the new text adds one beside paragraphs, `insert` it beside the paragraph you replaced, which keeps
|
|
620
|
-
that paragraph's id; only when no paragraph of the new text is left to take the old block's place is it an `insert`
|
|
621
|
-
of the new block plus a `delete` of the old, and a delete is what costs a block its id. Then read the document back with
|
|
622
|
-
`dispatch_doc_read` and read the passage and its neighbours, not a grep for the words you added: an empty
|
|
623
|
-
`with` deletes the matched text on purpose, so a `replace` whose `with` you meant to fill empties that
|
|
624
|
-
paragraph — the block and its id stay, holding nothing — and only a read shows what the document now says.
|
|
625
|
-
|
|
626
|
-
A batch that leaves the document's semantic identity unchanged — including its inline anchor marks, so an edit that only orphans a
|
|
627
|
-
comment or ask anchor still mints its version — mints no version, named or not: the response carries
|
|
628
|
-
`changed: false` with `unchanged_ops` naming each operation that did nothing, and the tool result says nothing changed. A `summary`
|
|
629
|
-
does not force a version for such a batch; `POST /api/v1/artifacts/<id>/versions`, which names the current state on purpose, still does.
|
|
630
|
-
|
|
631
|
-
A `delete` whose `find` is a block's entire text removes the block itself — the bullet, paragraph, or heading, not just its words — and
|
|
632
|
-
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
|
|
633
|
-
that holds a nested list hoists that list's items into the bullet's place (as an outliner does); a bullet with any other content
|
|
634
|
-
(paragraphs, code, tables) is refused with `INVALID_OP` naming `delete {block:"<item id>"}`, which removes the item with its content.
|
|
635
|
-
`delete` with `block` removes any block by id (paragraph, heading, list, list item, table, or typed block; deleting an open `ask` block
|
|
636
|
-
retracts its ask, while an answered one keeps its answer as the record), and `move` with `block` relocates one, keeping its id and
|
|
637
|
-
attributes — a moved `ask` keeps its ask and answer. Block ids are the `#id` a typed block renders
|
|
638
|
-
(`:::ask{#5467e5ce-…}`) and, for every block including untyped ones, the `id` rows from
|
|
639
|
-
`GET /api/v1/artifacts/<artifact UUID>/blocks` (or `/api/v1/issues/{key}/artifacts/{slug}/blocks`), each with its `type` and byte range
|
|
640
|
-
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
|
|
641
|
-
an earlier `delete {block}` fails as `INVALID_OP` naming the earlier operation and the parent block that cascaded the removal. A move
|
|
642
|
-
whose anchor lies inside the moved block, or a delete that would leave a typed block without the body its content rule requires, is
|
|
643
|
-
`INVALID_OP` naming the field and the rule.
|
|
644
|
-
|
|
645
|
-
`GET /api/v1/artifacts/<artifact UUID>/blocks` includes a full-state `token` on every block, including
|
|
646
|
-
inline marks. To reject a stale edit, pass `precondition` with exactly one of
|
|
647
|
-
`{ document: "<token from dispatch_doc_read>" }` or
|
|
648
|
-
`{ blocks: [{ id: "<block id>", token: "<block token>" }] }`. The server resolves the whole batch before
|
|
649
|
-
mutation: a block guard must cover every content block it changes, or Dispatch returns
|
|
650
|
-
`400 INVALID_PRECONDITION` without applying anything. Use a document token for insert and move because they
|
|
651
|
-
depend on document order. A block token lets other sections change concurrently; a new anchored ask or comment
|
|
652
|
-
changes the relevant token. A stale guard returns `409 PRECONDITION_FAILED` with each mismatch and current
|
|
653
|
-
token; Dispatch applies no part of that batch. It is the hashline `#TAG` property applied to stable block ids,
|
|
654
|
-
not line numbers: canonical Markdown lines shift under concurrent edits and rendering changes, while block ids
|
|
655
|
-
survive moves and retyping.
|
|
656
|
-
|
|
657
|
-
`retype` turns the paragraph or typed block with `block` into the named typed `type` in place. It keeps the
|
|
658
|
-
block id, keeps a typed block's body, and uses `attributes` for client-owned typed attributes. Use it when
|
|
659
|
-
an existing paragraph is the question that should become a decision.
|
|
660
|
-
|
|
661
|
-
### A document that is reloading
|
|
560
|
+
## A document that is reloading
|
|
662
561
|
|
|
663
562
|
These calls can answer `DOC_SERVICE_UNAVAILABLE` (HTTP 503), because each writes a document inside its
|
|
664
563
|
transaction: `dispatch_doc_edit`; `dispatch_ask` and `dispatch_comment` on a quote; a `dispatch_comment` reply
|
|
@@ -678,7 +577,10 @@ The server declares typed document blocks at `GET /api/v1/schema/blocks`. Write
|
|
|
678
577
|
container-directive form `:::name{#block-id key="value"}` on its own line, ordinary block children,
|
|
679
578
|
and a closing `:::` at the same nesting. An unclosed typed block at document level is rejected. For
|
|
680
579
|
a new typed block, omit `#block-id`; Dispatch mints it. When editing an existing typed block, retain
|
|
681
|
-
its id and every rendered attribute.
|
|
580
|
+
its id and every rendered attribute. Never copy an existing block's id into new markdown: an id
|
|
581
|
+
names one block, so an insert, upload or suggestion whose markdown names an id the document holds
|
|
582
|
+
outside the text it replaces is refused naming the id: `INVALID_OP` for an insert,
|
|
583
|
+
`INVALID_MARKDOWN` for any other write.
|
|
682
584
|
|
|
683
585
|
Use only the type names, content rule, attributes, and enum values returned by the schema. Values are
|
|
684
586
|
quoted: `:::callout{kind="warning" title="Risk"}`. Do not write Pandoc-style `::: {.callout}`, leaf
|
|
@@ -769,7 +671,7 @@ dispatch_artifact({ issue?, project?, name: "load-test-results.md", content: "#
|
|
|
769
671
|
Exactly one of `issue` and `project` is required. A project upload creates an unlinked project document; it must not include `artifact`.
|
|
770
672
|
Exactly one of `path` and `content` is required. It returns issue or project-document owner details plus `artifact` and `version`.
|
|
771
673
|
Uploading the same `name` creates its next version — so uploading `spec.md` **replaces the issue's own specification**
|
|
772
|
-
with your text. Never do that: the spec is edited in place with `dispatch_doc_edit` (see [
|
|
674
|
+
with your text. Never do that: the spec is edited in place with `dispatch_doc_edit` (see [Editing a document](references/document-edits.md)). Address an existing
|
|
773
675
|
artifact by the slug shown in the upload result or by its filename, and a project document by its artifact id, slug, or filename; the
|
|
774
676
|
slug also arrives on `artifact.created` events.
|
|
775
677
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Editing a document
|
|
2
|
+
|
|
3
|
+
Every document a Dispatch tool writes — an issue's spec, a project document — is edited in place
|
|
4
|
+
with `dispatch_doc_edit`, never re-uploaded. [The Spec](../SKILL.md#the-spec) sends you here for
|
|
5
|
+
the tool's shape, how to target the text you mean, what each operation costs a block, and how to
|
|
6
|
+
reject a stale edit.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
dispatch_doc_edit({ issue?, project?, artifact, ops, precondition?, summary? })
|
|
10
|
+
```
|
|
11
|
+
It returns issue or project-document owner details plus `applied`, optional `version`, `changed`, and
|
|
12
|
+
`unchanged_ops`. `ops` is an array of this exact `EditOp` shape:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
type EditOp = {
|
|
16
|
+
op: "replace" | "delete" | "insert" | "retype" | "move" | "delete_row" | "delete_column";
|
|
17
|
+
find?: string;
|
|
18
|
+
with?: string;
|
|
19
|
+
occurrence?: number;
|
|
20
|
+
markdown?: string;
|
|
21
|
+
after?: string;
|
|
22
|
+
before?: string;
|
|
23
|
+
block?: string;
|
|
24
|
+
index?: number;
|
|
25
|
+
type?: string;
|
|
26
|
+
attributes?: Record<string, unknown>;
|
|
27
|
+
};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
An operation takes only these keys. A key it does not declare is refused before the call leaves
|
|
31
|
+
your process, naming the operation and its keys, because every key but `op` is optional: a
|
|
32
|
+
misspelled `with` would otherwise be dropped and the `replace` would delete the text you meant to
|
|
33
|
+
rewrite.
|
|
34
|
+
|
|
35
|
+
Target `replace`, `delete`, and quote insert anchors by a block's text as rendered: write inline
|
|
36
|
+
code without backticks, bold without asterisks, and link text without link syntax. A table-cell
|
|
37
|
+
anchor is its cell text. Quote code-block contents without their Markdown fences. A quote must stay
|
|
38
|
+
within one textblock; split changes that span separate blocks into separate operations.
|
|
39
|
+
|
|
40
|
+
`replace` requires `find` and `with`; `delete` requires `find` or `block`; `insert` requires `markdown` and exactly one of `after` or
|
|
41
|
+
`before`; `move` requires `block` and exactly one of `after` or `before`; and `delete_row` / `delete_column` each require a table
|
|
42
|
+
`block` plus a zero-based `index`. An insert or move anchor is a quote, `"start"`, `"end"`, `"heading:Title"`, or `"block:<id>"`.
|
|
43
|
+
Ordinary inserts create a sibling block before or after the quote, heading, or block's enclosing document block, and a move lands the
|
|
44
|
+
block at that same boundary; `"start"` and `"end"` select the document edges. At a table-cell quote, a body-row fragment (no header or
|
|
45
|
+
delimiter rows) extends that table before or after the matched row instead; short rows are padded, wider rows are rejected, and deleting
|
|
46
|
+
a cell's quoted text removes only that text. `delete_row` / `delete_column` instead mutate their named table in place, keeping the
|
|
47
|
+
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
|
|
48
|
+
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
|
|
49
|
+
`INVALID_OP` on `index`, naming the supplied value and the table's actual dimensions before making any change. Markdown parsing
|
|
50
|
+
canonicalizes short ragged rows by padding missing cells, so column deletion preserves every non-selected cell in the canonical table.
|
|
51
|
+
`GET /api/v1/artifacts/<artifact UUID>/blocks` reports a table's own references plus its descendant cell anchors. A row or column
|
|
52
|
+
deletion that would remove an open ask or unresolved comment anchor is `INVALID_OP` on `index`, naming the axis and anchor ids;
|
|
53
|
+
answered asks and resolved comments are history and do not block it. A `find` or quote anchor tolerates inline Markdown
|
|
54
|
+
(`**bold**`, `` `code` ``) and a leading `# ` selects a heading by its text; a miss names the quote and the three nearest blocks so
|
|
55
|
+
the next quote lands, and a `find` cut before a closing `**` or `` ` `` is refused as an unbalanced inline mark rather than reported
|
|
56
|
+
as a miss. A `heading:` anchor matches the whole heading text exactly — a prefix of a longer heading is a miss, naming the anchor and
|
|
57
|
+
the nearest headings. `replace` is inline: `with` is the new text of the matched span inside its block, so a marker of a *different*
|
|
58
|
+
kind from the block's own (`4. Design` written into a heading, `# Title` into a paragraph) stays literal text and never turns the
|
|
59
|
+
block into a list or heading. A `with` that opens with a marker of the *same* kind as the matched block's own would write it twice and
|
|
60
|
+
is rejected (`INVALID_OP` on `with`) — including prose that merely looks like a marker (`1999. was a year` into an ordered item),
|
|
61
|
+
which is written as text with a backslash escape (`1999\. was a year`) — omit the marker to replace the block's text, or use `insert`
|
|
62
|
+
plus `delete` to change the block's kind, level or number. The one exception is a heading rename whose `find` carried a heading
|
|
63
|
+
marker: `replace(find="## Old", with="## New")` gives `## New`. A different level in `with` applies only when `find` named the
|
|
64
|
+
heading's actual level — `find="## Old"`, `with="### New"` retitles and makes it an h3 — because `# ` is the level-blind selector,
|
|
65
|
+
so `find="# Old"` renames the text and keeps whatever level it selected. `with` that forms more than one
|
|
66
|
+
paragraph is rejected (`INVALID_OP` on `with`) — see the recipe for a multi-paragraph rewrite below; so is any non-empty `with` that
|
|
67
|
+
renders to no text, which a line indented four spaces or a tab does (markdown reads that as a code block), as does whitespace
|
|
68
|
+
alone. An empty `with` is the one that deletes the matched text on purpose. Use zero-based `occurrence` for a
|
|
69
|
+
repeated target; re-read a missing or ambiguous target before retrying. Pass `summary` to name the version when recording a decision.
|
|
70
|
+
|
|
71
|
+
**Rewriting several paragraphs is one `replace` per paragraph, then a read-back.** `replace` is inline:
|
|
72
|
+
each `with` is the new text of one paragraph, and a `with` that forms two paragraphs is refused whatever the
|
|
73
|
+
text says. Give each paragraph you rewrite its own `replace`, which keeps that paragraph's block id and every
|
|
74
|
+
anchor outside the text you rewrite. A comment or ask anchored to the text you rewrite loses its quote but keeps
|
|
75
|
+
its pin to the block, so the dashboard still shows it beside that paragraph; a delete (below) loses both. An
|
|
76
|
+
anchor that straddles the boundary keeps its mark over the words you left alone, with its quote shortened to
|
|
77
|
+
them: rewriting `charlie delta.` under a comment on `bravo charlie` leaves that comment quoting `bravo `. When
|
|
78
|
+
the new text has more paragraphs than the old, write them as **one** `insert` whose `markdown` holds them all,
|
|
79
|
+
anchored on the last paragraph you rewrote with `after` quoting it exactly as it now reads (the operations in
|
|
80
|
+
one batch apply in order): each separate `insert` at the same anchor lands directly after it, so two of them
|
|
81
|
+
come out in the reverse of the order you wrote. An insert lands after the top-level block that holds the quote,
|
|
82
|
+
so beside a paragraph inside a list item or a typed block it goes after the whole list or block. When it has fewer,
|
|
83
|
+
`delete` each leftover paragraph, with its whole text as `find` or its id as `block`. All of that holds while
|
|
84
|
+
the new text is paragraphs: `replace` keeps a block's kind, so any block that is not a paragraph — a heading,
|
|
85
|
+
list, table, blockquote, rule, code fence or typed block — cannot be replaced into place. Its marker is written
|
|
86
|
+
as literal text, except a backtick fence, which becomes an inline code span whose text is everything inside it,
|
|
87
|
+
info string and line breaks included (```` ```go\nx := 1``` ```` becomes the code span `go` + a line break +
|
|
88
|
+
`x := 1`), and a tilde fence, which stays literal.
|
|
89
|
+
|
|
90
|
+
HTML is not written as text at all. A `with` whose HTML markdown reads as a block (`<div>x</div>`,
|
|
91
|
+
`<!-- note -->`) is stored as inline HTML, and `dispatch_doc_read` then returns it raw, in markdown Dispatch
|
|
92
|
+
will not take back: the schema carries no block HTML, so an `insert` or an upload of that markdown is refused.
|
|
93
|
+
Keep HTML inside a line.
|
|
94
|
+
|
|
95
|
+
When the new text adds a block that is not a paragraph beside paragraphs, `insert` it beside the
|
|
96
|
+
paragraph you replaced, which keeps that paragraph's id; only when no paragraph of the new text is left to take
|
|
97
|
+
the old block's place is it an `insert` of the new block plus a `delete` of the old, and the delete is what
|
|
98
|
+
costs the id (below). Then read the document back with
|
|
99
|
+
`dispatch_doc_read` and read the passage and its neighbours, not a grep for the words you added: an empty
|
|
100
|
+
`with` deletes the matched text on purpose, so a `replace` whose `with` you meant to fill empties that
|
|
101
|
+
paragraph — the block and its id stay, holding nothing — and only a read shows what the document now says.
|
|
102
|
+
|
|
103
|
+
A batch that leaves the document's semantic identity unchanged — including its inline anchor marks, so an edit that only orphans a
|
|
104
|
+
comment or ask anchor still mints its version — mints no version, named or not: the response carries
|
|
105
|
+
`changed: false` with `unchanged_ops` naming each operation that did nothing, and the tool result says nothing changed. A `summary`
|
|
106
|
+
does not force a version for such a batch; `POST /api/v1/artifacts/<id>/versions`, which names the current state on purpose, still does.
|
|
107
|
+
|
|
108
|
+
A `delete` whose `find` is a block's entire text removes the block itself — the bullet, paragraph, or heading, not just its words — and
|
|
109
|
+
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
|
|
110
|
+
that holds a nested list hoists that list's items into the bullet's place (as an outliner does); a bullet with any other content
|
|
111
|
+
(paragraphs, code, tables) is refused with `INVALID_OP` naming `delete {block:"<item id>"}`, which removes the item with its content.
|
|
112
|
+
`delete` with `block` removes any block by id (paragraph, heading, list, list item, table, or typed block; deleting an open `ask` block
|
|
113
|
+
retracts its ask, while an answered one keeps its answer as the record), and `move` with `block` relocates one, keeping its id and
|
|
114
|
+
attributes — a moved `ask` keeps its ask and answer. **A block loses its id only when it is removed**, and its anchors go with it:
|
|
115
|
+
`delete` by text or by id removes the block and any container it empties; `delete_row` / `delete_column` remove their cells' ids,
|
|
116
|
+
which is why they are refused while an open ask or unresolved comment sits on them; and a `move` that takes the last block out of a
|
|
117
|
+
blockquote or list item removes that emptied container, the list too when no item remains, and each enclosing container that
|
|
118
|
+
held nothing else (`> - Only.` loses the blockquote as well as the item and the list). The moved block itself keeps its id,
|
|
119
|
+
as do `replace` (an empty `with` and a heading-level change included), `insert` and `retype`; `retype` carries the paragraph's id
|
|
120
|
+
onto the typed block it becomes. Block ids are the `#id` a typed block renders
|
|
121
|
+
(`:::ask{#5467e5ce-…}`) and, for every block including untyped ones, the `id` rows from
|
|
122
|
+
`GET /api/v1/artifacts/<artifact UUID>/blocks` (or `/api/v1/issues/{key}/artifacts/{slug}/blocks`), each with its `type` and byte range
|
|
123
|
+
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
|
|
124
|
+
an earlier `delete {block}` fails as `INVALID_OP` naming the earlier operation and the parent block that cascaded the removal. A move
|
|
125
|
+
whose anchor lies inside the moved block, or a delete that would leave a typed block without the body its content rule requires, is
|
|
126
|
+
`INVALID_OP` naming the field and the rule.
|
|
127
|
+
|
|
128
|
+
`GET /api/v1/artifacts/<artifact UUID>/blocks` includes a full-state `token` on every block, including
|
|
129
|
+
inline marks. To reject a stale edit, pass `precondition` with exactly one of
|
|
130
|
+
`{ document: "<token from dispatch_doc_read>" }` or
|
|
131
|
+
`{ blocks: [{ id: "<block id>", token: "<block token>" }] }`. The server resolves the whole batch before
|
|
132
|
+
mutation: a block guard must cover every content block it changes, or Dispatch returns
|
|
133
|
+
`400 INVALID_PRECONDITION` without applying anything. Use a document token for insert and move because they
|
|
134
|
+
depend on document order. A block token lets other sections change concurrently; a new anchored ask or comment
|
|
135
|
+
changes the relevant token. A stale guard returns `409 PRECONDITION_FAILED` with each mismatch and current
|
|
136
|
+
token; Dispatch applies no part of that batch. It is the hashline `#TAG` property applied to stable block ids,
|
|
137
|
+
not line numbers: canonical Markdown lines shift under concurrent edits and rendering changes, while block ids
|
|
138
|
+
survive moves and retyping.
|
|
139
|
+
|
|
140
|
+
`retype` turns the paragraph or typed block with `block` into the named typed `type` in place. It keeps the
|
|
141
|
+
block id, keeps a typed block's body, and uses `attributes` for client-owned typed attributes. Use it when
|
|
142
|
+
an existing paragraph is the question that should become a decision.
|
|
@@ -611,16 +611,22 @@ do:
|
|
|
611
611
|
reported. The issue has moved to a newer run since your task was given, so the work you just
|
|
612
612
|
reported belongs to a run that is over. Nothing you can repeat changes that: stop, push nothing
|
|
613
613
|
further, and tell the architect what you completed and that its run has been superseded. A task
|
|
614
|
-
for the current run arrives in this same session if the phase still needs you.
|
|
614
|
+
for the current run arrives in this same session if the phase still needs you. The same answer
|
|
615
|
+
comes when your turn started before the daemon recorded the current run's task as yours, so it
|
|
616
|
+
still holds you to the earlier run: that task is sent again. When a task arrives, do what it
|
|
617
|
+
asks; if the work it asks for is already committed, call `handoff_complete` again, and never redo
|
|
618
|
+
the work or write a second handoff.
|
|
615
619
|
- `HANDOFF_NOT_CURRENT_PHASE` — names your role, the issue, and the phase it is in now. The issue
|
|
616
620
|
has left your phase; report to the architect rather than completing again.
|
|
617
621
|
- `HANDOFF_NO_RUN` — names neither: it says this claim has taken no task, so the daemon cannot
|
|
618
622
|
tell which run you are reporting. Your pane is completing outside any assignment. Say so to the
|
|
619
623
|
architect; do not re-run the phase.
|
|
620
624
|
- `HANDOFF_ALREADY_RECORDED` — names your role, the phase, the review round and the commit. This
|
|
621
|
-
exact call was received before, and its first answer stands
|
|
622
|
-
|
|
623
|
-
|
|
625
|
+
exact call was received before, and its first answer stands: accepted, or a refusal the daemon
|
|
626
|
+
records with the call — `HANDOFF_STALE_GENERATION`, `HANDOFF_NOT_CURRENT_PHASE`,
|
|
627
|
+
`READY_REQUIRED` or `HANDOFF_NOT_NEW`. `HANDOFF_NO_RUN` is never that first answer, since it is
|
|
628
|
+
given before anything is recorded. Sending it again changes nothing; if you did not see that
|
|
629
|
+
first answer, tell the architect so and quote this one.
|
|
624
630
|
|
|
625
631
|
Quote the answer verbatim in what you tell the architect: with the run and phase it names, the
|
|
626
632
|
difference between "my work is lost" and "my work belongs to the previous run" is visible.
|