registry.py
python
sha256:ac29b8ba9021514a03ab2782d92bf67671f0efa5b3b70d46f7598c5d4e923378
docs: record muse#74 Phase 4 live verification (SCR_13, SCR_14)
Sonnet 5
13 days ago
| 1 | """Plugin registry — maps domain names to :class:`~muse.domain.MuseDomainPlugin` instances. |
| 2 | |
| 3 | Every CLI command that operates on domain state calls :func:`resolve_plugin` |
| 4 | once to obtain the active plugin for the current repository. Adding support |
| 5 | for a new domain requires only two changes: |
| 6 | |
| 7 | 1. Implement :class:`~muse.domain.MuseDomainPlugin` in a new module under |
| 8 | ``muse/plugins/<domain>/plugin.py``. |
| 9 | 2. Register the plugin instance in ``_REGISTRY`` below. |
| 10 | |
| 11 | The domain for a repository is stored in ``.muse/repo.json`` under the key |
| 12 | ``"domain"``. Repositories created before this key was introduced default to |
| 13 | ``'midi'``. |
| 14 | """ |
| 15 | |
| 16 | import pathlib |
| 17 | |
| 18 | from muse.core.types import load_json_file |
| 19 | from muse.core.paths import repo_json_path as _repo_json_path |
| 20 | from muse.core.errors import MuseCLIError |
| 21 | from muse.core.schema import DomainSchema |
| 22 | from muse.domain import MuseDomainPlugin |
| 23 | from muse.plugins.code.plugin import CodePlugin |
| 24 | from muse.plugins.identity.plugin import IdentityPlugin |
| 25 | from muse.plugins.mist.plugin import MistPlugin |
| 26 | from muse.plugins.scaffold.plugin import ScaffoldPlugin |
| 27 | from muse.plugins.social.plugin import SocialPlugin |
| 28 | |
| 29 | type _PluginRegistry = dict[str, "MuseDomainPlugin"] |
| 30 | # MIDI domain is temporarily suspended pending its own security and |
| 31 | # performance audit. Re-enable by restoring the MidiPlugin import and |
| 32 | # the "midi" entry in _REGISTRY below. |
| 33 | # from muse.plugins.midi.plugin import MidiPlugin |
| 34 | |
| 35 | _REGISTRY: _PluginRegistry = { |
| 36 | "code": CodePlugin(), |
| 37 | "identity": IdentityPlugin(), |
| 38 | "mist": MistPlugin(), |
| 39 | "social": SocialPlugin(), |
| 40 | # "midi": MidiPlugin(), # suspended — see comment above |
| 41 | "scaffold": ScaffoldPlugin(), |
| 42 | } |
| 43 | |
| 44 | _DEFAULT_DOMAIN = "code" |
| 45 | |
| 46 | def _read_domain(root: pathlib.Path) -> str: |
| 47 | """Return the domain name stored in ``.muse/repo.json``. |
| 48 | |
| 49 | Falls back to ``'midi'`` for repos that pre-date the ``domain`` field. |
| 50 | """ |
| 51 | data = load_json_file(_repo_json_path(root)) |
| 52 | if data is None: |
| 53 | return _DEFAULT_DOMAIN |
| 54 | domain = data.get("domain") |
| 55 | return str(domain) if domain else _DEFAULT_DOMAIN |
| 56 | |
| 57 | def resolve_plugin(root: pathlib.Path) -> MuseDomainPlugin: |
| 58 | """Return the active domain plugin for the repository at *root*. |
| 59 | |
| 60 | Reads the ``"domain"`` key from ``.muse/repo.json`` and looks it up in |
| 61 | the plugin registry. Raises :class:`~muse.core.errors.MuseCLIError` if |
| 62 | the domain is not registered. |
| 63 | |
| 64 | Args: |
| 65 | root: Repository root directory (contains ``.muse/``). |
| 66 | |
| 67 | Returns: |
| 68 | The :class:`~muse.domain.MuseDomainPlugin` instance for this repo. |
| 69 | |
| 70 | Raises: |
| 71 | MuseCLIError: When the domain stored in ``repo.json`` is not in the |
| 72 | registry. This is a configuration error — either the plugin was |
| 73 | not installed or ``repo.json`` was edited manually. |
| 74 | """ |
| 75 | domain = _read_domain(root) |
| 76 | plugin = _REGISTRY.get(domain) |
| 77 | if plugin is None: |
| 78 | registered = ", ".join(sorted(_REGISTRY)) |
| 79 | raise MuseCLIError( |
| 80 | f"Unknown domain {domain!r}. Registered domains: {registered}" |
| 81 | ) |
| 82 | return plugin |
| 83 | |
| 84 | def read_domain(root: pathlib.Path) -> str: |
| 85 | """Return the domain name for the repository at *root*. |
| 86 | |
| 87 | This is the same lookup used internally by :func:`resolve_plugin`. |
| 88 | Use it when you need the domain string to construct a |
| 89 | :class:`~muse.domain.SnapshotManifest` for a stored manifest. |
| 90 | """ |
| 91 | return _read_domain(root) |
| 92 | |
| 93 | def resolve_plugin_by_domain(domain: str) -> MuseDomainPlugin: |
| 94 | """Return the plugin for *domain* without reading the filesystem. |
| 95 | |
| 96 | Use this when the caller has already read ``repo.json`` and only needs |
| 97 | the plugin instance — avoids a redundant ``repo.json`` read compared to |
| 98 | :func:`resolve_plugin`. |
| 99 | |
| 100 | Args: |
| 101 | domain: Domain name string (e.g. ``'code'``). |
| 102 | |
| 103 | Returns: |
| 104 | The :class:`~muse.domain.MuseDomainPlugin` instance for *domain*. |
| 105 | |
| 106 | Raises: |
| 107 | MuseCLIError: When *domain* is not in the registry. |
| 108 | """ |
| 109 | plugin = _REGISTRY.get(domain) |
| 110 | if plugin is None: |
| 111 | registered = ", ".join(sorted(_REGISTRY)) |
| 112 | raise MuseCLIError( |
| 113 | f"Unknown domain {domain!r}. Registered domains: {registered}" |
| 114 | ) |
| 115 | return plugin |
| 116 | |
| 117 | def registered_domains() -> list[str]: |
| 118 | """Return the sorted list of registered domain names.""" |
| 119 | return sorted(_REGISTRY) |
| 120 | |
| 121 | def schema_for(domain: str) -> DomainSchema | None: |
| 122 | """Return the ``DomainSchema`` for *domain*, or ``None`` if not registered. |
| 123 | |
| 124 | Allows the CLI and merge engine to look up a domain's schema without |
| 125 | holding a plugin instance. Returns ``None`` rather than raising so callers |
| 126 | can decide whether an unknown domain is an error or a soft miss. |
| 127 | |
| 128 | Args: |
| 129 | domain: Domain name string (e.g. ``'midi'``). |
| 130 | |
| 131 | Returns: |
| 132 | The :class:`~muse.core.schema.DomainSchema` declared by the plugin, |
| 133 | or ``None`` if *domain* is not in the registry. |
| 134 | """ |
| 135 | plugin = _REGISTRY.get(domain) |
| 136 | if plugin is None: |
| 137 | return None |
| 138 | return plugin.schema() |
File History
2 commits
sha256:ac29b8ba9021514a03ab2782d92bf67671f0efa5b3b70d46f7598c5d4e923378
docs: record muse#74 Phase 4 live verification (SCR_13, SCR_14)
Sonnet 5
13 days ago
sha256:2562dffa0a0822ac1bdea854f9b267843c6ce95b497a9dc5c55837c80a3ebd0a
feat: domain_command_registry — Phase 1 of muse#74 (SCR_01-03)
Sonnet 4.6
patch
13 days ago