gabriel / muse public
feat patch feat/53-blame-symlog-integration #1 / 1
AI Agent gabriel · 7 hours ago · Oct 22, 2026 · Diff

feat: muse#53 item 2 — muse code blame prefers symlog journal

`muse code blame ADDRESS` reconstructed authorship by BFS-walking the full commit graph and re-parsing each commit's structured_delta — O(commits) regardless of how many times the symbol actually changed. With symlogs already recording {commit_id, author, timestamp, operation} per change, blame can answer in O(entries for this symbol) instead.

New _events_from_symlog() in blame.py builds the same _BlameEvent objects the BFS path produces, reading via the (now-public, moved from symlog.py's CLI module) read_symlog_with_follow() — rename chains are followed exactly like `muse symlog --follow` already does, via born_from pointers. Returns None when no journal exists for the address, triggering the existing BFS path as a transparent fallback.

The journal's created/modified/deleted/renamed classification can't produce "moved" or "signature" events (categories only the richer per-commit structured_delta can distinguish), so a --kind filter requesting either forces the BFS fallback to stay correct. --from REF also forces BFS: the journal reflects all history unconditionally, with no way to slice it to "as reachable from an older ref" the way the BFS walk's starting commit boundary does.

read_symlog_with_follow() (building entries + a from_symbol origin map for rename-boundary annotation) was promoted from a private _read_with_follow() helper in muse/cli/commands/symlog.py to a public function in muse/core/symlog.py — it's pure symlog-reading logic with no CLI concerns, and blame.py now needs the same capability `muse symlog`'s own read path already had.

Covered by tests/test_blame_symlog_fast_path.py (7 cases, split deliberately into two groups after an initial TDD mistake: the first draft asserted only on final blame OUTPUT, which the pre-existing BFS path already produces correctly on its own — reverting the fix left all those tests passing, a false-negative RED that would have shipped an untested no-op. Rewritten to prove the MECHANISM instead: monkeypatching walk_commits_bfs to raise confirms the symlog path is used for the default case, confirms --from and --kind-signature queries still call through to BFS, plus 4 direct unit tests against _events_from_symlog() itself covering created/modified, deleted, and rename-chain address attribution). Confirmed genuine RED the second time by reverting all three changed files and re-running (5 of 7 failed — the two fallback-path tests passed incidentally, which is correct since the fallback path is pre-existing untouched behavior), then GREEN with the fix restored plus the full blame + symlog regression suite (329 + 145 tests across both areas, zero regressions). Live-verified via muse-dev against a disposable /tmp sandbox: default blame correctly reports created+modified via the fast path; --from HEAD~1 correctly reports only "created", confirming the fallback triggers as designed.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

sha256:87e65c3a0f38438d20b4534feb442bd274da6a5a7f0f5f95e9c4e4dfefa86c42 sha
+30 ~21 −2 symbols
5 changed · 1240 in snapshot files
sha256:0e66a19e6efa9c2235c4bf0490f3e160bcc5d5d1343d63f4d2394545bc2348ea snapshot
+30
symbols added
~21
symbols modified
−2
symbols removed
5
files changed
1240
files in snapshot
0
dead code introduced
Semantic Changes 53 symbols
~ tests/test_blame_symlog_fast_path.py .py 22 symbols added
+ TestEventsFromSymlogDirect class class TestEventsFromSymlogDirect L131–188
+ test_builds_created_and_modified_events method method test_builds_created_and_modified_events L144–156
+ test_deleted_symbol method method test_deleted_symbol L158–171
+ test_rename_chain_crosses_address_boundary method method test_rename_chain_crosses_address_boundary L173–188
+ test_returns_none_when_no_journal_exists method method test_returns_none_when_no_journal_exists L135–142
+ TestSymlogFastPathIsActuallyUsed class class TestSymlogFastPathIsActuallyUsed L53–128
+ test_blame_still_works_when_bfs_walk_is_broken method method test_blame_still_works_when_bfs_walk_is_broken L60–80
+ test_from_ref_falls_back_to_bfs_walk method method test_from_ref_falls_back_to_bfs_walk L82–105
+ test_kind_filter_for_unsupported_kind_falls_back_to_bfs method method test_kind_filter_for_unsupported_kind_falls_back_to_bfs L107–128
+ _commit function function _commit L46–50
+ _env function function _env L26–27
+ _run function function _run L30–31
+ _write function function _write L42–43
+ CliRunner import import CliRunner L21–21
+ Mapping import import Mapping L13–13
+ annotations import import annotations L12–12
+ json import import json L15–15
+ pathlib import import pathlib L16–16
+ pytest import import pytest L19–19
+ textwrap import import textwrap L17–17
+ repo function function repo L35–39
+ runner variable variable runner L23–23
~ docs/issues/symlog-followup.md .md 1 symbol added, 1 symbol removed, 2 symbols modified
− muse blame integration section 2. muse blame integration L31–37
+ muse blame integration ✅ IMPLEMENTED section 2. muse blame integration ✅ IMPLEMENTED L31–47
~ muse/cli/commands/blame.py .py 5 symbols added, 3 symbols modified
+ _SYMLOG_UNSUPPORTED_KINDS variable variable _SYMLOG_UNSUPPORTED_KINDS L293–293
+ _events_from_symlog function function _events_from_symlog L296–349
+ read_commit import import read_commit L92–92
+ read_symlog_with_follow import import read_symlog_with_follow L97–97
+ symlog_path import import symlog_path L97–97
~ run
~ muse/core/symlog.py .py 1 symbol added
+ read_symlog_with_follow function function read_symlog_with_follow L473–520
Files Changed
+1 ~4
1240 in snapshot
← Older Oldest on feat/53-blame-symlog-integration
All commits
Newer → Latest on feat/53-blame-symlog-integration

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:87e65c3a0f38438d20b4534feb442bd274da6a5a7f0f5f95e9c4e4dfefa86c42 --body "your comment"