gabriel / muse public

test_harmony_rebase_loop.py file-level

at sha256:a · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 πŸ’₯ blast risk
sha256:e fix: get_signing_identity's MUSE_AGENT_KEY_FD path derived with entity_… · gabriel · Oct 22, 2026
1 """muse#33 β€” call Harmony inside the ``muse rebase`` replay loop.
2
3 Before this fix, ``muse rebase``'s real per-commit replay loop
4 (``_run_replay_loop`` / ``replay_one`` in ``muse/core/rebase.py``) never
5 consulted Harmony at all: a conflict always paused the rebase, even when
6 Harmony already held a saved resolution for the exact same conflict
7 pattern. And once a rebase conflict WAS resolved manually via
8 ``muse resolve`` + ``muse rebase --continue``, the resolution was never
9 recorded β€” ``--continue`` wrote the new commit directly, bypassing the
10 ``record_resolutions`` call that ``muse commit`` makes for an equivalent
11 ``muse merge`` conflict. ``MERGE_STATE.json`` was also left behind on disk
12 after a rebase completed, since nothing ever cleared it.
13
14 This test drives the real CLI (not synthetic objects) through both halves
15 of the fix:
16
17 1. A rebase conflict, resolved manually, must be recorded by Harmony
18 using the replayed commit's OWN ours/theirs context (HR_01, HR_02,
19 RB_03) β€” not any squash-equivalent snapshot (RB_04) β€” and must clear
20 MERGE_STATE.json once finalised.
21 2. The identical conflict pattern, replayed again in a fresh rebase,
22 must be auto-resolved by Harmony without pausing (RB_02).
23 """
24 from __future__ import annotations
25
26 import json
27 import os
28 import pathlib
29
30 from tests.cli_test_helper import CliRunner, InvokeResult
31
32 runner = CliRunner()
33
34
35 def _run(repo: pathlib.Path, *args: str) -> InvokeResult:
36 saved = os.getcwd()
37 try:
38 os.chdir(repo)
39 return runner.invoke(None, list(args))
40 finally:
41 os.chdir(saved)
42
43
44 def _commit_calc(repo: pathlib.Path, body: str, message: str) -> None:
45 (repo / "calc.py").write_text(f"def add(a, b):\n return {body}\n")
46 r = _run(repo, "code", "add", "calc.py")
47 assert r.exit_code == 0, r.stderr
48 r = _run(repo, "commit", "-m", message)
49 assert r.exit_code == 0, r.stderr
50
51
52 def _branch_from(repo: pathlib.Path, base: str, name: str) -> None:
53 r = _run(repo, "checkout", base)
54 assert r.exit_code == 0, r.stderr
55 r = _run(repo, "branch", name)
56 assert r.exit_code == 0, r.stderr
57 r = _run(repo, "checkout", name)
58 assert r.exit_code == 0, r.stderr
59
60
61 def test_rebase_conflict_resolution_is_recorded_and_merge_state_cleared(
62 tmp_path: pathlib.Path,
63 ) -> None:
64 repo = tmp_path
65 r = _run(repo, "init")
66 assert r.exit_code == 0, r.stderr
67 _commit_calc(repo, "a + b", "base")
68
69 # feat: the branch whose commit will be replayed ("theirs" during rebase).
70 _branch_from(repo, "main", "feat")
71 _commit_calc(repo, "a + b + 1", "feat changes add")
72
73 # main moves on independently ("ours" β€” the new rebase target tip).
74 r = _run(repo, "checkout", "main")
75 assert r.exit_code == 0, r.stderr
76 _commit_calc(repo, "a + b + 100", "main changes add")
77
78 r = _run(repo, "checkout", "feat")
79 assert r.exit_code == 0, r.stderr
80 r = _run(repo, "rebase", "main", "--json")
81 assert r.exit_code == 1, r.output
82 payload = json.loads(r.output)
83 assert payload["status"] == "conflict"
84 assert payload["conflicts"] == ["calc.py"]
85
86 # MERGE_STATE.json must exist while the rebase is paused.
87 assert (repo / ".muse" / "MERGE_STATE.json").exists()
88
89 # Resolve manually to a THIRD value (neither side) and continue.
90 (repo / "calc.py").write_text("def add(a, b):\n return a + b + 101\n")
91 r = _run(repo, "resolve", "calc.py")
92 assert r.exit_code == 0, r.stderr
93 r = _run(repo, "rebase", "--continue", "--json")
94 assert r.exit_code == 0, r.output
95 payload = json.loads(r.output)
96 assert payload["status"] == "completed"
97
98 # MERGE_STATE.json must be cleared once the conflict's commit lands.
99 assert not (repo / ".muse" / "MERGE_STATE.json").exists()
100
101 # Harmony must have recorded exactly this resolution.
102 r = _run(repo, "harmony", "list", "--json")
103 patterns = json.loads(r.output)["patterns"]
104 assert len(patterns) == 1
105 assert patterns[0]["resolution_count"] == 1
106
107 r = _run(repo, "cat", "calc.py")
108 assert r.exit_code == 0, r.stderr
109 assert "+ 101" in r.output
110
111
112 def test_rebase_auto_resolves_identical_conflict_via_harmony(
113 tmp_path: pathlib.Path,
114 ) -> None:
115 repo = tmp_path
116 r = _run(repo, "init")
117 assert r.exit_code == 0, r.stderr
118 _commit_calc(repo, "a + b", "base")
119 # Stable pointer at the original base commit β€” both feat/a and feat/b
120 # must branch from here (not from main's moving tip) so their replay
121 # produces the identical base/ours/theirs blob triple both times.
122 r = _run(repo, "branch", "base")
123 assert r.exit_code == 0, r.stderr
124
125 # First occurrence β€” same shape as the test above β€” teaches harmony.
126 _branch_from(repo, "base", "feat/a")
127 _commit_calc(repo, "a + b + 1", "a changes add")
128 r = _run(repo, "checkout", "main")
129 assert r.exit_code == 0, r.stderr
130 _commit_calc(repo, "a + b + 100", "main changes add (a)")
131 r = _run(repo, "checkout", "feat/a")
132 assert r.exit_code == 0, r.stderr
133 r = _run(repo, "rebase", "main", "--json")
134 assert json.loads(r.output)["status"] == "conflict"
135 (repo / "calc.py").write_text("def add(a, b):\n return a + b + 101\n")
136 r = _run(repo, "resolve", "calc.py")
137 assert r.exit_code == 0, r.stderr
138 r = _run(repo, "rebase", "--continue", "--json")
139 assert r.exit_code == 0, r.output
140 assert json.loads(r.output)["status"] == "completed"
141
142 r = _run(repo, "harmony", "list", "--json")
143 patterns = json.loads(r.output)["patterns"]
144 assert len(patterns) == 1
145 assert patterns[0]["resolution_count"] == 1
146
147 # Second occurrence β€” identical conflict pattern: feat/b branches from
148 # the same original "base" pointer (not main's tip, which has already
149 # moved to "a + b + 100") and gets the identical "a + b + 1" content,
150 # reproducing the exact same base/ours/theirs blob triple as feat/a.
151 _branch_from(repo, "base", "feat/b")
152 _commit_calc(repo, "a + b + 1", "b changes add")
153
154 r = _run(repo, "rebase", "main", "--json")
155 assert r.exit_code == 0, r.output
156 payload = json.loads(r.output)
157 assert payload["status"] == "completed", (
158 "harmony should have auto-resolved this during replay -- rebase "
159 f"must report 'completed', not pause: {payload}"
160 )
161
162 # No conflict ever paused this rebase, so no MERGE_STATE.json should
163 # ever have existed for it.
164 assert not (repo / ".muse" / "MERGE_STATE.json").exists()
165
166 r = _run(repo, "cat", "calc.py")
167 assert r.exit_code == 0, r.stderr
168 assert "+ 101" in r.output
169 assert "+ 1\n" not in r.output.replace("+ 101", "")
170 assert "+ 100" not in r.output