https://github.com/chpock/gen-commit-msg
AI-powered Git commit message generator for staged changes, built for editor-first workflows with OpenCode.
https://github.com/chpock/gen-commit-msg
ai ai-tools commit-message commit-message-generator git-commit opencode terminal-ui tui-app
Last synced: 10 days ago
JSON representation
AI-powered Git commit message generator for staged changes, built for editor-first workflows with OpenCode.
- Host: GitHub
- URL: https://github.com/chpock/gen-commit-msg
- Owner: chpock
- License: mit
- Created: 2026-05-11T05:48:20.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-16T13:28:25.000Z (about 1 month ago)
- Last Synced: 2026-05-16T13:37:11.687Z (about 1 month ago)
- Topics: ai, ai-tools, commit-message, commit-message-generator, git-commit, opencode, terminal-ui, tui-app
- Language: Go
- Homepage:
- Size: 354 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# gen-commit-msg
`gen-commit-msg` is a Go CLI that generates Git commit message candidates from
staged changes using OpenCode, then lets you pick the best one in a terminal
UI (or outputs one message in non-interactive mode).
The tool is built for editor-first commit workflows: generate inside the commit
message editor, review, then edit before finalizing the commit.
## Why this tool exists
Many AI commit-message tools are either:
- tied to `prepare-commit-msg` hooks (runs every commit, even when you do not
want generation), or
- wrappers that run `git commit` for you.
This project intentionally does neither.
- No Git hook integration.
- No commit execution.
- You keep your existing flow (`git commit`, `lazygit`, editor buffer).
`gen-commit-msg` only generates message text and writes it to stdout (or a file
via `--output`).
## Features
- Collects structured staged-change context via Git before prompting AI.
- Starts and stops `opencode serve` automatically (with health checks).
- Creates and reuses a dedicated OpenCode agent prompt file.
- Interactive progress view for pipeline steps in TTY mode.
- Subject selection list when multiple candidates are generated.
- Non-interactive mode support (`--subject-max 1`) for scripts/editor callbacks.
- Config via flags and `GCM_*` environment variables.
- Optional output file mode (`--output` / `GCM_OUTPUT`).
## Agent installation and customization
On startup, the tool ensures an OpenCode agent file exists.
- Default agent name: `gen-commit-msg`
- Default install mode: `if-not-exists`
- Installed path:
`${XDG_CONFIG_HOME:-$HOME/.config}/opencode/agents/gen-commit-msg.md`
This file contains the agent configuration and generation instructions used for
commit message creation.
You can customize it when needed:
- adjust model settings (for example model name, temperature, steps),
- tune or replace the prompt/instructions for how commit messages are generated.
Important behavior:
- `--install-agent if-not-exists` (default) keeps your custom file intact.
- `--install-agent always` overwrites the file with the built-in default prompt.
- `--install-agent no` skips installation entirely.
## Requirements
- Go 1.26+
- `git` available in `PATH`
- `opencode` available in `PATH`
- Run inside a Git repository
- Have staged changes (`git add ...`) before running
## Install
Build from source:
```bash
make build
```
Binary will be created as `./gen-commit-msg`.
## Quick start
Generate up to 5 candidates and choose one:
```bash
gen-commit-msg
```
Generate exactly one candidate (no selection list):
```bash
gen-commit-msg --subject-max 1
```
Write result to a file instead of stdout:
```bash
gen-commit-msg --output /tmp/commit-msg.txt
```
Then use your normal commit command:
```bash
git commit
```
## Editor workflow (Vim example)
This utility is designed to be called from a commit message buffer.
The snippet below runs `gen-commit-msg`, captures output to a temp file, and
inserts the generated message at the top of the current `gitcommit` buffer.
```vim
let s:gen_commit_msg_height = 5
let s:gen_commit_msg_subject_max = 5
function! GenCommitMsgCallback(orig_bufnr, term_bufnr, tmpfile, job, status)
if a:status == 0
if filereadable(a:tmpfile) && getfsize(a:tmpfile) > 0
let l:output = readfile(a:tmpfile)
call appendbufline(a:orig_bufnr, 0, l:output)
endif
endif
call delete(a:tmpfile)
if a:status == 0 || a:status == 130
execute 'silent! bwipeout! ' . a:term_bufnr
else
echohl WarningMsg | echo 'Command failed with status: ' . a:status | echohl None
endif
endfunction
function! GenCommitMsg()
let l:tmpfile = tempname()
let l:cmd = 'gen-commit-msg --subject-max ' . shellescape(s:gen_commit_msg_subject_max) . ' --output ' . shellescape(l:tmpfile)
let l:orig_bufnr = bufnr('%')
execute 'topleft ' . s:gen_commit_msg_height . 'split | enew'
let l:Callback = function('GenCommitMsgCallback', [l:orig_bufnr, bufnr('%'), l:tmpfile])
call term_start([&shell, &shellcmdflag, l:cmd], {'curwin': 1, 'exit_cb': l:Callback})
endfunction
augroup GitCommitMapping
autocmd!
autocmd FileType gitcommit nnoremap O :call GenCommitMsg()
augroup END
```
Press `O` in a commit buffer to generate candidates and insert the
selected message.
## CLI options
Flag precedence is:
`CLI flag > environment variable > default`
| Flag | Env var | Default | Description |
|---|---|---|---|
| `--subject-min`, `-m` | `GCM_SUBJECT_MIN` | `1` | Minimum subject candidate count |
| `--subject-max`, `-x` | `GCM_SUBJECT_MAX` | `5` | Maximum subject candidate count (max 20) |
| `--body` | `GCM_BODY` | `true` | Generate commit body |
| `--quiet`, `-q` | `GCM_QUIET` | `false` | Hide progress view |
| `--agent`, `-a` | `GCM_AGENT` | `gen-commit-msg` | OpenCode agent name |
| `--install-agent` | `GCM_INSTALL_AGENT` | `if-not-exists` | `always`, `if-not-exists`, `no` |
| `--pause` | `GCM_PAUSE` | `on-error` | Pause before exit: `on`, `off`, `on-error` |
| `--output`, `-o` | `GCM_OUTPUT` | `""` | Write selected message to file |
| `--log-level`, `-l` | `GCM_LOG_LEVEL` | `none` | `trace`, `debug`, `info`, `warn`, `error`, `none` |
| `--log-file` | `GCM_LOG_FILE` | `stderr` | Log destination (`-` for stdout) |
| `--version`, `-V` | — | — | Print version and exit |
| `--help`, `-h` | — | — | Print help and exit |
Additional runtime env vars:
- `NO_COLOR`: disables selection color styling.
- `GCM_TUI_SELECTION_COLORS`: set to `0` to disable selection color styling.
## How it works
Pipeline (interactive mode):
1. Collect staged-change context with multiple Git commands.
2. Ensure OpenCode agent prompt exists.
3. Start OpenCode server and verify readiness.
4. Create OpenCode session.
5. Request structured commit candidates (JSON schema-constrained).
6. Show candidate subjects; user selects one.
7. Cleanup session/server and print (or write) selected message.
The staged context includes summaries (`--name-status`, `--stat`, `--numstat`,
`--summary`, `--dirstat`) and full staged patch (`git diff --cached ...`).
## Non-interactive behavior
- If stdout is not a TTY and `--subject-max > 1`: exits with an error and
suggests `--subject-max 1`.
- If stdout is not a TTY and `--subject-max == 1`: generates silently and prints
one message (or writes to `--output`).
## Exit codes
- `0`: success (including "no staged files" early exit)
- `1`: runtime error
- `2`: flag/config parsing error
## Development
Common commands:
```bash
make fmt
make vet
make lint
make test
make build
make all
```
Single package tests:
```bash
go test -count=1 -race ./internal/config/
```
## Project structure
```text
cmd/gen-commit-msg/main.go # CLI orchestration
internal/config/ # flags + env parsing
internal/git/ # repo checks + staged context collection
internal/agent/ # OpenCode agent prompt management
internal/server/ # opencode serve lifecycle
internal/opencode/ # OpenCode session/prompt client
internal/tui/ # progress + selection Bubble Tea UI
internal/logging/ # slog setup and custom trace level
```
## License
MIT (see `LICENSE`).