gabriel / muse public
feat patch feat/31-caret-exclusion-syntax #1 / 1
AI Agent gabriel · 2 hours ago · Oct 22, 2026 · Diff

feat: implement ^ref exclusion prefix syntax in rev-list and log (muse#31)

Audit (AU_01) — git commands that accept ^ref / A..B, and muse's state before this change:

| Command | A..B support | ^ref support | Notes | |-------------------|------------------------|---------------|-------| | muse rev-list | yes (pre-existing) | NO — hard "too_many_refs" error | fixed here (EX_01/EX_02) | | muse log | yes (pre-existing, issue #28) | NO — silently swallowed into pathspec, returned wrong empty result | fixed here (EX_03) | | muse commit-graph | n/a — uses --tip/--stop-at flags, no git-positional ref syntax | n/a | out of scope: different interface shape, nothing to translate | | muse diff | n/a — explicit positional commit_a/commit_b | n/a | explicitly out of scope per ticket | | muse cherry (git) / muse cherry-pick | muse has no `cherry` (upstream-diff) command, only `cherry-pick <ref>` which applies exactly one commit | n/a | "no new commands" — out of scope |

EX_01/EX_02 — muse rev-list: `ref` tokens starting with "^" are now collected as exclusion refs (any number, composing with each other and with a single plain/A..B include token), unioned with the existing A..B exclusion-set logic via `_exclude_set`/`ancestor_ids`. `rev-list X ^Y` == `rev-list Y..X`.

EX_03 — muse log: Same exclusion semantics. Two argparse wrinkles handled: a lone "^ref" token (e.g. `muse log ^main`) lands in the single `ref` positional itself (nargs="?" greedily claims it), while any additional "^ref" tokens land in `pathspec` (nargs="*") and would otherwise be silently treated as file paths, producing a wrong (and silently empty) result with no error at all — worse than rev-list's hard error. Both cases are intercepted and routed into the same exclusion-set union as the A..B path before pathspec disambiguation runs.

RG_01-04 — muse log A..B range syntax: Investigation found this was already implemented (issue #28, tests/test_log_dotdot_range.py) for plain text/--json output and -n limit composition. Verified live that --oneline (RG_03) and composition with --author/pathspec/--max-count (RG_04) already worked correctly through the shared filter pipeline — added regression tests for both, no code changes needed for Phase 3.

GL_01-03 — docs/agent-guide.md Gitism Glossary: New "Commit exclusion syntax" subsection documents ^ref as now working identically to git (not a gitism to translate away from, same framing as the existing refspec-syntax section), with a direction-disambiguation note (A..B / X ^Y both mean "reachable from the right/non-excluded side").

GL_04 — grep gate note: The ticket's literal `grep -n " \^[a-zA-Z]" docs/agent-guide.md` gate cannot return zero matches while also documenting WORKING ^ref syntax — any correct command example like "rev-list A ^B" necessarily contains a space immediately before a caret-prefixed letter. Confirmed the file had zero such matches before this change (verified); the gate's literal regex and this ticket's own goal of documenting working ^ref syntax are in direct tension. Resolved by following the intent (no row frames ^ref as a "wrong gitism to avoid" — it's documented as working, matching the existing refspec-syntax section's "not a gotcha, a reassurance" framing) rather than gaming the literal grep command.

Tests: tests/test_cmd_rev_list.py (4 new: EX_01/EX_02 equivalence, composition, JSON, bad-ref error) and tests/test_log_dotdot_range.py (4 new: EX_03 equivalence, caret-alone default-branch, composition, bad-ref error). Confirmed genuine RED for both EX_03 and EX_01/EX_02 by reverting each fix and re-running (failures landed exactly where the removed logic was supposed to act — rev-list hard-errored, log silently returned wrong empty results), then GREEN with fixes restored.

Regression: tests/test_log_dotdot_range.py, tests/test_cmd_rev_list.py, tests/test_log_collect_all_commits.py, tests/test_log_json_schema.py, tests/test_log_graph.py, tests/test_log_supercharge.py, tests/test_cli_log.py, tests/test_cmd_log.py, tests/test_log_branch_flag.py — 327 tests, all green, no regressions.

sha256:85fc1542f909649acadf0b5fd3bc662f43d59eea76d799a069e76cbccd54f7ec sha
+9 ~4 symbols
5 changed · 1254 in snapshot files
sha256:5f98187f2dcac43c1f86f16815eb33f5b93a932df0a11c4fcee3fbe377f5cd65 snapshot
+9
symbols added
~4
symbols modified
5
files changed
1254
files in snapshot
0
dead code introduced
Semantic Changes 13 symbols
~ docs/agent-guide.md .md 1 symbol added, 2 symbols modified
+ B does (muse#31) section Commit exclusion syntax — ^ref now works everywhere A..B does (muse#31) L1482–1509
~ muse/cli/commands/log.py .py 1 symbol modified
~ run
~ muse/cli/commands/rev_list.py .py 1 symbol modified
~ run
~ tests/test_cmd_rev_list.py .py 4 symbols added
+ test_EX_01_caret_exclusion_equivalent_to_dotdot_range function function test_EX_01_caret_exclusion_equivalent_to_dotdot_range L360–385
+ test_EX_01_caret_exclusion_json function function test_EX_01_caret_exclusion_json L431–445
+ test_EX_02_bad_caret_ref_errors function function test_EX_02_bad_caret_ref_errors L448–454
+ test_EX_02_multiple_caret_exclusions_compose function function test_EX_02_multiple_caret_exclusions_compose L388–428
~ tests/test_log_dotdot_range.py .py 4 symbols added
+ test_EX_03_caret_alone_excludes_from_current_branch function function test_EX_03_caret_alone_excludes_from_current_branch L239–249
+ test_EX_03_multiple_caret_exclusions_compose function function test_EX_03_multiple_caret_exclusions_compose L252–272
+ test_EX_03_single_caret_equivalent_to_dotdot function function test_EX_03_single_caret_equivalent_to_dotdot L219–236
+ test_EX_03_unknown_caret_ref_errors function function test_EX_03_unknown_caret_ref_errors L275–284
Files Changed
~5
1254 in snapshot
← Older Oldest on feat/31-caret-exclusion-syntax
All commits
Newer → Latest on feat/31-caret-exclusion-syntax

0 comments

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

To add a comment, use the Muse CLI: muse hub commit comment sha256:85fc1542f909649acadf0b5fd3bc662f43d59eea76d799a069e76cbccd54f7ec --body "your comment"