--- type: howto title: Migrating a standalone plugin into cc-os summary: Step-by-step procedure for moving a plugin built standalone in ~/dev/cc-plugins// into cc-os as plugins/os-/, renaming, re-registering, and documenting it — replayable for any future plugin migration. tags: - type/howto - domain/claude-code-plugins - tool/claude-code - project/cc-os scope: global last_updated: 2026-07-03 date: 2026-07-03 update_note: experience-driven related: - cc-os-plugin-skill-naming-convention source: cc-os --- # Migrating a standalone plugin into cc-os ## Opening Reach for this when a Claude Code plugin was prototyped standalone in `~/dev/cc-plugins//` and needs to move into `cc-os` — the canonical home for this person's Claude Code plugins, alongside `os-vault`, the reference implementation. First applied 2026-07-03 migrating `orchestration` → `os-orchestration`; next planned use: `doc-hygiene`. The steps assume two separate git repos (cc-plugins and cc-os) — this is a cross-repo move, not a `git mv`. ## Prerequisites - [ ] Plugin exists at `~/dev/cc-plugins//` with a `.claude-plugin/plugin.json` - [ ] `cc-os/plugins/os-vault/` is available to read as the structural reference to mirror - [ ] You know the plugin's current registration: check `~/.claude/plugins/.claude-plugin/marketplace.json` and `enabledPlugins` in `~/.claude/settings.json` for a `@cc-plugins` entry ## Steps ### Step 1: Decide the new name Apply the cc-os `os-` prefix convention (see [[cc-os-plugin-skill-naming-convention]]) — `` becomes `os-`. Decide this **before** touching registration files. Renaming after registration means redoing all three registration touchpoints (directory/symlink, marketplace manifest, `settings.json`). ### Step 2: Content-conflict check Before moving any files, check whether the plugin's own instructional content (injected hook text, guidance it provides) conflicts or duplicates anything already hardcoded in cc-os's `CLAUDE.md` or other projects' `CLAUDE.md` files. Two blocks that look like "the same copy-pasted text" can have silently drifted into contradictory rules — this happened with `orchestration`'s `ORCHESTRATION.md` (permissive: "small ops direct, delegate wide work") vs. cc-os `CLAUDE.md`'s "Session Orchestration" section (absolutist: "delegate everything, no exceptions"), which read as duplicates but were opposites. If a conflict exists: **surface it explicitly to the user as a decision point.** Do not pick a "canonical" version yourself, even if one side looks more specific or more recent — that intuition can point the wrong way (the more-permissive, more-evolved side may be the one the user actually intends as the global default). Ask two things: which version becomes the global default, and whether cc-os (or any other project) keeps a local override or adopts the global default outright. **Expected result:** either no conflict found (proceed), or an explicit user decision recorded before Step 3. ### Step 3: Read the source and reference structure Read the full plugin source tree (`~/dev/cc-plugins//`): manifest/`plugin.json`, `hooks/`, `skills/`, `agents/`, `commands/`, docs. Grep for hardcoded absolute paths (the old repo path, or unwarranted `$HOME` assumptions) that would break on a move — hook scripts should resolve their own root dynamically (e.g. `os.path.dirname(os.path.abspath(__file__))`, `${CLAUDE_PLUGIN_ROOT}` in `hooks.json`), not hardcode it. Separately, read `cc-os/plugins/os-vault/` as the structural pattern to mirror: it is a git-tracked `plugins//` directory with its **own** `.claude-plugin/plugin.json` committed inside the repo — not something generated only under `~/.claude/plugins/.claude-plugin/`. ### Step 4: Copy files and rename in place - Copy the plugin's files into `cc-os/plugins/os-/`, `git add` them there. - Update the `name` field in `.claude-plugin/plugin.json` to `os-`. - Check `hooks.json` and any scripts for hardcoded references to the *old* plugin name (not just English text) and update those too. - Do **not** edit the plugin's own instructional/content files (e.g. an injected markdown doc) unless Step 2's conflict check produced an explicit user decision to change them. ### Step 5: Remove the old source In `~/dev/cc-plugins/`: delete `/` entirely, and remove its entry from `~/dev/cc-plugins/.claude-plugin/marketplace.json` (if that manifest lists other plugins, remove only this one's entry). **Expected result:** `~/dev/cc-plugins//` no longer exists; the cc-plugins marketplace manifest has no reference to it. ### Step 6: Do not auto-commit Leave both repos' changes staged/unstaged for the user to review and commit explicitly at the end — this mirrors the standing "only commit when asked" rule. ### Step 7: Re-register under the local-plugins convention This mirrors the existing "Renaming or moving a local plugin" procedure in `cc-os/CLAUDE.md` (written for pure renames) — this migration is the superset that also covers a cross-repo move and first-time registration. - Symlink: `ln -s cc-os/plugins/os- ~/.claude/plugins/os-` - Add an entry to `~/.claude/plugins/.claude-plugin/marketplace.json` under `local-plugins`, matching the `os-vault` entry's exact JSON shape. - Add `"os-@local-plugins": true` to `enabledPlugins` in `~/.claude/settings.json`; remove the old `"@cc-plugins": true"` entry. - Run: `claude plugin marketplace update local-plugins`, then `claude plugin install os-@local-plugins`, then — if a stale install record exists — `claude plugin uninstall @cc-plugins`. - Verify with `claude plugin list` (new name enabled, old name gone) and `claude plugin details os-@local-plugins` (hooks/skills resolved). ### Step 8: Update cc-os documentation - `CLAUDE.md`: add or update an "Implemented Components" entry for the new plugin, following the `os-vault` entry's style — location, hooks, behavior, migration provenance/date. - If Step 2 surfaced and resolved a content conflict, or otherwise changed a locked decision, add an ADR to `docs/memory-system/03-architecture-decisions.md`: the problem, the decision, and what was rejected. - If the plugin's content supersedes a project-local block (e.g. a section in `CLAUDE.md` it now makes redundant), remove that section and replace it with a short pointer to the plugin. ## Verification - `git -C cc-os status --short` shows the new `plugins/os-/` files staged, plus the doc edits — nothing left in a half-migrated state. - `git -C cc-plugins status --short` (or equivalent) shows no trace of the old plugin directory. - `claude plugin list` shows `os-@local-plugins` enabled and the old `@cc-plugins` entry gone. - `claude plugin details os-@local-plugins` resolves hooks/skills without error. - Start a fresh session and confirm the plugin's hook/skill behavior actually fires (e.g. a SessionStart hook's injected content appears). ## Gotchas - **Two repos, not one `git mv`.** cc-plugins and cc-os are independent git repos — you must copy + `git add` in the destination and separately remove + (if tracked) `git rm` in the source. - **Drifted "duplicate" content.** Don't assume two blocks that look like copy-pasted duplicates actually agree — diff them. Silently picking one side to be "canonical" can flip behavior machine-wide without the user knowingly authorizing it. - **Doc drift on hook filenames.** When documentation is drafted by an agent that didn't directly inspect the final file tree, it can guess a conventional filename (e.g. `session_start.py`) instead of the plugin's actual hook script name (e.g. `inject.py`). Verify hook filenames against the actual directory listing before trusting generated doc updates. - **Rename before registration, not after.** Renaming a plugin post-registration means redoing the symlink, the marketplace entry, and the `settings.json` entry — decide the final name in Step 1. ## Related - [[cc-os-plugin-skill-naming-convention]] — the `os-` prefix and verb-first skill naming rules applied in Step 1