AINode.jsAutomation

AI Git Commit Message Generator

TT
TopicTrick Team
AI Git Commit Message Generator

AI Git Commit Message Generator

Never stare at a blank commit message field again. This CLI tool reads your staged diff with git diff --staged, sends it to GPT-4o, and returns a Conventional Commits-formatted message with type, scope, description, and body — in seconds.

This is Tool 26 of the Build 50 AI Automation Tools course.


What You'll Build

  • CLI: node commit.js — generate a commit message for staged changes
  • Conventional Commits format: feat(auth): add OAuth2 login flow
  • Optional body with bullet-point summary of changes
  • Git hook integration for automatic suggestions

Setup

bash
mkdir ai-commit && cd ai-commit
npm init -y
npm install openai dotenv
bash
# .env
OPENAI_API_KEY=sk-your-key-here

Commit Generator

js
// src/commit.js
import 'dotenv/config';
import { execSync } from 'child_process';
import OpenAI from 'openai';
import readline from 'readline';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

function getStagedDiff() {
  try {
    const diff = execSync('git diff --staged', { encoding: 'utf-8' });
    if (!diff.trim()) {
      console.error('No staged changes. Run: git add <files> first.');
      process.exit(1);
    }
    return diff;
  } catch {
    console.error('Not a git repository or git is not installed.');
    process.exit(1);
  }
}

function getRecentCommits() {
  try {
    return execSync('git log --oneline -5', { encoding: 'utf-8' }).trim();
  } catch {
    return '';
  }
}

async function generateCommitMessage(diff, recentCommits) {
  const truncatedDiff = diff.length > 15_000
    ? diff.slice(0, 15_000) + '\n[Diff truncated — showing first 15,000 chars]'
    : diff;

  const recentContext = recentCommits
    ? `\nRecent commits for context:\n${recentCommits}\n`
    : '';

  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      {
        role: 'system',
        content: `You are an expert software engineer who writes excellent git commit messages.
Analyze the staged diff and generate a Conventional Commits message.
${recentContext}
Conventional Commits format:
  <type>(<scope>): <short description>
  
  [optional body: bullet points explaining what changed and why]
  
  [optional footer: BREAKING CHANGE: ..., Closes #123]

Types: feat | fix | docs | style | refactor | perf | test | chore | ci | build
- scope: the module, component, or file area changed (e.g. auth, api, ui, db)
- description: imperative mood, lowercase, max 72 chars, no period
- body: only if the change is complex; bullet points starting with "-"

Return ONLY a JSON object:
{
  "message": "complete commit message including body if needed",
  "type": "feat | fix | docs | etc",
  "scope": "string or null",
  "description": "the short description line",
  "hasBreakingChange": true/false,
  "alternatives": ["2 alternative shorter commit messages"]
}`,
      },
      {
        role: 'user',
        content: `Staged diff:\n${truncatedDiff}`,
      },
    ],
    temperature: 0.3,
    response_format: { type: 'json_object' },
  });

  return JSON.parse(response.choices[0].message.content);
}

async function confirmAndCommit(message) {
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });

  return new Promise(resolve => {
    console.log('\n' + '─'.repeat(60));
    console.log('Generated commit message:\n');
    console.log(message);
    console.log('─'.repeat(60));
    rl.question('\n[C]ommit / [E]dit / [A]lternatives / [Q]uit? ', answer => {
      rl.close();
      resolve(answer.toLowerCase());
    });
  });
}

async function main() {
  const diff = getStagedDiff();
  const recentCommits = getRecentCommits();

  console.log('Analysing staged changes...');
  const result = await generateCommitMessage(diff, recentCommits);

  const choice = await confirmAndCommit(result.message);

  if (choice === 'c') {
    execSync(`git commit -m ${JSON.stringify(result.message)}`, { stdio: 'inherit' });
    console.log('✅ Committed!');
  } else if (choice === 'a') {
    console.log('\nAlternatives:');
    result.alternatives.forEach((alt, i) => console.log(`  ${i + 1}. ${alt}`));
  } else if (choice === 'e') {
    console.log('Run: git commit (the message is in your clipboard if you copy it)');
    console.log(result.message);
  } else {
    console.log('Aborted.');
  }
}

main().catch(console.error);

Install Globally

bash
# Add to package.json scripts
# "commit": "node src/commit.js"

# Or link globally
npm link
# Then use: ai-commit (if bin field set)

Git Hook Integration

bash
#!/bin/sh
# .git/hooks/prepare-commit-msg

COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2

# Only run on new commits (not amend, merge, etc.)
if [ -z "$COMMIT_SOURCE" ]; then
  echo "Generating AI commit message..."
  node /path/to/your/ai-commit/src/hook.js "$COMMIT_MSG_FILE"
fi
js
// src/hook.js — writes suggestion to commit msg file
import 'dotenv/config';
import { execSync, writeFileSync } from 'fs';
import { generateCommitMessage } from './commitService.js';

const msgFile = process.argv[2];

try {
  const diff = execSync('git diff --staged', { encoding: 'utf-8' });
  if (!diff.trim()) process.exit(0);

  const result = await generateCommitMessage(diff, '');
  writeFileSync(msgFile, result.message);
} catch {
  // Fail silently — don't block the commit
  process.exit(0);
}
bash
chmod +x .git/hooks/prepare-commit-msg

Testing

bash
# Stage some changes
git add src/auth/oauth.js

# Generate commit message
node src/commit.js

Sample output:

text
Analysing staged changes...

────────────────────────────────────────────────────────────
Generated commit message:

feat(auth): add OAuth2 login flow with Google provider

- Add GoogleStrategy using passport-google-oauth20
- Store OAuth tokens in users table with refresh token
- Redirect to /dashboard on successful authentication
- Add logout route that clears session and revokes token

────────────────────────────────────────────────────────────

[C]ommit / [E]dit / [A]lternatives / [Q]uit?

Build 50 AI Automation Tools — Tool 26 of 50

Git commit generator is live. Continue to Tool 27 to build an AI blog post generator.


    Summary

    • Staged diff only — reads exactly what's committed, not working tree noise
    • Recent commits context helps the AI maintain consistent type/scope conventions for the repo
    • Interactive flow — review before committing, with alternatives for quick iteration
    • Git hook integrates into standard git commit without disrupting workflow
    • Add --no-body flag for simple one-liners on small changes

    Continue to Tool 27: AI Blog Post Generator →