---
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/<name>/ into cc-os as plugins/os-<name>/, 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/<name>/`
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/<name>/` 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 `<name>@cc-plugins` entry

## Steps

### Step 1: Decide the new name

Apply the cc-os `os-` prefix convention (see [[cc-os-plugin-skill-naming-convention]]) —
`<name>` becomes `os-<name>`. 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/<name>/`): 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/<name>/` 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-<name>/`, `git add` them there.
- Update the `name` field in `.claude-plugin/plugin.json` to `os-<name>`.
- 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 `<name>/` 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/<name>/` 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-<name> ~/.claude/plugins/os-<name>`
- Add an entry to `~/.claude/plugins/.claude-plugin/marketplace.json` under `local-plugins`,
  matching the `os-vault` entry's exact JSON shape.
- Add `"os-<name>@local-plugins": true` to `enabledPlugins` in `~/.claude/settings.json`; remove
  the old `"<name>@cc-plugins": true"` entry.
- Run: `claude plugin marketplace update local-plugins`, then
  `claude plugin install os-<name>@local-plugins`, then — if a stale install record exists —
  `claude plugin uninstall <name>@cc-plugins`.
- Verify with `claude plugin list` (new name enabled, old name gone) and
  `claude plugin details os-<name>@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-<name>/` 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-<name>@local-plugins` enabled and the old
  `<name>@cc-plugins` entry gone.
- `claude plugin details os-<name>@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
