"""muse#53 item 3 -- `muse code narrative` uses the symlog journal as an index to avoid a full commit-DAG BFS walk, while still reading each relevant commit's real structured_delta for full narrative fidelity (impl vs signature distinction, SemVer bumps, breaking-change detection -- none of which the journal's own coarser classification can produce). As with blame's equivalent (muse#53 item 2), output-only assertions can't distinguish "symlog path used" from "BFS path happened to agree" -- the pre-existing BFS walk already produces correct narratives on its own. These tests monkeypatch walk_commits_bfs to prove which mechanism actually ran. """ from __future__ import annotations import json import pathlib import textwrap import pytest from tests.cli_test_helper import CliRunner cli = None runner = CliRunner() @pytest.fixture() def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: monkeypatch.chdir(tmp_path) monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path)) r = runner.invoke(cli, ["init", "--domain", "code"]) assert r.exit_code == 0, r.output return tmp_path def _write(repo: pathlib.Path, rel: str, content: str) -> None: (repo / rel).write_text(textwrap.dedent(content)) def _commit(repo: pathlib.Path, msg: str) -> None: r1 = runner.invoke(cli, ["code", "add", "."]) assert r1.exit_code == 0, r1.output r2 = runner.invoke(cli, ["commit", "-m", msg]) assert r2.exit_code == 0, r2.output CMD = ["code", "narrative"] class TestSymlogFastPathIsActuallyUsed: def test_narrative_still_works_when_bfs_walk_is_broken( self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "feat: add compute_total") _write(repo, "billing.py", "def compute_total(items):\n return sum(items) * 2\n") _commit(repo, "perf: double compute_total") import muse.cli.commands.narrative as narrative_mod def _broken_bfs(*args: object, **kwargs: object) -> None: raise AssertionError("walk_commits_bfs must not be called on the symlog fast path") monkeypatch.setattr(narrative_mod, "walk_commits_bfs", _broken_bfs) r = runner.invoke(cli, CMD + ["billing.py::compute_total", "--json"]) assert r.exit_code == 0, r.output data = json.loads(r.output) assert data["commits_analysed"] >= 2 def test_since_flag_falls_back_to_bfs_walk( self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: """--since has no equivalent against the flat journal -- must fall back to the full BFS walk, same gating as blame's --from.""" _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "feat: add compute_total") first_head_result = runner.invoke(cli, ["rev-parse", "HEAD", "--json"]) first_head = json.loads(first_head_result.output)["commit_id"] _write(repo, "billing.py", "def compute_total(items):\n return sum(items) * 2\n") _commit(repo, "perf: double compute_total") import muse.cli.commands.narrative as narrative_mod calls: list[bool] = [] real_bfs = narrative_mod.walk_commits_bfs def _spy_bfs(*args: object, **kwargs: object): calls.append(True) return real_bfs(*args, **kwargs) monkeypatch.setattr(narrative_mod, "walk_commits_bfs", _spy_bfs) r = runner.invoke(cli, CMD + ["billing.py::compute_total", "--since", first_head, "--json"]) assert r.exit_code == 0, r.output assert calls, "walk_commits_bfs should have been called for --since" class TestCollectHistoryViaSymlogDirect: def test_returns_none_when_no_journal_exists(self, repo: pathlib.Path) -> None: from muse.cli.commands.narrative import _collect_history_via_symlog _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "feat: add compute_total") result = _collect_history_via_symlog(repo, "billing.py::nonexistent", 500) assert result is None def test_impl_and_sig_classification_preserved(self, repo: pathlib.Path) -> None: """The symlog path must still distinguish impl vs signature changes -- that fidelity comes from re-reading each commit's structured_delta, not from the journal's own coarser classification.""" from muse.cli.commands.narrative import _collect_history_via_symlog _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "feat: add compute_total") _write(repo, "billing.py", "def compute_total(items):\n return sum(items) * 2\n") _commit(repo, "perf: vectorise compute_total body implementation") _write(repo, "billing.py", 'def compute_total(items, currency="USD"):\n return sum(items) * 2\n') _commit(repo, "feat: compute_total signature add currency") result = _collect_history_via_symlog(repo, "billing.py::compute_total", 500) assert result is not None history, commits_analysed, truncated = result assert history.impl_changes >= 1 assert history.sig_changes >= 1 def test_rename_boundary_crossed_via_born_from(self, repo: pathlib.Path) -> None: """The journal's born_from chain lets narrative see history across a rename boundary -- the original fixed-address BFS walk could not.""" from muse.cli.commands.narrative import _collect_history_via_symlog _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "feat: add compute_total") _write(repo, "billing.py", "def compute_invoice_total(items):\n return sum(items)\n") _commit(repo, "refactor: rename to compute_invoice_total") result = _collect_history_via_symlog(repo, "billing.py::compute_invoice_total", 500) assert result is not None history, commits_analysed, truncated = result # The creation event under the OLD address must be visible. assert history.born_commit is not None event_types = [e.event_type for e in history.events] assert "create" in event_types