Module 5: Plugins — Creating and Distributing

4. Dynamic Loading and Versioning — Loading, Semver, and Registries

4. Dynamic Loading and Versioning — Loading, Semver, and Registries

Description

Your plugin works locally. You installed it with claude plugins add ./path, verified that the agents load, and tested that the skills preload. But there remains the question that makes this effort valuable in the long run: how do you distribute it to your team? How do you handle versions when you update an agent file? What happens if a colleague installs version 2.0 but their project needs 1.x?

In this capsule you learn what turns a local plugin into a professional package: how Claude Code discovers and loads plugins at the start of a session, how semver versioning applied to plugins works, how to publish to an npm registry (local with verdaccio or remote with npm/GitHub Packages), and how to handle updates without breaking existing flows.

By the end, you'll understand the complete cycle: create → version → publish → install → update → pin version. Your plugin stops being "a directory on your machine" and becomes "a package any developer can install with one command."


⚠️ EXPERIMENTAL FEATURE

The plugin dynamic loading system and the integration with npm registries reflect the functionality available as of March 2026. The loading mechanisms and management commands may change. The semver versioning and registry-distribution principles are industry standards and hold.

Last check: March 2026


Dynamic Loading: How Claude Code Loads Plugins

The loading process at the start of a session

When you start a Claude Code session, this happens internally:

claude (session start)
    │
    ├── 1. Reads plugin configuration
    │      ├── Global plugins (user)
    │      └── Project plugins (.claude/plugins)
    │
    ├── 2. For each installed plugin:
    │      ├── Locates the package (node_modules or local path)
    │      ├── Reads package.json
    │      ├── Verifies claudeCodePlugin: true
    │      └── If missing or false → ignores the package
    │
    ├── 3. Loads components:
    │      ├── agents/*.md → registers as available subagents
    │      ├── skills/*.md → registers as preloadable skills
    │      ├── hooks → registers in the event system
    │      └── MCP servers → starts processes
    │
    └── 4. Session ready with active plugins

Plugin scopes

Plugins can be installed in two scopes:

GLOBAL (user)                        PROJECT
─────────────                        ───────

Available in all projects            Only in this project
Installed once                       Installed per project
Ideal for personal tools             Ideal for team plugins

claude plugins add --global @pkg     claude plugins add @pkg
~/.config/claude-code/plugins/       .claude/plugins/

Practical rule:

  • Personal plugins (your favorite reviewer) → global
  • Team plugins (project conventions) → project
  • If the same plugin is in global AND project → project wins

Name conflict resolution

What happens if two plugins have an agent named reviewer?

@team/quality-plugin       →  agents/reviewer.md
@team/security-plugin      →  agents/reviewer.md

Claude Code resolves this with plugin prefixes:

Available agents:
  quality-plugin/reviewer     ← from the quality plugin
  security-plugin/reviewer    ← from the security plugin

To avoid confusion, use unique names in your agent files: quality-reviewer instead of reviewer.


Semver Versioning for Plugins

Why versioning matters

Without versioning:

Monday:    You publish a plugin with reviewer v1
Tuesday:   You change the reviewer's report format
Wednesday: Your colleague installs → their CI pipeline breaks because
           it expects the old format

With versioning:

Monday:    You publish @team/quality@1.0.0
Tuesday:   You publish @team/quality@1.1.0 (new report format)
Wednesday: Your colleague has @team/quality@1.0.0 pinned → nothing breaks
           When they're ready, they update to 1.1.0

Semver applied to plugins

MAJOR.MINOR.PATCH
  │     │     │
  │     │     └── Bug fix in an agent file
  │     │         (fix a typo in a system prompt, adjust maxTurns)
  │     │
  │     └── New compatible functionality
  │         (new agent file, new skill, new hook)
  │
  └── Breaking change
      (agent renamed, skill removed, output format changed)

Concrete examples of each type of change

Patch (1.0.0 → 1.0.1):

 agents/reviewer.md:
 ---
 name: reviewer
-maxTurns: 15
+maxTurns: 20
 ---

A minor fix. No one who uses the plugin will notice a problem.

Minor (1.0.0 → 1.1.0):

ADDED: agents/test-writer.md
ADDED: skills/python-standards.md

New functionality. The existing agent files didn't change. Backward compatible.

Major (1.0.0 → 2.0.0):

 agents/reviewer.md:
 ---
-name: reviewer
+name: quality-reviewer
 ---

-## Output Format
-### Review Report
-**Issues:** [list]
+## Output Format
+### Quality Analysis
+**Findings:** [structured object]

Breaking change: the agent's name changed (scripts that invoke it by name break) and the output format changed (parsers that expect the old format fail).


Version Pinning

What version pinning is

Version pinning is fixing the exact version (or range) of a plugin your project uses. Without pinning, each npm update could bring a new version with unexpected changes.

Pinning strategies

{
  "dependencies": {
    "@team/quality": "1.2.3",     // Exact: only 1.2.3
    "@team/quality": "^1.2.3",    // Compatible: >=1.2.3 <2.0.0
    "@team/quality": "~1.2.3",    // Patch only: >=1.2.3 <1.3.0
    "@team/quality": "*"          // Anything (dangerous)
  }
}

When to use each strategy

PRODUCTION / CI:
├── Exact version: "1.2.3"
├── Reason: guaranteed reproducibility
└── Update: manual, controlled, with review

DEVELOPMENT:
├── Compatible: "^1.2.3"
├── Reason: receive patches and new features
└── Update: automatic on minor/patch

EXPERIMENTATION:
├── Latest: "*" or no pinning
├── Reason: always the most recent version
└── Risk: breaking changes without notice

Recommendation: For teams, use ^ (compatible) in development and an exact version in CI/CD.


Publishing to a Registry

Option 1: npm Registry (Public)

For open-source plugins or companies with an npm org:

cd ~/plugins-workshop/my-quality-plugin

# Login to npm (first time)
npm login

# Publish
npm publish --access public

If you use a scoped package (@org/name), you need --access public for it to be public, or to belong to the org to publish it as private.

Option 2: Verdaccio (Local Registry)

For teams that don't want to publish to public npm. Verdaccio is an npm registry that runs on your local network or internal server.

Install verdaccio:

npm install -g verdaccio
verdaccio

This starts a local registry at http://localhost:4873.

Configure npm to use verdaccio:

npm set registry http://localhost:4873

Publish to the local registry:

cd ~/plugins-workshop/my-quality-plugin
npm publish --registry http://localhost:4873

Install from verdaccio:

claude plugins add @your-org/code-quality-plugin --registry http://localhost:4873

Or if Claude Code uses npm internally:

npm install @your-org/code-quality-plugin --registry http://localhost:4873

Option 3: GitHub Packages

For teams that use GitHub:

Configure package.json:

{
  "name": "@your-github-org/code-quality-plugin",
  "version": "1.0.0",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  }
}

Authenticate:

echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN" >> ~/.npmrc

Publish:

npm publish

Registry comparison

AspectPublic npmVerdaccio (local)GitHub Packages
CostFree (public)Free (self-hosted)Free (with GitHub)
AccessAnyoneLocal network/VPNOrg members
Setupnpm accountInstall + runGitHub token
CI/CDEasyRequires internal networkIntegrated with GH Actions
Ideal forOpen sourceInternal teamsTeams on GitHub

Complete Flow: Create → Publish → Install → Update

The professional lifecycle

1. DEVELOPMENT
   ├── Create the plugin (capsule 03)
   ├── Test locally
   └── Iterate until stable

2. INITIAL PUBLICATION
   ├── Set version 1.0.0
   ├── npm publish (or verdaccio/GitHub)
   └── Notify the team

3. INSTALLATION
   ├── claude plugins add @team/quality@^1.0.0
   └── Verify with claude plugins list

4. UPDATE (patch/minor)
   ├── Edit the agent files
   ├── Bump version: npm version patch (or minor)
   ├── npm publish
   └── Users with ^1.0.0 receive the update automatically

5. BREAKING CHANGE (major)
   ├── Edit with incompatible changes
   ├── Document what changed and why
   ├── Bump version: npm version major
   ├── npm publish
   └── Users must manually update to ^2.0.0

Version bump commands

# Patch: 1.0.0 → 1.0.1
npm version patch

# Minor: 1.0.0 → 1.1.0
npm version minor

# Major: 1.0.0 → 2.0.0
npm version major

These commands update version in package.json and create a git commit + tag automatically.


Handling Updates and Migrations

Update without breaking changes

# The user runs:
npm update @team/code-quality-plugin

# Or if Claude Code manages plugins:
claude plugins update @team/code-quality-plugin

With ^1.0.0, this updates to the latest available 1.x.x.

Migration with breaking changes

When you publish a major version, provide a migration guide:

# Migrating from v1 to v2

## Breaking changes

### Agent "reviewer" renamed to "quality-reviewer"
- **Before:** `reviewer`
- **After:** `quality-reviewer`
- **Action:** Update any reference to `reviewer` in your prompts

### Output format changed
- **Before:** "Critical", "Warning", "Info" sections
- **After:** "Findings" sections with a severity field
- **Action:** If you parse the reviewer's output, update the parser

## How to update

```bash
claude plugins remove @team/code-quality-plugin
claude plugins add @team/code-quality-plugin@^2.0.0

Staying on v1

If you need more time:

claude plugins add @team/code-quality-plugin@~1.5.0

### Deprecation strategy

v1.5.0 → Add warnings: "reviewer will be renamed to quality-reviewer in v2" v2.0.0 → Apply the change v2.1.0 → Add a temporary alias: reviewer → quality-reviewer with a warning v3.0.0 → Remove the alias, only quality-reviewer


Gradual. No one breaks all at once.

---

## Comparison: Version Pinning vs Latest

### Side-by-side

VERSION PINNING ("^1.2.3") LATEST ("*" or no pin) ────────────────────────── ──────────────────────

Predictable Unpredictable Controlled updates Automatic updates Reproducible CI CI can fail on a surprise update Requires manual maintenance Zero maintenance Safe for production Risky for production May miss security patches Always has security patches


### Real scenarios

**Scenario: CI pipeline that runs daily**

With pinning: @team/quality@1.2.3 → same version every day → consistent results

Without pinning: Monday: v1.2.3 → pipeline OK Tuesday: v1.3.0 comes out → new agent with new output → parser fails → pipeline broken


**Scenario: Individual developer exploring**

With pinning: You install v1.2.3 → it works → 3 months later, you miss v1.5.0 with improvements

Without pinning: Always the latest → you lose stability but gain immediate improvements


**Recommendation:**
- Team projects: `^major.minor.patch` (compatible range)
- CI/CD: exact version `major.minor.patch`
- Personal development: `*` or a wide `^`

---

## Manual Alternative: Git Tags as Versioning

If you don't have access to an npm registry:

```bash
# In the plugin's repo
git tag v1.0.0
git push origin v1.0.0

# To install a specific version
git clone --branch v1.0.0 https://github.com/team/quality-plugin.git
claude plugins add ./quality-plugin

Or with git submodules:

# In the project that consumes the plugin
git submodule add -b v1.0.0 https://github.com/team/quality-plugin.git .plugins/quality
claude plugins add .plugins/quality

You lose the convenience of npm update, but you gain formal versioning with git tags.


Exercises

Exercise 1: Determine the type of version bump (Easy)

For each change, indicate whether it's patch, minor, or major and explain why:

  1. You fix a typo in the reviewer's system prompt
  2. You add a new agent file security-scanner.md
  3. You rename implementer.md to code-implementer.md (changes the name field)
  4. You increase the implementer's maxTurns from 25 to 30
  5. You change the reviewer's output format from a flat list to structured JSON
  6. You add a new skill python-patterns.md
See solution
  1. Patch (1.0.0 → 1.0.1) — Typo fix, doesn't affect functionality
  2. Minor (1.0.0 → 1.1.0) — New component, backward compatible
  3. Major (1.0.0 → 2.0.0) — The agent's name changes, scripts that invoke it break
  4. Patch (1.0.0 → 1.0.1) — Internal adjustment, doesn't affect the consumer
  5. Major (1.0.0 → 2.0.0) — Output format changes, existing parsers break
  6. Minor (1.0.0 → 1.1.0) — New component, nothing existing changes

Exercise 2: Write a .npmignore (Easy)

Write a .npmignore for a plugin that excludes development files but includes all the plugin's components. The plugin directory contains:

agents/ skills/ hooks/ tests/ docs/ .github/
package.json README.md CHANGELOG.md
.eslintrc.json .prettierrc .env.example
See solution
# .npmignore

# Testing
tests/

# Documentation (README.md is included by default)
docs/

# CI/CD
.github/

# Development config
.eslintrc.json
.prettierrc
.env.example

# Editor
.vscode/
.idea/
*.swp

Note: README.md, package.json, and the files in files are always included. This .npmignore excludes only what's not part of the distributable plugin.

Exercise 3: Configure verdaccio and publish (Medium)

Install verdaccio, publish to it locally, and install your plugin from the local registry. Document each step with the command executed and its expected output.

See solution
# 1. Install verdaccio
npm install -g verdaccio

# 2. Start verdaccio
verdaccio
# Expected output:
# http address - http://localhost:4873/

# 3. Create a user in verdaccio (in another terminal)
npm adduser --registry http://localhost:4873
# Username: your-user
# Password: your-password
# Email: you@email.com

# 4. Publish the plugin
cd ~/plugins-workshop/my-quality-plugin
npm publish --registry http://localhost:4873
# Expected output:
# + @your-org/code-quality-plugin@1.0.0

# 5. Verify the publication
npm view @your-org/code-quality-plugin --registry http://localhost:4873
# Output: package information

# 6. Install from verdaccio in another project
cd ~/another-project
claude plugins add @your-org/code-quality-plugin --registry http://localhost:4873
# Or: npm install @your-org/code-quality-plugin --registry http://localhost:4873

# 7. Verify the installation
claude plugins list
# Output: @your-org/code-quality-plugin@1.0.0

Exercise 4: Simulate a breaking change and migration (Medium)

Take your quality plugin at v1.0.0. Make three breaking changes: rename an agent, change a skill, and modify a hook. Write: the new package.json with v2.0.0, the modified files, and a complete migration guide.

See solution

Breaking changes:

  1. agents/reviewer.md → agents/quality-reviewer.md (name: quality-reviewer)
  2. skills/api-conventions.md → skills/api-standards.md (name: api-standards)
  3. Hook matcher changes from "Write" to "Write|Edit"

package.json v2.0.0:

{
  "name": "@your-org/code-quality-plugin",
  "version": "2.0.0",
  "description": "Code quality agents with reviewer and implementer (v2)",
  "claudeCodePlugin": true,
  "files": ["agents", "skills"],
  "keywords": ["claude-code", "plugin", "code-quality"]
}

Migration guide (MIGRATION.md):

# Migrating from v1 to v2

## Breaking Changes

### 1. Agent renamed: reviewer → quality-reviewer
**v1:** `reviewer`
**v2:** `quality-reviewer`
**Action:** Update any prompts or scripts that reference "reviewer" by name

### 2. Skill renamed: api-conventions → api-standards
**v1:** `api-conventions`
**v2:** `api-standards`
**Action:** If your CLAUDE.md references this skill, update the name

### 3. Hook scope expanded
**v1:** Hook triggers on Write only
**v2:** Hook triggers on Write and Edit
**Action:** Review hook behavior — it now also runs on edits, not just new files

## Upgrade Steps

1. Remove v1: `claude plugins remove @your-org/code-quality-plugin`
2. Install v2: `claude plugins add @your-org/code-quality-plugin@^2.0.0`
3. Update references in your prompts/scripts
4. Test with a simple review command

## Staying on v1

If you need more time:
`claude plugins add @your-org/code-quality-plugin@~1.5.0`

Exercise 5: Design a versioning strategy for a team (Hard)

Your team has 5 developers, 3 projects, and 2 internal plugins. Design a versioning strategy that covers:

  • Who can publish new versions
  • How breaking changes are communicated
  • What pinning each project uses (dev vs CI)
  • How urgent hotfixes are handled
See solution
# Plugin Versioning Strategy

## Who Can Publish

- **Minor/Patch:** Any team member with review from 1 peer
- **Major:** Requires review from tech lead + 1 week notice
- **Hotfix:** Any team member can publish patch directly (post-review)

## Communication

- **Patch:** CHANGELOG update + Slack notification
- **Minor:** CHANGELOG + Slack + email to plugin consumers
- **Major:** CHANGELOG + MIGRATION.md + team meeting + 2-week deprecation period

## Pinning Strategy

### Development (local)
```json
"@team/quality": "^1.0.0"

Receive patches and minors automatically.

Staging

"@team/quality": "~1.2.0"

Receive patches only. Minor updates require explicit bump.

CI/CD and Production

"@team/quality": "1.2.3"

Exact pin. Zero surprises. Update requires PR.

Hotfix Process

  1. Fix the issue on main branch
  2. npm version patch (1.2.3 → 1.2.4)
  3. npm publish
  4. Notify in Slack: "Hotfix @team/quality@1.2.4 — [issue]"
  5. CI projects: open PR to bump exact version
  6. Dev projects: automatic via ^/~ range

Release Cadence

  • Patches: as needed (bug fixes)
  • Minors: bi-weekly (new features)
  • Majors: quarterly at most (breaking changes)

</details>

---

## Troubleshooting

### Problem 1: "npm publish fails with 'You must be logged in'"

**Symptom:** `npm ERR! need auth`

**Solution:**

```bash
# For public npm
npm login

# For verdaccio
npm adduser --registry http://localhost:4873

# For GitHub Packages
echo "//npm.pkg.github.com/:_authToken=$GITHUB_TOKEN" >> ~/.npmrc

Problem 2: "Plugin installed but the version doesn't match"

Symptom: claude plugins list shows a different version than expected.

Solution:

# Check which version is installed
claude plugins list

# Force a specific version
claude plugins remove @team/quality
claude plugins add @team/quality@1.2.3

Problem 3: "Verdaccio doesn't start or isn't accessible"

Symptom: npm publish --registry http://localhost:4873 fails with connection refused.

Solution:

# Verify that verdaccio is running
ps aux | grep verdaccio

# Restart
verdaccio --listen 4873

# Verify access
curl http://localhost:4873

Problem 4: "Breaking change not detected — the user didn't know it was major"

Symptom: You published a breaking change as a minor and broke other people's flows.

Preventive solution:

Before each publication, review this checklist:

Did any agent file change its name?           → MAJOR
Was any agent file removed?                    → MAJOR
Did any agent's output format change?          → MAJOR
Was any skill renamed or removed?              → MAJOR
Were only new files added?                     → MINOR
Were only bugs fixed without changing the API? → PATCH

Problem 5: "Two projects need different versions of the same plugin"

Symptom: Project A needs v1.x, Project B needs v2.x.

Solution:

Each project has its own pinning in .claude/plugins or in its configuration:

Project A: @team/quality@^1.5.0
Project B: @team/quality@^2.0.0

npm resolves the versions per project. There's no conflict as long as each project has its own node_modules or plugin configuration.


Summary

  • Dynamic loading happens at the start of a session — Claude Code discovers plugins, verifies claudeCodePlugin: true, and registers agents, skills, and hooks
  • Plugins can be scoped to global (user) or project — project takes precedence
  • Semver is mandatory: patch for fixes, minor for compatible features, major for breaking changes
  • Version pinning protects from surprises: exact for CI, ^ for development, ~ for staging
  • Three registry options: public npm (open source), verdaccio (local/private), GitHub Packages (teams on GitHub)
  • The complete flow is: create → test locally → publish → install via registry → bump version → update
  • Breaking changes require: a major bump, a migration guide, and a deprecation period
  • npm version patch|minor|major automates the bump and creates git tags
  • The alternative without a registry is git tags + submodules — functional but without npm's convenience

Additional Resources

  1. Semantic Versioning (semver.org) — Complete versioning standard
  2. npm Versioning — Semver applied to npm packages
  3. npm publish — Reference for the publish command
  4. Verdaccio — Local/private npm registry
  5. GitHub Packages — GitHub's npm registry
  6. npm version — Automating version bumps
  7. Claude Code CLI Reference — Plugin commands
  8. npm .npmignore — Controlling published files

Next capsule: In capsule 05 you build the complete project: a code quality plugin with reviewer + implementer + api-conventions skill, packaged, tested locally, and published to a local registry. It's the close of the module — everything learned in capsules 02-04 integrated into a functional, distributable product.