#!/usr/bin/env python3
"""Muse Domain Registry — standalone HTML generator.
Produces a self-contained, shareable page that explains the MuseDomainPlugin
protocol, shows the registered plugin ecosystem, and guides developers through
scaffolding and publishing their own domain plugin.
Stand-alone usage
-----------------
python tools/render_domain_registry.py
python tools/render_domain_registry.py --out artifacts/domain_registry.html
"""
from __future__ import annotations
import json
import pathlib
import subprocess
import sys
_ROOT = pathlib.Path(__file__).resolve().parent.parent
# ---------------------------------------------------------------------------
# Live domain data from the CLI
# ---------------------------------------------------------------------------
def _compute_crdt_demos() -> list[dict]:
"""Run the four CRDT primitives live and return formatted demo output."""
sys.path.insert(0, str(_ROOT))
try:
from muse.core.crdts import GCounter, LWWRegister, ORSet, VectorClock
# ORSet
base, _ = ORSet().add("annotation-GO:0001234")
a, _ = base.add("annotation-GO:0001234")
b = base.remove("annotation-GO:0001234", base.tokens_for("annotation-GO:0001234"))
merged = a.join(b)
orset_out = "\n".join([
"ORSet — add-wins concurrent merge:",
f" base elements: {sorted(base.elements())}",
f" A re-adds → elements: {sorted(a.elements())}",
f" B removes → elements: {sorted(b.elements())}",
f" join(A, B) → elements: {sorted(merged.elements())}",
" [A's new token is not tombstoned — add always wins]",
])
# LWWRegister
ra = LWWRegister.from_dict({"value": "80 BPM", "timestamp": 1.0, "author": "agent-A"})
rb = LWWRegister.from_dict({"value": "120 BPM", "timestamp": 2.0, "author": "agent-B"})
rm = ra.join(rb)
lww_out = "\n".join([
"LWWRegister — last-write-wins scalar:",
f" Agent A writes: '{ra.read()}' at t=1.0",
f" Agent B writes: '{rb.read()}' at t=2.0 (later)",
f" join(A, B) → '{rm.read()}' [higher timestamp wins]",
" join(B, A) → same result [commutativity]",
])
# GCounter
ca = GCounter().increment("agent-A").increment("agent-A")
cb = GCounter().increment("agent-B").increment("agent-B").increment("agent-B")
cm = ca.join(cb)
gc_out = "\n".join([
"GCounter — grow-only distributed counter:",
f" Agent A x2 → A slot: {ca.value_for('agent-A')}",
f" Agent B x3 → B slot: {cb.value_for('agent-B')}",
f" join(A, B) global value: {cm.value()}",
" [monotonically non-decreasing — joins never lose counts]",
])
# VectorClock
va = VectorClock().increment("agent-A")
vb = VectorClock().increment("agent-B")
vm = va.merge(vb)
vc_out = "\n".join([
"VectorClock — causal ordering:",
f" Agent A: {va.to_dict()}",
f" Agent B: {vb.to_dict()}",
f" concurrent_with(A, B): {va.concurrent_with(vb)}",
f" merge(A, B): {vm.to_dict()} [component-wise max]",
])
return [
{"type": "ORSet", "sub": "Observed-Remove Set", "color": "#bc8cff", "icon": _ICONS["union"], "output": orset_out},
{"type": "LWWRegister", "sub": "Last-Write-Wins Register", "color": "#58a6ff", "icon": _ICONS["edit"], "output": lww_out},
{"type": "GCounter", "sub": "Grow-Only Distributed Counter", "color": "#3fb950", "icon": _ICONS["arrow-up"], "output": gc_out},
{"type": "VectorClock", "sub": "Causal Ordering", "color": "#f9a825", "icon": _ICONS["git-branch"], "output": vc_out},
]
except Exception as exc:
print(f" ⚠ CRDT demo failed ({exc}); using static fallback")
return []
def _load_domains() -> list[dict]:
"""Run `muse domains --json` and return parsed output."""
try:
result = subprocess.run(
[sys.executable, "-m", "muse", "domains", "--json"],
capture_output=True,
text=True,
cwd=str(_ROOT),
timeout=15,
)
if result.returncode == 0:
raw = result.stdout.strip()
data: list[dict] = json.loads(raw)
return data
except Exception:
pass
# Fallback: static reference data
return [
{
"domain": "music",
"active": "true",
"capabilities": ["Typed Deltas", "Domain Schema", "OT Merge"],
"schema": {
"schema_version": "1",
"merge_mode": "three_way",
"description": "MIDI and audio file versioning with note-level diff and semantic merge",
"dimensions": [
{"name": "melodic", "description": "Note pitches and durations over time"},
{"name": "harmonic", "description": "Chord progressions and key signatures"},
{"name": "dynamic", "description": "Velocity and expression curves"},
{"name": "structural", "description": "Track layout, time signatures, tempo map"},
],
},
}
]
# ---------------------------------------------------------------------------
# Scaffold template (shown in the "Build in 3 steps" section)
# ---------------------------------------------------------------------------
_TYPED_DELTA_EXAMPLE = """\
# muse show --json (any commit, any domain)
{
"commit_id": "b26f3c99",
"message": "Resolve: integrate shared-state (A+B reconciled)",
"operations": [
{
"op_type": "ReplaceOp",
"address": "shared-state.mid",
"before_hash": "a1b2c3d4",
"after_hash": "e5f6g7h8",
"dimensions": ["structural"]
},
{
"op_type": "InsertOp",
"address": "beta-a.mid",
"after_hash": "09ab1234",
"dimensions": ["rhythmic", "dynamic"]
}
],
"summary": {
"inserted": 1,
"replaced": 1,
"deleted": 0
}
}"""
_OT_MERGE_EXAMPLE = """\
# Scenario A — independent InsertOps at different addresses → commute → clean merge
left: InsertOp("ot-notes-a.mid") # tick=0, C4 E4 G4
right: InsertOp("ot-notes-b.mid") # tick=480, D4 F4 A4
transform(left, right) → no overlap → both applied
result: both files present, zero conflicts ✓
# Scenario B — same address, different content → genuine conflict
base: shared-melody.mid # C4 G4
left: ReplaceOp("shared-melody.mid") # C4 E4 G4 (major triad)
right: ReplaceOp("shared-melody.mid") # C4 Eb4 G4 (minor triad)
transform(left, right) → same address, non-commuting content
result: ❌ Merge conflict in 1 file(s):
CONFLICT (both modified): shared-melody.mid
[musical intent differs — human must choose major or minor]"""
_SCAFFOLD_SNIPPET = """\
from __future__ import annotations
from muse.domain import (
MuseDomainPlugin, LiveState, StateSnapshot,
StateDelta, DriftReport, MergeResult, DomainSchema,
)
class GenomicsPlugin(MuseDomainPlugin):
\"\"\"Version control for genomic sequences.\"\"\"
def snapshot(self, live_state: LiveState) -> StateSnapshot:
# Serialize current genome state to a content-addressable blob
raise NotImplementedError
def diff(self, base: StateSnapshot,
target: StateSnapshot) -> StateDelta:
# Compute minimal delta between two snapshots
raise NotImplementedError
def merge(self, base: StateSnapshot,
left: StateSnapshot,
right: StateSnapshot) -> MergeResult:
# Three-way merge — surface conflicts per dimension
raise NotImplementedError
def drift(self, committed: StateSnapshot,
live: LiveState) -> DriftReport:
# Detect uncommitted changes in the working state
raise NotImplementedError
def apply(self, delta: StateDelta,
live_state: LiveState) -> LiveState:
# Reconstruct historical state from a delta
raise NotImplementedError
def schema(self) -> DomainSchema:
# Declare dimensions — drives diff algorithm selection
raise NotImplementedError
"""
# ---------------------------------------------------------------------------
# SVG icon library — Lucide/Feather style, stroke="currentColor", no fixed size
# ---------------------------------------------------------------------------
def _icon(paths: str) -> str:
"""Wrap SVG paths in a standard icon shell."""
return (
'"
)
_ICONS: dict[str, str] = {
# Domains
"music": _icon('
{cap['output']}
{short_desc}
{s}{d['desc']}
One protocol. Any domain. Six methods between you and a complete version control system — branching, merging, conflict resolution, time-travel, and typed diffs — for free.
Every domain — music, genomics, 3D spatial, financial models — implements the same six-method protocol. The core engine handles everything else: content-addressed storage, DAG, branches, log, merge base, cherry-pick, revert, stash, tags.
The core engine provides four advanced capabilities that any domain plugin can opt into. Implement the protocol — the engine does the rest.
Unlike Git's blob diffs, Muse deltas are typed objects:
InsertOp, ReplaceOp, DeleteOp — each
carrying the address, before/after hashes, and affected dimensions.
Machine-readable with muse show --json.
{{TYPED_DELTA_EXAMPLE}}
Each plugin's schema() method declares its dimensions and merge mode.
The engine uses this to select the right diff algorithm per dimension and to
surface only the dimensions that actually conflict.
Plugins implementing StructuredMergePlugin get operational transformation. Operations at different addresses commute automatically — only operations on the same address with incompatible intent surface a conflict.
{{OT_MERGE_EXAMPLE}}
Plugins implementing CRDTPlugin get four battle-tested convergent data structures. No coordination required between replicas.
One command scaffolds the entire plugin skeleton. You fill in six methods. The full VCS follows.
raise NotImplementedError with your domain's
snapshot, diff, merge, drift, apply, and schema logic.
registry.py, then every Muse command works
for your domain out of the box.
muse domains --new genomics producesA fully typed, immediately runnable plugin skeleton. Every method has the correct signature. You replace the stubs — the protocol does the rest.
Full walkthrough → docs/guide/plugin-authoring-guide.md · CRDT extension → docs/guide/crdt-reference.md
Domains currently registered in this Muse instance. The active domain
is the one used when you run muse commit, muse diff,
and all other commands.
Music is the reference implementation. These are the domains planned next — and the slot waiting for yours.
Three tiers of distribution — from local prototype to globally searchable registry. Start local, publish when ready.
A centralized, searchable registry for Muse domain plugins — think npm or crates.io, but for any multidimensional versioned state. One command to publish. One command to install.
muse init --domain @musehub/genomics