feat: .museattributes + multidimensional MIDI merge (#12)
* feat: implement .museignore — gitignore-style snapshot exclusion (#7)
Adds .museignore support so users can declare which workspace files are never committed, identical to .gitignore syntax.
- muse/core/ignore.py: pure parser (load_patterns) and filter (is_ignored) with full gitignore semantics — globs, **, negation, anchored patterns, directory-only patterns, last-rule-wins - MusicPlugin.snapshot(): reads .museignore from repo root and filters excluded paths before hashing; dotfiles remain unconditionally excluded - muse/domain.py: documents the .museignore contract in the MuseDomainPlugin.snapshot() docstring so all domain authors know they must honour it - docs/reference/museignore.md: full format reference with examples for music, genomics, simulation, and spatial domains - tests/test_core_ignore.py: 41 tests covering load_patterns, _matches internals, is_ignored rule evaluation, and end-to-end MusicPlugin.snapshot() integration
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* chore: remove __pycache__ from git tracking (#9)
* feat: implement .museignore — gitignore-style snapshot exclusion (#7) (#8)
Adds .museignore support so users can declare which workspace files are never committed, identical to .gitignore syntax.
- muse/core/ignore.py: pure parser (load_patterns) and filter (is_ignored) with full gitignore semantics — globs, **, negation, anchored patterns, directory-only patterns, last-rule-wins - MusicPlugin.snapshot(): reads .museignore from repo root and filters excluded paths before hashing; dotfiles remain unconditionally excluded - muse/domain.py: documents the .museignore contract in the MuseDomainPlugin.snapshot() docstring so all domain authors know they must honour it - docs/reference/museignore.md: full format reference with examples for music, genomics, simulation, and spatial domains - tests/test_core_ignore.py: 41 tests covering load_patterns, _matches internals, is_ignored rule evaluation, and end-to-end MusicPlugin.snapshot() integration
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* chore: remove __pycache__ from git tracking
The .gitignore already listed __pycache__/ and *.pyc, but these files were committed before that rule existed so they showed as perpetual modifications. Untrack them with git rm --cached; they will continue to exist locally but will no longer appear in git status.
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* feat: .museattributes + multidimensional MIDI merge (#11)
* feat: implement .museignore — gitignore-style snapshot exclusion (#7) (#8)
Adds .museignore support so users can declare which workspace files are never committed, identical to .gitignore syntax.
- muse/core/ignore.py: pure parser (load_patterns) and filter (is_ignored) with full gitignore semantics — globs, **, negation, anchored patterns, directory-only patterns, last-rule-wins - MusicPlugin.snapshot(): reads .museignore from repo root and filters excluded paths before hashing; dotfiles remain unconditionally excluded - muse/domain.py: documents the .museignore contract in the MuseDomainPlugin.snapshot() docstring so all domain authors know they must honour it - docs/reference/museignore.md: full format reference with examples for music, genomics, simulation, and spatial domains - tests/test_core_ignore.py: 41 tests covering load_patterns, _matches internals, is_ignored rule evaluation, and end-to-end MusicPlugin.snapshot() integration
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* chore: untrack __pycache__ files (#10)
* feat: implement .museignore — gitignore-style snapshot exclusion (#7)
Adds .museignore support so users can declare which workspace files are never committed, identical to .gitignore syntax.
- muse/core/ignore.py: pure parser (load_patterns) and filter (is_ignored) with full gitignore semantics — globs, **, negation, anchored patterns, directory-only patterns, last-rule-wins - MusicPlugin.snapshot(): reads .museignore from repo root and filters excluded paths before hashing; dotfiles remain unconditionally excluded - muse/domain.py: documents the .museignore contract in the MuseDomainPlugin.snapshot() docstring so all domain authors know they must honour it - docs/reference/museignore.md: full format reference with examples for music, genomics, simulation, and spatial domains - tests/test_core_ignore.py: 41 tests covering load_patterns, _matches internals, is_ignored rule evaluation, and end-to-end MusicPlugin.snapshot() integration
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* chore: remove __pycache__ from git tracking (#9)
* feat: implement .museignore — gitignore-style snapshot exclusion (#7) (#8)
Adds .museignore support so users can declare which workspace files are never committed, identical to .gitignore syntax.
- muse/core/ignore.py: pure parser (load_patterns) and filter (is_ignored) with full gitignore semantics — globs, **, negation, anchored patterns, directory-only patterns, last-rule-wins - MusicPlugin.snapshot(): reads .museignore from repo root and filters excluded paths before hashing; dotfiles remain unconditionally excluded - muse/domain.py: documents the .museignore contract in the MuseDomainPlugin.snapshot() docstring so all domain authors know they must honour it - docs/reference/museignore.md: full format reference with examples for music, genomics, simulation, and spatial domains - tests/test_core_ignore.py: 41 tests covering load_patterns, _matches internals, is_ignored rule evaluation, and end-to-end MusicPlugin.snapshot() integration
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* chore: remove __pycache__ from git tracking
The .gitignore already listed __pycache__/ and *.pyc, but these files were committed before that rule existed so they showed as perpetual modifications. Untrack them with git rm --cached; they will continue to exist locally but will no longer appear in git status.
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* feat: implement .museattributes + multidimensional MIDI merge
Muse now understands that a MIDI file has independent orthogonal axes. Two collaborators can change different dimensions of the same file without producing a conflict — the first VCS to do this.
Core infrastructure - muse/core/attributes.py: AttributeRule, load_attributes(), resolve_strategy() parse and evaluate .museattributes first-match rules - muse/domain.py: MergeResult gains applied_strategies and dimension_reports fields; MuseDomainPlugin.merge() gains a repo_root keyword argument
MIDI dimension engine (muse/plugins/music/midi_merge.py) - extract_dimensions(): parse MIDI bytes → four dimension slices (notes, harmonic, dynamic, structural) each with a SHA-256 content hash - dimension_conflict_detail(): per-dimension change report (unchanged / left_only / right_only / both) for human-readable diagnostics - merge_midi_dimensions(): three-way dimension merge with .museattributes strategy dispatch; reconstructs a valid type-0 MIDI file when all dimension conflicts can be resolved
MusicPlugin.merge() (three-pass algorithm) Pass 1: file-level ours/theirs rules from .museattributes Pass 2: dimension-level MIDI merge for .mid files (reads from object store, applies per-dimension strategies, writes new merged object) Pass 3: remaining true conflicts + manual-forced paths
CLI - muse merge: passes repo_root to plugin.merge(); reports ✔ auto-resolved paths and dimension-merge detail in output - muse attributes: new command displaying .museattributes rules in a tabular or JSON format
Docs - docs/reference/muse-attributes.md: full reference rewritten to document the three-pass algorithm, all five dimension names, per-domain examples
Tests: 383 passing (63 new) - tests/test_core_attributes.py: 25 parser and resolver unit tests - tests/test_music_midi_merge.py: 38 MIDI dimension tests covering classification, extraction, conflict detection, merge, reconstruction, strategy dispatch, and path-pattern matching
* fix: use AttributeRule type directly in midi_merge.py (mypy list invariance)
* fix: replace dict[str, object] and **kwargs: object with strict types (typing audit)
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
Comments
0No comments yet. Be the first to start the discussion.