From f4df175bfe5a49fe8c633101eae9708bf3600641 Mon Sep 17 00:00:00 2001 From: Florian DAVID <150798857+fdaviddpt@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:29:46 +0200 Subject: [PATCH] fix: declare the supertool dependency as supertool-cli (#1806) dpt-plugins renamed the plugin; the old name no longer resolves, so oss failed its dependency check and registered no commands. Lookups by plugin name accept both spellings for installs registered before the rename. Co-Authored-By: Claude Opus 5.5 (1M context) --- .claude-plugin/plugin.json | 2 +- bin/oss-workspace | 4 +- changelog.d/1806.fixed.md | 1 + scripts/doctor.py | 23 ++++++++-- tests/test_supertool_cli_rename.py | 70 ++++++++++++++++++++++++++++++ tests/test_version_manifest.py | 4 +- 6 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 changelog.d/1806.fixed.md create mode 100644 tests/test_supertool_cli_rename.py diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 4006b691..e1e9e95e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -17,7 +17,7 @@ "github" ], "dependencies": [ - "supertool", + "supertool-cli", "remember", "claude-jit-context" ] diff --git a/bin/oss-workspace b/bin/oss-workspace index c7416ad5..5e5a6b29 100755 --- a/bin/oss-workspace +++ b/bin/oss-workspace @@ -1258,7 +1258,7 @@ if not isinstance(plugins, dict): installs = [] malformed = "" for key, entries in plugins.items(): - if key.split("@")[0] != "supertool": + if key.split("@")[0] not in ("supertool-cli", "supertool"): continue if entries and not isinstance(entries, list): malformed = "%s's supertool entry is not a JSON array" % registry @@ -1499,7 +1499,7 @@ if not isinstance(plugins, dict): installs = [] malformed = "" for key, entries in plugins.items(): - if key.split("@")[0] != "supertool": + if key.split("@")[0] not in ("supertool-cli", "supertool"): continue if entries and not isinstance(entries, list): malformed = "%s's supertool entry is not a JSON array" % registry diff --git a/changelog.d/1806.fixed.md b/changelog.d/1806.fixed.md new file mode 100644 index 00000000..c5912922 --- /dev/null +++ b/changelog.d/1806.fixed.md @@ -0,0 +1 @@ +- The manifest now declares its supertool dependency as `supertool-cli`, the name the dpt-plugins marketplace publishes it under, so installing oss no longer shows `Dependency "supertool@dpt-plugins" is not installed` and leaves `/oss:run` unregistered (#1806). `/oss:doctor` and `oss-workspace` recognise an install registered under either name. diff --git a/scripts/doctor.py b/scripts/doctor.py index cbe9b464..d14d2eea 100644 --- a/scripts/doctor.py +++ b/scripts/doctor.py @@ -1419,6 +1419,18 @@ def check_tool(name, probe): # mid-session, has none -- and no diagnostic said so. SUPERTOOL_ENTRY = "supertool" +#: The supertool plugin's marketplace names, current first. dpt-plugins renamed +#: it from `supertool` to `supertool-cli`; an install registered before the +#: rename keeps the old key in `installed_plugins.json` and the plugin cache, so +#: every lookup by plugin name accepts both. +SUPERTOOL_PLUGIN_NAMES = ("supertool-cli", "supertool") + + +def is_supertool_plugin_key(key): + """True when an `installed_plugins.json` key (`name@marketplace`) is supertool.""" + return str(key).split("@")[0] in SUPERTOOL_PLUGIN_NAMES + + #: The file the hook links to, and the file a supertool checkout carries at its own #: root. Both spellings are the same name on purpose; which one is meant is decided by #: what else is beside it. @@ -1751,11 +1763,15 @@ def _supertool_tree_identity_confirmed(directory, dependency_repos=None, run=Non confirm" rather than reading both as the same silence. """ repos = ( - dependency_repositories(["supertool"]) + dependency_repositories(list(SUPERTOOL_PLUGIN_NAMES)) if dependency_repos is None else dependency_repos ) - expected = repos.get("supertool") if isinstance(repos, dict) else None + expected = ( + next((repos[n] for n in SUPERTOOL_PLUGIN_NAMES if repos.get(n)), None) + if isinstance(repos, dict) + else None + ) if not expected: return False, "no installed supertool dependency to compare against" match = re.search(r"github\.com[:/]+([^/]+/[^/]+?)(?:\.git)?/?$", str(expected)) @@ -4480,7 +4496,7 @@ def _supertool_installs(): installs = [ entry.get("installPath") for key, entries in plugins.items() - if key.split("@")[0] == "supertool" + if is_supertool_plugin_key(key) for entry in (entries if isinstance(entries, list) else []) if isinstance(entry, dict) and entry.get("installPath") ] @@ -6371,6 +6387,7 @@ def published_versions(repos): #: already is. A fourth dependency with a fourth shape lands in `could-not-run` #: below until it is added here. DEPENDENCY_DIAGNOSTICS = { + "supertool-cli": {"kind": "op", "argv": ["doctor"]}, "supertool": {"kind": "op", "argv": ["doctor"]}, "remember": {"kind": "script", "rel": ("scripts", "doctor.sh")}, JIT_PLUGIN: {"kind": "script", "rel": ("scripts", "jit-doctor.sh")}, diff --git a/tests/test_supertool_cli_rename.py b/tests/test_supertool_cli_rename.py new file mode 100644 index 00000000..921f2a53 --- /dev/null +++ b/tests/test_supertool_cli_rename.py @@ -0,0 +1,70 @@ +"""The supertool plugin is published as `supertool-cli` in the dpt-plugins marketplace. + +The marketplace renamed it from `supertool` (dpt-plugins PR #7). A manifest still +declaring `supertool` asks the harness for `supertool@dpt-plugins`, which no longer +exists, and every install of this plugin then shows a dependency error. Installs +registered under the old key must still be recognised, so every name check here +accepts both spellings. +""" + +import json +from pathlib import Path + +import doctor + +ROOT = Path(__file__).resolve().parent.parent +REPO = "https://github.com/Digital-Process-Tools/claude-supertool" + + +def _declared(): + manifest = ROOT / ".claude-plugin" / "plugin.json" + return json.loads(manifest.read_text(encoding="utf-8"))["dependencies"] + + +def test_manifest_declares_the_published_name(): + deps = _declared() + assert "supertool-cli" in deps, deps + assert "supertool" not in deps, deps + + +def test_every_declared_dependency_has_a_diagnostic_route(): + missing = [ + name for name in _declared() if name not in doctor.DEPENDENCY_DIAGNOSTICS + ] + assert missing == [], missing + + +def test_both_names_are_supertool_plugin_keys(): + assert doctor.is_supertool_plugin_key("supertool-cli@dpt-plugins") + assert doctor.is_supertool_plugin_key("supertool@dpt-plugins") + assert not doctor.is_supertool_plugin_key("supertool-extra@dpt-plugins") + assert not doctor.is_supertool_plugin_key("remember@dpt-plugins") + + +def _fake_git_remote(argv, **kwargs): + class Result: + returncode = 0 + stdout = "git@github.com:Digital-Process-Tools/claude-supertool.git\n" + stderr = "" + + return Result() + + +def test_identity_check_reads_the_new_name(tmp_path): + confirmed, reason = doctor._supertool_tree_identity_confirmed( + tmp_path, dependency_repos={"supertool-cli": REPO}, run=_fake_git_remote + ) + assert (confirmed, reason) == (True, None) + + +def test_identity_check_still_reads_the_old_name(tmp_path): + confirmed, reason = doctor._supertool_tree_identity_confirmed( + tmp_path, dependency_repos={"supertool": REPO}, run=_fake_git_remote + ) + assert (confirmed, reason) == (True, None) + + +def test_launcher_accepts_both_registry_keys(): + text = (ROOT / "bin" / "oss-workspace").read_text(encoding="utf-8") + assert 'key.split("@")[0] != "supertool"' not in text + assert text.count('key.split("@")[0] not in ("supertool-cli", "supertool")') == 2 diff --git a/tests/test_version_manifest.py b/tests/test_version_manifest.py index 03201cb6..6e0d2497 100644 --- a/tests/test_version_manifest.py +++ b/tests/test_version_manifest.py @@ -162,4 +162,6 @@ def test_manifest_declares_its_plugin_dependencies(): turns a documented capability into a missing tool at runtime. """ dependencies = _manifest()["dependencies"] - assert dependencies == ["supertool", "remember", "claude-jit-context"], dependencies + assert dependencies == ["supertool-cli", "remember", "claude-jit-context"], ( + dependencies + )