Xây dựng AI Commit Message Generator tích hợp Git Hook: Tự động tạo Conventional Commits với Claude và Gemini API

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

Làm việc trong team có 5-6 người mà mỗi người viết commit message theo một phong cách riêng là cơn ác mộng khi review lịch sử git. Mình đã trải qua cảnh đó — git log ra toàn kiểu “fix bug”, “update file”, hay tệ hơn là “asdfsdf”. Từ đó mình bắt đầu tìm cách chuẩn hóa, và cuối cùng chọn một giải pháp khá ngon: dùng AI sinh commit message tự động từ git diff, tích hợp thẳng vào git hook.

Ba cách tiếp cận để chuẩn hóa commit message

Mình đã thử qua ba approach khác nhau trước khi dừng lại ở giải pháp AI. Mỗi cách có trade-off riêng, tùy context team mà chọn.

Approach 1: Commitizen — interactive CLI

Commitizen là tool phổ biến nhất. Thay vì gõ git commit, bạn gõ git cz và tool hỏi từng phần: type, scope, subject, body…

npm install -g commitizen cz-conventional-changelog
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc

# Thay vi git commit, dung:
git cz

Kết quả ra message chuẩn format feat(auth): add JWT refresh token logic. Trông xịn, nhưng vấn đề là mỗi commit mất thêm 30–60 giây để trả lời các câu hỏi. Team mình chạy được 2 tuần rồi người ta bắt đầu bypass bằng git commit -m "fix".

Approach 2: commitlint + husky — enforce validation

Đây là approach “cảnh sát”: không sinh message, chỉ validate rằng message phải đúng format. Nếu sai, hook reject commit.

npm install --save-dev @commitlint/cli @commitlint/config-conventional husky

# Tao config
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

# Setup husky hook
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

Cách này enforce được convention nhưng không giúp developer biết cần viết gì. Thực tế team junior hay bị blocked và phải hỏi lại, gây friction. Validation mà không có guidance thì chỉ tạo frustration.

Approach 3: AI sinh message tự động từ git diff

Approach này mình implement gần đây và thấy hiệu quả nhất cho workflow cá nhân hoặc team nhỏ. Nguyên lý đơn giản: trước khi mở editor để viết commit, tool đọc git diff --cached rồi gửi lên Claude hoặc Gemini API. AI phân tích context và sinh ra message chuẩn Conventional Commits. Developer chỉ cần review và confirm — hoặc chỉnh sửa nhỏ nếu cần.

Phân tích ưu và nhược điểm từng approach

  • Commitizen: Chuẩn hóa tốt, nhưng high friction — developer cần tương tác thủ công mỗi commit. Phù hợp khi team có thời gian và muốn kiểm soát hoàn toàn nội dung message.
  • commitlint: Chỉ validate, không sinh message. Gây khó chịu cho junior dev. Phù hợp khi team đã có convention rõ ràng và muốn enforce cứng trong CI/CD.
  • AI Hook: Tự động, context-aware, low friction. Nhược điểm duy nhất là cần internet và API key. Chi phí thực tế rất rẻ — mỗi commit chỉ tốn vài phần nghìn cent.

Trong quá trình làm việc thực tế, mình nhận thấy commit message quality là kỹ năng quan trọng của developer nhưng thường bị bỏ qua vì không ai muốn thêm bước phức tạp vào workflow. AI hook giải quyết đúng vấn đề đó.

Chọn approach nào cho dự án của bạn

Theo kinh nghiệm của mình:

  • Team lớn, corporate, CI/CD strict: Dùng commitlint + husky để enforce cứng, kết hợp hướng dẫn commitizen cho dev mới.
  • Personal project hoặc team nhỏ linh hoạt: AI Hook là lựa chọn ngon nhất — setup một lần, dùng mãi mà không gây friction.
  • Không muốn phụ thuộc API bên ngoài: Commitizen là lựa chọn an toàn nhất, offline hoàn toàn.

Mình sẽ hướng dẫn triển khai AI Hook vì đây là approach đang dùng và thấy hiệu quả nhất.

Hướng dẫn triển khai AI Commit Message Generator

Bước 1: Chuẩn bị API key

Bạn cần một trong hai:

  • Claude API key: đăng ký tại Anthropic Console — dùng model claude-haiku-4-5 vừa rẻ vừa nhanh, đủ dùng cho task này
  • Gemini API key: lấy tại Google AI Studio — có free tier với Gemini Flash, thực tế miễn phí nếu dùng nhẹ
# Them vao ~/.bashrc hoac ~/.zshrc
export ANTHROPIC_API_KEY="sk-ant-api03-..."
# Hoac neu dung Gemini:
export GEMINI_API_KEY="AIza..."

source ~/.bashrc

Bước 2: Tạo script Python cho git hook

Tạo file ~/.git-hooks/prepare-commit-msg với nội dung sau:

#!/usr/bin/env python3
"""AI Commit Message Generator — prepare-commit-msg hook"""

import subprocess
import sys
import os

CLAUDE_MODEL = 'claude-haiku-4-5-20251001'
GEMINI_MODEL = 'gemini-2.0-flash'

SYSTEM_PROMPT = '''You are a Git commit message expert. Analyze the git diff and write a concise commit message following Conventional Commits:

Format: <type>(<scope>): <short summary>

Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build
- scope: optional, the affected module/component
- summary: imperative mood, lowercase, no period, max 72 chars

Output ONLY the commit message, nothing else.'''


def get_diff():
    stat = subprocess.run(
        ['git', 'diff', '--cached', '--stat'],
        capture_output=True, text=True
    ).stdout.strip()
    diff = subprocess.run(
        ['git', 'diff', '--cached', '--unified=3'],
        capture_output=True, text=True
    ).stdout[:8000]  # Gioi han 8000 ky tu tranh ton qua nhieu token
    return f'=== Stat ===\n{stat}\n\n=== Diff ===\n{diff}'


def call_claude(diff_content):
    import anthropic
    client = anthropic.Anthropic(api_key=os.environ['ANTHROPIC_API_KEY'])
    msg = client.messages.create(
        model=CLAUDE_MODEL,
        max_tokens=100,
        system=SYSTEM_PROMPT,
        messages=[{'role': 'user', 'content': f'Generate commit message:\n\n{diff_content}'}]
    )
    return msg.content[0].text.strip()


def call_gemini(diff_content):
    import google.generativeai as genai
    genai.configure(api_key=os.environ['GEMINI_API_KEY'])
    model = genai.GenerativeModel(GEMINI_MODEL, system_instruction=SYSTEM_PROMPT)
    resp = model.generate_content(
        f'Generate commit message:\n\n{diff_content}',
        generation_config={'max_output_tokens': 100, 'temperature': 0.3}
    )
    return resp.text.strip()


def main():
    commit_msg_file = sys.argv[1]

    # Bo qua merge, squash, amend
    if len(sys.argv) > 2 and sys.argv[2] in ('merge', 'squash', 'commit'):
        sys.exit(0)

    # Neu user da tu nhap message thi skip
    with open(commit_msg_file) as f:
        current = f.read()
    non_comment = '\n'.join(l for l in current.splitlines() if not l.startswith('#'))
    if non_comment.strip():
        sys.exit(0)

    diff = get_diff()
    if not diff.strip():
        sys.exit(0)

    generated = ''
    try:
        if 'ANTHROPIC_API_KEY' in os.environ:
            generated = call_claude(diff)
        elif 'GEMINI_API_KEY' in os.environ:
            generated = call_gemini(diff)
        else:
            print('Warning: No API key found (ANTHROPIC_API_KEY or GEMINI_API_KEY)', file=sys.stderr)
            sys.exit(0)
    except Exception as e:
        # Fail gracefully — khong bao gio block commit vi loi API
        print(f'AI hook error: {e}', file=sys.stderr)
        sys.exit(0)

    if generated:
        with open(commit_msg_file, 'w') as f:
            f.write(generated + '\n\n')
            f.write(current)  # Giu lai comments huong dan
        print(f'AI generated: {generated}', file=sys.stderr)


if __name__ == '__main__':
    main()

Bước 3: Cài đặt hook toàn cục

Cài global để hook hoạt động với mọi repo git trên máy — không cần setup lại mỗi lần clone:

mkdir -p ~/.git-hooks
cp prepare-commit-msg.py ~/.git-hooks/prepare-commit-msg
chmod +x ~/.git-hooks/prepare-commit-msg

# Ap dung cho tat ca repo git tren may
git config --global core.hooksPath ~/.git-hooks

# Cai dependencies
pip install anthropic              # Neu dung Claude
pip install google-generativeai   # Neu dung Gemini

Nếu chỉ muốn cài cho một repo cụ thể (ví dụ repo dự án công ty), bỏ flag --global và copy hook vào thư mục .git/hooks/ của repo đó.

Bước 4: Test thực tế

cd your-project
echo "new feature" >> README.md
git add README.md
git commit  # Khong can -m, hook tu dien message

Git sẽ mở editor với message đã được AI điền sẵn, ví dụ: docs(readme): update project description. Review, save và close editor như bình thường là xong.

Kết quả thực tế và những lưu ý quan trọng

Sau vài tháng dùng setup này hàng ngày, mình rút ra được mấy điểm:

  • AI giỏi nhất với diff có context rõ ràng: thêm function mới, sửa bug có tên biến gợi ý — message ra rất chuẩn. Với thay đổi config mơ hồ thì đôi khi cần chỉnh thêm vài từ.
  • Giới hạn 8000 ký tự là có chủ ý: commit lớn thường là dấu hiệu cần chia nhỏ, không phải tăng limit lên. Một commit = một thay đổi logic rõ ràng.
  • Chi phí thực tế: Claude Haiku tốn khoảng $0.001 mỗi 20 commit. Gemini Flash free tier đủ dùng cho cá nhân.
  • Không bao giờ block commit: script được viết để luôn sys.exit(0) khi có lỗi API hoặc không có internet. Đây là nguyên tắc bắt buộc khi viết git hook — hook không được trở thành điểm fail của workflow.

Nếu team bạn dùng Node.js, có thể port script này sang JavaScript và distribute qua package.json để onboarding member mới dễ hơn. Còn nếu không muốn tự viết, các tool như aicommits hay opencommit cũng là lựa chọn tốt — nhưng tự build thì linh hoạt hơn và quan trọng là mình hiểu rõ đang làm gì.

Share: