8.0 KiB
| type | title | summary | tags | scope | last_updated | date | update_note | related | source | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| howto | Migrating a standalone plugin into cc-os | 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. |
|
global | 2026-07-03 | 2026-07-03 | experience-driven |
|
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.jsonandenabledPluginsin~/.claude/settings.jsonfor a<name>@cc-pluginsentry
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 addthem there. - Update the
namefield in.claude-plugin/plugin.jsontoos-<name>. - Check
hooks.jsonand 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.jsonunderlocal-plugins, matching theos-vaultentry's exact JSON shape. - Add
"os-<name>@local-plugins": truetoenabledPluginsin~/.claude/settings.json; remove the old"<name>@cc-plugins": true"entry. - Run:
claude plugin marketplace update local-plugins, thenclaude 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) andclaude 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 theos-vaultentry'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.mdit now makes redundant), remove that section and replace it with a short pointer to the plugin.
Verification
git -C cc-os status --shortshows the newplugins/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 listshowsos-<name>@local-pluginsenabled and the old<name>@cc-pluginsentry gone.claude plugin details os-<name>@local-pluginsresolves 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 addin the destination and separately remove + (if tracked)git rmin 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.jsonentry — 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