diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 3cddabae..6ab5a7cf 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,13 +7,10 @@ "name": "Microsoft", "email": "opensource@microsoft.com" }, - "metadata": { - "pluginRoot": "./plugins" - }, "plugins": [ { "name": "conductor", - "source": "./conductor", + "source": "./plugins/conductor", "description": "Validate, run, and execute Conductor multi-agent YAML workflows. Adds the conductor skill that knows the workflow schema, CLI commands, and execution model.", "author": { "name": "Microsoft" }, "homepage": "https://github.com/microsoft/conductor", diff --git a/changelog.d/587.fixed.md b/changelog.d/587.fixed.md new file mode 100644 index 00000000..21c587af --- /dev/null +++ b/changelog.d/587.fixed.md @@ -0,0 +1,4 @@ +**Claude Code plugin marketplace**: `/plugin install conductor@conductor` no +longer fails with `Source path does not exist`. The marketplace entry now points +at `./plugins/conductor`, relative to the repository root as Claude Code +resolves it, and the unused `metadata.pluginRoot` is removed. diff --git a/tests/test_plugins/test_marketplace.py b/tests/test_plugins/test_marketplace.py index 2cc4f192..1fb676a0 100644 --- a/tests/test_plugins/test_marketplace.py +++ b/tests/test_plugins/test_marketplace.py @@ -9,6 +9,7 @@ import pytest from conductor.plugins.errors import PluginNotFoundError, PluginSourceError +from conductor.plugins.manifest import is_plugin_root from conductor.plugins.marketplace import find_marketplace_manifest, read_marketplace from .conftest import make_marketplace, make_plugin @@ -430,3 +431,24 @@ def test_plugin_key_narrowing_finds_a_plugin_published_only_in_the_secondary_cat marketplace = read_marketplace(tmp_path, name="acme", plugin="solo") assert marketplace.plugins == {"solo": tmp_path / "dist" / "copilot" / "solo"} + + +class TestShippedMarketplace: + """The marketplace this repository ships to Claude Code must install.""" + + def test_claude_catalog_sources_exist_under_the_repo_root(self) -> None: + # Claude Code resolves a relative `source` from the marketplace root + # (the directory holding `.claude-plugin/`) and ignores + # `metadata.pluginRoot` for a `./` path, so a source written relative + # to `pluginRoot` fails at install time with "Source path does not + # exist" (issue #587). + repo_root = Path(__file__).resolve().parents[2] + catalog = json.loads( + (repo_root / ".claude-plugin" / "marketplace.json").read_text(encoding="utf-8") + ) + + assert catalog["plugins"] + for entry in catalog["plugins"]: + source = entry["source"] + assert isinstance(source, str) and source.startswith("./"), entry["name"] + assert is_plugin_root(repo_root / source), f"{entry['name']}: {source}"