--- name: plugin-maintenance description: Guide for modifying the Chorus plugin (Claude Code, Codex, OpenClaw, Kiro, Pi, dsh, and Hermes ports), updating skill documentation, and releasing new plugin versions. license: AGPL-3.0 metadata: author: chorus version: "0.5.0" category: development --- # Chorus Plugin & Skill Maintenance How to modify the Chorus plugin, update skill documentation, and release new versions. **Seven plugin packages** are maintained in parallel — Claude Code, Codex, OpenClaw, Kiro, Pi, dsh (DeepSeek Harness), and Hermes Agent — plus the standalone skill surface. That is **eight skill surfaces total**; when you change skill content, sweep all eight (see [Skill Content Changes — Seven Surfaces](#skill-content-changes--seven-surfaces), which also lists the Hermes copy). The Hermes port has its own section: [Hermes Agent port](#hermes-agent-port). ## File Structure ``` .claude-plugin/ marketplace.json ← Claude Code marketplace registry (version here) public/chorus-plugin/ ← Claude Code plugin package .claude-plugin/ plugin.json ← Plugin metadata (version here) hooks.json ← Hook definitions (SubagentStart, etc.) bin/ ← Hook scripts (bash) — stateful via API state-get/set skills/ chorus/SKILL.md ← Core skill develop/ idea/ proposal/ quick-dev/ review/ yolo/SKILL.md agents/ ← Reviewer agents as .md (Claude Code style) proposal-reviewer.md task-reviewer.md plugins/chorus/ ← Codex plugin package (separate from Claude Code) .codex-plugin/ plugin.json ← Plugin metadata (version here) hooks.json hooks/ ← Hook scripts (bash) — intentionally stateless on-session-start.sh on-post-submit-proposal.sh on-post-submit-for-verify.sh chorus-mcp-call.sh ← MCP helper (has hardcoded clientInfo version) hook-output.sh skills/ chorus/SKILL.md ← Codex port — mentions $skill syntax, ~/.codex/config.toml develop/ idea/ proposal/ quick-dev/ review/ yolo/SKILL.md chorus-proposal-reviewer/SKILL.md ← Reviewers as skills in Codex (no agents/) chorus-task-reviewer/SKILL.md packages/openclaw-plugin/ ← OpenClaw plugin package (TS runtime + skills) openclaw.plugin.json ← Plugin manifest (id, skills dir, activation, configSchema) — NO version field package.json ← npm package (version here; `openclaw` block: extensions, runtimeExtensions, install/compat) src/ ← TypeScript runtime (index.ts, mcp-client.ts, sse-listener.ts, event-router.ts, wake.ts) — real-time SSE event bridge + MCP registration; NOT bash hooks dist/ ← Compiled JS (npm install loads this; linked install loads src/ via jiti) skills/ chorus/SKILL.md ← OpenClaw port — tools namespaced `chorus__`, inline OpenSpec detection develop/ idea/ proposal/ quick-dev/ review/ yolo/ brainstorm/ openspec-aware/SKILL.md proposal-reviewer/SKILL.md ← Reviewers as skills (like Codex, no agents/) task-reviewer/SKILL.md public/kiro-plugin/ ← Kiro CLI plugin (loose .kiro/ template tree + install script) .kiro/ settings/mcp.json ← Chorus remote MCP server (${env:CHORUS_API_KEY} bearer, disabled:false) skills/chorus-*/SKILL.md ← the chorus-PREFIXED skills (no bare names — global-install distinctiveness) agents/chorus.json ← main agent (.json — Kiro CLI, NOT .md); hosts all hooks via __CHORUS_BIN__ placeholder agents/chorus.md ← main-agent system-prompt sidecar (file://./chorus.md) agents/chorus-*-reviewer.json ← 3 read-only reviewer subagents (tools:["read","@chorus"]) steering/chorus.md ← platform overview + AI-DLC context (folds in the `chorus` overview skill) bin/ ← Hook scripts (bash, 3.2-safe) + chorus-api.sh + test-syntax.sh installer copies these into /chorus-bin/ and resolves __CHORUS_BIN__ (public/install-kiro.sh) ← deprecation stub → redirects to `chorus agents add`; the .kiro/ tree is installed by cli/init/file-template.mjs (asset list: public/kiro-plugin/manifest.txt) packages/chorus-pi/ ← Pi coding agent package (TS extension + skills) package.json ← npm package + Pi manifest (version here) extensions/chorus.ts ← Native Pi event handlers; auto checkin/session lifecycle/reviewer nudges lib/lib.ts ← Pure helpers used by the extension and unit tests bin/chorus-mcp-call.sh ← MCP helper for byte-exact OpenSpec document mirroring skills/ chorus/SKILL.md ← Core skill; Pi `/skill:` syntax develop/ idea/ proposal/ quick-dev/ review/ yolo/ openspec-aware/SKILL.md agents/ ← 3 read-only reviewer agents copied to ~/.pi/agent/agents/ test/ ← Static, helper-unit, extension-event, and manual-session verification packages/chorus-dsh/ ← DeepSeek Harness (dsh) plugin — published npm bundle @chorus-aidlc/chorus-dsh package.json ← npm package + dsh bundle patch (version here; tracks the APP version, e.g. 0.16.3) src/index.ts ← Cordis plugin apply(): injects skills/persona/MCP wrapper; daemon-backend gate cordis.patch.yml ← dsh composition patch shipped in the bundle bin/chorus-mcp-call.mjs ← Node MCP-over-HTTP helper (clientInfo.version auto-read from package.json — no manual bump) skills/ chorus/SKILL.md ← Core overview (dsh HAS a chorus/ overview, unlike Kiro) -chorus/SKILL.md ← 13 stage/reviewer skills, `-chorus` SUFFIX (brainstorm/develop/idea/ proposal/quick-dev/review/yolo/docs/orchestrate/openspec-aware/ code-reviewer/proposal-reviewer/task-reviewer) packages/chorus-hermes/ ← Hermes Agent (Nous Research) port — NOT on npm/PyPI; installed from a git subdir pinned to a SHA chorus/ ← native plugin (plugin.yaml + register(ctx)); see "Hermes Agent port" chorus-mcp/ ← portable Agent Plugins v1 package (plugin.json + mcp.json): the chorus MCP server public/skill/ ← Standalone skill (any MCP-compatible agent) chorus/SKILL.md ← Same structure, softer language, IDE-agnostic proposal-chorus/SKILL.md quick-dev-chorus/SKILL.md ``` ### Plugin key differences | Aspect | Claude Code | Codex | OpenClaw | Kiro CLI | Pi | |--------|-------------|-------|----------|----------|----| | Skill invocation | `/chorus:develop` | `$develop` | `/develop` | `/chorus-develop` | `/skill:develop` | | Tool names | `chorus_` | `chorus_` | `chorus__` | `chorus_` / `@chorus` matcher | Native `mcp__chorus__chorus_` (direct/codemode); legacy adapter5 direct `chorus_chorus_` or bare; extension uses backend names | | MCP config | `.mcp.json` | `~/.codex/config.toml` | Plugin config | `~/.kiro/settings/mcp.json` | Native global `~/.pi/agent/mcp.json` / trusted project `.pi/mcp.json`; legacy adapter5 global `mcp-adapter.json` | | Session lifecycle | SubagentStart/Stop hooks | Manual/stateless | Manual | `agentSpawn`/`stop` hooks | Automatic via mutable `subagent_spawn` and `subagent_manage` events | | Reviewers | `agents/*.md` via Task | Skills mounted in `spawn_agent` | Reviewer skills via `sessions_spawn` | Native JSON subagents | `agents/chorus-*-reviewer.md` via `pi-subagents` | | User interaction | `AskUserQuestion` | Plain text | Plain text | Plain text | Plain text | | OpenSpec detection | SessionStart hook | SessionStart hook | Inline | `agentSpawn` hook | `session_start` extension event | | Runtime/hooks | Stateful bash hooks | Stateless bash hooks | TypeScript SSE runtime | Bash 3.2 hooks in `chorus.json` | TypeScript native extension; shell only for OpenSpec mirroring | | Task execution | Agent Teams waves | `spawn_agent` | Main-agent waves | Kiro subagents | `subagent_spawn` workers | | Install | Marketplace | `install-codex.sh` | OpenClaw plugin manager | `install-kiro.sh` | npm (`pi install npm:@chorus-aidlc/chorus-pi`) | When porting a change between plugins, preserve these intentional differences. Don't add state files to the Codex/OpenClaw plugins, don't use `$`-prefix outside Codex, keep OpenClaw's `chorus__` names and manual-session/inline-detection wording, and preserve Kiro's `chorus-` skill prefix, `@chorus/` matchers, and `__CHORUS_BIN__` placeholder. For Pi, use `/skill:`, `subagent_spawn`, native extension events, and plain-text interaction; do not introduce Claude hooks or Codex session wording. **dsh (DeepSeek Harness)** is a published npm bundle (`@chorus-aidlc/chorus-dsh`) added to a dsh profile via `dsh plugin --profile add -w`. It is Cordis-based: `src/index.ts`'s `apply()` injects the `-chorus`-suffixed skills, persona, and a Node MCP wrapper. Skills use the `-chorus` **suffix** (like standalone) but it keeps a `chorus/SKILL.md` overview. Its version tracks the **app version** (0.16.3+), NOT the 0.9.x skill sequence. The dsh **daemon backend** (`cli/dsh-spawner.mjs` / `dsh-managed-config.mjs`) is currently **de-listed / offline** — kept dormant, not advertised; do NOT re-add it to the daemon install menu, CLI `--agent` help, or `DAEMON.md` / `MCP_TOOLS.md` without bringing the backend back online. ## When to Update What "All six plugin skills" = Claude Code + Codex + OpenClaw + Kiro + Pi + dsh. "All seven surfaces" additionally includes standalone `public/skill/`. Note the Kiro surface has **no `chorus/SKILL.md`** — its overview lives in `steering/chorus.md`, and its stage skills are `chorus-`prefixed; dsh keeps a `chorus/SKILL.md` overview and uses `-chorus`-**suffixed** stage skills. | Change | Files to update | |--------|----------------| | New MCP tool added | `src/mcp/tools/*.ts` + `docs/MCP_TOOLS.md` + all six plugin overviews (Kiro: `steering/chorus.md`) + standalone overview | | MCP tool description changed | `src/mcp/tools/*.ts` only (skill docs reference tool names, not descriptions) | | Skill content / wording (e.g. AC now required) | The matching stage skill in **all seven surfaces** | | New workflow step | All six plugin stage skills + standalone equivalent | | New Idea/Task status | All six plugin overviews + standalone overview + locale messages | | New execution rule | All six plugin overviews + standalone overview (softer wording) | | Permission model change | All six plugin overviews + affected stage skills + runtime checks where applicable | | Hook script change (Claude Code) | `public/chorus-plugin/bin/*.sh` + `hooks.json` if new hook. **Never copy hook changes blindly into `plugins/chorus/hooks/`** — Codex hooks are intentionally stateless and lack subagent events. OpenClaw has no bash hooks at all. | | Hook script change (Codex) | `plugins/chorus/hooks/*.sh` — rarely needed; session-start, post-submit-proposal, post-submit-for-verify are the only three. If bumping plugin version, also update the hardcoded `clientInfo.version` in `chorus-mcp-call.sh`. | | OpenClaw runtime change | `packages/openclaw-plugin/src/*.ts` (TS SSE/MCP runtime) + `npm run typecheck` + `npm run test`. Not bash hooks — this is a compiled TypeScript extension. | | Pi runtime change | `packages/chorus-pi/extensions/*.ts` + `lib/*.ts`; run `bash test/all.sh` from the package directory | | Any plugin change | Bump version in every file for that package (see Version Bump Checklist) | ## Version Bump Checklist Every time **any** plugin package changes, bump the version in **all** of that package's locations. There are two version sequences in play: - **Skill-frontmatter sequence** — shared by corresponding `SKILL.md` files across the six plugin ports. When the same skill content ships to multiple plugins, bump them together. - **Per-package plugin sequences** — Claude Code + Codex share one (`marketplace.json` / both `plugin.json`, currently `0.9.x`); OpenClaw's `package.json` has its **own** sequence (currently `0.5.x`). These are independent files — edit each. ### Claude Code plugin — bump together 1. `.claude-plugin/marketplace.json` — `"version": "X.Y.Z"` 2. `public/chorus-plugin/.claude-plugin/plugin.json` — `"version": "X.Y.Z"` 3. Every skill under `public/chorus-plugin/skills/*/SKILL.md` — `metadata.version: "X.Y.Z"` (all skills, including `quick-dev/`, now use the standard nested `metadata:` block) ### Codex plugin — bump together 4. `plugins/chorus/.codex-plugin/plugin.json` — `"version": "X.Y.Z"` 5. Every skill under `plugins/chorus/skills/*/SKILL.md` — `metadata.version: "X.Y.Z"` (all skills, including `quick-dev/`, now use the standard nested `metadata:` block). **Don't forget the two reviewer skills**: `chorus-proposal-reviewer/SKILL.md` and `chorus-task-reviewer/SKILL.md`. 6. `plugins/chorus/hooks/chorus-mcp-call.sh` — hardcoded `clientInfo.version` string in the JSON-RPC `initialize` payload ### OpenClaw plugin — bump together 7. `packages/openclaw-plugin/package.json` — `"version": "X.Y.Z"` using OpenClaw's **own** sequence (`0.5.x`), NOT the skill sequence. `openclaw.plugin.json` has **no** version field — nothing to edit there. 8. Every skill under `packages/openclaw-plugin/skills/*/SKILL.md` — `metadata.version: "X.Y.Z"` on the **skill sequence** (`0.9.x`, matching the other plugins' skills). Includes the two reviewer skills `proposal-reviewer/SKILL.md` and `task-reviewer/SKILL.md`. - **Do NOT** touch `src/mcp-client.ts`'s `clientInfo.version` (`0.1.0`) — it is a static MCP client identifier, not the plugin version. ### Kiro plugin — bump together Kiro has **no plugin.json / marketplace registry** (it reads loose `.kiro/` files), so the only versioned files are the skill frontmatters. 10. Every skill under `public/kiro-plugin/.kiro/skills/chorus-*/SKILL.md` — `metadata.version: "X.Y.Z"` on the shared skill sequence (all `chorus-*` skills). The `agents/*.json` and `steering/chorus.md` carry **no** version field — nothing to edit there. 11. `public/kiro-plugin/bin/chorus-api.sh` — hardcoded `clientInfo.version` string in the JSON-RPC `initialize` payload (same as the Codex `chorus-mcp-call.sh` helper). ### Pi package — bump together 12. `packages/chorus-pi/package.json` — `"version": "X.Y.Z"`. 13. Every skill under `packages/chorus-pi/skills/*/SKILL.md` — `metadata.version: "X.Y.Z"`, including `openspec-aware`. 14. `packages/chorus-pi/extensions/chorus.ts` — hardcoded MCP `clientInfo.version`. ### dsh (DeepSeek Harness) plugin — bump together dsh tracks the **app version** (currently `0.16.3`), NOT the 0.9.x skill sequence. 16. `packages/chorus-dsh/package.json` — `"version": "X.Y.Z"` (the published `@chorus-aidlc/chorus-dsh` bundle). 17. Every skill under `packages/chorus-dsh/skills/*/SKILL.md` — `metadata.version: "X.Y.Z"` (the `chorus/` overview + all `-chorus`-suffixed stage/reviewer skills). - **Do NOT** hardcode a version in `bin/chorus-mcp-call.mjs` — its `clientInfo.version` is auto-read from `package.json`, so it never drifts. ### Coordinated npm release identity When cutting an application release, the four public npm packages are one lockstep release unit. Set the same `X.Y.Z` in: 1. root `package.json` (`@chorus-aidlc/chorus`); 2. `packages/openclaw-plugin/package.json` (`@chorus-aidlc/chorus-openclaw-plugin`) and refresh its standalone `package-lock.json`; 3. `packages/chorus-dsh/package.json` (`@chorus-aidlc/chorus-dsh`); 4. `packages/chorus-pi/package.json` (`@chorus-aidlc/chorus-pi`) — no standalone lockfile to refresh (it rides the workspace `pnpm-lock.yaml`); a plain version bump does not dirty the lockfile, but if you changed its deps run `pnpm install --lockfile-only` at the repo root (the release preflight runs `pnpm install --frozen-lockfile`). Publishing GitHub Release `vX.Y.Z` triggers `.github/workflows/publish-npm.yml`. The workflow completes all four package build/pack contracts before any upload, then publishes CLI → OpenClaw → dsh → chorus-pi. The older interactive package publish scripts are maintenance tools, not the CI release entry point. For all four npm package settings, Trusted Publisher must use the exact workflow filename `publish-npm.yml` in the matching GitHub repository. Leave the npm Environment field blank while the workflow job has no `environment`; if an Environment is introduced, configure the exact same case-sensitive name on npm and on the workflow job. Keep `id-token: write`, do not add `NPM_TOKEN`, `NODE_AUTH_TOKEN`, or a setup-node token placeholder, and do not disable npm's automatic provenance. A zero exit status from `npm publish` completes the upload and is recorded as `accepted-by-npm`. Registry visibility and provenance metadata can lag behind acceptance; the workflow does not poll them or use them to gate subsequent packages. `@chorus-aidlc/chorus-pi` is the newest package: its Trusted Publisher must be registered on npmjs.org before its first coordinated publish, or the run stops at chorus-pi with the first three already published — a human/ops step, not something to fake or token-shim around. For partial publication, repair the external problem and choose **Re-run jobs** on the same failed Actions run. Do not create a replacement Release/version. The rerun skips exact versions already confirmed in npm and resumes in fixed order. A failed registry lookup is not a skip and must stop the run; the job summary identifies `failed` and `not-attempted` packages for diagnosis. ### Standalone skills — independent versioning 15. `public/skill/*/SKILL.md` — bump only the standalone skills that changed, using their own version sequence. Quick way to check all versions: ```bash grep -rn '"version"\|^ version:\|^version:\|clientInfo' \ .claude-plugin/marketplace.json \ public/chorus-plugin/.claude-plugin/plugin.json \ public/chorus-plugin/skills/*/SKILL.md \ plugins/chorus/.codex-plugin/plugin.json \ plugins/chorus/skills/*/SKILL.md \ plugins/chorus/hooks/chorus-mcp-call.sh \ packages/openclaw-plugin/package.json \ packages/openclaw-plugin/skills/*/SKILL.md \ public/kiro-plugin/.kiro/skills/*/SKILL.md \ public/kiro-plugin/bin/chorus-api.sh \ packages/chorus-pi/package.json \ packages/chorus-pi/skills/*/SKILL.md \ packages/chorus-pi/extensions/chorus.ts \ packages/chorus-dsh/package.json \ packages/chorus-dsh/skills/*/SKILL.md \ public/skill/*/SKILL.md ``` Users update via: ```bash /plugin update chorus@chorus-plugins # Claude Code codex plugin update chorus@chorus-plugins # Codex # OpenClaw: reinstall/update via the OpenClaw plugin manager (npm spec @chorus-aidlc/chorus-openclaw-plugin) # Kiro: re-run `chorus agents add --agents kiro` (idempotent; installs the .kiro/ tree via the file-template installer) # Pi: pi install npm:@chorus-aidlc/chorus-pi (reinstall to pull the latest published extension) ``` Pi is published to npm as `@chorus-aidlc/chorus-pi` and installs with `pi install npm:@chorus-aidlc/chorus-pi` — the same path `chorus agents add` automates (select Pi, or `--agents pi`). The old sparse-git-checkout / local-path workaround is no longer needed; local-path `pi install ./packages/chorus-pi` is now only for developing chorus-pi from the repo checkout. ## Skill Content Changes — Seven Surfaces Chorus skill content lives in **seven parallel surfaces**. A content change must sweep all seven: 1. `public/chorus-plugin/skills//SKILL.md` — Claude Code 2. `plugins/chorus/skills//SKILL.md` — Codex 3. `packages/openclaw-plugin/skills//SKILL.md` — OpenClaw 4. `public/kiro-plugin/.kiro/skills/chorus-/SKILL.md` — Kiro (note the `chorus-` **prefix**; the overview lives in `steering/chorus.md`, not a `chorus/SKILL.md`) 5. `packages/chorus-pi/skills//SKILL.md` — Pi 6. `packages/chorus-dsh/skills/-chorus/SKILL.md` — dsh (note the `-chorus` **suffix**; keeps a `chorus/SKILL.md` overview) - plus `packages/chorus-hermes/chorus/skills//SKILL.md` — Hermes (bare names, loaded as `skill_view("chorus:")`; reviewers are `chorus--reviewer` skills, not agent files). See [Hermes Agent port](#hermes-agent-port). 7. `public/skill/-chorus/SKILL.md` — standalone (note the `-chorus` suffix and flatter set) **Sweep command** — find every occurrence before editing so nothing is missed: ```bash grep -rniE "" \ public/chorus-plugin/skills/ plugins/chorus/skills/ \ packages/openclaw-plugin/skills/ public/kiro-plugin/.kiro/skills/ \ packages/chorus-pi/skills/ packages/chorus-dsh/skills/ \ packages/chorus-hermes/chorus/skills/ public/skill/ ``` Then bump the relevant version sequences (shared skill frontmatter across plugin surfaces 1–5, independent standalone version for #6, and per-package versions as needed). ## Porting Changes Between Plugins Whenever you change content in one plugin, mirror it into the other plugin surfaces unless the difference is intentional. Typical workflow: 1. Make the change in `public/chorus-plugin/skills//SKILL.md` 2. Diff-check the counterparts: - `diff public/chorus-plugin/skills//SKILL.md plugins/chorus/skills//SKILL.md` - `diff public/chorus-plugin/skills//SKILL.md packages/openclaw-plugin/skills//SKILL.md` - `diff public/chorus-plugin/skills//SKILL.md packages/chorus-pi/skills//SKILL.md` 3. Apply the same semantic change to every port, but **preserve** intentional phrasing: - **Codex**: `.mcp.json` → `~/.codex/config.toml`, `Task tool` → `spawn_agent`, `/chorus:X` → `$X`, "sessions auto-managed" → "sessions are optional / stateless port" - **OpenClaw**: tool names `chorus_` → `chorus__`, `AskUserQuestion` → plain-text prompt, reviewers via `sessions_spawn`, OpenSpec detection is inline (no SessionStart hook), sessions are manual - **Kiro**: `chorus-` skill prefix, native JSON subagents, `@chorus` tools, and hook-driven context - **Pi**: `/skill:X`, `subagent_spawn`, plain-text prompts, reviewer `.md` agents, extension-managed worker sessions 4. Bump all affected plugins' versions (all files in the Version Bump Checklist) 5. For the Codex plugin, also verify `chorus-mcp-call.sh` `clientInfo.version` matches ## Plugin vs Standalone Skill: Tone Differences The plugin skill targets Claude Code specifically. The standalone skill targets any MCP-compatible agent (Cursor, Kiro, etc.). | Aspect | Plugin (`public/chorus-plugin/skills/`) | Standalone (`public/skill/`) | |--------|----------------------------------------|------------------------------| | AskUserQuestion | "ALWAYS use... NEVER display as text" | "prefer your IDE's interactive prompt if available" | | Session management | "Do NOT create sessions — plugin handles it" | "Create or reopen a session before starting work" | | Skip elaboration | "you MUST ask the user for permission first" | "confirm with the user first" | | Hook references | References specific hooks (SubagentStart, etc.) | No hook references | **Rule of thumb**: Plugin version uses MUST/NEVER/ALWAYS. Standalone version uses "prefer", "confirm", "consider". ## Adding a New MCP Tool — Full Checklist 1. Implement in `src/mcp/tools/*.ts` (pm.ts, public.ts, etc.) 2. Add to `docs/MCP_TOOLS.md` 3. Update permission tables and tool lists in all seven overview surfaces: - `public/chorus-plugin/skills/chorus/SKILL.md` - `plugins/chorus/skills/chorus/SKILL.md` - `packages/openclaw-plugin/skills/chorus/SKILL.md` (use the `chorus__` namespaced form) - `public/kiro-plugin/.kiro/steering/chorus.md` (Kiro has no `chorus/SKILL.md` — the overview is the steering doc) - `packages/chorus-pi/skills/chorus/SKILL.md` - `packages/chorus-dsh/skills/chorus/SKILL.md` - `public/skill/chorus/SKILL.md` 4. If it changes a stage workflow, update the matching stage skill in all seven locations. 5. Bump every affected plugin's versions (see Version Bump Checklist) 6. Run `npx tsc --noEmit` to verify ## Modifying Hook Scripts ### Claude Code plugin hooks (`public/chorus-plugin/bin/`) - `on-session-start.sh` — SessionStart hook (caches `agent_permissions` via `"$API" state-set`) - `on-user-prompt.sh` — UserPromptSubmit hook - `on-subagent-start.sh` — SubagentStart hook - `on-subagent-stop.sh` — SubagentStop hook (reads `agent_permissions` via `state-get`) - `on-teammate-idle.sh` — TeammateIdle hook - `on-pre-enter-plan.sh`, `on-pre-exit-plan.sh` — Plan mode hooks - `on-task-completed.sh` — TaskCompleted hook - `on-post-submit-proposal.sh`, `on-post-submit-for-verify.sh` — PostToolUse reviewer reminders ### Codex plugin hooks (`plugins/chorus/hooks/`) - `on-session-start.sh` — SessionStart hook (stateless; no caching) - `on-post-submit-proposal.sh` — PostToolUse for `chorus_pm_submit_proposal` - `on-post-submit-for-verify.sh` — PostToolUse for `chorus_submit_for_verify` - `chorus-mcp-call.sh` — shared MCP-over-HTTP helper (bump `clientInfo.version` on release) - `hook-output.sh` — stdout-formatting helper **Codex has no SubagentStart/Stop events** — do not try to port lifecycle hooks from the Claude Code plugin. Instead, session management is documented as a main-agent responsibility in `plugins/chorus/skills/develop/SKILL.md` and `$yolo`. ### OpenClaw plugin — TypeScript runtime, not bash hooks (`packages/openclaw-plugin/src/`) OpenClaw has **no bash hooks at all**. Its real-time behavior is a compiled TypeScript extension declared in `package.json`'s `openclaw` block (`extensions: ["./src/index.ts"]`, `runtimeExtensions: ["./dist/index.js"]`): - `index.ts` — entry point / activation (`activation.onStartup` in `openclaw.plugin.json`) - `mcp-client.ts`, `mcp-registration.ts` — registers Chorus MCP tools (namespaced `chorus__`) - `sse-listener.ts`, `event-router.ts`, `wake.ts` — SSE event stream → agent wake (the OpenClaw analogue of the other plugins' notification hooks) - `config.ts`, `commands.ts` — config schema handling and slash commands After modifying the runtime: `cd packages/openclaw-plugin && npm run typecheck && npm run test`. A **linked** install loads `src/` directly via jiti; an **npm** install requires the compiled `dist/` (`npm run build`). Bash 3.2 rules do **not** apply here (it's TypeScript, not shell). Two known runtime gotchas: a linked install loads TS via jiti (no `dist`) while an npm install needs compiled `dist` + `runtimeExtensions`; and SSE→agent wake requires `activation.onStartup` + `runEmbeddedAgent` with an explicit provider/model. ### Kiro plugin hooks (`public/kiro-plugin/bin/`) Kiro hooks are bash (Bash-3.2 rules apply) but declared **inside `agents/chorus.json`** (Kiro has no standalone `hooks.json`), and each hook's STDOUT is added to the agent context as **plain text** (there is no `additionalContext` JSON envelope like Claude Code). Scripts: - `on-agent-spawn.sh` — `agentSpawn` hook: `chorus_checkin` → startup context (owner/permissions/idea-tracker). "Not configured" if Chorus env is unset; never aborts the spawn. - `on-stop.sh` — `stop` hook: best-effort session heartbeat/checkout; never blocks the turn. - `on-post-submit-proposal.sh` / `on-post-submit-for-verify.sh` / `on-post-verify-task.sh` — `postToolUse` hooks matched to `@chorus/chorus_pm_submit_proposal` / `@chorus/chorus_submit_for_verify` / `@chorus/chorus_admin_verify_task`; emit a nudge to spawn the matching reviewer subagent. Exit 0 with no output if no parseable UUID. - `chorus-api.sh` — the reused MCP-over-HTTP wrapper (bump the hardcoded `clientInfo.version` on release, like Codex's `chorus-mcp-call.sh`). Hook scripts reference it via a path relative to their own location, never a hard-coded repo path. - `test-syntax.sh` — Bash-3.2 parse + mock-event smoke test harness. Hook `command` strings in the repo `chorus.json` use the `__CHORUS_BIN__` placeholder; `public/install-kiro.sh` copies `bin/*.sh` into `/chorus-bin/`, `chmod +x`, and substitutes `__CHORUS_BIN__` with that absolute path. **Never commit a concrete machine path in the repo copy** — only the installed copy is concretized. ### Pi extension (`packages/chorus-pi/extensions/`) Pi uses a native TypeScript extension instead of lifecycle bash hooks: - `session_start` performs checkin, OpenSpec detection, and startup notification. - `before_agent_start` injects checkin context once. - Mutable `tool_call` on `subagent_spawn` creates a session only for `worker` and injects its UUID/workflow. - `tool_result` and `tool_execution_end` map spawned agent IDs, close sessions, and emit reviewer nudges. - `session_shutdown` retries retained session closures. Keep pure parsing/path/banner helpers in `lib/lib.ts`. Preserve failed-close mappings for shutdown retry, and do not create sessions for scout/planner/reviewer/custom agents. The OpenSpec shell wrapper must resolve both env and `.mcp.json` configuration, but it is not a lifecycle hook. After modifying the Pi package, run `cd packages/chorus-pi && bash test/all.sh`. This covers static checks, pure helper tests, and extension-event tests; update `test/verify-pi-session.md` when behavior requires live Pi verification. **CRITICAL: All hook scripts MUST be compatible with Bash 3.2.** macOS ships with `/bin/bash` 3.2 (due to GPL licensing) and Claude Code + Codex + Kiro all use it to execute hooks. Do NOT use Bash 4+ features: | Bash 4+ (FORBIDDEN) | Bash 3.2 alternative | |---------------------|---------------------| | `${VAR,,}` (lowercase) | `$(printf '%s' "$VAR" \| tr '[:upper:]' '[:lower:]')` | | `${VAR^^}` (uppercase) | `$(printf '%s' "$VAR" \| tr '[:lower:]' '[:upper:]')` | | `declare -A` (associative arrays) | Use separate variables or `jq` | | `readarray` / `mapfile` | `while IFS= read -r line` loop | | `\|&` (pipe stderr) | `2>&1 \|` | | `&>>` (append both) | `>> file 2>&1` | After modifying: 1. Run `/bin/bash public/chorus-plugin/bin/test-syntax.sh` (Claude Code + Codex hooks) and `/bin/bash public/kiro-plugin/bin/test-syntax.sh` (Kiro hooks) on macOS to verify Bash 3.2 compatibility — OpenClaw and Pi lifecycle code are TypeScript 2. Test locally: `claude --plugin-dir public/chorus-plugin` (Claude Code), install via `codex plugin install` and reload (Codex), re-run `install-kiro.sh` (Kiro), or `pi install "$PWD/packages/chorus-pi"` from the Chorus checkout (Pi) 3. Bump plugin version for whichever packages changed (all affected packages) 4. Users must restart the affected agent after updating (Kiro: re-run the installer; Pi: reinstall the package) ## Testing Plugin Changes ```bash # Claude Code — load plugin locally (no install needed) claude --plugin-dir public/chorus-plugin # Or update installed plugin /plugin update chorus@chorus-plugins # Verify plugin loaded /plugin list # OpenClaw — typecheck + test the TypeScript runtime cd packages/openclaw-plugin && npm run typecheck && npm run test # Kiro — dry-run the installer into a throwaway HOME, then assert the tree HOME=$(mktemp -d) CHORUS_URL=https://example.com CHORUS_API_KEY=cho_test \ bash public/install-kiro.sh < /dev/null # verify hook scripts parse under Bash 3.2 bash public/kiro-plugin/bin/test-syntax.sh # Pi — static checks + helper and extension-event tests cd packages/chorus-pi && bash test/all.sh # dsh — typecheck + lint + unit tests + published-bundle validation cd packages/chorus-dsh && pnpm run typecheck && pnpm run lint && pnpm test && pnpm run check:package # Hermes — pytest (fakes only; node for prompt parity; no Hermes install needed) python -m pytest packages/chorus-hermes/chorus/tests ``` ## Hermes Agent port Source: `packages/chorus-hermes/`. User docs: `packages/chorus-hermes/README.md` (install, configure, troubleshooting, Hermes compatibility notes with file:line evidence) and `docs/CONNECT_HERMES{,.zh}.md`. Design/spec: `openspec/changes/add-hermes-plugin/`. Verified against Hermes commit `2b52acc2d`. ### Layout — two directories, installed separately ``` packages/chorus-hermes/ README.md chorus/ ← native plugin, installed as ~/.hermes/plugins/chorus plugin.yaml ← name: chorus, kind: standalone (no "general" kind), version, provides_hooks __init__.py ← register(ctx): hooks, skills, gateway platform, approval transport chorus_hermes/ ← config, mcp_client, rest, hooks, reminders, spec_mode, skills (reviewer guard), sse, router, prompts (port of cli/prompts.mjs), adapter, turns, approval skills//SKILL.md ← independent Hermes copy (see below) tests/ ← pytest; fixtures/ holds the prompt-parity fixtures + node renderer chorus-mcp/ ← portable package, installed as ~/.hermes/plugins/chorus-mcp plugin.json ← name: chorus-mcp, version mcp.json ← chorus server, LITERAL http://localhost:8637/api/mcp + Bearer ${CHORUS_API_KEY} ``` Why two: Hermes declares MCP servers only in portable packages, and only a native plugin can register hooks, skills, a platform and an approval transport. Keep `mcp.json` out of `chorus/` and Python out of `chorus-mcp/` (enforced by `test_package.py`). Portable `mcp.json` URLs are not `${VAR}`-expanded, so do NOT "fix" the literal loopback URL; remote Chorus uses a native `mcp_servers.chorus` entry, which `chorus agents add` writes (`cli/init/hermes-mcp-config.mjs`). Keep `provides_hooks` in `plugin.yaml` equal to what `register()` registers, or `hermes plugins validate` fails. Both directories must keep scanning `safe` — git installs are `community` trust and a `caution` verdict blocks the install (the scanner flags phrases like "you are … now"; see the `_NOW` workaround in `prompts.py`). Runtime model: online scheduling through the `chorus` gateway platform (SSE client type `hermes`), like OpenClaw — **not** the Chorus daemon (`agent-type-map.mjs` maps hermes → `offline`). Operation turns (`research_requested`, `idea_creation_requested`) are skipped by the router, same as OpenClaw. ### Version sync Hermes manifests are not npm packages, but they track the **app version** (root `package.json`) and pytest fails on drift. On every release bump, set `X.Y.Z` in: - `packages/chorus-hermes/chorus/plugin.yaml` — `version:` - `packages/chorus-hermes/chorus-mcp/plugin.json` — `"version"` - every `packages/chorus-hermes/chorus/skills/*/SKILL.md` — `metadata.version` (`test_skills.py::test_frontmatter`) The gateway's SSE `clientVersion` is read from `plugin.yaml` at runtime (`adapter._client_version`), and `mcp_client.py` sends no `initialize`/`clientInfo` — no version is hardcoded in Python. ### Independent skill copy `chorus/skills/` is its own copy (started from `packages/chorus-pi/skills`), registered with `ctx.register_skill` and resolved as `chorus:`. These skills are not listed in ``, so the session-start Quick Reference tells the model to `skill_view("chorus:")`. When porting a skill change: - human questions → elaboration rounds / Chorus comments (no `AskUserQuestion`); - sub-agents → `delegate_task(goal, context)` (no `Task`/`Agent`/`spawn_agent`/`subagent`); - no `${CLAUDE_PLUGIN_ROOT}`, Codex paths or `/skill:` syntax; - reviewers are skills `chorus-{proposal,task,code}-reviewer`. The parent's `delegate_task` context starts with `[chorus-reviewer:]`, and `skills.py` makes that child read-only. Keep the marker kinds equal to the skill names (`test_reviewer_marker_kinds_match_skill_names`). `test_skills.py` greps for foreign-tool references and scan-triggering phrases. Run it after every edit. ### Prompt parity `chorus_hermes/prompts.py` is a byte-exact Python port of `cli/prompts.mjs` (wake prompts for every `WAKE_ACTIONS` action). `tests/test_prompts_parity.py` renders `tests/fixtures/prompt_fixtures.json` with `node tests/fixtures/render_prompts.mjs` and compares the output with the Python output. **Any change to `cli/prompts.mjs` must be mirrored in `prompts.py`** and, for a new action or edge case, gets a fixture. The test skips without `node`. CI/dev must have it. Also mirror changes in `cli/event-router.mjs` (`NOTIFICATION_ACTION_TO_TURN_TRIGGER`) into `router.py` `ACTION_TO_TURN_TRIGGER`. Reminder text mirrors `plugins/chorus/hooks/on-post-*.sh` (Codex parity). ### Release = the tag's SHA is the install ref Hermes `--ref` accepts only a full 40-hex commit SHA, and a release commit cannot contain its own SHA. So **never commit a SHA** (`test_no_commit_sha_committed`). The release tag `vX.Y.Z` *is* the release: `chorus agents add --agents hermes` (`installHermes` in `cli/init/install-methods.mjs`) resolves `v` with `git ls-remote` — peeled `^{}` first, then the unpeeled ref (Chorus tags are lightweight) — and fails closed if the tag is missing. The tag must therefore be pushed to `github.com/Chorus-AIDLC/Chorus` before the matching CLI is published to npm, or new installs fail. Users update by re-running `chorus agents add --agents hermes`, which offers to reinstall with `--force` at the new tag's SHA (automatic under `--yes`), then `hermes gateway restart`. `chorus upgrade --plugins` does NOT cover Hermes: it only reinstalls daemon-backed agents. If you change the post-install checklist (`hermesFollowUpChecklist`), update the README "Configure" section, `docs/CONNECT_HERMES{,.zh}.md`, and the Hermes tab in `src/components/install-guide/AgentInstallGuide.tsx` (+ `messages/{en,zh,ja,ko}.json`). An end-to-end run against a real Hermes is optional and not part of version alignment. If the `hermes` CLI happens to be installed, `hermes plugins validate packages/chorus-hermes/chorus` (and `…/chorus-mcp`) is a cheap extra manifest check.