gabriel / muse public
feat patch feat/218-diff-symbol-origin-attribution #1 / 1
AI Agent gabriel · 8 hours ago · Oct 22, 2026 · Diff

feat: muse diff <refA> <refB> labels which branch introduced each symbol change (musehub#218)

For the genuine two-ref form, each symbol-level op is now attributed to the branch that introduced it, relative to their merge-base:

M hello.py ├─ added function farewell L4–5 (feature/farewell) └─ function greet (implementation changed) L1–2 (feature/friendlier)

Insert/delete ops are trivially attributable from the diff direction alone (to_ref added it / from_ref had it and lost it) -- no merge-base needed. Replace (modified) ops are genuinely ambiguous without one: the two refs merely differ, and which side actually changed it relative to their common ancestor can't be read off a bare two-way diff. New _compute_symbol_origins finds the merge-base and runs base->from_ref and base->to_ref diffs to determine, per symbol address, whether it changed on from_ref only, to_ref only, or "both" (independently, on both sides -- a genuine three-way divergence worth flagging as such, not attributable to one side).

Additive only, as the ticket itself suggested: new top-level `symbol_origins: {file: {symbol_name: origin}}` JSON key, empty {} for every non-two-ref diff form (HEAD vs working tree, --staged, --shelf, ...) or when no merge-base can be found -- the existing `symbols` key's shape is completely untouched. Text output appends `(branch_name)` after each symbol-level line, only when origins are available.

TDD: wrote 8 new tests in TestSymbolOriginAttribution first -- the ticket's exact repro (added+modified attribution, both text and JSON), the existing `symbols` shape is unchanged (additive-only guard), both sides independently changing the same symbol is labeled "both", and three fall-through guards (HEAD-vs-working-tree, single-ref diff, no merge-base found) all correctly omit origins rather than crashing or guessing. Confirmed genuine RED via muse-dev shelf save/pop isolation -- 7 of 8 failed pre-fix.

Regression: 178 passed across test_cmd_diff.py (full file, 122 tests), test_rename_op.py, test_cmd_merge_dry_run.py, and musehub#219's test_imports_unordered_set_merge.py -- no regressions.

Verified live via muse-dev against a real sandbox repo, reproducing the ticket's exact repro end-to-end -- text and JSON output both match the ticket's own suggested shape precisely.

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

sha256:72de78de7f68004fd4d89fd1ed31e96734ade136417ac0bc09c151c955540474 sha
+14 ~8 symbols
2 changed · 1260 in snapshot files
sha256:dcb9c6a6b5038f40b7753b87530c27837b1b851e2f4178029e9560a9038ec77d snapshot
+14
symbols added
~8
symbols modified
2
files changed
1260
files in snapshot
0
dead code introduced
Semantic Changes 22 symbols
~ muse/cli/commands/diff.py .py 4 symbols added, 8 symbols modified
+ _changed_child_addresses function function _changed_child_addresses L207–218
+ _compute_symbol_origins function function _compute_symbol_origins L220–277
+ MuseDomainPlugin import import MuseDomainPlugin L76–76
+ StateDelta import import StateDelta L76–76
~ run
~ tests/test_cmd_diff.py .py 10 symbols added
+ TestSymbolOriginAttribution class class TestSymbolOriginAttribution L484–591
+ _two_branch_repo method method _two_branch_repo L488–511
+ test_added_symbol_attributed_to_the_ref_that_added_it method method test_added_symbol_attributed_to_the_ref_that_added_it L513–519
+ test_both_sides_changing_the_same_symbol_is_labeled_both method method test_both_sides_changing_the_same_symbol_is_labeled_both L547–558
+ test_existing_symbols_shape_is_unchanged method method test_existing_symbols_shape_is_unchanged L529–539
+ test_head_vs_working_tree_has_no_symbol_origins method method test_head_vs_working_tree_has_no_symbol_origins L560–568
+ test_modified_symbol_attributed_to_the_ref_that_changed_it method method test_modified_symbol_attributed_to_the_ref_that_changed_it L521–527
+ test_no_merge_base_omits_origins_gracefully method method test_no_merge_base_omits_origins_gracefully L578–591
+ test_single_ref_diff_has_no_symbol_origins method method test_single_ref_diff_has_no_symbol_origins L570–576
+ test_text_output_shows_origin_label method method test_text_output_shows_origin_label L541–545
Files Changed
~2
1260 in snapshot
← Older Oldest on feat/218-diff-symbol-origin-attribution
All commits
Newer → Latest on feat/218-diff-symbol-origin-attribution

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:72de78de7f68004fd4d89fd1ed31e96734ade136417ac0bc09c151c955540474 --body "your comment"