app.py
python
sha256:0e3697f29ae6c62f39818a19861d4db6276465de16481fadf866eb93cbf4aae4
test: validate staging push and MP workflow for aaronrene
Human
16 days ago
| 1 | """Muse CLI — entry point for the ``muse`` console script. |
| 2 | |
| 3 | Two-tier command architecture |
| 4 | ------------------------------- |
| 5 | |
| 6 | **Tier 1 — Core + Low-level** (top-level ``muse …``) |
| 7 | All VCS commands live here — both human-facing porcelain and the |
| 8 | machine-readable, JSON-outputting primitives that scripts and agents use. |
| 9 | No ``plumbing`` sub-namespace: agents reach every command in one token. |
| 10 | |
| 11 | **Tier 2 — Semantic Porcelain** (``muse midi …``, ``muse code …``, ``muse coord …``) |
| 12 | Domain-specific commands that interpret multidimensional state. Each |
| 13 | sub-namespace is served by the corresponding ``muse/plugins/`` plugin. |
| 14 | |
| 15 | Top-level commands (alphabetical):: |
| 16 | |
| 17 | annotate archive attributes auth |
| 18 | bisect blame branch bundle |
| 19 | cat cat-object check check-attr |
| 20 | check-ignore check-ref-format checkout cherry-pick |
| 21 | clean clone code commit |
| 22 | commit-graph commit-tree config conflicts |
| 23 | content-grep coord describe diff |
| 24 | domain-info domains fetch for-each-ref |
| 25 | gc hash-object hub init |
| 26 | log ls-files ls-remote merge |
| 27 | merge-base midi name-rev pack-objects |
| 28 | pull push rebase read-commit |
| 29 | read-snapshot reflog release remote |
| 30 | rerere reset revert rev-parse |
| 31 | rm |
| 32 | shortlog show show-ref snapshot |
| 33 | snapshot-diff stash status symbolic-ref |
| 34 | tag unpack-objects update-ref verify |
| 35 | verify-object verify-pack workspace |
| 36 | worktree |
| 37 | |
| 38 | Identity & hub fabric:: |
| 39 | |
| 40 | auth keygen muse auth keygen [--hub HUB] |
| 41 | auth register muse auth register [--hub HUB] [--handle NAME] [--agent] |
| 42 | auth whoami muse auth whoami [--json] |
| 43 | auth logout muse auth logout [--hub HUB] |
| 44 | hub connect muse hub connect <url> |
| 45 | hub status muse hub status [--json] |
| 46 | hub disconnect muse hub disconnect |
| 47 | hub ping muse hub ping |
| 48 | config show muse config show [--json] |
| 49 | config get muse config get <key> |
| 50 | config set muse config set <key> <value> |
| 51 | config edit muse config edit |
| 52 | |
| 53 | Tier 3 — MIDI domain commands (``muse midi …``, alphabetical) — disabled pending rollout:: |
| 54 | |
| 55 | agent-map arpeggiate cadence check |
| 56 | compare contour density find-phrase |
| 57 | harmony humanize instrumentation invert |
| 58 | mix motif normalize note-blame |
| 59 | note-hotspots note-log notes piano-roll |
| 60 | quantize query retrograde rhythm |
| 61 | scale shard tempo tension |
| 62 | transpose velocity-profile voice-leading |
| 63 | |
| 64 | Tier 3 — Code domain commands (``muse code …``, alphabetical):: |
| 65 | |
| 66 | add age api-surface blast-risk |
| 67 | blame breakage cat checkout-symbol |
| 68 | clones code-check code-query codemap |
| 69 | compare contract coupling coverage |
| 70 | dead deps detect-refactor docs |
| 71 | entangle find-symbol gravity grep |
| 72 | hotspots impact index invariants |
| 73 | languages lineage narrative patch |
| 74 | predict query query-history rename |
| 75 | reset semantic-cherry-pick semantic-test-coverage stable |
| 76 | symbol-log symbols test type |
| 77 | velocity |
| 78 | |
| 79 | Tier 3 — Coordination commands (``muse coord …``, alphabetical):: |
| 80 | |
| 81 | cancel-task claim complete dag |
| 82 | enqueue fail-task forecast gc |
| 83 | heartbeat intent list plan-merge |
| 84 | reconcile release reserve shard |
| 85 | sync tasks watch |
| 86 | """ |
| 87 | |
| 88 | from __future__ import annotations |
| 89 | |
| 90 | import argparse |
| 91 | import difflib |
| 92 | import os |
| 93 | import signal |
| 94 | import sys |
| 95 | |
| 96 | # Restore the default SIGPIPE handler so that piping muse output to commands |
| 97 | # that read partial output (head, grep, jq, etc.) exits cleanly instead of |
| 98 | # raising BrokenPipeError and printing a Python traceback. This matters most |
| 99 | # for agents that routinely pipe muse JSON output to filters. |
| 100 | if hasattr(signal, "SIGPIPE"): |
| 101 | signal.signal(signal.SIGPIPE, signal.SIG_DFL) |
| 102 | |
| 103 | from muse.cli.commands import ( |
| 104 | age as age_cmd, |
| 105 | agent_config, |
| 106 | # agent_map, # midi — disabled until midi rollout |
| 107 | annotate, |
| 108 | api_surface, |
| 109 | archive, |
| 110 | # arpeggiate, # midi — disabled until midi rollout |
| 111 | attributes, |
| 112 | auth, |
| 113 | trust, |
| 114 | bisect, |
| 115 | blame, |
| 116 | blast_risk, |
| 117 | branch, |
| 118 | breakage, |
| 119 | bundle, |
| 120 | # cadence, # midi — disabled until midi rollout |
| 121 | cat, |
| 122 | check, |
| 123 | checkout, |
| 124 | checkout_symbol, |
| 125 | cherry_pick, |
| 126 | clean, |
| 127 | clone, |
| 128 | clones, |
| 129 | code_check, |
| 130 | code_query, |
| 131 | code_stage, |
| 132 | codemap, |
| 133 | commit, |
| 134 | compare, |
| 135 | config_cmd, |
| 136 | conflicts, |
| 137 | content_grep, |
| 138 | contract, |
| 139 | # contour, # midi — disabled until midi rollout |
| 140 | coord_gc, |
| 141 | coord_sync, |
| 142 | core_blame, |
| 143 | coupling, |
| 144 | coverage, |
| 145 | dag as dag_cmd, |
| 146 | dead, |
| 147 | # density, # midi — disabled until midi rollout |
| 148 | deps, |
| 149 | describe, |
| 150 | detect_refactor, |
| 151 | diff, |
| 152 | docs_cmd, |
| 153 | domains, |
| 154 | entangle, |
| 155 | fetch, |
| 156 | # find_phrase, # midi — disabled until midi rollout |
| 157 | find_symbol, |
| 158 | forecast, |
| 159 | gc, |
| 160 | gravity, |
| 161 | grep, |
| 162 | harmony, |
| 163 | heartbeat_coord, |
| 164 | hotspots, |
| 165 | hub, |
| 166 | # humanize, # midi — disabled until midi rollout |
| 167 | impact, |
| 168 | index_rebuild, |
| 169 | init, |
| 170 | # instrumentation, # midi — disabled until midi rollout |
| 171 | intent, |
| 172 | invariants, |
| 173 | # invert, # midi — disabled until midi rollout |
| 174 | languages, |
| 175 | lineage, |
| 176 | list_coord, |
| 177 | log, |
| 178 | merge, |
| 179 | migrate, |
| 180 | # midi_check, # midi — disabled until midi rollout |
| 181 | # midi_compare, # midi — disabled until midi rollout |
| 182 | # midi_query, # midi — disabled until midi rollout |
| 183 | # midi_shard, # midi — disabled until midi rollout |
| 184 | # mix, # midi — disabled until midi rollout |
| 185 | # motif_detect, # midi — disabled until midi rollout |
| 186 | narrative, |
| 187 | # note_blame, # midi — disabled until midi rollout |
| 188 | # note_hotspots, # midi — disabled until midi rollout |
| 189 | # note_log, # midi — disabled until midi rollout |
| 190 | # notes, # midi — disabled until midi rollout |
| 191 | patch, |
| 192 | # piano_roll, # midi — disabled until midi rollout |
| 193 | plan_merge, |
| 194 | predict, |
| 195 | pull, |
| 196 | push, |
| 197 | # quantize, # midi — disabled until midi rollout |
| 198 | query, |
| 199 | query_history, |
| 200 | rebase, |
| 201 | reconcile, |
| 202 | reflog, |
| 203 | release as release_cmd, |
| 204 | release_coord, |
| 205 | remote, |
| 206 | rename, |
| 207 | rerere, |
| 208 | reserve, |
| 209 | reset, |
| 210 | rm, |
| 211 | # retrograde, # midi — disabled until midi rollout |
| 212 | revert, |
| 213 | # rhythm, # midi — disabled until midi rollout |
| 214 | # scale_detect, # midi — disabled until midi rollout |
| 215 | semantic_cherry_pick, |
| 216 | semantic_test_coverage, |
| 217 | shard, |
| 218 | shortlog, |
| 219 | show, |
| 220 | snapshot_cmd, |
| 221 | stable, |
| 222 | stash, |
| 223 | status, |
| 224 | symbol_log, |
| 225 | symbols, |
| 226 | tag, |
| 227 | task_queue as task_queue_cmd, |
| 228 | # tempo, # midi — disabled until midi rollout |
| 229 | # tension, # midi — disabled until midi rollout |
| 230 | test_cmd, |
| 231 | # transpose, # midi — disabled until midi rollout |
| 232 | type_cmd, |
| 233 | velocity, |
| 234 | # velocity_normalize, # midi — disabled until midi rollout |
| 235 | # velocity_profile, # midi — disabled until midi rollout |
| 236 | verify, |
| 237 | verify_commit, |
| 238 | # voice_leading, # midi — disabled until midi rollout |
| 239 | watch_coord, |
| 240 | |
| 241 | workspace, |
| 242 | worktree, |
| 243 | ) |
| 244 | |
| 245 | from muse.cli.commands.plumbing import ( |
| 246 | cat_object, |
| 247 | check_attr, |
| 248 | check_ignore, |
| 249 | check_ref_format, |
| 250 | commit_graph, |
| 251 | commit_tree, |
| 252 | domain_info, |
| 253 | for_each_ref, |
| 254 | hash_object, |
| 255 | ls_files, |
| 256 | ls_remote, |
| 257 | merge_base, |
| 258 | name_rev, |
| 259 | pack_objects, |
| 260 | read_commit, |
| 261 | read_snapshot, |
| 262 | rev_parse, |
| 263 | show_ref, |
| 264 | snapshot_diff, |
| 265 | symbolic_ref, |
| 266 | unpack_objects, |
| 267 | update_ref, |
| 268 | verify_object, |
| 269 | verify_pack, |
| 270 | ) |
| 271 | |
| 272 | |
| 273 | def _no_command(parser: argparse.ArgumentParser) -> None: |
| 274 | """Print help when no subcommand is provided.""" |
| 275 | parser.print_help() |
| 276 | raise SystemExit(0) |
| 277 | |
| 278 | |
| 279 | class _MuseArgumentParser(argparse.ArgumentParser): |
| 280 | """ArgumentParser subclass that adds 'did you mean?' suggestions on error. |
| 281 | |
| 282 | When the user mistypes a subcommand (e.g. ``muse comit``), argparse |
| 283 | normally prints only "argument COMMAND: invalid choice: 'comit'". This |
| 284 | subclass intercepts that error, checks for close matches among the |
| 285 | registered subcommand names, and appends a suggestion line so agents and |
| 286 | humans can self-correct without consulting the full ``--help`` output. |
| 287 | |
| 288 | Example output:: |
| 289 | |
| 290 | muse: error: argument COMMAND: invalid choice: 'comit' |
| 291 | Did you mean: commit? |
| 292 | """ |
| 293 | |
| 294 | def error(self, message: str) -> None: |
| 295 | # Extract the unknown token from messages like: |
| 296 | # "argument COMMAND: invalid choice: 'comit' (choose from ...)" |
| 297 | unknown: str | None = None |
| 298 | if "invalid choice:" in message: |
| 299 | try: |
| 300 | start = message.index("'") + 1 |
| 301 | end = message.index("'", start) |
| 302 | unknown = message[start:end] |
| 303 | except ValueError: |
| 304 | pass |
| 305 | |
| 306 | if unknown is not None: |
| 307 | choices: list[str] = [] |
| 308 | for action in self._actions: |
| 309 | if isinstance(action, argparse._SubParsersAction): |
| 310 | choices.extend(action.choices.keys()) |
| 311 | suggestions = difflib.get_close_matches(unknown, choices, n=3, cutoff=0.6) |
| 312 | if suggestions: |
| 313 | message = f"{message}\n Did you mean: {', '.join(suggestions)}?" |
| 314 | |
| 315 | super().error(message) |
| 316 | |
| 317 | |
| 318 | def main(argv: list[str] | None = None) -> None: |
| 319 | """Parse arguments and dispatch to the appropriate command handler. |
| 320 | |
| 321 | Exit codes |
| 322 | ---------- |
| 323 | 0 Success — command completed normally. |
| 324 | 1 Runtime error — e.g. ``-C DIR`` directory does not exist. |
| 325 | 2 Argument error — unknown command, missing required argument, or a |
| 326 | namespace command (``code``/``midi``/``coord``) was invoked without a |
| 327 | subcommand. argparse prints the error before this function raises. |
| 328 | |
| 329 | Flag processing order |
| 330 | --------------------- |
| 331 | 1. ``--version`` / ``-V`` — print version and exit 0, no further parsing. |
| 332 | 2. ``-C DIR`` — ``os.chdir(DIR)`` before dispatch; exit 1 on |
| 333 | ``OSError`` (non-existent or permission-denied). |
| 334 | 3. Subcommand dispatch — ``args.func(args)`` called once. |
| 335 | |
| 336 | No-subcommand behaviour |
| 337 | ----------------------- |
| 338 | ``muse`` with no subcommand prints the help text and exits 0. |
| 339 | ``muse code`` / ``muse midi`` / ``muse coord`` with no subcommand exits 2 |
| 340 | (argparse enforces ``subs.required = True``). |
| 341 | |
| 342 | Calling from tests |
| 343 | ------------------ |
| 344 | Pass an explicit ``argv`` list to avoid reading ``sys.argv``:: |
| 345 | |
| 346 | from tests.cli_test_helper import CliRunner |
| 347 | result = CliRunner().invoke(None, ["--version"]) |
| 348 | assert "muse" in result.output |
| 349 | """ |
| 350 | parser = _MuseArgumentParser( |
| 351 | prog="muse", |
| 352 | description="Muse — domain-agnostic version control for multidimensional state.", |
| 353 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 354 | ) |
| 355 | parser.add_argument( |
| 356 | "--version", "-V", "-v", |
| 357 | action="store_true", |
| 358 | dest="show_version", |
| 359 | help="Print 'muse <version>' to stdout and exit 0. " |
| 360 | "Agents: parse with output.split()[1] for the bare version string.", |
| 361 | ) |
| 362 | parser.add_argument( |
| 363 | "-C", |
| 364 | metavar="DIR", |
| 365 | dest="chdir", |
| 366 | default=None, |
| 367 | help="Change to DIR before running the command (DIR must exist).", |
| 368 | ) |
| 369 | |
| 370 | subparsers = parser.add_subparsers(dest="command", metavar="COMMAND") |
| 371 | |
| 372 | # ``muse help`` — ergonomic alias for ``muse --help``. |
| 373 | # Agents and humans reach for ``help`` by reflex; honour it. |
| 374 | subparsers.add_parser("help", help="Show this help message and exit.") |
| 375 | |
| 376 | # ------------------------------------------------------------------ |
| 377 | # All top-level commands — alphabetical order. |
| 378 | # Low-level plumbing primitives and core porcelain are interleaved |
| 379 | # so that ``muse --help`` presents one sorted list. |
| 380 | # ------------------------------------------------------------------ |
| 381 | agent_config.register(subparsers) |
| 382 | annotate.register(subparsers) |
| 383 | archive.register(subparsers) |
| 384 | attributes.register(subparsers) |
| 385 | auth.register(subparsers) |
| 386 | bisect.register(subparsers) |
| 387 | core_blame.register(subparsers) # "blame" |
| 388 | branch.register(subparsers) |
| 389 | bundle.register(subparsers) |
| 390 | cat.register(subparsers) |
| 391 | cat_object.register(subparsers) # "cat-object" |
| 392 | check.register(subparsers) |
| 393 | check_attr.register(subparsers) # "check-attr" |
| 394 | check_ignore.register(subparsers) # "check-ignore" |
| 395 | check_ref_format.register(subparsers) # "check-ref-format" |
| 396 | checkout.register(subparsers) |
| 397 | cherry_pick.register(subparsers) # "cherry-pick" |
| 398 | clean.register(subparsers) |
| 399 | clone.register(subparsers) |
| 400 | |
| 401 | # ── code namespace (muse code …) ────────────────────────────────── |
| 402 | code_parser = subparsers.add_parser( |
| 403 | "code", |
| 404 | help="[Tier 3] Code domain semantic commands — symbol graph, call graph, and provenance.", |
| 405 | description="Code domain semantic commands.", |
| 406 | ) |
| 407 | code_subs = code_parser.add_subparsers(dest="code_command", metavar="CODE_COMMAND") |
| 408 | code_subs.required = True |
| 409 | |
| 410 | code_stage.register_add(code_subs) # "add" |
| 411 | age_cmd.register(code_subs) # "age" |
| 412 | api_surface.register(code_subs) # "api-surface" |
| 413 | blast_risk.register(code_subs) # "blast-risk" |
| 414 | blame.register(code_subs) # "blame" |
| 415 | breakage.register(code_subs) # "breakage" |
| 416 | cat.register(code_subs) # "cat" |
| 417 | checkout_symbol.register(code_subs) # "checkout-symbol" |
| 418 | clones.register(code_subs) # "clones" |
| 419 | code_check.register(code_subs) # "code-check" |
| 420 | code_query.register(code_subs) # "code-query" |
| 421 | codemap.register(code_subs) # "codemap" |
| 422 | compare.register(code_subs) # "compare" |
| 423 | contract.register(code_subs) # "contract" |
| 424 | coupling.register(code_subs) # "coupling" |
| 425 | coverage.register(code_subs) # "coverage" |
| 426 | dead.register(code_subs) # "dead" |
| 427 | deps.register(code_subs) # "deps" |
| 428 | detect_refactor.register(code_subs) # "detect-refactor" |
| 429 | docs_cmd.register(code_subs) # "docs" |
| 430 | entangle.register(code_subs) # "entangle" |
| 431 | find_symbol.register(code_subs) # "find-symbol" |
| 432 | gravity.register(code_subs) # "gravity" |
| 433 | grep.register(code_subs) # "grep" |
| 434 | hotspots.register(code_subs) # "hotspots" |
| 435 | impact.register(code_subs) # "impact" |
| 436 | index_rebuild.register(code_subs) # "index" |
| 437 | invariants.register(code_subs) # "invariants" |
| 438 | languages.register(code_subs) # "languages" |
| 439 | lineage.register(code_subs) # "lineage" |
| 440 | migrate.register(code_subs) # "migrate" |
| 441 | narrative.register(code_subs) # "narrative" |
| 442 | patch.register(code_subs) # "patch" |
| 443 | predict.register(code_subs) # "predict" |
| 444 | query.register(code_subs) # "query" |
| 445 | query_history.register(code_subs) # "query-history" |
| 446 | rename.register(code_subs) # "rename" |
| 447 | code_stage.register_reset(code_subs) # "reset" |
| 448 | semantic_cherry_pick.register(code_subs) # "semantic-cherry-pick" |
| 449 | semantic_test_coverage.register(code_subs) # "semantic-test-coverage" |
| 450 | stable.register(code_subs) # "stable" |
| 451 | symbol_log.register(code_subs) # "symbol-log" |
| 452 | symbols.register(code_subs) # "symbols" |
| 453 | test_cmd.register(code_subs) # "test" |
| 454 | type_cmd.register(code_subs) # "type" |
| 455 | velocity.register(code_subs) # "velocity" |
| 456 | # ───────────────────────────────────────────────────────────────── |
| 457 | |
| 458 | commit.register(subparsers) |
| 459 | commit_graph.register(subparsers) # "commit-graph" |
| 460 | commit_tree.register(subparsers) # "commit-tree" |
| 461 | config_cmd.register(subparsers) # "config" |
| 462 | conflicts.register(subparsers) |
| 463 | content_grep.register(subparsers) # "content-grep" |
| 464 | |
| 465 | # ── coord namespace (muse coord …) ─────────────────────────────── |
| 466 | coord_parser = subparsers.add_parser( |
| 467 | "coord", |
| 468 | help="[Tier 3] Multi-agent coordination commands — reservations, intent, conflict forecasting.", |
| 469 | description="Multi-agent coordination commands.", |
| 470 | ) |
| 471 | coord_subs = coord_parser.add_subparsers(dest="coord_command", metavar="COORD_COMMAND") |
| 472 | coord_subs.required = True |
| 473 | |
| 474 | task_queue_cmd.register_cancel_task(coord_subs) # "cancel-task" |
| 475 | task_queue_cmd.register_claim(coord_subs) # "claim" |
| 476 | task_queue_cmd.register_complete(coord_subs) # "complete" |
| 477 | dag_cmd.register(coord_subs) # "dag" |
| 478 | task_queue_cmd.register_enqueue(coord_subs) # "enqueue" |
| 479 | task_queue_cmd.register_fail_task(coord_subs) # "fail-task" |
| 480 | forecast.register(coord_subs) # "forecast" |
| 481 | coord_gc.register(coord_subs) # "gc" |
| 482 | heartbeat_coord.register(coord_subs) # "heartbeat" |
| 483 | intent.register(coord_subs) # "intent" |
| 484 | list_coord.register(coord_subs) # "list" |
| 485 | plan_merge.register(coord_subs) # "plan-merge" |
| 486 | reconcile.register(coord_subs) # "reconcile" |
| 487 | release_coord.register(coord_subs) # "release" |
| 488 | reserve.register(coord_subs) # "reserve" |
| 489 | shard.register(coord_subs) # "shard" |
| 490 | coord_sync.register(coord_subs) # "sync" |
| 491 | task_queue_cmd.register_tasks(coord_subs) # "tasks" |
| 492 | watch_coord.register(coord_subs) # "watch" |
| 493 | # ───────────────────────────────────────────────────────────────── |
| 494 | |
| 495 | describe.register(subparsers) |
| 496 | diff.register(subparsers) |
| 497 | domain_info.register(subparsers) # "domain-info" |
| 498 | domains.register(subparsers) |
| 499 | fetch.register(subparsers) |
| 500 | for_each_ref.register(subparsers) # "for-each-ref" |
| 501 | gc.register(subparsers) |
| 502 | hash_object.register(subparsers) # "hash-object" |
| 503 | hub.register(subparsers) |
| 504 | init.register(subparsers) |
| 505 | log.register(subparsers) |
| 506 | ls_files.register(subparsers) # "ls-files" |
| 507 | ls_remote.register(subparsers) # "ls-remote" |
| 508 | merge.register(subparsers) |
| 509 | merge_base.register(subparsers) # "merge-base" |
| 510 | |
| 511 | # ── midi namespace (muse midi …) — disabled until midi rollout ─── |
| 512 | # midi_parser = subparsers.add_parser( |
| 513 | # "midi", |
| 514 | # help="[Tier 3] MIDI domain semantic commands.", |
| 515 | # description="MIDI domain semantic commands — analysis, transformation, and multi-agent.", |
| 516 | # ) |
| 517 | # midi_subs = midi_parser.add_subparsers(dest="midi_command", metavar="MIDI_COMMAND") |
| 518 | # midi_subs.required = True |
| 519 | # |
| 520 | # agent_map.register(midi_subs) # "agent-map" |
| 521 | # arpeggiate.register(midi_subs) # "arpeggiate" |
| 522 | # cadence.register(midi_subs) # "cadence" |
| 523 | # midi_check.register(midi_subs) # "check" |
| 524 | # midi_compare.register(midi_subs) # "compare" |
| 525 | # contour.register(midi_subs) # "contour" |
| 526 | # density.register(midi_subs) # "density" |
| 527 | # find_phrase.register(midi_subs) # "find-phrase" |
| 528 | # harmony.register(midi_subs) # "harmony" |
| 529 | # humanize.register(midi_subs) # "humanize" |
| 530 | # instrumentation.register(midi_subs) # "instrumentation" |
| 531 | # invert.register(midi_subs) # "invert" |
| 532 | # mix.register(midi_subs) # "mix" |
| 533 | # motif_detect.register(midi_subs) # "motif" |
| 534 | # velocity_normalize.register(midi_subs) # "normalize" |
| 535 | # note_blame.register(midi_subs) # "note-blame" |
| 536 | # note_hotspots.register(midi_subs) # "note-hotspots" |
| 537 | # note_log.register(midi_subs) # "note-log" |
| 538 | # notes.register(midi_subs) # "notes" |
| 539 | # piano_roll.register(midi_subs) # "piano-roll" |
| 540 | # quantize.register(midi_subs) # "quantize" |
| 541 | # midi_query.register(midi_subs) # "query" |
| 542 | # retrograde.register(midi_subs) # "retrograde" |
| 543 | # rhythm.register(midi_subs) # "rhythm" |
| 544 | # scale_detect.register(midi_subs) # "scale" |
| 545 | # midi_shard.register(midi_subs) # "shard" |
| 546 | # tempo.register(midi_subs) # "tempo" |
| 547 | # tension.register(midi_subs) # "tension" |
| 548 | # transpose.register(midi_subs) # "transpose" |
| 549 | # velocity_profile.register(midi_subs) # "velocity-profile" |
| 550 | # voice_leading.register(midi_subs) # "voice-leading" |
| 551 | # ───────────────────────────────────────────────────────────────── |
| 552 | |
| 553 | name_rev.register(subparsers) # "name-rev" |
| 554 | pack_objects.register(subparsers) # "pack-objects" |
| 555 | pull.register(subparsers) |
| 556 | push.register(subparsers) |
| 557 | rebase.register(subparsers) |
| 558 | read_commit.register(subparsers) # "read-commit" |
| 559 | read_snapshot.register(subparsers) # "read-snapshot" |
| 560 | reflog.register(subparsers) |
| 561 | release_cmd.register(subparsers) # "release" |
| 562 | remote.register(subparsers) |
| 563 | rerere.register(subparsers) |
| 564 | reset.register(subparsers) |
| 565 | rm.register(subparsers) |
| 566 | revert.register(subparsers) |
| 567 | rev_parse.register(subparsers) # "rev-parse" |
| 568 | shortlog.register(subparsers) |
| 569 | show.register(subparsers) |
| 570 | show_ref.register(subparsers) # "show-ref" |
| 571 | snapshot_cmd.register(subparsers) # "snapshot" |
| 572 | snapshot_diff.register(subparsers) # "snapshot-diff" |
| 573 | stash.register(subparsers) |
| 574 | status.register(subparsers) |
| 575 | symbolic_ref.register(subparsers) # "symbolic-ref" |
| 576 | tag.register(subparsers) |
| 577 | trust.register(subparsers) |
| 578 | unpack_objects.register(subparsers) # "unpack-objects" |
| 579 | update_ref.register(subparsers) # "update-ref" |
| 580 | verify.register(subparsers) |
| 581 | verify_commit.register(subparsers) # "verify-commit" — Phase 7.5 |
| 582 | verify_object.register(subparsers) # "verify-object" |
| 583 | verify_pack.register(subparsers) # "verify-pack" |
| 584 | workspace.register(subparsers) |
| 585 | worktree.register(subparsers) |
| 586 | |
| 587 | # ------------------------------------------------------------------ |
| 588 | # Parse and dispatch |
| 589 | # ------------------------------------------------------------------ |
| 590 | args = parser.parse_args(argv) |
| 591 | |
| 592 | if args.show_version: |
| 593 | from muse._version import __version__ |
| 594 | # Output format is stable: exactly "muse <version>\n" on stdout, exit 0. |
| 595 | # Agents and scripts can parse the version without --json: |
| 596 | # |
| 597 | # version = subprocess.check_output(["muse", "--version"]).decode().split()[1] |
| 598 | # |
| 599 | # The token at index 1 (space-split) is always the bare semver string. |
| 600 | # This format will not change without a major-version bump. |
| 601 | print(f"muse {__version__}") |
| 602 | raise SystemExit(0) |
| 603 | |
| 604 | if args.chdir is not None: |
| 605 | from muse.core.validation import sanitize_display |
| 606 | try: |
| 607 | os.chdir(args.chdir) |
| 608 | except OSError as exc: |
| 609 | safe_dir = sanitize_display(args.chdir) |
| 610 | print(f"muse: cannot change to directory '{safe_dir}': {exc}", file=sys.stderr) |
| 611 | raise SystemExit(1) |
| 612 | |
| 613 | if args.command is None or args.command == "help": |
| 614 | _no_command(parser) |
| 615 | return |
| 616 | |
| 617 | if not hasattr(args, "func"): |
| 618 | # A namespace command (midi/code/coord) was given without a subcommand. |
| 619 | # argparse already printed an error above via subs.required = True. |
| 620 | raise SystemExit(2) |
| 621 | |
| 622 | args.func(args) |
| 623 | |
| 624 | |
| 625 | if __name__ == "__main__": |
| 626 | main() |
File History
1 commit
sha256:0e3697f29ae6c62f39818a19861d4db6276465de16481fadf866eb93cbf4aae4
test: validate staging push and MP workflow for aaronrene
Human
16 days ago