@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.
- package/dist/commands.js +5 -11
- package/dist/commands.js.map +1 -1
- package/dist/daemon-client.d.ts.map +1 -1
- package/dist/daemon-client.js +7 -4
- package/dist/daemon-client.js.map +1 -1
- package/dist/daemon-rest-client.d.ts +4 -1
- package/dist/daemon-rest-client.d.ts.map +1 -1
- package/dist/daemon-rest-client.js +31 -7
- package/dist/daemon-rest-client.js.map +1 -1
- package/dist/event-router.d.ts +9 -0
- package/dist/event-router.d.ts.map +1 -1
- package/dist/event-router.js +13 -2
- package/dist/event-router.js.map +1 -1
- package/package.json +1 -1
- package/skills/brainstorm/SKILL.md +1 -1
- package/skills/chorus/SKILL.md +2 -1
- package/skills/chorus-cli/SKILL.md +57 -0
- package/skills/code-reviewer/SKILL.md +1 -1
- package/skills/develop/SKILL.md +2 -2
- package/skills/docs/SKILL.md +1 -1
- package/skills/idea/SKILL.md +2 -1
- package/skills/openspec-aware/SKILL.md +90 -45
- package/skills/orchestrate/SKILL.md +129 -0
- package/skills/proposal/SKILL.md +2 -2
- package/skills/proposal-reviewer/SKILL.md +1 -1
- package/skills/quick-dev/SKILL.md +1 -1
- package/skills/review/SKILL.md +1 -1
- package/skills/task-reviewer/SKILL.md +1 -1
- package/skills/yolo/SKILL.md +3 -3
- package/src/commands.ts +21 -21
- package/src/daemon-client.ts +8 -4
- package/src/daemon-rest-client.ts +37 -9
- package/src/event-router.ts +13 -2
|
@@ -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
|
|
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.
|
|
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
|
|
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 —
|
|
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**
|
|
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
|
-
|
|
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.
|
|
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
|
|
78
|
-
3. **Single source of truth.** With
|
|
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.**
|
|
81
|
-
> - call
|
|
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
|
-
>
|
|
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
|
|
202
|
+
### 3.4 Filling the `content` field byte-exact
|
|
200
203
|
|
|
201
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
246
|
+
#### Fallback — when the `chorus` CLI is not on `PATH`
|
|
239
247
|
|
|
240
|
-
|
|
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
|
-
|
|
283
|
+
### 3.7 Editing a draft after the first mirror
|
|
261
284
|
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
422
|
-
g. For each row in §5 with "yes" — mirror via `chorus
|
|
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.**
|
package/skills/proposal/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
package/skills/review/SKILL.md
CHANGED
package/skills/yolo/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
|
217
|
-
- Mirror each local file via `chorus-api.sh mcp-tool chorus_pm_add_document_draft "$PAYLOAD"`
|
|
216
|
+
- Define the `chorus_check_response` helper (§6); 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.
|
|
6
|
-
// - chorus_checkin → { checkinTime, agent,
|
|
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
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
`
|
|
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
|
];
|
package/src/daemon-client.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|