gabriel / muse public
Closed #37 Enhancement
filed by gabriel human · 130 days ago

Git-parity: -N commit-count shorthand + single-<ref> range semantics (format-patch, log, rev-list)

0 Anchors
— Blast radius
— Churn 30d
0 Proposals

Git-parity: -N commit-count shorthand + single-<ref> range semantics (format-patch, log, rev-list)

Summary

Two git muscle-memory gaps surfaced while using muse format-patch. Agents and devs coming from git reach for these idioms reflexively; muse should accept them (or diverge only with a loud, documented reason). Both are low-severity (workable) but high collision-likelihood.

1. -N commit-count shorthand is not accepted

git format-patch -1 / -N exports the last N commits from HEAD. muse format-patch -1 fails:

❌ '-1' is not a known branch or commit ID.

The -N shorthand is a pervasive git idiom (git log -3, git format-patch -1, …). muse log supports -n/--max-count N but not the bare -N form. Agents will reach for -N first.

2. Single-<ref> range semantics diverge from git (silent footgun)

git format-patch <ref> exports <ref>..HEAD — commits since <ref> (exclusive). muse format-patch <ref> instead exports the single commit AT <ref> (its parent → ref). A git user who passes the base ref expecting "everything since the base" silently gets the wrong patch, with no error:

# branch tip = a6ca2233, its parent = e452ad9a
muse format-patch e452ad9a   # exported e452ad9a ITSELF (wrong) — expected a6ca2233
muse format-patch a6ca2233   # had to pass the commit itself to export it

This bit a real workflow during the rc13 ship-out (exporting a6ca2233 to park it as a mist): the first attempt passed the parent and produced the wrong .mpatch, caught only by inspecting to_commit_id.

Proposed

  • Accept -N (last N commits) on format-patch, and audit log / rev-list for the same bare--N git shorthand.
  • Either align format-patch <ref> to git's <ref>..HEAD semantics, or keep single-commit semantics but require an explicit A..B range for "since" and document the divergence prominently in --help and the gitism glossary.
  • Sweep other ref/range-taking commands (log, rev-list, diff, range-diff) for the same two gaps and capture them here or in linked tickets.

Context

Surfaced during the rc13 ship-out. Worked around at the time; filed so the git-parity gaps are captured where agents will reach for them.

◎ Activity1
●
gabriel opened this issue 130 days ago
○
gabriel 2 days ago

Resolved — audited and closed all 3 gaps, shipped to main/dev on all remotes

Commit: sha256:8fff16f1bc41d40757bb7488701ddbc520adbed1effb6429da72f06b84631e56 (branch feat/37-dash-n-range-parity, fast-forward merged dev→main, pushed to local/staging/production).

Audit result: muse log already had both gaps closed

Before touching anything, I checked muse log's current implementation and found it already supports both the bare -N shorthand and A..B range syntax (implemented in an earlier, unrelated session — muse log -3 and muse log base..HEAD both already work correctly). Only format-patch and rev-list still had the two gaps this ticket describes.

Gap 1 — -N shorthand

Added to both commands:

  • format-patch -N — exports the last N commits from HEAD, oldest-first (git's own format-patch numbering convention: 0001 = oldest).
  • rev-list -N — same shorthand, accepted in either position (rev-list -3 HEAD or rev-list HEAD -3), matching git's flexible ordering. Also added -n as a proper short-flag alias for --max-count — a second, independently-discovered bug: rev-list's own docstring agent-quickstart example (muse rev-list -n 10 HEAD) has been broken since inception, because -n was never actually registered as an alias, only the long --max-count form existed.

Gap 2 — single-<ref> range semantics (the reported footgun)

Per the ticket's own accepted alternative, I kept format-patch <ref>'s existing single-commit semantics rather than silently changing it to ref..HEAD — that would be a much larger, harder-to-review behavior change to an already-shipped command, with real risk of breaking existing scripts/workflows built on the current (if surprising) meaning. Instead:

  • Added explicit A..B range syntax to format-patch (muse format-patch base..HEAD), giving the correct git-equivalent "everything since" behavior when asked for explicitly.
  • Prominently documented the divergence in three places: the --help text for the REF positional, the run() docstring, and a new Gitism Glossary entry in docs/agent-guide.md with the ticket's own exact reported scenario as the example.
  • Reproduced and verified the fix against the ticket's own real-world bug report: muse format-patch e452ad9a (the parent) still correctly exports only e452ad9a itself (documented, unchanged), while muse format-patch e452ad9a..HEAD now correctly exports a6ca2233 — the exact "since the base" result the rc13 ship-out workflow needed.

JSON schema design decision

-N and A..B mode produce multiple patches, which can't fit the existing flat single-patch JSON schema without an ambiguous shape change. I introduced a distinct {"count": N, "patches": [...]} envelope for multi-commit mode, while the bare <ref> schema is 100% unchanged — agents can detect which mode they received by checking for the count key. --output-dir in multi-commit mode writes numbered files (0001-*.mpatch, 0002-*.mpatch, ...), matching git's own format-patch file-naming convention.

Verification

  • 17 new tests in tests/test_format_patch_range_shorthand.py: -N (including exceeding available history, -0 rejection, numbered file output), A..B range (including the ticket's exact bug scenario), and the rev-list shorthand/alias/ordering/range-regression cases.
  • Confirmed via genuine RED: reverted both command files (kept the test file), re-ran — 11 of 17 tests failed as expected (the other 6 are mechanism-agnostic edge cases — e.g. "too many refs rejected" — that correctly pass under both old and new argument-parsing paths) — then restored and confirmed GREEN.
  • 380-test regression sweep across every format-patch/rev-list/log-relevant suite (test_cmd_rev_list.py, test_cmd_format_patch.py, test_format_patch_supercharge.py, test_format_patch_envelope.py, test_format_patch_range_shorthand.py, test_cmd_log.py, test_log_supercharge.py, test_log_branch_flag.py) — all passing, zero regressions to the unchanged bare-<ref> path.
  • Live-verified via muse-dev: format-patch -1/-2 oldest-first multi-patch output, format-patch base..HEAD reproducing and resolving the ticket's own reported footgun exactly, and rev-list -1/-n/either-ordering all confirmed working.

Docs

New Gitism Glossary entries in docs/agent-guide.md for both the -N shorthand (now consistent across log/rev-list/format-patch) and the format-patch <ref> divergence, with the exact ticket scenario as the worked example. muse agent-config sync confirmed zero drift after the edit.

Scope note

Did not touch diff/range-diff — the ticket said to "sweep... and capture them here or in linked tickets," and neither of those commands showed the reported gaps on inspection (both already take explicit A..B-shaped arguments as their primary interface, not a single ambiguous <ref>). If a gap is later found there, it should be its own ticket.

✓
closed this issue 2 days ago