From 5a698bb1e6b705017da2d82f8f062803048a651b Mon Sep 17 00:00:00 2001 From: haoqiuzhi Date: Fri, 17 Apr 2026 15:46:26 +0800 Subject: [PATCH 1/4] docs(codex): add symlink install guide for git-pull upgrades --- .codex/INSTALL.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 3 ++- README.md | 2 ++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 .codex/INSTALL.md diff --git a/.codex/INSTALL.md b/.codex/INSTALL.md new file mode 100644 index 000000000..70f7a4bc4 --- /dev/null +++ b/.codex/INSTALL.md @@ -0,0 +1,66 @@ +# Installing Compound Engineering for Codex + +Enable compound-engineering skills in Codex via native skill discovery. Clone the repo and symlink the plugin skills directory. + +## Prerequisites + +- Git + +## Installation + +1. **Clone the repository:** + ```bash + git clone https://github.com/EveryInc/compound-engineering-plugin.git ~/.codex/compound-engineering-plugin + ``` + +2. **Create the skills symlink:** + ```bash + mkdir -p ~/.agents/skills + ln -s ~/.codex/compound-engineering-plugin/plugins/compound-engineering/skills ~/.agents/skills/compound-engineering + ``` + + **Windows (PowerShell):** + ```powershell + New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills" + cmd /c mklink /J "$env:USERPROFILE\.agents\skills\compound-engineering" "$env:USERPROFILE\.codex\compound-engineering-plugin\plugins\compound-engineering\skills" + ``` + +3. **Restart Codex** (quit and relaunch the CLI) to discover the skills. + +## Migrating from copied skill installs + +If you previously installed CE skills by copying into `~/.codex/skills`, remove the copied CE directories so the symlinked source is authoritative: + +```bash +rm -rf ~/.codex/skills/ce-* +``` + +Then restart Codex. + +## Verify + +```bash +ls -la ~/.agents/skills/compound-engineering +``` + +You should see a symlink (or junction on Windows) pointing to: + +```text +~/.codex/compound-engineering-plugin/plugins/compound-engineering/skills +``` + +## Updating + +```bash +cd ~/.codex/compound-engineering-plugin && git pull +``` + +Skills update through the symlink after pull. Restart Codex if the current session still shows stale skill metadata. + +## Uninstalling + +```bash +rm ~/.agents/skills/compound-engineering +``` + +Optionally delete the clone: `rm -rf ~/.codex/compound-engineering-plugin`. diff --git a/.gitignore b/.gitignore index 4d4069d6f..016d62d2a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ .DS_Store *.log node_modules/ -.codex/ +.codex/* +!.codex/INSTALL.md todos/ .worktrees .context/ diff --git a/README.md b/README.md index 4d8401b85..2ebbd7a89 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,8 @@ bunx @every-env/compound-plugin install compound-engineering --to qwen bunx @every-env/compound-plugin install compound-engineering --to all ``` +For a git-clone + symlink workflow that supports `git pull` upgrades in Codex, see [.codex/INSTALL.md](.codex/INSTALL.md). +
Output format details per target From 55442f8166592cdf857d9521d58610688789643f Mon Sep 17 00:00:00 2001 From: haoqiuzhi Date: Fri, 17 Apr 2026 16:18:50 +0800 Subject: [PATCH 2/4] docs(codex): derive migration cleanup targets dynamically --- .codex/INSTALL.md | 61 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/.codex/INSTALL.md b/.codex/INSTALL.md index 70f7a4bc4..be2412a19 100644 --- a/.codex/INSTALL.md +++ b/.codex/INSTALL.md @@ -29,10 +29,67 @@ Enable compound-engineering skills in Codex via native skill discovery. Clone th ## Migrating from copied skill installs -If you previously installed CE skills by copying into `~/.codex/skills`, remove the copied CE directories so the symlinked source is authoritative: +If you previously installed CE skills by copying into `~/.codex/skills`, remove copied CE directories by deriving the exact Codex copy targets from the plugin's current `skills/*/SKILL.md` metadata (`name` + `ce_platforms`): ```bash -rm -rf ~/.codex/skills/ce-* +python3 - <<'PY' +from pathlib import Path +import os +import re +import shutil + +codex_home = Path(os.environ.get("CODEX_HOME", str(Path.home() / ".codex"))) +repo_root = codex_home / "compound-engineering-plugin" +source_skills_root = repo_root / "plugins" / "compound-engineering" / "skills" +target_skills_root = codex_home / "skills" + +if not source_skills_root.exists(): + raise SystemExit(f"Source skills directory not found: {source_skills_root}") + +def parse_frontmatter(skill_md: Path) -> dict[str, str]: + text = skill_md.read_text(encoding="utf-8") + if not text.startswith("---\n"): + return {} + end = text.find("\n---", 4) + if end == -1: + return {} + frontmatter = text[4:end + 1] + parsed: dict[str, str] = {} + for line in frontmatter.splitlines(): + if ":" not in line: + continue + key, value = line.split(":", 1) + parsed[key.strip()] = value.strip() + return parsed + +removed = 0 +for skill_dir in sorted(p for p in source_skills_root.iterdir() if p.is_dir()): + skill_md = skill_dir / "SKILL.md" + if not skill_md.exists(): + continue + + fm = parse_frontmatter(skill_md) + platforms = fm.get("ce_platforms") + if platforms: + values = [v.strip().strip("'\"") for v in platforms.strip("[]").split(",") if v.strip()] + if "codex" not in values: + continue + + name = fm.get("name", skill_dir.name).strip().strip("'\"") + copied_dir_name = re.sub(r":", "-", name) + target = target_skills_root / copied_dir_name + + if target.is_symlink() or target.is_file(): + target.unlink() + removed += 1 + print(f"removed {target}") + elif target.is_dir(): + shutil.rmtree(target) + removed += 1 + print(f"removed {target}") + +print(f"done: removed {removed} copied CE skill directories") +PY ``` Then restart Codex. From eadd223bb2db0de636b30efdfec853904f8b80bf Mon Sep 17 00:00:00 2001 From: haoqiuzhi Date: Fri, 17 Apr 2026 16:22:47 +0800 Subject: [PATCH 3/4] docs(codex): move cleanup logic to script and nest skill mapping --- .codex/INSTALL.md | 80 +++----------- scripts/cleanup-codex-copied-skills.py | 144 +++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 65 deletions(-) create mode 100755 scripts/cleanup-codex-copied-skills.py diff --git a/.codex/INSTALL.md b/.codex/INSTALL.md index be2412a19..a06a0dbd3 100644 --- a/.codex/INSTALL.md +++ b/.codex/INSTALL.md @@ -15,81 +15,30 @@ Enable compound-engineering skills in Codex via native skill discovery. Clone th 2. **Create the skills symlink:** ```bash - mkdir -p ~/.agents/skills - ln -s ~/.codex/compound-engineering-plugin/plugins/compound-engineering/skills ~/.agents/skills/compound-engineering + mkdir -p ~/.agents/skills/compound-engineering-plugin + ln -s ~/.codex/compound-engineering-plugin/plugins/compound-engineering/skills ~/.agents/skills/compound-engineering-plugin/compound-engineering ``` **Windows (PowerShell):** ```powershell - New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills" - cmd /c mklink /J "$env:USERPROFILE\.agents\skills\compound-engineering" "$env:USERPROFILE\.codex\compound-engineering-plugin\plugins\compound-engineering\skills" + New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills\compound-engineering-plugin" + cmd /c mklink /J "$env:USERPROFILE\.agents\skills\compound-engineering-plugin\compound-engineering" "$env:USERPROFILE\.codex\compound-engineering-plugin\plugins\compound-engineering\skills" ``` 3. **Restart Codex** (quit and relaunch the CLI) to discover the skills. ## Migrating from copied skill installs -If you previously installed CE skills by copying into `~/.codex/skills`, remove copied CE directories by deriving the exact Codex copy targets from the plugin's current `skills/*/SKILL.md` metadata (`name` + `ce_platforms`): +If you previously installed CE skills by copying into `~/.codex/skills`, run the cleanup script to derive exact Codex copy targets from current `skills/*/SKILL.md` metadata (`name` + `ce_platforms`) and remove only those copied CE skill directories: ```bash -python3 - <<'PY' -from pathlib import Path -import os -import re -import shutil - -codex_home = Path(os.environ.get("CODEX_HOME", str(Path.home() / ".codex"))) -repo_root = codex_home / "compound-engineering-plugin" -source_skills_root = repo_root / "plugins" / "compound-engineering" / "skills" -target_skills_root = codex_home / "skills" - -if not source_skills_root.exists(): - raise SystemExit(f"Source skills directory not found: {source_skills_root}") - -def parse_frontmatter(skill_md: Path) -> dict[str, str]: - text = skill_md.read_text(encoding="utf-8") - if not text.startswith("---\n"): - return {} - end = text.find("\n---", 4) - if end == -1: - return {} - frontmatter = text[4:end + 1] - parsed: dict[str, str] = {} - for line in frontmatter.splitlines(): - if ":" not in line: - continue - key, value = line.split(":", 1) - parsed[key.strip()] = value.strip() - return parsed - -removed = 0 -for skill_dir in sorted(p for p in source_skills_root.iterdir() if p.is_dir()): - skill_md = skill_dir / "SKILL.md" - if not skill_md.exists(): - continue - - fm = parse_frontmatter(skill_md) - platforms = fm.get("ce_platforms") - if platforms: - values = [v.strip().strip("'\"") for v in platforms.strip("[]").split(",") if v.strip()] - if "codex" not in values: - continue - - name = fm.get("name", skill_dir.name).strip().strip("'\"") - copied_dir_name = re.sub(r":", "-", name) - target = target_skills_root / copied_dir_name - - if target.is_symlink() or target.is_file(): - target.unlink() - removed += 1 - print(f"removed {target}") - elif target.is_dir(): - shutil.rmtree(target) - removed += 1 - print(f"removed {target}") - -print(f"done: removed {removed} copied CE skill directories") -PY +python3 scripts/cleanup-codex-copied-skills.py +``` + +If you previously mapped to `~/.agents/skills/compound-engineering`, remove that legacy symlink: + +```bash +rm ~/.agents/skills/compound-engineering ``` Then restart Codex. @@ -97,7 +46,7 @@ Then restart Codex. ## Verify ```bash -ls -la ~/.agents/skills/compound-engineering +ls -la ~/.agents/skills/compound-engineering-plugin/compound-engineering ``` You should see a symlink (or junction on Windows) pointing to: @@ -117,7 +66,8 @@ Skills update through the symlink after pull. Restart Codex if the current sessi ## Uninstalling ```bash -rm ~/.agents/skills/compound-engineering +rm ~/.agents/skills/compound-engineering-plugin/compound-engineering +rmdir ~/.agents/skills/compound-engineering-plugin 2>/dev/null || true ``` Optionally delete the clone: `rm -rf ~/.codex/compound-engineering-plugin`. diff --git a/scripts/cleanup-codex-copied-skills.py b/scripts/cleanup-codex-copied-skills.py new file mode 100755 index 000000000..6fef990d0 --- /dev/null +++ b/scripts/cleanup-codex-copied-skills.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Remove copied CE skill directories from ~/.codex/skills for migration to symlinked skills.""" + +from __future__ import annotations + +import argparse +import os +import re +import shutil +from pathlib import Path + + +def parse_frontmatter(skill_md: Path) -> dict[str, str]: + text = skill_md.read_text(encoding="utf-8") + if not text.startswith("---\n"): + return {} + end = text.find("\n---", 4) + if end == -1: + return {} + frontmatter = text[4 : end + 1] + parsed: dict[str, str] = {} + for line in frontmatter.splitlines(): + if ":" not in line: + continue + key, value = line.split(":", 1) + parsed[key.strip()] = value.strip() + return parsed + + +def parse_platforms(raw_value: str | None) -> list[str] | None: + if not raw_value: + return None + text = raw_value.strip() + if not text.startswith("[") or not text.endswith("]"): + return None + values = [item.strip().strip("'\"") for item in text[1:-1].split(",") if item.strip()] + return values + + +def should_copy_to_codex(frontmatter: dict[str, str]) -> bool: + platforms = parse_platforms(frontmatter.get("ce_platforms")) + if platforms is None: + return True + return "codex" in platforms + + +def resolve_repo_root() -> Path: + return Path(__file__).resolve().parents[1] + + +def collect_target_skill_dirs(source_skills_root: Path) -> list[str]: + targets: set[str] = set() + for skill_dir in sorted(path for path in source_skills_root.iterdir() if path.is_dir()): + skill_md = skill_dir / "SKILL.md" + if not skill_md.exists(): + continue + + frontmatter = parse_frontmatter(skill_md) + if not should_copy_to_codex(frontmatter): + continue + + # 核心规则:使用 frontmatter name(不存在时回退目录名),并与 Codex 落盘规则一致做 ":" -> "-" + skill_name = frontmatter.get("name", skill_dir.name).strip().strip("'\"") + target_name = re.sub(r":", "-", skill_name) + targets.add(target_name) + return sorted(targets) + + +def remove_copied_skills(target_skills_root: Path, target_names: list[str], dry_run: bool) -> int: + removed = 0 + for name in target_names: + target = target_skills_root / name + if not target.exists() and not target.is_symlink(): + continue + + if dry_run: + print(f"[dry-run] would remove {target}") + removed += 1 + continue + + # 核心流程:仅删除计算出的 CE 复制目录,不触碰其他第三方/个人 skill + if target.is_symlink() or target.is_file(): + target.unlink() + print(f"removed {target}") + removed += 1 + elif target.is_dir(): + shutil.rmtree(target) + print(f"removed {target}") + removed += 1 + return removed + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Remove copied Compound Engineering skill directories from ~/.codex/skills " + "based on current plugin SKILL.md metadata." + ) + ) + parser.add_argument( + "--codex-home", + default=os.environ.get("CODEX_HOME", str(Path.home() / ".codex")), + help="Codex home directory (default: $CODEX_HOME or ~/.codex)", + ) + parser.add_argument( + "--source-skills-root", + default=None, + help="Override source skills root (default: /plugins/compound-engineering/skills)", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Print targets that would be removed without deleting files", + ) + return parser + + +def main() -> int: + parser = build_parser() + args = parser.parse_args() + + codex_home = Path(args.codex_home).expanduser() + repo_root = resolve_repo_root() + source_skills_root = ( + Path(args.source_skills_root).expanduser() + if args.source_skills_root + else repo_root / "plugins" / "compound-engineering" / "skills" + ) + target_skills_root = codex_home / "skills" + + if not source_skills_root.exists(): + parser.error(f"source skills directory not found: {source_skills_root}") + if not target_skills_root.exists(): + print(f"nothing to remove: target skills root does not exist: {target_skills_root}") + return 0 + + target_names = collect_target_skill_dirs(source_skills_root) + removed = remove_copied_skills(target_skills_root, target_names, args.dry_run) + print(f"done: removed {removed} copied CE skill directories") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From d93ee28ead79a478224f7d16f4224981feca4ad8 Mon Sep 17 00:00:00 2001 From: haoqiuzhi Date: Fri, 17 Apr 2026 17:03:19 +0800 Subject: [PATCH 4/4] docs(codex): harden cleanup safety and simplify skill symlink path --- .codex/INSTALL.md | 30 ++++--- scripts/cleanup-codex-copied-skills.py | 118 +++++++++++++++++++++---- 2 files changed, 122 insertions(+), 26 deletions(-) diff --git a/.codex/INSTALL.md b/.codex/INSTALL.md index a06a0dbd3..c229bfa43 100644 --- a/.codex/INSTALL.md +++ b/.codex/INSTALL.md @@ -9,20 +9,23 @@ Enable compound-engineering skills in Codex via native skill discovery. Clone th ## Installation 1. **Clone the repository:** + ```bash git clone https://github.com/EveryInc/compound-engineering-plugin.git ~/.codex/compound-engineering-plugin ``` 2. **Create the skills symlink:** + ```bash - mkdir -p ~/.agents/skills/compound-engineering-plugin - ln -s ~/.codex/compound-engineering-plugin/plugins/compound-engineering/skills ~/.agents/skills/compound-engineering-plugin/compound-engineering + mkdir -p ~/.agents/skills + ln -s ~/.codex/compound-engineering-plugin/plugins/compound-engineering/skills ~/.agents/skills/compound-engineering ``` **Windows (PowerShell):** + ```powershell - New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills\compound-engineering-plugin" - cmd /c mklink /J "$env:USERPROFILE\.agents\skills\compound-engineering-plugin\compound-engineering" "$env:USERPROFILE\.codex\compound-engineering-plugin\plugins\compound-engineering\skills" + New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills" + cmd /c mklink /J "$env:USERPROFILE\.agents\skills\compound-engineering" "$env:USERPROFILE\.codex\compound-engineering-plugin\plugins\compound-engineering\skills" ``` 3. **Restart Codex** (quit and relaunch the CLI) to discover the skills. @@ -32,13 +35,21 @@ Enable compound-engineering skills in Codex via native skill discovery. Clone th If you previously installed CE skills by copying into `~/.codex/skills`, run the cleanup script to derive exact Codex copy targets from current `skills/*/SKILL.md` metadata (`name` + `ce_platforms`) and remove only those copied CE skill directories: ```bash -python3 scripts/cleanup-codex-copied-skills.py +python3 scripts/cleanup-codex-copied-skills.py --dry-run +python3 scripts/cleanup-codex-copied-skills.py --apply ``` -If you previously mapped to `~/.agents/skills/compound-engineering`, remove that legacy symlink: +If the script reports `skipped_unverified`, review those paths first. Use `--force-unverified` only when you explicitly want to delete those mismatched directories: ```bash -rm ~/.agents/skills/compound-engineering +python3 scripts/cleanup-codex-copied-skills.py --apply --force-unverified +``` + +If you previously mapped to the nested path `~/.agents/skills/compound-engineering-plugin/compound-engineering`, remove that legacy symlink: + +```bash +rm ~/.agents/skills/compound-engineering-plugin/compound-engineering +rmdir ~/.agents/skills/compound-engineering-plugin 2>/dev/null || true ``` Then restart Codex. @@ -46,7 +57,7 @@ Then restart Codex. ## Verify ```bash -ls -la ~/.agents/skills/compound-engineering-plugin/compound-engineering +ls -la ~/.agents/skills/compound-engineering ``` You should see a symlink (or junction on Windows) pointing to: @@ -66,8 +77,7 @@ Skills update through the symlink after pull. Restart Codex if the current sessi ## Uninstalling ```bash -rm ~/.agents/skills/compound-engineering-plugin/compound-engineering -rmdir ~/.agents/skills/compound-engineering-plugin 2>/dev/null || true +rm ~/.agents/skills/compound-engineering ``` Optionally delete the clone: `rm -rf ~/.codex/compound-engineering-plugin`. diff --git a/scripts/cleanup-codex-copied-skills.py b/scripts/cleanup-codex-copied-skills.py index 6fef990d0..4a417117b 100755 --- a/scripts/cleanup-codex-copied-skills.py +++ b/scripts/cleanup-codex-copied-skills.py @@ -7,6 +7,7 @@ import os import re import shutil +from dataclasses import dataclass from pathlib import Path @@ -48,8 +49,15 @@ def resolve_repo_root() -> Path: return Path(__file__).resolve().parents[1] -def collect_target_skill_dirs(source_skills_root: Path) -> list[str]: - targets: set[str] = set() +@dataclass(frozen=True) +class TargetSkill: + expected_skill_name: str + target_dir_name: str + source_skill_md: Path + + +def collect_target_skill_dirs(source_skills_root: Path) -> list[TargetSkill]: + targets: dict[str, TargetSkill] = {} for skill_dir in sorted(path for path in source_skills_root.iterdir() if path.is_dir()): skill_md = skill_dir / "SKILL.md" if not skill_md.exists(): @@ -62,22 +70,73 @@ def collect_target_skill_dirs(source_skills_root: Path) -> list[str]: # 核心规则:使用 frontmatter name(不存在时回退目录名),并与 Codex 落盘规则一致做 ":" -> "-" skill_name = frontmatter.get("name", skill_dir.name).strip().strip("'\"") target_name = re.sub(r":", "-", skill_name) - targets.add(target_name) - return sorted(targets) - - -def remove_copied_skills(target_skills_root: Path, target_names: list[str], dry_run: bool) -> int: + targets[target_name] = TargetSkill( + expected_skill_name=skill_name, + target_dir_name=target_name, + source_skill_md=skill_md, + ) + return [targets[key] for key in sorted(targets.keys())] + + +def verify_target_ownership( + target: Path, + expected_skill_name: str, + source_skill_md: Path, +) -> tuple[bool, str]: + """ + Verify ownership before deletion. + A target is considered CE-owned when it contains SKILL.md and that file's + frontmatter `name` matches the expected CE skill name. + """ + skill_md = target / "SKILL.md" + if not skill_md.is_file(): + return False, "missing SKILL.md" + frontmatter = parse_frontmatter(skill_md) + actual_name = frontmatter.get("name", "").strip().strip("'\"") + if not actual_name: + return False, "missing frontmatter name" + if actual_name != expected_skill_name: + return False, f"name mismatch (actual={actual_name}, expected={expected_skill_name})" + expected_content = source_skill_md.read_text(encoding="utf-8") + actual_content = skill_md.read_text(encoding="utf-8") + if actual_content != expected_content: + return False, "SKILL.md content differs from current CE source" + return True, "verified" + + +def remove_copied_skills( + target_skills_root: Path, + targets: list[TargetSkill], + apply: bool, + force_unverified: bool, +) -> tuple[int, int]: removed = 0 - for name in target_names: - target = target_skills_root / name + skipped_unverified = 0 + + for target_skill in targets: + target = target_skills_root / target_skill.target_dir_name if not target.exists() and not target.is_symlink(): continue - if dry_run: - print(f"[dry-run] would remove {target}") - removed += 1 + # 核心安全检查:删除前先验证目录归属,避免误删同名第三方 skill + verified, reason = verify_target_ownership( + target=target, + expected_skill_name=target_skill.expected_skill_name, + source_skill_md=target_skill.source_skill_md, + ) + if not verified and not force_unverified: + print(f"skip {target}: unverified ownership ({reason})") + skipped_unverified += 1 + continue + + if not apply: + suffix = "" if verified else " [force-required]" + print(f"[dry-run] would remove {target}{suffix}") continue + if not verified and force_unverified: + print(f"force remove {target}: {reason}") + # 核心流程:仅删除计算出的 CE 复制目录,不触碰其他第三方/个人 skill if target.is_symlink() or target.is_file(): target.unlink() @@ -87,7 +146,7 @@ def remove_copied_skills(target_skills_root: Path, target_names: list[str], dry_ shutil.rmtree(target) print(f"removed {target}") removed += 1 - return removed + return removed, skipped_unverified def build_parser() -> argparse.ArgumentParser: @@ -112,12 +171,26 @@ def build_parser() -> argparse.ArgumentParser: action="store_true", help="Print targets that would be removed without deleting files", ) + parser.add_argument( + "--apply", + action="store_true", + help="Actually delete verified copied CE skill directories", + ) + parser.add_argument( + "--force-unverified", + action="store_true", + help="Delete unverified targets as well (use with --apply only)", + ) return parser def main() -> int: parser = build_parser() args = parser.parse_args() + if args.apply and args.dry_run: + parser.error("--apply and --dry-run are mutually exclusive") + if args.force_unverified and not args.apply: + parser.error("--force-unverified requires --apply") codex_home = Path(args.codex_home).expanduser() repo_root = resolve_repo_root() @@ -134,9 +207,22 @@ def main() -> int: print(f"nothing to remove: target skills root does not exist: {target_skills_root}") return 0 - target_names = collect_target_skill_dirs(source_skills_root) - removed = remove_copied_skills(target_skills_root, target_names, args.dry_run) - print(f"done: removed {removed} copied CE skill directories") + apply = args.apply + if args.dry_run: + apply = False + + targets = collect_target_skill_dirs(source_skills_root) + removed, skipped_unverified = remove_copied_skills( + target_skills_root=target_skills_root, + targets=targets, + apply=apply, + force_unverified=args.force_unverified, + ) + mode = "apply" if apply else "dry-run" + print( + f"done ({mode}): removed {removed} copied CE skill directories, " + f"skipped_unverified {skipped_unverified}" + ) return 0