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