gabriel / muse public
domain_info.py python
327 lines 11.1 KB
Raw
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 21 days ago
1 """muse domain-info — inspect the active domain plugin.
2
3 Reports which domain is active for this repository, which plugin class
4 implements it, what optional capabilities it exposes, and the full structural
5 schema it declares (merge mode, top-level element shape, dimensions).
6
7 Output (JSON, default)::
8
9 {
10 "domain": "midi",
11 "plugin_class": "MidiPlugin",
12 "capabilities": {
13 "addressed_merge": true,
14 "crdt": false,
15 "harmony": false
16 },
17 "schema": {
18 "domain": "midi",
19 "description": "...",
20 "merge_mode": "three_way",
21 "schema_version": "0.x.y",
22 "top_level": { ... },
23 "dimensions": [ ... ]
24 },
25 "registered_domains": ["bitcoin", "code", "midi", "scaffold"],
26 "duration_ms": 0.003421,
27 "exit_code": 0
28 }
29
30 Text output (``--format text``)::
31
32 Domain: midi
33 Plugin: MidiPlugin
34 Merge mode: three_way
35 Capabilities: addressed_merge
36
37 Output contract
38 ---------------
39
40 - Exit 0: domain resolved and schema emitted.
41 - Exit 1: no repository found; domain not registered; bad ``--format`` value.
42 - Exit 3: plugin raised an unexpected error when computing its schema.
43
44 Capabilities
45 ------------
46
47 ``addressed_merge``
48 The plugin implements ``AddressedMergePlugin`` and can perform
49 symbol-level address-keyed merges rather than falling back to text-level
50 line diffs. Agents should check this before attempting a semantic merge.
51
52 ``crdt``
53 The plugin implements ``CRDTPlugin`` and exposes CRDT-annotated fields
54 (e.g. ``reviewed_by`` ORSet, ``test_runs`` GCounter). Agents performing
55 collaborative annotation should check this before writing CRDT fields.
56
57 ``harmony``
58 The plugin implements ``HarmonyPlugin`` and can record and replay
59 conflict resolutions. Agents resolving conflicts in a swarm should
60 check this to avoid duplicate resolution work.
61
62 Agent use
63 ---------
64
65 Inspect any domain without entering its repository::
66
67 muse domain-info --domain midi
68 muse domain-info --domain code --capabilities-only
69 muse domain-info --all-domains
70 """
71
72 import argparse
73 import json
74 import logging
75 import sys
76 from typing import TypedDict
77
78 from muse.core.types import JsonValue
79 from muse.core.envelope import EnvelopeJson, make_envelope
80 from muse.core.errors import ExitCode
81 from muse.core.repo import require_repo
82 from muse.core.validation import sanitize_display, validate_domain_name
83 from muse.core.timing import start_timer
84 from muse.domain import AddressedMergePlugin, CRDTPlugin, HarmonyPlugin
85 from muse.plugins.registry import (
86 read_domain,
87 registered_domains,
88 resolve_plugin,
89 resolve_plugin_by_domain,
90 )
91
92 logger = logging.getLogger(__name__)
93
94 type _DomainSchema = dict[str, JsonValue]
95
96 class _CapabilitiesDict(TypedDict):
97 """Capability flags for a domain plugin — nested in domain-info JSON output.
98
99 Fields
100 ------
101 addressed_merge True when the plugin implements ``AddressedMergePlugin``
102 (Map CRDT address-keyed merge via ``merge_ops``). Required
103 for ``muse merge`` to use domain-aware symbol-level resolution.
104 crdt True when the plugin exposes a CRDT data model (enables
105 real-time multi-writer collaboration without conflicts).
106 harmony True when the plugin registers harmony patterns for conflict
107 replay via the Harmony resolution engine.
108 """
109
110 addressed_merge: bool
111 crdt: bool
112 harmony: bool
113
114 class _DomainInfoAllJson(EnvelopeJson):
115 """JSON output for ``muse domain-info --all-domains --json``.
116
117 Inherits the 6 standard envelope fields from :class:`~muse.core.envelope.EnvelopeJson`.
118
119 Fields
120 ------
121 registered_domains Sorted list of every domain identifier registered in the
122 plugin registry (not necessarily active in any repo).
123 """
124
125 registered_domains: list[str]
126
127 class _DomainInfoCapabilitiesJson(EnvelopeJson):
128 """JSON output for ``muse domain-info --capabilities-only --json``.
129
130 Inherits the 6 standard envelope fields from :class:`~muse.core.envelope.EnvelopeJson`.
131
132 Fields
133 ------
134 domain The active domain identifier for this repository.
135 capabilities Capability flags for the domain plugin — see :class:`_CapabilitiesDict`.
136 """
137
138 domain: str
139 capabilities: _CapabilitiesDict
140
141 class _DomainInfoFullJson(EnvelopeJson):
142 """JSON output for ``muse domain-info --json`` (full schema view).
143
144 Inherits the 6 standard envelope fields from :class:`~muse.core.envelope.EnvelopeJson`.
145
146 Fields
147 ------
148 domain The active domain identifier for this repository.
149 plugin_class Fully-qualified Python class name of the active plugin.
150 capabilities Capability flags — see :class:`_CapabilitiesDict`.
151 domain_schema Domain schema dict as returned by ``plugin.schema()``.
152 Contains merge mode, dimensions, version, and description.
153 registered_domains All domains registered in the plugin registry.
154 """
155
156 domain: str
157 plugin_class: str
158 capabilities: _CapabilitiesDict
159 domain_schema: _DomainSchema
160 registered_domains: list[str]
161
162 def register(subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None:
163 """Register the domain-info subcommand."""
164 parser = subparsers.add_parser(
165 "domain-info",
166 help="Inspect active domain plugin capabilities and schema.",
167 description=__doc__,
168 formatter_class=argparse.RawDescriptionHelpFormatter,
169 )
170 parser.add_argument(
171 "--json", "-j", action="store_true", dest="json_out",
172 help="Emit machine-readable JSON instead of human text.",
173 )
174 parser.add_argument(
175 "--all-domains", "-a",
176 action="store_true",
177 dest="all_domains",
178 help="List every registered domain instead of querying the active repo.",
179 )
180 parser.add_argument(
181 "--domain",
182 default=None,
183 dest="domain_name",
184 metavar="DOMAIN",
185 help=(
186 "Inspect a specific domain by name without requiring an active repo. "
187 "Example: --domain midi. "
188 "Use --all-domains to list available names."
189 ),
190 )
191 parser.add_argument(
192 "--capabilities-only",
193 action="store_true",
194 dest="capabilities_only",
195 help=(
196 "Emit only the capabilities dict. "
197 "Ideal for agents doing merge-strategy negotiation — "
198 "avoids serialising the full schema."
199 ),
200 )
201 parser.set_defaults(func=run)
202
203 def run(args: argparse.Namespace) -> None:
204 """Inspect the domain plugin active for this repository.
205
206 Reports the domain name, plugin class, optional protocol capabilities
207 (``AddressedMergePlugin``, ``CRDTPlugin``, ``HarmonyPlugin``), and the
208 full structural schema declared by the plugin. Use ``--all-domains`` to
209 enumerate every registered domain without entering a repo.
210
211 Agent quickstart
212 ----------------
213 ::
214
215 muse domain-info --json
216 muse domain-info --all-domains --json
217 muse domain-info --domain code --json
218 muse domain-info --capabilities-only --json
219
220 JSON fields
221 -----------
222 domain Active domain name.
223 plugin_class Fully qualified plugin class name.
224 capabilities Map of capability flags (``addressed_merge``, etc.).
225 schema Full structural schema declared by the plugin.
226 registered_domains List of all registered domain names.
227
228 Exit codes
229 ----------
230 0 Success.
231 1 Unknown domain name or invalid arguments.
232 2 Not inside a Muse repository (unless ``--all-domains`` is set).
233 """
234 elapsed = start_timer()
235 json_out: bool = args.json_out
236 all_domains: bool = args.all_domains
237 domain_name: str | None = args.domain_name
238 capabilities_only: bool = args.capabilities_only
239
240 # --all-domains — no repo required.
241 if all_domains:
242 domains = registered_domains()
243 if not json_out:
244 for d in domains:
245 print(d)
246 else:
247 print(json.dumps(_DomainInfoAllJson(
248 **make_envelope(elapsed),
249 registered_domains=domains,
250 )))
251 return
252
253 # --domain <name> — inspect by name without entering a repo.
254 if domain_name is not None:
255 try:
256 validate_domain_name(domain_name)
257 except ValueError as exc:
258 print(json.dumps({"error": f"Invalid domain name: {exc}"}), file=sys.stderr)
259 raise SystemExit(ExitCode.USER_ERROR)
260 try:
261 plugin = resolve_plugin_by_domain(domain_name)
262 except Exception as exc:
263 print(json.dumps({"error": str(exc)}), file=sys.stderr)
264 raise SystemExit(ExitCode.USER_ERROR)
265 active_domain = domain_name
266 else:
267 root = require_repo()
268 active_domain = read_domain(root)
269 try:
270 plugin = resolve_plugin(root)
271 except Exception as exc:
272 print(json.dumps({"error": str(exc)}), file=sys.stderr)
273 raise SystemExit(ExitCode.USER_ERROR)
274
275 plugin_class = type(plugin).__name__
276
277 capabilities: _CapabilitiesDict = {
278 "addressed_merge": isinstance(plugin, AddressedMergePlugin),
279 "crdt": isinstance(plugin, CRDTPlugin),
280 "harmony": isinstance(plugin, HarmonyPlugin),
281 }
282
283 # --capabilities-only — skip schema serialisation.
284 if capabilities_only:
285 if not json_out:
286 active_caps = [k for k, v in capabilities.items() if v]
287 cap_str = ", ".join(active_caps) if active_caps else "none"
288 print(f"Domain: {sanitize_display(active_domain)}")
289 print(f"Capabilities: {cap_str}")
290 else:
291 print(json.dumps(_DomainInfoCapabilitiesJson(
292 **make_envelope(elapsed),
293 domain=active_domain,
294 capabilities=dict(capabilities),
295 )))
296 return
297
298 try:
299 schema = plugin.schema()
300 except Exception as exc:
301 logger.debug("domain-info: plugin.schema() failed: %s", exc)
302 print(json.dumps({"error": f"Plugin schema error: {exc}"}), file=sys.stderr)
303 raise SystemExit(ExitCode.INTERNAL_ERROR)
304
305 all_domains_list = registered_domains()
306
307 if not json_out:
308 print(f"Domain: {sanitize_display(active_domain)}")
309 print(f"Plugin: {sanitize_display(plugin_class)}")
310 print(f"Merge mode: {schema.get('merge_mode', 'unknown')}")
311 active_caps = [k for k, v in capabilities.items() if v]
312 cap_str = ", ".join(active_caps) if active_caps else "none"
313 print(f"Capabilities: {cap_str}")
314 print(f"Registered: {', '.join(all_domains_list)}")
315 return
316
317 domain_schema = dict(schema)
318 domain_schema["domain"] = active_domain
319
320 print(json.dumps(_DomainInfoFullJson(
321 **make_envelope(elapsed),
322 domain=active_domain,
323 plugin_class=plugin_class,
324 capabilities=dict(capabilities),
325 domain_schema=domain_schema,
326 registered_domains=all_domains_list,
327 )))
File History 5 commits
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 21 days ago
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 22 days ago
sha256:be3641f35bdbcc094677776a77b9aa6a5dab891f8fab201dc162d03c2bab5aea fix(read): strip position:null from structured_delta ops in… Sonnet 4.6 patch 23 days ago
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 28 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 29 days ago