cgcardona / muse public
feat main

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>

G Gabriel Cardona <cgcardona@gmail.com> · 3d ago Mar 17, 2026 · 0e0cbf44 · parent 5f5d4912
← Older Oldest commit on main
All commits
Newer → Latest commit on main

Comments

0

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