"""Supercharge tests for muse code lineage. Coverage -------- JSON Envelope exit_code — always 0; confirms clean exit for agents duration_ms — non-negative float; timing telemetry -j alias — shorthand for --json (agent-ergonomic) address — symbol address echoed back; agents can verify the target Context Fields (agent-verifiable constraints applied to the run) filter — kind_filter echoed in JSON (or None when unset) since — lower date bound echoed as ISO string (or None) until — upper date bound echoed as ISO string (or None) Event Detail Fields renamed_from / moved_from / copied_from — detail field preserved in JSON modified — old_content_id / new_content_id present in JSON event deleted — old_content_id present in JSON event created — new_content_id present in JSON event Kind Filters (all six enumerated values) created, modified, deleted, renamed_from, moved_from, copied_from commit_id Format Real commit_ids use sha256:<64-hex> format (71 chars), not bare hex Stability stability_pct computed correctly after kind_filter stability_pct 100 when no modifications TypedDict _LineageJson exported — all output keys documented """ from __future__ import annotations import datetime import json import pathlib import textwrap import pytest from tests.cli_test_helper import CliRunner from muse.cli.commands.lineage import ( _LineageEvent, _classify_replace, _stability, build_lineage, ) from muse.core.commits import CommitRecord from muse.domain import DeleteOp, DomainOp, InsertOp, ReplaceOp cli = None runner = CliRunner() # --------------------------------------------------------------------------- # Shared fixtures # --------------------------------------------------------------------------- _REPO_ID = "test-repo-id" _SEQ: list[int] = [0] def _cid(tag: str) -> str: return tag.ljust(64, "0")[:64] def _ts(offset_days: int = 0) -> datetime.datetime: base = datetime.datetime(2026, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc) return base + datetime.timedelta(days=offset_days) def _commit( *, message: str = "commit", ops: list[DomainOp] | None = None, day: int = 0, commit_id: str | None = None, ) -> CommitRecord: _SEQ[0] += 1 cid = commit_id or f"c{_SEQ[0]:063d}" return CommitRecord( commit_id=cid, branch="main", snapshot_id=f"snap-{cid}", message=message, committed_at=_ts(day), structured_delta={"ops": ops or [], "domain": "code", "summary": message}, ) def _insert(address: str, content_id: str) -> InsertOp: return InsertOp( op="insert", address=address, position=None, content_id=_cid(content_id), content_summary=f"function {address.split('::')[-1]}", ) def _delete(address: str, content_id: str) -> DeleteOp: return DeleteOp( op="delete", address=address, position=None, content_id=_cid(content_id), content_summary=f"function {address.split('::')[-1]}", ) def _replace( address: str, old_cid: str, new_cid: str, old_sum: str = "", new_sum: str = "", ) -> ReplaceOp: return ReplaceOp( op="replace", address=address, position=None, old_content_id=_cid(old_cid), new_content_id=_cid(new_cid), old_summary=old_sum, new_summary=new_sum, ) @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 @pytest.fixture def code_repo(repo: pathlib.Path) -> pathlib.Path: """Repo with a two-commit history: created + renamed.""" (repo / "billing.py").write_text(textwrap.dedent("""\ def compute_total(items): return sum(items) def process_order(invoice, items): return compute_total(items) """)) runner.invoke(cli, ["code", "add", "billing.py"]) r = runner.invoke(cli, ["commit", "-m", "Initial billing module"]) assert r.exit_code == 0, r.output (repo / "billing.py").write_text(textwrap.dedent("""\ def compute_invoice_total(items): return sum(items) def process_order(invoice, items): return compute_invoice_total(items) """)) runner.invoke(cli, ["code", "add", "billing.py"]) r = runner.invoke(cli, ["commit", "-m", "Rename compute_total"]) assert r.exit_code == 0, r.output return repo @pytest.fixture def modified_repo(repo: pathlib.Path) -> pathlib.Path: """Repo with three commits: created, modified, modified.""" (repo / "billing.py").write_text("def compute_total(items):\n return sum(items)\n") runner.invoke(cli, ["code", "add", "billing.py"]) r = runner.invoke(cli, ["commit", "-m", "create"]) assert r.exit_code == 0, r.output (repo / "billing.py").write_text("def compute_total(items, tax=0):\n return sum(items) + tax\n") runner.invoke(cli, ["code", "add", "billing.py"]) r = runner.invoke(cli, ["commit", "-m", "add tax"]) assert r.exit_code == 0, r.output (repo / "billing.py").write_text("def compute_total(items, tax=0, currency='USD'):\n return sum(items) + tax\n") runner.invoke(cli, ["code", "add", "billing.py"]) r = runner.invoke(cli, ["commit", "-m", "add currency"]) assert r.exit_code == 0, r.output return repo # --------------------------------------------------------------------------- # JSON Envelope — exit_code, duration_ms, -j, address # --------------------------------------------------------------------------- class TestJsonEnvelope: def test_exit_code_zero_in_json(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) assert result.exit_code == 0, result.output data = json.loads(result.output) assert "exit_code" in data, "JSON must include exit_code" assert data["exit_code"] == 0 def test_duration_ms_non_negative_float(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) assert result.exit_code == 0, result.output data = json.loads(result.output) assert "duration_ms" in data, "JSON must include duration_ms" assert isinstance(data["duration_ms"], float | int) assert data["duration_ms"] >= 0 def test_j_alias_works(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "lineage", "-j", "billing.py::process_order"]) assert result.exit_code == 0, result.output data = json.loads(result.output) assert "events" in data, "-j must produce same JSON as --json" assert "exit_code" in data def test_j_alias_output_matches_json_flag(self, code_repo: pathlib.Path) -> None: r1 = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) r2 = runner.invoke(cli, ["code", "lineage", "-j", "billing.py::process_order"]) assert r1.exit_code == 0 assert r2.exit_code == 0 d1 = json.loads(r1.output) d2 = json.loads(r2.output) # All structural keys must match (duration_ms may differ slightly) for key in ("address", "total", "exit_code", "events", "stability_pct"): assert d1[key] == d2[key], f"key {key!r} differs between -j and --json" def test_address_echoed_in_json(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) assert result.exit_code == 0 data = json.loads(result.output) assert data["address"] == "billing.py::process_order" def test_json_schema_all_required_keys(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) assert result.exit_code == 0 data = json.loads(result.output) required = {"address", "total", "events", "stability_pct", "modified_count", "exit_code", "duration_ms"} missing = required - set(data.keys()) assert not missing, f"JSON missing keys: {missing}" # --------------------------------------------------------------------------- # Context Fields — applied constraints echoed for agent verification # --------------------------------------------------------------------------- class TestJsonContextFields: def test_filter_field_present_when_applied(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "created", "billing.py::process_order", ]) assert result.exit_code == 0 data = json.loads(result.output) assert "filter" in data, "JSON must echo the applied filter" assert data["filter"] == "created" def test_filter_field_none_when_not_applied(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) assert result.exit_code == 0 data = json.loads(result.output) assert "filter" in data assert data["filter"] is None def test_since_field_in_json_when_applied(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "--since", "2020-01-01", "billing.py::process_order", ]) assert result.exit_code == 0 data = json.loads(result.output) assert "since" in data, "JSON must echo the since date" assert data["since"] == "2020-01-01" def test_until_field_in_json_when_applied(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "--until", "2099-01-01", "billing.py::process_order", ]) assert result.exit_code == 0 data = json.loads(result.output) assert "until" in data, "JSON must echo the until date" assert data["until"] == "2099-01-01" def test_since_field_none_when_not_applied(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) assert result.exit_code == 0 data = json.loads(result.output) assert "since" in data assert data["since"] is None def test_until_field_none_when_not_applied(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) assert result.exit_code == 0 data = json.loads(result.output) assert "until" in data assert data["until"] is None # --------------------------------------------------------------------------- # commit_id Format — sha256: prefix, not bare hex # --------------------------------------------------------------------------- class TestCommitIdFormat: def test_commit_id_uses_sha256_prefix(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) assert result.exit_code == 0 data = json.loads(result.output) for ev in data["events"]: assert ev["commit_id"].startswith("sha256:"), ( f"commit_id must start with 'sha256:', got: {ev['commit_id']!r}" ) def test_commit_id_full_length(self, code_repo: pathlib.Path) -> None: """sha256:<64-hex> = 71 chars total — not truncated.""" result = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) assert result.exit_code == 0 data = json.loads(result.output) for ev in data["events"]: assert len(ev["commit_id"]) == 71, ( f"Expected sha256:<64-hex> (71 chars), got {len(ev['commit_id'])}: {ev['commit_id']!r}" ) # --------------------------------------------------------------------------- # Event Detail Fields — content_id and detail preservation # --------------------------------------------------------------------------- class TestEventDetailFields: def test_modified_event_has_old_and_new_content_id( self, modified_repo: pathlib.Path ) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "modified", "billing.py::compute_total", ]) assert result.exit_code == 0 data = json.loads(result.output) for ev in data["events"]: assert "old_content_id" in ev, "modified events must have old_content_id" assert "new_content_id" in ev, "modified events must have new_content_id" def test_created_event_has_new_content_id(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "created", "billing.py::process_order", ]) assert result.exit_code == 0 data = json.loads(result.output) for ev in data["events"]: assert "new_content_id" in ev, "created events must have new_content_id" def test_renamed_event_has_detail_in_json(self, code_repo: pathlib.Path) -> None: """renamed_from events must carry the source address in detail.""" result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "renamed_from", "billing.py::compute_invoice_total", ]) assert result.exit_code == 0 data = json.loads(result.output) for ev in data["events"]: assert ev["event"] == "renamed_from" assert "detail" in ev, "renamed_from events must include detail (source address)" assert "::" in ev["detail"], f"detail should be a symbol address, got: {ev['detail']!r}" # --------------------------------------------------------------------------- # Kind Filters — all six values # --------------------------------------------------------------------------- class TestKindFilters: def test_filter_created_only(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "created", "billing.py::process_order", ]) assert result.exit_code == 0 data = json.loads(result.output) for ev in data["events"]: assert ev["event"] == "created" def test_filter_modified_only(self, modified_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "modified", "billing.py::compute_total", ]) assert result.exit_code == 0 data = json.loads(result.output) for ev in data["events"]: assert ev["event"] == "modified" def test_filter_deleted_only(self, repo: pathlib.Path) -> None: """Create then delete a symbol — filter should return only the delete event.""" (repo / "billing.py").write_text("def helper(): return 1\n") runner.invoke(cli, ["code", "add", "billing.py"]) r = runner.invoke(cli, ["commit", "-m", "add helper"]) assert r.exit_code == 0, r.output (repo / "billing.py").write_text("# helper removed\n") runner.invoke(cli, ["code", "add", "billing.py"]) r = runner.invoke(cli, ["commit", "-m", "remove helper"]) assert r.exit_code == 0, r.output result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "deleted", "billing.py::helper", ]) assert result.exit_code == 0 data = json.loads(result.output) for ev in data["events"]: assert ev["event"] == "deleted" def test_filter_renamed_from_only(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "renamed_from", "billing.py::compute_invoice_total", ]) assert result.exit_code == 0 data = json.loads(result.output) for ev in data["events"]: assert ev["event"] == "renamed_from" def test_filter_returns_empty_for_unmatched_kind(self, code_repo: pathlib.Path) -> None: """Filtering for 'deleted' on a symbol that was never deleted → empty events list.""" result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "deleted", "billing.py::process_order", ]) assert result.exit_code == 0 data = json.loads(result.output) assert data["total"] == 0 assert data["events"] == [] def test_invalid_filter_rejected_by_argparse(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--filter", "bogus_kind", "billing.py::process_order", ]) assert result.exit_code != 0 # --------------------------------------------------------------------------- # Stability in JSON # --------------------------------------------------------------------------- class TestStabilityInJson: def test_stability_pct_always_in_json(self, code_repo: pathlib.Path) -> None: """stability_pct is emitted without the --stability flag — agents always get it.""" result = runner.invoke(cli, ["code", "lineage", "--json", "billing.py::process_order"]) assert result.exit_code == 0 data = json.loads(result.output) assert "stability_pct" in data assert isinstance(data["stability_pct"], int) def test_stability_pct_100_when_no_modifications(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "created", "billing.py::process_order", ]) assert result.exit_code == 0 data = json.loads(result.output) # Only created events → no modifications → stability 100% assert data["stability_pct"] == 100 def test_stability_pct_zero_when_all_filtered_to_modified( self, modified_repo: pathlib.Path ) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "--filter", "modified", "billing.py::compute_total", ]) assert result.exit_code == 0 data = json.loads(result.output) if data["total"] > 0: assert data["stability_pct"] == 0 def test_modified_count_matches_filtered_events(self, modified_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--json", "billing.py::compute_total", ]) assert result.exit_code == 0 data = json.loads(result.output) actual_modified = sum(1 for ev in data["events"] if ev["event"] == "modified") assert data["modified_count"] == actual_modified # --------------------------------------------------------------------------- # --count flag # --------------------------------------------------------------------------- class TestCountFlag: def test_count_only_outputs_integer(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, [ "code", "lineage", "--count", "billing.py::process_order", ]) assert result.exit_code == 0 assert result.output.strip().isdigit() def test_count_with_json_emits_structured_total(self, code_repo: pathlib.Path) -> None: """--count --json emits full JSON (with 'total' field) not bare integer.""" result = runner.invoke(cli, [ "code", "lineage", "--count", "--json", "billing.py::process_order", ]) assert result.exit_code == 0 data = json.loads(result.output) assert "total" in data assert isinstance(data["total"], int) assert "exit_code" in data def test_count_filter_combination(self, modified_repo: pathlib.Path) -> None: """--count --filter modified returns count of only modified events.""" result = runner.invoke(cli, [ "code", "lineage", "--count", "--filter", "modified", "billing.py::compute_total", ]) assert result.exit_code == 0 count = int(result.output.strip()) assert count >= 2 # two modifications in modified_repo fixture # --------------------------------------------------------------------------- # Docstring / TypedDict export # --------------------------------------------------------------------------- class TestTypedDictExport: def test_lineage_json_typeddict_importable(self) -> None: """_LineageJson TypedDict must be importable from lineage module.""" from muse.cli.commands.lineage import _LineageJson # type: ignore[attr-defined] assert _LineageJson is not None def test_typeddict_has_required_fields(self) -> None: from muse.cli.commands.lineage import _LineageJson # type: ignore[attr-defined] annotations = _LineageJson.__annotations__ required = {"address", "total", "events", "stability_pct", "modified_count", "exit_code", "duration_ms", "filter", "since", "until"} missing = required - set(annotations) assert not missing, f"_LineageJson missing annotations: {missing}" # --------------------------------------------------------------------------- # Classify replace — docstring gap: "impl_only" documented but never returned # --------------------------------------------------------------------------- class TestClassifyReplaceDocstringAccuracy: """Verify _classify_replace only returns documented values.""" def test_signature_change_on_signature_keyword(self) -> None: assert _classify_replace("signature changed", "") == "signature_change" def test_full_rewrite_is_default(self) -> None: result = _classify_replace("body rewritten entirely", "") assert result in ("full_rewrite", "impl_only"), ( f"_classify_replace returned unexpected value: {result!r}" ) def test_return_value_is_a_known_kind(self) -> None: known = {"signature_change", "full_rewrite", "impl_only"} for old_s, new_s in [ ("", ""), ("signature changed", ""), ("", "new signature here"), ("impl updated", "impl updated v2"), ("complete rewrite", "new logic"), ]: result = _classify_replace(old_s, new_s) assert result in known, ( f"_classify_replace({old_s!r}, {new_s!r}) → {result!r} not in {known}" ) # --------------------------------------------------------------------------- # TestRegisterFlags — argparse-level verification # --------------------------------------------------------------------------- class TestRegisterFlags: """Verify that register() wires --json / -j correctly.""" def _make_parser(self) -> "argparse.ArgumentParser": import argparse from muse.cli.commands.lineage import register ap = argparse.ArgumentParser() subs = ap.add_subparsers() register(subs) return ap def test_json_flag_long(self) -> None: ns = self._make_parser().parse_args(["lineage", "file.py::Fn", "--json"]) assert ns.json_out is True def test_j_alias(self) -> None: ns = self._make_parser().parse_args(["lineage", "file.py::Fn", "-j"]) assert ns.json_out is True def test_default_is_text(self) -> None: ns = self._make_parser().parse_args(["lineage", "file.py::Fn"]) assert ns.json_out is False def test_dest_is_json_out(self) -> None: ns = self._make_parser().parse_args(["lineage", "file.py::Fn", "-j"]) assert hasattr(ns, "json_out") assert not hasattr(ns, "fmt")