SecondBrain/howto/migrating-a-standalone-plug...

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.
type/howto
domain/claude-code-plugins
tool/claude-code
project/cc-os
global 2026-07-03 2026-07-03 experience-driven
cc-os-plugin-skill-naming-convention
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 orchestrationos-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.