cgcardona / muse public
annotate.py python
118 lines 4.5 KB
766ee24d feat: code domain leverages core invariants, query engine, manifests, p… Gabriel Cardona <gabriel@tellurstori.com> 1d ago
1 """``muse annotate`` — attach CRDT-backed metadata to an existing commit.
2
3 Annotations use real CRDT semantics so that multiple agents can annotate the
4 same commit concurrently without conflicts:
5
6 - ``--reviewed-by`` merges into the commit's ``reviewed_by`` field using
7 **ORSet** semantics (set union — once added, a reviewer is never lost).
8 - ``--test-run`` increments the commit's ``test_runs`` field using
9 **GCounter** semantics (monotonically increasing).
10
11 These annotations are persisted directly in the commit JSON on disk. If two
12 agents race to annotate the same commit, the last writer wins for the raw
13 JSON, but the semantics are CRDT-safe: ORSet entries are unioned and GCounter
14 values are taken as the max.
15
16 Usage::
17
18 muse annotate abc1234 --reviewed-by agent-x
19 muse annotate abc1234 --reviewed-by human-bob --reviewed-by claude-v4
20 muse annotate abc1234 --test-run
21 muse annotate abc1234 --reviewed-by ci-bot --test-run
22 muse annotate # annotate HEAD
23 """
24 from __future__ import annotations
25
26 import logging
27 import pathlib
28
29 import typer
30
31 from muse.core.crdts.or_set import ORSet
32 from muse.core.repo import require_repo
33 from muse.core.store import get_head_commit_id, overwrite_commit, read_commit
34
35 logger = logging.getLogger(__name__)
36
37 app = typer.Typer()
38
39
40 def _resolve_commit_id(root: pathlib.Path, commit_arg: str | None) -> str | None:
41 """Return the resolved commit ID (HEAD branch if *commit_arg* is None)."""
42 if commit_arg is None:
43 head_ref = (root / ".muse" / "HEAD").read_text().strip()
44 branch = head_ref.removeprefix("refs/heads/").strip()
45 return get_head_commit_id(root, branch)
46 return commit_arg
47
48
49 @app.callback(invoke_without_command=True)
50 def annotate(
51 ctx: typer.Context,
52 commit_arg: str | None = typer.Argument(None, help="Commit ID to annotate (default: HEAD)."),
53 reviewed_by: str | None = typer.Option(None, "--reviewed-by", help="Add a reviewer (comma-separated for multiple: --reviewed-by 'alice,bob')."),
54 test_run: bool = typer.Option(False, "--test-run", help="Increment the GCounter test-run count for this commit."),
55 ) -> None:
56 """Attach CRDT-backed annotations to an existing commit.
57
58 ``--reviewed-by`` uses ORSet semantics — a reviewer once added is never
59 removed. Pass multiple reviewers as a comma-separated
60 string: ``--reviewed-by 'alice,claude-v4'``.
61 ``--test-run`` uses GCounter semantics — the count is monotonically
62 increasing and concurrent increments are additive.
63 """
64 root = require_repo()
65
66 commit_id = _resolve_commit_id(root, commit_arg)
67 if commit_id is None:
68 typer.echo("❌ No commit found.")
69 raise typer.Exit(code=1)
70
71 record = read_commit(root, commit_id)
72 if record is None:
73 typer.echo(f"❌ Commit {commit_id!r} not found.")
74 raise typer.Exit(code=1)
75
76 # Parse comma-separated reviewers into a list.
77 reviewers: list[str] = (
78 [r.strip() for r in reviewed_by.split(",") if r.strip()]
79 if reviewed_by else []
80 )
81
82 if not reviewers and not test_run:
83 typer.echo(f"ℹ️ commit {commit_id[:8]}")
84 if record.reviewed_by:
85 typer.echo(f" reviewed-by: {', '.join(sorted(record.reviewed_by))}")
86 else:
87 typer.echo(" reviewed-by: (none)")
88 typer.echo(f" test-runs: {record.test_runs}")
89 return
90
91 changed = False
92
93 if reviewers:
94 # ORSet merge: current set ∪ new reviewers.
95 current_set: ORSet = ORSet()
96 for r in record.reviewed_by:
97 current_set, _tok = current_set.add(r)
98 for r in reviewers:
99 current_set, _tok = current_set.add(r)
100 new_list = sorted(current_set.elements())
101 if new_list != sorted(record.reviewed_by):
102 record.reviewed_by = new_list
103 changed = True
104 for r in reviewers:
105 typer.echo(f"✅ Added reviewer: {r}")
106
107 if test_run:
108 # GCounter semantics: the value is monotonically non-decreasing.
109 # Each call increments by 1 (the GCounter join of two replicas
110 # would take max(a, b) per agent key; here we model the single-writer
111 # common case as a simple increment).
112 record.test_runs += 1
113 changed = True
114 typer.echo(f"✅ Test run recorded (total: {record.test_runs})")
115
116 if changed:
117 overwrite_commit(root, record)
118 typer.echo(f"[{commit_id[:8]}] annotation updated")