mm is a Rust rewrite of the MichaelCurrin/auto-commit-msg logic with:
- Copying AI Agent chat logs with only one command.
- "commitlint" rules.
- No AI, save tokens.
It generates a Conventional Commit message from Git changes and commits in one step:
mmBy default there is no AI or network call. The message is built from the paths
and change types that Git reports.
If the project has a commitlint configuration, its type,
scope and length rules are applied too. Inside OpenCode, Antigravity, Claude Code, Codex, Devin / Codeium, or Freebuff / Codebuff, --chat, --opencode, --agy, --claude, --codex, --devin, or --freebuff
can copy the session/prompt and last assistant message instead; see
Using an OpenCode, Antigravity, Claude Code, Codex, Devin / Codeium, or Freebuff / Codebuff chat.
- Ask AI Agents, LLMs to commit changes is waste of tokens.
- Commit messages already is logs from AI Agent.
- Simple create, update, delete a file not worth for AI Agent.
brew tap loclv/tap
brew install mmInstall pre-compiled binaries fast without building from source:
cargo binstall mmDownload pre-built archives for Linux (x86_64, aarch64), macOS (Intel, Apple Silicon), and Windows from GitHub Releases.
cargo install --path .To build without installing:
cargo build --release
./target/release/mm --helpmm # Generate a message and commit
mm --print # Show the message without committing
mm --add # Stage everything (including new files), then commit
mm --edit # Open the editor before committing
mm --staged # Use only staged changes
mm --chat # Copy the current OpenCode, Antigravity, Claude Code, Codex, Devin, or Freebuff chat
mm -o # Copy the current OpenCode session (same as --opencode)
mm --agy # Copy the latest Antigravity log commit message
mm --claude # Copy the latest Claude Code log commit message
mm --codex # Copy the latest Codex log commit message
mm --devin # Copy the latest Devin / Cascade log commit message
mm --freebuff # Copy the latest Freebuff / Codebuff log commit message
mm -m 'JIRA-1: fix' # Keep an existing prefix/description (or override a chat subject)
mm --completions zsh # Print shell completions (bash, zsh, or fish)Run mm --help for the full option list. See docs/usage.md
for macOS/zsh installation, the complete command reference, and troubleshooting.
Generated headers follow the
Conventional Commits specification
by default, for example feat: create foo.rs or chore: update 5 files. When
no type can be inferred, mm uses chore.
To disable the convention, add mm.json in the project (the file is searched
for from the current directory upwards, stopping at your home directory):
{
"conventionalCommits": false
}Messages then use plain descriptions such as update README.md. An explicit
--message prefix is still preserved, and commitlint rules still apply.
By default, mm automatically stages all changes (including untracked files)
when running without any staged changes. To disable this, set "autoAdd": false in mm.json:
{
"autoAdd": false
}mm can extract the commit message from OpenCode, Antigravity, Claude Code, Codex, Devin / Codeium, or Freebuff / Codebuff chats:
mm --chat --print # preview the 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 log- OpenCode:
OPENCODE_SESSION_IDselects the session;--opencode/-oforces OpenCode. The subject is the session title (or latest user prompt line), and the body is the last assistant message (or compaction summary). - Antigravity: Transcript logs from
~/.gemini/antigravity-ide/brain(orANTIGRAVITY_CONVERSATION_ID) are read. The subject is the latest user request line, and the body is the last assistant response. - Claude Code: Transcript logs from
~/.claude/projects/(orCLAUDE_SESSION_ID) are read. The subject is the latest user prompt line, and the body is the last assistant text response. - Codex: Transcript logs from
~/.codex/sessions/(orCODEX_SESSION_ID) are read. The subject is the thread title fromsession_index.jsonl(or latest user prompt line), and the body is the last assistant response. - 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(orDEVIN_HOME,DEVIN_SESSION_ID) are read. The subject is the session title or latest user prompt line, and the body is the last assistant text response. - Freebuff / Codebuff: Chat transcripts from
~/.config/manicode/projects/<project>/chats/<chat-id>/chat-messages.json(orFREEBUFF_CONFIG_DIR,CODEBUFF_CONFIG_DIR,FREEBUFF_HOME, orFREEBUFF_SESSION_ID) are read. The subject is the first user prompt line (or session title fromchat-meta.json), and the body is the last assistant text response. --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.--messageoverrides the subject; the copied message remains the body.- User file URIs like
file:///Users/user/...are sanitized to~/...to prevent leaking local user names, and references to the current repository directory are sanitized to./. - When Conventional Commits are enabled, commitlint rules shape the copied chat subject: inferring a missing conventional
type:prefix from git changes, formatting casing, stripping forbidden trailing full stops, and fitting length limits. The assistant body is copied intact. - If copying the log from the AI Agent fails,
mmautomatically falls back to generating the commit message from Git changes.
- If anything is staged, only staged changes are used.
- Otherwise, tracked changes in the working tree are used and the commit is run
as
git commit -m MSG .. - Untracked files are ignored unless
--addis passed.
This matches the original gacm behaviour. Use --print when you want to see
the message without changing the repository.
mm reads the project's commitlint configuration and applies the rules which
affect message generation. All formats supported by commitlint are handled:
package.jsonorpackage.yamlwith acommitlintfield.commitlintrc,.commitlintrc.json,.commitlintrc.yaml,.commitlintrc.yml.commitlintrc.js,.commitlintrc.cjs,.commitlintrc.mjs.commitlintrc.ts,.commitlintrc.cts,.commitlintrc.mtscommitlint.config.js,commitlint.config.cjs,commitlint.config.mjscommitlint.config.ts,commitlint.config.cts,commitlint.config.mts
The file is searched for from the current directory upwards, stopping at your
home directory, in the same order as commitlint (cosmiconfig). Use
mm --config path/to/file to point at a specific file.
JSON and YAML configs are parsed natively. JavaScript and TypeScript configs are
evaluated with Node, and extends presets are resolved with @commitlint/load
when it is installed. If it is not installed, the config file is imported
directly and a warning is printed because presets cannot be resolved.
Applied rules:
| Rule | Effect |
|---|---|
type-enum |
Restricts the generated type; falls back to an allowed type |
type-case |
Changes the type case, e.g. upper-case gives FEAT |
type-empty |
Requires a type, or removes it when it must be empty |
type-max-length |
Chooses a shorter type or truncates |
scope-enum, scope-empty |
Keeps or drops a scope such as deps |
scope-case |
Changes the scope case |
header-max-length |
Falls back to counted descriptions, then truncates |
subject-max-length |
Truncates the subject |
header-full-stop, subject-full-stop |
Removes a forbidden trailing full stop |
Rules about bodies, footers, commit trailers and the case of free-form subjects
are not applied to generated messages, because mm writes only a header. An
explicit --message value is treated as an override and its prefix is kept.
In chat modes (--chat, --opencode, --agy, --claude, --codex, --devin), header shaping rules
(type prefix, casing, length, and trailing full stop removal) are applied to the
subject, while copying the assistant body intact.
# .commitlintrc.yml
# rules:
# type-enum: [2, always, [feat, fix, chore]]
# header-max-length: [2, always, 72]
mm --print- One file:
feat: create foo.rs,docs: update README.md,chore: delete old.txt,chore: rename a.rs to b.rs - Two to four files: names are listed grouped by action, e.g.
update src/main.rs and src/lib.rsordocs: update guide.md and create api.md - Five or more files: counts are used, e.g.
update 5 files - The type is inferred from the changed paths. When changes disagree or a type
cannot be determined, the default is
chore - Prefixes come from the path: docs (
README,docs/,.rst), 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 --messagekeeps your existing type prefix and appends your description after the generated one
./test.shOr run individual commands:
cargo test
cargo fmt --check
cargo clippy --all-targets -- -D warningsThe crate only depends on serde_json and serde_norway for reading
configuration files, and is split into modules which mirror the original
TypeScript files:
| Module | Original |
|---|---|
git.rs |
git/cli.ts, git/parseOutput.ts |
paths.rs |
lib/paths.ts, lib/commonPath.ts |
conventional.rs |
generate/convCommit.ts, generate/convCommitConstants.ts |
message.rs |
generate/action.ts, generate/count.ts, generate/message.ts |
generate.rs |
prepareCommitMsg.ts, generate/parseExisting.ts |
cli.rs |
cli/diffIndexGenerate.ts, cli/diffIndexGenerateCommit.ts |
config.rs |
New: reads mm.json settings |
chat.rs |
New: copies a title and last message from an OpenCode session |
commitlint.rs |
New: finds and loads the project's commitlint configuration |
policy.rs |
New: applies commitlint rules to a generated message |
completions.rs |
New: generates bash, zsh, and fish completion scripts |
The selection of changes and the path-based message rules follow gacm. These
parts differ on purpose:
- Messages use Conventional Commits by default, so a
choretype is added when no type can be inferred. Set"conventionalCommits": falseinmm.jsonto restore plain descriptions. --chatcan copy the title and last assistant message from an OpenCode session instead of deriving the message from file paths.- The commit editor is off by default; pass
--editto open it.-nand--no-editare accepted so existing scripts keep working. --print(--dry-run) prints the message and never touches the repository.- Paths are read with
git diff-index --name-status -z, so spaces, quotes and unicode in file names do not need escaping. - A repository without commits yet is supported by diffing against the empty tree.
MIT