@chorus-aidlc/chorus-openclaw-plugin 0.16.2 → 0.17.1

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.
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: openspec-aware
3
- description: Opt-in OpenSpec-mode authoring for Chorus PM workflows on OpenClaw. Runs inline three-check detection for the local `openspec` CLI, scaffolds `openspec/changes/<slug>/` on disk, and mirrors Markdown files into Chorus document drafts via the `chorus-api.sh` wrapper. Required reading for the proposal, develop, and yolo skills whenever the user has the `openspec` CLI installed.
3
+ description: Opt-in OpenSpec-mode authoring for Chorus PM workflows on OpenClaw. Runs inline three-check detection for the local `openspec` CLI, scaffolds `openspec/changes/<slug>/` on disk, and mirrors Markdown files into Chorus document drafts via `chorus mcp call --arg-file` (bash `chorus-api.sh` wrapper as fallback). Required reading for the proposal, develop, and yolo skills whenever the user has the `openspec` CLI installed.
4
4
  license: AGPL-3.0
5
5
  metadata:
6
6
  author: chorus
7
- version: "0.16.2"
7
+ version: "0.17.0"
8
8
  category: project-management
9
9
  mcp_server: chorus
10
10
  ---
@@ -16,7 +16,7 @@ This skill is a **shared sub-procedure** invoked by the Chorus stage skills (pro
16
16
  - Activates when **all three** signals hold (see §1): `CHORUS_OPENSPEC_MODE` is not `off`, an `openspec/` directory exists at the project root, and the `openspec` CLI is on `PATH`.
17
17
  - Otherwise the calling skill falls back to its existing free-form behavior.
18
18
 
19
- > **Tool namespace:** Chorus MCP tools are exposed under a `chorus__` prefix on OpenClaw (e.g. `chorus__chorus_pm_create_proposal`). Bare names are used in prose for readability — prepend `chorus__` when invoking the MCP tools directly. **Document-mirror calls do NOT go through the MCP harness at all** — they go through the `chorus-api.sh` wrapper (see §2 Rule 1), which talks to the Chorus MCP endpoint over HTTP using your API key, independent of the `chorus__` namespacing.
19
+ > **Tool namespace:** Chorus MCP tools are exposed under a `chorus__` prefix on OpenClaw (e.g. `chorus__chorus_pm_create_proposal`). Bare names are used in prose for readability — prepend `chorus__` when invoking the MCP tools directly. **Document-mirror calls do NOT go through the MCP harness at all** — they go through the `chorus` CLI (`chorus mcp call`, preferred) or the `chorus-api.sh` wrapper (fallback) (see §2 Rule 1), which talk to the Chorus MCP endpoint over HTTP using your API key, independent of the `chorus__` namespacing.
20
20
 
21
21
  ---
22
22
 
@@ -63,25 +63,28 @@ Run this detection inline whenever proposal / develop / yolo reference this skil
63
63
 
64
64
  Both are enforced at review time. Both have caused incidents in past releases.
65
65
 
66
- ### Rule 1 — Mirror via the wrapper, never re-type document content from agent output
66
+ ### Rule 1 — Fill `content` from the file (CLI preferred, bash-wrapper fallback); never re-type document content from agent output
67
67
 
68
- Document/draft mirror calls (`chorus_pm_add_document_draft`, `chorus_pm_update_document_draft`, `chorus_pm_update_document`) **MUST** go through:
68
+ Document/draft mirror calls (`chorus_pm_add_document_draft`, `chorus_pm_update_document_draft`, `chorus_pm_update_document`) **MUST** fill the `content` field from the local file's bytes, never from a hand-typed body. Calling these tools directly from the agent's MCP harness with a hand-typed `content` field is a **protocol violation** for OpenSpec mode and will fail review. Use whichever transport is available, preferred first:
69
69
 
70
- ```
71
- chorus-api.sh mcp-tool <tool_name> "$PAYLOAD"
72
- ```
70
+ - **Primary — the `chorus` CLI:** `chorus mcp call <tool_name> '<json-without-content>' --arg-file content=<file>`. `--arg-file content=<path>` reads the file's raw bytes and injects them as the JSON `content` string, byte-exact — the CLI's built-in replacement for `json_encode_file`, so no helper is needed. `chorus mcp call` reads the same `CHORUS_URL` / `CHORUS_API_KEY` from the environment (availability note below). See §3.6. **Requires chorus >= 0.17.0** (the `chorus mcp` subcommand was added then; an older CLI errors with "unknown command"); on any version or unknown-command failure, upgrade with `npm install -g @chorus-aidlc/chorus`.
71
+ - **Fallback — the `chorus-api.sh` wrapper, when `chorus` is not on `PATH`:** build `$PAYLOAD` with the `json_encode_file` helper and call `chorus-api.sh mcp-tool <tool_name> "$PAYLOAD"`. Defined in the §3.6 fallback block.
72
+
73
+ > **New to the `chorus` CLI?** See the **`chorus-cli`** skill for install, configuring agents (`chorus agents add|remove|list`), the connection env vars, and `chorus mcp` basics.
74
+
75
+ > **Acting identity — which agent the call acts as.** `chorus mcp call` resolves the agent from, in order: `CHORUS_AGENT_PROFILE` (a name or UUID) → `CHORUS_URL` + `CHORUS_API_KEY` in the environment → the single agent configured in `~/.chorus/daemon.json`. A daemon-woken session already has `CHORUS_AGENT_PROFILE` set. If a mirror call fails with `Multiple agents … specify --agent` (several agents configured and no profile/creds in the env), pass your own identity explicitly: `chorus mcp call <tool> … --agent <your-agentUuid>` — your UUID is in your `chorus_checkin` result, and `chorus agents` lists every configured name/UUID.
73
76
 
74
- with `$PAYLOAD` built using `json_encode_file` (defined in §3.4). Calling these tools directly from the agent's MCP harness with a hand-typed `content` field is a **protocol violation** for OpenSpec mode and will fail review. Reasons:
77
+ Reasons (they apply to both paths):
75
78
 
76
- 1. **Token cost.** Re-typing a multi-thousand-line markdown body through the LLM burns input + output tokens for every draft. The wrapper streams bytes through `jq -Rs '.'` — content never enters LLM context. A typical 3-doc proposal mirror via the script costs roughly zero content-tokens; via direct MCP it routinely costs 20k+.
77
- 2. **Byte-equality.** `jq -Rs '.'` is a byte-faithful encoder: backslashes, quotes, newlines, code-fence content, zero-width chars all survive. LLM re-emission has a non-zero failure rate on long markdown — table alignment drifts, fence escapes get "fixed", long URLs wrap. The byte-equality guarantee (modulo trailing `\n`) holds **only** on the wrapper path.
78
- 3. **Single source of truth.** With the wrapper, the local `openspec/changes/<slug>/*.md` is authoritative and Chorus is a mirror. With agent re-typing, authority splits between local file and whatever the LLM happened to output — a future diff cannot tell which one is correct.
79
+ 1. **Token cost.** Re-typing a multi-thousand-line markdown body through the LLM burns input + output tokens for every draft. Both the CLI's `--arg-file` and the fallback's `json_encode_file` stream the file's bytes into the JSON string — content never enters LLM context. A typical 3-doc proposal mirror costs roughly zero content-tokens this way; via direct MCP with a re-typed body it routinely costs 20k+.
80
+ 2. **Byte-equality.** A file-fill path (CLI `--arg-file`, or the fallback's `jq -Rs '.'`) is a byte-faithful encoder: backslashes, quotes, newlines, code-fence content, zero-width chars all survive. LLM re-emission has a non-zero failure rate on long markdown — table alignment drifts, fence escapes get "fixed", long URLs wrap. The byte-equality guarantee (modulo trailing `\n`) holds **only** on a file-fill path, never on LLM re-emission.
81
+ 3. **Single source of truth.** With a file-fill mirror, the local `openspec/changes/<slug>/*.md` is authoritative and Chorus is a mirror. With agent re-typing, authority splits between local file and whatever the LLM happened to output — a future diff cannot tell which one is correct.
79
82
 
80
- > **`chorus-api.sh` availability on OpenClaw.** This wrapper is the document-mirror transport. It must be reachable as `chorus-api.sh` on `PATH` (the Chorus standalone skill bundle ships it; if you installed via that bundle it is already on `PATH`). If it is **not** on `PATH` in your OpenClaw environment, do one of:
81
- > - call it by its absolute path (e.g. `"$HOME/.chorus/bin/chorus-api.sh" mcp-tool ...`), or
83
+ > **`chorus` / `chorus-api.sh` availability on OpenClaw.** The document-mirror transport is either the `chorus` CLI (preferred) or the `chorus-api.sh` wrapper (fallback). The wrapper must be reachable as `chorus-api.sh` on `PATH` (the Chorus standalone skill bundle ships it; if you installed via that bundle it is already on `PATH`). If neither `chorus` nor `chorus-api.sh` is on `PATH` in your OpenClaw environment, do one of:
84
+ > - call the wrapper by its absolute path (e.g. `"$HOME/.chorus/bin/chorus-api.sh" mcp-tool ...`), or
82
85
  > - reproduce its single behavior inline — POST a JSON-RPC `tools/call` for `<tool_name>` with arguments `$PAYLOAD` to `"$CHORUS_URL/api/mcp"` with header `Authorization: Bearer $CHORUS_API_KEY` using `curl`, capturing the raw body for the §6 halt-on-error check.
83
86
  >
84
- > The wrapper requires `CHORUS_URL` and `CHORUS_API_KEY` in the environment (same values as your plugin config `chorusUrl` / `apiKey`). Export them before the first call if they are not already set. What you must NOT do is re-type the document body through the model — the wrapper-only rule stands regardless of how you invoke the wrapper.
87
+ > Both `chorus mcp call` and the wrapper read `CHORUS_URL` and `CHORUS_API_KEY` from the environment (same values as your plugin config `chorusUrl` / `apiKey`). Export them before the first call if they are not already set. What you must NOT do is re-type the document body through the model — the file-fill rule stands regardless of which transport you use.
85
88
 
86
89
  ### Rule 2 — Halt on error via `chorus_check_response`
87
90
 
@@ -196,25 +199,12 @@ Optional:
196
199
  openspec validate "$SLUG"
197
200
  ```
198
201
 
199
- ### 3.4 Helper: `json_encode_file`
202
+ ### 3.4 Filling the `content` field byte-exact
200
203
 
201
- Define once at the top of the authoring session. With `jq` available it streams the file into a JSON string; the fallback matches `chorus-api.sh`'s own escaping when `jq` is missing.
204
+ The document `content` must be inserted **byte-for-byte** from the local file never re-typed by the LLM. Two mechanisms, preferred first:
202
205
 
203
- ```bash
204
- json_encode_file() {
205
- local _path="$1"
206
- if command -v jq >/dev/null 2>&1; then
207
- jq -Rs '.' < "$_path"
208
- else
209
- local _content
210
- _content=$(cat "$_path")
211
- _content=${_content//\\/\\\\}
212
- _content=${_content//\"/\\\"}
213
- _content=${_content//$'\n'/\\n}
214
- printf '"%s"' "$_content"
215
- fi
216
- }
217
- ```
206
+ - **Primary — `chorus mcp call … --arg-file content=<path>`** (§3.6). The CLI reads the file's raw bytes and injects them as the JSON `content` string. This is the byte-faithful replacement for `json_encode_file`, so on the CLI path **no helper is needed** — pass the base JSON without a `content` field and let `--arg-file` fill it.
207
+ - **Fallback — `json_encode_file`** (defined in the §3.6 fallback block, used only when `chorus` is not on `PATH`). With `jq` available it streams the file through `jq -Rs '.'`; the pure-shell branch matches `chorus-api.sh`'s own escaping when `jq` is missing.
218
208
 
219
209
  Round-trip: the Chorus backend appends a single `\n` to draft content on write, so server `content` is **byte-equal modulo a trailing newline**. Reviewers diffing local file vs server should ignore that one byte.
220
210
 
@@ -233,13 +223,46 @@ OpenSpec change slug: <slug>
233
223
 
234
224
  This line is machine-grep-able by future runs of this skill and by the §3.9 archive trigger.
235
225
 
236
- ### 3.6 Mirror each document draft via the wrapper
226
+ ### 3.6 Mirror each document draft (CLI primary, wrapper fallback)
227
+
228
+ > **Rule 1 reminder:** `content` comes from the file's bytes, never a hand-typed body. The agent must not retype the document body.
229
+
230
+ Ensure `CHORUS_URL` and `CHORUS_API_KEY` are exported (Rule 1 note) — both the CLI and the wrapper read them. Define the halt-on-error helper from §6 once at the top. **Primary path — the `chorus` CLI:** pass the base JSON *without* a `content` field and let `--arg-file content=<file>` fill it byte-exact. One call per file:
231
+
232
+ ```bash
233
+ # PRD draft — --arg-file fills content byte-exact from the file; no json_encode_file needed.
234
+ RESULT=$(chorus mcp call chorus_pm_add_document_draft \
235
+ "{\"proposalUuid\":\"$PROPOSAL_UUID\",\"type\":\"prd\",\"title\":\"PRD: $HUMAN_TITLE\"}" \
236
+ --arg-file content="openspec/changes/$SLUG/proposal.md")
237
+ RC=$?
238
+ chorus_check_response "chorus_pm_add_document_draft (prd)" "$RC" "$RESULT"
239
+ PRD_DRAFT_UUID=$(printf '%s' "$RESULT" | grep -o '"draftUuid"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
240
+ ```
241
+
242
+ Repeat with `type: "tech_design"` for `design.md`, and one call per capability with `type: "spec"` for each `specs/<capability>/spec.md`. Do **not** mirror `tasks.md` — Chorus task drafts (created via the `chorus_pm_add_task_draft` MCP tool, no wrapper needed) are the source of truth for tasks.
243
+
244
+ > Why parsing uses `printf '%s' "$RESULT" | grep` not `echo "$RESULT" | jq`: `echo` interprets backslash sequences inside the captured JSON, turning embedded `\n` into a real newline. `jq` then aborts with `Invalid string: control characters from U+0000 through U+001F must be escaped`. `printf '%s'` emits the captured bytes verbatim. Same pattern applies to all wrapper-result parsing in this skill.
237
245
 
238
- > **Rule 1 reminder:** these calls go through `chorus-api.sh`, not direct MCP. The agent must not retype the document body.
246
+ #### Fallback when the `chorus` CLI is not on `PATH`
239
247
 
240
- Ensure `CHORUS_URL` and `CHORUS_API_KEY` are exported (Rule 1 note). Define the halt-on-error helper from §6 once at the top, then run one call per file:
248
+ If `command -v chorus` fails, mirror through the `chorus-api.sh` wrapper (or the absolute-path / inline-`curl` variants from the Rule 1 availability note). Define `json_encode_file` here (it is used **only** on this fallback path), then build `$PAYLOAD` with an embedded `content`. The `chorus_check_response` halt-on-error check applies exactly as on the primary path.
241
249
 
242
250
  ```bash
251
+ # Define once, fallback-only: byte-faithful file → JSON string.
252
+ json_encode_file() {
253
+ local _path="$1"
254
+ if command -v jq >/dev/null 2>&1; then
255
+ jq -Rs '.' < "$_path"
256
+ else
257
+ local _content
258
+ _content=$(cat "$_path")
259
+ _content=${_content//\\/\\\\}
260
+ _content=${_content//\"/\\\"}
261
+ _content=${_content//$'\n'/\\n}
262
+ printf '"%s"' "$_content"
263
+ fi
264
+ }
265
+
243
266
  # PRD draft
244
267
  CONTENT=$(json_encode_file "openspec/changes/$SLUG/proposal.md")
245
268
  PAYLOAD=$(cat <<JSON
@@ -257,13 +280,19 @@ chorus_check_response "chorus_pm_add_document_draft (prd)" "$RC" "$RESULT"
257
280
  PRD_DRAFT_UUID=$(printf '%s' "$RESULT" | grep -o '"draftUuid"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
258
281
  ```
259
282
 
260
- Repeat with `type: "tech_design"` for `design.md`, and one call per capability with `type: "spec"` for each `specs/<capability>/spec.md`. Do **not** mirror `tasks.md` — Chorus task drafts (created via the `chorus_pm_add_task_draft` MCP tool, no wrapper needed) are the source of truth for tasks.
283
+ ### 3.7 Editing a draft after the first mirror
261
284
 
262
- > Why parsing uses `printf '%s' "$RESULT" | grep` not `echo "$RESULT" | jq`: `echo` interprets backslash sequences inside the captured JSON, turning embedded `\n` into a real newline. `jq` then aborts with `Invalid string: control characters from U+0000 through U+001F must be escaped`. `printf '%s'` emits the captured bytes verbatim. Same pattern applies to all wrapper-result parsing in this skill.
285
+ Local file changes propagate via `chorus_pm_update_document_draft` same primary/fallback split as §3.6, same halt check. Primary (CLI):
263
286
 
264
- ### 3.7 Editing a draft after the first mirror
287
+ ```bash
288
+ RESULT=$(chorus mcp call chorus_pm_update_document_draft \
289
+ "{\"proposalUuid\":\"$PROPOSAL_UUID\",\"draftUuid\":\"$PRD_DRAFT_UUID\"}" \
290
+ --arg-file content="openspec/changes/$SLUG/proposal.md")
291
+ RC=$?
292
+ chorus_check_response "chorus_pm_update_document_draft" "$RC" "$RESULT"
293
+ ```
265
294
 
266
- Local file changes propagate via `chorus_pm_update_document_draft` — same wrapper, same `json_encode_file`, same halt check.
295
+ Fallback (no `chorus` on `PATH`) — `json_encode_file` from the §3.6 fallback block:
267
296
 
268
297
  ```bash
269
298
  CONTENT=$(json_encode_file "openspec/changes/$SLUG/proposal.md")
@@ -282,7 +311,17 @@ chorus_check_response "chorus_pm_update_document_draft" "$RC" "$RESULT"
282
311
 
283
312
  ### 3.8 Editing a Document after proposal approval
284
313
 
285
- Once the proposal is approved, drafts materialize into Documents with their own UUIDs. To keep `openspec/changes/$SLUG/` and the Chorus Document in sync, mirror file edits via `chorus_pm_update_document`:
314
+ Once the proposal is approved, drafts materialize into Documents with their own UUIDs. To keep `openspec/changes/$SLUG/` and the Chorus Document in sync, mirror file edits via `chorus_pm_update_document`. Primary (CLI):
315
+
316
+ ```bash
317
+ RESULT=$(chorus mcp call chorus_pm_update_document \
318
+ "{\"documentUuid\":\"$SPEC_DOCUMENT_UUID\"}" \
319
+ --arg-file content="openspec/changes/$SLUG/specs/<capability>/spec.md")
320
+ RC=$?
321
+ chorus_check_response "chorus_pm_update_document" "$RC" "$RESULT"
322
+ ```
323
+
324
+ Fallback (no `chorus` on `PATH`) — `json_encode_file` from the §3.6 fallback block:
286
325
 
287
326
  ```bash
288
327
  CONTENT=$(json_encode_file "openspec/changes/$SLUG/specs/<capability>/spec.md")
@@ -351,9 +390,9 @@ When the §1 detection puts the agent in fallback mode (`CHORUS_OPENSPEC_ACTIVE=
351
390
 
352
391
  ## §6. Failure visibility — the `chorus_check_response` helper
353
392
 
354
- There is a known wrapper edge case: when the server returns HTTP 4xx (e.g. 401 from a bad `CHORUS_API_KEY`), `chorus-api.sh mcp-tool` captures the JSON-RPC error body internally, pipes it through a `.result.content[]?` jq filter that produces no output when `.result` is absent, and exits 0 with empty stdout. A bare `RC=$?` check would not halt on this — the most common runtime failure mode would be invisible. (If you reproduced the wrapper inline via `curl` per the Rule 1 note, the same three-signal check still applies to the raw HTTP body.)
393
+ This helper guards **both** the primary CLI path and the fallback wrapper path. On the fallback path there is a known wrapper edge case: when the server returns HTTP 4xx (e.g. 401 from a bad `CHORUS_API_KEY`), `chorus-api.sh mcp-tool` captures the JSON-RPC error body internally, pipes it through a `.result.content[]?` jq filter that produces no output when `.result` is absent, and exits 0 with empty stdout. A bare `RC=$?` check would not halt on this — the most common runtime failure mode would be invisible. (If you reproduced the wrapper inline via `curl` per the Rule 1 note, the same three-signal check still applies to the raw HTTP body. `chorus mcp call` exits non-zero on tool/transport errors, so `RC` is reliable on the primary path — but run the same three-signal check on both, as defense in depth.)
355
394
 
356
- Define this helper **once** at the top of the authoring session and use it after every wrapper call:
395
+ Define this helper **once** at the top of the authoring session and use it after every mirror call (CLI or wrapper):
357
396
 
358
397
  ```bash
359
398
  chorus_check_response() {
@@ -393,9 +432,15 @@ chorus_check_response() {
393
432
  - Skip capturing `$RESULT` into a variable; the helper needs the body.
394
433
  - Use only `if [ "$RC" -ne 0 ]; then ...` — that misses the HTTP-error path.
395
434
 
396
- **Minimal call site shape:**
435
+ **Minimal call site shape (both paths):**
397
436
 
398
437
  ```bash
438
+ # Primary — chorus CLI:
439
+ RESULT=$(chorus mcp call <tool_name> '<json-without-content>' --arg-file content=<file>)
440
+ RC=$?
441
+ chorus_check_response "<tool_name>" "$RC" "$RESULT"
442
+
443
+ # Fallback — chorus-api.sh wrapper (chorus not on PATH):
399
444
  RESULT=$(chorus-api.sh mcp-tool <tool_name> "$PAYLOAD")
400
445
  RC=$?
401
446
  chorus_check_response "<tool_name>" "$RC" "$RESULT"
@@ -418,8 +463,8 @@ When invoked from a stage skill (proposal / develop / yolo):
418
463
  c. Author `proposal.md`, `design.md`, `specs/<capability>/spec.md` (§3.2–§3.3). Mix `ADDED` / `MODIFIED` / `REMOVED` / `RENAMED` blocks as needed; remember `MODIFIED` overwrites the whole Requirement.
419
464
  d. Optional: `openspec validate "$SLUG"`.
420
465
  e. `chorus_pm_create_proposal` (direct MCP) with the `OpenSpec change slug: $SLUG` line in description (§3.5).
421
- f. Export `CHORUS_URL` / `CHORUS_API_KEY`; define `json_encode_file`, `chorus_check_response` helpers; confirm `chorus-api.sh` is reachable (Rule 1 note).
422
- g. For each row in §5 with "yes" — mirror via `chorus-api.sh mcp-tool chorus_pm_add_document_draft` (§3.6). Record each `$DRAFT_UUID`.
466
+ f. Export `CHORUS_URL` / `CHORUS_API_KEY` (both the CLI and the wrapper read them); define the `chorus_check_response` helper. Prefer `chorus mcp call … --arg-file content=<file>` for mirrors (§3.6) — no `json_encode_file` needed on that path; define `json_encode_file` and confirm `chorus-api.sh` is reachable (Rule 1 note) only when falling back to the wrapper because `chorus` is not on `PATH`.
467
+ g. For each row in §5 with "yes" — mirror via `chorus mcp call chorus_pm_add_document_draft … --arg-file content=<file>` (§3.6; fallback = `chorus-api.sh mcp-tool`). Record each `$DRAFT_UUID`.
423
468
  h. On any failed `chorus_check_response` — halt, surface the error, do NOT proceed.
424
469
  4. Edits before approval → §3.7. Edits after approval → §3.8.
425
470
  5. Last task verified → detect the trigger yourself (no hook) → run §3.9 archive flow.
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: orchestrate
3
+ description: Multi-agent orchestration playbook — coordinate OTHER agents and humans across the AI-DLC lifecycle by delegating ideas and tasks, running independent reviewers, and gatekeeping at the Reversed-Conversation gates.
4
+ license: AGPL-3.0
5
+ metadata:
6
+ author: chorus
7
+ version: "0.17.0"
8
+ category: project-management
9
+ mcp_server: chorus
10
+ ---
11
+
12
+ # Orchestrate Skill
13
+
14
+ This skill is for an **orchestrator** (typically an Admin-preset agent) that coordinates *other* agents and humans across the AI-DLC lifecycle instead of doing all the work itself. The orchestrator decomposes work, hands each piece to a chosen owner, runs independent reviewers as quality gates, and gatekeeps the human-owned approval/verify gates — but never ships on its own.
15
+
16
+ > **Tool namespace under OpenClaw.** Bare tool names below (e.g. `chorus_pm_assign_idea`) are the readable form; the actual callable name is `chorus__`-prefixed (`chorus__chorus_pm_assign_idea`). See the namespace note in the core `chorus` skill.
17
+
18
+ It complements the other skills rather than replacing them:
19
+
20
+ - `/yolo` — **one** agent drives the whole pipeline solo. Orchestration is the opposite: **many** agents, each owning a piece, coordinated by you.
21
+ - `/idea`, `/proposal`, `/develop`, `/review`, `/quick-dev` — a single stage you execute yourself. Orchestration is the layer *above* those: you decide who runs each stage.
22
+
23
+ ---
24
+
25
+ ## When to use this skill
26
+
27
+ Use it when **more than one agent (or agent + human) will touch the work** and someone has to keep them coherent:
28
+
29
+ - You own a **theme / epic / container idea** that decomposes into several independent child ideas, and you want to hand each child to a specific worker (the motivating case: a theme owner gives one child to Codex, another to a Claude dev agent, another to a human).
30
+ - An approved proposal has a **task DAG** and you want several developer agents working the unblocked tasks in parallel waves.
31
+ - You need an **independent adversarial review** of someone else's proposal / task / feature before it advances.
32
+ - You are the responsible owner and must keep **one accountable assignee per idea** while work fans out.
33
+
34
+ Prerequisite: delegating ideas needs `idea:admin`; delegating tasks needs `proposal:write`. Run `chorus_checkin()` first to confirm your permission set.
35
+
36
+ ---
37
+
38
+ ## Delegation primitives
39
+
40
+ ### Assign an idea — `chorus_pm_assign_idea` (`idea:admin`)
41
+
42
+ Hand a whole idea to a chosen agent or user. Parameters:
43
+
44
+ | Param | Meaning |
45
+ |-------|---------|
46
+ | `ideaUuid` | The idea to delegate |
47
+ | `assigneeType` | `"agent"` or `"user"` |
48
+ | `assigneeUuid` | The chosen agent/user UUID (resolve names with `chorus_search_mentionables`) |
49
+ | `instanceUuid` | *(optional, agent targets only)* pin the work to one durable AgentInstance — the `(agent, host, cwd)` place — so wakes land where the code lives |
50
+
51
+ Behavior you must understand:
52
+
53
+ - **The assignee is woken and advances from the idea's *current* stage** — it does **NOT** re-claim. If the idea is `open` it moves to `elaborating`; any other status is preserved. The assignee picks up wherever the idea already is (elaboration, ready-for-proposal, etc.).
54
+ - **Agent targets must hold `idea:write`** (via a preset such as `pm_agent`/`admin_agent`, or an explicit permission) or the call is rejected. **User targets must be in your company.** `instanceUuid` is rejected for user targets.
55
+ - **Silent takeover.** Reassigning an already-owned idea simply moves ownership to the new assignee — there is one owner at a time, no confirmation prompt. Use this deliberately, not by accident.
56
+
57
+ ### Assign a task — `chorus_pm_assign_task` (`proposal:write`)
58
+
59
+ Hand a single task to a developer agent. Parameters: `taskUuid`, `agentUuid` (must hold `task:write`), optional `instanceUuid`. The task must be `open` or `assigned`. The assignee is woken to execute it. Use this to distribute the tasks of an approved proposal.
60
+
61
+ ### Derive child ideas and fan them out
62
+
63
+ The theme-decomposition case, end to end:
64
+
65
+ 1. Read the container/theme idea and its context (`chorus_get_idea`, `chorus_get_documents`, `chorus_get_comments`).
66
+ 2. For each independent slice, create a child idea with `chorus_pm_create_idea` (link it back to the parent in the body / via `references[]`).
67
+ 3. Assign each child to a distinct owner with `chorus_pm_assign_idea` — e.g. one child to Codex, one to a Claude dev agent, one to a human. Each child now has its **own single owner** and advances independently.
68
+ 4. @mention each assignee and the theme owner so the delegation is visible.
69
+
70
+ ---
71
+
72
+ ## Independent review as an adversarial gate
73
+
74
+ Chorus ships three read-only reviewer skills. As orchestrator you run them at the three gates and act on the verdict — this is your primary quality lever when you are not writing the code yourself.
75
+
76
+ | Reviewer skill | Run after | Reviews |
77
+ |----------------|-----------|---------|
78
+ | `/proposal-reviewer` | a proposal is submitted | proposal draft quality (VERDICT on the proposal) |
79
+ | `/task-reviewer` | a task is submitted for verify | one task vs its acceptance criteria (VERDICT on the task) |
80
+ | `/code-reviewer` | the idea's last task is verified | the idea's **aggregate** code change — the final ship gateway (VERDICT on the idea) |
81
+
82
+ Spawn a read-only sub-agent with `sessions_spawn` and instruct it (in the spawn `task`) to run the matching reviewer skill against the entity (pass the `ideaUuid` for code review), then poll for its VERDICT. If spawning is disabled by policy, run the reviewer's procedure yourself as a focused read-only pass and record the VERDICT via `chorus_add_comment`. Each posts exactly one `VERDICT: PASS` / `PASS WITH NOTES` / `FAIL` comment. Verdicts are **advisory** — they do not auto-approve, auto-verify, or hard-block; you read the BLOCKERs and decide. A `FAIL` means route the BLOCKERs back for a fix before advancing (for a code-review FAIL, add fix tasks to the *approved* proposal via `/quick-dev` and re-run once they are `done`). See `/review` for the full pattern.
83
+
84
+ ---
85
+
86
+ ## Choosing a collaboration mode
87
+
88
+ Pick the lightest mode that fits the shape of the work:
89
+
90
+ | Mode | Use when | How you run it |
91
+ |------|----------|----------------|
92
+ | **Single-owner drives one idea** | The work is one coherent feature | Assign the idea once (`chorus_pm_assign_idea`); that owner runs idea → proposal → tasks; you gatekeep the gates |
93
+ | **Fan-out children to N agents** | A theme decomposes into independent slices | Derive child ideas, assign each to a distinct owner; children run in parallel, each single-owner |
94
+ | **Parallel task waves** | One approved proposal with a task DAG | Assign the currently-unblocked tasks (`chorus_pm_assign_task`) to several dev agents; as tasks reach `done`, assign the next wave |
95
+ | **Review-only** | Work is already produced elsewhere | Spawn the relevant reviewer, read the VERDICT, and gatekeep — no new delegation |
96
+
97
+ Guidance: start narrow. If a single owner can hold the whole feature in their head, prefer **single-owner** — coordination overhead is not free. Reach for **fan-out** only when slices are genuinely independent (separate scope, separable elaboration). Use **parallel task waves** only after a proposal is approved and its DAG is real; respect dependencies — `to_verify` does **not** unblock downstream, only `done` does.
98
+
99
+ ---
100
+
101
+ ## Single-owner & concurrency discipline
102
+
103
+ - **One responsible assignee per idea at a time.** This mirrors the daemon's single-owner semantics: the idea is the authoritative pin root, and its owner's proposals/tasks/wakes inherit that identity. Don't leave an idea ambiguously "owned by the team."
104
+ - **Don't race duplicate sessions on the same work.** Two daemon sessions (or two agents) driving the same idea/task will collide on status transitions and produce conflicting wakes. Assign, then let one owner run.
105
+ - **Pin with `instanceUuid` when the work is tied to a place.** If a child idea's code lives on a specific host/cwd, pin the assignment to that AgentInstance so every downstream wake lands there instead of a random daemon.
106
+
107
+ ---
108
+
109
+ ## Reversed-Conversation gates (you never auto-ship)
110
+
111
+ Chorus is **AI proposes, humans verify**. As orchestrator you enforce that, you do not bypass it:
112
+
113
+ - **Proposal gate.** When a delegated owner submits a proposal, STOP. Run the proposal reviewer, then hand the approve/reject decision to the human owner. Do not self-approve just because you *can* (`proposal:admin`).
114
+ - **Verify gate.** When a task reaches `to_verify`, STOP. Run the task reviewer, then let the human verify. Permission to verify is not authorization to rubber-stamp your own coordinated work.
115
+ - **Never merge or push.** The orchestrator drives work up to "PR ready" and hands it back to the human — it does not merge, push, or otherwise ship autonomously.
116
+
117
+ @mention the owner at each gate so the handoff is explicit and auditable.
118
+
119
+ ---
120
+
121
+ ## Derive a child idea vs add a task vs assign directly
122
+
123
+ | Do this | When |
124
+ |---------|------|
125
+ | **Derive a new child idea** (`chorus_pm_create_idea` + assign) | The slice is genuinely separate scope that deserves its own elaboration, proposal, and owner — a theme decomposition, or a parallelizable sub-feature. |
126
+ | **Add a task to an approved proposal** (`chorus_create_tasks` with `proposalUuid`) | The work is a discrete unit of the **same** feature that already has a proposal — e.g. code-review fix tasks, or a follow-up step in an existing DAG. |
127
+ | **Assign the existing idea/task directly** (`chorus_pm_assign_idea` / `chorus_pm_assign_task`) | The work is already scoped and just needs a (different) owner or executor — reassignment, taking over a stalled idea, or distributing existing tasks. |
128
+
129
+ Rule of thumb: **new scope → child idea; same-proposal unit → task; only the owner changes → assign.**
@@ -4,7 +4,7 @@ description: Chorus Proposal workflow — create proposals with document and tas
4
4
  license: AGPL-3.0
5
5
  metadata:
6
6
  author: chorus
7
- version: "0.16.2"
7
+ version: "0.17.0"
8
8
  category: project-management
9
9
  mcp_server: chorus
10
10
  ---
@@ -97,7 +97,7 @@ Branch on the result:
97
97
 
98
98
  - **OpenSpec active (all three checks pass)** → follow `openspec-aware` §3. Pick `$SLUG`, scaffold `openspec/changes/<slug>/`, author `proposal.md` / `design.md` / `specs/<capability>/spec.md` locally, then create the proposal container (Step 1 above) with the literal line `OpenSpec change slug: <slug>` in `description`, and mirror each local file into a document draft.
99
99
 
100
- > **⛔ Mandatory in OpenSpec mode:** mirror calls go through the `chorus-api.sh` wrapper with `content` produced by `json_encode_file` — see `openspec-aware` §3.6. Do **not** call `chorus_pm_add_document_draft` directly from the MCP harness with a hand-typed `content` field. Re-typing thousands of lines through the LLM burns 20k+ content tokens per proposal and breaks byte-equality with the local source of truth (`openspec-aware` §2 Rule 1 explains the full reasoning). Skip Step 2 below when in OpenSpec mode — the wrapper-based flow in `openspec-aware` §3.6 replaces it for documents.
100
+ > **⛔ Mandatory in OpenSpec mode:** mirror calls fill `content` from the local file — prefer `chorus mcp call … --arg-file content=<file>`, falling back to the `chorus-api.sh` wrapper with `json_encode_file` when `chorus` is not on `PATH` — see `openspec-aware` §3.6. Do **not** call `chorus_pm_add_document_draft` directly from the MCP harness with a hand-typed `content` field. Re-typing thousands of lines through the LLM burns 20k+ content tokens per proposal and breaks byte-equality with the local source of truth (`openspec-aware` §2 Rule 1 explains the full reasoning). Skip Step 2 below when in OpenSpec mode — the file-fill flow in `openspec-aware` §3.6 replaces it for documents.
101
101
 
102
102
  - **OpenSpec inactive (any check fails, or `CHORUS_OPENSPEC_MODE=off`)** → proceed with Step 2 unchanged. Author drafts inline as free-form Markdown via direct MCP `chorus_pm_add_document_draft`.
103
103
 
@@ -4,7 +4,7 @@ description: Adversarial read-only review of a submitted Chorus proposal — doc
4
4
  license: AGPL-3.0
5
5
  metadata:
6
6
  author: chorus
7
- version: "0.16.2"
7
+ version: "0.17.0"
8
8
  category: project-management
9
9
  mcp_server: chorus
10
10
  ---
@@ -4,7 +4,7 @@ description: Quick Task workflow — skip Idea→Proposal, create tasks directly
4
4
  license: AGPL-3.0
5
5
  metadata:
6
6
  author: chorus
7
- version: "0.16.2"
7
+ version: "0.17.0"
8
8
  category: project-management
9
9
  mcp_server: chorus
10
10
  ---
@@ -4,7 +4,7 @@ description: Chorus Review workflow — approve/reject proposals, verify tasks,
4
4
  license: AGPL-3.0
5
5
  metadata:
6
6
  author: chorus
7
- version: "0.16.2"
7
+ version: "0.17.0"
8
8
  category: project-management
9
9
  mcp_server: chorus
10
10
  ---
@@ -4,7 +4,7 @@ description: Adversarial verification of a submitted Chorus task against its AC
4
4
  license: AGPL-3.0
5
5
  metadata:
6
6
  author: chorus
7
- version: "0.16.2"
7
+ version: "0.17.0"
8
8
  category: project-management
9
9
  mcp_server: chorus
10
10
  ---
@@ -4,7 +4,7 @@ description: Full-auto AI-DLC pipeline — from prompt to done. Automates the en
4
4
  license: AGPL-3.0
5
5
  metadata:
6
6
  author: chorus
7
- version: "0.16.2"
7
+ version: "0.17.0"
8
8
  category: project-management
9
9
  mcp_server: chorus
10
10
  ---
@@ -213,8 +213,8 @@ In /yolo mode, the agent generates elaboration questions and answers them itself
213
213
  **2a. OpenSpec mode (all three checks pass).** Follow `openspec-aware` §3 end-to-end:
214
214
  - Pick `$SLUG`, run `openspec new change "$SLUG"` (§3.1–§3.2).
215
215
  - Author `proposal.md`, `design.md`, and one `specs/<capability>/spec.md` per capability locally on disk (§3.3). ADDED Requirements only; per-spec fallback to free-form Markdown if MODIFIED/REMOVED is needed.
216
- - Define `json_encode_file`, `chorus_check_response` helpers (§3.4, §6).
217
- - Mirror each local file via `chorus-api.sh mcp-tool chorus_pm_add_document_draft "$PAYLOAD"` (§3.6) — one call per file, with the document type from `openspec-aware` §5.
216
+ - Define the `chorus_check_response` helper6); prefer `chorus mcp call … --arg-file content=<file>` for mirrors (§3.4/§3.6) — the bash-wrapper fallback's `json_encode_file` is only needed when `chorus` is not on `PATH`.
217
+ - Mirror each local file via `chorus mcp call chorus_pm_add_document_draft … --arg-file content=<file>` (§3.6; fallback = `chorus-api.sh mcp-tool chorus_pm_add_document_draft "$PAYLOAD"`) — one call per file, with the document type from `openspec-aware` §5.
218
218
 
219
219
  > **⛔ Do not** invoke `chorus_pm_add_document_draft` / `chorus_pm_update_document_draft` / `chorus_pm_update_document` from the MCP harness with a hand-typed `content` field in this branch. Re-typing the markdown body wastes 20k+ tokens per proposal and breaks byte-equality with the local files. See `openspec-aware` §2 Rule 1.
220
220
 
package/src/commands.ts CHANGED
@@ -2,13 +2,14 @@ import type { ChorusMcpClient } from "./mcp-client.js";
2
2
 
3
3
  // ===== Response types from Chorus MCP tools =====
4
4
  //
5
- // These mirror the CURRENT (Chorus 0.7.2+) tool output shapes:
6
- // - chorus_checkin → { checkinTime, agent, ideaTracker, notifications }
5
+ // These mirror the CURRENT (Chorus 0.17.0+) tool output shapes:
6
+ // - chorus_checkin → { checkinTime, agent, activeProjects, guidance, notifications }
7
7
  // - chorus_get_my_assignments → { ideaTracker, taskTracker }
8
- // Both `ideaTracker` and `taskTracker` are Records keyed by project UUID, with
9
- // the work items nested inside each project bucket. Every field is read
10
- // defensively (optional chaining) so a missing/renamed field degrades to "0"
11
- // or "none" rather than throwing.
8
+ // chorus_checkin returns a per-project `activeProjects` distribution (name +
9
+ // activeIdeaCount, NOT a per-idea list); the full per-idea `ideaTracker` lives
10
+ // only in chorus_get_my_assignments. Both trackers are Records keyed by project
11
+ // UUID. Every field is read defensively (optional chaining) so a missing/renamed
12
+ // field degrades to "0"/"none" rather than throwing.
12
13
 
13
14
  interface IdeaTrackerEntry {
14
15
  uuid: string;
@@ -36,6 +37,11 @@ interface TaskTrackerProject {
36
37
  tasks?: TaskTrackerEntry[];
37
38
  }
38
39
 
40
+ interface CheckinActiveProject {
41
+ name?: string;
42
+ activeIdeaCount?: number;
43
+ }
44
+
39
45
  interface CheckinResponse {
40
46
  checkinTime?: string;
41
47
  agent?: {
@@ -43,7 +49,7 @@ interface CheckinResponse {
43
49
  name?: string;
44
50
  persona?: string | null;
45
51
  };
46
- ideaTracker?: Record<string, IdeaTrackerProject>;
52
+ activeProjects?: Record<string, CheckinActiveProject>;
47
53
  notifications?: {
48
54
  unread?: number;
49
55
  };
@@ -71,6 +77,7 @@ const PLUGIN_SKILLS = [
71
77
  { name: "review", description: "Approve/reject proposals, verify tasks, project governance" },
72
78
  { name: "yolo", description: "Full-auto AI-DLC pipeline — from prompt to done" },
73
79
  { name: "openspec-aware", description: "Opt-in OpenSpec authoring for PM workflows when the openspec CLI is present" },
80
+ { name: "chorus-cli", description: "Install, configure agents (chorus agents add|remove|list), env vars, and chorus mcp operations" },
74
81
  ] as const;
75
82
 
76
83
  // ===== Formatting helpers =====
@@ -97,24 +104,17 @@ function formatSkillsList(): string {
97
104
  ].join("\n");
98
105
  }
99
106
 
100
- // Sum a count across every project bucket in a tracker Record.
101
- function countTracker<T>(
102
- tracker: Record<string, { ideas?: T[]; tasks?: T[] }> | undefined,
103
- key: "ideas" | "tasks"
104
- ): number {
105
- if (!tracker) return 0;
106
- return Object.values(tracker).reduce((total, project) => {
107
- const items = key === "ideas" ? project.ideas : project.tasks;
108
- return total + (items?.length ?? 0);
109
- }, 0);
110
- }
111
-
112
107
  function formatStatus(checkin: CheckinResponse, connectionStatus: string): string {
113
- const ideaCount = countTracker(checkin?.ideaTracker, "ideas");
108
+ const projects = Object.values(checkin?.activeProjects ?? {});
109
+ const activeIdeaTotal = projects.reduce(
110
+ (total, p) => total + (p.activeIdeaCount ?? 0),
111
+ 0
112
+ );
114
113
  const lines: string[] = [
115
114
  `Connection: ${connectionStatus}`,
116
115
  `Agent: ${checkin?.agent?.name ?? "unknown"}`,
117
- `Assigned ideas: ${ideaCount}`,
116
+ `Active projects: ${projects.length} (${activeIdeaTotal} active idea(s))`,
117
+ ...projects.map((p) => ` - ${p.name ?? "(unnamed)"}: ${p.activeIdeaCount ?? 0}`),
118
118
  `Notifications: ${checkin?.notifications?.unread ?? 0} unread`,
119
119
  `Skills: ${PLUGIN_SKILLS.map((s) => s.name).join(", ")}`,
120
120
  ];
@@ -402,7 +402,7 @@ export class OpenClawDaemonClient {
402
402
  this.emitExecutionSnapshot();
403
403
  }
404
404
  let advancedToRunning = false;
405
- await this.advanceTurn(reportSessionId, "running", entity);
405
+ const runningTurnUuid = await this.advanceTurn(reportSessionId, "running", entity);
406
406
  advancedToRunning = true;
407
407
 
408
408
  const runId = this.nextRunId(contextKey);
@@ -462,7 +462,7 @@ export class OpenClawDaemonClient {
462
462
  // clean, aborted, or crashed). Guarded on advancedToRunning so a never-started wake
463
463
  // never attempts an illegal pending→ended transition.
464
464
  if (advancedToRunning) {
465
- await this.advanceTurn(reportSessionId, "ended", entity);
465
+ await this.advanceTurn(reportSessionId, "ended", entity, runningTurnUuid);
466
466
  }
467
467
 
468
468
  // Interrupt-vs-crash reporting (entity-keyed — only for a reportable resource).
@@ -533,16 +533,20 @@ export class OpenClawDaemonClient {
533
533
  sessionId: string,
534
534
  status: "running" | "ended",
535
535
  entity: { entityType: string; entityUuid: string } | null,
536
- ): Promise<void> {
536
+ turnUuid: string | null = null,
537
+ ): Promise<string | null> {
537
538
  try {
538
- await this.restClient.turnAdvance({
539
+ const outcome = await this.restClient.turnAdvance({
539
540
  sessionId,
541
+ ...(turnUuid ? { turnUuid } : {}),
540
542
  status,
541
543
  entityType: entity?.entityType ?? null,
542
544
  entityUuid: entity?.entityUuid ?? null,
543
545
  });
546
+ return typeof outcome.data?.turnUuid === "string" ? outcome.data.turnUuid : null;
544
547
  } catch (err) {
545
548
  this.logger.warn(`[Chorus] advanceTurn failed for session ${sessionId} → ${status}: ${err}`);
549
+ return null;
546
550
  }
547
551
  }
548
552