From 70d5be4cae0c1d3e242477ba4b9dac457acb3bbb Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Wed, 1 Jul 2026 14:57:39 +0530 Subject: [PATCH 1/5] feat(zeroclaw): add native skills install support Ship ZeroClaw install docs, a copy-based skills installer, and release component mapping so CE skills load through ZeroClaw workspace discovery without a Bun converter target. ZeroClaw rejects symlinked skills; users must enable [skills] allow_scripts for CE bundled scripts. --- .zeroclaw/INSTALL.md | 74 ++++++++ .zeroclaw/scripts/install-skills.sh | 159 ++++++++++++++++++ README.md | 19 +++ .../native-plugin-install-strategy.md | 24 ++- docs/specs/zeroclaw.md | 89 ++++++++++ src/release/components.ts | 1 + 6 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 .zeroclaw/INSTALL.md create mode 100755 .zeroclaw/scripts/install-skills.sh create mode 100644 docs/specs/zeroclaw.md diff --git a/.zeroclaw/INSTALL.md b/.zeroclaw/INSTALL.md new file mode 100644 index 000000000..c2eb703fb --- /dev/null +++ b/.zeroclaw/INSTALL.md @@ -0,0 +1,74 @@ +# Installing Compound Engineering for ZeroClaw + +[ZeroClaw](https://github.com/zeroclaw-labs/zeroclaw) loads CE through native **skills** discovery — the same `SKILL.md` directories shipped in this repository's `skills/` folder. No Bun converter or generated copy step is required. + +## Prerequisites + +1. Install ZeroClaw ([install guide](https://github.com/zeroclaw-labs/zeroclaw#install)). +2. Enable bundled scripts in your ZeroClaw config. Many CE skills ship `scripts/*.sh` and `scripts/*.py`; ZeroClaw's skill audit blocks script files unless you opt in: + +```toml +# ~/.zeroclaw/config.toml +[skills] +allow_scripts = true +``` + +3. Run `zeroclaw quickstart` (or confirm your agent workspace) so `~/.zeroclaw/workspace/skills/` exists. + +## Install skills + +From a clone of this repository: + +```bash +# Default workspace (~/.zeroclaw/workspace/skills/) +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global + +# Per-agent workspace (replace with your agent name) +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --dir ~/.zeroclaw/agents//workspace/skills +``` + +The script **copies** skill directories into ZeroClaw's skills tree. ZeroClaw rejects symlinked skill directories at audit time, so CE does not symlink like the Cline installer. + +When `zeroclaw` is on `PATH`, the script uses `zeroclaw skills install` per skill (security audit + copy). Pass `--use-zeroclaw-cli` to require the native CLI and fail if it is missing. + +Re-run the script after `git pull` to refresh installed copies when skill content changes. + +Skills marked `disable-model-invocation: true` (for example `lfg`, `ce-dogfood`, `ce-polish`) are **not** installed by default. ZeroClaw does not honor that frontmatter field — installing them makes their instructions available like any other skill. Opt in when you need those workflows: + +```bash +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global --include-manual +``` + +## Pin a release + +Clone the tag you want, then run the install script against that checkout: + +```bash +git clone --branch compound-engineering-vX.Y.Z --depth 1 \ + https://github.com/EveryInc/compound-engineering-plugin.git +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Replace `X.Y.Z` with a tag from the [releases page](https://github.com/EveryInc/compound-engineering-plugin/releases). + +## Local development + +From your working copy: + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Edit skills under `skills/` and re-run the install script to refresh copies. Restart the agent session or gateway if skills do not reload immediately. + +## Uninstall + +Remove CE skill directories from `~/.zeroclaw/workspace/skills/` (or your `--dir` target). Names match folders under `skills/` (for example `ce-brainstorm`, `ce-plan`). You can also use: + +```bash +zeroclaw skills remove ce-brainstorm +``` + +## Project context + +ZeroClaw reads workspace context from standard instruction files. CE skills reference "the project's active instructions and conventions already in your context" rather than hardcoding harness-specific filenames. Root `AGENTS.md` in your project is the conventional target. diff --git a/.zeroclaw/scripts/install-skills.sh b/.zeroclaw/scripts/install-skills.sh new file mode 100755 index 000000000..c734c19a4 --- /dev/null +++ b/.zeroclaw/scripts/install-skills.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +# Copy Compound Engineering skills/ into ZeroClaw's workspace skills directory. +# ZeroClaw rejects symlinked skill directories at audit time — copies only. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +SKILLS_SRC="$REPO_ROOT/skills" + +usage() { + cat <<'EOF' +Usage: install-skills.sh [--global | --dir PATH] [--include-manual] [--use-zeroclaw-cli] + + --global Install into ~/.zeroclaw/workspace/skills/ (default) + --dir PATH Install into an explicit skills directory (per-agent workspace) + --include-manual Also install manual-only skills (disable-model-invocation: true) + --use-zeroclaw-cli Require the zeroclaw binary (runs audit + copy via native CLI) + +Set ZEROCLAW_SKILLS_DIR to override the global destination. + +CE skills ship bundled shell/Python scripts. Enable allow_scripts in +~/.zeroclaw/config.toml before installing: + + [skills] + allow_scripts = true +EOF + exit 1 +} + +SCOPE="--global" +DEST="" +INCLUDE_MANUAL=false +USE_ZEROCLAW_CLI=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --global) + SCOPE="--global" + shift + ;; + --dir) + [[ $# -ge 2 ]] || usage + SCOPE="--dir" + DEST="$2" + shift 2 + ;; + --include-manual) + INCLUDE_MANUAL=true + shift + ;; + --use-zeroclaw-cli) + USE_ZEROCLAW_CLI=true + shift + ;; + *) + usage + ;; + esac +done + +case "$SCOPE" in + --global) + DEST="${ZEROCLAW_SKILLS_DIR:-$HOME/.zeroclaw/workspace/skills}" + ;; + --dir) + [[ -n "$DEST" ]] || usage + ;; + *) + usage + ;; +esac + +if [[ ! -d "$SKILLS_SRC" ]]; then + echo "error: skills directory not found at $SKILLS_SRC" >&2 + exit 1 +fi + +if [[ "$USE_ZEROCLAW_CLI" == "true" ]] && ! command -v zeroclaw >/dev/null 2>&1; then + echo "error: zeroclaw not found in PATH (--use-zeroclaw-cli)" >&2 + exit 1 +fi + +mkdir -p "$DEST" +installed=0 +skipped=0 +manual_omitted=0 +manual_included=0 +manual_removed=0 + +copy_skill() { + local src="$1" + local dest="$2" + rm -rf "$dest" + cp -R "$src" "$dest" +} + +install_one() { + local skill_dir="$1" + local name="$2" + local target="$DEST/$name" + + if command -v zeroclaw >/dev/null 2>&1; then + zeroclaw skills remove "$name" >/dev/null 2>&1 || true + if zeroclaw skills install "$skill_dir"; then + echo "installed $name via zeroclaw -> $target" + return 0 + fi + if [[ "$USE_ZEROCLAW_CLI" == "true" ]]; then + echo "error: zeroclaw install failed for $name (check [skills] allow_scripts)" >&2 + exit 1 + fi + echo "warn $name: zeroclaw install failed — falling back to copy (check [skills] allow_scripts)" >&2 + elif [[ "$USE_ZEROCLAW_CLI" == "true" ]]; then + echo "error: zeroclaw not found in PATH" >&2 + exit 1 + fi + + copy_skill "$skill_dir" "$target" + echo "installed $name (copy) -> $target" +} + +for skill_dir in "$SKILLS_SRC"/*/; do + [[ -f "${skill_dir}SKILL.md" ]] || continue + name="$(basename "$skill_dir")" + is_manual=false + + if grep -qE '^disable-model-invocation:[[:space:]]*true[[:space:]]*$' "${skill_dir}SKILL.md"; then + is_manual=true + if [[ "$INCLUDE_MANUAL" != "true" ]]; then + target="$DEST/$name" + if [[ -e "$target" ]]; then + rm -rf "$target" + echo "removed $name: manual-only skill" >&2 + manual_removed=$((manual_removed + 1)) + fi + echo "skip $name: manual-only (disable-model-invocation)" >&2 + manual_omitted=$((manual_omitted + 1)) + continue + fi + echo "warn $name: manual-only skill installed — ZeroClaw ignores disable-model-invocation" >&2 + manual_included=$((manual_included + 1)) + fi + + target="$DEST/$name" + if [[ -e "$target" && ! -d "$target" ]]; then + echo "skip $name: $target exists and is not a directory" >&2 + skipped=$((skipped + 1)) + continue + fi + + install_one "$skill_dir" "$name" + installed=$((installed + 1)) +done + +if [[ "$INCLUDE_MANUAL" == "true" ]]; then + echo "done: $installed installed, $skipped skipped, $manual_included manual-only included (destination: $DEST)" +else + echo "done: $installed installed, $skipped skipped, $manual_omitted manual-only omitted, $manual_removed manual-only removed (destination: $DEST)" +fi diff --git a/README.md b/README.md index d7f2b3642..5d0e0fab2 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,17 @@ agy plugin install ./compound-engineering-plugin/.agy `agy` also loads `GEMINI.md` workspace context from the checkout. +### ZeroClaw + +[ZeroClaw](https://github.com/zeroclaw-labs/zeroclaw) loads CE skills from `SKILL.md` directories copied into the agent workspace. Enable bundled scripts in `~/.zeroclaw/config.toml` (`[skills] allow_scripts = true`), then install from a checkout: + +```bash +git clone https://github.com/EveryInc/compound-engineering-plugin +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Re-run the install script after updating the checkout. See [`.zeroclaw/INSTALL.md`](.zeroclaw/INSTALL.md) for per-agent paths, pinning, and uninstall steps. + ### Existing Installs Compound Engineering moved to a root-native, skills-only layout. An existing marketplace install keeps a **cached** marketplace snapshot that still points at the old `plugins/compound-engineering` path, so updating the plugin on its own reads that stale snapshot and leaves you on the previous version. Refresh the cached marketplace **first**, then update the plugin — order matters. @@ -428,6 +439,14 @@ agy plugin install "$PWD/.agy" `agy` installs the bundled `.agy` plugin directory from your checkout and loads `GEMINI.md` workspace context. +**ZeroClaw** + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Set `[skills] allow_scripts = true` in `~/.zeroclaw/config.toml` before installing. + ## Limitations OpenCode and Pi use native package/plugin loading from this repository. The Bun CLI remains for repository development and converter maintenance, not normal installation. diff --git a/docs/solutions/integrations/native-plugin-install-strategy.md b/docs/solutions/integrations/native-plugin-install-strategy.md index 000500e9e..2e5c4ef86 100644 --- a/docs/solutions/integrations/native-plugin-install-strategy.md +++ b/docs/solutions/integrations/native-plugin-install-strategy.md @@ -1,7 +1,7 @@ --- title: "Native plugin install strategy for supported harnesses" date: 2026-06-19 -last_updated: 2026-06-23 +last_updated: 2026-06-30 category: integrations module: installer problem_type: integration_decision @@ -25,6 +25,7 @@ tags: - antigravity - opencode - pi + - zeroclaw --- # Native Plugin Install Strategy @@ -49,6 +50,7 @@ The install strategy follows from that: prefer each harness's native plugin/pack | OpenCode | Git-backed OpenCode plugin entry in `opencode.json` | No | `.opencode/plugins/compound-engineering.js` registers the CE skills directory directly. | | Pi | Git-backed Pi package install from this repository | No | Root `package.json` exposes `.pi/extensions/compound-engineering.ts` and the CE skills directory. `pi-ask-user` is a recommended companion for richer prompts. | | Antigravity CLI | Native Antigravity plugin from the committed `.agy/` bundle | No | Clone the repo, then `agy plugin install ./compound-engineering-plugin/.agy`. The `.agy/` bundle holds `plugin.json` plus a `skills -> ../skills` symlink. `agy` still reads `GEMINI.md` as workspace context. | +| ZeroClaw | Native skills install via `.zeroclaw/scripts/install-skills.sh` | No | Copies CE skills into `~/.zeroclaw/workspace/skills/` (symlinks rejected by ZeroClaw audit). Set `[skills] allow_scripts = true` in `~/.zeroclaw/config.toml` for script-bearing CE skills. | Kiro is no longer a documented CE install target. Historical converter and cleanup code may remain for regression coverage or old artifact handling, but user-facing install docs should not advertise Kiro. @@ -119,6 +121,26 @@ agy plugin install ./compound-engineering-plugin/.agy `agy` still reads `GEMINI.md` as workspace context (retained despite the Gemini CLI converter target being removed). For local development, point `agy` at the `.agy/` subdirectory of the checkout so it finds `plugin.json`, the `skills` symlink, and `GEMINI.md` together. +## ZeroClaw + +ZeroClaw discovers skills from the agent workspace at `~/.zeroclaw/workspace/skills/` (or per-agent under `~/.zeroclaw/agents//workspace/skills/`). CE ships `.zeroclaw/scripts/install-skills.sh`, which copies each directory under this repository's `skills/` into the chosen destination. ZeroClaw's skill audit rejects symlinked skill directories, so CE does not symlink like Cline. + +Recommended global install: + +```bash +git clone https://github.com/EveryInc/compound-engineering-plugin +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Enable bundled scripts in `~/.zeroclaw/config.toml` before installing — many CE skills ship `scripts/*.sh` and `scripts/*.py`: + +```toml +[skills] +allow_scripts = true +``` + +Re-run the install script after pulling a newer CE release. The script skips manual-only skills marked `disable-model-invocation: true` by default (ZeroClaw ignores that frontmatter field). Pass `--include-manual` to copy those skills when needed. + ## Kimi Code CLI Kimi Code CLI has a native plugin surface, so CE should not maintain a Kimi converter target for normal installation. The root `.kimi-plugin/plugin.json` declares the CE skills directory with `skills: "./skills/"` and carries display metadata through Kimi's `interface` object. diff --git a/docs/specs/zeroclaw.md b/docs/specs/zeroclaw.md new file mode 100644 index 000000000..9ac32ebbe --- /dev/null +++ b/docs/specs/zeroclaw.md @@ -0,0 +1,89 @@ +# ZeroClaw Spec (Skills) + +Last verified: 2026-06-30 + +## Primary sources + +``` +https://github.com/zeroclaw-labs/zeroclaw +https://github.com/zeroclaw-labs/zeroclaw/blob/master/docs/book/src/tools/skills.md +https://github.com/zeroclaw-labs/zeroclaw/blob/master/docs/book/src/agents/filesystem.md +``` + +## Skills (primary CE install surface) + +ZeroClaw skills follow the open [Agent Skills](https://agentskills.io) standard. Each skill is a directory containing `SKILL.md` with YAML frontmatter (`name`, `description`, `version`, `author`, `tags`). ZeroClaw loads skills from the agent workspace at install time and injects them into the agent prompt (full or compact mode per config). + +### Discovery paths + +| Scope | Path | +| --- | --- | +| Default workspace | `~/.zeroclaw/workspace/skills//` | +| Per-agent workspace | `~/.zeroclaw/agents//workspace/skills//` | +| Shared bundles (config) | `/shared/skills//` | + +CE ships skills at `./skills//SKILL.md` in this repository. Compound Engineering does **not** copy skills into a generated tree for ZeroClaw at release time; users install from a checkout with `.zeroclaw/scripts/install-skills.sh`. + +### Copy-only install (no symlinks) + +ZeroClaw's skill audit rejects symlinked skill directories and symlinked files inside a skill. The CE installer copies each skill directory into the target skills path. Re-run the installer after pulling a newer CE release to refresh copies. + +### Bundled scripts + +Many CE skills include `scripts/*.sh` and `scripts/*.py`. ZeroClaw blocks script-like files unless `skills.allow_scripts = true` in `~/.zeroclaw/config.toml`. Without that setting, `zeroclaw skills install` fails audit for script-bearing CE skills. + +### Manual-only skills + +Some CE skills set `disable-model-invocation: true` so Claude and Codex do not auto-invoke them (for example `lfg`, `ce-dogfood`, `ce-polish`). ZeroClaw's frontmatter parser does not read that field. `.zeroclaw/scripts/install-skills.sh` skips manual-only skills by default; pass `--include-manual` to copy them anyway. + +## CLI integration + +ZeroClaw exposes native skill management: + +```bash +zeroclaw skills list +zeroclaw skills install /path/to/skill-dir +zeroclaw skills remove +zeroclaw skills audit +``` + +The CE install script wraps per-skill `zeroclaw skills install` when the binary is available, with a plain `cp -R` fallback. + +## Instruction files + +ZeroClaw projects commonly use root `AGENTS.md` for workspace context. CE skills reference "the project's active instructions and conventions already in your context" rather than hardcoding harness-specific filenames. + +## Install commands + +Default workspace from a checkout: + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Per-agent workspace: + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh \ + --dir ~/.zeroclaw/agents//workspace/skills +``` + +Manual-only skills require the opt-in flag: + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global --include-manual +``` + +After installing or updating skills, restart the agent session or gateway if the skill list does not refresh. + +## Update and removal + +Re-run the install script after pulling a newer CE release. The script removes prior copies (via `zeroclaw skills remove` or `rm -rf`) before reinstalling. + +To remove CE skills, delete the directories from the skills path or run `zeroclaw skills remove ` for each skill id. + +## Subagent and tool notes + +CE skills dispatch generic subagents with skill-local prompt assets under `references/agents/` and `references/personas/`. ZeroClaw's subagent and MCP capabilities vary by deployment (CLI, gateway, zerocode). Skills degrade gracefully when a primitive is unavailable — the same cross-harness posture used for OpenCode and Pi. + +Bundled shell scripts in skills use the model-filled `SKILL_DIR` anchor documented in the repository's contributor instructions so paths resolve when the agent's working directory is the user's project, not the skill directory. diff --git a/src/release/components.ts b/src/release/components.ts index abb28d63c..f08f0fad8 100644 --- a/src/release/components.ts +++ b/src/release/components.ts @@ -24,6 +24,7 @@ const FILE_COMPONENT_MAP: Array<{ component: ReleaseComponent; prefixes: string[ ".codex-plugin/", ".kimi-plugin/plugin.json", ".opencode/", + ".zeroclaw/", ".pi/", "AGENTS.md", "CLAUDE.md", From d9450143e5a375edd8930c66e3fde46f27dc60b6 Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Wed, 1 Jul 2026 15:04:50 +0530 Subject: [PATCH 2/5] fix(zeroclaw): copy to custom --dir paths instead of CLI install ZeroClaw skills install always writes to config.data_dir, so returning early after a successful CLI install left --dir and ZEROCLAW_SKILLS_DIR destinations empty. Restrict zeroclaw CLI to default global path only. --- .zeroclaw/INSTALL.md | 8 ++++++-- .zeroclaw/scripts/install-skills.sh | 30 +++++++++++++++++++++++------ docs/specs/zeroclaw.md | 2 +- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.zeroclaw/INSTALL.md b/.zeroclaw/INSTALL.md index c2eb703fb..c76e77ccf 100644 --- a/.zeroclaw/INSTALL.md +++ b/.zeroclaw/INSTALL.md @@ -29,7 +29,9 @@ From a clone of this repository: The script **copies** skill directories into ZeroClaw's skills tree. ZeroClaw rejects symlinked skill directories at audit time, so CE does not symlink like the Cline installer. -When `zeroclaw` is on `PATH`, the script uses `zeroclaw skills install` per skill (security audit + copy). Pass `--use-zeroclaw-cli` to require the native CLI and fail if it is missing. +For the default global path (`~/.zeroclaw/workspace/skills/`), the script uses `zeroclaw skills install` when the CLI is on `PATH` (security audit + copy into `config.data_dir`). Custom destinations (`--dir` or `ZEROCLAW_SKILLS_DIR`) always use direct copy — the ZeroClaw CLI has no flag to target a different skills directory. + +Pass `--use-zeroclaw-cli` to require the native CLI for default global installs only. Re-run the script after `git pull` to refresh installed copies when skill content changes. @@ -63,12 +65,14 @@ Edit skills under `skills/` and re-run the install script to refresh copies. Res ## Uninstall -Remove CE skill directories from `~/.zeroclaw/workspace/skills/` (or your `--dir` target). Names match folders under `skills/` (for example `ce-brainstorm`, `ce-plan`). You can also use: +Remove CE skill directories from `~/.zeroclaw/workspace/skills/` (or your `--dir` target). Names match folders under `skills/` (for example `ce-brainstorm`, `ce-plan`). For the default global install you can also use: ```bash zeroclaw skills remove ce-brainstorm ``` +`zeroclaw skills remove` only affects skills under `config.data_dir`, not custom `--dir` targets. + ## Project context ZeroClaw reads workspace context from standard instruction files. CE skills reference "the project's active instructions and conventions already in your context" rather than hardcoding harness-specific filenames. Root `AGENTS.md` in your project is the conventional target. diff --git a/.zeroclaw/scripts/install-skills.sh b/.zeroclaw/scripts/install-skills.sh index c734c19a4..d2cff4ea4 100755 --- a/.zeroclaw/scripts/install-skills.sh +++ b/.zeroclaw/scripts/install-skills.sh @@ -14,7 +14,7 @@ Usage: install-skills.sh [--global | --dir PATH] [--include-manual] [--use-zeroc --global Install into ~/.zeroclaw/workspace/skills/ (default) --dir PATH Install into an explicit skills directory (per-agent workspace) --include-manual Also install manual-only skills (disable-model-invocation: true) - --use-zeroclaw-cli Require the zeroclaw binary (runs audit + copy via native CLI) + --use-zeroclaw-cli Require zeroclaw for default global install only (not with --dir) Set ZEROCLAW_SKILLS_DIR to override the global destination. @@ -58,9 +58,11 @@ while [[ $# -gt 0 ]]; do esac done +DEFAULT_DEST="${HOME}/.zeroclaw/workspace/skills" + case "$SCOPE" in --global) - DEST="${ZEROCLAW_SKILLS_DIR:-$HOME/.zeroclaw/workspace/skills}" + DEST="${ZEROCLAW_SKILLS_DIR:-$DEFAULT_DEST}" ;; --dir) [[ -n "$DEST" ]] || usage @@ -70,14 +72,30 @@ case "$SCOPE" in ;; esac +can_use_zeroclaw_cli() { + [[ "$SCOPE" == "--global" ]] || return 1 + [[ -z "${ZEROCLAW_SKILLS_DIR:-}" ]] || return 1 + local dest_canonical default_canonical + mkdir -p "$DEST" "$DEFAULT_DEST" + dest_canonical="$(cd "$DEST" && pwd -P)" + default_canonical="$(cd "$DEFAULT_DEST" && pwd -P)" + [[ "$dest_canonical" == "$default_canonical" ]] +} + if [[ ! -d "$SKILLS_SRC" ]]; then echo "error: skills directory not found at $SKILLS_SRC" >&2 exit 1 fi -if [[ "$USE_ZEROCLAW_CLI" == "true" ]] && ! command -v zeroclaw >/dev/null 2>&1; then - echo "error: zeroclaw not found in PATH (--use-zeroclaw-cli)" >&2 - exit 1 +if [[ "$USE_ZEROCLAW_CLI" == "true" ]]; then + if ! command -v zeroclaw >/dev/null 2>&1; then + echo "error: zeroclaw not found in PATH (--use-zeroclaw-cli)" >&2 + exit 1 + fi + if ! can_use_zeroclaw_cli; then + echo "error: --use-zeroclaw-cli only works for default --global (~/.zeroclaw/workspace/skills)" >&2 + exit 1 + fi fi mkdir -p "$DEST" @@ -99,7 +117,7 @@ install_one() { local name="$2" local target="$DEST/$name" - if command -v zeroclaw >/dev/null 2>&1; then + if can_use_zeroclaw_cli && command -v zeroclaw >/dev/null 2>&1; then zeroclaw skills remove "$name" >/dev/null 2>&1 || true if zeroclaw skills install "$skill_dir"; then echo "installed $name via zeroclaw -> $target" diff --git a/docs/specs/zeroclaw.md b/docs/specs/zeroclaw.md index 9ac32ebbe..f63ddb152 100644 --- a/docs/specs/zeroclaw.md +++ b/docs/specs/zeroclaw.md @@ -47,7 +47,7 @@ zeroclaw skills remove zeroclaw skills audit ``` -The CE install script wraps per-skill `zeroclaw skills install` when the binary is available, with a plain `cp -R` fallback. +The CE install script wraps per-skill `zeroclaw skills install` only for the default global destination (`~/.zeroclaw/workspace/skills/`). The ZeroClaw CLI always installs relative to `config.data_dir` and cannot target `--dir` overrides — custom paths use direct `cp -R`. ## Instruction files From c87169aee531f901a9fd43b2ae72067c409feea2 Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Wed, 1 Jul 2026 15:25:06 +0530 Subject: [PATCH 3/5] fix(zeroclaw): target per-agent workspace paths for v0.8 ZeroClaw agents load skills from agents//workspace/skills, not the legacy workspace/skills tree or data_dir/skills where the CLI installs. Default --global now maps to the default agent workspace; add --agent and --shared modes and drop zeroclaw skills install from the script. --- .zeroclaw/INSTALL.md | 45 ++-- .zeroclaw/scripts/install-skills.sh | 220 ++++++++++-------- README.md | 2 +- .../native-plugin-install-strategy.md | 8 +- docs/specs/zeroclaw.md | 51 ++-- 5 files changed, 176 insertions(+), 150 deletions(-) diff --git a/.zeroclaw/INSTALL.md b/.zeroclaw/INSTALL.md index c76e77ccf..de73459d9 100644 --- a/.zeroclaw/INSTALL.md +++ b/.zeroclaw/INSTALL.md @@ -5,7 +5,8 @@ ## Prerequisites 1. Install ZeroClaw ([install guide](https://github.com/zeroclaw-labs/zeroclaw#install)). -2. Enable bundled scripts in your ZeroClaw config. Many CE skills ship `scripts/*.sh` and `scripts/*.py`; ZeroClaw's skill audit blocks script files unless you opt in: +2. Run `zeroclaw quickstart` so you have at least one agent (typically `default`) under `~/.zeroclaw/agents/`. +3. Enable bundled scripts in your ZeroClaw config. Many CE skills ship `scripts/*.sh` and `scripts/*.py`; ZeroClaw's skill audit blocks script files unless you opt in: ```toml # ~/.zeroclaw/config.toml @@ -13,29 +14,45 @@ allow_scripts = true ``` -3. Run `zeroclaw quickstart` (or confirm your agent workspace) so `~/.zeroclaw/workspace/skills/` exists. - ## Install skills +ZeroClaw v0.8+ loads skills from **per-agent workspace** paths (`~/.zeroclaw/agents//workspace/skills/`), not the legacy `~/.zeroclaw/workspace/skills/` tree. The installer copies skill directories into the paths agents actually read. + From a clone of this repository: ```bash -# Default workspace (~/.zeroclaw/workspace/skills/) +# Default agent (recommended after quickstart) ./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global -# Per-agent workspace (replace with your agent name) -./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --dir ~/.zeroclaw/agents//workspace/skills +# Explicit agent alias +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --agent my-agent + +# Every configured agent +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --agent all +``` + +### Shared skill bundle (multi-agent hosts) + +To install once under `~/.zeroclaw/shared/skills/compound-engineering/` and reference it from agent config: + +```bash +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --shared ``` -The script **copies** skill directories into ZeroClaw's skills tree. ZeroClaw rejects symlinked skill directories at audit time, so CE does not symlink like the Cline installer. +Then add to `~/.zeroclaw/config.toml`: + +```toml +[skill_bundles.compound-engineering] -For the default global path (`~/.zeroclaw/workspace/skills/`), the script uses `zeroclaw skills install` when the CLI is on `PATH` (security audit + copy into `config.data_dir`). Custom destinations (`--dir` or `ZEROCLAW_SKILLS_DIR`) always use direct copy — the ZeroClaw CLI has no flag to target a different skills directory. +[agents.default] +skill_bundles = ["compound-engineering"] +``` -Pass `--use-zeroclaw-cli` to require the native CLI for default global installs only. +The script **copies** skill directories (ZeroClaw rejects symlinks at audit time). It does **not** call `zeroclaw skills install` — that CLI writes to `config.data_dir/skills`, which agent sessions do not load. Re-run the script after `git pull` to refresh installed copies when skill content changes. -Skills marked `disable-model-invocation: true` (for example `lfg`, `ce-dogfood`, `ce-polish`) are **not** installed by default. ZeroClaw does not honor that frontmatter field — installing them makes their instructions available like any other skill. Opt in when you need those workflows: +Skills marked `disable-model-invocation: true` (for example `lfg`, `ce-dogfood`, `ce-polish`) are **not** installed by default. ZeroClaw does not honor that frontmatter field. Opt in when you need those workflows: ```bash ./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global --include-manual @@ -65,13 +82,9 @@ Edit skills under `skills/` and re-run the install script to refresh copies. Res ## Uninstall -Remove CE skill directories from `~/.zeroclaw/workspace/skills/` (or your `--dir` target). Names match folders under `skills/` (for example `ce-brainstorm`, `ce-plan`). For the default global install you can also use: - -```bash -zeroclaw skills remove ce-brainstorm -``` +Remove CE skill directories from the install target (for example `~/.zeroclaw/agents/default/workspace/skills/ce-brainstorm`). Names match folders under `skills/`. -`zeroclaw skills remove` only affects skills under `config.data_dir`, not custom `--dir` targets. +For `--shared` installs, remove skills from `~/.zeroclaw/shared/skills/compound-engineering/` and drop the bundle reference from agent config. ## Project context diff --git a/.zeroclaw/scripts/install-skills.sh b/.zeroclaw/scripts/install-skills.sh index d2cff4ea4..c4a2a2fb2 100755 --- a/.zeroclaw/scripts/install-skills.sh +++ b/.zeroclaw/scripts/install-skills.sh @@ -1,25 +1,39 @@ #!/usr/bin/env bash -# Copy Compound Engineering skills/ into ZeroClaw's workspace skills directory. +# Copy Compound Engineering skills/ into ZeroClaw agent workspace skills directories. # ZeroClaw rejects symlinked skill directories at audit time — copies only. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" SKILLS_SRC="$REPO_ROOT/skills" +INSTALL_ROOT="${ZEROCLAW_INSTALL_ROOT:-$HOME/.zeroclaw}" +SHARED_BUNDLE="compound-engineering" usage() { cat <<'EOF' -Usage: install-skills.sh [--global | --dir PATH] [--include-manual] [--use-zeroclaw-cli] +Usage: install-skills.sh [--global | --agent ALIAS | --shared | --dir PATH] [--include-manual] - --global Install into ~/.zeroclaw/workspace/skills/ (default) - --dir PATH Install into an explicit skills directory (per-agent workspace) + --global Install into the default agent workspace (same as --agent default) + --agent ALIAS Install into /agents//workspace/skills/ + --agent all Install into every agent under /agents/ + --shared Install bundle at /shared/skills/compound-engineering/ + --dir PATH Install into an explicit skills directory --include-manual Also install manual-only skills (disable-model-invocation: true) - --use-zeroclaw-cli Require zeroclaw for default global install only (not with --dir) -Set ZEROCLAW_SKILLS_DIR to override the global destination. +Set ZEROCLAW_INSTALL_ROOT to override ~/.zeroclaw (honors ZEROCLAW_CONFIG_DIR parent). -CE skills ship bundled shell/Python scripts. Enable allow_scripts in -~/.zeroclaw/config.toml before installing: +ZeroClaw v0.8+ loads agent skills from per-agent workspace paths, not the legacy +~/.zeroclaw/workspace/skills tree. This script does not call zeroclaw skills install +(that CLI writes to config.data_dir/skills, which agents do not read). + +For --shared, add to ~/.zeroclaw/config.toml: + + [skill_bundles.compound-engineering] + + [agents.default] + skill_bundles = ["compound-engineering"] + +CE skills ship bundled shell/Python scripts. Enable allow_scripts before use: [skills] allow_scripts = true @@ -28,9 +42,10 @@ EOF } SCOPE="--global" +AGENT_ALIAS="default" DEST="" INCLUDE_MANUAL=false -USE_ZEROCLAW_CLI=false +DESTS=() while [[ $# -gt 0 ]]; do case "$1" in @@ -38,6 +53,16 @@ while [[ $# -gt 0 ]]; do SCOPE="--global" shift ;; + --agent) + [[ $# -ge 2 ]] || usage + SCOPE="--agent" + AGENT_ALIAS="$2" + shift 2 + ;; + --shared) + SCOPE="--shared" + shift + ;; --dir) [[ $# -ge 2 ]] || usage SCOPE="--dir" @@ -49,7 +74,7 @@ while [[ $# -gt 0 ]]; do shift ;; --use-zeroclaw-cli) - USE_ZEROCLAW_CLI=true + echo "warn: --use-zeroclaw-cli is deprecated and ignored (zeroclaw skills install targets data_dir, not agent workspaces)" >&2 shift ;; *) @@ -58,28 +83,39 @@ while [[ $# -gt 0 ]]; do esac done -DEFAULT_DEST="${HOME}/.zeroclaw/workspace/skills" - -case "$SCOPE" in - --global) - DEST="${ZEROCLAW_SKILLS_DIR:-$DEFAULT_DEST}" - ;; - --dir) - [[ -n "$DEST" ]] || usage - ;; - *) - usage - ;; -esac - -can_use_zeroclaw_cli() { - [[ "$SCOPE" == "--global" ]] || return 1 - [[ -z "${ZEROCLAW_SKILLS_DIR:-}" ]] || return 1 - local dest_canonical default_canonical - mkdir -p "$DEST" "$DEFAULT_DEST" - dest_canonical="$(cd "$DEST" && pwd -P)" - default_canonical="$(cd "$DEFAULT_DEST" && pwd -P)" - [[ "$dest_canonical" == "$default_canonical" ]] +resolve_destinations() { + case "$SCOPE" in + --global | --agent) + if [[ "$AGENT_ALIAS" == "all" ]]; then + if [[ ! -d "$INSTALL_ROOT/agents" ]]; then + echo "error: no agents directory at $INSTALL_ROOT/agents" >&2 + exit 1 + fi + local agent_dir alias_name + for agent_dir in "$INSTALL_ROOT/agents"/*/; do + [[ -d "$agent_dir" ]] || continue + alias_name="$(basename "$agent_dir")" + DESTS+=("$INSTALL_ROOT/agents/$alias_name/workspace/skills") + done + if [[ ${#DESTS[@]} -eq 0 ]]; then + echo "error: no agents found under $INSTALL_ROOT/agents" >&2 + exit 1 + fi + else + DESTS+=("$INSTALL_ROOT/agents/$AGENT_ALIAS/workspace/skills") + fi + ;; + --shared) + DESTS+=("$INSTALL_ROOT/shared/skills/$SHARED_BUNDLE") + ;; + --dir) + [[ -n "$DEST" ]] || usage + DESTS+=("$DEST") + ;; + *) + usage + ;; + esac } if [[ ! -d "$SKILLS_SRC" ]]; then @@ -87,23 +123,7 @@ if [[ ! -d "$SKILLS_SRC" ]]; then exit 1 fi -if [[ "$USE_ZEROCLAW_CLI" == "true" ]]; then - if ! command -v zeroclaw >/dev/null 2>&1; then - echo "error: zeroclaw not found in PATH (--use-zeroclaw-cli)" >&2 - exit 1 - fi - if ! can_use_zeroclaw_cli; then - echo "error: --use-zeroclaw-cli only works for default --global (~/.zeroclaw/workspace/skills)" >&2 - exit 1 - fi -fi - -mkdir -p "$DEST" -installed=0 -skipped=0 -manual_omitted=0 -manual_included=0 -manual_removed=0 +resolve_destinations copy_skill() { local src="$1" @@ -112,66 +132,62 @@ copy_skill() { cp -R "$src" "$dest" } -install_one() { - local skill_dir="$1" - local name="$2" - local target="$DEST/$name" - - if can_use_zeroclaw_cli && command -v zeroclaw >/dev/null 2>&1; then - zeroclaw skills remove "$name" >/dev/null 2>&1 || true - if zeroclaw skills install "$skill_dir"; then - echo "installed $name via zeroclaw -> $target" - return 0 - fi - if [[ "$USE_ZEROCLAW_CLI" == "true" ]]; then - echo "error: zeroclaw install failed for $name (check [skills] allow_scripts)" >&2 - exit 1 +install_to_dest() { + local dest="$1" + local installed=0 + local skipped=0 + local manual_omitted=0 + local manual_included=0 + local manual_removed=0 + + mkdir -p "$dest" + + for skill_dir in "$SKILLS_SRC"/*/; do + [[ -f "${skill_dir}SKILL.md" ]] || continue + local name + name="$(basename "$skill_dir")" + local is_manual=false + + if grep -qE '^disable-model-invocation:[[:space:]]*true[[:space:]]*$' "${skill_dir}SKILL.md"; then + is_manual=true + if [[ "$INCLUDE_MANUAL" != "true" ]]; then + local target="$dest/$name" + if [[ -e "$target" ]]; then + rm -rf "$target" + echo "removed $name: manual-only skill" >&2 + manual_removed=$((manual_removed + 1)) + fi + echo "skip $name: manual-only (disable-model-invocation)" >&2 + manual_omitted=$((manual_omitted + 1)) + continue + fi + echo "warn $name: manual-only skill installed — ZeroClaw ignores disable-model-invocation" >&2 + manual_included=$((manual_included + 1)) fi - echo "warn $name: zeroclaw install failed — falling back to copy (check [skills] allow_scripts)" >&2 - elif [[ "$USE_ZEROCLAW_CLI" == "true" ]]; then - echo "error: zeroclaw not found in PATH" >&2 - exit 1 - fi - - copy_skill "$skill_dir" "$target" - echo "installed $name (copy) -> $target" -} -for skill_dir in "$SKILLS_SRC"/*/; do - [[ -f "${skill_dir}SKILL.md" ]] || continue - name="$(basename "$skill_dir")" - is_manual=false - - if grep -qE '^disable-model-invocation:[[:space:]]*true[[:space:]]*$' "${skill_dir}SKILL.md"; then - is_manual=true - if [[ "$INCLUDE_MANUAL" != "true" ]]; then - target="$DEST/$name" - if [[ -e "$target" ]]; then - rm -rf "$target" - echo "removed $name: manual-only skill" >&2 - manual_removed=$((manual_removed + 1)) - fi - echo "skip $name: manual-only (disable-model-invocation)" >&2 - manual_omitted=$((manual_omitted + 1)) + local target="$dest/$name" + if [[ -e "$target" && ! -d "$target" ]]; then + echo "skip $name: $target exists and is not a directory" >&2 + skipped=$((skipped + 1)) continue fi - echo "warn $name: manual-only skill installed — ZeroClaw ignores disable-model-invocation" >&2 - manual_included=$((manual_included + 1)) - fi - target="$DEST/$name" - if [[ -e "$target" && ! -d "$target" ]]; then - echo "skip $name: $target exists and is not a directory" >&2 - skipped=$((skipped + 1)) - continue + copy_skill "$skill_dir" "$target" + echo "installed $name -> $target" + installed=$((installed + 1)) + done + + if [[ "$INCLUDE_MANUAL" == "true" ]]; then + echo "done: $installed installed, $skipped skipped, $manual_included manual-only included (destination: $dest)" + else + echo "done: $installed installed, $skipped skipped, $manual_omitted manual-only omitted, $manual_removed manual-only removed (destination: $dest)" fi +} - install_one "$skill_dir" "$name" - installed=$((installed + 1)) +for dest in "${DESTS[@]}"; do + install_to_dest "$dest" done -if [[ "$INCLUDE_MANUAL" == "true" ]]; then - echo "done: $installed installed, $skipped skipped, $manual_included manual-only included (destination: $DEST)" -else - echo "done: $installed installed, $skipped skipped, $manual_omitted manual-only omitted, $manual_removed manual-only removed (destination: $DEST)" +if [[ ${#DESTS[@]} -gt 1 ]]; then + echo "completed installs for ${#DESTS[@]} destinations under $INSTALL_ROOT" fi diff --git a/README.md b/README.md index 5d0e0fab2..bfd95e796 100644 --- a/README.md +++ b/README.md @@ -318,7 +318,7 @@ agy plugin install ./compound-engineering-plugin/.agy ### ZeroClaw -[ZeroClaw](https://github.com/zeroclaw-labs/zeroclaw) loads CE skills from `SKILL.md` directories copied into the agent workspace. Enable bundled scripts in `~/.zeroclaw/config.toml` (`[skills] allow_scripts = true`), then install from a checkout: +[ZeroClaw](https://github.com/zeroclaw-labs/zeroclaw) loads CE skills from per-agent workspace paths (`~/.zeroclaw/agents//workspace/skills/`). Run `zeroclaw quickstart` first, enable bundled scripts in `~/.zeroclaw/config.toml` (`[skills] allow_scripts = true`), then install from a checkout: ```bash git clone https://github.com/EveryInc/compound-engineering-plugin diff --git a/docs/solutions/integrations/native-plugin-install-strategy.md b/docs/solutions/integrations/native-plugin-install-strategy.md index 2e5c4ef86..25375c7f9 100644 --- a/docs/solutions/integrations/native-plugin-install-strategy.md +++ b/docs/solutions/integrations/native-plugin-install-strategy.md @@ -50,7 +50,7 @@ The install strategy follows from that: prefer each harness's native plugin/pack | OpenCode | Git-backed OpenCode plugin entry in `opencode.json` | No | `.opencode/plugins/compound-engineering.js` registers the CE skills directory directly. | | Pi | Git-backed Pi package install from this repository | No | Root `package.json` exposes `.pi/extensions/compound-engineering.ts` and the CE skills directory. `pi-ask-user` is a recommended companion for richer prompts. | | Antigravity CLI | Native Antigravity plugin from the committed `.agy/` bundle | No | Clone the repo, then `agy plugin install ./compound-engineering-plugin/.agy`. The `.agy/` bundle holds `plugin.json` plus a `skills -> ../skills` symlink. `agy` still reads `GEMINI.md` as workspace context. | -| ZeroClaw | Native skills install via `.zeroclaw/scripts/install-skills.sh` | No | Copies CE skills into `~/.zeroclaw/workspace/skills/` (symlinks rejected by ZeroClaw audit). Set `[skills] allow_scripts = true` in `~/.zeroclaw/config.toml` for script-bearing CE skills. | +| ZeroClaw | Native skills install via `.zeroclaw/scripts/install-skills.sh` | No | Copies CE skills into `~/.zeroclaw/agents//workspace/skills/` (or a shared bundle). Set `[skills] allow_scripts = true` for script-bearing CE skills. | Kiro is no longer a documented CE install target. Historical converter and cleanup code may remain for regression coverage or old artifact handling, but user-facing install docs should not advertise Kiro. @@ -123,9 +123,9 @@ agy plugin install ./compound-engineering-plugin/.agy ## ZeroClaw -ZeroClaw discovers skills from the agent workspace at `~/.zeroclaw/workspace/skills/` (or per-agent under `~/.zeroclaw/agents//workspace/skills/`). CE ships `.zeroclaw/scripts/install-skills.sh`, which copies each directory under this repository's `skills/` into the chosen destination. ZeroClaw's skill audit rejects symlinked skill directories, so CE does not symlink like Cline. +ZeroClaw v0.8+ loads agent skills from per-agent workspace paths at `~/.zeroclaw/agents//workspace/skills/`, or from shared bundles under `~/.zeroclaw/shared/skills//` when referenced in agent config. CE ships `.zeroclaw/scripts/install-skills.sh`, which copies each directory under this repository's `skills/` into the chosen destination. The legacy `~/.zeroclaw/workspace/skills/` tree is not used by the current agent loader. -Recommended global install: +Recommended install (default agent after `zeroclaw quickstart`): ```bash git clone https://github.com/EveryInc/compound-engineering-plugin @@ -139,7 +139,7 @@ Enable bundled scripts in `~/.zeroclaw/config.toml` before installing — many C allow_scripts = true ``` -Re-run the install script after pulling a newer CE release. The script skips manual-only skills marked `disable-model-invocation: true` by default (ZeroClaw ignores that frontmatter field). Pass `--include-manual` to copy those skills when needed. +For multi-agent hosts, use `--shared` plus a `[skill_bundles.compound-engineering]` entry (see `.zeroclaw/INSTALL.md`). Re-run the install script after pulling a newer CE release. The script skips manual-only skills by default; pass `--include-manual` when needed. ## Kimi Code CLI diff --git a/docs/specs/zeroclaw.md b/docs/specs/zeroclaw.md index f63ddb152..8fee2725f 100644 --- a/docs/specs/zeroclaw.md +++ b/docs/specs/zeroclaw.md @@ -12,15 +12,15 @@ https://github.com/zeroclaw-labs/zeroclaw/blob/master/docs/book/src/agents/files ## Skills (primary CE install surface) -ZeroClaw skills follow the open [Agent Skills](https://agentskills.io) standard. Each skill is a directory containing `SKILL.md` with YAML frontmatter (`name`, `description`, `version`, `author`, `tags`). ZeroClaw loads skills from the agent workspace at install time and injects them into the agent prompt (full or compact mode per config). +ZeroClaw skills follow the open [Agent Skills](https://agentskills.io) standard. Each skill is a directory containing `SKILL.md` with YAML frontmatter (`name`, `description`, `version`, `author`, `tags`). ZeroClaw loads skills at agent boot from the per-agent workspace and from configured shared skill bundles. -### Discovery paths +### Discovery paths (v0.8+) -| Scope | Path | -| --- | --- | -| Default workspace | `~/.zeroclaw/workspace/skills//` | -| Per-agent workspace | `~/.zeroclaw/agents//workspace/skills//` | -| Shared bundles (config) | `/shared/skills//` | +| Scope | Path | Loaded by | +| --- | --- | --- | +| Per-agent workspace (primary) | `~/.zeroclaw/agents//workspace/skills//` | `zeroclaw agent -a ` | +| Shared skill bundle | `~/.zeroclaw/shared/skills///` | Agents with `[agents.].skill_bundles` referencing the bundle | +| Legacy (pre-v0.8 migration) | `~/.zeroclaw/workspace/skills/` | Not used by current agent loader | CE ships skills at `./skills//SKILL.md` in this repository. Compound Engineering does **not** copy skills into a generated tree for ZeroClaw at release time; users install from a checkout with `.zeroclaw/scripts/install-skills.sh`. @@ -28,44 +28,41 @@ CE ships skills at `./skills//SKILL.md` in this repository. Compound Engin ZeroClaw's skill audit rejects symlinked skill directories and symlinked files inside a skill. The CE installer copies each skill directory into the target skills path. Re-run the installer after pulling a newer CE release to refresh copies. +### Do not use `zeroclaw skills install` for CE bulk install + +The ZeroClaw CLI's `skills install` command writes under `config.data_dir/skills/`. Agent sessions load from `agent_workspace_dir(alias)/skills/` (and optional shared bundles), not from `data_dir`. The CE install script copies directly into agent workspace paths instead. + ### Bundled scripts -Many CE skills include `scripts/*.sh` and `scripts/*.py`. ZeroClaw blocks script-like files unless `skills.allow_scripts = true` in `~/.zeroclaw/config.toml`. Without that setting, `zeroclaw skills install` fails audit for script-bearing CE skills. +Many CE skills include `scripts/*.sh` and `scripts/*.py`. ZeroClaw blocks script-like files unless `skills.allow_scripts = true` in `~/.zeroclaw/config.toml`. ### Manual-only skills Some CE skills set `disable-model-invocation: true` so Claude and Codex do not auto-invoke them (for example `lfg`, `ce-dogfood`, `ce-polish`). ZeroClaw's frontmatter parser does not read that field. `.zeroclaw/scripts/install-skills.sh` skips manual-only skills by default; pass `--include-manual` to copy them anyway. -## CLI integration - -ZeroClaw exposes native skill management: - -```bash -zeroclaw skills list -zeroclaw skills install /path/to/skill-dir -zeroclaw skills remove -zeroclaw skills audit -``` - -The CE install script wraps per-skill `zeroclaw skills install` only for the default global destination (`~/.zeroclaw/workspace/skills/`). The ZeroClaw CLI always installs relative to `config.data_dir` and cannot target `--dir` overrides — custom paths use direct `cp -R`. - ## Instruction files ZeroClaw projects commonly use root `AGENTS.md` for workspace context. CE skills reference "the project's active instructions and conventions already in your context" rather than hardcoding harness-specific filenames. ## Install commands -Default workspace from a checkout: +Default agent workspace from a checkout: ```bash /path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global ``` -Per-agent workspace: +Explicit agent or all agents: + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --agent my-agent +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --agent all +``` + +Shared bundle (requires config — see `.zeroclaw/INSTALL.md`): ```bash -/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh \ - --dir ~/.zeroclaw/agents//workspace/skills +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --shared ``` Manual-only skills require the opt-in flag: @@ -78,9 +75,9 @@ After installing or updating skills, restart the agent session or gateway if the ## Update and removal -Re-run the install script after pulling a newer CE release. The script removes prior copies (via `zeroclaw skills remove` or `rm -rf`) before reinstalling. +Re-run the install script after pulling a newer CE release. The script removes prior copies before reinstalling. -To remove CE skills, delete the directories from the skills path or run `zeroclaw skills remove ` for each skill id. +To remove CE skills, delete the skill directories from the target workspace or shared bundle path. ## Subagent and tool notes From f03ad3765fcbab0db6cbf007d919f76d6ffdff8d Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Wed, 1 Jul 2026 16:00:03 +0530 Subject: [PATCH 4/5] fix(zeroclaw): use valid shared bundle alias compound_engineering ZeroClaw skill bundle aliases must match [a-z0-9][a-z0-9_]{0,62}; hyphenated compound-engineering could not be referenced in config.toml. --- .zeroclaw/INSTALL.md | 8 ++++---- .zeroclaw/scripts/install-skills.sh | 8 ++++---- .../integrations/native-plugin-install-strategy.md | 2 +- docs/specs/zeroclaw.md | 2 ++ 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.zeroclaw/INSTALL.md b/.zeroclaw/INSTALL.md index e525f54a2..139c0839b 100644 --- a/.zeroclaw/INSTALL.md +++ b/.zeroclaw/INSTALL.md @@ -34,7 +34,7 @@ From a clone of this repository: ### Shared skill bundle (multi-agent hosts) -To install once under `~/.zeroclaw/shared/skills/compound-engineering/` and reference it from agent config: +To install once under `~/.zeroclaw/shared/skills/compound_engineering/` and reference it from agent config: ```bash ./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --shared @@ -43,10 +43,10 @@ To install once under `~/.zeroclaw/shared/skills/compound-engineering/` and refe Then add to `~/.zeroclaw/config.toml`: ```toml -[skill_bundles.compound-engineering] +[skill_bundles.compound_engineering] [agents.default] -skill_bundles = ["compound-engineering"] +skill_bundles = ["compound_engineering"] ``` The script **copies** skill directories (ZeroClaw rejects symlinks at audit time). It does **not** call `zeroclaw skills install` — that CLI writes to `config.data_dir/skills`, which agent sessions do not load. The installer honors `ZEROCLAW_CONFIG_DIR` when set, and refuses unknown agent aliases (run `zeroclaw quickstart` before `--global` or `--agent`). @@ -85,7 +85,7 @@ Edit skills under `skills/` and re-run the install script to refresh copies. Res Remove CE skill directories from the install target (for example `~/.zeroclaw/agents/default/workspace/skills/ce-brainstorm`). Names match folders under `skills/`. -For `--shared` installs, remove skills from `~/.zeroclaw/shared/skills/compound-engineering/` and drop the bundle reference from agent config. +For `--shared` installs, remove skills from `~/.zeroclaw/shared/skills/compound_engineering/` and drop the bundle reference from agent config. ## Project context diff --git a/.zeroclaw/scripts/install-skills.sh b/.zeroclaw/scripts/install-skills.sh index 1e91b8aa4..41b2bdf9d 100755 --- a/.zeroclaw/scripts/install-skills.sh +++ b/.zeroclaw/scripts/install-skills.sh @@ -6,7 +6,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" SKILLS_SRC="$REPO_ROOT/skills" -SHARED_BUNDLE="compound-engineering" +SHARED_BUNDLE="compound_engineering" expand_path() { local path="$1" @@ -44,7 +44,7 @@ Usage: install-skills.sh [--global | --agent ALIAS | --shared | --dir PATH] [--i --global Install into the default agent workspace (same as --agent default) --agent ALIAS Install into /agents//workspace/skills/ --agent all Install into every configured agent under /agents/ - --shared Install bundle at /shared/skills/compound-engineering/ + --shared Install bundle at /shared/skills/compound_engineering/ --dir PATH Install into an explicit skills directory --include-manual Also install manual-only skills (disable-model-invocation: true) @@ -57,10 +57,10 @@ ZeroClaw v0.8+ loads agent skills from per-agent workspace paths, not the legacy For --shared, add to /config.toml: - [skill_bundles.compound-engineering] + [skill_bundles.compound_engineering] [agents.default] - skill_bundles = ["compound-engineering"] + skill_bundles = ["compound_engineering"] CE skills ship bundled shell/Python scripts. Enable allow_scripts before use: diff --git a/docs/solutions/integrations/native-plugin-install-strategy.md b/docs/solutions/integrations/native-plugin-install-strategy.md index 4f0caca43..a55221e3d 100644 --- a/docs/solutions/integrations/native-plugin-install-strategy.md +++ b/docs/solutions/integrations/native-plugin-install-strategy.md @@ -149,7 +149,7 @@ Enable bundled scripts in `~/.zeroclaw/config.toml` before installing — many C allow_scripts = true ``` -For multi-agent hosts, use `--shared` plus a `[skill_bundles.compound-engineering]` entry (see `.zeroclaw/INSTALL.md`). Re-run the install script after pulling a newer CE release. The script skips manual-only skills by default; pass `--include-manual` when needed. +For multi-agent hosts, use `--shared` plus a `[skill_bundles.compound_engineering]` entry (see `.zeroclaw/INSTALL.md`). Re-run the install script after pulling a newer CE release. The script skips manual-only skills by default; pass `--include-manual` when needed. ## Kimi Code CLI diff --git a/docs/specs/zeroclaw.md b/docs/specs/zeroclaw.md index 08fad7548..0d818f465 100644 --- a/docs/specs/zeroclaw.md +++ b/docs/specs/zeroclaw.md @@ -20,6 +20,8 @@ ZeroClaw skills follow the open [Agent Skills](https://agentskills.io) standard. | --- | --- | --- | | Per-agent workspace (primary) | `~/.zeroclaw/agents//workspace/skills//` | `zeroclaw agent -a ` | | Shared skill bundle | `~/.zeroclaw/shared/skills///` | Agents with `[agents.].skill_bundles` referencing the bundle | + +CE uses bundle alias `compound_engineering` (underscore, not hyphen — ZeroClaw aliases must match `[a-z0-9][a-z0-9_]{0,62}`). | Legacy (pre-v0.8 migration) | `~/.zeroclaw/workspace/skills/` | Not used by current agent loader | CE ships skills at `./skills//SKILL.md` in this repository. Compound Engineering does **not** copy skills into a generated tree for ZeroClaw at release time; users install from a checkout with `.zeroclaw/scripts/install-skills.sh`. From abbd338545847450db80268145b16bc6ab71c25c Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Wed, 1 Jul 2026 22:31:03 +0530 Subject: [PATCH 5/5] fix(zeroclaw): mirror runtime install root and workspace path resolution Match ZeroClaw env precedence (CONFIG_DIR > DATA_DIR > WORKSPACE), resolve DATA_DIR through the same config-dir-for-data helper, install into [agents..workspace.path] when set, and add script tests. --- .zeroclaw/INSTALL.md | 2 +- .zeroclaw/scripts/install-skills.sh | 175 +++++++++++++++--- .../native-plugin-install-strategy.md | 2 +- docs/specs/zeroclaw.md | 2 +- tests/zeroclaw-install-skills.test.ts | 145 +++++++++++++++ 5 files changed, 300 insertions(+), 26 deletions(-) create mode 100644 tests/zeroclaw-install-skills.test.ts diff --git a/.zeroclaw/INSTALL.md b/.zeroclaw/INSTALL.md index 139c0839b..4a07d7e8c 100644 --- a/.zeroclaw/INSTALL.md +++ b/.zeroclaw/INSTALL.md @@ -6,7 +6,7 @@ 1. Install ZeroClaw ([install guide](https://github.com/zeroclaw-labs/zeroclaw#install)). 2. Run `zeroclaw quickstart` so you have at least one agent (typically `default`) under `~/.zeroclaw/agents/`. -3. If you use a non-default profile (`ZEROCLAW_CONFIG_DIR` or `--config-dir`), the installer resolves paths from that directory automatically. +3. If you use a non-default profile, the installer follows ZeroClaw runtime precedence for the install root: `ZEROCLAW_CONFIG_DIR`, then `ZEROCLAW_DATA_DIR`, then legacy `ZEROCLAW_WORKSPACE`. Per-agent destinations honor `[agents..workspace.path]` when set in `config.toml`. 4. Enable bundled scripts in your ZeroClaw config. Many CE skills ship `scripts/*.sh` and `scripts/*.py`; ZeroClaw's skill audit blocks script files unless you opt in: ```toml diff --git a/.zeroclaw/scripts/install-skills.sh b/.zeroclaw/scripts/install-skills.sh index 41b2bdf9d..5f33859ed 100755 --- a/.zeroclaw/scripts/install-skills.sh +++ b/.zeroclaw/scripts/install-skills.sh @@ -23,6 +23,33 @@ expand_path() { esac } +resolve_config_dir_for_data() { + local data_dir + data_dir="$(expand_path "$1")" + + if [[ -f "$data_dir/config.toml" ]]; then + printf '%s\n' "$data_dir" + return + fi + + local legacy_dir="${data_dir%/}/../.zeroclaw" + legacy_dir="$(cd "$(dirname "$data_dir")" && pwd)/.zeroclaw" + + if [[ -f "$legacy_dir/config.toml" ]]; then + printf '%s\n' "$legacy_dir" + return + fi + + local base + base="$(basename "$data_dir")" + if [[ "$base" == "data" || "$base" == "workspace" ]]; then + printf '%s\n' "$legacy_dir" + return + fi + + printf '%s\n' "$data_dir" +} + resolve_install_root() { if [[ -n "${ZEROCLAW_INSTALL_ROOT:-}" ]]; then expand_path "$ZEROCLAW_INSTALL_ROOT" @@ -32,6 +59,14 @@ resolve_install_root() { expand_path "$ZEROCLAW_CONFIG_DIR" return fi + if [[ -n "${ZEROCLAW_DATA_DIR:-}" ]]; then + resolve_config_dir_for_data "$ZEROCLAW_DATA_DIR" + return + fi + if [[ -n "${ZEROCLAW_WORKSPACE:-}" ]]; then + resolve_config_dir_for_data "$ZEROCLAW_WORKSPACE" + return + fi printf '%s\n' "$HOME/.zeroclaw" } @@ -42,14 +77,17 @@ usage() { Usage: install-skills.sh [--global | --agent ALIAS | --shared | --dir PATH] [--include-manual] --global Install into the default agent workspace (same as --agent default) - --agent ALIAS Install into /agents//workspace/skills/ - --agent all Install into every configured agent under /agents/ + --agent ALIAS Install into the agent's workspace skills directory + --agent all Install into every configured agent workspace --shared Install bundle at /shared/skills/compound_engineering/ --dir PATH Install into an explicit skills directory --include-manual Also install manual-only skills (disable-model-invocation: true) Set ZEROCLAW_INSTALL_ROOT to override the install root explicitly. -When unset, ZEROCLAW_CONFIG_DIR is used (same precedence as the ZeroClaw runtime). +When unset, install root follows ZeroClaw runtime precedence: + ZEROCLAW_CONFIG_DIR > ZEROCLAW_DATA_DIR > ZEROCLAW_WORKSPACE > ~/.zeroclaw + +Per-agent destinations honor [agents..workspace.path] when set in config.toml. ZeroClaw v0.8+ loads agent skills from per-agent workspace paths, not the legacy ~/.zeroclaw/workspace/skills tree. This script does not call zeroclaw skills install @@ -112,20 +150,68 @@ while [[ $# -gt 0 ]]; do esac done +read_agent_workspace_path() { + local alias="$1" + local config="$INSTALL_ROOT/config.toml" + + [[ -f "$config" ]] || return 0 + + awk -v alias="$alias" ' + function trim(s) { + sub(/^[ \t]+/, "", s) + sub(/[ \t]+$/, "", s) + return s + } + function unquote(s) { + s = trim(s) + if (s ~ /^".*"$/) { + sub(/^"/, "", s) + sub(/"$/, "", s) + } else if (s ~ /^'\''.*'\''$/) { + sub(/^'\''/, "", s) + sub(/'\''$/, "", s) + } + return s + } + /^\[agents\./ { + in_agent = ($0 == "[agents." alias "]" || $0 == "[agents.\"" alias "\"]") + in_workspace = ($0 == "[agents." alias ".workspace]" || $0 == "[agents.\"" alias "\".workspace]") + next + } + in_workspace && /^[ \t]*path[ \t]*=/ { + sub(/^[ \t]*path[ \t]*=[ \t]*/, "") + print unquote($0) + exit + } + in_agent && /^[ \t]*workspace[ \t]*=[ \t]*\{/ { + line = $0 + sub(/^[ \t]*workspace[ \t]*=[ \t]*\{[ \t]*/, "", line) + sub(/\}[ \t]*$/, "", line) + split(line, parts, /,[ \t]*/) + for (i in parts) { + if (parts[i] ~ /^path[ \t]*=/) { + sub(/^path[ \t]*=[ \t]*/, "", parts[i]) + print unquote(parts[i]) + exit + } + } + } + ' "$config" +} + agent_configured() { local alias="$1" - local agent_root="$INSTALL_ROOT/agents/$alias" + local config="$INSTALL_ROOT/config.toml" - if [[ ! -d "$agent_root" ]]; then - return 1 + if [[ -f "$config" ]] && grep -qE "^\[agents\.(${alias}|\"${alias}\")\]" "$config"; then + return 0 fi - local config="$INSTALL_ROOT/config.toml" - if [[ ! -f "$config" ]]; then + if [[ -d "$INSTALL_ROOT/agents/$alias" && ! -f "$config" ]]; then return 0 fi - grep -qE "^\[agents\.(${alias}|\"${alias}\")\]" "$config" + return 1 } require_agent() { @@ -138,29 +224,72 @@ require_agent() { exit 1 } +agent_skills_dir() { + local alias="$1" + local custom_path + + custom_path="$(read_agent_workspace_path "$alias")" + if [[ -n "$custom_path" ]]; then + printf '%s\n' "$(expand_path "$custom_path")/skills" + return + fi + + printf '%s\n' "$INSTALL_ROOT/agents/$alias/workspace/skills" +} + +list_configured_agent_aliases() { + local config="$INSTALL_ROOT/config.toml" + [[ -f "$config" ]] || return 1 + + awk ' + /^\[agents\.([a-zA-Z0-9_-]+|\"[^\"]+\")\]$/ { + line = $0 + sub(/^\[agents\./, "", line) + sub(/\]$/, "", line) + gsub(/^"|"$/, "", line) + print line + } + ' "$config" +} + resolve_destinations() { case "$SCOPE" in --global | --agent) if [[ "$AGENT_ALIAS" == "all" ]]; then - if [[ ! -d "$INSTALL_ROOT/agents" ]]; then - echo "error: no agents directory at $INSTALL_ROOT/agents" >&2 - exit 1 - fi - local agent_dir alias_name - for agent_dir in "$INSTALL_ROOT/agents"/*/; do - [[ -d "$agent_dir" ]] || continue - alias_name="$(basename "$agent_dir")" - if agent_configured "$alias_name"; then - DESTS+=("$INSTALL_ROOT/agents/$alias_name/workspace/skills") + local aliases=() + local alias_name + + while IFS= read -r alias_name; do + [[ -n "$alias_name" ]] || continue + aliases+=("$alias_name") + done < <(list_configured_agent_aliases || true) + + if [[ ${#aliases[@]} -eq 0 ]]; then + if [[ ! -d "$INSTALL_ROOT/agents" ]]; then + echo "error: no agents directory at $INSTALL_ROOT/agents" >&2 + exit 1 fi - done - if [[ ${#DESTS[@]} -eq 0 ]]; then - echo "error: no configured agents found under $INSTALL_ROOT/agents" >&2 + local agent_dir + for agent_dir in "$INSTALL_ROOT/agents"/*/; do + [[ -d "$agent_dir" ]] || continue + alias_name="$(basename "$agent_dir")" + if agent_configured "$alias_name"; then + aliases+=("$alias_name") + fi + done + fi + + if [[ ${#aliases[@]} -eq 0 ]]; then + echo "error: no configured agents found under $INSTALL_ROOT" >&2 exit 1 fi + + for alias_name in "${aliases[@]}"; do + DESTS+=("$(agent_skills_dir "$alias_name")") + done else require_agent "$AGENT_ALIAS" - DESTS+=("$INSTALL_ROOT/agents/$AGENT_ALIAS/workspace/skills") + DESTS+=("$(agent_skills_dir "$AGENT_ALIAS")") fi ;; --shared) diff --git a/docs/solutions/integrations/native-plugin-install-strategy.md b/docs/solutions/integrations/native-plugin-install-strategy.md index a55221e3d..798bdb912 100644 --- a/docs/solutions/integrations/native-plugin-install-strategy.md +++ b/docs/solutions/integrations/native-plugin-install-strategy.md @@ -50,7 +50,7 @@ The install strategy follows from that: prefer each harness's native plugin/pack | OpenCode | Git-backed OpenCode plugin entry in `opencode.json` | No | `.opencode/plugins/compound-engineering.js` registers the CE skills directory directly. | | Pi | Git-backed Pi package install from this repository | No | Root `package.json` exposes `.pi/extensions/compound-engineering.ts` and the CE skills directory. `pi-ask-user` is a recommended companion for richer prompts. | | Antigravity CLI | Native plugin install from root `plugin.json` + `skills/`, or bundled `.agy/` entry point | No | `agy plugin install https://github.com/EveryInc/compound-engineering-plugin` for one-command remote install. `.agy/plugin.json` symlinks to the root manifest; `.agy/skills` symlinks to `skills/`. | -| ZeroClaw | Native skills install via `.zeroclaw/scripts/install-skills.sh` | No | Copies CE skills into `~/.zeroclaw/agents//workspace/skills/` (or a shared bundle). Set `[skills] allow_scripts = true` for script-bearing CE skills. Honors `ZEROCLAW_CONFIG_DIR`. | +| ZeroClaw | Native skills install via `.zeroclaw/scripts/install-skills.sh` | No | Copies CE skills into agent workspace skills dirs (honors `[agents..workspace.path]`). Install root follows `ZEROCLAW_CONFIG_DIR` > `ZEROCLAW_DATA_DIR` > `ZEROCLAW_WORKSPACE`. Set `[skills] allow_scripts = true` for script-bearing CE skills. | Kiro is no longer a documented CE install target. Historical converter and cleanup code may remain for regression coverage or old artifact handling, but user-facing install docs should not advertise Kiro. diff --git a/docs/specs/zeroclaw.md b/docs/specs/zeroclaw.md index 0d818f465..f95a4743f 100644 --- a/docs/specs/zeroclaw.md +++ b/docs/specs/zeroclaw.md @@ -32,7 +32,7 @@ ZeroClaw's skill audit rejects symlinked skill directories and symlinked files i ### Do not use `zeroclaw skills install` for CE bulk install -The ZeroClaw CLI's `skills install` command writes under `config.data_dir/skills/`. Agent sessions load from `agent_workspace_dir(alias)/skills/` (and optional shared bundles), not from `data_dir`. The CE install script copies directly into agent workspace paths instead. It honors `ZEROCLAW_CONFIG_DIR` when set and refuses unknown agent aliases when `config.toml` is present. +The ZeroClaw CLI's `skills install` command writes under `config.data_dir/skills/`. Agent sessions load from `agent_workspace_dir(alias)/skills/` (and optional shared bundles), not from `data_dir`. The CE install script copies directly into agent workspace paths instead. Install root follows ZeroClaw runtime precedence (`ZEROCLAW_CONFIG_DIR` > `ZEROCLAW_DATA_DIR` > `ZEROCLAW_WORKSPACE` > `~/.zeroclaw`), honors `[agents..workspace.path]` overrides, and refuses unknown agent aliases when `config.toml` is present. ### Bundled scripts diff --git a/tests/zeroclaw-install-skills.test.ts b/tests/zeroclaw-install-skills.test.ts new file mode 100644 index 000000000..b6800304a --- /dev/null +++ b/tests/zeroclaw-install-skills.test.ts @@ -0,0 +1,145 @@ +import { describe, expect, test } from "bun:test" +import { promises as fs } from "fs" +import os from "os" +import path from "path" + +const installScript = path.join( + import.meta.dir, + "..", + ".zeroclaw", + "scripts", + "install-skills.sh", +) + +const sampleSkill = "ce-brainstorm" +const defaultDerivedSkills = (root: string) => + path.join(root, "agents", "default", "workspace", "skills", sampleSkill) + +type RunResult = { + exitCode: number + stdout: string + stderr: string +} + +async function runInstall( + env: Record, + args: string[] = ["--global"], +): Promise { + const proc = Bun.spawn(["bash", installScript, ...args], { + cwd: path.join(import.meta.dir, ".."), + env: { ...process.env, ...env }, + stderr: "pipe", + stdout: "pipe", + }) + + const [exitCode, stdout, stderr] = await Promise.all([ + proc.exited, + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + ]) + + return { exitCode, stdout, stderr } +} + +async function pathExists(filePath: string): Promise { + try { + await fs.access(filePath) + return true + } catch { + return false + } +} + +describe("zeroclaw install-skills.sh", () => { + test("honors ZEROCLAW_DATA_DIR when config.toml lives at the data root", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "zc-data-root-")) + const dataRoot = path.join(root, "profile") + await fs.mkdir(path.join(dataRoot, "agents", "default"), { recursive: true }) + await fs.writeFile( + path.join(dataRoot, "config.toml"), + "[agents.default]\n", + ) + + const result = await runInstall({ + ZEROCLAW_INSTALL_ROOT: undefined, + ZEROCLAW_CONFIG_DIR: undefined, + ZEROCLAW_DATA_DIR: dataRoot, + ZEROCLAW_WORKSPACE: undefined, + }) + + expect(result.exitCode).toBe(0) + expect(await pathExists(path.join(dataRoot, "agents", "default", "workspace", "skills", sampleSkill))).toBe( + true, + ) + expect(await pathExists(defaultDerivedSkills(root))).toBe(false) + }) + + test("honors ZEROCLAW_DATA_DIR when config.toml lives under parent .zeroclaw", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "zc-data-nested-")) + const installRoot = path.join(root, "project", ".zeroclaw") + const dataDir = path.join(root, "project", "data") + await fs.mkdir(path.join(installRoot, "agents", "default"), { recursive: true }) + await fs.mkdir(dataDir, { recursive: true }) + await fs.writeFile(path.join(installRoot, "config.toml"), "[agents.default]\n") + + const result = await runInstall({ + ZEROCLAW_INSTALL_ROOT: undefined, + ZEROCLAW_CONFIG_DIR: undefined, + ZEROCLAW_DATA_DIR: dataDir, + ZEROCLAW_WORKSPACE: undefined, + }) + + expect(result.exitCode).toBe(0) + expect( + await pathExists(path.join(installRoot, "agents", "default", "workspace", "skills", sampleSkill)), + ).toBe(true) + }) + + test("prefers ZEROCLAW_CONFIG_DIR over ZEROCLAW_DATA_DIR", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "zc-config-wins-")) + const configRoot = path.join(root, "config-profile") + const dataRoot = path.join(root, "data-profile") + await fs.mkdir(path.join(configRoot, "agents", "default"), { recursive: true }) + await fs.mkdir(path.join(dataRoot, "agents", "default"), { recursive: true }) + await fs.writeFile(path.join(configRoot, "config.toml"), "[agents.default]\n") + await fs.writeFile(path.join(dataRoot, "config.toml"), "[agents.default]\n") + + const result = await runInstall({ + ZEROCLAW_CONFIG_DIR: configRoot, + ZEROCLAW_DATA_DIR: dataRoot, + ZEROCLAW_INSTALL_ROOT: undefined, + ZEROCLAW_WORKSPACE: undefined, + }) + + expect(result.exitCode).toBe(0) + expect( + await pathExists(path.join(configRoot, "agents", "default", "workspace", "skills", sampleSkill)), + ).toBe(true) + expect( + await pathExists(path.join(dataRoot, "agents", "default", "workspace", "skills", sampleSkill)), + ).toBe(false) + }) + + test("installs into [agents..workspace.path] when configured", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "zc-custom-workspace-")) + const installRoot = path.join(root, "install") + const customWorkspace = path.join(root, "custom-workspace") + await fs.mkdir(path.join(installRoot, "agents", "default"), { recursive: true }) + await fs.writeFile( + path.join(installRoot, "config.toml"), + `[agents.default]\n\n[agents.default.workspace]\npath = "${customWorkspace}"\n`, + ) + + const result = await runInstall({ + ZEROCLAW_INSTALL_ROOT: installRoot, + ZEROCLAW_CONFIG_DIR: undefined, + ZEROCLAW_DATA_DIR: undefined, + ZEROCLAW_WORKSPACE: undefined, + }) + + expect(result.exitCode).toBe(0) + expect(await pathExists(path.join(customWorkspace, "skills", sampleSkill))).toBe(true) + expect(await pathExists(defaultDerivedSkills(installRoot))).toBe(false) + expect(result.stdout).toContain(customWorkspace) + }) +})