mm generates a Conventional Commit message from the changes Git reports, then
commits them. By default it is deterministic and path-based: no AI, no network
calls. --chat instead copies the title and last assistant message from a local
OpenCode session.
- macOS with zsh (the install steps below use Cargo)
- Git
- Rust 1.85+ (the crate uses edition 2024) when installing from source
- Optional: Node.js for JavaScript/TypeScript commitlint configs
brew tap loclv/tap
brew install mmcargo binstall mmFrom the repository root:
cargo install --path . --forceCargo installs the binary to ~/.cargo/bin/mm. Confirm that Cargo's bin
directory is on your zsh PATH and that the command resolves:
echo "$PATH" | tr ':' '\n' | grep -F "$HOME/.cargo/bin"
command -v mm
mm --versionIf mm is not found, add this to ~/.zshrc and start a new shell:
export PATH="$HOME/.cargo/bin:$PATH"After reinstalling or replacing the binary, run rehash so zsh refreshes its
command cache.
To update after changing the source, reinstall:
cargo install --path . --forceTo uninstall:
cargo uninstall mmmm commits immediately, so preview the message first when you are unsure:
mm -p # generate and print the message only
mm # generate and commit| Option | Meaning |
|---|---|
--completions <SHELL> |
Print a completion script for bash, zsh, or fish, then exit |
-a, --add |
Stage all changes, including untracked files, then commit |
-e, --edit |
Open the generated message in Git's editor before committing |
-p, --print |
Print the message and exit; never commit |
-d, --dry-run |
Alias for --print |
-m, --message <MSG> |
Merge the generated description with an existing message, or override the chat subject |
--chat |
Copy the chat title and last message (OpenCode, Antigravity, Claude Code, Codex, Devin, or Freebuff) |
--chat-session <ID> |
Use a specific session ID or transcript path; implies --chat |
-o, --opencode |
Copy the OpenCode session title and last message; implies --chat |
--agy, --antigravity |
Copy the Antigravity last log commit message; implies --chat |
--agy-session <ID> |
Use a specific Antigravity conversation or transcript; implies --agy |
--claude |
Copy the Claude Code last log commit message; implies --chat |
--claude-session <ID> |
Use a specific Claude Code session or transcript; implies --claude |
--codex |
Copy the Codex last log commit message; implies --chat |
--codex-session <ID> |
Use a specific Codex session or transcript; implies --codex |
--devin, --codeium |
Copy the Devin / Codeium Cascade last log commit message; implies --chat |
--devin-session <ID> |
Use a specific Devin or Cascade session or transcript; implies --devin |
--freebuff, --codebuff |
Copy the Freebuff / Codebuff last log commit message; implies --chat |
--freebuff-session <ID> |
Use a specific Freebuff or Codebuff session or transcript; implies --freebuff |
-c, --config <FILE> |
Use a specific commitlint config instead of searching |
-s, --staged |
Only use staged changes; never fall back to unstaged changes |
--no-verify |
Bypass Git commit hooks |
--verbose |
Print diagnostics to stderr |
-n, --no-edit |
Compatibility flag; editing is already off by default |
-h, --help |
Show help and exit |
-v, -V, --version |
Show the version and exit |
mm --completions <shell> prints a completion script to stdout and exits. It
works outside a Git repository and does not change anything on disk, so you can
redirect the output wherever your shell loads completions from:
# bash (macOS Homebrew or Linux)
mm --completions bash > /etc/bash_completion.d/mm
mm --completions bash > ~/.local/share/bash-completion/completions/mm
# zsh
mkdir -p ~/.zfunc
mm --completions zsh > ~/.zfunc/_mm
# then add `fpath+=~/.zfunc` before `compinit` in ~/.zshrc
# fish
mm --completions fish > ~/.config/fish/completions/mm.fishFor zsh with Oh My Zsh, the script also works as a plugin: save it as
$ZSH_CUSTOM/plugins/mm/mm.plugin.zsh and add mm to plugins.
An unsupported shell name is an error:
mm --completions nushell
# Error: unsupported shell `nushell` (expected bash, zsh, fish)- If anything is staged, only staged changes are used.
- If nothing is staged, all changes (including untracked files) are
automatically staged and committed (default
autoAdd: true). - If
autoAddis disabled inmm.json("autoAdd": false), tracked working-tree changes are committed withgit commit -m MSG ., and untracked files are ignored unless--addis passed.
--staged disables the fallback in step 2, so an empty index is an error.
--print never changes the repository. --add only stages for a real commit, so
mm --print --add still excludes untracked files from the preview; if only
untracked files exist, it reports that it cannot find changes.
Generated headers follow the Conventional Commits specification by default:
- One file:
feat: create foo.rs,docs: update README.md,chore: delete old.txt, orchore: rename a.rs to b.rs. - Two to four files: names are listed grouped by action, for example
chore: update src/main.rs and src/lib.rsordocs: update guide.md and create api.md. - Five or more files: counts are used, for example
chore: update 5 files. - Types come from the path: docs, tests, CI, dependencies (
build(deps)), build files, and configuration (chore). - A new file with no other match is a
feat. - Renames, moves, and deletions are
chore. - When no type can be inferred, or the changed paths disagree, the type is
chore.
An existing type prefix supplied with --message wins over the generated type;
an existing description is appended after the generated description.
Add mm.json to the project (or a parent directory up to $HOME) to configure project options:
conventionalCommits(boolean, default:true): set tofalse(or alias"conventional") to generate plain descriptions without a Conventional Commit type prefix:
{
"conventionalCommits": false
}autoAdd(boolean, default:true): by default,mmautomatically stages and commits all changes (including untracked files) when running without any staged changes. If files are already staged,mmcommits only the staged changes and leaves unstaged/untracked files untouched. To disable this and ignore untracked files when nothing is staged, set"autoAdd": false(or aliases"add","autoCommit","autoStage"):
{
"autoAdd": false
}mm then generates plain descriptions such as update README.md when Conventional Commits are disabled; it does not
add a chore fallback or any type: prefix. An explicit --message prefix is still preserved, and
commitlint rules still apply.
mm.json is searched upward from the current directory, stopping at your home
directory, the same way commitlint configuration is found. --config is
unrelated: it overrides the commitlint configuration file.
mm can extract the commit message directly from local assistant chats:
- OpenCode: OpenCode exports sessions using its
opencodeCLI. Whenmmruns inside an OpenCode session,OPENCODE_SESSION_IDidentifies the current chat. Pass--opencode/-oto force OpenCode when other assistant logs are also present. - Antigravity: Antigravity saves conversation transcripts in
~/.gemini/antigravity-ide/brain/<ID>/.system_generated/logs/transcript.jsonl. When running inside Antigravity (or whenANTIGRAVITY_CONVERSATION_IDis set), or via--agy,mmreads the conversation log. - Claude Code: Claude Code saves session transcripts in
~/.claude/projects/<encoded-project-path>/<session-id>.jsonl(or underCLAUDE_CONFIG_DIR). When running inside Claude Code (or whenCLAUDE_SESSION_IDis set), or via--claude,mmreads the session log. - Codex: OpenAI Codex saves session transcripts in
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl.zst(or.jsonl, underCODEX_HOME). When running inside Codex (or whenCODEX_SESSION_IDis set), or via--codex,mmreads the session log. - Devin / Codeium: Devin ATIF JSON/JSONL transcripts and Codeium Windsurf Cascade trajectory markdown files from
~/.local/share/devin/cli/transcripts,~/.config/devin,~/.devin,~/.codeium/windsurf/cascade(or underDEVIN_HOME,DEVIN_SESSION_ID). When running inside Devin or Windsurf Cascade, or via--devin/--codeium,mmreads the transcript. - Freebuff / Codebuff: Chat transcripts from
~/.config/manicode/projects/<project>/chats/<chat-id>/chat-messages.json(or underFREEBUFF_CONFIG_DIR,CODEBUFF_CONFIG_DIR,FREEBUFF_HOME, orFREEBUFF_SESSION_ID). When running inside Freebuff / Codebuff, or via--freebuff/--codebuff,mmreads the chat messages.
mm --chat --print # preview copied message (auto-detects OpenCode, Antigravity, Claude Code, Codex, Devin, or Freebuff)
mm --opencode --print # preview message from the current OpenCode session
mm --agy --print # preview message from the latest Antigravity log
mm --claude --print # preview message from the latest Claude Code log
mm --codex --print # preview message from the latest Codex log
mm --devin --print # preview message from the latest Devin / Cascade log
mm --freebuff --print # preview message from the latest Freebuff / Codebuff log
mm --freebuff # commit using the latest Freebuff / Codebuff logThe message is built like this:
- Subject: The full selected title or first line is preserved unless an explicit commitlint length rule requires shortening.
- For OpenCode, the session title is used (or the first line of the latest user prompt, if untitled).
- For Antigravity, the first line of the latest user request (extracted from
<USER_REQUEST>and stripped of@file references) is used. If absent, the first line of the assistant message is used. - For Claude Code, the first line of the latest user prompt is used (tool execution results are skipped). If absent, the first line of the assistant message is used.
- For Codex, the thread title from
session_index.jsonlis used if available; otherwise the first line of the latest user prompt is used. If absent, the first line of the assistant message is used. - For Devin / Codeium, the session title is used if available; otherwise the first line of the latest user prompt is used. If absent, the first line of the assistant message is used.
- For Freebuff / Codebuff, the first user prompt (or session title from
chat-meta.jsonif available) is used. If absent, the first line of the assistant message is used.
- Body: The last assistant response (
PLANNER_RESPONSE, Claude Code text message, Codex assistant message/task completion, Devin assistant message, Freebuff assistant message, or OpenCode assistant text message). Tool calls, reasoning/thinking, and metadata are excluded. --opencodeor-oforces copying from OpenCode (OPENCODE_SESSION_ID, or a session passed with--chat-session).--agyor--antigravityforces copying from Antigravity logs.--claudeforces copying from Claude Code logs.--codexforces copying from Codex logs.--devinor--codeiumforces copying from Devin / Codeium logs.--freebuffor--codebuffforces copying from Freebuff / Codebuff logs.--agy-session <ID>,--claude-session <ID>,--codex-session <ID>,--devin-session <ID>,--freebuff-session <ID>, or--chat-session <ID>selects a specific session or transcript path and implies--chat.--messageoverrides the subject; the copied assistant message remains the body.- Privacy and path sanitization: Absolute user file links (such as
file:///Users/user/...orfile:///home/user/...) copied from chat logs are automatically replaced with~/...to prevent leaking local user names in commit messages. Paths referring to the current repository directory (such as~/w/auto-commit/rust/orfile:///.../rust/) are replaced with./. - Commitlint prefix auto-formatting: When Conventional Commits are enabled (the default),
mmformats the chat subject according to commitlint rules (e.g.@commitlint/config-conventional). If the copied subject lacks a conventionaltype:prefix,mminfers one (usingdocswhen all changed files are markdown files, otherwisefeatif the text includes keywords likeadd,create, orimplement, otherwise from git changes), formats the subject casing, strips trailing full stops (such as...or.), and fits the header within length limits. The assistant response body is preserved.
--chat / --opencode / --agy / --claude / --codex / --devin / --freebuff uses the chat subject (shaped by commitlint when enabled) and copied body. If copying the log from
the AI Agent fails (for example, missing session, no transcript found, CLI failure,
or empty messages), mm automatically falls back to path-based commit message
generation instead of aborting with an error. In --verbose mode, the failure and
fallback are reported on stderr. It still uses the normal change selection,
--add, --staged, --edit, --no-verify, and --print behavior.
mm searches upward from the current directory, stopping at $HOME, and reads
the same commitlint file names as cosmiconfig, including package.json and
package.yaml. JSON and YAML are parsed natively. JavaScript and TypeScript
configs are evaluated with node; extends presets additionally require
@commitlint/load in the project.
Applied rules are the ones that affect the generated header: type and scope
enums/case, type/scope presence, type/header/subject length, and trailing full
stops. Rules for bodies, footers, and trailers are not applied to generated
messages because mm writes only a one-line header. In chat modes (--chat, --opencode, --agy, --claude, --codex, --devin, and --freebuff), mm applies header shaping rules (type prefix, casing, length, and trailing full stop removal) to the subject line, while copying the assistant body intact.
Use an explicit config with:
mm --config path/to/commitlint.config.js --printmm -p # preview without committing
mm -a # include untracked files, then commit
mm -s # commit only what is already staged
mm -e # review the message in an editor
mm --no-verify # bypass Git hooks
mm --chat # copy the current OpenCode or Antigravity chat
mm --opencode --print # preview the current OpenCode session
mm --agy --print # preview the last Antigravity conversation log
mm --agy-session conv-123 # commit with a specific Antigravity conversation
mm --devin --print # preview the last Devin / Cascade conversation log
mm --devin-session sess-123 # commit with a specific Devin session
mm --freebuff --print # preview the last Freebuff / Codebuff conversation log
mm --freebuff-session sess-123 # commit with a specific Freebuff session
mm --chat-session ses_123 --print # preview another OpenCode session
mm -m 'JIRA-1 fix: keep this' # keep an existing prefix/description
mm --verbose --print # explain what mm is doing on stderr
mm --completions fish > ~/.config/fish/completions/mm.fish # install fish completions- The generated message goes to stdout.
- Errors go to stderr as
Error: .... - Verbose diagnostics go to stderr with an
[mm]prefix. - Exit status is
0on success (including--help,--version,--completions, and--print) and1on failure.
Unable to generate message as no changes files can be seen
Nothing is staged and there are no tracked changes. Stage files with git add,
or use mm --add to include untracked files.
not a git repository
Run mm inside a Git work tree.
A JavaScript/TypeScript config is ignored or warns about extends
Install Node.js, and install @commitlint/load in the project that owns the
config if you use presets.
mm.json: conventionalCommits must be a boolean
The setting must be true or false. Fix the value in mm.json; an invalid
config stops mm before it stages or commits anything.
no OpenCode session selected
--chat or --opencode needs OPENCODE_SESSION_ID from a running OpenCode
session. Set it, or pass --chat-session <ID>.
could not run opencode session export
Check that the opencode CLI is installed and on PATH, and that the session ID
exists.
has no assistant text or completed compaction summary to copy
The session has no assistant reply or completed compaction yet. Send a prompt
and wait for the answer, then rerun mm --chat.
mm: command not found
Check that ~/.cargo/bin is in PATH, then start a new zsh session or run
rehash.
An old ac command still exists
This tool was renamed to mm. Reinstall with the command above; any previously
installed ac binary is separate and must be removed manually.