Generate structured commit messages from a local git diff, using a Discord bot and the Groq API.
- Overview
- Architecture
- Groq Integration
- Installation
- Usage
- Configuration Reference
- Troubleshooting
- Security
- Uninstall
gmd consists of two components:
- CLI (
gmd): reads the uncommittedgit diffin the current repository, splits it into chunks that fit Discord's message limit, and posts them to a channel through a webhook. - Discord bot: monitors that channel, reassembles the chunks, sends the complete diff to Groq, and posts a formatted commit message as an embed.
Example output:
[mb1425] feat(robot): Add RobotArm class with pick-and-place functionality
- Implement RobotArm class to interface with CoppeliaSim
- Add methods for depth reading and pixel-to-robot conversion
- Include configuration handling and camera-to-robot transformation logic
- Commit messages in a consistent
[shortcode] type(scope): Subjectformat - Line counts (
+A/-R) computed in code rather than estimated by the model - Automatic chunking of large diffs to fit Discord's 2000-character limit
- Retry handling for Discord rate limits (HTTP 429)
- Idle flush: if the end signal is lost, the bot processes the buffered diff after 15 seconds
- Automatic deletion of raw diff chunks from the channel
- Mapping from git emails to shortcodes and Discord users through a YAML file
- Errors are reported in the channel rather than failing silently
flowchart LR
Dev([Developer]) -->|runs gmd| CLI[diff_cli.py]
CLI -->|reads diff| Git[(Git repository)]
CLI -->|posts chunks and end signal| Discord{{Discord channel}}
Discord -->|dispatches messages| Bot[diff_analyzer_bot.py]
Bot -->|buffers chunks, deletes originals| Bot
Bot -->|sends assembled diff| Groq[[Groq API]]
Groq -->|returns JSON| Bot
Bot -->|posts embed| Discord
Cfg[config.yaml] -.-> CLI
Cfg -.-> Bot
Processing sequence:
gmdrunsgit diff, removes empty lines, and splits the output into chunks of up to 1980 characters.- Each chunk is posted through the webhook, followed by a unique end marker:
<<<GMD_END_OF_DIFF_7f3a>>>. - The bot buffers chunks per webhook and deletes each message as it arrives.
- When the end marker arrives, or 15 seconds pass without a new chunk, the bot sends the full diff to Groq.
- The bot parses the response, applies formatting corrections, and posts the result as an embed.
The bot uses the official asynchronous Groq Python SDK (AsyncGroq) and calls the chat completions endpoint with the openai/gpt-oss-20b model.
| Message | Content |
|---|---|
system |
Commit format rules, commit type priority order, and the required JSON output structure |
user |
List of changed files, exact line counts, and the assembled diff |
The diff is truncated to 24,000 characters before submission to remain within prompt limits. The file list and line counts are computed from the full diff, so the model is informed of every changed file even when truncation occurs.
| Parameter | Value | Rationale |
|---|---|---|
model |
openai/gpt-oss-20b |
Fast, low-cost reasoning model |
temperature |
0.2 |
Limits variation between runs |
seed |
42 |
Best-effort reproducibility |
max_tokens |
8192 |
Reasoning tokens count against this budget |
reasoning_effort |
"low" |
Prevents the model from exhausting its budget on reasoning |
The model returns a JSON object containing the commit message. The bot then:
- Extracts the JSON block with a regular expression, falling back to field-level extraction if parsing fails.
- Replaces the line count with the value computed in code.
- Capitalises the first letter after the subject colon and after each bullet point.
- Removes any text after the last bullet point.
- Trims fields to Discord's embed size limits.
The prompt selects the first matching type in the following order, which keeps classification consistent for mixed diffs:
| Type | Condition |
|---|---|
feat |
The diff adds any new capability, function, option or behaviour |
fix |
The diff mainly corrects incorrect behaviour and adds nothing new |
refactor |
Code is restructured with no change in behaviour |
docs |
Only documentation or comments changed |
chore |
Only configuration, dependencies, build or tooling changed |
Data handling: diffs are transmitted to Groq's API. Do not use gmd on code that may not be shared with a third-party service.
- Python 3.9 or newer
git, available onPATH- A Discord server in which you can create webhooks and add a bot
- A Groq account
git clone https://github.com/<your-username>/<your-repo>.git
cd <your-repo>Expected layout:
.
├── src/
│ ├── diff_analyzer_bot.py # Discord bot (runs on the server)
│ ├── diff_cli.py # CLI (installed as `gmd`)
│ └── config_loader.py # Shared YAML loader
├── config.example.yaml
├── install.sh
└── .env # created by you (bot secrets)
pip install discord.py python-dotenv groq pyyaml requestsThe CLI runs with the system python3, so requests and pyyaml must be installed for that interpreter as well.
- Sign in at console.groq.com.
- Open API Keys and select Create API Key.
- Copy the key. Groq keys begin with
gsk_.
- Open the Discord Developer Portal and select New Application.
- On the Bot tab, select Reset Token and copy the token.
- On the same tab, enable the following Privileged Gateway Intents:
- Message Content Intent
- Server Members Intent
- Under OAuth2 > URL Generator, select the
botscope and grant these permissions:- View Channel
- Send Messages
- Embed Links
- Read Message History
- Manage Messages (required to delete diff chunks)
- Open the generated URL and add the bot to your server.
- In Discord, open User Settings > Advanced and enable Developer Mode.
- Right-click the target channel and select Copy Channel ID.
- Open Channel Settings > Integrations > Webhooks, create a webhook, and copy its URL.
Create a secrets file in the repository root:
# .env
DISCORD_TOKEN=your_discord_bot_token
GROQ_API_KEY=gsk_your_groq_keyCreate the configuration file:
cp config.example.yaml config.yamlEdit config.yaml:
discord:
webhook_url: "https://discord.com/api/webhooks/ID/TOKEN"
target_channel_id: 123456789012345678
cli:
max_chunk_length: 1980
delay_seconds: 1
users:
jdoe:
shortcode: "jdoe"
discord_username: "JohnDoe"
git_emails:
- "john@example.com"
- "john@work.com"Add the following to .gitignore before the first commit:
.env
config.yamlThe webhook URL allows anyone who holds it to post to your channel and must not be committed.
chmod +x install.sh
./install.shThe script copies the CLI, the config loader and config.yaml to ~/.gmd-tool and creates a gmd command in ~/.local/bin. If gmd is not found afterwards, add the following to your shell profile:
export PATH="$HOME/.local/bin:$PATH"Note:
gmdruns an installed copy of the code. After changingsrc/diff_cli.py,src/config_loader.pyorconfig.yaml, run./install.shagain.
python3 src/diff_analyzer_bot.pyExpected output:
Logged in as YourBot#1234 (ID: ...)
Monitoring and formatting channel ID: 123456789012345678
To keep the bot running after the terminal is closed, use tmux, screen, or a systemd service.
From any git repository with uncommitted changes:
gmdThe terminal prints Successfully generated commit message on discord, and the formatted result appears in the Discord channel shortly afterwards.
Note:
gmduses plaingit diff, which includes unstaged changes only. Staged changes are not included.
| Key | Description |
|---|---|
discord.webhook_url |
Webhook to which the CLI posts diff chunks |
discord.target_channel_id |
Channel monitored by the bot |
cli.max_chunk_length |
Maximum characters per webhook message (keep at or below 1980) |
cli.delay_seconds |
Pause between chunks to avoid Discord rate limits |
users.<key>.shortcode |
Short identifier used as the commit prefix, for example [jdoe] |
users.<key>.discord_username |
Discord username mentioned in the result |
users.<key>.git_emails |
All git email addresses that map to this user |
If the git email is not listed under users, the shortcode resolves to UNKNOWN_USER.
Defined near the top of diff_analyzer_bot.py:
| Constant | Default | Purpose |
|---|---|---|
IDLE_FLUSH_SECONDS |
15 |
Time to wait after the last chunk before processing without an end signal |
MAX_DIFF_CHARS |
24000 |
Maximum number of diff characters sent to Groq |
| Symptom | Likely cause and resolution |
|---|---|
| Chunks disappear and nothing is posted | The end signal was not received. Run ./install.sh again and confirm END_SIGNAL is identical in diff_cli.py and diff_analyzer_bot.py. The idle flush should still process the diff after 15 seconds |
| Chunks remain visible in the channel | The bot lacks the Manage Messages permission |
Error: config.yaml not found |
Copy config.example.yaml to config.yaml and run ./install.sh again |
Error: Tokens are missing |
Confirm .env exists and defines DISCORD_TOKEN and GROQ_API_KEY |
| Bot cannot read messages or resolve users | Enable Message Content Intent and Server Members Intent in the Developer Portal |
gmd: command not found |
Add ~/.local/bin to PATH |
Failed to send message to discord |
Verify the webhook URL. The HTTP error is printed above the message |
Diff analysis failed in the channel |
The error is posted to Discord and the full traceback is written to the bot console |
| Duplicate or unexpected responses | An earlier bot process is still running. Check ps aux | grep diff_analyzer_bot and stop duplicates |
| Incorrect line counts or missing changes | The CLI strips blank added and removed lines, and diffs above MAX_DIFF_CHARS are truncated |
- Keep
.envandconfig.yamlout of version control. - Grant the bot only the permissions it requires.
- Be aware that diffs are sent to Groq for processing.
- If a secret is exposed, rotate it immediately: delete the webhook, reset the Discord bot token, and revoke the Groq API key. Removing a file in a later commit does not remove it from git history.
rm -rf ~/.gmd-tool ~/.local/bin/gmd