@sjawhar/opencode-legion-envoy 0.31.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,8 +32,8 @@ the session working directory, stamps it with the OpenCode session id and title,
32
32
  `details.topic` as tool metadata so a successful mutation subscribes to that exact Dispatch topic.
33
33
 
34
34
  `dispatch_artifact` accepts exactly one upload source: a local `path`, or inline `content`.
35
- An architect can post a primary specification directly with
36
- `{ issue, name: "spec.md", content: "# Design", primary: true }`.
35
+ An architect can post a specification directly with
36
+ `{ issue, name: "spec.md", content: "# Design" }`.
37
37
 
38
38
  It also maintains the live session registry metadata needed for Envoy to discover OpenCode sessions and their API ports.
39
39
 
@@ -13603,6 +13603,16 @@ function dispatchToolSchema(spec, z, opts) {
13603
13603
  return spec.validation === undefined ? z.object(shape, opts) : z.refineObject(shape, spec.validation.check, spec.validation.message, opts);
13604
13604
  }
13605
13605
  var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference; an external reference creates its native issue in the repository's dashboard-configured project or, failing that, the default project (DISPATCH_DEFAULT_PROJECT).";
13606
+ var SPEC_SECTIONS = [
13607
+ "Decisions needed",
13608
+ "Acceptance",
13609
+ "Requirements",
13610
+ "Design",
13611
+ "Errors",
13612
+ "Testing",
13613
+ "Rejected"
13614
+ ];
13615
+ var SPEC_WRITING_GUIDANCE = `When writing a spec, use these sections in order: ${SPEC_SECTIONS.join(", ")}. ` + "Every line is a fact, decision, or risk; use tables over prose; see skills/dispatch Writing a spec.";
13606
13616
  var ASK_URGENCIES = ["low", "med", "high", "blocking"];
13607
13617
  var DOC_EDIT_OPS = ["replace", "delete", "insert"];
13608
13618
  var dispatchToolSpecs = [
@@ -13614,7 +13624,7 @@ var dispatchToolSpecs = [
13614
13624
  title: z.string().describe("Concise issue title."),
13615
13625
  parent: z.string().describe("Optional parent issue.").optional(),
13616
13626
  external: z.string().describe("Optional external issue reference.").optional(),
13617
- spec: z.string().describe("Optional initial primary-document markdown.").optional()
13627
+ spec: z.string().describe(`Optional initial primary-document markdown. ${SPEC_WRITING_GUIDANCE}`).optional()
13618
13628
  })
13619
13629
  },
13620
13630
  {
@@ -13680,7 +13690,7 @@ var dispatchToolSpecs = [
13680
13690
  },
13681
13691
  {
13682
13692
  name: "dispatch_doc_edit",
13683
- description: "Apply deterministic text edits to a document. Do not use it for review feedback or for reading; use " + `dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. ${ISSUE_REFERENCE}`,
13693
+ description: "Apply deterministic text edits to a document. Do not use it for review feedback or for reading; use " + `dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. ${ISSUE_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
13684
13694
  arguments: (z) => ({
13685
13695
  issue: z.string().describe(ISSUE_REFERENCE),
13686
13696
  artifact: z.string().describe("Artifact slug or id for the document."),
@@ -13714,7 +13724,6 @@ var dispatchToolSpecs = [
13714
13724
  name: z.string().describe("Artifact filename shown in Dispatch."),
13715
13725
  path: z.string().describe("Local path to the file to upload.").optional(),
13716
13726
  content: z.string().describe("Inline text to store as a Markdown document.").optional(),
13717
- primary: z.boolean().describe("Make this document the issue primary artifact.").optional(),
13718
13727
  summary: z.string().describe("Optional version summary.").optional()
13719
13728
  }),
13720
13729
  validation: {
@@ -14359,8 +14368,6 @@ class DispatchClient {
14359
14368
  return this.#json("POST", artifactPath, input);
14360
14369
  const form = new FormData;
14361
14370
  form.set("name", input.name);
14362
- if (input.primary !== undefined)
14363
- form.set("primary", String(input.primary));
14364
14371
  if (input.summary !== undefined)
14365
14372
  form.set("summary", input.summary);
14366
14373
  if (input.actor !== undefined)
@@ -14879,20 +14886,17 @@ Open anchored asks/comments: ${marks.join(", ")}`,
14879
14886
  };
14880
14887
  }
14881
14888
  case "dispatch_artifact": {
14882
- const primary = optionalBoolean(args, "primary");
14883
14889
  const summary = optionalString(args, "summary");
14884
14890
  const name = stringArg(args, "name");
14885
14891
  const content = optionalString(args, "content");
14886
14892
  const result = await client.artifact(issue(), content === undefined ? {
14887
14893
  name,
14888
14894
  file: Bun.file(resolvePath(input.cwd, stringArg(args, "path"))),
14889
- ...primary === undefined ? {} : { primary },
14890
14895
  ...summary === undefined ? {} : { summary },
14891
14896
  actor
14892
14897
  } : {
14893
14898
  name,
14894
14899
  content,
14895
- ...primary === undefined ? {} : { primary },
14896
14900
  ...summary === undefined ? {} : { summary },
14897
14901
  actor
14898
14902
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "0.31.0",
3
+ "version": "0.33.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -12,6 +12,38 @@ The server enforces high signal: an ask question is at most 800 characters with
12
12
  options; comment and message bodies are at most 2,000 characters; an artifact is at most 25 MiB.
13
13
  It refuses over-limit input; it never truncates it. GitHub threads and markers no longer exist.
14
14
 
15
+ ## Writing a spec
16
+
17
+ A spec is a decision record for the human who decides and the implementer who builds, not a
18
+ transcript of your thinking. Use exactly these document headings in this order.
19
+
20
+ | Section | Required content | Form |
21
+ | --- | --- | --- |
22
+ | **Decisions needed** | Only decisions requiring human authority, taste, or risk appetite. Each states one question, two or three options with tradeoffs, and a recommendation. Every item is an anchored `dispatch_ask`. Answered items move into Requirements with provenance, then leave this section. No other section asks the reader anything. Empty means `None.` | One decision per line; anchor each ask to that line. |
23
+ | **Acceptance** | Every outcome names its check and user-facing surface. An outcome without a verification method is not acceptance criteria. | Numbered lines; browser scenario, API call, or CLI command. |
24
+ | **Requirements** | Provenance is a verbatim human quote or `inferred: <reasoning>`; readers treat inferred requirements as hypotheses. Do not restate the prompt in prose. | `requirement \| provenance` table. |
25
+ | **Design** | State the files, components, routes, and data flow that change. | Facts, not narrative; diagrams only for genuine structure. |
26
+ | **Errors** | Name the behaviour for every error condition; never specify a silent fallback. | `condition \| behaviour` table. |
27
+ | **Testing** | Map every acceptance line to the proof that exercises it. | Suite or scenario. |
28
+ | **Rejected** | Record each considered alternative and why it was rejected so it is not proposed again. | One alternative per line. |
29
+
30
+ ### Rules
31
+
32
+ - Every sentence is a fact, decision, or risk; delete the rest.
33
+ - Use tables over prose and keep one idea per line.
34
+ - Do not use Overview, Background, Introduction, Summary, or Conclusion sections.
35
+ - Do not hedge with “might” or “could consider.”
36
+ - Do not use TBD, TODO, or placeholders; an open item is a Decision needed.
37
+ - Keep each section to one screen; work that exceeds one screen per section is two specs.
38
+ - Update the spec in place as decisions land. The spec is the record; comments are the discussion.
39
+
40
+ ### Self-review
41
+
42
+ - [ ] No placeholders remain.
43
+ - [ ] No sections conflict.
44
+ - [ ] The spec covers one implementation plan's worth of work.
45
+ - [ ] Every requirement has exactly one reading.
46
+
15
47
  ## Your issue
16
48
 
17
49
  Every session works on an issue. Legion pre-fills `issue` from `LEGION_ISSUE`: use a native issue key
@@ -26,6 +58,7 @@ dispatch_issue({ project, title, parent?, external?, spec? })
26
58
  ```
27
59
  It returns `details` `{ issue, topic }`. Use `dispatch_issue` only to create an issue; never use
28
60
  it to park a question.
61
+ When `spec` is supplied, follow [Writing a spec](#writing-a-spec).
29
62
 
30
63
  ## Asking
31
64
 
@@ -43,8 +76,10 @@ dispatch_ask({
43
76
  It returns `details` `{ issue, topic, ask }`. Options are buttons: never enumerate choices in
44
77
  prose. Put the recommendation in `question`, and put each selectable choice in `options`.
45
78
  Anchor a document question with `anchor: { artifact, quote, occurrence? }`; `occurrence` is
46
- zero-based and selects a repeated quote. An anchor whose quote disappears becomes orphaned but
47
- remains readable against its original document version.
79
+ zero-based and selects a repeated quote. The server writes the resulting mark. The HTTP API also
80
+ accepts `{ artifact, mark_id }` from a browser that has already written its mark; Dispatch tools
81
+ use the quote form. An anchor whose quote disappears becomes orphaned but remains readable against
82
+ its original document version.
48
83
 
49
84
  An ask stays open until a human answers, unless its question no longer needs that answer. Retract a
50
85
  moot or superseded question, or self-resolve one after finding the answer:
@@ -65,6 +100,7 @@ answer. Use `reply_to_ask` on `dispatch_comment` to reply under your own ask; it
65
100
  exclusive with `reply_to`.
66
101
 
67
102
  ## The spec is where narrative goes
103
+ Write and update the issue specification according to [Writing a spec](#writing-a-spec).
68
104
 
69
105
  Read the current document before changing it:
70
106
 
@@ -72,7 +108,7 @@ Read the current document before changing it:
72
108
  dispatch_doc_read({ issue?, artifact?, version?, ref? })
73
109
  ```
74
110
  It returns live or versioned markdown with open marks and `details` `{ issue }`; omit `artifact`
75
- with `issue` to read the primary document. Then write narrative with:
111
+ with `issue` to read the issue specification. Then write narrative with:
76
112
 
77
113
  ```ts
78
114
  dispatch_doc_edit({ issue, artifact, ops, summary? })
@@ -107,7 +143,8 @@ dispatch_comment({ issue, artifact?, quote?, occurrence?, body, reply_to?, reply
107
143
  ```
108
144
 
109
145
  It returns `details` `{ issue, topic, comment }`. `quote` requires `artifact`; omit both for a
110
- floating issue comment. Use `reply_to` to continue a comment thread; use `reply_to_ask` to reply
146
+ floating issue comment. A reply (`reply_to`/`reply_to_ask`) takes no `quote`; it belongs to its
147
+ parent's anchor. Use `reply_to` to continue a comment thread; use `reply_to_ask` to reply
111
148
  directly under a question asked with `dispatch_ask`. The two are mutually exclusive.
112
149
 
113
150
  Propose an exact replacement instead of describing it:
@@ -118,28 +155,31 @@ dispatch_suggest({ issue, artifact, quote, replace_with, body?, occurrence? })
118
155
 
119
156
  It returns `details` `{ issue, topic, comment }`. A human accepts or rejects a suggestion. On
120
157
  `TARGET_AMBIGUOUS`, add zero-based `occurrence`. On `TARGET_NOT_FOUND`, re-read the document
121
- before retrying. `INVALID_OP` names a malformed edit; `CAP_EXCEEDED` never truncates;
122
- `ISSUE_CLOSED` rejects a write. `ACTOR_KIND`, `ROUTE_INVALID`, and `PRIMARY_NOT_DOC` reject an
123
- invalid actor, route, or primary artifact.
158
+ before retrying. `INVALID_ANCHOR` requires exactly one nonempty anchor `quote` or `mark_id`;
159
+ `ANCHOR_MISSING` means a browser mark was not observed in the live tree, and
160
+ `ANCHOR_ORPHANED` means its marked text no longer exists. `INVALID_MARKDOWN` and `DOC_SCHEMA`
161
+ reject Markdown or a live tree outside the Proof schema. `INVALID_OP` names a malformed edit;
162
+ `CAP_EXCEEDED` never truncates; `ISSUE_CLOSED` rejects a write. `ACTOR_KIND` and `ROUTE_INVALID`
163
+ reject an invalid actor or route.
124
164
 
125
165
  ## Artifacts
126
166
 
127
167
  Attach an image, diagram, or local file with:
128
168
 
129
169
  ```ts
130
- dispatch_artifact({ issue, name, path, primary?, summary? })
170
+ dispatch_artifact({ issue, name, path, summary? })
131
171
  ```
132
172
 
133
173
  Or, when the text is already in the call, post a Markdown document directly:
134
174
 
135
175
  ```ts
136
- dispatch_artifact({ issue, name: "spec.md", content: "# Design\n...", primary: true })
176
+ dispatch_artifact({ issue, name: "spec.md", content: "# Design\n..." })
137
177
  ```
138
178
 
139
179
  Exactly one of `path` and `content` is required. The inline form sends JSON with
140
180
  `Content-Type: application/json`. It returns `details` `{ issue, topic, artifact, version }`.
141
- Uploading the same `name` creates its next version. Use `content` for an architect's primary spec;
142
- set `primary: true` only for Markdown documents.
181
+ Uploading the same `name` creates its next version. Use `content` when the text is already in
182
+ the call.
143
183
 
144
184
  ## Messages
145
185
 
@@ -70,13 +70,15 @@ exercise a criterion end to end, building that path is a child issue of this tre
70
70
  relationship from `parent`. Keep the returned issue keys in ordered waves; a child is
71
71
  inert until released.
72
72
 
73
+ Specifications written into Dispatch follow [`skills/dispatch`'s Writing a spec](../dispatch/SKILL.md#writing-a-spec).
74
+
73
75
  Write one root specification containing the accepted scope, adoption/decomposition,
74
76
  waves, acceptance criteria, and integration test. When the config-armed root design gate
75
77
  applies, run this exact sequence **before any Legion-role spawn**, including a
76
78
  sub-architect:
77
79
 
78
80
  ```text
79
- dispatch_artifact({ issue: "<root issue>", name: "spec.md", content: "<root specification>", primary: true, summary: "<one-line summary>" })
81
+ dispatch_artifact({ issue: "<root issue>", name: "spec.md", content: "<root specification>", summary: "<one-line summary>" })
80
82
  askId = dispatch_ask({
81
83
  issue: "<root issue>",
82
84
  question: "<specification summary and the decision requested>",
@@ -118,6 +118,8 @@ path-scoped workflow.
118
118
 
119
119
  ## Phase work
120
120
 
121
+ Specifications written into Dispatch follow [`skills/dispatch`'s Writing a spec](../dispatch/SKILL.md#writing-a-spec).
122
+
121
123
  Follow the repository's normal engineering workflow and the assigned issue's acceptance
122
124
  criteria. Your phase's own charter and the predecessor handoffs you read define the phase
123
125
  artifact and its completion evidence. Do not replace architect-owned decomposition, gate