For MIDI files, MidiPlugin.diff() -> _diff_modified_file() tries to load both
old and new blobs via read_object() to run diff_midi_notes() (note-level LCS
diff). Since the new blob isn't in the store yet, read_object() returns None
for the new hash, diff_midi_notes is never even called, and the function falls
straight to the ReplaceOp fallback (file-level, opaque before/after hash pair).
Confirmed reproducible: committed a 4-note phrase.mid, branched, changed one note's
pitch (E4 -> F4), committed again. muse read --json --manifest on the second
commit shows structured_delta.ops == [{"op": "replace", ...}] — never the
PatchOp with note-level child_ops the domain schema advertises
(domain-info --json claims "note-level diff and 21-dimension independent merge").
Verified the underlying note-diff logic itself is correct: calling
diff_midi_notes() directly against the same two blobs (once both exist in the
store, e.g. post-commit) correctly returns:
So this is purely an ordering bug in commit.py, not a defect in midi_diff.py.
Secondary issue compounding this:_diff_modified_file() in
muse/plugins/midi/plugin.py wraps the deep-diff attempt in
except (ValueError, Exception) (redundant — Exception already covers
ValueError) and only logs at .debug level. Any failure — including this one —
is silently invisible at default log verbosity, which is how the fallback went
unnoticed.
Suggested fix: move the object-write loop in commit.py before the
plugin.diff() call (or otherwise make the new manifest's blobs readable —
e.g. by passing the live working-tree bytes directly rather than round-tripping
through the not-yet-written object store).
Data-integrity-adjacent but not itself a data-loss bug — the commit content is
correct, only the note-level observability of the change is lost. Deferred
per usual until after the Build With Muse season wraps.
Confirmed while building Episode 11 ("Muse Understands Music") demo material.
Root cause:
muse/cli/commands/commit.pycomputes the structured delta at line 532:...before the new content's objects are written to the store at line 658:
For MIDI files,
MidiPlugin.diff()->_diff_modified_file()tries to load both old and new blobs viaread_object()to rundiff_midi_notes()(note-level LCS diff). Since the new blob isn't in the store yet,read_object()returnsNonefor the new hash,diff_midi_notesis never even called, and the function falls straight to theReplaceOpfallback (file-level, opaque before/after hash pair).Confirmed reproducible: committed a 4-note phrase.mid, branched, changed one note's pitch (E4 -> F4), committed again.
muse read --json --manifeston the second commit showsstructured_delta.ops == [{"op": "replace", ...}]— never thePatchOpwith note-levelchild_opsthe domain schema advertises (domain-info --jsonclaims "note-level diff and 21-dimension independent merge").Verified the underlying note-diff logic itself is correct: calling
diff_midi_notes()directly against the same two blobs (once both exist in the store, e.g. post-commit) correctly returns:So this is purely an ordering bug in
commit.py, not a defect inmidi_diff.py.Secondary issue compounding this:
_diff_modified_file()inmuse/plugins/midi/plugin.pywraps the deep-diff attempt inexcept (ValueError, Exception)(redundant —Exceptionalready coversValueError) and only logs at.debuglevel. Any failure — including this one — is silently invisible at default log verbosity, which is how the fallback went unnoticed.Suggested fix: move the object-write loop in
commit.pybefore theplugin.diff()call (or otherwise make the new manifest's blobs readable — e.g. by passing the live working-tree bytes directly rather than round-tripping through the not-yet-written object store).Data-integrity-adjacent but not itself a data-loss bug — the commit content is correct, only the note-level observability of the change is lost. Deferred per usual until after the Build With Muse season wraps.