Created
April 8, 2026 18:38
-
-
Save JustSuperHuman/ac71e551688655bfe8dd6f73c8d3ea30 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Auto-Generate Custom AI Git Commit Messages with Cursor | |
| When you commit without a message, a git hook calls an AI CLI tool to read your changes and write the message for you. | |
| ## How It Works | |
| 1. You run `git commit` with no `-m` message | |
| 2. Git runs the `prepare-commit-msg` hook | |
| 3. The hook sees the message is empty, grabs your staged diff, and pipes it to an AI CLI | |
| 4. The AI reads your diff + your commit style rules and writes the message | |
| 5. Git uses that generated message for the commit | |
| ## Setup (3 files) | |
| ### 1. The Hook — `scripts/hooks/prepare-commit-msg` | |
| This is a bash script that git runs before every commit. It checks if the message is empty, and if so, asks the AI to write one. | |
| ```bash | |
| #!/usr/bin/env bash | |
| COMMIT_MSG_FILE="$1" | |
| COMMIT_SOURCE="$2" | |
| # Skip merges, squashes, amends | |
| if [ "$COMMIT_SOURCE" = "merge" ] || [ "$COMMIT_SOURCE" = "squash" ] || [ "$COMMIT_SOURCE" = "commit" ]; then | |
| exit 0 | |
| fi | |
| # If there's already a message, leave it alone | |
| MSG=$(sed '/^#/d;/^$/d' "$COMMIT_MSG_FILE") | |
| if [ -n "$MSG" ]; then | |
| exit 0 | |
| fi | |
| # Find your rules file | |
| REPO_ROOT="$(git rev-parse --show-toplevel)" | |
| RULES_FILE="$REPO_ROOT/path/to/your-commit-rules.md" # <-- change this | |
| if [ ! -f "$RULES_FILE" ]; then | |
| exit 0 | |
| fi | |
| # Check the AI tool exists | |
| # | |
| # Swap "claude" for whatever CLI you use: | |
| # - claude -p (Claude Code) | |
| # - gh copilot (GitHub Copilot CLI) | |
| # - aichat (aichat) | |
| # - llm (simon willison's llm) | |
| # - Any CLI that reads stdin and writes to stdout | |
| # | |
| AI_CMD="claude" | |
| if ! command -v "$AI_CMD" >/dev/null 2>&1; then | |
| echo "Warning: '$AI_CMD' not found, skipping." >&2 | |
| exit 0 | |
| fi | |
| DIFF=$(git diff --cached --stat) | |
| DIFF_DETAIL=$(git diff --cached) | |
| # Truncate huge diffs so you don't blow up the prompt | |
| DIFF_LEN=${#DIFF_DETAIL} | |
| if [ "$DIFF_LEN" -gt 12000 ]; then | |
| DIFF_DETAIL="$(printf '%s' "$DIFF_DETAIL" | head -c 12000) | |
| ... (truncated)" | |
| fi | |
| RULES=$(cat "$RULES_FILE") | |
| PROMPT=$(cat <<ENDPROMPT | |
| Generate a git commit message for these changes. Follow these rules: | |
| $RULES | |
| Diff summary: | |
| $DIFF | |
| Detailed diff: | |
| $DIFF_DETAIL | |
| Reply with ONLY the commit message. Nothing else. | |
| ENDPROMPT | |
| ) | |
| echo "Generating commit message..." >&2 | |
| # Swap this line for your AI tool: | |
| GENERATED_MSG=$(printf '%s' "$PROMPT" | claude -p 2>/dev/null) | |
| # Examples for other tools: | |
| # GENERATED_MSG=$(printf '%s' "$PROMPT" | gh copilot suggest 2>/dev/null) | |
| # GENERATED_MSG=$(printf '%s' "$PROMPT" | llm 2>/dev/null) | |
| # GENERATED_MSG=$(printf '%s' "$PROMPT" | aichat 2>/dev/null) | |
| if [ $? -ne 0 ] || [ -z "$GENERATED_MSG" ]; then | |
| echo "Warning: AI failed, proceeding with empty message." >&2 | |
| exit 0 | |
| fi | |
| TMPFILE="$COMMIT_MSG_FILE.tmp" | |
| printf '%s\n' "$GENERATED_MSG" > "$TMPFILE" | |
| grep '^#' "$COMMIT_MSG_FILE" >> "$TMPFILE" 2>/dev/null || true | |
| mv "$TMPFILE" "$COMMIT_MSG_FILE" | |
| echo "Commit message generated." >&2 | |
| ``` | |
| ### 2. The Setup Script — `scripts/git-config.js` | |
| Tells git to use your tracked hooks folder instead of `.git/hooks/` (which isn't tracked). | |
| ```js | |
| const { execSync } = require("child_process"); | |
| const { chmodSync, readdirSync } = require("fs"); | |
| const path = require("path"); | |
| const hooksDir = path.join(__dirname, "hooks"); | |
| // Make hooks executable (needed on macOS/Linux) | |
| for (const file of readdirSync(hooksDir)) { | |
| try { chmodSync(path.join(hooksDir, file), 0o755); } catch {} | |
| } | |
| // Point git at our hooks folder | |
| execSync("git config core.hooksPath scripts/hooks", { | |
| cwd: path.resolve(__dirname, ".."), | |
| stdio: "inherit", | |
| }); | |
| console.log("Git hooks configured."); | |
| ``` | |
| ### 3. Wire It Up — `package.json` | |
| ```json | |
| { | |
| "scripts": { | |
| "git-config": "node scripts/git-config.js", | |
| "postinstall": "node scripts/git-config.js" | |
| } | |
| } | |
| ``` | |
| `postinstall` means anyone who runs `npm install` / `bun install` / `yarn` gets the hooks set up automatically. | |
| ## That's It | |
| - `git commit` with no message → AI writes it | |
| - `git commit -m "my message"` → your message is used as-is | |
| - No AI tool installed? → hook silently skips, normal git behavior | |
| - Works on Windows (Git Bash), macOS, and Linux |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment