Files
hermes-agent/tests/pm/test_plugins_state.py
ethernet 7a7abd3ebe fix(pm): active memory provider joins the union — the mnemosyne path
Memory providers install via memory.provider (mnemosyne's documented
path), not plugins.enabled — enabled_member_dirs never saw them, so a
provider's dep plugin never joined the workspace union. The ordered
enabled read now appends the home's active memory.provider (when its
plugin dir exists on disk; no-dup; rides LAST so the incumbent-wins
tiebreak prefers older plugins over the provider).

Closes the our-side half of the mnemosyne port; the upstream half is
their member manifest. tests: provider joins, ghost-provider skipped,
dual-listed not duplicated. tests/pm: 199 passed, 0 failed.
2026-09-03 13:46:53 -04:00

133 lines
4.5 KiB
Python

"""pm.plugins_state: order-preserving enabled reads + disable write-back.
The union is cross-profile and recency-ordered; the bisect writes its
disable decisions back through the same config.yaml the plugins CLI
owns.
"""
from __future__ import annotations
from pathlib import Path
import pytest
import pm.plugins_state as pstate
@pytest.fixture
def homes(tmp_path, monkeypatch):
"""Default home + one profile, each with a config.yaml."""
default_home = tmp_path / "default-home"
profile_home = tmp_path / "profiles" / "work"
default_home.mkdir(parents=True)
profile_home.mkdir(parents=True)
import hermes_constants
monkeypatch.setattr(
hermes_constants, "get_default_hermes_root", lambda: default_home
)
monkeypatch.setattr(pstate, "_profiles_root", lambda: tmp_path / "profiles")
return default_home, profile_home
def _write_config(home: Path, enabled: list) -> None:
import yaml
config = {"plugins": {"enabled": enabled}} if enabled else {"plugins": {}}
with (home / "config.yaml").open("w", encoding="utf-8") as f:
yaml.safe_dump(config, f)
def test_enabled_plugins_ordered_reads_all_homes(homes):
default_home, profile_home = homes
_write_config(default_home, ["a-plug", "b-plug"])
_write_config(profile_home, ["c-plug"])
by_root = pstate.enabled_plugins_ordered()
assert by_root.get(default_home / "plugins") == ["a-plug", "b-plug"]
assert by_root.get(profile_home / "plugins") == ["c-plug"]
def test_enabled_list_preserves_config_order(homes):
default_home, _ = homes
# NOT alphabetical: recency order must survive the read
_write_config(default_home, ["z-first-enabled", "a-second"])
by_root = pstate.enabled_plugins_ordered()
assert by_root[default_home / "plugins"] == ["z-first-enabled", "a-second"]
def test_disable_plugins_removes_across_homes(homes):
default_home, profile_home = homes
_write_config(default_home, ["bad-plug", "keep-plug"])
_write_config(profile_home, ["bad-plug", "other"])
removed = pstate.disable_plugins(["bad-plug"])
assert removed[str(default_home)] == ["bad-plug"]
assert removed[str(profile_home)] == ["bad-plug"]
by_root = pstate.enabled_plugins_ordered()
assert by_root[default_home / "plugins"] == ["keep-plug"]
assert by_root[profile_home / "plugins"] == ["other"]
def test_disable_plugins_noop_when_not_enabled(homes):
default_home, _ = homes
_write_config(default_home, ["keep-plug"])
removed = pstate.disable_plugins(["not-there"])
assert removed == {}
# config untouched
by_root = pstate.enabled_plugins_ordered()
assert by_root[default_home / "plugins"] == ["keep-plug"]
def test_enabled_read_survives_garbage_config(homes):
default_home, _ = homes
(default_home / "config.yaml").write_text("{ not yaml", encoding="utf-8")
assert pstate.enabled_plugins_ordered() == {}
def test_active_memory_provider_joins_union(homes, tmp_path):
"""The mnemosyne path: a provider installed via memory.provider (not
plugins.enabled) must join the union — deps ride the lock either way."""
default_home, _ = homes
provider_dir = default_home / "plugins" / "mnemosyne-like"
provider_dir.mkdir(parents=True)
(provider_dir / "pyproject.toml").write_text("[project]\n", encoding="utf-8")
import yaml
with (default_home / "config.yaml").open("w", encoding="utf-8") as f:
yaml.safe_dump(
{"plugins": {"enabled": ["regular-plug"]},
"memory": {"provider": "mnemosyne-like"}},
f,
)
by_root = pstate.enabled_plugins_ordered()
assert by_root[default_home / "plugins"] == ["regular-plug", "mnemosyne-like"]
def test_memory_provider_without_dir_is_skipped(homes):
"""memory.provider set but no plugin dir on disk — not a member."""
default_home, _ = homes
import yaml
with (default_home / "config.yaml").open("w", encoding="utf-8") as f:
yaml.safe_dump({"memory": {"provider": "ghost-provider"}}, f)
assert pstate.enabled_plugins_ordered() == {}
def test_memory_provider_already_enabled_not_duplicated(homes):
default_home, _ = homes
(default_home / "plugins" / "dual").mkdir(parents=True)
import yaml
with (default_home / "config.yaml").open("w", encoding="utf-8") as f:
yaml.safe_dump(
{"plugins": {"enabled": ["dual"]}, "memory": {"provider": "dual"}}, f
)
by_root = pstate.enabled_plugins_ordered()
assert by_root[default_home / "plugins"] == ["dual"] # once, not twice