gabriel / muse public
Closed #14 refactor
filed by gabriel human · 18 days ago

Decompose muse/cli/commands/bridge.py (3138 lines) into focused modules

0 Anchors
Blast radius
Churn 30d
0 Proposals

Summary

`muse/cli/commands/bridge.py` is 3138 lines and owns eight distinct responsibilities. Like `store.py` before it, it is a god object — every bridge operation (import, export, status, hooks, state I/O, git subprocess wrappers, harmony/shelf migration, drift computation) runs through it. This issue tracks a phased decomposition into focused, single-responsibility modules.

Goal: zero functional change, zero test regressions, cleaner architecture.

The destination is a `muse/core/bridge/` package that owns all domain logic, with `muse/cli/commands/bridge.py` reduced to a thin argparse registration shell (≤ 200 lines).


Responsibility map (current)

Lines Symbols Responsibility
67–390 `_LastImportState`, `_LastExportState`, `BridgeState`, `_SidecarData`, `DriftInfo`, `read_bridge_state`, `write_bridge_state`, `_dict_to_toml` TOML state persistence
167–267 `BridgeHook`, `BridgeHooks`, `load_bridge_hooks`, `run_hook`, `run_hooks` Lifecycle hook dispatch
455–802 `AttributionMapper`, `_CatFile`, `_git`, `_batch_commit_log`, `_batch_diff_tree`, `_is_lfs_pointer`, `_strip_ansi`, `_parse_sem_ver_bump` Low-level git subprocess wrappers
804–1456 `_replay_commit`, `_replay_branch`, `_list_git_branches`, `_list_git_tags`, `_import_tags`, `run_git_import` Git → Muse import engine
1463–1915 `import_rerere_to_harmony`, `export_harmony_to_rerere`, `import_stashes_to_shelf`, `export_shelves_to_stash` Harmony / shelf cross-system migration
1929–2619 `_validate_git_sha`, `_validate_git_branch_name`, `GitExporter`, `_ensure_git_branch`, `_watch_loop`, `run_git_export` Muse → Git export engine
2621–2824 `run_git_status`, `_compute_drift`, `_count_muse_commits_since`, `_print_bridge_status_text` Status and drift computation
2830–3138 `register`, `_register_git_import`, `_register_git_export`, `_register_git_status` CLI argparse registration

Target layout

``` muse/core/bridge/ init.py — re-exports public API (BridgeState, run_git_import, …) state.py — TOML state types + read/write hooks.py — hook loading and dispatch git_primitives.py — subprocess wrappers (git, _CatFile, _batch*, …) importer.py — Git → Muse import engine harmony_shelf.py — rerere↔harmony and stash↔shelf migration exporter.py — Muse → Git export engine + watch loop status.py — drift computation and status formatting muse/cli/commands/bridge.py — thin shell: argparse registration only (≤ 200 lines) ```


Phase 1 — Create `muse/core/bridge/state.py`

Scope: TOML state types and persistence.

Extract:

  • `_LastImportState`, `_LastExportState`, `BridgeState`, `_SidecarData`, `DriftInfo` (TypedDicts / dataclasses)
  • `read_bridge_state`, `write_bridge_state`, `_dict_to_toml`

Acceptance: `muse/core/bridge/state.py` exists, all callers in `bridge.py` updated to import from it, all bridge tests pass.


Phase 2 — Create `muse/core/bridge/hooks.py`

Scope: lifecycle hook loading and dispatch.

Extract:

  • `BridgeHook`, `BridgeHooks` (TypedDicts)
  • `load_bridge_hooks`, `run_hook`, `run_hooks`

Acceptance: hook logic fully decoupled from import/export engines, all bridge tests pass.


Phase 3 — Create `muse/core/bridge/git_primitives.py`

Scope: low-level git subprocess I/O. No Muse domain knowledge.

Extract:

  • `AttributionMapper` — author/committer → Muse identity mapping
  • `_CatFile` — git cat-file --batch process wrapper
  • `_git` — subprocess runner
  • `_batch_commit_log` — bulk commit log reader
  • `_batch_diff_tree` — bulk diff-tree reader
  • `_is_lfs_pointer`, `_strip_ansi`, `_parse_sem_ver_bump`

Acceptance: no git subprocess logic remains in `bridge.py`, all bridge tests pass.


Phase 4 — Create `muse/core/bridge/importer.py`

Scope: Git → Muse commit replay engine.

Extract:

  • `_replay_commit` (~140 lines)
  • `_replay_branch` (~188 lines)
  • `_list_git_branches`, `_list_git_tags`, `_import_tags`
  • `run_git_import` (~213 lines)

Internal dependencies: imports from `git_primitives`, `state`, `hooks`.

Acceptance: `run_git_import` callable from `muse.core.bridge.importer`, no import logic in `bridge.py`, `test_bridge_git_import.py` fully green.


Phase 5 — Create `muse/core/bridge/harmony_shelf.py`

Scope: cross-system data migration (rerere ↔ harmony, stash ↔ shelf).

Extract:

  • `import_rerere_to_harmony` (~136 lines)
  • `export_harmony_to_rerere` (~63 lines)
  • `import_stashes_to_shelf` (~135 lines)
  • `export_shelves_to_stash` (~112 lines)

Acceptance: migration logic isolated, `test_bridge_harmony_shelf.py` fully green.


Phase 6 — Create `muse/core/bridge/exporter.py`

Scope: Muse → Git export engine and watch loop.

Extract:

  • `_validate_git_sha`, `_validate_git_branch_name`
  • `GitExporter` class (~360 lines)
  • `_ensure_git_branch`, `_watch_loop`
  • `run_git_export` (~188 lines)

Internal dependencies: imports from `git_primitives`, `state`, `hooks`.

Acceptance: `run_git_export` callable from `muse.core.bridge.exporter`, `test_bridge_git_export.py` and `test_bridge_watch.py` fully green.


Phase 7 — Create `muse/core/bridge/status.py`

Scope: drift computation and status display.

Extract:

  • `run_git_status`
  • `_compute_drift`, `_count_muse_commits_since`
  • `_print_bridge_status_text`

Acceptance: `test_bridge_git_status.py` fully green.


Phase 8 — Create `muse/core/bridge/init.py`, thin the CLI shell, delete dead code

Scope: final wiring and cleanup.

  • Create `muse/core/bridge/init.py` exporting the public API
  • Reduce `muse/cli/commands/bridge.py` to argparse registration only (≤ 200 lines: `register`, `_register_git_import`, `_register_git_export`, `_register_git_status`)
  • Run `muse code dead --high-confidence-only` and delete anything unreachable
  • Update any doc references to `muse/cli/commands/bridge.py` internals
  • Update `test_bridge_phase1.py` and `test_bridge_security.py` if they reference internal paths

Acceptance:

  • `muse/cli/commands/bridge.py` ≤ 200 lines, contains only argparse wiring
  • `muse code breakage` — 0 issues
  • `muse code test` — clean
  • All `test_bridge_*.py` files pass
  • No stale doc references

Cross-cutting rules (all phases)

  • No functional changes in any phase — pure mechanical extraction
  • Each phase is its own commit on a task branch
  • Each phase merges independently to dev before the next starts
  • No backward-compat shims, no re-exports, no deprecated annotations
  • Delete everything that is unused after extraction
  • `muse code test --json` must pass before each phase merges
  • Circular import guard: `git_primitives` has no knowledge of Muse domain types; `importer`/`exporter` import from `git_primitives` and `state` but never from each other

Test files to keep green throughout

  • `tests/test_bridge_git_import.py`
  • `tests/test_bridge_git_export.py`
  • `tests/test_bridge_git_status.py`
  • `tests/test_bridge_hooks.py`
  • `tests/test_bridge_harmony_shelf.py`
  • `tests/test_bridge_watch.py`
  • `tests/test_bridge_roundtrip.py`
  • `tests/test_bridge_phase1.py`
  • `tests/test_bridge_security.py`
Activity1
gabriel opened this issue 18 days ago
gabriel 18 days ago

Implementation complete — all spec items verified

Final state

muse/cli/commands/bridge.py: 3138 → 74 lines — argparse wiring only, zero re-exports, zero domain logic.

muse/core/bridge/
    __init__.py          — full public API re-export (BridgeState, run_git_import, …)
    state.py             — TOML state types + read/write
    hooks.py             — hook loading and dispatch
    git_primitives.py    — subprocess wrappers (_git, _CatFile, FileDiffTree, …)
    importer.py          — Git → Muse import engine + _register_git_import_parser
    harmony_shelf.py     — rerere↔harmony and stash↔shelf migration
    exporter.py          — Muse → Git export engine + watch loop + _register_git_export_parser
    status.py            — drift computation and status display + _register_git_status_parser

Acceptance criteria check

  • bridge.py ≤ 200 lines, argparse wiring only (74 lines)
  • No re-exports, no backward-compat shims, no deprecated annotations
  • muse.core.bridge.__init__ exports full public API
  • muse code dead --high-confidence-only → 0 dead symbols
  • muse code breakage → 0 issues
  • All test imports updated to canonical muse.core.bridge.* paths
  • git_primitives carries no Muse domain types (FileDiffTree moved there from state.py)
  • Circular import guard: importer/exporter never import from each other
  • 277/277 tests pass across all test_bridge_*.py files
  • No stale doc references