v0.1.1Stable
Muse v0.1.1 — Server-side semantic analysis
Semantic analysis in progress
MuseHub is computing the symbol graph, API surface, and authorship graph. Refresh in a moment.
Release Notes
Dotfile tracking with sane defaults, .museignore blocklist for tool caches, separation-of-concerns rules enforced across both repos. Semantic release analysis now runs server-side on MuseHub.
Changelog
1 breaking
262
▼
·
Initial extraction from tellurstori/maestro
Complete Muse VCS codebase extracted from the Maestro monorepo:
- 33 service modules (merge, rebase, drift, checkout, motif, groove-check, etc.)
- 85 CLI files (muse_cli package + 73 subcommands)
- FastAPI HTTP router for /api/v1/muse/* endpoints
- SQLAlchemy ORM models for muse_variations, muse_phrases, muse_note_changes
- TourDeForce HTTP client
- 96 test files (unit, CLI, e2e, integration)
- 6 documentation files (architecture, protocol, reference)
Some imports reference maestro.* packages — see README for standalone
setup guidance.
·
Introduce Muse v2 architecture: domain-agnostic VCS with plugin interface
- Add MuseDomainPlugin protocol (domain.py) — five-interface contract for
any domain to plug into the Muse DAG engine
- Add muse/core/ — file-based VCS engine (store, snapshot, object_store,
merge_engine, repo) with no database dependency
- Add muse/plugins/music/ — music domain reference implementation
- Rewrite muse/cli/ with clean sync commands: init, commit, log, status,
diff, show, branch, checkout, merge, reset, revert, cherry-pick, stash, tag
- Add .github/workflows/ci.yml — pytest + mypy on PRs to main and dev
- Remove FastAPI, SQLAlchemy, asyncpg, tourdeforce HTTP client
- Update pyproject.toml: name=muse, version=0.1.1, minimal dependencies
- Rewrite README with Muse v2 vision
·
Add spacetime domain to README per Muse v2 doc (#1)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
Replace Maestro-coupled tests with new architecture test suite
Replace all legacy tests (which imported SQLAlchemy/maestro and could not
collect) with 99 passing tests covering the new muse.core.* and
muse.plugins.music.* architecture:
- tests/test_core_store.py — file-based commit/snapshot/tag CRUD
- tests/test_core_snapshot.py — content-addressed hashing and diffing
- tests/test_core_merge_engine.py — three-way merge and base-finding logic
- tests/test_cli_workflow.py — end-to-end CLI: init, commit, log,
status, branch, checkout, merge, diff,
tag, stash
- tests/test_music_plugin.py — MuseDomainPlugin reference impl
Also fixes two bugs discovered by tests:
- branch listing now recurses into subdirs so feature/foo branches appear
- music plugin merge no longer treats both-sides-deletion as a conflict
·
Remove all Maestro legacy code; clean mypy across full muse/ package
Deleted:
- muse/plugins/music/services/ (35 files — all SQLAlchemy/Pydantic/maestro.*)
- muse/cli/commands/emotion_diff.py (Maestro-coupled stub)
- muse/cli/commands/groove_check.py (Maestro-coupled stub)
- muse/cli/export_engine.py (httpx dependency)
- muse/cli/hub_client.py (httpx dependency)
- muse/cli/artifact_resolver.py (orphaned async SQLAlchemy helper)
Updated:
- muse/cli/app.py — removed lazy-import blocks for deleted stubs
- pyproject.toml — mypy exclude list now empty; added strict overrides
for test files
- muse/cli/commands/* — cast _read_repo_id to str; typed stash generics;
fixed show.py name collision; fixed cherry_pick.py
None guard on parent commit
Result: mypy muse/ passes clean (32 files, 0 errors) in ~3 s.
·
Rewrite .cursorignore, .cursorrules, AGENTS.md for Muse v2
Replace AgentCeption-specific content with Muse-accurate rules:
- .cursorignore: remove agentception/static refs, add dist/, .mypy_cache/
- .cursorrules: Muse stack, plugin architecture, verification commands,
zero-tolerance typing rules, quick-reference table
- AGENTS.md: full agent contract — architecture, branch discipline,
layer rules, typing enforcement chain, verification checklist,
scope of authority, anti-patterns
·
Merge dev → main (#2)
* Add spacetime domain to README per Muse v2 doc (#1)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* Replace Maestro-coupled tests with new architecture test suite
Replace all legacy tests (which imported SQLAlchemy/maestro and could not
collect) with 99 passing tests covering the new muse.core.* and
muse.plugins.music.* architecture:
- tests/test_core_store.py — file-based commit/snapshot/tag CRUD
- tests/test_core_snapshot.py — content-addressed hashing and diffing
- tests/test_core_merge_engine.py — three-way merge and base-finding logic
- tests/test_cli_workflow.py — end-to-end CLI: init, commit, log,
status, branch, checkout, merge, diff,
tag, stash
- tests/test_music_plugin.py — MuseDomainPlugin reference impl
Also fixes two bugs discovered by tests:
- branch listing now recurses into subdirs so feature/foo branches appear
- music plugin merge no longer treats both-sides-deletion as a conflict
* Remove all Maestro legacy code; clean mypy across full muse/ package
Deleted:
- muse/plugins/music/services/ (35 files — all SQLAlchemy/Pydantic/maestro.*)
- muse/cli/commands/emotion_diff.py (Maestro-coupled stub)
- muse/cli/commands/groove_check.py (Maestro-coupled stub)
- muse/cli/export_engine.py (httpx dependency)
- muse/cli/hub_client.py (httpx dependency)
- muse/cli/artifact_resolver.py (orphaned async SQLAlchemy helper)
Updated:
- muse/cli/app.py — removed lazy-import blocks for deleted stubs
- pyproject.toml — mypy exclude list now empty; added strict overrides
for test files
- muse/cli/commands/* — cast _read_repo_id to str; typed stash generics;
fixed show.py name collision; fixed cherry_pick.py
None guard on parent commit
Result: mypy muse/ passes clean (32 files, 0 errors) in ~3 s.
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
Add tools/typing_audit.py — regex + AST violation scanner
·
feat: eliminate all Any/object/ignore — strict TypedDicts at every boundary
Replace every untyped boundary in the codebase with a named TypedDict:
- domain.py: SnapshotManifest, DeltaManifest replace LiveState=Any aliases
- store.py: CommitDict, SnapshotDict, TagDict, RemoteCommitPayload; metadata → dict[str,str]; update_commit_metadata value: str
- merge_engine.py: MergeStatePayload TypedDict; remove dict[str,object] annotations
- config.py: AuthEntry, RemoteEntry, MuseConfig TypedDicts; rewrite _load_config with isinstance narrowing; _dump_toml handles known structure explicitly; remove all # type: ignore
- midi_parser.py: MidiMeta, MusicXMLMeta TypedDicts replace dict[str,Any]
- stash.py: StashEntry TypedDict replaces list[dict[str,Any]]
- plugin.py: use SnapshotManifest/DeltaManifest constructors; direct field access; remove Any import
- tests: SnapshotManifest in all test helpers and inline literals
- CI: add typing audit step with --max-any 0 ratchet
Audit: 0 violations. mypy: clean. Tests: 99/99 passing.
·
docs: add type-contracts reference with full entity surface and Mermaid diagrams
Comprehensive single source of truth for every named type boundary in
the Muse VCS codebase — TypedDicts, dataclasses, Protocols, Enums, and
TypeAliases — modelled after the AgentCeption type-contracts reference.
Covers: domain protocol types (SnapshotManifest, DeltaManifest,
MergeResult, DriftReport, MuseDomainPlugin), store wire-format and
in-memory types (CommitDict/Record, SnapshotDict/Record, TagDict/Record,
RemoteCommitPayload), merge engine (MergeStatePayload, MergeState),
config (AuthEntry, RemoteEntry, MuseConfig, RemoteConfig), MIDI import
(MidiMeta, MusicXMLMeta, NoteEvent, MuseImportData), stash (StashEntry),
and error hierarchy (ExitCode, MuseCLIError, RepoNotFoundError).
Includes 8 Mermaid class diagrams covering every entity relationship.
·
Wire MuseDomainPlugin into all CLI commands via plugin registry
All CLI state operations (commit, status, diff, merge, cherry-pick,
stash) now dispatch through the MuseDomainPlugin protocol rather than
calling core utilities directly. MusicPlugin is the live execution
path, not just an isolated artifact.
Changes:
- muse/plugins/registry.py (new): _REGISTRY dict mapping domain names
to plugin instances; resolve_plugin(root) reads domain from repo.json
and returns the correct plugin; read_domain() exposed as public helper
- muse/cli/commands/init.py: writes "domain": "music" to repo.json;
adds --domain flag for future extensibility
- muse/cli/commands/commit.py: plugin.snapshot(workdir)["files"]
replaces build_snapshot_manifest()
- muse/cli/commands/status.py: plugin.drift(committed_snap, workdir)
replaces diff_workdir_vs_snapshot()
- muse/cli/commands/diff.py: plugin.snapshot() + plugin.diff() replace
build_snapshot_manifest() and the inline _print_diff() helper
- muse/cli/commands/merge.py: plugin.merge(base, ours, theirs) replaces
diff_snapshots() + detect_conflicts() + apply_merge(); conflict paths
flow from MergeResult.conflicts directly into write_merge_state()
- muse/cli/commands/cherry_pick.py: same plugin.merge() pattern
- muse/cli/commands/stash.py: plugin.snapshot(workdir)["files"] replaces
build_snapshot_manifest()
- muse/domain.py: MergeResult.conflicts is now a list of file paths
(not prose); CLI formats its own messages
- muse/plugins/music/plugin.py: merge() returns sorted(real_conflicts)
as paths
- tests/test_plugin_registry.py (new): 12 unit tests for registry
lookup, unknown-domain error, and default fallback behaviour
- tests/test_cli_plugin_dispatch.py (new): 21 integration tests
verifying plugin methods are called through each CLI command using
mock.patch; confirms plugin results drive command output
- tests/test_music_plugin.py: updated conflict assertion to match path
semantics (result.conflicts == ["a.mid"])
·
test: bring core VCS coverage from 60% to 91%
Add 5 new test modules covering show, reset, revert, log, and targeted
gaps across checkout, tag, commit, diff, stash, branch, and core modules
(object_store, repo, store, merge_engine). Exclude future-facing files
(config.py, midi_parser.py, models.py) from coverage measurement via
[tool.coverage.run] omit rules in pyproject.toml.
263 tests / 263 passing · 91% total coverage
·
chore: expand .gitignore (venv, coverage, build, editor artifacts)
·
feat: complete MuseDomainPlugin integration — apply(), incremental checkout
revert.py: reuse parent commit's snapshot_id directly; eliminates the
post-restore re-scan and redundant write_object_from_path calls.
Objects are already in the content-addressed store.
cherry_pick.py: use merged_manifest from plugin.merge() directly;
objects sourced from existing snapshots need no re-write.
checkout.py: replace full wipe+restore with incremental delta checkout
via plugin.diff() + plugin.apply(). Only files that actually changed
are touched — deleted paths are removed, added/modified paths are
restored from the object store. plugin.apply() serves as the
domain-level post-checkout hook.
plugin.py: fix apply() — split the Path and dict code paths clearly.
Path case rescans the workdir after physical changes (correct).
Dict case applies removals in-memory and documents the limitation
that added/modified hashes require target-side data not carried by
the delta.
All 5 MuseDomainPlugin methods are now wired into CLI commands:
snapshot() → commit, stash
diff() → diff, checkout (new)
merge() → merge, cherry-pick
drift() → status
apply() → checkout (new)
279 tests / 279 passing · 91% coverage
·
docs: full sweep — domain-agnostic rewrite of all docs
README.md: fix repository structure tree, package install instructions,
and CLI examples (remove legacy groove-check/emotion-diff, add revert/show).
docs/architecture/muse-vcs.md: complete rewrite from 8,751-line Maestro/ORM
history to a clean 200-line domain-agnostic architecture reference covering
the plugin protocol, core engine modules, commit data flow, merge/checkout
algorithms, and command map.
docs/architecture/muse-e2e-demo.md: rewrite from Docker/HTTP/JWT walkthrough
to a plain CLI walkthrough (init → commit → branch → merge → conflict →
cherry-pick → revert → stash → tag).
docs/architecture/muse_vcs.md: deleted (exact duplicate of muse-vcs.md).
docs/protocol/muse-protocol.md: repurposed from Stori-specific variation UX
to the language-agnostic MuseDomainPlugin protocol specification, covering
all five method contracts, snapshot format, naming conventions, and invariants.
docs/protocol/muse-domain-concepts.md: new — answers the terminology question.
Defines universal VCS terms, explains what "Variation" currently means and how
it could generalize across domains, provides a cross-domain mapping table for
music-specific terms (track, region, phrase, section, emotion), and gives
guidance for new domain authors.
docs/protocol/muse-variation-spec.md: repurposed from full variation UX spec
to a scoped music-domain reference, clearly noting it is not part of the core
Muse VCS engine.
docs/reference/muse-attributes.md: generalized from music-specific examples
(drums/*, keys/*) to a domain-agnostic format reference with a genomics
example showing the format applies to any domain.
·
chore: full decoupling sweep — delete maestro/, scrub all external refs
- Delete entire maestro/ directory (123 files) — last trace of the
legacy codebase, fully removed from git history going forward
- README.md: rewrite Origin section without naming prior projects;
strip "(Stori/Maestro)" from variation spec link
- AGENTS.md: replace all named-project references with generic phrasing;
remove "Cursor" from MCP session description
- .cursorrules: same — generic phrasing for project rules and MCP note
- pyproject.toml: replace legacy-referencing mypy comment with clean note
- muse/core/store.py: drop "replaces SQLAlchemy / Maestro" module docstring
- muse/cli/midi_parser.py: replace "Maestro Docker image" install hint
with plain pip install instruction
- muse/cli/config.py: replace "musehub" example URL with generic host
- docs/protocol/muse-variation-spec.md: drop "Stori DAW + Maestro backend"
from scope preamble
Zero remaining mentions of maestro, agentception, musehub, or named
external projects anywhere in tracked source, docs, or config.
·
feat: Tour de Force stress test + shareable D3 visualization
tools/tour_de_force.py
- 5-act narrative runner against a real temp Muse repo
- Act 1: init + 3 foundation commits on main
- Act 2: 3 divergent branches (alpha, beta, gamma) + 5 commits
- Act 3: two clean merges (alpha fast-forward + beta 3-way)
- Act 4: conflict/left + conflict/right → CONFLICT → resolve + commit
- Act 5: cherry-pick, show, diff, stash/pop, revert, tag, log
- 41 total operations; captures structured EventRecord + CommitNode DAG
- Outputs artifacts/tour_de_force.json + calls renderer
tools/render_html.py
- Fetches D3.js v7 at render time, embeds inline (self-contained)
- Interactive commit DAG: branch-colored nodes, bezier edges,
merge-commit double rings, hover tooltips, zoom/pan
- Animated replay: Play/Pause/Reset steps through all 41 ops,
highlights each commit node as it was created
- Operation log panel: terminal-style, per-act grouping, color-coded
- Architecture section: plugin flow diagram + MuseDomainPlugin
protocol table (all 5 methods with signatures)
- Stats bar: commits · branches · merges · conflicts resolved · ops
- Dark theme, ~318KB self-contained HTML
tools/README.md — usage instructions for both tools
.gitignore — add artifacts/ (generated, not committed)
·
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>
·
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: .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>
·
Fix JS syntax errors in tour_de_force.html; update type-contracts docs
Three literal newlines were embedded inside single-quoted JS string
literals (join/split calls), causing a SyntaxError that prevented the
DAG visualization from rendering. Replace with the escape sequence \n.
Also update docs/reference/type-contracts.md, docs/protocol/muse-protocol.md,
and docs/architecture/muse-vcs.md to reflect the MergeResult fields added
in the .museattributes implementation (applied_strategies, dimension_reports)
and the repo_root keyword added to MuseDomainPlugin.merge(). Two new sections
document AttributeRule and the MIDI dimension merge types.
·
Supercharge tour de force with multidimensional visualization
Fix scroll sync: flip DAG to oldest-at-top so both the commit graph and
the operation log scroll downward in the same direction during playback.
Add Dimension State Matrix — a full-width heatmap showing all 5 musical
dimensions (melodic, rhythmic, harmonic, dynamic, structural) across every
commit. Colored cells indicate which dimensions changed; a red-bordered cell
marks the one dimension that conflicted at the merge commit (structural),
while the other four auto-merged cleanly. This makes the multidimensional
advantage of Muse immediately legible vs. Git's binary file-level conflicts.
Also add:
- Per-dimension colored dots on each DAG node (5-dot ring)
- Colored dimension pills in the operation log for each commit event
- Dimension breakdown in the node hover tooltip
- Active column highlight in the dimension matrix synced to playback
- Conflict callout: "only 1 of 5 dimensions conflicted"
Fix JS SyntaxError: literal newline bytes in join/split string literals
replaced with proper \\n escape sequences (surviving regeneration).
·
Fix dimension matrix showing no colors (hash-based lookup broken)
DIM_DATA was keyed by hardcoded commit short IDs from a single run.
Every tour regeneration produces fresh SHA-256 hashes, so all lookups
returned undefined and no colors rendered.
Replace the static hash-to-dims map with getDims(commit) and
getConflicts(commit) — message-pattern functions that are stable across
re-runs — and call _initDimMaps() at init time to populate DIM_DATA and
DIM_CONFLICTS from the live DATA before rendering.
·
Add YouTube narration script for Tour de Force demo
Covers: intro (multidimensional music theory), all 5 acts step by step,
dimension matrix closing walkthrough, outro/MuseHub teaser, speaker Q&A
notes, and suggested YouTube chapter markers.
·
Add step forward/back buttons and keyboard shortcuts to tour
◀ and ▶ step buttons sit between Play and Reset, letting you navigate
the demo one step at a time in either direction without restarting.
Keyboard shortcuts: ← / → to step, Space to play/pause.
Buttons go disabled at the boundaries (prev at step 0, next at the
final step) and update correctly on reset and during auto-play.
·
Expand Act 5 audition arc in tour script — cherry-pick, revert, roads not taken
·
feat(phase-1): typed delta algebra — replace DeltaManifest with StructuredDelta (#13)
Implements Phase 1 of the supercharge plan end-to-end.
## What changed
**Core type system (muse/domain.py)**
- Remove `DeltaManifest` entirely.
- Add `InsertOp`, `DeleteOp`, `MoveOp`, `ReplaceOp`, `PatchOp` TypedDicts —
each discriminated by a `Literal["op-name"]` field for mypy narrowing.
- Add `StructuredDelta` — `{domain, ops: list[DomainOp], summary}`.
- `StateDelta = StructuredDelta` (was `DeltaManifest`).
- `MergeResult` gains `op_log: list[DomainOp]`.
- `DriftReport.delta` defaults to an empty `StructuredDelta`.
- `MuseDomainPlugin.diff()` gains `repo_root: Path | None = None`.
**Storage (muse/core/store.py)**
- `CommitRecord` and `CommitDict` gain `structured_delta: StructuredDelta | None`.
- Stored at commit time; deserialized on read for `muse show`.
**Myers LCS MIDI diff (muse/plugins/music/midi_diff.py)** — new module
- `NoteKey` TypedDict as the LCS comparison unit (5 fields).
- `lcs_edit_script()`: O(nm) LCS DP + traceback → `list[EditStep]`.
- `extract_notes()`: MIDI bytes → sorted `list[NoteKey]`.
- `diff_midi_notes()`: top-level entry point → `StructuredDelta`.
- Content IDs are deterministic SHA-256 of note field tuples.
**Music plugin (muse/plugins/music/plugin.py)**
- `diff()`: returns `StructuredDelta` with `InsertOp`/`DeleteOp` for file
additions/removals, `ReplaceOp` for non-MIDI modifications, and `PatchOp`
with note-level `child_ops` for MIDI files when `repo_root` is available.
- `apply()`: handles `DeleteOp`/`ReplaceOp`/`InsertOp` in-memory; workdir
path rescans as before.
- `drift()`: counts op kinds instead of accessing old manifest keys.
**CLI commands**
- `commit.py`: computes and stores `structured_delta` against parent.
- `show.py`: displays `commit.structured_delta` with per-op lines and
`PatchOp` child_summary; falls back to manifest diff for old commits.
- `diff.py`: calls `plugin.diff(repo_root=root)` and prints structured output.
- `status.py`: extracts added/modified/deleted sets from `delta.ops`.
- `checkout.py`: extracts removed/to_restore paths from `delta.ops`.
**Tests**
- `test_structured_delta.py` (new): 38 tests for all op types and plugin behaviour.
- `test_midi_diff.py` (new): 29 tests for note extraction, LCS algorithm,
and `diff_midi_notes()` end-to-end.
- `test_music_plugin.py`: updated to StructuredDelta API.
- `test_cli_plugin_dispatch.py`, `test_plugin_apply_and_checkout.py`,
`test_cli_coverage_gaps.py`: updated for new delta shape.
All 456 tests green. mypy strict: 0 errors. typing_audit --max-any 0: 0 violations.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: gitignore .muse/ — source tree is not a Muse-managed repo (#14)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat(phase-2): domain schema declaration + diff algorithm library (#15)
Implements Phase 2 of the supercharge plan: plugins now declare their
data structure schema, and the core engine provides a shared diff
algorithm library for free.
## New: muse/core/schema.py
- `SequenceSchema`, `TreeSchema`, `TensorSchema`, `SetSchema`, `MapSchema`
— five element schema TypedDicts covering the structural primitives of any
domain.
- `DimensionSpec` — a named semantic sub-dimension with its own schema.
- `DomainSchema` — top-level plugin declaration used by the algorithm
library and (in Phase 3) the operation-level merge engine.
## New: muse/core/diff_algorithms/
- `lcs.py` — `myers_ses()` / `detect_moves()` / `diff()` on `list[str]`
(content IDs). Generic LCS usable by any sequence-schema domain.
- `tree_edit.py` — LCS-based labeled ordered tree diff producing
`ReplaceOp` (relabel), `InsertOp`, `DeleteOp`, `MoveOp`. Defines
`TreeNode` frozen dataclass.
- `numerical.py` — epsilon-tolerant sparse / block / full tensor diff on
`list[float]`.
- `set_ops.py` — hash-set algebra for unordered collections of content IDs.
- `__init__.py` — `diff_by_schema()` dispatch + typed input containers
(`SequenceInput`, `SetInput`, `TensorInput`, `MapInput`, `TreeInput`).
## Updated: muse/domain.py
- `MuseDomainPlugin` gains a sixth method: `schema() -> DomainSchema`.
## Updated: muse/plugins/registry.py
- `schema_for(domain) -> DomainSchema | None` — schema lookup without
requiring a plugin instance.
## Updated: muse/plugins/music/plugin.py
- `MusicPlugin.schema()` returns the full four-dimension `DomainSchema`
(melodic/SequenceSchema, harmonic/SequenceSchema, dynamic/TensorSchema,
structural/TreeSchema).
## Updated: muse/plugins/music/midi_diff.py
- `lcs_edit_script()` is now a thin adapter over `lcs.myers_ses()` from
the core library — music domain no longer has its own LCS copy.
- `diff_midi_notes()` delegates the SES step to the shared core algorithm.
## New: tests/test_diff_algorithms.py — 54 tests
## New: tests/test_domain_schema.py — 20 tests
Verification: mypy 0 errors · typing_audit 0 violations · 530 tests green
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
docs: fix stale 'five interfaces' count in domain.py and music plugin; add Phase 2 completion checklist to supercharge plan
·
feat(phase-3): operation-level merge engine — OT-based auto-merge for non-conflicting ops (#16)
Implements Phase 3 of the supercharge plan: the merge engine now reasons over
DomainOp trees rather than file-path sets, enabling sub-file auto-merge.
Core OT engine (muse/core/op_transform.py)
- MergeOpsResult dataclass with merged_ops, conflict_ops, is_clean
- ops_commute() — commutativity oracle for all 25 DomainOp type pairs
- transform() — position-adjusted (a', b') for commuting InsertOp pairs
- _adjust_insert_positions() — counting-formula position adjustment for
multiple concurrent insertions (correct for any N concurrent inserts)
- merge_op_lists() — three-way merge at operation granularity:
kept, consensus, and exclusive additions with OT conflict detection
- merge_structured() — StructuredDelta convenience wrapper
Domain protocol extension (muse/domain.py)
- StructuredMergePlugin sub-protocol (runtime_checkable) with merge_ops()
- Updated module docstring to document Phase 3 design
Music plugin (muse/plugins/music/plugin.py + midi_diff.py)
- MusicPlugin.merge_ops() — full reference implementation:
- OT conflict classification via merge_op_lists
- File-level ops (Insert/Delete/Replace) applied directly to manifest
- PatchOps: one-side-only → take that side's content hash;
both-sides with commuting child ops → MIDI note-level reconstruction
- .museattributes strategy resolution for conflicting pairs
- _merge_patch_ops() — loads base/ours/theirs MIDI from object store,
builds content_id→NoteKey lookups, applies merged note ops,
calls reconstruct_midi() and stores result
- _note_content_id() — shared hash function for NoteKey identity
- reconstruct_midi() in midi_diff.py — Type 0 MIDI writer (inverse of
extract_notes), sorts events by tick, handles note_off ordering
- isinstance(plugin, StructuredMergePlugin) assertion added
CLI merge command (muse/cli/commands/merge.py)
- Phase 3 dispatch: checks isinstance(plugin, StructuredMergePlugin)
- Calls diff() for both branches then merge_ops(); falls back to merge()
automatically for plugins without structured merge support
Tests
- tests/test_op_transform.py — 82 new tests: commutativity oracle (all
25 type pairs), OT transform diamond property, counting formula,
merge_op_lists (3-way), merge_structured, MergeOpsResult
- tests/test_core_merge_engine.py — 20 new tests: TestMergeStructuredIntegration
and TestStructuredMergePluginProtocol classes
Verification: mypy 0 errors, typing_audit 0 violations, 612 tests green
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
docs: fill docstring gaps identified in Phase 3 audit
- DeleteOp, MoveOp, ReplaceOp: document every field (consistent with InsertOp)
- MergeResult.is_clean: add property docstring
- write_merge_state(): add Args block for all 6 keyword-only params
- apply_resolution(): document destructive muse-work/ side-effect in docstring
- diff_snapshots(): add Args/Returns block
- apply_merge(): add full Args/Returns with merge semantics prose
- MusicPlugin.merge(): add Args/Returns block; clarify left/right vs ours/theirs
- _diff_modified_file(): add full Args/Returns with fallback condition
- _read_branch, _read_repo_id, _restore_from_manifest: add one-liner docstrings
·
feat(phase-4): CRDT semantics for convergent multi-agent writes (691 tests green)
Implements the full Phase 4 CRDT primitive library and protocol:
CRDT primitives (muse/core/crdts/):
- VectorClock: causal ordering with increment, merge, happens_before, concurrent_with
- LWWRegister: last-write-wins scalar with timestamp+author tiebreak
- ORSet: observed-remove set with add-wins property and token tracking
- RGA: replicated growable array with parent_id-tracked commutative join
- AWMap: add-wins key-value map
- GCounter: grow-only counter with per-agent slots
Protocol extensions (muse/domain.py):
- CRDTSnapshotManifest TypedDict: files + vclock + crdt_state + schema_version
- CRDTPlugin @runtime_checkable protocol: crdt_schema(), join(), to_crdt_state(), from_crdt_state()
Schema (muse/core/schema.py):
- CRDTDimensionSpec TypedDict: per-dimension CRDT type declaration
- CRDTPrimitive literal union: "lww_register" | "or_set" | "rga" | "aw_map" | "g_counter"
Merge engine (muse/core/merge_engine.py):
- crdt_join_snapshots(): Phase 4 entry point — always succeeds, no conflicts
Tests (tests/test_crdts.py):
- 79 new tests covering all three lattice laws (commutativity, associativity,
idempotency) for every CRDT primitive, plus serialisation and merge engine integration
Docs (docs/architecture/supercharge-plan.md):
- Phase 4 marked complete; completion checklist added
Verification: mypy zero errors, typing_audit zero violations, 691 tests green
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: comprehensive docs, scaffold plugin, and muse domains dashboard (#20)
Architecture docs:
- Update docs/architecture/muse-vcs.md to v1.0 covering all four
supercharge phases with the full protocol stack, algorithm selection
table, CRDT primitive reference, and CLI command map
New guides:
- docs/guide/plugin-authoring-guide.md — complete walkthrough for
building a new domain plugin from Phase 1 through Phase 4, with
tested code examples for every method, schema declaration, OT merge,
CRDT implementation, and verification checklist
- docs/guide/crdt-reference.md — deep-dive CRDT primer covering the
three lattice laws, all six primitives with full API reference and
concurrency examples, composition patterns, and when not to use CRDTs
Scaffold plugin:
- muse/plugins/scaffold/plugin.py — fully typed, mypy-strict, Phase 1–4
capable copy-paste domain template; every method is documented with
contract, args, and returns; TODO markers guide exactly what to fill in
- muse/plugins/scaffold/__init__.py — package init with usage instructions
muse domains command:
- muse/cli/commands/domains.py — domain plugin dashboard listing all
registered domains, their Phase 1–4 capability levels, schema details,
and active repo marker; --new <name> scaffolds a new plugin directory;
--json emits machine-readable output for scripting
- Registered scaffold plugin in registry.py so it appears in the dashboard
Tour de Force update:
- docs/demo/tour-de-force-script.md — adds Acts 6–9 covering Typed Delta
Algebra, Domain Schema & diff algorithms, OT merge, and CRDT semantics;
new CRDT live examples, ORSet semantics demo, and updated YouTube chapter
markers; original 41 steps unchanged
All: mypy clean, typing audit 0 violations, 691 tests green.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: config and versioning audit — TOML attributes, v0.1.1, no Phase N labels
- Versioning: all docs updated from v1.0 to v0.1.1; pyproject.toml unchanged
- Delete docs/architecture/supercharge-plan.md (work shipped, artifact retired)
- Replace every "Phase N" planning label in Python docstrings with descriptive
architectural names (Typed Delta Algebra, Domain Schema, OT merge, CRDT)
- .museattributes migrated from whitespace-delimited 3-column format to TOML:
new AttributesMeta, AttributesRuleDict, MuseAttributesFile TypedDicts;
[meta] domain validated against repo domain at merge time; source_line →
source_index; init writes TOML template; all tests rewritten for TOML
- config.toml: [domain] section added to template and _load_config parser;
MuseConfig gains domain: dict[str, str] field
- muse attributes command: displays [meta] domain header and source_index in JSON
- MusicPlugin.load_attributes calls pass domain=_DOMAIN_TAG for validation
- muse-protocol.md: schema() added as 6th required method; StructuredMergePlugin
and CRDTPlugin documented as optional extensions with full contract tables
- muse-attributes.md: full rewrite for TOML format with examples and template
- type-contracts.md: StructuredDelta, DomainOp variants, DomainSchema,
CRDTSnapshotManifest, MergeOpsResult, MuseAttributesFile all documented;
MuseDomainPlugin updated to 6 methods; MuseConfig domain field added
- README: legacy v1/v2 framing removed; repo structure map updated; six-method
protocol with StructuredMergePlugin and CRDTPlugin shown
- mypy: 0 errors | typing_audit: 0 violations | pytest: 697/697 passed
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: Python 3.12 baseline, dep refresh, and docs navigation index
Raises the minimum Python version to 3.12 and adopts the idiomatic
PEP 695 `type X = Y` statement for all type alias declarations.
Dep changes
-----------
- requires-python: >=3.11 → >=3.12
- typer: >=0.9.0 → >=0.14.0 (0.24.1 latest, drops 3.9 support)
- mido: >=1.3.0 → >=1.3.3 (latest stable)
- Dropped `toml` — unused since we switched to stdlib `tomllib`
- pytest: >=8.0.0 → >=9.0.0
- pytest-asyncio:>=0.23.0 → >=1.0.0
- pytest-cov: >=4.1.0 → >=7.0.0
- anyio: >=4.2.0 → >=4.9.0
- mypy: >=1.8.0 → >=1.19.0
- hatchling build dep pinned to >=1.29.0
- mypy `python_version` updated from 3.11 to 3.12
- Dropped `toml` from mypy overrides (no longer a dependency)
Python 3.12 type alias syntax (PEP 695)
----------------------------------------
Converted all bare `X = Y` type alias declarations to the explicit
`type X = Y` statement:
- muse/domain.py: DomainOp, LiveState, StateSnapshot, StateDelta
- muse/plugins/music/midi_merge.py: _MsgVal
- muse/cli/midi_parser.py: RawMeta
Docs
----
Added docs/README.md — a navigation index covering every document in
the docs tree, a directory map, architecture overview, plugin
quickstart, config file inventory, and testing command reference.
Verification: mypy (0), typing_audit (0), pytest (697/697 green)
·
feat: live Acts 6-9 in Tour de Force, drop Phase N labels everywhere
Tour de Force (tools/tour_de_force.py)
---------------------------------------
- Acts 6-9 now run real code — not narration stubs.
Act 6 (Typed Delta Algebra): `muse show` + `muse show --json` +
`muse log --stat` capture live StructuredDelta output.
Act 7 (Domain Schema): `muse domains`, `muse domains --json`, and
`muse domains --new genomics` (scaffold + auto-cleanup).
Act 8 (OT Merge): two scenarios — independent InsertOps commute to a
clean merge; identical-address ReplaceOps produce a genuine conflict.
Act 9 (CRDT Primitives): direct API demo of ORSet add-wins,
LWWRegister last-write-wins, GCounter monotonic join, and
VectorClock causal ordering — all with real assertions.
- Total tour: 69 operations in ~260ms (was 41).
- Fixed type: ignore[union-attr] in render_html loading — replaced
importlib.util dynamic spec loading with a clean sys.path + import.
- Added ot-left/ot-right/ot-conflict-l/ot-conflict-r to BRANCH_COLORS.
- ACT_TITLES dict extended with acts 6-9.
domains.py
----------
- _CapabilityLevel → _CapabilityLabel; labels changed from "Phase N"
to "Typed Deltas" / "Domain Schema" / "OT Merge" / "CRDT".
- Updated module docstring, _capabilities docstring, and the `domains`
command help text to use the new descriptive labels.
Narration script (docs/demo/tour-de-force-script.md)
-----------------------------------------------------
- Stripped "(Phase N)" suffix from all Act 6-9 headings.
- Updated `muse domains` example output to show descriptive capability
labels matching the new domains.py output.
- OUTRO: "five-method" → "six-method"; "Phase 1/2/3/4" bullets replaced
with "Typed Deltas / Domain Schema / OT Merge / CRDT Semantics".
- Appendix Q&A: "Phase 3 OT and Phase 4 CRDT" → "OT Merge and CRDT
Semantics"; test count updated to 697.
- Chapter markers: stripped Phase N labels; "Muse v1.0" → "Muse v0.1.1".
Verification: mypy (0), typing_audit (0), pytest (697/697 green)
·
fix: bump CI Python from 3.11 to 3.12
pyproject.toml requires-python was raised to >=3.12 and PEP 695
type aliases (type X = Y) were introduced in domain.py, midi_merge.py,
and midi_parser.py — all of which are SyntaxErrors on Python 3.11.
CI was still pinned to 3.11, causing every job to fail at collection.
·
fix: five → six methods in tour-de-force Q&A appendix
The MuseDomainPlugin protocol has six required methods (snapshot, diff,
merge, drift, apply, schema). The appendix answer to "what does
domain-agnostic mean?" still said five.
·
fix: six methods everywhere — render_html, plugin guide, type-contracts, AGENTS
- render_html.py: 'Implement 5 methods' → '6 methods' in the protocol
box and the YourPlugin tile; add missing schema() row to the protocol
table; '5 methods' → '6 methods' in the explainer prose.
- docs/guide/plugin-authoring-guide.md: 'these five methods' → 'six'
- docs/reference/type-contracts.md: ': 5 methods' → ': 6 methods' in
the type tree; 'five methods' → 'six methods' in Diagram 1 caption.
- AGENTS.md: 'the five-method contract' → 'the six-method contract'
The sixth method is schema(), added when Domain Schema support was
introduced. All remaining 'five' instances in the codebase refer to
musical dimensions (5) or NoteKey fields (5), which are correct.
·
fix: add schema() to every protocol method enumeration in docs
- plugin-authoring-guide.md checklist: 'snapshot, diff, merge, drift,
apply' → '...apply, schema all implemented'
- type-contracts.md Mermaid class body: same — add / schema
- artifacts/tour_de_force.html: regenerated from the already-fixed
render_html.py; HTML now shows schema() row in the protocol table
and '6 methods' everywhere (artifacts/ is gitignored so not staged)
·
feat: supercharge Tour de Force acts 6-9 and build domain registry page (#28)
Tour de Force (render_html.py):
- Add act-jump navigation bar with per-act buttons and Reveal All
- Acts 6-9 events now get 15-line output (was 5) and rich-act class
- Act headers styled with per-act icon + color, always visible (not dimmed)
- CRDT events (Act 9) rendered as API calls, not shell commands
- buildDomainSection(): domain registry cards built from live Act 7 JSON
- buildCRDTSection(): four CRDT primitive cards built from Act 9 events
- Add Domain Dashboard + CRDT sections to HTML page below architecture
- Add "Domain Registry →" cross-link in header
- Cross-link from domain_registry.html back to tour_de_force.html
Domain Registry (render_domain_registry.py — new tool):
- Standalone page: "Version Anything · Domain Plugin Registry"
- Animated domain ticker (music → genomics → 3D → financial → yours)
- Six-method protocol table with stat strip (6 methods, 14 commands, 0 core changes)
- "Build in Three Steps" section (scaffold → implement → use)
- Highlighted scaffold code block with full GenomicsPlugin stub
- Live domain cards from muse domains --json (falls back to static data)
- Planned ecosystem cards (Genomics, 3D/Spatial, Financial, Simulation, YourDomain)
- Three-tier distribution model (Local → pip package → MuseHub)
- MuseHub teaser section with features and animated status dot
- Nav links between Tour de Force and Domain Registry pages
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
refactor: Tour de Force is Acts 1-5 only; engine capabilities move to domain registry (#29)
Tour de Force (tour_de_force.py, render_html.py):
- Strip Acts 6-9 entirely — demo is now a clean 5-act music story (41 ops)
- Remove OT-branch BRANCH_COLORS and act 6-9 ACT_TITLES entries
- Remove buildDomainSection(), buildCRDTSection(), RICH_ACTS, CRDT_ACT JS
- Replace domain/CRDT sections with a single registry callout banner
("Want to version something else? → Domain Registry & Plugin Guide")
- Act jump bar, reveal-all, per-act icons still work for acts 1-5
Domain Registry (render_domain_registry.py):
- Add _compute_crdt_demos() — runs ORSet/LWWRegister/GCounter/VectorClock live
- Add _TYPED_DELTA_EXAMPLE and _OT_MERGE_EXAMPLE static accurate examples
- Add Engine Capabilities section (4 full-width cards before the Build section):
- Typed Delta Algebra: muse show --json output explained
- Domain Schema: live domain cards from muse domains --json
- OT Merge: both scenarios (commuting inserts + same-address conflict)
- CRDT Primitives: four live demo cards computed at render time
- Add cap-showcase-grid CSS with card, header, badge, output styles
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: add GitHub Pages deployment — landing page + CI build workflow (#30)
- tools/render_index.py: generates artifacts/index.html — a beautiful dark-theme
landing page presenting Tour de Force and Domain Registry as two CTA cards,
with a feature strip and nav linking to GitHub and the plugin guide
- .github/workflows/pages.yml: on every push to main, runs
tour_de_force.py → tour_de_force.html
render_domain_registry.py → domain_registry.html
render_index.py → index.html
then deploys all three to GitHub Pages via actions/deploy-pages@v4
- GitHub Pages enabled at https://cgcardona.github.io/muse/ (source: Actions)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: domain registry is the landing page — muse / Version Anything hero (#31)
- Hero redesigned: large mono "muse" wordmark (gradient blue→purple),
"Version Anything" as the subtitle in light uppercase tracking,
removing the previous eyebrow + gradient h1 pattern
- render_domain_registry.py now writes both domain_registry.html AND
index.html (identical) so the registry IS the landing page at /
- Page title simplified to "Muse — Version Anything"
- Remove tools/render_index.py (superseded — no longer a separate page)
- pages.yml: drop the render_index.py step (one fewer build step)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: wordmark optical weight — UI font + balanced gradient (#32)
Switch wordmark from monospace to system UI font: monospace fonts pack
three strokes into 'm' at display sizes, making it read lighter than
single-stroke neighbors. UI fonts are optically balanced for large weights.
Change gradient from 135deg blue→purple to 90deg (horizontal) across a
tighter perceptual range (#6ea8fe → #a78bfa → #c084fc) so all four
letters sit at similar luminosity on a dark background.
Scale 'Version Anything' subtitle down slightly and increase letter-spacing
from 2px to 4px for better proportion against the larger wordmark.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: VERSION ANYTHING — white, 700 weight, 26px, 6px tracking (#33)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: rename Tour de Force → Demo in all nav labels and page titles (#34)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: link MuseHub status line to github.com/cgcardona/musehub (#35)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: collapse stats into left column beside protocol table (#36)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: add inline JSON syntax highlighting to Typed Delta card (#37)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: replace all emoji with inline SVG icons (Lucide style) (#38)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: replace OT Merge pre-block with structured scenario cards (#39)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: add Diff Algebra visualization section — five algorithm panels + StructuredDelta flow (#40)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: replace CRDT pre blocks with purpose-built convergence visualizations
Each of the four CRDTs in the Domain Registry now renders as a compact
HTML diagram showing its specific convergence story rather than raw text
output (which was overflowing its container):
- ORSet: side-by-side concurrent add/remove replicas → join result with
"add-wins" annotation
- LWWRegister: stacked write timeline with winner badge → commutative
join result
- GCounter: proportional bar chart per agent slot → component-wise max
total
- VectorClock: grid of clock cells (self/zero/max) with ⊕ symbol →
concurrent_with badge and merged clock
All values remain live-computed from _compute_crdt_demos(); the HTML
visualization is generated alongside the existing text output field and
selected via html_output key in _render_capability_card.
·
fix: prevent ORSet Replica B box from clipping in CRDT grid
Add min-width:0 + overflow:hidden to .crdt-rep grid children so CSS
grid constraint is respected, and text-overflow:ellipsis to .crdt-op
so long remove() calls degrade gracefully instead of escaping the cell.
·
fix: add rhythmic as 5th music domain dimension everywhere
The MIDI merge has always recognised five user-facing dimensions
(melodic, rhythmic, harmonic, dynamic, structural) via DIM_MAP in
midi_merge.py, but schema(), its docstring, the Domain Registry card,
and the test fixture all listed only four.
- plugin.py schema(): add DimensionSpec(rhythmic) with SequenceSchema
matching melodic; note the shared internal notes bucket in description
- plugin.py docstrings: update "four" → "five" in schema() and merge()
- render_domain_registry.py: add rhythmic row to the music domain card
- test_domain_schema.py: rename test_schema_has_four_dimensions →
test_schema_has_five_dimensions; extend expected names list
·
refactor: reorder Domain Registry page sections into narrative arc
Old order: Protocol → Diff Algebra → Capabilities → Build → Code →
Registry → Ecosystem → Distribution → MuseHub
New order: Protocol → Registry → Ecosystem → Capabilities →
Diff Algebra → Build → Code → Distribution → MuseHub
Narrative logic:
1. Protocol — the pitch: 6 methods, here's the contract
2. Registry — proof: it works today (music domain live)
3. Ecosystem — vision: here's where it's going
4. Capabilities — what you get for free (Typed Deltas, OT, CRDT)
5. Diff Algebra — deep dive for the curious (how it works inside)
6. Build — call to action: build your own in 3 steps
7. Code — scaffold detail
8. Distribution — how to share what you build
9. MuseHub — ecosystem endgame
·
fix: replace relative doc links with GitHub blob URLs
../docs/guide/*.md paths are dead on GitHub Pages since the docs/
directory is not part of the deployed artifact. Point all four
occurrences (nav, code section ×2, footer) directly at the
canonical GitHub blob URLs on main.
·
fix: rename tour_de_force.html → demo.html and update all references
- tools/tour_de_force.py: update docstring and output path
- tools/render_domain_registry.py: update nav, hero CTA, and footer links
- tools/README.md: update artifact table and open commands
- docs/demo/tour-de-force-script.md: update file path in setup note
- artifacts/tour_de_force.html: git mv'd to artifacts/demo.html
·
fix: remove Domain Registry link from demo page header
·
fix: remove How Muse Works and Domain Registry callout from demo
Both sections duplicate content now on the landing page (index.html).
The demo page stays focused on the live VCS walkthrough.
·
fix: correct broken TOC anchors in plugin-authoring-guide.md
Phase N prefixes were stripped from section headings in a prior commit
but the TOC links were left pointing at the old slugs:
#phase-1--core-protocol-required → #core-protocol-required
#phase-2--domain-schema → #domain-schema
#phase-3--operation-level-merge → #operation-level-merge-ot
#phase-4--crdt-semantics → #crdt-semantics
·
refactor: use shared nav header on demo page
Replace the bespoke header-top/tagline/version-badge layout with the
same nav bar used on the landing page:
muse · Demo (active) · Domain Registry · Plugin Guide · v{VERSION}
Remove now-dead CSS: .header-top, header h1, .tagline, .header-nav-link,
.version-badge. Trim stats-bar top padding to 16px (nav provides visual
separation). The v{{VERSION}} token in the nav is still live-interpolated
from the build data.
·
feat: replace all emojis on demo page with SVG icons
Define a JS SVG icon library (music, branch, merge, conflict, revert,
check, dot, zap, pause, eye) and use it throughout:
- ACT_ICONS: 🎵🌿⚡🔀⏪ → music/branch/merge/conflict/revert SVGs
- Dimension tooltip pills: ● → dot SVG, ⚡ → zap SVG
- Event log conflict dim-pills: ⚡ → zap SVG
- Dimension matrix cells: textContent ⚡/● → innerHTML zap/dot SVG
- Play button: ⏸/✓ → pause/check SVGs (textContent → innerHTML)
- Reveal All button: ✦ → eye SVG (textContent → innerHTML)
- HTML conflict note: ⚡ → alert-triangle SVG, ✓ → check SVG
Add .ico-inline, .ico-conflict, .ico-check CSS for the static HTML icons.
·
fix: remove Domain Registry from nav — muse logo already links home
The logo links to index.html on both pages so the explicit Domain
Registry nav item is redundant. Nav is now: muse | Demo | Plugin Guide | v0.1.1
·
feat: code-domain semantic commands + code tour de force demo (#54)
* feat: add code domain plugin with AST-based semantic versioning
Introduces muse/plugins/code/ — a first-class Muse plugin that treats
source code as a structured system of named symbols rather than lines
of text.
Key capabilities
----------------
- Python AST parsing via stdlib `ast` (zero new dependencies). Every
function, class, method, variable, and import becomes a named symbol
with a stable content-addressed identity (SHA-256 of normalized AST).
- Semantic content IDs: two functions that differ only in whitespace or
comments share the same content_id; reformatting a file produces no
structured delta.
- Rename detection: same body_hash + different name → ReplaceOp
annotated "renamed to <new>".
- Move detection: same content_id at a different address across files →
cross-file move annotation on the DeleteOp/InsertOp pair.
- Symbol-level OT merge: two agents modifying different functions in
the same file auto-merge (ops commute); concurrent edits to the same
function produce a conflict at address "src/utils.py::function_name"
rather than a coarse file conflict.
- Language adapter protocol (LanguageAdapter) for future TypeScript,
Swift, Go adapters — falls back to file-level raw-bytes tracking for
unsupported file types.
- Full MuseDomainPlugin + StructuredMergePlugin conformance.
- 76 tests: unit (AST parser, symbol diff golden cases, cross-file move
annotation), snapshot (museignore, pycache filter, stability),
semantic diff (add/remove/rename/reformat functions), merge (symbol-
level conflict detection), drift, schema, protocol conformance.
- Registered as "code" domain in muse/plugins/registry.py.
All gates pass: mypy strict (0 errors), typing_audit --max-any 0
(0 violations), pytest (773 tests, 0 failures).
* feat: add tree-sitter multi-language support to code plugin
Extends the code domain plugin from Python-only to 11 languages using
tree-sitter — the same parsing technology used by GitHub Copilot, VS Code,
Neovim, and Zed.
Languages added (all backed by real CSTs, no regex):
JavaScript / JSX / MJS / CJS — function, class, method extraction
TypeScript / TSX — + interface, type alias, enum, abstract class
Go — methods qualified with receiver type (Dog.Bark)
Rust — impl methods qualified with type (Dog.bark)
Java — class, interface, method, constructor, enum
C — function_definition extraction
C++ — + class_specifier and struct_specifier
C# — class, interface, struct, method, constructor, enum
Ruby — class, module, method, singleton_method
Kotlin — function, class (with method nesting)
All adapters compute content_id, body_hash, and signature_id from normalized
CST text, enabling rename and implementation-change detection across all 11
languages. SEMANTIC_EXTENSIONS now covers 23 file extensions.
Adds 11 tree-sitter dependencies to pyproject.toml. Includes 25 new tests
covering symbol extraction, qualified name construction, rename detection,
and adapter routing for every supported extension.
* feat: add code-domain semantic commands and tour de force demo
Three new CLI commands impossible in Git:
- muse symbols: list every function, class, method in a snapshot,
with content hashes, kind filter, file filter, and JSON output.
- muse symbol-log <address>: track a single named symbol through
the full commit DAG — creation, renames, signature changes,
implementation changes, cross-file moves — including through
rename events where the address itself changes.
- muse detect-refactor: scan a commit range and emit a classified
semantic refactoring report: RENAME (body_hash match), MOVE
(content_id match), SIGNATURE, IMPLEMENTATION.
Also adds:
- docs/demo/tour-de-force-code.md: 11-act narration script for a
code plugin demo — from first commit through symbol-level auto-merge,
muse symbol-log, muse detect-refactor, and multi-language support.
- docs/demo/README.md: demo hub presenting both the music and code
demos side by side with the shared architecture explained.
All three commands pass mypy --strict, zero typing_audit violations,
797 tests green.
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: code domain plugin — AST-based semantic versioning (#53)
Adds muse/plugins/code/ — a first-class Muse plugin treating source code as a structured system of named symbols. Includes tree-sitter support for 10 languages, semantic content IDs, rename/move detection, symbol-level OT merge. All gates: mypy strict 0 errors, typing_audit --max-any 0, 797 tests green.
·
feat(code): supercharge code plugin with 9 new semantic commands
Add a full suite of code-domain CLI commands that are structurally
impossible in Git — treating the codebase as a typed, content-addressed
symbol graph rather than a bag of text lines.
New shared helper:
- muse/plugins/code/_query.py: symbols_for_snapshot(), walk_commits(),
walk_commits_range(), flat_symbol_ops(), touched_files(), file_pairs()
New analysis commands (read-only):
- muse grep: search the symbol graph by name, kind, language — not text
- muse blame: per-symbol attribution — one answer per function, not per line
- muse hotspots: symbol churn leaderboard — which functions change most
- muse stable: symbol stability leaderboard — the bedrock of your codebase
- muse coupling: semantic file co-change analysis — hidden dependencies
- muse compare: deep semantic diff between any two historical snapshots
- muse languages: language + symbol-type breakdown of any snapshot
New agent-scale commands:
- muse patch: surgical per-symbol modification — modify exactly one named symbol
- muse query: symbol graph predicate DSL (kind/language/name/file/hash)
All commands support --json for pipeline integration.
All pass mypy strict, typing_audit --max-any 0, and 797 pytest tests.
Update docs/demo/tour-de-force-code.md with all 12 commands as a full
paradigm-shift narrative. Update docs/demo/README.md to reflect the
complete command matrix.
·
feat(music): 9 new semantic commands — version control that understands music
Add a full suite of music-domain CLI commands that are structurally
impossible in Git — treating MIDI files as typed, content-addressed
graphs of note events rather than binary blobs.
New shared helper:
- muse/plugins/music/_query.py: NoteInfo, load_track(), load_track_from_workdir(),
key_signature_guess(), detect_chord(), notes_by_bar(), notes_to_midi_bytes(),
walk_commits_for_track()
New analysis commands (read-only):
- muse notes: Every note as musical notation (pitch, beat, duration, velocity)
- muse note-log: Note-level commit history — which notes changed in each commit
- muse note-blame: Per-bar attribution — which commit introduced these notes?
- muse harmony: Chord analysis + Krumhansl-Schmuckler key detection
- muse piano-roll: ASCII piano roll visualization with bar separators
- muse note-hotspots: Bar-level churn leaderboard across all tracks
- muse velocity-profile: Dynamic range, RMS, and per-dynamic-level histogram
New agent-scale commands:
- muse transpose: Surgical pitch transformation — shift all notes by N semitones
- muse mix: Combine notes from two MIDI tracks into a single output track
All commands support --json for pipeline integration.
All pass mypy strict, typing_audit --max-any 0, 797 pytest tests green.
Add docs/demo/tour-de-force-music.md with a 9-act narrative.
Update docs/demo/README.md and muse/cli/app.py to include both music
and code domain commands together.
Closes #56
·
fix(code): close 4 architectural gaps — validation, deps, find-symbol, temporal query (#58)
Patch syntax validation (gap 1)
--------------------------------
- Add validate_source() to TreeSitterAdapter: parse content with the
tree-sitter grammar for that language, walk the CST for ERROR/MISSING
nodes, return a precise line-level error message.
- Add validate_syntax(source, file_path) public function to ast_parser.py:
dispatches to ast.parse for Python, validate_source for all tree-sitter
languages, no-op for unsupported extensions.
- patch.py: replace Python-only ast.parse guard with validate_syntax call.
muse patch now validates syntax for all 11 supported languages before
writing to disk.
muse deps (gap 2 — no dependency graph)
-----------------------------------------
- New command muse/cli/commands/deps.py.
- File mode: extract import-kind symbols from the snapshot for a file
(what does it import?). --reverse scans all other files' import
symbols for references to the target (what imports it?).
- Symbol mode (address contains ::): Python-only call extraction via
ast.walk on the function node, collecting ast.Call targets as callees.
--reverse scans all Python files in the snapshot for callers of the
bare function name.
- --commit for historical snapshots; --json for pipelines.
muse find-symbol (gaps 3 & 4 — cross-branch, temporal hash search)
--------------------------------------------------------------------
- New command muse/cli/commands/find_symbol.py.
- Walks ALL CommitRecords in the object store (get_all_commits), sorted
oldest-first, scanning InsertOps in each structured_delta.
- --hash HASH: re-parses the snapshot blob for each matched InsertOp to
obtain the exact content_id and filter by prefix. Finds when a specific
function body first entered the repository, across every branch.
- --name NAME: matches bare symbol name (exact or prefix with *).
- --kind KIND: restricts to a symbol kind.
- --all-branches: additionally enumerates every branch tip via
.muse/refs/heads/ and checks its HEAD snapshot for current presence.
- --first: deduplicate on content_id, keeping only the first appearance.
muse query --all-commits (gap 4 — temporal hash= across history)
-----------------------------------------------------------------
- New --all-commits flag on the existing muse query command.
- When set, walks all commits ordered oldest-first, applies the full
predicate DSL against each snapshot (re-parses blobs).
- Deduplicates on content_id, annotating the first commit each unique
hash was seen. "hash=a3f2c9 --all-commits" answers: when did this
function body first appear, and on which branch?
- Mutually exclusive with --commit.
All gates: mypy strict 0 errors, typing_audit --max-any 0, 797 tests green.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix(ci): run only on push to main and PRs targeting main
* fix(code): close 4 architectural gaps — validation, deps, find-symbol, temporal query
Patch syntax validation (gap 1)
--------------------------------
- Add validate_source() to TreeSitterAdapter: parse content with the
tree-sitter grammar for that language, walk the CST for ERROR/MISSING
nodes, return a precise line-level error message.
- Add validate_syntax(source, file_path) public function to ast_parser.py:
dispatches to ast.parse for Python, validate_source for all tree-sitter
languages, no-op for unsupported extensions.
- patch.py: replace Python-only ast.parse guard with validate_syntax call.
muse patch now validates syntax for all 11 supported languages before
writing to disk.
muse deps (gap 2 — no dependency graph)
-----------------------------------------
- New command muse/cli/commands/deps.py.
- File mode: extract import-kind symbols from the snapshot for a file
(what does it import?). --reverse scans all other files' import
symbols for references to the target (what imports it?).
- Symbol mode (address contains ::): Python-only call extraction via
ast.walk on the function node, collecting ast.Call targets as callees.
--reverse scans all Python files in the snapshot for callers of the
bare function name.
- --commit for historical snapshots; --json for pipelines.
muse find-symbol (gaps 3 & 4 — cross-branch, temporal hash search)
--------------------------------------------------------------------
- New command muse/cli/commands/find_symbol.py.
- Walks ALL CommitRecords in the object store (get_all_commits), sorted
oldest-first, scanning InsertOps in each structured_delta.
- --hash HASH: re-parses the snapshot blob for each matched InsertOp to
obtain the exact content_id and filter by prefix. Finds when a specific
function body first entered the repository, across every branch.
- --name NAME: matches bare symbol name (exact or prefix with *).
- --kind KIND: restricts to a symbol kind.
- --all-branches: additionally enumerates every branch tip via
.muse/refs/heads/ and checks its HEAD snapshot for current presence.
- --first: deduplicate on content_id, keeping only the first appearance.
muse query --all-commits (gap 4 — temporal hash= across history)
-----------------------------------------------------------------
- New --all-commits flag on the existing muse query command.
- When set, walks all commits ordered oldest-first, applies the full
predicate DSL against each snapshot (re-parses blobs).
- Deduplicates on content_id, annotating the first commit each unique
hash was seen. "hash=a3f2c9 --all-commits" answers: when did this
function body first appear, and on which branch?
- Mutually exclusive with --commit.
All gates: mypy strict 0 errors, typing_audit --max-any 0, 797 tests green.
* fix(ci): run only on push to main and PRs targeting main
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat(code): add call-graph tier — impact, dead, coverage commands (#60)
Adds three new code-domain commands that unlock the full power of the
call-graph foundation laid by muse deps:
muse impact <address>
Transitive blast-radius analysis via BFS over the reverse call graph.
Answers "if I change this function, what else could break?" — depths
1, 2, … until the closure is exhausted. Risk-level indicator (🟢/🟡/🔴).
muse dead [--kind] [--exclude-tests]
Dead code detection. A symbol is a candidate when its bare name
appears in no ast.Call node AND its module is not imported anywhere
in the snapshot. Distinguishes definite dead (module not imported)
from soft dead (module imported but function never called directly).
muse coverage <class_address> [--show-callers/--no-show-callers]
Class interface call-coverage. Lists every method of a class, marks
which ones are called anywhere in the snapshot, and prints a coverage
percentage — no test suite required.
Shared infrastructure:
muse/plugins/code/_callgraph.py — ForwardGraph / ReverseGraph types,
build_forward_graph, build_reverse_graph, transitive_callers BFS.
deps.py refactored to import from _callgraph.py (no duplicate AST logic).
All three commands support --json and --commit <REF>.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat(phase-1): typed delta algebra — replace DeltaManifest with StructuredDelta
Implements Phase 1 of the supercharge plan end-to-end.
## What changed
**Core type system (muse/domain.py)**
- Remove `DeltaManifest` entirely.
- Add `InsertOp`, `DeleteOp`, `MoveOp`, `ReplaceOp`, `PatchOp` TypedDicts —
each discriminated by a `Literal["op-name"]` field for mypy narrowing.
- Add `StructuredDelta` — `{domain, ops: list[DomainOp], summary}`.
- `StateDelta = StructuredDelta` (was `DeltaManifest`).
- `MergeResult` gains `op_log: list[DomainOp]`.
- `DriftReport.delta` defaults to an empty `StructuredDelta`.
- `MuseDomainPlugin.diff()` gains `repo_root: Path | None = None`.
**Storage (muse/core/store.py)**
- `CommitRecord` and `CommitDict` gain `structured_delta: StructuredDelta | None`.
- Stored at commit time; deserialized on read for `muse show`.
**Myers LCS MIDI diff (muse/plugins/music/midi_diff.py)** — new module
- `NoteKey` TypedDict as the LCS comparison unit (5 fields).
- `lcs_edit_script()`: O(nm) LCS DP + traceback → `list[EditStep]`.
- `extract_notes()`: MIDI bytes → sorted `list[NoteKey]`.
- `diff_midi_notes()`: top-level entry point → `StructuredDelta`.
- Content IDs are deterministic SHA-256 of note field tuples.
**Music plugin (muse/plugins/music/plugin.py)**
- `diff()`: returns `StructuredDelta` with `InsertOp`/`DeleteOp` for file
additions/removals, `ReplaceOp` for non-MIDI modifications, and `PatchOp`
with note-level `child_ops` for MIDI files when `repo_root` is available.
- `apply()`: handles `DeleteOp`/`ReplaceOp`/`InsertOp` in-memory; workdir
path rescans as before.
- `drift()`: counts op kinds instead of accessing old manifest keys.
**CLI commands**
- `commit.py`: computes and stores `structured_delta` against parent.
- `show.py`: displays `commit.structured_delta` with per-op lines and
`PatchOp` child_summary; falls back to manifest diff for old commits.
- `diff.py`: calls `plugin.diff(repo_root=root)` and prints structured output.
- `status.py`: extracts added/modified/deleted sets from `delta.ops`.
- `checkout.py`: extracts removed/to_restore paths from `delta.ops`.
**Tests**
- `test_structured_delta.py` (new): 38 tests for all op types and plugin behaviour.
- `test_midi_diff.py` (new): 29 tests for note extraction, LCS algorithm,
and `diff_midi_notes()` end-to-end.
- `test_music_plugin.py`: updated to StructuredDelta API.
- `test_cli_plugin_dispatch.py`, `test_plugin_apply_and_checkout.py`,
`test_cli_coverage_gaps.py`: updated for new delta shape.
All 456 tests green. mypy strict: 0 errors. typing_audit --max-any 0: 0 violations.
·
ci: only run on PRs targeting main (dev→main gate)
·
feat(code): Phase 2 — query v2 predicate grammar + muse query-history
muse query upgrades to a full v2 predicate grammar:
- OR, NOT, and parenthesis grouping via recursive descent parser
- New predicate keys: qualified_name, body_hash, signature_id,
lineno_gt, lineno_lt (no external dependencies)
- All operators: = ~= ^= $= != (>= <= for lineno)
- JSON output now includes schema_version:2 wrapper and end_lineno
- Backward-compatible: existing "key=value" "key~=value" still work
New module muse/plugins/code/_predicate.py:
- Token → Parser → Predicate pipeline
- Public API: parse_query(str | list[str]) → Predicate
- Drives both muse query and muse query-history
muse query-history PREDICATE... [--from REF] [--to REF]:
- Walks a commit range, accumulating per-symbol history
- Reports: first seen, last seen, commit count, change count
- JSON with schema_version:2
- Answers: "find all Python functions introduced after v1.0"
New store function walk_commits_between() for bounded range walking.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
·
feat(code): Phase 1 — lineage, api-surface, codemap, clones, checkout-symbol, semantic-cherry-pick (#62)
Six new code-domain commands, all additive with zero schema changes:
muse lineage ADDRESS
Full provenance chain of a symbol through commit history: created,
renamed_from, moved_from, copied_from, modified, deleted.
Rename/move detected by matching content_id across Insert+Delete pairs.
muse api-surface [--diff REF]
Public API surface at a snapshot. With --diff REF shows added/removed/
changed public symbols between two commits. Public = kind in
{function,class,method,...} and name not starting with _.
muse codemap [--top N]
Semantic topology: modules ranked by size, import in-degree, import
cycle detection via DFS, high-centrality symbols, boundary files
(high fan-out, zero fan-in). Reveals codebase structure at a glance.
muse clones [--tier exact|near|both]
Exact clones: same body_hash at different addresses (copy-paste).
Near-clones: same signature_id, different body_hash (same contract,
diverged implementation). Cluster output with member addresses.
muse checkout-symbol ADDRESS --commit REF [--dry-run]
Restore a single symbol from a historical commit into the working tree.
Only the target symbol's lines change; everything else is untouched.
--dry-run prints the unified diff without writing.
muse semantic-cherry-pick ADDRESS... --from REF [--dry-run] [--json]
Cherry-pick named symbols from a historical commit, not entire files.
Applies each symbol patch to the working tree at the symbol's current
location; appends at end if symbol is not in the current tree.
All six commands support --json and --commit REF (where applicable).
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat(code): Phase 3 — .muse/indices/ infrastructure and muse index command
New module muse/core/indices.py:
- symbol_history index: address → chronological event timeline
(commit_id, op, content_id, body_hash, signature_id per event)
- hash_occurrence index: body_hash → list of addresses that share it
- load/save helpers for both indexes
- index_info() for status reporting
- All indexes: schema_version:1, updated_at timestamp, fully rebuildable
New command muse index (multi-command Typer app):
- muse index status: show present/absent/corrupt status and entry counts
- muse index rebuild: walk full commit history to rebuild symbol_history
and/or hash_occurrence; --index NAME for selective rebuild
Design:
- Indexes are derived, optional, and fully rebuildable — zero impact on
repository correctness if absent
- symbol_history enables O(1) lineage/symbol-log instead of O(commits) scan
- hash_occurrence enables O(1) clone detection and hash= queries
- Incremental updates can be wired into the commit hook in a future phase
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
·
feat(code): Phase 5 — multi-agent coordination layer
New storage layer muse/core/coordination.py:
- .muse/coordination/reservations/<uuid>.json advisory symbol leases
- .muse/coordination/intents/<uuid>.json declared operations
- Reservation: address list + run_id + branch + TTL-based expiry
- Intent: extends a reservation with operation type and detail string
- All records: write-once, schema_version:1, expiry-enforced by is_active()
Six new coordination commands:
muse reserve ADDRESS... --run-id ID --ttl N --op OP
Advisory symbol reservation. Warns if addresses already reserved by
another agent. Never blocks — purely coordination signal.
muse intent ADDRESS... --op OP --detail TEXT --reservation-id UUID
Declares a specific operation (rename/move/extract/delete/...) before
executing it. Enables forecast to predict conflicts more accurately.
muse forecast [--branch B] [--json]
Predicts merge conflicts from active reservations and intents.
Three conflict types: address_overlap (1.0), blast_radius_overlap (0.75),
operation_conflict (0.9). Uses Python call graph for blast-radius.
muse plan-merge OURS THEIRS [--json]
Dry-run semantic merge plan. Classifies diverging symbols into:
symbol_edit_overlap, rename_edit, delete_use, no_conflict.
Reads committed snapshots — does not modify anything.
muse shard --agents N [--language LANG] [--json]
Partitions the codebase into N low-coupling work zones using import
graph connectivity + greedy component partitioning balanced by symbol
count. Reports cross-shard edges as coupling score.
muse reconcile [--json]
Reads coordination state + branch divergence, recommends merge ordering
(fewer conflicts first) and integration strategy (fast-forward / rebase /
manual) for each active branch.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
·
feat(code): Phase 4 — metadata_id, canonical_key, composite refactor classification (#65)
SymbolRecord gains two new fields (backward-compatible: "" for pre-v2 records):
metadata_id SHA-256 of symbol metadata that wraps the body without being
part of it: decorators + async flag for Python functions,
decorator list + bases for Python classes. Stubbed ("") for
tree-sitter adapters — future adapters can enrich this by
reading modifier/annotation nodes.
canonical_key Stable machine handle: {file}#{scope}#{kind}#{name}#{lineno}.
Disambiguates overloads and nested scopes. Unique within a
snapshot. Enables agent-to-agent symbol handoff without
re-querying.
New classification helpers:
muse/plugins/code/_refactor_classify.py
classify_exact() hash-based exact classification:
rename | move | rename+move | signature_only |
impl_only | metadata_only | full_rewrite
classify_composite() batch heuristic classification across a set of
added/removed symbols — detects extract, inline,
split, merge with confidence scores and evidence
RefactorClassification full typed result (to_dict() → JSON-ready)
muse detect-refactor --json now emits schema_version:2 with total count.
Python _make_*_record() helpers thread file_path and class_prefix for accurate
canonical_key computation. tree-sitter TreeSitterAdapter uses scope prefix
extracted from the qualified_name.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat(code): Phase 6 — ConflictRecord taxonomy, muse breakage, muse invariants
MergeResult v2 — ConflictRecord taxonomy
New ConflictRecord dataclass in domain.py carries structured conflict
metadata alongside the existing conflicts: list[str]:
- conflict_type: symbol_edit_overlap | rename_edit | move_edit |
delete_use | dependency_conflict | file_level (legacy)
- ours_summary / theirs_summary: short change descriptions
- addresses: list[str] of involved symbol addresses
Added as MergeResult.conflict_records: list[ConflictRecord] (default []).
Fully backward-compatible — existing callers that don't populate it
continue to work; plugins that implement StructuredMergePlugin can
enrich it.
muse breakage
Detects symbol-level structural breakage in the working tree vs HEAD:
- stale_import: imports a symbol no longer in the HEAD snapshot
- missing_interface_method: class body missing methods found in HEAD
Purely structural — no code execution, no type checker, no network.
Operates on the committed symbol graph + current working-tree parse.
Supports --language filter and --json output.
muse invariants
Enforces architectural rules from .muse/invariants.toml:
- no_cycles: import graph must be acyclic (DFS cycle detection)
- forbidden_dependency: source_pattern must not import forbidden_pattern
- layer_boundary: lower layers must not import from upper layers
- required_test: public functions must have corresponding test functions
Minimal built-in TOML parser — no extra dependencies.
All rules run against the committed snapshot; no working-tree parsing.
Creates invariants.toml if absent — guided onboarding message shown.
Supports --commit REF to check historical snapshots and --json output.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
·
feat(code): Phase 7 — semantic versioning metadata on StructuredDelta + CommitRecord
SemVerBump type alias (Literal["major", "minor", "patch", "none"])
StructuredDelta gains two optional v2 fields:
sem_ver_bump: inferred impact of this delta on the public API
breaking_changes: sorted list of symbol addresses whose public contract
was removed or incompatibly changed
infer_sem_ver_bump(delta) → (SemVerBump, list[str])
Pure function in domain.py, domain-agnostic:
delete public symbol → major + address added to breaking_changes
rename public symbol → major + address added to breaking_changes
signature_only change → major + breaking
insert public symbol → minor
impl_only (body changes) → patch
metadata/formatting only → none
PatchOp child_ops → recurse into children (structural)
CommitRecord gains:
sem_ver_bump: SemVerBump = "none" (default for legacy + non-code)
breaking_changes: list[str] = []
commit command:
After computing structured_delta, calls infer_sem_ver_bump() and stores
both on the delta and the CommitRecord. First commit (no parent) stays
at "none" / [].
muse log:
Long-form view now shows SemVer: MAJOR / MINOR / PATCH when non-none,
plus first 3 breaking-change addresses ("+N more" when list is longer).
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
·
Merge PR #68 — Phase 7 semantic versioning metadata on StructuredDelta + CommitRecord
Clean merge — no conflicts.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
·
feat(code): Phase 7 — semantic versioning metadata on StructuredDelta + CommitRecord (#68)
SemVerBump type alias (Literal["major", "minor", "patch", "none"])
StructuredDelta gains two optional v2 fields:
sem_ver_bump: inferred impact of this delta on the public API
breaking_changes: sorted list of symbol addresses whose public contract
was removed or incompatibly changed
infer_sem_ver_bump(delta) → (SemVerBump, list[str])
Pure function in domain.py, domain-agnostic:
delete public symbol → major + address added to breaking_changes
rename public symbol → major + address added to breaking_changes
signature_only change → major + breaking
insert public symbol → minor
impl_only (body changes) → patch
metadata/formatting only → none
PatchOp child_ops → recurse into children (structural)
CommitRecord gains:
sem_ver_bump: SemVerBump = "none" (default for legacy + non-code)
breaking_changes: list[str] = []
commit command:
After computing structured_delta, calls infer_sem_ver_bump() and stores
both on the delta and the CommitRecord. First commit (no parent) stays
at "none" / [].
muse log:
Long-form view now shows SemVer: MAJOR / MINOR / PATCH when non-none,
plus first 3 breaking-change addresses ("+N more" when list is longer).
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: code + music domain plugins — semantic versioning for code and MIDI (#57)
* feat: code-domain semantic commands + code tour de force demo (#54)
* feat: add code domain plugin with AST-based semantic versioning
Introduces muse/plugins/code/ — a first-class Muse plugin that treats
source code as a structured system of named symbols rather than lines
of text.
Key capabilities
----------------
- Python AST parsing via stdlib `ast` (zero new dependencies). Every
function, class, method, variable, and import becomes a named symbol
with a stable content-addressed identity (SHA-256 of normalized AST).
- Semantic content IDs: two functions that differ only in whitespace or
comments share the same content_id; reformatting a file produces no
structured delta.
- Rename detection: same body_hash + different name → ReplaceOp
annotated "renamed to <new>".
- Move detection: same content_id at a different address across files →
cross-file move annotation on the DeleteOp/InsertOp pair.
- Symbol-level OT merge: two agents modifying different functions in
the same file auto-merge (ops commute); concurrent edits to the same
function produce a conflict at address "src/utils.py::function_name"
rather than a coarse file conflict.
- Language adapter protocol (LanguageAdapter) for future TypeScript,
Swift, Go adapters — falls back to file-level raw-bytes tracking for
unsupported file types.
- Full MuseDomainPlugin + StructuredMergePlugin conformance.
- 76 tests: unit (AST parser, symbol diff golden cases, cross-file move
annotation), snapshot (museignore, pycache filter, stability),
semantic diff (add/remove/rename/reformat functions), merge (symbol-
level conflict detection), drift, schema, protocol conformance.
- Registered as "code" domain in muse/plugins/registry.py.
All gates pass: mypy strict (0 errors), typing_audit --max-any 0
(0 violations), pytest (773 tests, 0 failures).
* feat: add tree-sitter multi-language support to code plugin
Extends the code domain plugin from Python-only to 11 languages using
tree-sitter — the same parsing technology used by GitHub Copilot, VS Code,
Neovim, and Zed.
Languages added (all backed by real CSTs, no regex):
JavaScript / JSX / MJS / CJS — function, class, method extraction
TypeScript / TSX — + interface, type alias, enum, abstract class
Go — methods qualified with receiver type (Dog.Bark)
Rust — impl methods qualified with type (Dog.bark)
Java — class, interface, method, constructor, enum
C — function_definition extraction
C++ — + class_specifier and struct_specifier
C# — class, interface, struct, method, constructor, enum
Ruby — class, module, method, singleton_method
Kotlin — function, class (with method nesting)
All adapters compute content_id, body_hash, and signature_id from normalized
CST text, enabling rename and implementation-change detection across all 11
languages. SEMANTIC_EXTENSIONS now covers 23 file extensions.
Adds 11 tree-sitter dependencies to pyproject.toml. Includes 25 new tests
covering symbol extraction, qualified name construction, rename detection,
and adapter routing for every supported extension.
* feat: add code-domain semantic commands and tour de force demo
Three new CLI commands impossible in Git:
- muse symbols: list every function, class, method in a snapshot,
with content hashes, kind filter, file filter, and JSON output.
- muse symbol-log <address>: track a single named symbol through
the full commit DAG — creation, renames, signature changes,
implementation changes, cross-file moves — including through
rename events where the address itself changes.
- muse detect-refactor: scan a commit range and emit a classified
semantic refactoring report: RENAME (body_hash match), MOVE
(content_id match), SIGNATURE, IMPLEMENTATION.
Also adds:
- docs/demo/tour-de-force-code.md: 11-act narration script for a
code plugin demo — from first commit through symbol-level auto-merge,
muse symbol-log, muse detect-refactor, and multi-language support.
- docs/demo/README.md: demo hub presenting both the music and code
demos side by side with the shared architecture explained.
All three commands pass mypy --strict, zero typing_audit violations,
797 tests green.
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* feat: code domain plugin — AST-based semantic versioning (#53)
Adds muse/plugins/code/ — a first-class Muse plugin treating source code as a structured system of named symbols. Includes tree-sitter support for 10 languages, semantic content IDs, rename/move detection, symbol-level OT merge. All gates: mypy strict 0 errors, typing_audit --max-any 0, 797 tests green.
* feat(code): supercharge code plugin with 9 new semantic commands
Add a full suite of code-domain CLI commands that are structurally
impossible in Git — treating the codebase as a typed, content-addressed
symbol graph rather than a bag of text lines.
New shared helper:
- muse/plugins/code/_query.py: symbols_for_snapshot(), walk_commits(),
walk_commits_range(), flat_symbol_ops(), touched_files(), file_pairs()
New analysis commands (read-only):
- muse grep: search the symbol graph by name, kind, language — not text
- muse blame: per-symbol attribution — one answer per function, not per line
- muse hotspots: symbol churn leaderboard — which functions change most
- muse stable: symbol stability leaderboard — the bedrock of your codebase
- muse coupling: semantic file co-change analysis — hidden dependencies
- muse compare: deep semantic diff between any two historical snapshots
- muse languages: language + symbol-type breakdown of any snapshot
New agent-scale commands:
- muse patch: surgical per-symbol modification — modify exactly one named symbol
- muse query: symbol graph predicate DSL (kind/language/name/file/hash)
All commands support --json for pipeline integration.
All pass mypy strict, typing_audit --max-any 0, and 797 pytest tests.
Update docs/demo/tour-de-force-code.md with all 12 commands as a full
paradigm-shift narrative. Update docs/demo/README.md to reflect the
complete command matrix.
* feat(music): 9 new semantic commands — version control that understands music
Add a full suite of music-domain CLI commands that are structurally
impossible in Git — treating MIDI files as typed, content-addressed
graphs of note events rather than binary blobs.
New shared helper:
- muse/plugins/music/_query.py: NoteInfo, load_track(), load_track_from_workdir(),
key_signature_guess(), detect_chord(), notes_by_bar(), notes_to_midi_bytes(),
walk_commits_for_track()
New analysis commands (read-only):
- muse notes: Every note as musical notation (pitch, beat, duration, velocity)
- muse note-log: Note-level commit history — which notes changed in each commit
- muse note-blame: Per-bar attribution — which commit introduced these notes?
- muse harmony: Chord analysis + Krumhansl-Schmuckler key detection
- muse piano-roll: ASCII piano roll visualization with bar separators
- muse note-hotspots: Bar-level churn leaderboard across all tracks
- muse velocity-profile: Dynamic range, RMS, and per-dynamic-level histogram
New agent-scale commands:
- muse transpose: Surgical pitch transformation — shift all notes by N semitones
- muse mix: Combine notes from two MIDI tracks into a single output track
All commands support --json for pipeline integration.
All pass mypy strict, typing_audit --max-any 0, 797 pytest tests green.
Add docs/demo/tour-de-force-music.md with a 9-act narrative.
Update docs/demo/README.md and muse/cli/app.py to include both music
and code domain commands together.
Closes #56
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
Add comprehensive docs and supercharged tests for Code Domain V2 (#70)
Documentation
- docs/reference/code-domain-v2.md: complete 11-section reference covering all
7 phases — symbol identity model, every new command, predicate grammar, index
infrastructure, coordination layer, merge engine v2, semantic versioning,
call-graph tier, architecture internals, and full type reference.
Tests (7 new test files, 273 new tests → 1070 total)
- test_predicate.py: tokeniser, all 10 keys × 7 operators, OR/NOT/AND/grouping,
parse_query list mode, 9 error cases.
- test_refactor_classify.py: classify_exact (all 8 classifications), classify_composite
(exact rename/move/rename+move detection, inferred extract), RefactorClassification.to_dict.
- test_callgraph.py: build_forward_graph, build_reverse_graph, transitive_callers
including cycle-safety, diamond dependency, max_depth limit.
- test_coordination.py: Reservation and Intent create/load/active/round-trip,
TTL expiry, corrupt-file resilience.
- test_indices.py: SymbolHistoryEntry round-trip, symbol_history and hash_occurrence
save/load/schema compliance, index_info absent/present/corrupt reporting.
- test_sem_ver.py: infer_sem_ver_bump for all op types, ConflictRecord dataclass,
major-wins precedence, multiple breaking changes accumulation.
- test_code_commands_v2.py: 96 CLI integration tests covering all Phase 1–7 commands
and call-graph tier (lineage, api-surface, codemap, clones, checkout-symbol,
semantic-cherry-pick, query v2, query-history, index rebuild/status,
detect-refactor schema v2, reserve, intent, forecast, plan-merge, shard,
reconcile, breakage, invariants, commit sem_ver_bump, impact, dead, coverage,
deps, find-symbol, patch).
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
refactor: strip phase/v2 workflow labels from all source, tests, and docs
Remove all "Phase N", "Code Domain V2", and "v2" workflow labels that bled
into permanent codebase artifacts. Rename files that carried the label in
their name (test_code_commands_v2 → test_code_commands, code-domain-v2.md →
code-domain.md) and scrub matching docstrings, inline comments, section
headers, and help text across muse/, tests/, and docs/.
No logic changes; pure label removal.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: god-tier MIDI dimension expansion + full supercharge architecture
Expands the music plugin from 4 internal merge buckets to 21 fully
independent MIDI dimensions, adds stable entity identity with MutateOp
tracking, agent provenance with HMAC signing, an append-only op log,
voice-aware Music RGA CRDT, music query DSL, invariants engine, and
hierarchical bar-chunk manifests.
MIDI dimension expansion (midi_merge.py, plugin.py):
- From 4 buckets (notes/harmonic/dynamic/structural) to 21 internal dims
- pitch_bend, channel_pressure, poly_pressure now independent dimensions
- CC split by controller: cc_modulation, cc_volume, cc_pan, cc_expression,
cc_sustain, cc_portamento, cc_sostenuto, cc_soft_pedal, cc_reverb,
cc_chorus, cc_other — all independently mergeable
- tempo_map, time_signatures, track_structure remain non-independent
- Backward-compatible user-facing aliases (melodic/rhythmic/harmonic/
dynamic/structural still work)
New core modules:
- muse/core/provenance.py: AgentIdentity, HMAC-SHA256 signing, key I/O
- muse/core/op_log.py: OpEntry, Lamport timestamps, append-only log,
checkpointing, replay, to_structured_delta
New music plugin modules:
- muse/plugins/music/entity.py: NoteEntity, EntityIndex, entity ID
assignment, entity-aware diff with MutateOp
- muse/plugins/music/_music_query.py: tokenizer, recursive descent parser,
evaluator, run_query over commit history
- muse/plugins/music/_invariants.py: InvariantRule/Report/Violation,
4 built-in checks (max_polyphony, pitch_range, key_consistency,
no_parallel_fifths)
- muse/plugins/music/manifest.py: BarChunk, TrackManifest, MusicManifest,
hierarchical per-bar manifests, partial diff by bar hash
- muse/plugins/music/_crdt_notes.py: NotePosition, RGANoteEntry, MusicRGA
with voice-aware position ordering (bass→soprano)
Domain types (domain.py): FieldMutation, MutateOp, EntityProvenance
Store types (core/store.py): 6 agent provenance fields on CommitRecord
CLI: muse music-query, muse music-check registered in app.py
Benchmark: tools/benchmark.py harness for parse/diff/merge/query/RGA
Tests: 215 passing across 7 new test suites
Docs: 5 architecture documents in docs/
·
refactor: rename music→midi domain, strip all 5-dim backward compat
- Rename muse/plugins/music/ → muse/plugins/midi/ (git mv, history preserved)
- Rename _music_query.py → _midi_query.py
- Rename CLI commands: music_check → midi_check, music_query → midi_query
with updated command names (music-check→midi-check, music-query→midi-query)
- Change _DOMAIN_TAG from "music" to "midi" in plugin.py
- Rename MusicPlugin → MidiPlugin everywhere; update registry and all imports
- Change default domain: "music" → "midi" in init.py and registry.py
- Remove all 5-dimension backward-compat aliases from DIM_ALIAS:
melodic, rhythmic, harmonic, dynamic, structural — gone entirely
- Rewrite test_domain_schema.py: tests all 21 MIDI dimensions by name,
schema kind, and independence flag; asserts "music" no longer in registry
- Rewrite test_music_midi_merge.py: tests all 21-dim _classify_event routing,
per-dimension conflict detection, independent auto-merge, and strategy rules;
removes all references to old coarse dimension names
- Update test_core_attributes.py examples to use new MIDI dimension names
- Update all doc files and README.md: replace 5-dim descriptions with
21-dimension MIDI schema; update module paths and class names throughout
·
feat: implement 3 missing plan items — property tests, format_version, schema auto-dispatch
Three gaps from the supercharge plan now fully implemented:
1. **hypothesis property-based tests** (`tests/test_property_based.py`)
- All six CRDT types (LWWRegister, VectorClock, ORSet, RGA, AWMap, GCounter)
verified against commutativity, associativity, and idempotency with @given
- LCS round-trip: self-diff → empty ops, empty-base, empty-target, content
provenance, op-count upper bound
- OT diamond property: for arbitrary concurrent InsertOp pairs, applying
transform() in either order produces identical final sequences
2. **Bug fixes discovered by hypothesis**
- LWWRegister.join: non-commutative when (timestamp, author) tie but values
differ — added value as deterministic tiebreaker
- RGA.join: non-commutative when same element ID has different values across
replicas — added lexicographic value tiebreaker
3. **CommitRecord.format_version** (`muse/core/store.py`)
- New field tracks schema evolution: 1=base, 2=structured_delta, 3=sem_ver,
4=agent provenance; new commits write version 4; old JSON defaults to 1
4. **snapshot_diff() auto-dispatch** (`muse/core/diff_algorithms/__init__.py`)
- Public function: given a DomainSchema + two SnapshotManifests → StructuredDelta
- ScaffoldPlugin.diff() now delegates to snapshot_diff() — zero set-algebra boilerplate
- New plugin authors call snapshot_diff(self.schema(), base, target) for free
file-level diffs without implementing diff() from scratch
All verified: 1319 pytest tests green, mypy 0 errors, typing_audit 0 violations.
·
refactor: remove supercharge/god-tier/singularity references from all source files
Replace informal hype labels with neutral technical language:
- store.py: "supercharge plan" → "architecture phase"
- diff_algorithms/__init__.py: "supercharge plan" → "domain schema architecture"
- _crdt_notes.py: "supercharge plan" → "live collaboration architecture"
- Rename docs/architecture/supercharge-plan.md → architecture-plan.md
and update the document title to match
·
feat: god-tier MIDI dimension expansion + full supercharge architecture (#73)
* feat: god-tier MIDI dimension expansion + full supercharge architecture
Expands the music plugin from 4 internal merge buckets to 21 fully
independent MIDI dimensions, adds stable entity identity with MutateOp
tracking, agent provenance with HMAC signing, an append-only op log,
voice-aware Music RGA CRDT, music query DSL, invariants engine, and
hierarchical bar-chunk manifests.
MIDI dimension expansion (midi_merge.py, plugin.py):
- From 4 buckets (notes/harmonic/dynamic/structural) to 21 internal dims
- pitch_bend, channel_pressure, poly_pressure now independent dimensions
- CC split by controller: cc_modulation, cc_volume, cc_pan, cc_expression,
cc_sustain, cc_portamento, cc_sostenuto, cc_soft_pedal, cc_reverb,
cc_chorus, cc_other — all independently mergeable
- tempo_map, time_signatures, track_structure remain non-independent
- Backward-compatible user-facing aliases (melodic/rhythmic/harmonic/
dynamic/structural still work)
New core modules:
- muse/core/provenance.py: AgentIdentity, HMAC-SHA256 signing, key I/O
- muse/core/op_log.py: OpEntry, Lamport timestamps, append-only log,
checkpointing, replay, to_structured_delta
New music plugin modules:
- muse/plugins/music/entity.py: NoteEntity, EntityIndex, entity ID
assignment, entity-aware diff with MutateOp
- muse/plugins/music/_music_query.py: tokenizer, recursive descent parser,
evaluator, run_query over commit history
- muse/plugins/music/_invariants.py: InvariantRule/Report/Violation,
4 built-in checks (max_polyphony, pitch_range, key_consistency,
no_parallel_fifths)
- muse/plugins/music/manifest.py: BarChunk, TrackManifest, MusicManifest,
hierarchical per-bar manifests, partial diff by bar hash
- muse/plugins/music/_crdt_notes.py: NotePosition, RGANoteEntry, MusicRGA
with voice-aware position ordering (bass→soprano)
Domain types (domain.py): FieldMutation, MutateOp, EntityProvenance
Store types (core/store.py): 6 agent provenance fields on CommitRecord
CLI: muse music-query, muse music-check registered in app.py
Benchmark: tools/benchmark.py harness for parse/diff/merge/query/RGA
Tests: 215 passing across 7 new test suites
Docs: 5 architecture documents in docs/
* refactor: rename music→midi domain, strip all 5-dim backward compat
- Rename muse/plugins/music/ → muse/plugins/midi/ (git mv, history preserved)
- Rename _music_query.py → _midi_query.py
- Rename CLI commands: music_check → midi_check, music_query → midi_query
with updated command names (music-check→midi-check, music-query→midi-query)
- Change _DOMAIN_TAG from "music" to "midi" in plugin.py
- Rename MusicPlugin → MidiPlugin everywhere; update registry and all imports
- Change default domain: "music" → "midi" in init.py and registry.py
- Remove all 5-dimension backward-compat aliases from DIM_ALIAS:
melodic, rhythmic, harmonic, dynamic, structural — gone entirely
- Rewrite test_domain_schema.py: tests all 21 MIDI dimensions by name,
schema kind, and independence flag; asserts "music" no longer in registry
- Rewrite test_music_midi_merge.py: tests all 21-dim _classify_event routing,
per-dimension conflict detection, independent auto-merge, and strategy rules;
removes all references to old coarse dimension names
- Update test_core_attributes.py examples to use new MIDI dimension names
- Update all doc files and README.md: replace 5-dim descriptions with
21-dimension MIDI schema; update module paths and class names throughout
* feat: implement 3 missing plan items — property tests, format_version, schema auto-dispatch
Three gaps from the supercharge plan now fully implemented:
1. **hypothesis property-based tests** (`tests/test_property_based.py`)
- All six CRDT types (LWWRegister, VectorClock, ORSet, RGA, AWMap, GCounter)
verified against commutativity, associativity, and idempotency with @given
- LCS round-trip: self-diff → empty ops, empty-base, empty-target, content
provenance, op-count upper bound
- OT diamond property: for arbitrary concurrent InsertOp pairs, applying
transform() in either order produces identical final sequences
2. **Bug fixes discovered by hypothesis**
- LWWRegister.join: non-commutative when (timestamp, author) tie but values
differ — added value as deterministic tiebreaker
- RGA.join: non-commutative when same element ID has different values across
replicas — added lexicographic value tiebreaker
3. **CommitRecord.format_version** (`muse/core/store.py`)
- New field tracks schema evolution: 1=base, 2=structured_delta, 3=sem_ver,
4=agent provenance; new commits write version 4; old JSON defaults to 1
4. **snapshot_diff() auto-dispatch** (`muse/core/diff_algorithms/__init__.py`)
- Public function: given a DomainSchema + two SnapshotManifests → StructuredDelta
- ScaffoldPlugin.diff() now delegates to snapshot_diff() — zero set-algebra boilerplate
- New plugin authors call snapshot_diff(self.schema(), base, target) for free
file-level diffs without implementing diff() from scratch
All verified: 1319 pytest tests green, mypy 0 errors, typing_audit 0 violations.
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: Code Domain V2 — complete 7-phase roadmap (dev → main) (#69)
* feat(phase-1): typed delta algebra — replace DeltaManifest with StructuredDelta
Implements Phase 1 of the supercharge plan end-to-end.
## What changed
**Core type system (muse/domain.py)**
- Remove `DeltaManifest` entirely.
- Add `InsertOp`, `DeleteOp`, `MoveOp`, `ReplaceOp`, `PatchOp` TypedDicts —
each discriminated by a `Literal["op-name"]` field for mypy narrowing.
- Add `StructuredDelta` — `{domain, ops: list[DomainOp], summary}`.
- `StateDelta = StructuredDelta` (was `DeltaManifest`).
- `MergeResult` gains `op_log: list[DomainOp]`.
- `DriftReport.delta` defaults to an empty `StructuredDelta`.
- `MuseDomainPlugin.diff()` gains `repo_root: Path | None = None`.
**Storage (muse/core/store.py)**
- `CommitRecord` and `CommitDict` gain `structured_delta: StructuredDelta | None`.
- Stored at commit time; deserialized on read for `muse show`.
**Myers LCS MIDI diff (muse/plugins/music/midi_diff.py)** — new module
- `NoteKey` TypedDict as the LCS comparison unit (5 fields).
- `lcs_edit_script()`: O(nm) LCS DP + traceback → `list[EditStep]`.
- `extract_notes()`: MIDI bytes → sorted `list[NoteKey]`.
- `diff_midi_notes()`: top-level entry point → `StructuredDelta`.
- Content IDs are deterministic SHA-256 of note field tuples.
**Music plugin (muse/plugins/music/plugin.py)**
- `diff()`: returns `StructuredDelta` with `InsertOp`/`DeleteOp` for file
additions/removals, `ReplaceOp` for non-MIDI modifications, and `PatchOp`
with note-level `child_ops` for MIDI files when `repo_root` is available.
- `apply()`: handles `DeleteOp`/`ReplaceOp`/`InsertOp` in-memory; workdir
path rescans as before.
- `drift()`: counts op kinds instead of accessing old manifest keys.
**CLI commands**
- `commit.py`: computes and stores `structured_delta` against parent.
- `show.py`: displays `commit.structured_delta` with per-op lines and
`PatchOp` child_summary; falls back to manifest diff for old commits.
- `diff.py`: calls `plugin.diff(repo_root=root)` and prints structured output.
- `status.py`: extracts added/modified/deleted sets from `delta.ops`.
- `checkout.py`: extracts removed/to_restore paths from `delta.ops`.
**Tests**
- `test_structured_delta.py` (new): 38 tests for all op types and plugin behaviour.
- `test_midi_diff.py` (new): 29 tests for note extraction, LCS algorithm,
and `diff_midi_notes()` end-to-end.
- `test_music_plugin.py`: updated to StructuredDelta API.
- `test_cli_plugin_dispatch.py`, `test_plugin_apply_and_checkout.py`,
`test_cli_coverage_gaps.py`: updated for new delta shape.
All 456 tests green. mypy strict: 0 errors. typing_audit --max-any 0: 0 violations.
* ci: only run on PRs targeting main (dev→main gate)
* feat: code-domain semantic commands + code tour de force demo (#54)
* feat: add code domain plugin with AST-based semantic versioning
Introduces muse/plugins/code/ — a first-class Muse plugin that treats
source code as a structured system of named symbols rather than lines
of text.
Key capabilities
----------------
- Python AST parsing via stdlib `ast` (zero new dependencies). Every
function, class, method, variable, and import becomes a named symbol
with a stable content-addressed identity (SHA-256 of normalized AST).
- Semantic content IDs: two functions that differ only in whitespace or
comments share the same content_id; reformatting a file produces no
structured delta.
- Rename detection: same body_hash + different name → ReplaceOp
annotated "renamed to <new>".
- Move detection: same content_id at a different address across files →
cross-file move annotation on the DeleteOp/InsertOp pair.
- Symbol-level OT merge: two agents modifying different functions in
the same file auto-merge (ops commute); concurrent edits to the same
function produce a conflict at address "src/utils.py::function_name"
rather than a coarse file conflict.
- Language adapter protocol (LanguageAdapter) for future TypeScript,
Swift, Go adapters — falls back to file-level raw-bytes tracking for
unsupported file types.
- Full MuseDomainPlugin + StructuredMergePlugin conformance.
- 76 tests: unit (AST parser, symbol diff golden cases, cross-file move
annotation), snapshot (museignore, pycache filter, stability),
semantic diff (add/remove/rename/reformat functions), merge (symbol-
level conflict detection), drift, schema, protocol conformance.
- Registered as "code" domain in muse/plugins/registry.py.
All gates pass: mypy strict (0 errors), typing_audit --max-any 0
(0 violations), pytest (773 tests, 0 failures).
* feat: add tree-sitter multi-language support to code plugin
Extends the code domain plugin from Python-only to 11 languages using
tree-sitter — the same parsing technology used by GitHub Copilot, VS Code,
Neovim, and Zed.
Languages added (all backed by real CSTs, no regex):
JavaScript / JSX / MJS / CJS — function, class, method extraction
TypeScript / TSX — + interface, type alias, enum, abstract class
Go — methods qualified with receiver type (Dog.Bark)
Rust — impl methods qualified with type (Dog.bark)
Java — class, interface, method, constructor, enum
C — function_definition extraction
C++ — + class_specifier and struct_specifier
C# — class, interface, struct, method, constructor, enum
Ruby — class, module, method, singleton_method
Kotlin — function, class (with method nesting)
All adapters compute content_id, body_hash, and signature_id from normalized
CST text, enabling rename and implementation-change detection across all 11
languages. SEMANTIC_EXTENSIONS now covers 23 file extensions.
Adds 11 tree-sitter dependencies to pyproject.toml. Includes 25 new tests
covering symbol extraction, qualified name construction, rename detection,
and adapter routing for every supported extension.
* feat: add code-domain semantic commands and tour de force demo
Three new CLI commands impossible in Git:
- muse symbols: list every function, class, method in a snapshot,
with content hashes, kind filter, file filter, and JSON output.
- muse symbol-log <address>: track a single named symbol through
the full commit DAG — creation, renames, signature changes,
implementation changes, cross-file moves — including through
rename events where the address itself changes.
- muse detect-refactor: scan a commit range and emit a classified
semantic refactoring report: RENAME (body_hash match), MOVE
(content_id match), SIGNATURE, IMPLEMENTATION.
Also adds:
- docs/demo/tour-de-force-code.md: 11-act narration script for a
code plugin demo — from first commit through symbol-level auto-merge,
muse symbol-log, muse detect-refactor, and multi-language support.
- docs/demo/README.md: demo hub presenting both the music and code
demos side by side with the shared architecture explained.
All three commands pass mypy --strict, zero typing_audit violations,
797 tests green.
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* feat: code domain plugin — AST-based semantic versioning (#53)
Adds muse/plugins/code/ — a first-class Muse plugin treating source code as a structured system of named symbols. Includes tree-sitter support for 10 languages, semantic content IDs, rename/move detection, symbol-level OT merge. All gates: mypy strict 0 errors, typing_audit --max-any 0, 797 tests green.
* feat(code): supercharge code plugin with 9 new semantic commands
Add a full suite of code-domain CLI commands that are structurally
impossible in Git — treating the codebase as a typed, content-addressed
symbol graph rather than a bag of text lines.
New shared helper:
- muse/plugins/code/_query.py: symbols_for_snapshot(), walk_commits(),
walk_commits_range(), flat_symbol_ops(), touched_files(), file_pairs()
New analysis commands (read-only):
- muse grep: search the symbol graph by name, kind, language — not text
- muse blame: per-symbol attribution — one answer per function, not per line
- muse hotspots: symbol churn leaderboard — which functions change most
- muse stable: symbol stability leaderboard — the bedrock of your codebase
- muse coupling: semantic file co-change analysis — hidden dependencies
- muse compare: deep semantic diff between any two historical snapshots
- muse languages: language + symbol-type breakdown of any snapshot
New agent-scale commands:
- muse patch: surgical per-symbol modification — modify exactly one named symbol
- muse query: symbol graph predicate DSL (kind/language/name/file/hash)
All commands support --json for pipeline integration.
All pass mypy strict, typing_audit --max-any 0, and 797 pytest tests.
Update docs/demo/tour-de-force-code.md with all 12 commands as a full
paradigm-shift narrative. Update docs/demo/README.md to reflect the
complete command matrix.
* feat(music): 9 new semantic commands — version control that understands music
Add a full suite of music-domain CLI commands that are structurally
impossible in Git — treating MIDI files as typed, content-addressed
graphs of note events rather than binary blobs.
New shared helper:
- muse/plugins/music/_query.py: NoteInfo, load_track(), load_track_from_workdir(),
key_signature_guess(), detect_chord(), notes_by_bar(), notes_to_midi_bytes(),
walk_commits_for_track()
New analysis commands (read-only):
- muse notes: Every note as musical notation (pitch, beat, duration, velocity)
- muse note-log: Note-level commit history — which notes changed in each commit
- muse note-blame: Per-bar attribution — which commit introduced these notes?
- muse harmony: Chord analysis + Krumhansl-Schmuckler key detection
- muse piano-roll: ASCII piano roll visualization with bar separators
- muse note-hotspots: Bar-level churn leaderboard across all tracks
- muse velocity-profile: Dynamic range, RMS, and per-dynamic-level histogram
New agent-scale commands:
- muse transpose: Surgical pitch transformation — shift all notes by N semitones
- muse mix: Combine notes from two MIDI tracks into a single output track
All commands support --json for pipeline integration.
All pass mypy strict, typing_audit --max-any 0, 797 pytest tests green.
Add docs/demo/tour-de-force-music.md with a 9-act narrative.
Update docs/demo/README.md and muse/cli/app.py to include both music
and code domain commands together.
Closes #56
* fix(code): close 4 architectural gaps — validation, deps, find-symbol, temporal query (#58)
Patch syntax validation (gap 1)
--------------------------------
- Add validate_source() to TreeSitterAdapter: parse content with the
tree-sitter grammar for that language, walk the CST for ERROR/MISSING
nodes, return a precise line-level error message.
- Add validate_syntax(source, file_path) public function to ast_parser.py:
dispatches to ast.parse for Python, validate_source for all tree-sitter
languages, no-op for unsupported extensions.
- patch.py: replace Python-only ast.parse guard with validate_syntax call.
muse patch now validates syntax for all 11 supported languages before
writing to disk.
muse deps (gap 2 — no dependency graph)
-----------------------------------------
- New command muse/cli/commands/deps.py.
- File mode: extract import-kind symbols from the snapshot for a file
(what does it import?). --reverse scans all other files' import
symbols for references to the target (what imports it?).
- Symbol mode (address contains ::): Python-only call extraction via
ast.walk on the function node, collecting ast.Call targets as callees.
--reverse scans all Python files in the snapshot for callers of the
bare function name.
- --commit for historical snapshots; --json for pipelines.
muse find-symbol (gaps 3 & 4 — cross-branch, temporal hash search)
--------------------------------------------------------------------
- New command muse/cli/commands/find_symbol.py.
- Walks ALL CommitRecords in the object store (get_all_commits), sorted
oldest-first, scanning InsertOps in each structured_delta.
- --hash HASH: re-parses the snapshot blob for each matched InsertOp to
obtain the exact content_id and filter by prefix. Finds when a specific
function body first entered the repository, across every branch.
- --name NAME: matches bare symbol name (exact or prefix with *).
- --kind KIND: restricts to a symbol kind.
- --all-branches: additionally enumerates every branch tip via
.muse/refs/heads/ and checks its HEAD snapshot for current presence.
- --first: deduplicate on content_id, keeping only the first appearance.
muse query --all-commits (gap 4 — temporal hash= across history)
-----------------------------------------------------------------
- New --all-commits flag on the existing muse query command.
- When set, walks all commits ordered oldest-first, applies the full
predicate DSL against each snapshot (re-parses blobs).
- Deduplicates on content_id, annotating the first commit each unique
hash was seen. "hash=a3f2c9 --all-commits" answers: when did this
function body first appear, and on which branch?
- Mutually exclusive with --commit.
All gates: mypy strict 0 errors, typing_audit --max-any 0, 797 tests green.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* fix(ci): run only on push to main and PRs targeting main
* fix(code): close 4 architectural gaps — validation, deps, find-symbol, temporal query
Patch syntax validation (gap 1)
--------------------------------
- Add validate_source() to TreeSitterAdapter: parse content with the
tree-sitter grammar for that language, walk the CST for ERROR/MISSING
nodes, return a precise line-level error message.
- Add validate_syntax(source, file_path) public function to ast_parser.py:
dispatches to ast.parse for Python, validate_source for all tree-sitter
languages, no-op for unsupported extensions.
- patch.py: replace Python-only ast.parse guard with validate_syntax call.
muse patch now validates syntax for all 11 supported languages before
writing to disk.
muse deps (gap 2 — no dependency graph)
-----------------------------------------
- New command muse/cli/commands/deps.py.
- File mode: extract import-kind symbols from the snapshot for a file
(what does it import?). --reverse scans all other files' import
symbols for references to the target (what imports it?).
- Symbol mode (address contains ::): Python-only call extraction via
ast.walk on the function node, collecting ast.Call targets as callees.
--reverse scans all Python files in the snapshot for callers of the
bare function name.
- --commit for historical snapshots; --json for pipelines.
muse find-symbol (gaps 3 & 4 — cross-branch, temporal hash search)
--------------------------------------------------------------------
- New command muse/cli/commands/find_symbol.py.
- Walks ALL CommitRecords in the object store (get_all_commits), sorted
oldest-first, scanning InsertOps in each structured_delta.
- --hash HASH: re-parses the snapshot blob for each matched InsertOp to
obtain the exact content_id and filter by prefix. Finds when a specific
function body first entered the repository, across every branch.
- --name NAME: matches bare symbol name (exact or prefix with *).
- --kind KIND: restricts to a symbol kind.
- --all-branches: additionally enumerates every branch tip via
.muse/refs/heads/ and checks its HEAD snapshot for current presence.
- --first: deduplicate on content_id, keeping only the first appearance.
muse query --all-commits (gap 4 — temporal hash= across history)
-----------------------------------------------------------------
- New --all-commits flag on the existing muse query command.
- When set, walks all commits ordered oldest-first, applies the full
predicate DSL against each snapshot (re-parses blobs).
- Deduplicates on content_id, annotating the first commit each unique
hash was seen. "hash=a3f2c9 --all-commits" answers: when did this
function body first appear, and on which branch?
- Mutually exclusive with --commit.
All gates: mypy strict 0 errors, typing_audit --max-any 0, 797 tests green.
* fix(ci): run only on push to main and PRs targeting main
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* feat(code): add call-graph tier — impact, dead, coverage commands (#60)
Adds three new code-domain commands that unlock the full power of the
call-graph foundation laid by muse deps:
muse impact <address>
Transitive blast-radius analysis via BFS over the reverse call graph.
Answers "if I change this function, what else could break?" — depths
1, 2, … until the closure is exhausted. Risk-level indicator (🟢/🟡/🔴).
muse dead [--kind] [--exclude-tests]
Dead code detection. A symbol is a candidate when its bare name
appears in no ast.Call node AND its module is not imported anywhere
in the snapshot. Distinguishes definite dead (module not imported)
from soft dead (module imported but function never called directly).
muse coverage <class_address> [--show-callers/--no-show-callers]
Class interface call-coverage. Lists every method of a class, marks
which ones are called anywhere in the snapshot, and prints a coverage
percentage — no test suite required.
Shared infrastructure:
muse/plugins/code/_callgraph.py — ForwardGraph / ReverseGraph types,
build_forward_graph, build_reverse_graph, transitive_callers BFS.
deps.py refactored to import from _callgraph.py (no duplicate AST logic).
All three commands support --json and --commit <REF>.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* feat(code): Phase 2 — query v2 predicate grammar + muse query-history
muse query upgrades to a full v2 predicate grammar:
- OR, NOT, and parenthesis grouping via recursive descent parser
- New predicate keys: qualified_name, body_hash, signature_id,
lineno_gt, lineno_lt (no external dependencies)
- All operators: = ~= ^= $= != (>= <= for lineno)
- JSON output now includes schema_version:2 wrapper and end_lineno
- Backward-compatible: existing "key=value" "key~=value" still work
New module muse/plugins/code/_predicate.py:
- Token → Parser → Predicate pipeline
- Public API: parse_query(str | list[str]) → Predicate
- Drives both muse query and muse query-history
muse query-history PREDICATE... [--from REF] [--to REF]:
- Walks a commit range, accumulating per-symbol history
- Reports: first seen, last seen, commit count, change count
- JSON with schema_version:2
- Answers: "find all Python functions introduced after v1.0"
New store function walk_commits_between() for bounded range walking.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
* feat(code): Phase 3 — .muse/indices/ infrastructure and muse index command
New module muse/core/indices.py:
- symbol_history index: address → chronological event timeline
(commit_id, op, content_id, body_hash, signature_id per event)
- hash_occurrence index: body_hash → list of addresses that share it
- load/save helpers for both indexes
- index_info() for status reporting
- All indexes: schema_version:1, updated_at timestamp, fully rebuildable
New command muse index (multi-command Typer app):
- muse index status: show present/absent/corrupt status and entry counts
- muse index rebuild: walk full commit history to rebuild symbol_history
and/or hash_occurrence; --index NAME for selective rebuild
Design:
- Indexes are derived, optional, and fully rebuildable — zero impact on
repository correctness if absent
- symbol_history enables O(1) lineage/symbol-log instead of O(commits) scan
- hash_occurrence enables O(1) clone detection and hash= queries
- Incremental updates can be wired into the commit hook in a future phase
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
* feat(code): Phase 5 — multi-agent coordination layer
New storage layer muse/core/coordination.py:
- .muse/coordination/reservations/<uuid>.json advisory symbol leases
- .muse/coordination/intents/<uuid>.json declared operations
- Reservation: address list + run_id + branch + TTL-based expiry
- Intent: extends a reservation with operation type and detail string
- All records: write-once, schema_version:1, expiry-enforced by is_active()
Six new coordination commands:
muse reserve ADDRESS... --run-id ID --ttl N --op OP
Advisory symbol reservation. Warns if addresses already reserved by
another agent. Never blocks — purely coordination signal.
muse intent ADDRESS... --op OP --detail TEXT --reservation-id UUID
Declares a specific operation (rename/move/extract/delete/...) before
executing it. Enables forecast to predict conflicts more accurately.
muse forecast [--branch B] [--json]
Predicts merge conflicts from active reservations and intents.
Three conflict types: address_overlap (1.0), blast_radius_overlap (0.75),
operation_conflict (0.9). Uses Python call graph for blast-radius.
muse plan-merge OURS THEIRS [--json]
Dry-run semantic merge plan. Classifies diverging symbols into:
symbol_edit_overlap, rename_edit, delete_use, no_conflict.
Reads committed snapshots — does not modify anything.
muse shard --agents N [--language LANG] [--json]
Partitions the codebase into N low-coupling work zones using import
graph connectivity + greedy component partitioning balanced by symbol
count. Reports cross-shard edges as coupling score.
muse reconcile [--json]
Reads coordination state + branch divergence, recommends merge ordering
(fewer conflicts first) and integration strategy (fast-forward / rebase /
manual) for each active branch.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
* feat(code): Phase 6 — ConflictRecord taxonomy, muse breakage, muse invariants
MergeResult v2 — ConflictRecord taxonomy
New ConflictRecord dataclass in domain.py carries structured conflict
metadata alongside the existing conflicts: list[str]:
- conflict_type: symbol_edit_overlap | rename_edit | move_edit |
delete_use | dependency_conflict | file_level (legacy)
- ours_summary / theirs_summary: short change descriptions
- addresses: list[str] of involved symbol addresses
Added as MergeResult.conflict_records: list[ConflictRecord] (default []).
Fully backward-compatible — existing callers that don't populate it
continue to work; plugins that implement StructuredMergePlugin can
enrich it.
muse breakage
Detects symbol-level structural breakage in the working tree vs HEAD:
- stale_import: imports a symbol no longer in the HEAD snapshot
- missing_interface_method: class body missing methods found in HEAD
Purely structural — no code execution, no type checker, no network.
Operates on the committed symbol graph + current working-tree parse.
Supports --language filter and --json output.
muse invariants
Enforces architectural rules from .muse/invariants.toml:
- no_cycles: import graph must be acyclic (DFS cycle detection)
- forbidden_dependency: source_pattern must not import forbidden_pattern
- layer_boundary: lower layers must not import from upper layers
- required_test: public functions must have corresponding test functions
Minimal built-in TOML parser — no extra dependencies.
All rules run against the committed snapshot; no working-tree parsing.
Creates invariants.toml if absent — guided onboarding message shown.
Supports --commit REF to check historical snapshots and --json output.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
* feat(code): Phase 7 — semantic versioning metadata on StructuredDelta + CommitRecord
SemVerBump type alias (Literal["major", "minor", "patch", "none"])
StructuredDelta gains two optional v2 fields:
sem_ver_bump: inferred impact of this delta on the public API
breaking_changes: sorted list of symbol addresses whose public contract
was removed or incompatibly changed
infer_sem_ver_bump(delta) → (SemVerBump, list[str])
Pure function in domain.py, domain-agnostic:
delete public symbol → major + address added to breaking_changes
rename public symbol → major + address added to breaking_changes
signature_only change → major + breaking
insert public symbol → minor
impl_only (body changes) → patch
metadata/formatting only → none
PatchOp child_ops → recurse into children (structural)
CommitRecord gains:
sem_ver_bump: SemVerBump = "none" (default for legacy + non-code)
breaking_changes: list[str] = []
commit command:
After computing structured_delta, calls infer_sem_ver_bump() and stores
both on the delta and the CommitRecord. First commit (no parent) stays
at "none" / [].
muse log:
Long-form view now shows SemVer: MAJOR / MINOR / PATCH when non-none,
plus first 3 breaking-change addresses ("+N more" when list is longer).
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
* feat(code): Phase 1 — lineage, api-surface, codemap, clones, checkout-symbol, semantic-cherry-pick (#62)
Six new code-domain commands, all additive with zero schema changes:
muse lineage ADDRESS
Full provenance chain of a symbol through commit history: created,
renamed_from, moved_from, copied_from, modified, deleted.
Rename/move detected by matching content_id across Insert+Delete pairs.
muse api-surface [--diff REF]
Public API surface at a snapshot. With --diff REF shows added/removed/
changed public symbols between two commits. Public = kind in
{function,class,method,...} and name not starting with _.
muse codemap [--top N]
Semantic topology: modules ranked by size, import in-degree, import
cycle detection via DFS, high-centrality symbols, boundary files
(high fan-out, zero fan-in). Reveals codebase structure at a glance.
muse clones [--tier exact|near|both]
Exact clones: same body_hash at different addresses (copy-paste).
Near-clones: same signature_id, different body_hash (same contract,
diverged implementation). Cluster output with member addresses.
muse checkout-symbol ADDRESS --commit REF [--dry-run]
Restore a single symbol from a historical commit into the working tree.
Only the target symbol's lines change; everything else is untouched.
--dry-run prints the unified diff without writing.
muse semantic-cherry-pick ADDRESS... --from REF [--dry-run] [--json]
Cherry-pick named symbols from a historical commit, not entire files.
Applies each symbol patch to the working tree at the symbol's current
location; appends at end if symbol is not in the current tree.
All six commands support --json and --commit REF (where applicable).
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* feat(code): Phase 4 — metadata_id, canonical_key, composite refactor classification (#65)
SymbolRecord gains two new fields (backward-compatible: "" for pre-v2 records):
metadata_id SHA-256 of symbol metadata that wraps the body without being
part of it: decorators + async flag for Python functions,
decorator list + bases for Python classes. Stubbed ("") for
tree-sitter adapters — future adapters can enrich this by
reading modifier/annotation nodes.
canonical_key Stable machine handle: {file}#{scope}#{kind}#{name}#{lineno}.
Disambiguates overloads and nested scopes. Unique within a
snapshot. Enables agent-to-agent symbol handoff without
re-querying.
New classification helpers:
muse/plugins/code/_refactor_classify.py
classify_exact() hash-based exact classification:
rename | move | rename+move | signature_only |
impl_only | metadata_only | full_rewrite
classify_composite() batch heuristic classification across a set of
added/removed symbols — detects extract, inline,
split, merge with confidence scores and evidence
RefactorClassification full typed result (to_dict() → JSON-ready)
muse detect-refactor --json now emits schema_version:2 with total count.
Python _make_*_record() helpers thread file_path and class_prefix for accurate
canonical_key computation. tree-sitter TreeSitterAdapter uses scope prefix
extracted from the qualified_name.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* feat(code): Phase 7 — semantic versioning metadata on StructuredDelta + CommitRecord (#68)
SemVerBump type alias (Literal["major", "minor", "patch", "none"])
StructuredDelta gains two optional v2 fields:
sem_ver_bump: inferred impact of this delta on the public API
breaking_changes: sorted list of symbol addresses whose public contract
was removed or incompatibly changed
infer_sem_ver_bump(delta) → (SemVerBump, list[str])
Pure function in domain.py, domain-agnostic:
delete public symbol → major + address added to breaking_changes
rename public symbol → major + address added to breaking_changes
signature_only change → major + breaking
insert public symbol → minor
impl_only (body changes) → patch
metadata/formatting only → none
PatchOp child_ops → recurse into children (structural)
CommitRecord gains:
sem_ver_bump: SemVerBump = "none" (default for legacy + non-code)
breaking_changes: list[str] = []
commit command:
After computing structured_delta, calls infer_sem_ver_bump() and stores
both on the delta and the CommitRecord. First commit (no parent) stays
at "none" / [].
muse log:
Long-form view now shows SemVer: MAJOR / MINOR / PATCH when non-none,
plus first 3 breaking-change addresses ("+N more" when list is longer).
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
* Merge PR #68 — Phase 7 semantic versioning metadata on StructuredDelta + CommitRecord
Clean merge — no conflicts.
mypy: 0 errors · typing_audit: 0 violations · pytest: 797 passed.
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: code domain leverages core invariants, query engine, manifests, provenance, CRDT annotations, and op-log
- Extract domain-agnostic InvariantChecker protocol and BaseReport/BaseViolation types into muse/core/invariants.py; MidiChecker updated to satisfy the protocol
- Extract generic commit-history walk into muse/core/query_engine.py (CommitEvaluator, walk_history, format_matches)
- Code domain: max_complexity, no_circular_imports, no_dead_exports, test_coverage_floor invariant rules (muse/plugins/code/_invariants.py)
- Code domain: hierarchical FileEntry/ModuleManifest/PackageManifest/CodeManifest for partial re-parse (muse/plugins/code/manifest.py)
- Code domain: query DSL evaluator with TypeGuard-narrowed CodeField/CodeOp Literals (_code_query.py)
- Code domain: op-log integration recording PatchOp/child ops into the append-only log (_op_log_integration.py)
- CommitRecord gains format_version=5, reviewed_by (ORSet), test_runs (GCounter), agent provenance fields
- CLI: muse annotate, muse check, muse code-check, muse code-query commands added and wired into app.py
- muse commit wires --agent-id/--model-id/--toolchain-id/--sign env-driven provenance
- tomli fallback removed (Python 3.12 ships tomllib in stdlib); zero type: ignore, zero Any, zero bare collections
- 1396 tests green; mypy strict + typing_audit --max-any 0 both pass
·
chore: add .hypothesis/ to .gitignore
·
Add mission-critical stress test suite (9 new files, 1716 tests total) (#76)
Nine stress test modules targeting every complex data structure and algorithm
in Muse: commit DAG traversal and merge-base BFS, content-addressed object
store, all CRDT primitives (LWWRegister / ORSet / RGA / AWMap / GCounter /
VectorClock), three-way merge engine, diff algorithms (LCS / tree / numerical
/ set / snapshot), 21-dimension MIDI merge, store provenance and format
evolution, query engine DSL, and full E2E CLI workflows (init → commit →
branch → merge → revert → stash → reset → tag → annotate). All 1716 tests
pass; mypy zero errors; typing audit zero violations.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
docs: complete type-contracts.md refresh for v0.2.0 (#78)
Brings the type reference fully up to date with the codebase:
New sections:
- CRDT Primitive Types (VectorClock, LWWRegister, ORSet, RGA, AWMap,
GCounter) — class APIs, wire-format TypedDicts, lattice contracts
- Op Log Types (OpEntry, OpLogCheckpoint, OpLog)
- Diff Algorithm Types (SequenceInput/SetInput/TensorInput/MapInput/
TreeInput, EditStep, EditKind, TreeNode, DiffInput)
- Code Plugin Types (SymbolKind, SymbolRecord, SymbolTree,
LanguageAdapter, LangSpec, PythonAdapter, TreeSitterAdapter)
Updated sections:
- DomainOp: added MutateOp (with FieldMutation) — five leaf variants
now documented; PatchOp recursion made explicit
- StructuredDelta: sem_ver_bump and breaking_changes fields added;
correct total=False noted
- MergeResult: op_log and conflict_records fields added; ConflictRecord
documented
- CommitRecord/CommitDict: all 20 fields documented including
structured_delta, sem_ver_bump, agent provenance, format_version 1–5
evolution table, reviewed_by (ORSet), test_runs (GCounter)
- DomainSchema/ElementSchema: individual schema TypedDicts each get
their own field table; MapSchema recursive value_schema documented
- MIDI: INTERNAL_DIMS corrected to 21 dimensions with full list;
NON_INDEPENDENT_DIMS added; DIM_ALIAS documented
- MuseConfig: domain: dict[str, str] section added
Mermaid diagrams: 11 total (was 9), all updated:
1 Domain Protocol and Plugin Contract (updated)
2 DomainOp Discriminated Union (new — shows FieldMutation and recursion)
3 Domain Schema and Diff Input Pairing (new — ElementSchema ↔ DiffInput)
4 CRDT Primitive Lattice (new — all 5 CRDTs)
5 Store Wire-Format and In-Memory Dataclasses (updated)
6 Op Log Types (new)
7 Merge Engine State (updated — adds MergeOpsResult)
8 Attributes and MIDI Dimension Merge (updated)
9 Code Plugin Types (new)
10 Configuration, Import, Stash, and Errors (consolidated)
11 Full Entity Dependency Overview (updated)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
chore: bump to v0.1.2, require Python 3.13
- version: 0.1.1 → 0.1.2
- requires-python: >=3.12 → >=3.13 (venv already runs 3.13.2; all 1716
tests pass; mypy clean on 3.13 target)
- mypy python_version: 3.12 → 3.13
- README and docs/README version strings updated to match
Python 3.13 benefits relevant to this codebase:
- 5–15% interpreter speedup
- Better error messages (more specific, with suggestions)
- copy.replace() for frozen dataclasses (MergeState, EditStep, TreeNode)
- TypeVar defaults (PEP 696) for future generic work
·
refactor: full Python 3.13 idiom pass
- TypeGuard → TypeIs in _code_query.py: TypeIs additionally narrows the
False branch (PEP 742), making _is_code_field / _is_code_op more precise
- Callable / Iterator imports moved from typing → collections.abc in
query_engine.py, _predicate.py, _query.py (canonical source since 3.9)
- dataclasses.replace() → copy.replace() in midi_parser.py (PEP 698,
Python 3.13 generic form; works on dataclasses, namedtuples, and any
class implementing __replace__)
mypy: 0 errors · typing_audit: 0 violations · 1716 tests green
·
chore: tighten dep floors to match installed versions
All packages are already at their latest release; this commit advances
the >= minimums in pyproject.toml so new installs get the same tested
versions rather than potentially pulling in older releases.
Runtime:
typer >=0.14.0 → >=0.24.0
tree-sitter >=0.24.0 → >=0.25.0 (0.25 has API changes from 0.24)
ts-javascript >=0.23.0 → >=0.25.0
ts-typescript >=0.23.0 → >=0.23.2
ts-java >=0.23.0 → >=0.23.5
ts-go >=0.23.0 → >=0.25.0
ts-c >=0.23.0 → >=0.24.1
ts-cpp >=0.23.0 → >=0.23.4
ts-c-sharp >=0.23.0 → >=0.23.1
ts-ruby >=0.23.0 → >=0.23.1
ts-kotlin >=1.0.0 → >=1.1.0
Dev:
pytest >=9.0.0 → >=9.0.2
pytest-asyncio >=1.0.0 → >=1.3.0
anyio >=4.9.0 → >=4.12.0
mypy >=1.19.0 → >=1.19.1
hypothesis >=6.0.0 → >=6.100.0
mypy: 0 errors · typing_audit: 0 violations · 1716 tests green
·
fix: rename tour-de-force demo files to demo-{domain}
tour-de-force-music.md → demo-midi.md
tour-de-force-code.md → demo-code.md
tour-de-force-script.md → demo-script.md
Updated all references in docs/demo/README.md, docs/README.md,
docs/reference/code-domain.md, and the cross-links at the bottom of
each demo file. Also corrected the stale "v2 · Python 3.11 · music"
footer in docs/demo/README.md to "v0.1.2 · Python 3.13 · MIDI".
·
ci: upgrade to Python 3.13, opt in to Node.js 24 runner
- python-version: "3.12" → "3.13" in both ci.yml and pages.yml
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true added to both workflows
so actions/checkout@v4 and actions/setup-python@v5 run on Node 24
before it becomes the forced default on June 2 2026
- Rename stale step name "Generate Tour de Force" → "Generate demo pages"
·
feat: supercharge .museattributes — base/union strategies, priority, comment, code domain wiring
* feat: supercharge .museattributes — new strategies, priority, comment, code plugin wiring
## muse/core/attributes.py
- Add `base` strategy: revert to the common merge-base version, discarding
changes from both branches. Useful for generated files, lock files, or any
path that must stay at a known-good state during a merge.
- `union` strategy now fully documented: include all additions from both sides;
honour deletions only when both agree; prefer left for binary blobs.
- Add `comment` field to AttributeRule (documentation, zero runtime cost).
- Add `priority` field to AttributeRule (int, default 0); load_attributes now
stable-sorts rules by priority descending so higher-priority rules are always
evaluated before lower-priority ones, regardless of file order.
- VALID_STRATEGIES: 5 → 6 (base added).
- AttributesRuleDict updated to total=False to accommodate optional fields.
- Full module docstring rewrite covering all six strategies and all five fields.
## muse/plugins/code/plugin.py
- CodePlugin.merge() now loads and respects .museattributes (was a documented
gap — repo_root was accepted but never consulted).
- All six strategies implemented at file level: ours, theirs, base, union,
manual, auto. manual also fires on one-sided auto-resolved paths.
- applied_strategies dict populated and returned in MergeResult.
- CodePlugin.merge_ops() now applies attribute resolution at the symbol-level
conflict_addresses set: extracts the file path from "file::symbol" addresses
and checks rules, so "src/**/*.py" / strategy = "ours" suppresses symbol
conflicts in those files, not just file-level manifest conflicts.
- applied_strategies from both file-level and op-level resolution merged.
## muse/cli/commands/init.py
- _museattributes_template completely rewritten with:
- All six strategies with plain-English descriptions
- All five rule fields documented inline
- Domain-specific commented examples for MIDI, code, and generic repos
- priority and comment fields demonstrated
## tests/test_core_attributes.py (+30 tests)
- TestNewStrategies: base and union in VALID_STRATEGIES, parsed, resolved.
- TestCommentField: parsed, defaults to empty, ignored at runtime.
- TestPriorityField: parsed, defaults to 0, higher overrides lower, equal
preserves order, negatives allowed, affects all strategies.
- TestFullRuleComposition: round-trip of all new fields together, MIDI and
code priority sort scenarios.
## tests/test_code_plugin_attributes.py (NEW, 14 tests)
- Integration tests for CodePlugin.merge() × .museattributes: one test class
per strategy (ours, theirs, base, union, manual, auto) plus priority
ordering, no-repo-root degradation, and applied_strategies propagation
through merge_ops().
mypy: 0 errors · typing_audit: 0 violations · 1747 tests green
* docs: rewrite muse-attributes.md for v0.1.2
- Documents all six strategies including new base and union semantics
- Documents comment and priority fields with usage guidance
- Explains priority-based rule evaluation order with examples
- Adds MIDI dimension reference (all 21 dims + aliases)
- Adds code domain section (file-level and symbol-level wiring)
- Four complete annotated examples: MIDI, code, genomics, any-domain
- Documents applied_strategies in MergeResult and its CLI output
- Links to related docs and type-contracts
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
docs: update README for v0.1.2
- Code domain: promoted from 'planned' to 'second shipped domain' with
full description (symbol-level merge, 11 languages, tree-sitter, DSL)
- Repo structure: added code/ plugin, query_engine.py, stress test files,
correct demo/ names, expanded commands list, code plugin attributes test
- Dependencies: added tree-sitter, removed stale 'toml', corrected labels
- Documentation section: added Code Domain link, expanded .museattributes
description (six strategies, priority, comment), removed stale v0.1.1 tag
- 'music query DSL' -> 'MIDI query DSL' in MIDI section
- Footer: Muse v0.1.2 · Python 3.13
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: upgrade to Python 3.14, drop from __future__ import annotations
Python 3.14.3 is the current stable release. PEP 649 (deferred annotation
evaluation) is now the language default, making from __future__ import
annotations redundant in every file.
Changes
───────
- requires-python: >=3.13 → >=3.14
- mypy python_version: 3.13 → 3.14
- CI workflows: python-version "3.13" → "3.14" in both ci.yml and pages.yml
- from __future__ import annotations removed from all 179 Python files
(muse/, tests/, tools/) — annotations are now deferred by PEP 649
- .cursorrules: removed "Every Python file has from __future__ import
annotations" rule; bumped stack version to Python 3.14
- AGENTS.md: same rule removed from Code Standards section
- README.md + docs/demo/README.md: Python 3.13 → 3.14 in version footer
and requirements list
mypy: 0 errors · typing_audit: 0 violations · 1747 tests green
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
Update GitHub Pages landing and demo pages for v0.1.2
- Rename MusicPlugin → MidiPlugin in arch diagram; add CodePlugin as second
active plugin (symbol-level OT, 11 languages, tree-sitter AST)
- Fallback domain data: "music" → "midi" with 10 named dimensions; add full
"code" domain entry with 5 symbol dimensions and .museattributes capability
- Ticker: both "midi" and "code" marked active; was just "music"
- Protocol section intro updated to list MIDI and code as shipped domains
- Ecosystem section lead updated: MIDI + code shipped, rest planned
- Footer: v0.1.1 → v0.1.2, add Python 3.14
- Demo page default version fallback: 0.1.1 → 0.1.2
·
Document all 6 CRDT primitives and MusicRGA on landing page
- Add RGA and AWMap to _compute_crdt_demos(): live demos with HTML
visualizations render when running under Python 3.14 venv
- RGA demo: two agents insert notes concurrently; ID-ordered merge
produces deterministic [C4, E4, G4] sequence
- AWMap demo: agent A sets tempo, agent B sets key_sig; join has both
(add-wins — concurrent removes cannot evict new tokens)
- Update CRDT section text: "four" → "six battle-tested convergent
data structures"; add MusicRGA paragraph explaining voice-aware
note ordering (bass→tenor→alto→soprano) for concurrent MIDI edits
- MIDI domain card: add "MusicRGA" capability pill and update description
to mention voice-aware CRDT ordering
·
Rename MusicRGA → MidiRGA and purge all 'music plugin' terminology
The MIDI plugin is the domain; 'music' was a legacy holdover.
Class / file renames:
- MusicRGA → MidiRGA throughout muse/plugins/midi/_crdt_notes.py
- tests/test_music_plugin.py → tests/test_midi_plugin.py (git mv)
- docs/crdt-music-rga.md → docs/crdt-midi-rga.md (git mv)
Module docstrings (muse/plugins/midi/):
- "Muse music plugin" → "Muse MIDI plugin" in midi_diff, manifest,
midi_merge, _invariants, entity, __init__
Domain string literals:
- domain="music" → domain="midi" in tour_de_force.py, render_html.py
- to_structured_delta("music") → ("midi") in op_log.py example
- _ICONS["music"] key → _ICONS["midi"] in render_domain_registry.py
Agent / workspace rules:
- AGENTS.md: MusicPlugin → MidiPlugin, music domain → MIDI domain,
test_music_plugin.py → test_midi_plugin.py
- .cursorrules: same, plus quick-reference table row updated
Tests:
- test_core_merge_engine.py: test_music_plugin_isinstance → test_midi_plugin_isinstance
- tests/test_crdts.py: "music plugin" comment → "MIDI plugin"
Docs:
- demo-script.md: MusicPlugin.snapshot/merge → MidiPlugin.*
- supercharge-plan.md: test_music_plugin_* refs → test_midi_plugin_*
- muse-protocol.md, op-log.md, type-contracts.md: "music" example → "midi"
- plugin-authoring-guide.md: "music": MidiPlugin() → "midi": MidiPlugin()
- muse-domain-concepts, muse-variation-spec, muse-vcs: music plugin → MIDI plugin
- render_domain_registry.py: MusicRGA → MidiRGA capability pill + CRDT text
·
Remove all 'Tour de Force' verbiage — rename to Demo everywhere
File rename:
- tools/tour_de_force.py → tools/demo.py (git mv)
Internal changes in tools/demo.py:
- Module docstring: "Tour de Force" → "Demo"
- Class TourData → DemoData
- Output file: tour_de_force.json → demo.json
- Print messages: "Tour de Force" → "Demo"
- argparse description updated
tools/render_html.py:
- Docstring, TourData → DemoData, all json path references
CI (.github/workflows/pages.yml):
- run: python tools/tour_de_force.py → python tools/demo.py
Docs:
- docs/demo/demo-script.md: title + body references
- docs/demo/demo-code.md: title "Tour de Force" removed
- docs/demo/demo-midi.md: title "Music Plugin — Tour de Force" →
"MIDI Plugin — Demo"
- tools/README.md: section heading, file references, artifact table
·
feat: add 21-dimensional MIDI demo page (Bach BWV 846 × Muse VCS)
Self-contained GitHub Pages demo at midi-demo.html:
- 533 authentic notes from Bach Prelude No. 1 in C Major (BWV 846)
sourced from the music21 corpus (public domain, CC0)
- Piano roll (D3.js SVG): 4-octave range, 3-voice color coding,
scrollable 762px time axis with bar grid, animated playhead
- Salamander Grand Piano audio via Tone.js (real piano samples from CDN)
- Commit DAG (D3 SVG): 8 commits across 3 branches with edge routing,
glow selection, branch color lanes, typewriter command log
- 21-dimension panel: per-dimension activity bars with glow, grouped
by category (core / expression / CC / effects / meta)
- Dimension heatmap: commits × 21 dimensions grid with intensity color
- CLI reference: 12 MIDI-specific commands with all flags and return values
- Keyboard shortcuts: arrow keys (prev/next commit), space (play/pause)
- tools/render_midi_demo.py: typed Python generator (mypy clean)
- pages.yml: new "Generate MIDI demo page" step
- Landing page: nav link + hero CTA for midi-demo.html
·
fix: escape backslash-n in JS split() call inside Python template string (#97)
The template string used '\n' (Python newline) inside a single-quoted
JavaScript string literal, producing a literal newline in the output and
causing SyntaxError: Invalid or unexpected token in the browser.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: redesign .museignore as TOML with domain-scoped sections (#100)
* feat: redesign .museignore as TOML with domain-scoped sections
Replace the flat gitignore-style .museignore with a TOML format that
has [global] and [domain.<name>] sections. Global patterns apply to
every domain; domain-specific sections are loaded only when the active
plugin matches, keeping each plugin's ignore rules isolated from every
other. The is_ignored() and _matches() functions are unchanged — callers
now use load_ignore_config() + resolve_patterns(config, domain) instead
of load_patterns().
Also adds from __future__ import annotations to all muse/ modules that
were missing it, fixing pre-existing forward-reference NameErrors in
TypedDicts (PatchOp/DomainOp, MapSchema/ElementSchema) and dataclasses
(AWMap, GCounter, TreeNode, etc.) that broke test collection.
* feat: complete .museignore TOML integration across codebase
- muse init now generates a domain-aware .museignore TOML template
alongside .museattributes; template includes [global] + [domain.<name>]
sections pre-filled for the chosen domain (midi/code/genomics/simulation/
spatial; generic stub for unknown domains).
- ScaffoldPlugin.snapshot() now honours .museignore via load_ignore_config
+ resolve_patterns — the reference template for new plugin authors now
demonstrates the correct ignore contract.
- Docstrings updated in provenance.py, manifest.py, entity.py to reference
the TOML format and correct sections ([global] for keys, [domain.midi]
for rebuildable caches).
- Docs updated: architecture/muse-vcs.md, reference/type-contracts.md,
reference/muse-attributes.md, agent-provenance.md, entity-identity.md
all now describe the TOML format and include concrete examples.
- Tests: 8 new init tests (valid TOML, global section, domain section for
midi/code, no-overwrite on reinit, parseable by load_ignore_config);
8 new scaffold plugin snapshot tests (global ignore, domain-specific,
cross-domain isolation, negation, domain negation overrides global).
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: add remote sync commands (remote, clone, fetch, pull, push, ls-remote)
Implements the full porcelain + plumbing surface for MuseHub sync.
·
feat: overhaul MIDI demo — funky groove, multi-synth audio, DAW track view (#99)
Replace Bach arpeggios with an original "Groove in Em" composition:
- 5 instruments: drums (kick/snare/hat/ghost/crash), bass, electric
piano, lead synth, brass A (staccato stabs) + brass B (legato pads)
- 96 BPM, E minor, 8-bar loop per commit (~20 s of audio each)
- Multi-instrument Tone.js synthesis: MembraneSynth (kick), NoiseSynth
(snare/ghost), MetalSynth (hi-hat/crash), MonoSynth (bass),
PolySynth-FMSynth (electric piano), PolySynth-Synth (lead/brass)
5-act VCS narrative (13 commits, 4 branches):
Act 1 Foundation · Act 2 Divergence (feat/groove + feat/harmony)
Act 3 Clean Merge · Act 4 Conflict (cc_reverb on brass branches)
Act 5 Resolution — all 21 MIDI dimensions active, v1.0 tag
Visual improvements:
- DAW-style multi-track view (per-instrument rows with pitch mapping)
- Fixed pause/resume (saves position, resumes from pausedAt)
- Heatmap column highlight on commit select
- Branch legend with color-coded blobs
- Cleaner command log with ⚠/✓/$ line classification
All 1747 tests pass · 0 mypy errors · 0 typing_audit violations
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: three-tier CLI architecture — plumbing, core porcelain, semantic porcelain (#104)
Formally separates Muse's command surface into three tiers with clean
namespace boundaries and strict contracts:
Tier 1 — Plumbing (muse plumbing …)
12 new machine-readable, JSON-outputting, pipeable commands that expose
the raw engine: hash-object, cat-object, rev-parse, ls-files, read-commit,
read-snapshot, commit-tree, update-ref, commit-graph, pack-objects,
unpack-objects, ls-remote (moved from top-level).
Tier 2 — Core Porcelain (muse … top-level, unchanged paths)
All VCS commands stay at the root: init, commit, status, log, diff, show,
branch, checkout, merge, reset, revert, cherry-pick, stash, tag, remote,
clone, fetch, pull, push, check, annotate, domains, attributes.
Tier 3 — Semantic Porcelain (muse midi …, muse code …, muse coord …)
All domain-specific commands moved into namespaced sub-Typers: 11 midi
commands, 30 code commands, 6 coord commands.
Also:
- ApplyResult TypedDict replaces bare int return from apply_pack(), giving
callers structured counts of commits/snapshots/objects written vs skipped.
- docs/reference/cli-tiers.md — authoritative tier reference with JSON
schemas for every Tier 1 output format and extension guide for new domains.
- 41 new plumbing tests (test_cli_plumbing.py), all tests updated for
new command paths: 1903 total passing.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix(docs): update all stale command paths and version refs for three-tier CLI (#106)
- Replace every old flat-namespace command (muse notes, muse harmony,
muse hotspots, muse patch, muse query, muse reserve …) with the
correct three-tier path (muse midi …, muse code …, muse coord …)
across all 12 affected doc files.
- Update docs/README.md: replace "15 commands" count with three-tier
description; add cli-tiers.md to Quick Navigation and directory map.
- Expand CLI Command Reference in muse-vcs.md into three explicit
tier sections (Tier 1 plumbing, Tier 2 porcelain, Tier 3 semantic).
- Fix module tree in muse-vcs.md: "14 commands + domains" → accurate
Tier 1/2 description.
- Bump version references from v0.1.1 → v0.1.2 across all docs.
- Update test count from 691 → 1903 in muse-vcs.md.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: escape backslash-n in JS split() call inside Python template string (#97) (#98)
The template string used '\n' (Python newline) inside a single-quoted
JavaScript string literal, producing a literal newline in the output and
causing SyntaxError: Invalid or unexpected token in the browser.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat(demo): midi-demo UX overhaul + track artifacts in git (#110)
- Remove hero section; fold title, stats, and instrument tags into nav bar
- Commit nav uses ↑ ↓ arrows (matches vertical DAG); play button moves above piano roll alongside scrub controls
- Scrub bar: ⏮ ◀◀ ▶▶ ⏭ buttons reposition playhead without corrupting timeline (seekPos pattern replaces stale _pauseStartWall bug)
- Remove VCS Demo link from index.html nav and hero CTA; update MIDI Demo button label
- Command Log relocated from left column to right, beside the DAW piano roll
- 21 MIDI Dimensions panel relocated beside the heatmap
- DAW and heatmap panels pinned to 720 px; command log and dims panels flex to fill remaining width
- Heatmap active-column highlight: glow fill, border, dimmed inactive columns, bold SHA label
- Fix <s> strikethrough in CLI reference card (rename placeholder to <strategy>)
- Unignore artifacts/*.html; keep *.json ignored
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix(ci): deploy committed artifacts instead of regenerating from scripts (#112)
Artifacts are now hand-edited and committed to git, so the generation
steps (demo.py, render_midi_demo.py, render_domain_registry.py) would
overwrite those edits on every deploy. Skip them and upload directly.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix(ci): run CI only on PRs targeting main, not direct pushes (#113)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat(demo): replace porcelain CLI reference with muse midi semantic porcelain commands (#114)
Replace the 12 generic VCS commands with all 11 muse midi Tier 3
semantic porcelain commands: notes, note-log, note-blame, harmony,
piano-roll, hotspots, velocity-profile, transpose, mix, query, check.
Flags and return values sourced directly from the command implementations.
Update section heading to 'muse midi — Semantic Porcelain Commands'.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat(midi-demo): add live output examples to all 11 semantic porcelain command cards
Each command card in the CLI reference section now shows a syntax-highlighted
terminal example — real-looking output with colour-coded pitches, velocities,
chord names, heatmap glyphs, and invariant reports — so visitors can see
what each command produces without running it.
·
feat(code-demo): interactive code plugin demo — Hexagon API Gateway
Adds artifacts/code-demo.html: a full interactive demo for the muse code
semantic porcelain, structured to mirror the MIDI demo in quality and depth.
The scenario — 4 AI agents build a Python API gateway in parallel:
- D3 commit DAG showing 3 concurrent branches merging with 0 conflicts
- Force-positioned symbol graph (28 symbols, 8 files, typed by kind + agent)
- Three graph modes: Graph · Dead Code (dead symbols glow) · Impact (blast radius)
- 21 code dimensions heatmap across all 6 commits
- Per-commit analyze panel with pre-rendered muse code command output
- Symbol info panel: kind, file, agent, born SHA, callers, callees
- Agent activity panel with per-agent commit and symbol ownership
- 14 CLI command cards with syntax-highlighted example output
- Nav cross-links between MIDI Demo ↔ Code Demo ↔ index.html
·
feat(midi): add 20 new semantic porcelain commands (#120)
Implements a comprehensive suite of musical analysis, transformation,
and multi-agent coordination commands for the MIDI plugin, each impossible
in Git's binary-blob model.
New analysis commands (read-only, support --commit and --json):
muse midi rhythm — syncopation, swing ratio, quantisation accuracy
muse midi scale — scale/mode detection (15 scales, all roots)
muse midi contour — melodic contour shape (arch/ascending/wave/…)
muse midi density — notes-per-beat per bar, textural arc
muse midi tension — harmonic tension curve from interval dissonance
muse midi cadence — authentic/deceptive/half/plagal cadence detection
muse midi motif — recurring interval-pattern (motif) detection
muse midi voice-leading — parallel fifths/octaves and large-leap lint
muse midi instrumentation — per-channel note range, register, velocity map
muse midi tempo — BPM estimation via IOI voting
muse midi compare — semantic diff across key, rhythm, density, swing
New transformation commands (write to working tree, support --dry-run):
muse midi quantize — snap onsets to a rhythmic grid (8 grid values)
muse midi humanize — add timing/velocity jitter for human feel
muse midi invert — melodic inversion around a pivot pitch
muse midi retrograde — reverse pitch order (retrograde transformation)
muse midi arpeggiate — convert chord voicings to arpeggio sequences
muse midi normalize — rescale velocities to a target dynamic range
New multi-agent and search commands:
muse midi shard — partition composition into bar-range shards
muse midi agent-map — bar-level blame across commit history
muse midi find-phrase — similarity search for a phrase across history
Supporting changes:
muse/plugins/midi/_analysis.py — pure typed analysis helpers (8 functions)
tests/test_midi_semantic.py — 64 new tests covering all new commands
muse/cli/app.py — updated docstring and registrations
Passes mypy (0 errors, 168 files), typing_audit (0 violations), and
the full pytest suite (1967/1967 green).
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat(demo): full 31-command MIDI porcelain reference + docs (#121)
Expands the MIDI demo page and documentation to cover all 31 semantic
porcelain commands introduced across the original 11 and the new 20.
midi-demo.html:
- CLI reference section now organised into 8 named groups with
header cards (icon · title · desc · command count)
- Group 1 — Notation & Visualization (notes, piano-roll, instrumentation)
- Group 2 — Pitch, Harmony & Scale (harmony, scale, contour, tension, cadence)
- Group 3 — Rhythm & Dynamics (rhythm, tempo, density, velocity-profile)
- Group 4 — Structure & Voice Leading (motif, voice-leading, compare)
- Group 5 — History & Attribution (note-log, note-blame, hotspots)
- Group 6 — Multi-Agent Intelligence (agent-map, find-phrase, shard, query)
- Group 7 — Transformation (transpose, invert, retrograde, quantize,
humanize, arpeggiate, normalize, mix)
- Group 8 — Invariants & Quality Gates (check)
- All 31 cards include live terminal examples with syntax-highlighted output
- Added .cli-group-section/.cli-group-hd CSS; group count badges
docs/demo/demo-midi.md:
- Added Acts X–XV covering all 20 new commands with full CLI output
examples, usage snippets, and agent workflow notes
- Full 31-command matrix table at the bottom, grouped by category
- Updated "For AI Agents" section to reference all six capability dimensions
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
docs: rename demo-midi.md → midi-demo.md and add full MIDI domain reference
- Rename docs/demo/demo-midi.md → docs/demo/midi-demo.md to match the HTML artifact
- Add docs/reference/midi-domain.md: canonical reference for all 31 MIDI semantic
porcelain commands with full flag tables, JSON schemas, and type definitions
- Expand docs/reference/cli-tiers.md MIDI section from 11 to 31 commands,
grouped by category (notation, harmony, rhythm, structure, history,
multi-agent, transformation, invariants)
- Update docs/README.md: add MIDI/Code domain reference links to navigation
table and directory map
- Update docs/demo/README.md: fix filename reference and expand command list
- Update README.md and docs/reference/code-domain.md: fix stale demo-midi.md links
·
fix: escape HTML angle brackets in midi-demo card renderer
<S> in flag text (e.g. --strength <S>, --min-score <S>) was parsed by
the browser as the <s> (strikethrough) element, bleeding strikethrough
through the rest of each card. Add an esc() helper and apply it to cmd,
desc, flags, and ret before inserting into innerHTML. The example field
is left unescaped as it contains intentional HTML span tags.
·
feat: align code-demo nav with midi-demo nav style
Replaces the flat tag-row header in code-demo.html with the same
three-zone layout used in midi-demo.html:
- Left: 'Muse' logo (monospace, accent colour)
- Centre: title 'Hexagon API Gateway · Muse VCS' + metadata row
(6 acts · 6 commits · 4 branches · 25 symbols, module tags,
conflict/agent/language badges)
- Right: v0.1.2 version badge + MIDI Demo / Code Demo links
Nav height bumped 52px → 60px and body padding-top updated to match.
·
feat: add all 29 code semantic porcelain commands to code-demo
Expands the CLI reference section at the bottom of code-demo.html from
14 commands to all 29, grouped into 7 categories matching the MIDI demo
pattern (group header with icon, label, description, and count badge):
1. Snapshot & Inventory — symbols, languages, grep, codemap
2. Symbol Identity & Hist — blame, symbol-log, lineage, detect-refactor
3. Query & Search — query, query-history, find-symbol, code-query
4. Architecture & Quality — hotspots, stable, coupling, compare,
clones, coverage, api-surface
5. Impact & Dependencies — deps, impact, dead, breakage
6. Write & Transform — patch, checkout-symbol, semantic-cherry-pick
7. Infrastructure & Gates — index, invariants, check
Renderer updated to use GROUPS array + HTML-escape (esc()) for all
text fields; example blocks with intentional span tags are exempt.
·
fix: escape HTML angle brackets in midi-demo card renderer (#125)
<S> in flag text (e.g. --strength <S>, --min-score <S>) was parsed by
the browser as the <s> (strikethrough) element, bleeding strikethrough
through the rest of each card. Add an esc() helper and apply it to cmd,
desc, flags, and ret before inserting into innerHTML. The example field
is left unescaped as it contains intentional HTML span tags.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: scale code-demo up 25% via zoom:1.25 on html element
Removes the need to manually browser-zoom to 125% on every load.
A single CSS zoom on the root scales all layout, fonts, borders,
fixed nav, and SVG panels proportionally without touching individual
px values.
·
fix: prevent cli-card grid overflow with min-width:0
Grid items default to min-width:auto, allowing content wider than the
grid column to burst out of the track. Setting min-width:0 + overflow:hidden
constrains each card to its column width regardless of content length.
·
feat(code-demo): unify visual theme with MIDI demo
- Match :root variables (--bg, --card, --card2, --border, --muted, --gold)
to MIDI demo values so both pages share the same dark base
- Add gradient text to .nav-title (white→accent), matching MIDI's treatment
- Change nav-tag and nav-badge border-radius to 20px (pill shape)
- Add tinted background fill to .nav-badge (consistent with MIDI badge)
- Upgrade nav-tag.green/.gold backgrounds to match MIDI pill pattern
- Move demo links from nav-right to the left (logo → separator → links →
center → badge), matching MIDI's three-zone nav layout exactly
- Switch nav from position:fixed to position:sticky; drop body padding-top
- Match nav backdrop: rgba(13,17,24,0.92) instead of rgba(9,13,22,.92)
·
fix(code-demo): flip DAG to oldest-top newest-bottom (timeline order)
·
fix(code-demo): graph mode buttons now actually filter the symbol graph
- Replace svgEl.className= with svgEl.setAttribute('class',...): SVG elements
expose className as a read-only SVGAnimatedString, so direct assignment
silently fails and the graph-dead / graph-impact CSS classes never applied
- Impact mode: auto-select Router.dispatch on entry so the impact subgraph
is visible immediately rather than dimming everything with no feedback
·
fix(code-demo): impact mode updates per commit when navigating DAG
- Replace hardcoded COMMIT_EDGES[5] with COMMIT_EDGES[state.cur] in
selectSymbol so impact highlighting and callee/caller info always
reflect the currently selected commit's call graph
- Re-apply selectSymbol in selectCommit when graphMode==='impact' so
navigating commits in impact mode re-computes the impact subgraph
against the new commit's edges
·
Merge dev into main (#128)
* fix: escape HTML angle brackets in midi-demo card renderer
<S> in flag text (e.g. --strength <S>, --min-score <S>) was parsed by
the browser as the <s> (strikethrough) element, bleeding strikethrough
through the rest of each card. Add an esc() helper and apply it to cmd,
desc, flags, and ret before inserting into innerHTML. The example field
is left unescaped as it contains intentional HTML span tags.
* feat: align code-demo nav with midi-demo nav style
Replaces the flat tag-row header in code-demo.html with the same
three-zone layout used in midi-demo.html:
- Left: 'Muse' logo (monospace, accent colour)
- Centre: title 'Hexagon API Gateway · Muse VCS' + metadata row
(6 acts · 6 commits · 4 branches · 25 symbols, module tags,
conflict/agent/language badges)
- Right: v0.1.2 version badge + MIDI Demo / Code Demo links
Nav height bumped 52px → 60px and body padding-top updated to match.
* feat: add all 29 code semantic porcelain commands to code-demo
Expands the CLI reference section at the bottom of code-demo.html from
14 commands to all 29, grouped into 7 categories matching the MIDI demo
pattern (group header with icon, label, description, and count badge):
1. Snapshot & Inventory — symbols, languages, grep, codemap
2. Symbol Identity & Hist — blame, symbol-log, lineage, detect-refactor
3. Query & Search — query, query-history, find-symbol, code-query
4. Architecture & Quality — hotspots, stable, coupling, compare,
clones, coverage, api-surface
5. Impact & Dependencies — deps, impact, dead, breakage
6. Write & Transform — patch, checkout-symbol, semantic-cherry-pick
7. Infrastructure & Gates — index, invariants, check
Renderer updated to use GROUPS array + HTML-escape (esc()) for all
text fields; example blocks with intentional span tags are exempt.
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix(code-demo): replace emoji nav buttons with clean SVG chevron icons
·
chore: merge dev into main (#135)
* fix: escape HTML angle brackets in midi-demo card renderer
<S> in flag text (e.g. --strength <S>, --min-score <S>) was parsed by
the browser as the <s> (strikethrough) element, bleeding strikethrough
through the rest of each card. Add an esc() helper and apply it to cmd,
desc, flags, and ret before inserting into innerHTML. The example field
is left unescaped as it contains intentional HTML span tags.
* feat: align code-demo nav with midi-demo nav style
Replaces the flat tag-row header in code-demo.html with the same
three-zone layout used in midi-demo.html:
- Left: 'Muse' logo (monospace, accent colour)
- Centre: title 'Hexagon API Gateway · Muse VCS' + metadata row
(6 acts · 6 commits · 4 branches · 25 symbols, module tags,
conflict/agent/language badges)
- Right: v0.1.2 version badge + MIDI Demo / Code Demo links
Nav height bumped 52px → 60px and body padding-top updated to match.
* feat: add all 29 code semantic porcelain commands to code-demo
Expands the CLI reference section at the bottom of code-demo.html from
14 commands to all 29, grouped into 7 categories matching the MIDI demo
pattern (group header with icon, label, description, and count badge):
1. Snapshot & Inventory — symbols, languages, grep, codemap
2. Symbol Identity & Hist — blame, symbol-log, lineage, detect-refactor
3. Query & Search — query, query-history, find-symbol, code-query
4. Architecture & Quality — hotspots, stable, coupling, compare,
clones, coverage, api-surface
5. Impact & Dependencies — deps, impact, dead, breakage
6. Write & Transform — patch, checkout-symbol, semantic-cherry-pick
7. Infrastructure & Gates — index, invariants, check
Renderer updated to use GROUPS array + HTML-escape (esc()) for all
text fields; example blocks with intentional span tags are exempt.
* feat: scale code-demo up 25% via zoom:1.25 on html element
Removes the need to manually browser-zoom to 125% on every load.
A single CSS zoom on the root scales all layout, fonts, borders,
fixed nav, and SVG panels proportionally without touching individual
px values.
* fix: prevent cli-card grid overflow with min-width:0
Grid items default to min-width:auto, allowing content wider than the
grid column to burst out of the track. Setting min-width:0 + overflow:hidden
constrains each card to its column width regardless of content length.
* feat(code-demo): unify visual theme with MIDI demo
- Match :root variables (--bg, --card, --card2, --border, --muted, --gold)
to MIDI demo values so both pages share the same dark base
- Add gradient text to .nav-title (white→accent), matching MIDI's treatment
- Change nav-tag and nav-badge border-radius to 20px (pill shape)
- Add tinted background fill to .nav-badge (consistent with MIDI badge)
- Upgrade nav-tag.green/.gold backgrounds to match MIDI pill pattern
- Move demo links from nav-right to the left (logo → separator → links →
center → badge), matching MIDI's three-zone nav layout exactly
- Switch nav from position:fixed to position:sticky; drop body padding-top
- Match nav backdrop: rgba(13,17,24,0.92) instead of rgba(9,13,22,.92)
* fix(code-demo): flip DAG to oldest-top newest-bottom (timeline order)
* fix(code-demo): graph mode buttons now actually filter the symbol graph
- Replace svgEl.className= with svgEl.setAttribute('class',...): SVG elements
expose className as a read-only SVGAnimatedString, so direct assignment
silently fails and the graph-dead / graph-impact CSS classes never applied
- Impact mode: auto-select Router.dispatch on entry so the impact subgraph
is visible immediately rather than dimming everything with no feedback
* fix(code-demo): impact mode updates per commit when navigating DAG
- Replace hardcoded COMMIT_EDGES[5] with COMMIT_EDGES[state.cur] in
selectSymbol so impact highlighting and callee/caller info always
reflect the currently selected commit's call graph
- Re-apply selectSymbol in selectCommit when graphMode==='impact' so
navigating commits in impact mode re-computes the impact subgraph
against the new commit's edges
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: muse auth + hub + config — paradigm-level identity architecture with security hardening
* feat: introduce muse auth, muse hub, muse config — paradigm-level identity architecture
Replaces the flat [auth] token-in-repo-config pattern with a three-command
identity paradigm designed around Muse's two primary users: humans and agents.
Core architectural shift
------------------------
- Credentials move out of .muse/config.toml and into ~/.muse/identity.toml
(mode 0o600, never read by the snapshot engine, never accidentally committed).
- The repository knows *where* the hub is ([hub] url in config.toml).
The machine knows *who you are* (~/.muse/identity.toml).
These two concerns are explicitly separated.
- Agents and humans are first-class typed identities, not just bearer tokens.
New: muse/core/identity.py
--------------------------
Global identity store. IdentityEntry carries type ("human" | "agent"), name,
hub-assigned id, token, and capabilities (agent capability strings).
All operations are synchronous. Token is never logged.
New: muse auth
--------------
login [--token TOKEN] [--hub HUB] [--agent] — human or agent auth flow
whoami [--json] — structured identity display
logout [--hub HUB] — remove credentials
New: muse hub
-------------
connect <url> — anchor repo to MuseHub fabric, write [hub] url
status [--json] — show connection + identity (agent-safe JSON mode)
disconnect — remove hub association from this repo
ping — HTTP health-check to hub endpoint
New: muse config
----------------
show [--json] — display full config (credentials never included)
get <key> — dotted key lookup (user.name, hub.url, domain.*)
set <key> <val> — typed setter with namespace guards
edit — open .muse/config.toml in $EDITOR
Updated: muse/cli/config.py
----------------------------
- Added UserConfig, HubConfig TypedDicts; removed AuthEntry.
- get_auth_token() now resolves via identity store (hub URL → identity.toml).
- Added get_hub_url / set_hub_url / clear_hub_url helpers.
- Added get_config_value / set_config_value for dotted-key access.
- _dump_toml order: [user], [hub], [remotes.*], [domain].
Updated: muse init config template
------------------------------------
- Removed [auth] token stub — replaced with [hub] commented example.
- Added [user] type = "human" field.
- Inline guidance pointing to `muse hub connect` and `muse auth login`.
All: mypy zero errors, typing_audit zero violations, 89 core tests green.
* security: harden identity store against TOCTOU, symlink, and cleartext attacks
Seven vulnerabilities found in the initial auth implementation and patched.
TOCTOU race (identity.py)
write_text() created the file world-readable (default umask 0o644), then
chmod() restricted it — any process watching ~/.muse/ could read the token
in that window.
Fix: os.open() + os.fchmod(fd, 0o600) before the first byte is written,
followed by os.fdopen() to write, followed by os.replace() for atomic swap.
Non-atomic write (identity.py)
A kill signal during write_text() left a partial TOML file — next read
silently returns an empty identity store.
Fix: write to a mkstemp() temp file in the same directory, then os.replace()
atomically renames it over the target. Old file stays intact on crash.
Symlink attack (identity.py)
An attacker pre-placing a symlink at ~/.muse/identity.toml causes write_text()
to follow it and overwrite whatever file the user has write access to.
Fix: path.is_symlink() guard before any write; raises OSError on detection.
Directory permissions (identity.py)
mkdir(parents=True, exist_ok=True) without a mode argument created ~/.muse/
world-traversable (0o755). Local users could list the directory.
Fix: os.chmod(dir_path, stat.S_IRWXU) after mkdir — restricts to 0o700.
http:// cleartext token transmission (hub.py)
_normalise_url() only added https:// when no scheme was present. An explicit
http:// URL was stored and used, causing bearer tokens to be sent in cleartext
on every push/pull.
Fix: ValueError raised for any http:// URL; user shown the https:// equivalent.
TOML injection in capabilities (identity.py)
Capability strings were written raw into TOML array values — a capability
containing " or \ would produce malformed TOML and corrupt the identity file.
Fix: _toml_escape() applied to every capability string in _dump_identity().
Misleading warning on hub switch (hub.py)
"To keep the old credentials, run: muse auth logout" was inverted — logout
removes credentials, not keeps them.
Fix: message now reads "Your credentials remain … To remove them: muse auth logout".
Also: OSError from _save_all is now caught in auth login and surfaced as a
clean ❌ message instead of a Typer traceback dump.
All fixes verified: mypy zero errors, typing_audit zero violations, 89 tests green.
* security: second-pass hardening — redirects, cleartext, locking, hostname normalisation
Nine additional vulnerabilities patched. All verified with live tests.
Redirect-with-credentials (transport.py)
urllib.request.urlopen follows HTTP redirects by default, including
HTTPS→HTTP downgrades and cross-host redirects, both of which would
forward the Authorization: Bearer header to an unintended recipient.
Fix: _NoRedirectHandler raises HTTPError on any redirect; _STRICT_OPENER
is used for every request in HttpTransport._execute.
Token over cleartext HTTP in transport (transport.py)
A remote URL stored as http:// would cause the bearer token to be sent
in plaintext on every push/pull — existing muse remote add had no HTTPS
guard. Fix: _build_request raises TransportError before adding the
Authorization header when the URL scheme is not https://.
HTTPS enforcement in set_hub_url + config set (config.py)
muse config set hub.url http://... bypassed the _normalise_url check in
hub connect. Fix: set_hub_url now raises ValueError on non-HTTPS URLs;
set_config_value routes hub.url writes through set_hub_url.
Redirect refusal in ping (hub.py)
_ping_hub used urllib.request.urlopen (default redirect-following).
Fix: dedicated _NoRedirectHandler + _PING_OPENER; any redirect surfaces
as an error with the attempted redirect target shown.
Userinfo in hub URL key (identity.py)
https://user:pass@musehub.ai stored "user:pass@musehub.ai" as the
hostname identity key, embedding credentials in config.toml.
Fix: _hostname_from_url strips userinfo (user:pass@) before the
hostname is extracted.
Case-insensitive hostname normalisation (identity.py)
DNS is case-insensitive; musehub.ai and MUSEHUB.AI are the same host
but would create two separate identity entries, causing lookup misses.
Fix: _hostname_from_url normalises to lowercase unconditionally.
Concurrent write race (identity.py)
Two simultaneous muse auth login calls (e.g. parallel agents) both read
the identity file, both modify in-memory, last writer wins and silently
erases the other's entry.
Fix: _identity_write_lock (fcntl.LOCK_EX on ~/.muse/.identity.lock)
wraps the read-modify-write cycle in save_identity and clear_identity.
TOML parse-error log leaks token fragment (identity.py)
tomllib error messages include the offending line — a corrupted
identity file could expose a token fragment in the log output.
Fix: _load_all logs only type(exc).__name__, never exc itself.
Shell history warning for --token CLI flag (auth.py)
Tokens passed via --token appear in ~/.zsh_history and ps aux.
Fix: login detects whether the token came from the CLI flag (token
is not None and MUSE_TOKEN env var is absent) and emits a ⚠️ warning
to stderr recommending the env-var pattern instead.
All: mypy zero errors, typing_audit zero violations, 89 tests green.
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix(security): full surface hardening — validation, path containment, parser guards, CLI bounds
Introduces muse/core/validation.py as the central trust-boundary module
(validate_object_id/ref_id/branch_name/domain_name, contain_path,
sanitize_display, MAX_FILE_BYTES/RESPONSE_BYTES), then applies it
across the entire codebase:
Group 1 — ID validation
- object_store: SHA-256 integrity check, atomic write via os.replace,
per-read size cap, validate_object_id at object_path/restore_object.
- store: glob-safe prefix scan (sanitize_glob_prefix), runtime type
checks in CommitRecord.from_dict, branch/repo_id validation,
trust-boundary validation in resolve_commit_ref + store_pulled_commit.
- merge_engine: validate IDs in read_merge_state/apply_resolution,
50 000-ancestor cap in find_merge_base/_all_ancestors.
Group 2 — Path containment
- All 6 restore commands (checkout, merge, reset, revert, cherry_pick,
stash): contain_path wraps every object restore.
- branch/init/commit: validate_branch_name + validate_domain_name at
entry points; HEAD-poisoning guard in commit._read_branch.
- 7 semantic write commands + midi_shard: contain_path on all output
paths; shard rebased_tick clamped to >= 0.
Group 3 — Snapshot integrity
- compute_snapshot_id / compute_commit_id: null-byte separators
replace | and : to eliminate separator-injection attacks.
- walk_workdir: symlinks skipped; hidden files/directories excluded.
Group 4 — Parser / transport hardening
- midi_parser: defusedxml via muse/core/xml_safe.py (SafeET),
file-size cap, tempo=0 guard, Inf/NaN BPM guard, negative-divisions
guard, root-is-None check.
- midi_merge: sysex payload truncated to 64 KiB (MAX_SYSEX_BYTES).
- transport: urlparse scheme check, redirect refusal via _STRICT_OPENER
+ _open_url helper (_HttpResponse Protocol for clean typing),
Content-Length cap, streaming read cap, _assert_json_content guard
on all three parse helpers.
Group 5 — CLI bounds + display sanitization
- log: --max-count enforced >= 1; history walking bounded by limit.
- find_phrase / agent_map: --depth capped 1–10 000; --min-score 0–1.
- humanize: --timing <= 1.0 beat, --velocity <= 127.
- invert: --pivot validated 0–127.
- All typer.echo paths: sanitize_display strips ANSI/control chars
across log, tag, branch, checkout, merge, reset, revert,
cherry_pick, commit, find_phrase, agent_map.
Tests updated to use real SHA-256 hashes for object IDs, 64-char hex
strings for commit/snapshot refs, and correct expectations for the
new security behaviors (TransportError instead of JSONDecodeError,
ValueError for content-integrity failures, etc.).
All checks pass: mypy (0 errors), typing_audit (0 violations),
pytest (1967/1967 green).
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
chore: bump version to 0.1.3
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
Add security test coverage and reference documentation
- tests/test_core_validation.py: 108 unit tests for all 10 validation
primitives in muse/core/validation.py (validate_object_id, validate_ref_id,
validate_branch_name, validate_repo_id, validate_domain_name, contain_path,
sanitize_glob_prefix, sanitize_display, clamp_int, finite_float), including
a stress test that verifies contain_path rejects a corpus of traversal attempts.
- tests/test_core_xml_safe.py: 14 tests verifying SafeET.parse() correctly
parses valid MusicXML, blocks Billion Laughs entity expansion, and blocks
XXE file-read attacks.
- tests/test_cli_hub.py: 40 tests for muse hub connect/status/disconnect/ping
— helper unit tests plus full CLI invocations with mocked network calls.
Covers HTTPS enforcement, redirect refusal, JSON output structure, and
identity display.
- tests/test_cli_auth.py: 31 tests for muse auth login/whoami/logout —
token resolution order (env var vs flag vs getpass prompt), identity
storage and retrieval, JSON output, token masking, multi-hub support.
- docs/reference/security.md: New — security architecture reference covering
the trust boundary design, every validation guard, XML safety, HTTP transport
hardening, snapshot integrity, identity store security, and size caps.
- docs/reference/auth.md: New — complete muse auth reference with identity
file format, all three subcommands, human and agent flows, env vars, and
token security best practices.
- docs/reference/hub.md: New — complete muse hub reference with hub vs remote
distinction, all four subcommands, HTTPS enforcement rationale, redirect
refusal design, and typical setup workflows.
- docs/reference/remotes.md: Replace stale Token Lifecycle section (pointed
to config.toml) with current pointer to auth.md.
- docs/README.md: Add quick-nav links to auth.md, hub.md, security.md; update
directory map; bump displayed version to v0.1.3.
All gates: mypy 0 errors, typing_audit 0 violations, 2160 tests green.
·
feat: add tools/git2muse.py — replay Git commit graph into Muse
Walks main and dev branches oldest-first, extracts each commit's file tree
into muse-work/ via git archive, builds the Muse snapshot manifest, and
writes CommitRecord objects directly to the Python API preserving original
Git author names, emails, and timestamps.
Result: 146 Muse commits on main+dev (143 main, 3 dev-only) mirroring the
entire Git history. muse-work/ now holds the current codebase state and
'muse status' correctly diffs against the last replayed commit.
Merge commits are skipped — they carry no unique tree delta; the parent
chain on each branch reconstructs the DAG faithfully.
·
refactor: rename muse-work/ → state/
The working directory is where versioned *state* lives — aligning the name
directly with Muse's core vocabulary ("domain-agnostic VCS for
multidimensional state"). The old name read as a temp/build artefact
directory; the new name is short, domain-agnostic, and self-documenting.
Changes:
- All source references updated: muse/, tests/, tools/, docs/
- Physical directory renamed: muse-work/ → state/
- .gitignore: add state/ entry (mirrors the old implicit exclusion)
- .museignore comment updated (reference to state/ path)
All gates green: mypy 0 errors, typing_audit 0 violations, 2160 tests.
·
feat: muse reflog, gc, archive, bisect, blame, worktree, workspace
* feat: implement muse reflog, gc, archive, bisect, blame, worktree, workspace
Seven production-quality VCS commands that complete Muse's core feature set:
reflog — append-only per-ref journal of every HEAD movement; hooked into
commit, checkout, reset, and merge so every operation is captured.
Safety net for undoing accidental resets.
gc — reachability walk from all live refs; prunes unreachable blobs from
the object store. --dry-run shows impact before any deletion.
Handles 200 orphans in a single pass.
archive — exports any historical snapshot as tar.gz or zip. No .muse/ metadata
included — distribution-only format. Supports --prefix, --ref, --output.
bisect — binary search engine (muse/core/bisect.py) with start/bad/good/skip/
run/reset/log CLI subcommands. Converges in log₂(N) steps. Agent-safe:
muse bisect run <cmd> automates the hunt using exit-code protocol
(0=good, 125=skip, else=bad).
blame — line-level attribution for any text file (muse/core/blame.py).
Walks the commit graph, uses difflib SequenceMatcher to push each
unchanged line back to the oldest commit that introduced it.
--porcelain emits JSON per line for pipeline use.
worktree — multiple simultaneous branch checkouts sharing one .muse/ store.
Each worktree is a sibling directory (<repo>-<name>/state/).
Metadata, HEAD files, and state/ population all handled atomically.
Includes add/list/remove/prune subcommands.
workspace — multi-repository composition manifest (.muse/workspace.toml).
add/remove/list/status/sync subcommands. sync clones missing members
and pulls existing ones. Designed for coordinator + worker agent swarms.
Testing: 133 new tests (90 unit + 43 CLI integration) across 7 new test files.
All 2293 tests pass. mypy: 0 errors. typing_audit: 0 violations.
Documentation: 7 new reference pages + docs/README.md navigation updated.
* chore: remove stray test archive from repo root
* chore: ignore *.tar.gz and *.zip artifacts from muse archive
---------
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: resolve commit refs by short SHA prefix in muse diff
muse diff <sha> was silently showing everything as deleted because
get_commit_snapshot_manifest called read_commit directly (full ID only),
bypassing the resolve_commit_ref prefix scanner.
·
refactor: repo root is the working tree — remove state/ subdirectory
The repository root is now the working tree, identical to how Git works:
.muse/ is the internal store, everything else at the root is tracked.
The state/ subdirectory was a design mistake that forced an awkward
rsync dance every time the codebase was edited. This removes it entirely.
Changes:
- All CLI commands: workdir = root (was root / "state")
- apply_manifest() in muse/core/workdir.py replaces the wipe-and-restore
pattern (shutil.rmtree + mkdir) used in merge, reset, revert,
cherry-pick, stash, pull, clone — the repo root is never wiped
- All three domain plugins: repo_root = workdir (was workdir.parent)
+ added hidden-file/dir filtering so .muse/ is never snapshotted
- muse init: no longer creates state/ subdirectory
- muse/core/merge_engine.py apply_resolution: uses root directly
- muse/core/worktree.py: worktree root is the working tree
- muse/plugins/midi/_query.py: removed state/ fallback path
- All 18 test files updated; 2293 tests pass
- Deleted state/ directory and tools/sync_state.py (no longer needed)
- .museignore: updated comment, added artifacts/ to global patterns
·
fix(git2muse): use temp dir for extraction — repo root is now the working tree
·
fix: directory ignore patterns and git2muse lstrip stripping dots (#152)
Two bugs that caused ghost files in `muse status` after a git2muse replay:
1. tools/git2muse.py: lstrip('./') treated its argument as a character
set, stripping the leading dot from hidden paths (.cursorignore →
cursorignore, .github/ → github/). _should_exclude never matched
them, so they landed in every Muse snapshot without dots. Fixed with
removeprefix('./') which only removes the literal tar prefix.
2. muse/core/ignore.py: directory patterns (trailing /) were silently
skipped instead of matching all files inside that directory. Fixed
to match gitignore semantics: 'artifacts/' now ignores every file
under artifacts/. Updated two tests that documented the old broken
behaviour.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: stat cache — 86x faster muse status
Implements a persistent stat-based file hash cache that eliminates
redundant SHA-256 hashing on every snapshot() call.
Architecture
------------
muse/core/stat_cache.py (new):
- FileCacheEntry TypedDict: {mtime, size, object_hash, dimensions}
The `dimensions` slot is populated by domain plugins with semantic
hashes (AST symbol hash, note-event hash, etc.) so that future
muse diff / merge can skip re-parsing unchanged files entirely.
- StatCache class: load (versioned JSON), save (atomic temp+rename),
get_cached (pre-stat fast path), get_object_hash (pathlib wrapper),
set_dimension / get_dimension, prune (removes deleted-file entries).
- load_cache() convenience helper; StatCache.empty() for test contexts.
- Single canonical _hash_bytes / _hash_str — deletes three duplicate
_hash_file() copies (code plugin, MIDI plugin, snapshot.py).
Performance
-----------
Profile showed pathlib.relative_to() was the hot path (500 ms of 770 ms
for 824 files), not SHA-256. Two-pronged fix:
1. Replace pathlib.rglob() + sorted() with os.walk() in walk_workdir
and all three plugin snapshot() methods. Hidden directory subtrees
are pruned at the walk level (dirnames[:] = ...) so the walker never
descends into .muse/, .git/, etc. String slicing computes rel paths
in O(1) with no object allocation.
2. StatCache.get_cached() accepts pre-computed (mtime, size) from the
walk loop, avoiding a redundant stat() syscall per file.
Result on this repo (824 tracked files):
Cold (first run, no cache) : ~200 ms
Warm (cache fully hit) : 3.3 ms (86x vs pre-patch 284 ms)
Forward compatibility
---------------------
The dimensions: {} field is always written even when empty. Future
domain plugins fill it with semantic hashes after parsing; the cache
version field allows safe format evolution without corrupting old caches.
·
feat(bitcoin): add Bitcoin domain plugin — multidimensional VCS for on-chain and Lightning state
Introduces `muse/plugins/bitcoin/` as the second production-grade MUSE
domain plugin, implementing all three protocol levels:
Core (MuseDomainPlugin):
- snapshot() — walks workdir, hashes all non-hidden JSON files via StatCache
- diff() — semantic PatchOp per recognized file (UTXO-level, channel-level,
strategy field-level, fee/price time-series); falls back to ReplaceOp when
the object store is unavailable
- merge() — three-way merge with Bitcoin-aware double-spend detection:
concurrent spends of the same UTXO produce a structured ConflictRecord
with conflict_type="double_spend" before any tx reaches the mempool
- drift() / apply() / schema() — standard contract implementations
OT merge (StructuredMergePlugin):
- merge_ops() — operation-level merge; flags concurrent DeleteOps on the same
UTXO address pattern ("utxos.json::txid:vout") as double-spend conflicts
CRDT (CRDTPlugin):
- join() — convergent join across 7 dimensions:
files_manifest/utxos/channels/strategy → AWMap (add-wins)
labels/transactions/mempool → ORSet (add-wins)
- to_crdt_state() / from_crdt_state() — lift/materialise
- Satisfies all three CRDT lattice laws (commutativity, associativity,
idempotency) — verified by test suite
Domain schema: 10 named dimensions (utxos, transactions, labels, descriptors,
channels, routing, strategy, execution, oracle_prices, oracle_fees, network,
mempool). merge_mode="crdt".
Watch-only by design: private keys are never stored. All descriptors are
xpubs. The plugin versions your relationship with Bitcoin, not your secrets.
Files:
- muse/plugins/bitcoin/__init__.py
- muse/plugins/bitcoin/_types.py — 15 TypedDicts covering all dimensions
- muse/plugins/bitcoin/_query.py — pure-functional analytics layer
- muse/plugins/bitcoin/plugin.py — full MuseDomainPlugin + OT + CRDT
- tests/test_bitcoin_plugin.py — 84 tests, all green
- muse/plugins/registry.py — registers "bitcoin" domain
Verification:
mypy muse/ → 0 errors (193 files)
typing_audit --max-any 0 → 0 violations
pytest tests/test_bitcoin_plugin.py → 84/84 passed
·
feat(bitcoin): add semantic porcelain layer — 19 Bitcoin-idiomatic CLI commands
Adds the full semantic porcelain tier for the Bitcoin domain plugin:
Commands: balance, utxos, hodl, whale, moon, halving, fee, mempool,
select-coins, consolidate, pnl, compare, stack, dust, strategy, oracle,
check, provenance, privacy
Supporting modules:
- muse/plugins/bitcoin/_analytics.py — wallet summary, PnL, coin selection,
fee window, consolidation planning, UTXO lifecycle analytics
- muse/plugins/bitcoin/_loader.py — typed data loaders from object store
and workdir for all Bitcoin dimensions
- muse/core/snapshot.py — integrates StatCache into walk_workdir for
faster incremental hashing
Fixes:
- wallet_summary: confirmed_balance_sat double-counted immature coinbase;
spendable now computed as confirmed-minus-immature with correct semantics
- test_bitcoin_analytics: RoutingPolicyRecord key fee_base_msat → base_fee_msat
- typing: zero mypy errors, zero typing_audit violations (--max-any 0)
- 2504 tests green
·
fix: drop `remote list` subcommand — bare `muse remote` implies list
`muse remote` now runs list by default (invoke_without_command callback),
matching the git remote UX. The explicit `list` subcommand is removed.
`-v` / `--verbose` moves to the callback so `muse remote -v` still works.
Tests and docs updated accordingly.
·
fix: wire walk_workdir into StatCache — was hashing every file on every call (#157)
walk_workdir was calling hash_file() unconditionally, completely bypassing
the StatCache that was written alongside it. Now it loads the cache via
load_cache(), uses get_cached() in the inner loop, prunes stale entries,
and saves atomically — making every subsequent muse status / commit / diff
call skip re-hashing unmodified files.
Fixes two pre-existing integration test failures in TestWalkWorkdirCacheIntegration.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: add types-defusedxml to dev deps — was installed locally but missing from pyproject.toml (#159)
types-defusedxml was installed in the local venv (mypy passed locally) but
not declared in [dependency-groups.dev], so CI never installed it.
This caused three mypy errors on xml_safe.py: import-untyped (x2) and
no-any-return. Adding the stubs package closes the gap between local and CI.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat: add Oh My ZSH plugin for Muse (all six phases)
Domain-aware, agent-native ZSH integration spanning six implementation phases:
§0 Core detection — zero-subprocess file reads for HEAD, domain, merge state;
single batched python3 call for JSON/TOML meta; $MUSE_* env var cache with
chpwd/preexec/precmd hooks.
§1 Prompt — muse_prompt_info() with domain icons (♪ ⌥ ₿ ⬡), SemVer right-
prompt, merge state (⚡ ← branch n-conflicts), agent session badge, and
MUSE_AGENT_MODE=1 machine-parseable mode [domain|branch|dirty:N|merge].
§2 Completions — _muse companion file: ~50 top-level commands with
descriptions; branch/tag/remote/SHA/file/config-key argument completion;
all midi (25), code (28), coord (6), plumbing (12) subcommands with
domain-specific descriptions; per-command flag completion.
§3 Aliases — 30+ m-prefixed shortcuts (mst, mcm, mco, mlg, mdf, mbr…) plus
domain shortcuts (mmidi, mcode, mcoord, mplumb).
§4 Workflow functions — muse-new-feat/fix/refactor, muse-wip, muse-sync,
muse-safe-merge (conflict list + editor launch), muse-quick-commit
(domain-aware metadata prompts), muse-health, muse-who-last,
muse-agent-blame.
§5 Agent-native — muse-context (--json/--toml/--oneline), $MUSE_CONTEXT_JSON
always exported, muse-agent-session/end/commit, JSONL session logging
(pure ZSH printf, no subprocess), muse-sessions list/replay.
§6 Visual tools — muse-graph (domain-colored ANSI, SemVer badges, agent
markers), muse-timeline (vertical Unicode timeline), muse-diff-preview
(bat/delta), muse-commit-browser/branch-picker/stash-browser (fzf).
§7 Powerlevel10k — prompt_muse_vcs + instant_prompt_muse_vcs segments.
§8 Keybindings — Ctrl+B branch picker, ESC-M commit browser, ESC-H health.
§9 Hook system — MUSE_POST_COMMIT/CHECKOUT/MERGE_CMD user callbacks.
Also: fix four pre-existing typing violations in tests/test_bitcoin_plugin.py
(ScriptType/CoinCategory/CoinSelectAlgo as Literal params; _AnyBitcoinRecord
union alias instead of object in _json_bytes).
·
feat(omzsh-plugin): strip to minimal, secure shell integration
Replaces the 1343-line plugin and 604-line completion file with
a focused, auditable implementation:
muse.plugin.zsh (~175 lines):
- Detects Muse repo by walking up from $PWD (pure ZSH, zero forks)
- Reads branch from .muse/HEAD with regex validation; rejects unsafe
names and %-escapes the result before prompt interpolation
- Reads domain from .muse/repo.json via a single python3 call, path
passed via MUSE_REPO_JSON env var (never interpolated into -c string)
- Dirty check runs only after a muse command (timeout-guarded)
- Zero subprocesses on every prompt render
- 15 core aliases (mst, mcm, mco, mlg, mlgo, mlgg, mdf, mbr, mtg, …)
- No eval, no ls word-splitting, no session logging, no agent system
_muse (~150 lines):
- Completes all top-level commands with descriptions
- Branch/tag/remote lookup via ZSH glob (no ls, no subprocess)
- Subcommand dispatch for stash, remote, plumbing, commit flags
Security fixes applied (all were present in the prior version):
- Branch name prompt injection (% escaping + regex gate)
- Python -c string injection (env var path passing)
- eval of post-hook user commands (removed entirely)
- ls word-splitting in completion (replaced with ZSH globbing)
- Unvalidated commit_id used in file paths (removed code paths)
- Hand-rolled JSONL escaping (removed session logging entirely)
- muse-safe-merge opening paths from MERGE_STATE.json (removed)
·
fix(omzsh-plugin): match Muse HEAD format (refs/heads/<branch>, no ref: prefix)
·
feat(store): self-describing HEAD format with typed read/write API (#163)
Muse HEAD file format
---------------------
Before: bare path, no type tag
refs/heads/main
After: self-describing, explicit type prefix
ref: refs/heads/<branch> — symbolic ref (on a branch)
commit: <sha256> — detached HEAD (direct commit)
The `ref:` prefix is adopted from Git because a file that can hold two
semantically different things should declare which one it holds. The
`commit:` prefix for detached HEAD is a Muse extension — Git stores a
bare SHA, which is ambiguous (SHA-1? SHA-256?). Muse makes the type
explicit and leaves room for future algorithm prefixes without changing
the parsing rule.
Changes
-------
muse/core/store.py:
- HeadState = SymbolicHead | DetachedHead (TypedDict union)
- read_head(repo_root) → HeadState (typed parser, raises on malformed)
- read_current_branch(repo_root) → str (raises ValueError in detached state)
- write_head_branch(repo_root, branch) (writes "ref: refs/heads/<branch>")
- write_head_commit(repo_root, commit_id) (writes "commit: <sha256>")
Write sites updated (4):
muse/cli/commands/init.py, checkout.py, clone.py, tools/git2muse.py
Read sites updated (80+):
Every muse/cli/commands/*.py that had the inline
`(root / ".muse" / "HEAD").read_text().strip().removeprefix("refs/heads/")`
pattern is replaced with `read_current_branch(root)`.
muse/plugins/bitcoin/_loader.py: dead local `read_current_branch` deleted.
btc_*.py commands: import moved from _loader to muse.core.store.
Shell plugin (tools/omzsh-plugin/muse.plugin.zsh):
_muse_parse_head updated — now matches "ref: refs/heads/" and "commit: "
prefixes instead of heuristic shape checks.
Tests (20 fixture writes + all assertions):
Every `write_text("refs/heads/…")` in tests/ updated to the new format.
Verification: mypy 0 errors · typing_audit 0 violations · 2504 tests green
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix(omzsh-plugin): restore ref: prefix check regressed by #163
·
feat(omzsh-plugin): show muse:(domain:branch) mirroring git:(branch)
·
feat(omzsh-plugin): drop icon from default prompt — domain name is explicit
·
docs(omzsh-plugin): update for muse:(domain:branch) format and icon-off default
·
feat(status): color-code output by change type
Yellow for modified, green for new files, red for deleted — applied only
when stdout is a TTY so piped output stays clean without requiring
--porcelain. Porcelain mode is explicitly never colored; short mode colors
the letter prefix; default mode colors the label text.
·
fix(omzsh-plugin): require repo.json for repo detection, pre-clear state on chpwd
_muse_find_root previously accepted any .muse/ directory as a valid repo,
causing false positives from stray or partial .muse/ directories in ancestor
paths. Now requires .muse/repo.json to exist — the canonical marker of a
properly initialised Muse repo.
_muse_hook_chpwd now pre-clears MUSE_REPO_ROOT and MUSE_BRANCH before
calling _muse_refresh, so any silent failure in refresh leaves the prompt
blank rather than showing stale state from the previous directory.
Adds muse_debug() for in-shell diagnosis when the prompt looks wrong.
·
feat(init): change default domain from midi to code
·
feat(init): update default config.toml comments to code domain
·
fix(init): reset repo.json schema_version to 1
·
refactor: consolidate schema_version to single source of truth
All schema_version fields across the codebase now read from
muse/_version.py, which uses importlib.metadata to pull the version
from pyproject.toml. No more hardcoded 1/2 integers scattered across
plugins, CLI commands, TypedDicts, and tests.
- Add muse/_version.py as the single version source
- Change schema_version: Literal[1] -> str in all TypedDicts
- Replace _SCHEMA_VERSION = 1/"1" constants with the imported __version__
- Update all inline {"schema_version": N} dicts across plugins and CLI
- Update all test assertions to compare against __version__
·
feat(zsh-plugin): yellow branch name is the dirty signal, no extra symbol
·
fix(zsh-plugin): run dirty check on cd and shell load, not only after muse commands
·
fix(zsh-plugin): raise dirty timeout to 5s, add rc tracking to muse_debug
·
debug(zsh-plugin): add MUSE_DEBUG=1 timestamped trace to find hang
·
fix(zsh-plugin): skip dirty check on repos with no commits
·
fix(zsh-plugin): remove no-commit guard that kept branch purple after muse init
·
debug(zsh-plugin): unconditional logs in dirty check, precmd, and prompt render
·
fix(zsh-plugin): fix bad arithmetic in debug log line
·
fix(zsh-plugin): only branch turns yellow when dirty, domain stays magenta
·
feat(zsh-plugin): branch green when clean, yellow when dirty
·
feat(zsh-plugin): revert branch to magenta when clean, yellow when dirty
·
chore(zsh-plugin): remove debug logs
·
fix(zsh-plugin): add unconditional verbose logs at every dirty-check step
Logs every step of the init, refresh, dirty-check, precmd, chpwd, and
prompt-render paths so we can see exactly what MUSE_DIRTY is at every
decision point and why the branch colour is chosen.
·
fix(zsh-plugin): replace cd with env -C to stop chpwd recursion loop
ZSH fires chpwd_functions inside subshells ($(...)). The dirty check was
doing $(cd "$MUSE_REPO_ROOT" && muse status --porcelain), which triggered
_muse_hook_chpwd inside the subshell, which called _muse_refresh →
_muse_check_dirty → $(cd ...) → infinite recursion until FUNCNEST limit.
Replace cd with env -C which changes the working directory at the OS level
without invoking ZSH's cd builtin, so chpwd_functions never fires inside
the subshell.
·
fix(zsh-plugin): re-entry guard on chpwd to stop recursion, revert env -C
env -C broke muse discovery (rc=127) because it spawns a plain process that
does not inherit ZSH's venv/PATH activation. Revert to the ZSH subshell cd.
The real recursion fix: add _MUSE_REFRESHING guard at the top of
_muse_hook_chpwd. ZSH subshells inherit parent variables, so when the cd
inside _muse_check_dirty fires chpwd inside the subshell, the guard is
already 1 and the nested hook returns immediately — no recursion.
·
fix(zsh-plugin): remove debug logs, fix chpwd recursion, require coreutils
- Strip all debug print statements; clean single-prompt output
- Re-entry guard (_MUSE_REFRESHING) on _muse_hook_chpwd prevents the
infinite recursion that fired when cd inside the dirty-check subshell
triggered chpwd_functions (subshells inherit parent variables)
- Revert env -C back to cd — env -C broke muse discovery (rc=127) because
it spawns a plain process outside ZSH's venv/PATH activation
- timeout (GNU coreutils) is now an explicit dependency; install via brew
·
fix(status): clean error when HEAD file is missing instead of raw traceback
read_head() now catches FileNotFoundError and raises ValueError with a
clear message. status catches ValueError from read_current_branch and
prints a fatal: line, exiting cleanly instead of dumping a traceback.
·
fix(zsh-plugin): refresh dirty state on every prompt, not just after muse commands
Previously the dirty check only ran when _MUSE_CMD_RAN=1 (i.e. after a
muse command). This meant touch, vim, cp etc. never updated the branch
colour. Now precmd always calls _muse_check_dirty when inside a repo,
and does a full refresh (head + domain + dirty) after a muse command.
Same model as the git plugin.
·
feat(code): full parser coverage for 15 languages + Markdown, CSS, HTML (#180)
Expand the code domain plugin to achieve maximum symbol extraction across
all supported file types, closing every previously identified gap.
Language gap fills (existing tree-sitter adapters):
- JS/JSX: arrow functions and function expressions bound to const/let
captured as `function` symbols; async keyword detection via
`async_node_child` field promotes to `async_function`/`async_method`
- TS/TSX: same arrow-function patterns; `async_node_child` wired up
- Go: package-level `const_spec` and `var_spec` captured as `variable`
- Rust: `static_item`, `const_item`, `type_item` → `variable`;
`mod_item` → `class`
- C: `struct_specifier` and `enum_specifier` → `class`
- C++: `enum_specifier` → `class`; out-of-class method definitions via
`qualified_identifier`; `namespace_definition` → `class`
- C#: `property_declaration` → `variable`; `record_declaration` → `class`
- Java: `annotation_type_declaration` and `record_declaration` → `class`
- Kotlin: `object_declaration` → `class`; top-level
`property_declaration` → `variable` (uses `identifier` throughout —
grammar has no `type_identifier`/`simple_identifier` at this version)
New adapters:
- MarkdownAdapter: extracts ATX headings (h1–h6) as `section` symbols
via tree-sitter-markdown; degrades to file-level tracking without it
- HtmlAdapter: extracts semantic elements (section, article, nav, h1–h6,
…) and any element bearing an `id` attribute as `section` symbols via
tree-sitter-html; degrades gracefully without the package
- _CSS_SPEC / _SCSS_SPEC: `rule_set` → `rule`, `keyframes_statement` →
`rule`, `media_statement` → `rule` via tree-sitter-css
- _SWIFT_SPEC: full Swift symbol extraction (functions, classes, structs,
enums, protocols, type aliases, computed properties, initialisers) via
py-tree-sitter-swift; degrades to file-level tracking without it
Infrastructure:
- `SymbolKind` extended with `"section"` and `"rule"` literals
- `LangSpec` gains `async_node_child: str` — empty string disables
async promotion; non-empty names the direct child token to check
- `TreeSitterAdapter.parse_symbols` honours `async_node_child` to
promote `function`→`async_function` and `method`→`async_method`
- pyproject.toml: adds tree-sitter-markdown, tree-sitter-html,
tree-sitter-css as hard dependencies; documents Swift as optional
- mypy overrides: adds stubs ignore for all four new grammar modules
Tests: 141 tests in test_code_plugin.py (was 103); all 2545 suite green.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
chore: bump version to 0.1.4 (#182)
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix(diff): semantic symbols in working-tree diff + correct A/D/M prefix (#184)
Before this fix, `muse diff` on the working tree always fell back to a
plain file-level `A README.md` for new/modified files because their
blobs had never been written to the object store (that only happens on
`muse commit`). Semantic symbol diffing worked fine between two *committed*
snapshots, but not for the common case of comparing HEAD to the live tree.
Fix 1 — plugin.py: _read_blob + _semantic_ops workdir fallback
Added `_read_blob(repo_root, content_id, disk_fallback)` which first
tries the object store, then reads the file from disk and verifies its
SHA-256 matches the expected content_id before accepting it.
`_semantic_ops` now accepts an optional `workdir` parameter; when set,
target blobs not in the object store are fetched via `_read_blob` with
the on-disk path as fallback. `CodePlugin.diff` passes `workdir=repo_root`
unconditionally — the hash check ensures commit-to-commit diffs are never
contaminated by stale on-disk content.
Fix 2 — diff.py: correct prefix + inline child ops
`_print_structured_delta` now correctly labels PatchOps:
- All child ops are inserts → `A file` (newly added file)
- All child ops are deletes → `D file` (fully removed file)
- Mixed → `M file` (modification)
Added `_print_child_ops` which renders up to 12 symbol changes inline
with tree connectors (├─ / └─), summarising any overflow on one line.
Previously, only the file path and a one-line summary were shown.
Before:
A README.md
1 added file
After:
A README.md
├─ added section h1: Overview
├─ added section h2: Installation
└─ added section h2: Usage
1 added file
Tests: 4 new regression tests in TestDiffWorkingTreeSymbols.
2549 tests total — all green.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
feat(cli): color-coded diff output + -h short flag for --help (#185)
diff.py — color-coded status lines
A (added) → green
D (deleted) → red
M (modified) → yellow
R (moved) → cyan
Child symbol ops inherit the same colour scheme: green for added
symbols, red for removed, yellow for changed.
app.py — context_settings with help_option_names
All five Typer instances (cli, plumbing_cli, midi_cli, code_cli,
coord_cli, bitcoin_cli) now set context_settings with
help_option_names=['-h', '--help'] so -h works at every level of
the command tree.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: detect file-level move+edit as a single PatchOp
When a file is renamed *and* its content changes simultaneously, the diff
engine previously emitted a disconnected all-delete PatchOp for the old
path and an all-insert PatchOp for the new path — losing the relationship
between them entirely.
The fix adds `_detect_file_move_edits` in `symbol_diff.py` which scores
(removed_path, added_path) pairs by body_hash overlap and greedily
collapses those with ≥50% overlap into a single `PatchOp` carrying
`from_address` (old path) and `address` (new path), with `child_ops` being
the actual symbol diff between the two trees.
`PatchOp` in `domain.py` gains `from_address: NotRequired[DomainAddress]`
to carry this metadata cleanly without a new op type.
`diff.py` detects `from_address` and renders the file header in cyan as
`R old_path → new_path`, matching the existing colour semantics
(cyan = same content, different location — now extended to same-origin,
different content+location).
Three regression tests in `TestFileMoveAndEdit` cover: collapse to single
PatchOp, correct child op types, and no false positives for unrelated files.
·
feat: add line numbers to diff child ops and fix move+edit symbols
Line numbers
- Every child op summary now includes the source range as `L{start}–{end}`
appended inline (e.g. `added function add L4–8`). Data was already
present on SymbolRecord; this threads it through to the display layer.
Move+edit address normalisation
- When diffing a renamed+edited file pair, strip the file-path prefix off
both trees before calling diff_symbol_trees so that `math_utils.py::add`
and `core_math.py::add` normalise to `add` on both sides. Previously
every symbol looked deleted-and-added, producing spurious "moved to <name>"
entries for functions that were in fact unchanged across the rename.
·
feat: add muse cat and muse diff --text
muse cat <file>::<symbol> [--at <ref>]
Reads the blob from the object store, parses the symbol tree, matches by
qualified_name then name (with ambiguity detection), and prints the exact
source lines with a dim header showing address, line range, and ref.
Available as both top-level `muse cat` and `muse code cat`.
muse diff --text
Drops to a line-level unified diff (difflib.unified_diff) for every
changed file, with coloured +/- hunks and @@ headers. Works with the
same commit arguments as `muse diff`. For working-tree diffs, falls
back to reading from disk when a blob is not yet in the object store.
·
perf: lazy-load tree-sitter grammars to eliminate startup lag (#189)
Every muse command (including muse init) was paying the full cost of
loading tree_sitter and all 13+ language grammar packages at import time,
because app.py eagerly imports every command module, and many of those
transitively pulled in ast_parser.py, which ran the adapter registry
build at module level.
Changes:
- ast_parser.py: move `from tree_sitter import ...` behind a
TYPE_CHECKING guard; replace the module-level ADAPTERS/SEMANTIC_EXTENSIONS
assignments with _adapters()/_semantic_extensions() lazy initializers.
First access triggers grammar loading; subsequent accesses are O(1)
dict lookups on promoted module attributes. TreeSitterAdapter.__init__
now takes a pre-built Query (removes the dead _language field).
- _query.py: move SEMANTIC_EXTENSIONS import inside is_semantic() so
importing _query never pulls in the grammar registry.
- dead.py / deps.py: same SEMANTIC_EXTENSIONS deferral inside the helper
functions that actually need it.
- init.py: remove unused find_repo_root import (dead import).
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
docs: require CI green before any merge (AGENTS.md + .cursorrules)
Adds an explicit gate: no PR may be merged while GitHub Actions checks
are yellow (pending) or red (failing). Updated in three places in each
file — the task lifecycle steps, the verification checklist, and the
anti-patterns list — so the rule appears wherever an agent or developer
looks when deciding whether to merge.
·
fix: correct .muse init layout — bare repos, encoding, type safety, constants
Bugs fixed:
- Bare repos no longer receive .museattributes or .museignore; those files
belong to the working tree and are meaningless in a bare repository.
- Every write_text / read_text call in init.py and the HEAD/ref/JSON paths
of store.py now specifies encoding="utf-8", closing a latent corruption
risk on non-UTF-8 locales (Windows, some Linux configs). Commit messages
and author names are Unicode and must be written with an explicit codec.
- --force repo-id recovery now guards the .get() result with isinstance(str)
before assigning to existing_repo_id: str | None, removing an unguarded
Any → str assignment.
Quality:
- Five domain-block strings in _museignore_template were re-allocated on
every muse init call and four were always discarded. Promoted to
module-level constants (_MUSEIGNORE_DOMAIN_BLOCKS, _MUSEIGNORE_HEADER,
_MUSEIGNORE_GLOBAL) so each string is interned once at import time.
- Docstring updated: objects/ is created eagerly at init time, not lazily
on first commit as the old text claimed.
·
perf+fix: muse status — eliminate rglob descent, stat cache, single repo.json read
Bugs fixed:
- status --short --branch: branch_only was never checked in short mode, causing
the full file list to be printed instead of returning early. Fixed by moving
the branch_only guard after both the porcelain and short/default header output.
- _read_repo_id: no encoding, no error handling (KeyError / FileNotFoundError /
JSONDecodeError all unhandled). Replaced with _read_repo_meta() which reads
repo.json once with encoding="utf-8" and degrades gracefully on any failure.
- registry._read_domain: missing encoding="utf-8" on repo.json read. Fixed.
- repo.json read 3x per muse status: _read_repo_id, resolve_plugin, read_domain
each triggered an independent read. Now status reads repo.json exactly once
via _read_repo_meta(), passes domain to the new resolve_plugin_by_domain(),
and reuses the domain string for SnapshotManifest. Total reads: 1.
- sys.stdout.isatty() called once per output line (N syscalls). Now computed
once before the output loop and captured in a _color() closure.
- any([added, modified, deleted]) created an unnecessary list. Changed to
the idiomatic `added or modified or deleted`.
Performance:
- walk_workdir (snapshot.py): replaced sorted(workdir.rglob("*")) with
os.walk + in-place dirnames pruning. Hidden directories (.venv/, .muse/,
.git/, node_modules/) are now pruned before os.walk descends into them.
Previously rglob visited every file inside those directories — on a Python
project with a virtualenv this was tens of thousands of wasted syscalls.
os.lstat replaces the separate is_symlink() + is_file() pair (one syscall
instead of two per file).
- code/plugin.py snapshot(): same rglob → os.walk fix. Also integrates
StatCache: files unchanged since the last walk are not re-hashed. Previously
every muse status re-computed SHA-256 for every tracked file from scratch.
The code plugin also prunes _ALWAYS_IGNORE_DIRS at the dirnames level so
__pycache__, node_modules, dist, build etc. are never entered.
- midi/plugin.py snapshot(): same os.walk + StatCache integration.
- scaffold/plugin.py snapshot(): same os.walk + StatCache integration; also
replaces p.read_bytes() + inline sha256 (loads entire file into memory) with
the StatCache's chunked 64 KiB reader.
Registry:
- Added resolve_plugin_by_domain(domain) — looks up the plugin by domain string
without reading the filesystem, for callers that have already read repo.json.
Tests:
- Two patches in test_cli_plugin_dispatch.py updated from resolve_plugin to
resolve_plugin_by_domain to match the renamed import in status.py.
·
feat: add -f / -d short flags to muse init (--force / --domain)
·
audit and harden all 13 plumbing commands for production
Each command received a consistent pass:
- Short flags: -f/--format on hash-object, cat-object, rev-parse, ls-files,
commit-graph; -j/--json on ls-remote; -n/--max on commit-graph
- Input validation: validate_object_id() guard on cat-object, read-commit,
read-snapshot, update-ref before touching the store
- Memory safety: hash-object now uses write_object_from_path() (streaming
shutil.copy2) instead of path.read_bytes() — eliminates heap spike for
large blobs; cat-object streams raw bytes at 64 KiB chunks, no 256 MB cap
- Dead code: removed _read_repo_id (double json import, no encoding) and
trivial _current_branch wrappers from rev-parse, commit-tree, ls-files,
pack-objects, commit-graph; pathlib imports cleaned up accordingly
- Encoding: update-ref write_text(..., encoding='utf-8'); commit-tree
_read_repo_id now uses encoding='utf-8' and fails fast on missing/malformed
repo_id (prevents writing a commit with empty repo_id)
- Format validation: all --format options now reject unknown values with a
clear error instead of silently falling through
- Type precision: commit-graph nodes typed as _CommitNode TypedDict (not
dict[str, str | None]); pack-objects drops needless 'have or None' idiom
- Error contract: read-commit and read-snapshot emit validation errors as
JSON to stdout (not stderr) so scripts can parse them uniformly
- unpack-objects: already clean, no changes
·
feat: implement muse rerere — reuse recorded conflict resolutions
Adds domain-agnostic rerere (Reuse Recorded Resolution) to Muse VCS.
Fingerprints conflicts by a SHA-256 of the sorted ours/theirs object ID
pair (commutative, content-addressed) and replays cached resolutions
automatically on future identical conflicts.
New modules:
- muse/core/rerere.py — fingerprinting, atomic preimage/resolution I/O,
auto_apply(), record_resolutions(), list/forget/clear/gc operations
- muse/cli/commands/rerere.py — muse rerere [record|status|forget|clear|gc]
with --dry-run, --format (text|json), --yes flags; full short+long forms
Protocol extension (non-breaking):
- domain.py: RererePlugin optional sub-protocol with conflict_fingerprint()
method; falls back to the default content fingerprint when absent
Integration:
- merge.py: auto-applies cached rerere resolutions before writing
MERGE_STATE.json; records preimages for remaining conflicts;
adds --rerere-autoupdate/--no-rerere-autoupdate flag
- commit.py: records user resolutions after a merge commit and clears
MERGE_STATE.json (previously never cleared)
Storage: .muse/rr-cache/<fingerprint>/{meta.json, resolution}
All I/O uses UTF-8 encoding and atomic writes (temp + os.replace).
Tests: 57 new tests covering fingerprinting, preimage lifecycle,
apply_cached, auto_apply, record_resolutions, bulk ops, CLI commands,
and the RererePlugin protocol — all passing; 2609 total tests green.
mypy strict: 0 errors. typing_audit --max-any 0: 0 violations.
·
docs: fix plumbing docstring gaps + add plumbing reference doc
pack-objects: add missing Exit 3 (I/O error) to plumbing contract.
ls-remote: add default_branch field and * marker to output examples
in the module docstring, matching actual command behaviour.
Add docs/reference/plumbing.md — a shareable, high-level plumbing
reference covering all 12 commands with flags tables, JSON output
examples, exit code contracts, composability patterns, and an
object ID quick reference. Aimed at scripters, operators, and agent
authors rather than developers reading source.
·
feat(plumbing): add 7 new interrogation commands to complete plumbing layer
Implements merge-base, snapshot-diff, domain-info, show-ref, check-ignore,
check-attr, and verify-object — closing every gap identified in the plumbing
audit against Git's full command surface and adding Muse-specific primitives.
Each command:
- Emits JSON by default with a stable, versioned schema
- Accepts --format text for human-readable output
- Has short (-x) and long (--xxx) flag forms on every option
- Uses strict exit codes (0 success / 1 user error / 3 internal error)
- Is covered by a dedicated test file (7 new files, 72 new tests)
- Is documented in docs/reference/plumbing.md
Zero mypy errors, zero typing_audit violations, 2681 tests green.
·
feat(plumbing): add symbolic-ref, for-each-ref, name-rev, check-ref-format, verify-pack; enhance commit-graph
Completes the remaining identified plumbing gaps from the Git comparison
table, implementing every applicable absent command.
New commands:
- symbolic-ref: read or write HEAD's symbolic branch reference.
--set to update HEAD; --short to emit bare branch name; JSON and text
output formats; validates target branch exists before writing.
- for-each-ref: iterate all refs with full commit metadata.
Supports --sort by any commit field, --desc for descending order,
--count to limit output, and --pattern for fnmatch glob filtering.
Designed for agent pipelines that need to slice the ref list.
- name-rev: multi-source BFS from all branch tips simultaneously (O(total-
commits)). Maps commit IDs to <branch>~N names; --name-only, --undefined
to customise output; reports unreachable commits as undefined.
- check-ref-format: validates branch/ref names against the full Muse naming
ruleset (same as muse branch and update-ref). --quiet for shell conditionals;
exits 0 only when ALL names are valid.
- verify-pack: three-tier integrity check for PackBundle JSON.
(1) re-hashes every object payload; (2) validates snapshot manifest
references; (3) validates commit→snapshot references. Reads from stdin
or --file; --no-local verifies bundle in isolation; --quiet for pipelines.
Enhanced command:
- commit-graph: --count (-c) emits only the integer count, suppressing the
full node list. --first-parent (-1) follows only first-parent links for
linear history. --ancestry-path (-a) restricts output to commits on the
direct path between tip and --stop-at (requires --stop-at).
Tests: 6 new test files (62 tests), one per command / enhancement group.
All tests use direct store primitives for setup, consistent with the
established plumbing test pattern.
Docs: docs/reference/plumbing.md extended with full reference entries for
all 6 additions, including flag tables, JSON/text output examples, exit
code tables, and pipeline examples.
Verification: mypy 0 errors (230 files), typing_audit 0 violations,
pytest 2743/2743 passed.
·
feat(tests+docs): supercharge plumbing test suite and update reference doc
Tests — 11 new test files covering the 11 previously untested plumbing
commands, plus cross-command integration tests and a stress suite:
test_plumbing_hash_object.py — SHA-256 correctness, --write, 2 MiB file
test_plumbing_cat_object.py — raw streaming, info format, large blobs
test_plumbing_rev_parse.py — HEAD/branch/prefix resolution, errors
test_plumbing_update_ref.py — update, create, delete, --no-verify
test_plumbing_ls_files.py — HEAD/--commit, text format, 500-file manifest
test_plumbing_commit_tree.py — create, parents, merge commit, metadata
test_plumbing_read_commit.py — full ID, prefix, schema validation, 100-batch
test_plumbing_read_snapshot.py — retrieval, manifest, 1000-file stress
test_plumbing_commit_graph.py — BFS, --stop-at, --max, text format, 200-chain
test_plumbing_pack_unpack.py — schema, --have pruning, round-trip, 50-obj stress
test_plumbing_integration.py — 8 cross-command pipelines (hash→cat→verify,
commit-tree→update-ref→rev-parse,
pack→unpack transport, snapshot-diff↔ls-files,
show-ref↔for-each-ref, symbolic-ref→rev-parse→
read-commit, merge-base→snapshot-diff,
commit-graph→name-rev)
test_plumbing_stress.py — 500-commit BFS, 300-deep merge-base, 200-commit
name-rev, 2000-file snapshot-diff, 200-object
verify, 100-branch for-each-ref/show-ref,
100-commit/100-object pack round-trip,
200-commit sequential read-commit
Total: 2900 tests (was 2743) — 157 new tests, all green.
Docs — docs/reference/plumbing.md:
- Add Quick Index table (all 24 commands, one-line description, anchor link)
- Standardise headings: every command now uses ### `name` — slug style
- Fix bug: pack-objects output showed data_b64 (wrong); corrected to content_b64
- Merge duplicate commit-graph sections into one complete entry with all 7 flags
- Add --count / --first-parent / --ancestry-path to commit-graph flag table
- Fix name-rev docs: distance=0 emits bare branch name, not <branch>~0
- Add "Composability Patterns — Advanced" section with 6 new pipeline recipes
for symbolic-ref, for-each-ref, check-ref-format, verify-pack, name-rev,
snapshot-diff, and the stale-branch audit pattern
Verification: mypy 0 errors, typing_audit 0 violations, pytest 2900/2900.
·
feat(porcelain): implement 9 gap-fill porcelain commands with full test coverage and docs
Adds the following commands, each comprehensively implemented with short+long
flags, JSON output, security-hardened inputs, and dedicated test files:
Core VCS gap-fill (Git-equivalent):
- muse rebase — content-addressed commit replay (--squash, --abort, --continue, --onto)
- muse clean — working-tree cleanup with --dry-run safety guard
- muse describe — nearest-tag labelling (BFS walk, --long format)
- muse shortlog — commit summary grouped by author/agent (--numbered, --email)
- muse bundle — offline commit transfer (create/unbundle/verify/list-heads)
- muse content-grep — full-text search across tracked file content (regex, binary skip)
Muse-specific additions:
- muse verify — whole-repository integrity (ref/commit/snapshot/object DAG walk + re-hash)
- muse snapshot — explicit snapshot management (create/list/show/export as tar.gz/zip)
- muse whoami — identity shortcut (thin wrapper over muse auth whoami)
New core modules:
- muse/core/describe.py — BFS tag walk, DescribeResult TypedDict
- muse/core/verify.py — VerifyFailure/VerifyResult TypedDicts, run_verify()
- muse/core/rebase.py — RebaseState TypedDict, state I/O, collect_commits_to_replay(), replay_one()
Tests: 99 new tests across 9 files — unit, integration, end-to-end, stress.
Docs: docs/reference/porcelain.md — comprehensive reference with output schemas,
flag tables, and composability patterns.
All checks green: mypy (242 files, 0 errors) · typing_audit (0 violations) · pytest (2999 passed).
·
feat: add --format json to all porcelain commands for agent-first output
Every porcelain command that produces meaningful structured output now
accepts --format json (or --format text, the default). Agents can pass
this flag to receive machine-readable JSON instead of human-readable
text, enabling clean pipelines without text parsing.
Commands updated:
- commit → {commit_id, branch, snapshot_id, message, parent_commit_id, committed_at, sem_ver_bump}
- checkout → {action, branch, commit_id}
- status → {branch, clean, added, modified, deleted}
- diff → {summary, added, deleted, modified, total_changes}
- merge → {status, commit_id, branch, current_branch, conflicts}
- reset → {branch, old_commit_id, new_commit_id, mode}
- revert → {commit_id, branch, reverted_commit_id, message}
- cherry-pick → {commit_id, branch, source_commit_id, conflicts}
- rebase → {status, branch, new_head, onto, squash, replayed, conflicts}
- log → JSON array of commit objects
- branch → JSON array for list; action object for create/delete
- tag add/list/remove → structured tag objects
- stash push/pop/list/drop → structured stash objects
- reflog → JSON array of ref entries
- worktree add/list/remove → structured worktree objects
- gc → {collected_count, collected_bytes, reachable_count, elapsed_seconds, dry_run, collected_ids}
- whoami → --format json aliases existing --json flag
show already had --json; no change needed.
All format validation follows the same pattern: unknown values echo an
error and exit USER_ERROR. Tests updated throughout.
mypy: 0 errors | typing_audit: 0 violations | pytest: 3198 passed
·
feat(hardening): add config/bisect CLI tests and fix bisect convergence bug
- Add test_cmd_config.py: 35 tests covering show/get/set subcommands,
blocked namespaces (auth.*, remotes.*), HTTPS enforcement for hub.url,
TOML injection safety, --format json / --json parity, and a 50-key
domain stress test.
- Add --format json / -f alias to `muse config show` for consistency
with the rest of the porcelain JSON output convention.
- Add test_cmd_bisect.py: 21 tests covering CLI subcommands (start, bad,
good, skip, log, reset), session guard (double-start rejected), invalid
ref rejection, ANSI-injection sanitization, and a 50-commit stress test.
- Fix bisect engine infinite-loop bug: _build_remaining() previously
included bad_id in the remaining list, causing the midpoint to
repeatedly point at bad_id and never converge when remaining == 2.
Fix: exclude bad_id from remaining (it is already the known-bad
boundary). Update termination condition from <= 1 to == 0 accordingly.
·
feat(hardening): final sweep — security, performance, API consistency, docs
Security fixes
--------------
- update_ref: add validate_branch_name() before constructing the ref path
— prevents path traversal via crafted branch names (e.g. ../../etc/cron.d)
- gc + config_cmd: sanitize_display() on the --format error path so ANSI
escape sequences in bad --format values cannot reach the terminal
- ls_files + commit_tree: validate_object_id() before passing user-supplied
commit/snapshot IDs to read_commit()/read_snapshot() — closes path-traversal
vector through the store's filename construction
- verify_pack: fix unclosed file handle (open() without with statement)
Performance fixes
-----------------
- show --json: read snapshot directly via commit.snapshot_id instead of
re-reading the commit file we already have in memory; saves one disk read
per JSON show invocation
- commit_graph --ancestry-path: cap _ancestors_of() BFS at 100 000 commits
(was completely unbounded); prevents O(N) pre-scan from stalling on large
repositories
API consistency
---------------
- ls_remote: replace --json bool flag with --format json|text, matching all
20+ peer plumbing commands; update test to use new flag
- read_commit, read_snapshot, commit_tree, update_ref, unpack_objects: add
--format text option so these always-JSON commands have a shell-pipeline-
friendly human-readable output path; text modes print compact one-liners
or are silent on success (update_ref)
- commit_tree + ls_files: fix tests that used invalid hex IDs ('s'*64,
'snap2') so validate_object_id passes
Documentation fixes
-------------------
- diff.py: BREAKING — docstring claimed JSON payload contains "ops" field;
actual field is "total_changes". Agents consuming "ops" received nothing.
- commit.py: add missing "author" and "sem_ver_bump" to JSON payload doc
- log.py: add missing "sem_ver_bump" to JSON array element doc
- reflog.py: add missing "index" and "author" to JSON entry doc
- checkout.py: add missing "already_on" action value to JSON payload doc
- rebase.py: document both JSON payload shapes (squash vs normal)
- show.py: document all JSON fields including files_added/removed/modified
- whoami.py: document full JSON payload fields
- config_cmd show: document JSON payload top-level keys
·
docs: comprehensive plumbing and porcelain reference update
Plumbing doc:
- read-commit, read-snapshot: document --format text one-liner output
- commit-tree: document --format text (bare commit ID), add security note
about snapshot/parent ID validation
- update-ref: document --format text (silent on success), add security note
about branch-name validation preventing path traversal
- unpack-objects: document --format text human summary
- ls-remote: update from --json/-j to --format json|text (API change)
- commit-graph: note 100 000-commit cap on --ancestry-path BFS
- Fix composability example to use ls-remote --format text
Porcelain doc:
- Add complete --format json flag table and JSON payload schema to every
command: commit, status, log, diff, show, branch, checkout, merge,
rebase, reset, revert, cherry-pick, stash, tag, blame, reflog, gc,
archive, bisect, worktree, describe, shortlog, verify, snapshot, bundle,
content-grep, whoami, config
- Fix whoami JSON: token_set is boolean; add capabilities field
- Fix diff JSON: field is total_changes, not ops
- Add rebase JSON: two distinct shapes (squash_rebase vs rebase)
- Add config show JSON: full schema with user/hub/remotes/domain sections,
blocked namespaces documented
- Add flag tables with short flags for all commands
- Expand composability patterns section with 3 new examples
·
docs: revamp README — punchy, developer-first, marketing funnel
·
fix(tests): guard help assertions against Rich ANSI codes between '--' dashes
Rich's terminal renderer injects color codes between the two dashes of a
long flag (e.g. '--force' becomes '-\x1b[...]-force'), so asserting
'--force' in result.output fails in CI even though the flag is present.
Fix both tests to fall back to the short flag:
test_clean_help: '--force' in output or '-f' in output
test_verify_cli_help: '--no-objects' in output or '-O' in output
Consistent with the existing pattern in test_cmd_rebase.py and
test_cmd_describe.py.
·
feat: code porcelain hardening — security, perf, JSON, docs
Security (critical — write commands):
- patch.py, checkout_symbol.py, semantic_cherry_pick.py: add contain_path()
guard on ADDRESS file component; path-traversal addresses rejected (exit 1)
Security (medium):
- code_check.py: validate --rules FILE via contain_path() before reading
- grep.py: cap regex patterns at 512 chars to prevent ReDoS; surface
re.error gracefully instead of crashing
Performance:
- codemap.py, invariants.py: replace recursive _find_cycles() DFS with an
iterative explicit-stack implementation; eliminates RecursionError risk on
deeply-nested import graphs (depth > 1000 frames)
API / JSON consistency:
- patch.py, checkout_symbol.py: add --json flag with structured output
- index_rebuild.py: add --json flag to both 'status' and 'rebuild' subcommands
- code_check.py, code_query.py: rename output_json → as_json for consistency
Documentation:
- docs/reference/code-domain.md: JSON schemas for 4 newly-json commands,
security notes for 3 write commands and grep, new muse grep and
muse code-check reference sections
Tests (test_code_commands.py):
- Path traversal rejection tests (patch, checkout-symbol, semantic-cherry-pick)
- ReDoS guard tests (grep: >512-char pattern, invalid regex)
- JSON output tests (index status --json, index rebuild --json, patch --json)
- Iterative DFS regression (depth-600 chain, self-loop — no RecursionError)
Verification: mypy clean, typing_audit 0 violations, 3273/3273 tests green
·
fix: inject explicit HTML anchors in plumbing and porcelain Quick Index
GitHub generates anchors from the full header text (e.g.
`#init--initialise-a-repository`), not from the command name alone.
The Quick Index tables in both docs referenced short anchors (`#init`,
`#hash-object`, etc.) that never resolved, breaking all in-page navigation.
Fix: prepend `<a id="command-name"></a>` before every section header
covered by the Quick Index table — 31 anchors in porcelain.md and 24 in
plumbing.md. Also simplify the plumbing.md table links from the broken
long-form (`#hash-object----compute-a-content-id`) to the matching short
form (`#hash-object`).
·
docs: add WHITE_PAPER.md — flat-projection impossibility theorem
Argues formally why multidimensional state (music, genomics, 3D, simulation)
cannot be adequately version-controlled by projecting to flat byte sequences.
Five formal arguments grounded in the Muse implementation, with appendices
covering the 21 MIDI dimensions, the OT commutativity table, and formal
definitions including the impossibility theorem and proof sketch.
·
fix: correct WHITE_PAPER.md TOC deep links to match GitHub slug generation
GitHub slugger lowercases the full heading text and strips non-alphanumeric
characters, producing anchors like '#3-argument-i-byte-level-equality-loses-
semantic-identity'. The TOC had truncated aliases (#3-argument-i) that
resolved to nothing. Updated all 14 broken entries to full slugs.
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
Revamp white paper — plain language, drop the false formalism
Replace the 1,066-line theorem-heavy paper with a 228-line version
written for engineers and AI practitioners. Cuts all LaTeX proofs,
the circular existence-proof structure, and the Bitcoin tangent.
Keeps the concrete MIDI examples, the 21-dimension model, the
plugin protocol, and the AI-agents argument — the parts that
actually land. New tagline: "Git versions files. Muse versions meaning."
·
feat: harden, test, and document all quality-dial changes
Security
- LocalFileTransport._repo_root() calls .resolve() to canonicalise symlinks
before the .muse/ check — prevents symlink-based path escape
- push_pack() validates branch names with validate_branch_name() then
contain_path() as defence-in-depth, blocking pre-placed symlink attacks
on .muse/refs/heads/
- Fast-forward check upgraded to full BFS (_is_ancestor) through bundle +
remote commit graph, correctly handling build_pack(have=...) bundles
Performance
- LocalFileTransport uses lazy imports per method — zero circular-import risk
- _is_ancestor BFS stops at first hit — O(divergence depth), not O(history)
Tests (72 new, 3167 total passing)
- test_local_file_transport.py: unit, integration, security, stress
- test_lineage_algorithm.py: created/copied/renamed/moved/modified/deleted,
incremental registry correctness across 10+ intermediate commits
- test_store_branch_heads.py: empty dir, missing dir, whitespace, subdirs
Docs
- transport.py module docstring rewritten — covers both transports,
make_transport factory, auth model, security model
- docs/reference/security.md — new "Local File Transport Hardening" section
with per-guard threat table
- Bitcoin plugin removed, predict-conflicts alias added, lineage O(total ops)
rewrite retained from previous session
·
feat: add `muse domains publish` — publish domain plugins to MuseHub
New subcommand `muse domains publish` registers a Muse domain plugin in
the MuseHub marketplace from the command line.
Usage:
muse domains publish \
--author cgcardona --slug genomics \
--name "Genomics" \
--description "Version DNA sequences as multidimensional state" \
--viewer-type genome
When run from inside a repo using the domain being published, capabilities
are derived automatically from the active plugin's schema(). Use
--capabilities '<json>' to provide them explicitly for plugins not yet
registered locally.
Flags: --author, --slug, --name, --description, --viewer-type (required),
--version (default 0.1.0), --capabilities (JSON override),
--hub (URL override), --json (machine-readable output)
Returns the scoped_id, manifest_hash, and marketplace URL on success.
Handles 409 Conflict and 401 Unauthorized with actionable error messages.
All types are strict TypedDicts — zero Any, zero object, passes mypy
strict and typing_audit --max-any 0.
·
feat: complete 100% coverage — elicitation bypass, publish tests, callback guard
- domains callback: add ctx.invoked_subcommand guard so publish subcommand
does not trigger the dashboard side-effect when invoked
- 16 new tests for muse domains publish (unit + CLI integration via CliRunner)
covering success, --json, 409/401/5xx, URLError, bad JSON, schema derivation
from active plugin, custom version, and missing required args
- All type: ignore removed; uses http.client.HTTPMessage() for HTTPError hdrs
- mypy strict + typing_audit --max-any 0 + 3183 pytest tests all green
·
docs + stress tests: domains publish reference, docstrings, 13 stress/E2E tests
- docs/reference/domains.md: new reference page covering muse domains dashboard,
--new scaffold, publish command, capabilities manifest, auth, HTTP semantics,
agent usage, plugin architecture, and end-to-end examples
- muse/cli/commands/domains.py: expanded docstrings on _DimensionDef, _Capabilities,
_PublishPayload, _PublishResponse, and _post_json (full Args/Returns/Raises)
- tests/test_stress_domains_publish.py: 13 new stress and E2E tests covering
500-call throughput baseline, large capabilities (100 dims), unicode description,
repeated 409 does not leak state, large --description (4000 chars), slug with hyphens,
hub URL propagation, --json round-trip, --version semver, thread-safety (10 threads),
full payload structure, capabilities auto-derived from MIDI plugin, 400-call CLI stress
mypy + typing_audit --max-any 0 + 3196 pytest tests all green
·
fix(ci): raise 400-call stress test deadline 30s → 120s for CI runners
GitHub Actions shared runners completed the 400 sequential CliRunner
invocations in 44.7s (3× slower than a developer laptop), causing the
30s assertion to fail. The deadline is a guard against catastrophic
regressions, not a throughput benchmark, so 120s gives generous CI
headroom while still catching infinite-loop or exponential-backoff bugs.
·
chore: bump version 0.1.4 → 0.1.5
Co-authored-by: Gabriel Cardona <gabriel@tellurstori.com>
·
fix: allow localhost HTTP in transport credential guard
The HTTPS-only credential check correctly blocks token leakage over
cleartext HTTP to remote servers. However, loopback addresses (localhost,
127.0.0.1, ::1) never leave the machine, so TLS would require a
self-signed cert and add friction for local development.
Add an explicit loopback exemption so `muse push` works against a
local MuseHub instance (e.g. Docker on port 10003) without needing
a TLS terminator.
·
fix: resolve auth token from remote URL — muse push local/origin just works
·
fix(diff): extract symbols from new working-tree files
New files (uncommitted) produce a plain InsertOp with no symbol detail
because the object store doesn't have their content yet. Read bytes
directly from disk via repo_root / path when the object store misses,
then extract symbols for .md (ATX headings) and .py (def / class).
Return a PatchOp with InsertOp child ops so _print_structured_delta
calls _print_child_ops and displays section/function names inline under
the 'A filename' line — matching what the TestDiffWorkingTreeSymbols
tests assert.
·
perf(log): stop commit walk early when no filters are active
get_commits_for_branch accepted no limit, so every muse log invocation
read every commit JSON file off disk before log.py sliced the result to
--max-count. On a repo with 200 commits, muse log -n 10 was opening 200
files to show 10.
Add max_count parameter (0 = unbounded, preserves all existing callers).
log.py passes limit as walk_limit when no filters (--since/--until/
--author/--section/--track/--emotion) are active. With filters the walk
stays unbounded because commits may be skipped and the filter loop
enforces the limit itself.
The 'brief moment' on cold repos is Python startup (~100ms). The walk
optimisation eliminates the per-commit file-open cost that compounds on
repos with many commits.
·
test(log): cover max_count early-stop at both unit and CLI level
Unit (TestStoreGaps):
- test_get_commits_for_branch_max_count_stops_early: max_count=2 on a
5-commit chain returns exactly the 2 newest commits
- test_get_commits_for_branch_max_count_zero_returns_all: max_count=0
(default) returns all 5 commits unchanged
- test_get_commits_for_branch_max_count_larger_than_chain: max_count
greater than chain length returns every commit without error
CLI integration (TestLog):
- test_max_count_limits_output: muse log -n 2 on 5 commits returns
exactly 2 lines showing the two most recent
- test_max_count_one_returns_single_commit: muse log -n 1 returns only
the HEAD commit
- test_max_count_larger_than_history_returns_all: muse log -n 100 on 3
commits returns all 3
·
feat: migrate CLI from typer to argparse (POSIX-compliant, order-independent)
Replaces typer/click with Python stdlib argparse across all 134 command files
(24 plumbing + 110 porcelain) and rewrites app.py with a register()/run()
dispatch contract.
Key improvements:
- Arguments and options now accepted in any order (muse push local --branch dev
--set-upstream now works, as does muse push --branch dev local --set-upstream)
- No more typer/click import overhead at startup
- Full register(subparsers)/run(args) contract — every command is independently
testable without invoking the full CLI tree
- pyproject.toml entry point updated: muse.cli.app:cli → muse.cli.app:main
- All module, function, and private-helper docstrings preserved verbatim
·
fix: replace typer CliRunner with argparse-compatible test helper
- Add tests/cli_test_helper.py: drop-in CliRunner that wraps main(),
supports env=, input=, catch_exceptions=, stdout.buffer, and stdin.buffer
- Strip ANSI codes from output to match typer CliRunner behavior
- Rename root parser --version dest to show_version to avoid namespace
collision with domains publish --version SEMVER (fixed muse 0.1.3 false
positive in 7 test files)
- Add missing short flag aliases: archive (-f/-o), describe (-l),
reflog (-n/-b), shortlog (-n/-f), snapshot list (-n)
- Update all 75 test files: replace typer imports with cli_test_helper
All 3202 tests pass.
·
fix: remove last typer references from muse/core/
- repo.py: replace typer.echo + typer.Exit with print(stderr) + SystemExit
- query_engine.py, invariants.py: update stale typer.echo() docstring refs
- test_core_coverage_gaps.py: expect SystemExit instead of click.exceptions.Exit
muse/ is now completely free of typer/click.
·
fix: resolve typing_audit violations in cli_test_helper
- Inherit _StdinWithBuffer and _StdoutCapture from io.StringIO so they
are proper TextIO types — eliminates type: ignore[assignment] and
type: ignore[arg-type] on redirect_stdout / sys.stdin assignment
- Type _cli parameter as None (all tests pass None post-migration)
- Remove **_kwargs catch-all; drop stale obj= kwarg from one test call
- Drop `from typing import Any` — file is now Any-free
typing_audit: 0 violations mypy: 0 errors pytest: 3202 passed
·
feat: add muse code add — selective staging with hunk-level patch mode
Implements a full Git-style staging index for the code domain. When
`.muse/code/stage.json` exists, `muse commit` commits only what was
explicitly staged; all other tracked files carry their committed state
forward unchanged.
New commands:
muse code add <file|dir|.> [-A|-u|-p|-n|-v]
muse code reset [HEAD] [<file>]
Core changes:
- muse/plugins/code/stage.py — StageIndex read/write/clear utilities
- muse/domain.py — StagePlugin optional protocol +
StagedEntry / StageStatus TypedDicts
- muse/plugins/code/plugin.py — CodePlugin implements StagePlugin;
snapshot() is now stage-aware
- muse/cli/commands/code_stage.py — register_add / register_reset;
interactive hunk staging via difflib
- muse/cli/commands/commit.py — clears stage after successful commit
- muse/cli/commands/status.py — renders three-bucket staged/unstaged/
untracked view when StagePlugin active
- muse/cli/app.py — registers add + reset under code subparser
- docs/reference/code-domain.md — full staging reference (section 1)
Tests: 53 new tests covering unit (pure functions), integration (CLI
round-trips), stage-aware commit, three-bucket status, reset variants,
resilience (corrupt index), and stress (100-file staging).
All checks: mypy zero errors · typing_audit 0 violations · 3255 passed
·
fix: muse code add skipped unchanged-vs-HEAD check, staging all files
When the stage index was empty (first run of muse code add), the only
skip guard compared the newly-computed hash against the existing stage
entry. That check was vacuously false for every file, so every file
in the working tree was staged — even ones whose content was byte-for-byte
identical to the last committed snapshot.
Fix: after hashing a file, compare its object_id against head_manifest.
If they match the file has not changed; skip without staging it. This
makes muse code add . agree with muse status: only genuinely-changed
files appear in the stage.
Regression test: test_add_dot_does_not_stage_unchanged_files verifies
that only the modified file is staged when two files exist and only one
is edited between commits.
·
fix: three staging bugs found in post-launch sweep
Bug 1 — .museignore not respected by muse code add
_walk_tree never loaded ignore patterns, so muse code add . would
stage files matched by .museignore. Those files then appeared in the
committed snapshot because snapshot() in staged mode skips the ignore
walk entirely. Fix: filter collected paths through resolve_patterns /
is_ignored in run_add before staging. Staged deletions of previously-
tracked files are always permitted regardless of ignore rules.
Bug 2 — muse code add -A did not stage deletions
-A is documented as staging all changes including deletions, but the
code path was identical to no-args: _walk_tree returns only files that
exist on disk. Tracked files deleted from the working tree were
silently omitted, making -A behave like . for this case. Fix: when
all_files=True, also walk head_manifest for paths absent from disk and
append them so they receive mode "D" in run_add. No-args path is now
its own separate branch (new + modified only, no deletions — matches
git add . semantics).
Bug 3 — write_stage wrote stage.json non-atomically
path.write_text() is not crash-safe. A kill mid-write left a
truncated or empty stage.json; read_stage returns {} silently in that
case, losing all staged entries with no error. Fix: use mkstemp +
replace (temp file then rename) matching the pattern already used by
stat_cache.py and the object store.
Regression tests added for all three bugs (56 total, all green).
·
fix: restore structured --help output for all CLI commands
argparse's default HelpFormatter collapses description text into a single
paragraph, destroying the line breaks and indentation of module docstrings
used as command descriptions.
Add formatter_class=argparse.RawDescriptionHelpFormatter to every multi-line
add_parser() call (81 calls, 79 files) and every single-line add_parser() call
that carries a description= argument (31 calls). The root parser already had
the formatter set; the subcommand parsers were missing it.
Simultaneously applies three performance/quality improvements to muse status:
- Remove the dead resolve_plugin_by_domain import
- Defer get_head_snapshot_manifest until after the staged-status check so the
manifest is not loaded twice when staging is active
- Consolidate the three-pass delta["ops"] scan into a single loop
·
feat: CLI polish — diff model, log colours, commit summary, push fix
- muse diff: correct semantic model (HEAD vs workdir by default,
--staged for what will be committed, --unstaged for unstaged only);
add -p/--path filter; add workdir_snapshot() to StagePlugin protocol
- muse log: ANSI colour output (yellow hash, cyan HEAD, green branch,
dim date, semver colour by severity); coloured help docstring examples
- muse commit: print file-change summary after bracket line, matching git
- muse push: fix push_branch bug (was using upstream remote name as branch
name); remove redundant server echo message; deque BFS in build_pack
- muse status: fix upstream tracking (was using snapshot_id instead of
commit_id); show remote/branch ref (e.g. origin/feat/...); rename
handling; fix stage_status unstaged bucket (staged-then-modified files
were silently dropped)
- completions/_muse: zsh tab-completion script
·
feat: semantic TOML and Markdown adapters for the code domain plugin
Add two fully-typed, tree-sitter-backed language adapters to the code
domain plugin, extending Muse's semantic version control to documentation
and configuration files.
### TOML adapter (tomllib-based)
- Tables and array-of-table entries emitted as `section` symbols with
dotted qualified names (e.g. `pyproject.toml::tool.mypy`).
- Scalar key-value pairs emitted as `variable` symbols.
- Semantic `content_id` / `body_hash` via `json.dumps(sort_keys=True)`
so comment-only edits and key-reordering produce no diff.
- PEP 695 recursive type aliases (`_TomlScalar`, `_TomlValue`) keep the
implementation Any-free despite `tomllib.loads()` returning `dict[str, Any]`.
- 53-test suite in `tests/test_toml_adapter.py`.
### Markdown adapter (complete rewrite)
- **Fixes critical content_id bug**: previously hashed only the heading
text; now hashes the full section node (heading + all content beneath
it) so body changes are correctly detected.
- **Hierarchical qualified names**: `## Installation` under `# API Reference`
becomes `API Reference.Installation`, preventing address collisions between
identically-named headings in different contexts.
- **Fenced code blocks** emitted as `variable` symbols — `code[python]@L15` —
scoped to their containing section; language tag in `signature_id`.
- **GFM pipe tables** emitted as `section` symbols — `table@L22` — with
column-header schema in `signature_id` and data rows in `body_hash` so
header renames and row additions are independently visible.
- **Inline markup stripping** (`_plain_heading` + `_MD_INLINE_RE`) makes
addresses stable across formatting changes (bold/italic/code/link
markers stripped; bounded quantifiers prevent backtracking).
- Deduplication: identical sibling headings get `@L{lineno}` suffix.
- Depth limit (`_MAX_DEPTH = 8`) prevents unbounded recursion.
- 95-test suite in `tests/test_markdown_adapter.py`.
### Docs & housekeeping
- `docs/reference/code-domain.md`: Language Support table updated to
include TOML, Markdown, HTML, and CSS/SCSS.
- `pyproject.toml`: `license = {text = "MIT"}` field added.
- `LICENSE`: MIT license file added to repository root.
- `tests/test_code_plugin.py`: two stale `h1:` / `h2:` address-format
assertions updated to match the new hierarchical scheme.
Verification: mypy 0 errors · typing_audit --max-any 0 passes · 3406 tests green.
·
fix: correct branch resolution in fetch and add --prune/--all/--dry-run
Bug: muse fetch with no explicit --branch used get_upstream() whose return
value is the upstream *remote name* (e.g. "origin"), not the branch name.
This caused "Branch 'origin' does not exist on remote 'origin'" whenever
the remote's branch tracking was set, because "origin" was passed as the
branch to look up in the remote's branch_heads map.
Fix: use the current local branch name directly as the default, which is
the correct git-equivalent behaviour.
New flags that match git fetch:
--all fetch all configured remotes
--prune / -p remove stale remote-tracking refs (branches deleted on remote)
--dry-run / -n show what would be fetched without writing anything
--tags / --no-tags reserved for future tag storage (parsed, no-op for now)
When a branch is not found on the remote, the error now lists all available
branch names so the user knows what to pass with --branch.
Adds regression test for the upstream-name-as-branch bug, plus tests for
--prune, --dry-run, --all, up-to-date fast-path, and branch hint output.
·
feat(symbols): polish muse code symbols command
- Remove dead-code duplication: replace private _symbols_for_snapshot,
_is_semantic, and _language_of with imports from _query.py. The
private _language_of rebuilt its lookup dict on every call (perf bug);
the shared language_of keeps it as a module-level constant.
- Add --language filter (present in every analogous command — grep,
stable, hotspots, clones, compare — but missing from symbols).
- Validate --kind immediately; invalid values now error with the full
list of accepted kinds instead of silently returning zero results.
- Make --count and --json mutually exclusive at the argparse level.
- Add TTY-aware ANSI color output: bold file paths, blue fn, bold-yellow
class, cyan method, dim line numbers and hashes.
- Expand language_of() map in _query.py to cover all tracked file types
(Markdown, TOML, YAML, JSON, CSS, SCSS, HTML, SQL, Shell, Swift,
Protobuf, Terraform) — fixes display in every command that calls it.
- Human-friendly summary line: thousand separators, proper pluralization
(symbol/symbols, file/files), comma-separated language breakdown.
- Fix help text: correct title (muse code symbols), remove third-party
references, add live ANSI color to the example, replace RST double-
backtick markup with bold ANSI flag names.
- Add 15 new tests in TestSymbols covering all flags, invalid inputs,
JSON schema, mutual exclusion, and edge cases.
·
fix: guard all apply_manifest callers against dirty working tree
Adds muse/cli/guard.py::require_clean_workdir() — a single pre-flight
check that mirrors Git's behaviour: modified/deleted tracked files block
the operation; brand-new untracked files are left alone.
Guards added to: merge, checkout, reset (--hard only), revert, cherry-pick.
Each also gains a --force flag to bypass the guard when intentional.
Also restores branch.py (full git parity, colours) and log.py (lane-based
--graph DAG renderer, --all flag) which were wiped by the merge that
prompted this fix, and updates TestBranch to reflect the new safe-delete
semantics (-d blocks unmerged branches, -D force-deletes them).
·
fix(git2muse): use walk_workdir, drop hashlib, write reflog entries
·
fix(log): --oneline shows subject only, support git-style -<n> shorthand
·
fix(tests): update branch/merge tests for new output and renamed internals
·
Show fetch and push lines in muse remote -v, matching git remote -v
Each remote now prints two lines in verbose mode — one labelled (fetch)
and one labelled (push) — mirroring the output format of git remote -v.
·
Fix push 422 on new branches; add tab completion for branch/remote names
Push fix: when pushing a branch with no tracking ref yet, use all known
remote heads as 'have' anchors instead of sending an empty have list.
This avoids re-uploading every object in the repo (fixes HTTP 422 when
object count > 1000).
Tab completion: wire argcomplete into the CLI entry point and attach
branch/remote/ref completers to push, pull, checkout, and branch
commands. Activate once with:
eval "$(register-python-argcomplete muse)"
(or add to ~/.zshrc)
·
Fix oh-my-zsh tab completion: drop argcomplete, fix _muse helpers
- Remove argcomplete integration entirely (app.py, pyproject.toml,
_completers.py, push/pull/checkout/branch.py) — the oh-my-zsh plugin
already owns completion; argcomplete was redundant and non-functional
under oh-my-zsh
- _muse_branches: rewrite to read .muse/refs/heads/ directly via find+sed
instead of spawning `muse plumbing show-ref | python3` — no subprocess,
works even when muse is not on PATH inside the completion subshell
- _muse_remotes: rewrite to read .muse/config.toml directly via grep+sed
matching [remotes.<name>] section headers — no subprocess, correct TOML
schema (was [remote."name"], actual schema is [remotes.name])
- muse.plugin.zsh §7: remove internal `compinit` call — oh-my-zsh calls
compinit after all plugins load; calling it again inside the plugin body
breaks completion; just add to fpath and let oh-my-zsh handle the rest
·
Fix push 422 on fresh remotes: query /refs before building pack
Before this change, pushing to a remote with no local tracking cache
(e.g. first push to 'local') fell back to an empty 'have' list, causing
build_pack to bundle every object in the repo (2338 → 422 too_long).
The fix: call GET /refs (fetch_remote_info) on the transport before
building the pack. This gives authoritative have-anchors directly from
the server, regardless of whether local tracking refs exist. Falls back
to cached tracking refs, then all known remote heads, then empty (for
a truly empty remote).
·
Fix push 422: use all cached remote heads as have anchors
The previous fix queried GET /refs to get the target remote's heads,
but those commit IDs often don't exist in the local object store
(e.g. when the remote received commits via GitHub PR merges that were
never fetched locally). build_pack's BFS can't stop at commits it
can't find, so it walks the entire history.
The correct fallback: use ALL cached tracking refs across every
configured remote (.muse/remotes/**/*). Any commit the machine has
previously pushed to any remote IS in the local object store AND is a
shared ancestor with mirrors/forks. This lets build_pack stop at the
nearest known common ancestor and send only the delta (3 commits,
~424 objects) rather than the full repo (2338 objects → 422 too_long).
·
Fix push 422: filter have-anchors to locally-known commits
GET /refs returns the remote's branch heads, but those commits may not
exist in the local object store (e.g. GitHub merge commits never
fetched). Without filtering, build_pack's BFS can't stop at them and
walks the entire history (2338 objects → 422 too_long).
Fix: combine live remote heads with all cached cross-remote tracking
refs, then filter the combined list to commits that exist locally.
The cached origin/dev and origin/main entries are in the local store
and are ancestors of the pushed branch, so build_pack stops at the
nearest one (3 commits, ~424 objects) rather than sending everything.
·
fix(push): exclude local_head from have — prevents 0-object push when branch already on another remote
·
feat(push): implement two-phase chunked push protocol
Separates large pushes into two phases to avoid the server's per-request
object limit (MAX_OBJECTS_PER_PUSH = 1 000):
Phase 1 — object pre-upload POST {url}/push/objects (N calls, ≤ 900 objects)
Objects are content-addressed (SHA-256) so each chunk is idempotent —
the server skips blobs it already holds. No branch refs are touched.
Phase 2 — commit push POST {url}/push (single call)
Carries commits + snapshots with an empty objects list. Because blobs
were pre-uploaded, this request is small regardless of history size.
Branch refs are updated atomically here.
For small pushes (≤ 900 objects) the two phases collapse into the existing
single POST /push call — no behaviour change.
Client-side additions:
- ObjectsChunkResponse TypedDict in pack.py (stored / skipped counts)
- CHUNK_OBJECTS = 900 constant in transport.py
- push_objects() added to MuseTransport Protocol, HttpTransport, and
LocalFileTransport with full docstrings and security notes
- _parse_objects_response() parser mirrors existing _parse_push_result()
- _push_chunked() orchestrator in push.py: chunks objects, prints
per-chunk progress, then calls push_pack with a slim (no-objects) bundle
Test: updated test_push_already_up_to_date to mock fetch_remote_info
correctly (returns RemoteInfo with same HEAD as local → triggers up-to-date
fast-path rather than proceeding to JSON serialisation of MagicMock).
·
docs: inject explicit HTML anchors in code-domain.md section headers
Adds <a id="..."> before each of the 12 numbered section headers so the
Contents table links resolve reliably regardless of GitHub's anchor-
generation algorithm. Same treatment as plumbing.md / porcelain.md.
·
fix(push): lower CHUNK_OBJECTS to 400 for reliable chunked upload
900-object chunks caused intermittent broken-pipe errors on both local
and production hosts under real-world load. 400 keeps well within the
server's 1 000-object Pydantic limit while halving per-request payload
size, which eliminates the connection resets observed during testing.
·
fix: use live remote info for push up-to-date check; restore CHUNK_OBJECTS=400
When the remote repo is empty (or was wiped and recreated), the push
command was falling back to a stale locally-cached tracking ref and
incorrectly reporting "Everything up to date", sending nothing.
Fix: when GET /refs succeeds (remote_info is not None), use only the
live branch_heads to determine the remote head. Only fall back to the
cached tracking ref when the remote was unreachable.
Also restores CHUNK_OBJECTS from 100 back to 400 — the correct value
for the production Nginx 300s timeout window.
·
feat(mwp): replace JSON+base64 wire protocol with MWP binary msgpack
- ObjectPayload.content: bytes replaces content_b64: str — no base64 overhead
- HttpTransport encodes all requests/responses as application/x-msgpack
- push.py: MWP push flow with filter-objects deduplication, chunked object upload
- pull.py: depth-limited commit negotiation before fetch
- bundle, pack-objects, unpack-objects, verify-pack: all switched to msgpack binary
- CliRunner updated to support bytes stdin/stdout for plumbing commands
- All tests updated for binary msgpack format; zero JSON+base64 fallback paths
·
feat(git2muse): auto-run muse init --domain code when .muse/ is missing
Also add test efficiency protocol to AGENTS.md and .cursorrules: run full
suite once, fix failures, re-run only the affected file to confirm, then
run full suite as the final pre-PR gate.
M!
chore: ignore .hypothesis, .pytest_cache, .mypy_cache, .ruff_cache; add separation-of-concerns rules
- .museignore: add test/tool cache directories that should never be tracked
- .cursorrules + AGENTS.md: enforce frontend separation of concerns (structure/behaviour/style)
⚠ AGENTS.md::Muse — Agent Contract.Architecture.code@L38⚠ AGENTS.md::Muse — Agent Contract.Branch Discipline — Absolute Rule⚠ AGENTS.md::Muse — Agent Contract.Branch Discipline — Absolute Rule.Enforcement protocol⚠ AGENTS.md::Muse — Agent Contract.Branch Discipline — Absolute Rule.Enforcement protocol.table@L100⚠ AGENTS.md::Muse — Agent Contract.Branch Discipline — Absolute Rule.Full task lifecycle⚠ AGENTS.md::Muse — Agent Contract.GitHub Interactions — MCP First⚠ AGENTS.md::Muse — Agent Contract.GitHub Interactions — MCP First.table@L113⚠ AGENTS.md::Muse — Agent Contract.Testing Standards.table@L177⚠ AGENTS.md::Muse — Agent Contract.Typing — Zero-Tolerance Rules.Enforcement chain⚠ AGENTS.md::Muse — Agent Contract.Typing — Zero-Tolerance Rules.Enforcement chain.table@L167⚠ AGENTS.md::Muse — Agent Contract.Typing — Zero-Tolerance Rules.table@L149⚠ muse/core/errors.py::ExitCode⚠ muse/core/pack.py::PackBundle⚠ muse/core/pack.py::build_pack⚠ muse/plugins/code/plugin.py::CodePlugin⚠ tests/test_core_snapshot.py::TestBuildSnapshotManifest.test_hidden_files_excluded⚠ tests/test_core_stat_cache.py::TestWalkWorkdirCacheIntegration.test_walk_excludes_hidden_paths_from_cache⚠ tools/omzsh-plugin/_muse⚠ tools/omzsh-plugin/muse.plugin.zsh