157 lines
8.0 KiB
Markdown
157 lines
8.0 KiB
Markdown
---
|
|
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
|