55# bash scripts/generate_commit_message.sh
66#
77# Environment variables:
8- # COMMIT_MODEL_ID - Bedrock model to use (default: amazon.nova-lite-v1:0)
8+ # COMMIT_MODEL_ID - Bedrock model/inference profile to use (default: us. amazon.nova-lite-v1:0)
99# Examples:
10- # amazon.nova-lite-v1:0
11- # anthropic.claude-3-haiku-20240307-v1:0
10+ # us.amazon.nova-lite-v1:0 (Nova Lite - default, cheapest)
11+ # us.amazon.nova-micro-v1:0 (Nova Micro - fastest)
12+ # us.amazon.nova-pro-v1:0 (Nova Pro - most capable)
13+ # us.anthropic.claude-3-haiku-20240307-v1:0 (Claude Haiku)
1214# AWS_REGION - AWS region for Bedrock (uses default if not set)
15+ # COMMIT_DEBUG - Set to 1 to enable debug output to stderr
1316
1417set -euo pipefail
1518
16- MODEL_ID=" ${COMMIT_MODEL_ID:- amazon.nova-lite-v1: 0} "
19+ MODEL_ID=" ${COMMIT_MODEL_ID:- us.amazon.nova-lite-v1: 0} "
20+ DEBUG=" ${COMMIT_DEBUG:- 0} "
1721
18- # Get the diff - prefer staged changes, fall back to unstaged
19- DIFF_STAT=$( git diff --cached --stat 2> /dev/null)
20- DIFF_CONTENT=$( git diff --cached 2> /dev/null)
22+ debug () {
23+ if [ " $DEBUG " = " 1" ]; then
24+ echo " [DEBUG] $* " >&2
25+ fi
26+ }
2127
28+ # Collect diff using multiple strategies (in priority order)
29+ # After "git add .", staged changes show in "git diff --cached" or "git diff HEAD"
30+ DIFF_STAT=" "
31+ DIFF_CONTENT=" "
32+
33+ # Strategy 1: Staged changes (git diff --cached)
34+ DIFF_STAT=$( git diff --cached --stat 2> /dev/null || true)
35+ DIFF_CONTENT=$( git diff --cached 2> /dev/null || true)
36+
37+ # Strategy 2: If no staged changes, try diff against HEAD (catches both staged + unstaged)
38+ if [ -z " $DIFF_STAT " ]; then
39+ debug " No staged diff found, trying HEAD..."
40+ DIFF_STAT=$( git diff HEAD --stat 2> /dev/null || true)
41+ DIFF_CONTENT=$( git diff HEAD 2> /dev/null || true)
42+ fi
43+
44+ # Strategy 3: Unstaged changes
2245if [ -z " $DIFF_STAT " ]; then
23- DIFF_STAT=$( git diff --stat 2> /dev/null)
24- DIFF_CONTENT=$( git diff 2> /dev/null)
46+ debug " No HEAD diff found, trying unstaged..."
47+ DIFF_STAT=$( git diff --stat 2> /dev/null || true)
48+ DIFF_CONTENT=$( git diff 2> /dev/null || true)
2549fi
2650
27- # If still empty, try diff against HEAD
51+ # Strategy 4: Last commit diff (if everything is already committed)
2852if [ -z " $DIFF_STAT " ]; then
29- DIFF_STAT=$( git diff HEAD --stat 2> /dev/null || echo " no diff available" )
30- DIFF_CONTENT=$( git diff HEAD 2> /dev/null || echo " " )
53+ debug " No working tree diff, using last commit..."
54+ DIFF_STAT=$( git diff HEAD~1 --stat 2> /dev/null || echo " no diff available" )
55+ DIFF_CONTENT=$( git diff HEAD~1 2> /dev/null || echo " " )
3156fi
3257
33- # Truncate diff content to ~4000 chars to stay within token limits
34- DIFF_TRUNCATED=$( echo " $DIFF_CONTENT " | head -c 4000)
58+ debug " Diff stat length: ${# DIFF_STAT} "
59+ debug " Diff content length: ${# DIFF_CONTENT} "
60+
61+ # Truncate diff content to ~6000 chars to stay within token limits while providing good context
62+ DIFF_TRUNCATED=$( echo " $DIFF_CONTENT " | head -c 6000)
3563
3664# Build the prompt
37- PROMPT=" Based on the following git diff, generate a single-line commit message following conventional commit format (e.g., feat:, fix:, docs:, refactor:, chore:, test:).
38- Return ONLY the commit message on a single line. No quotes, no explanation, no prefix like 'Commit message:'.
65+ PROMPT=" You are a Git commit message expert. Analyze the following git changes and generate a clear, informative commit message.
66+
67+ Rules:
68+ 1. Use conventional commit format: type(scope): description
69+ 2. Types: feat, fix, docs, refactor, chore, test, style, build, ci, perf
70+ 3. The scope should identify the component or area changed (e.g., makefile, config, api, ui)
71+ 4. The description should concisely summarize WHAT changed and WHY
72+ 5. Return ONLY the commit message on a single line
73+ 6. Do NOT wrap in quotes, do NOT add any explanation or preamble
74+ 7. Keep it under 100 characters
3975
4076Files changed:
4177${DIFF_STAT}
4278
43- Diff:
79+ Diff details :
4480${DIFF_TRUNCATED} "
4581
4682# Escape the prompt for JSON using jq
@@ -49,23 +85,45 @@ ESCAPED_PROMPT=$(echo "$PROMPT" | jq -Rs .)
4985# Build the messages JSON
5086MESSAGES=" [{\" role\" :\" user\" ,\" content\" :[{\" text\" :${ESCAPED_PROMPT} }]}]"
5187
52- # Call Bedrock converse API
53- COMMIT_MSG=$( aws bedrock-runtime converse \
88+ debug " Calling Bedrock with model: $MODEL_ID "
89+
90+ # Call Bedrock converse API - capture both stdout and stderr separately
91+ BEDROCK_OUTPUT=" "
92+ BEDROCK_ERR=" "
93+ BEDROCK_OUTPUT=$( aws bedrock-runtime converse \
5494 --model-id " $MODEL_ID " \
5595 --messages " $MESSAGES " \
5696 --inference-config ' {"maxTokens":100,"temperature":0.3}' \
5797 --query ' output.message.content[0].text' \
58- --output text 2>&1 ) || true
98+ --output text 2> /tmp/bedrock_commit_err) || true
99+
100+ BEDROCK_ERR=$( cat /tmp/bedrock_commit_err 2> /dev/null || true)
101+ rm -f /tmp/bedrock_commit_err
59102
60- # Fallback if Bedrock call fails or returns empty
61- if [ -z " $COMMIT_MSG " ] || [ " $COMMIT_MSG " = " None" ] || echo " $COMMIT_MSG " | grep -qi " error" ; then
62- # Generate a basic fallback message from the diff stat
63- FILE_COUNT=$( echo " $DIFF_STAT " | grep -c " file" || echo " 0" )
64- echo " chore: update ${FILE_COUNT} file(s)"
103+ debug " Bedrock output: '$BEDROCK_OUTPUT '"
104+ debug " Bedrock stderr: '$BEDROCK_ERR '"
105+
106+ # Check if we got a valid response
107+ if [ -z " $BEDROCK_OUTPUT " ] || [ " $BEDROCK_OUTPUT " = " None" ] || [ " $BEDROCK_OUTPUT " = " null" ]; then
108+ debug " Bedrock returned empty/None, using fallback"
109+ if [ -n " $BEDROCK_ERR " ]; then
110+ debug " Bedrock error: $BEDROCK_ERR "
111+ fi
112+ # Generate a descriptive fallback from the diff stat
113+ SUMMARY=$( echo " $DIFF_STAT " | head -5 | tr ' \n' ' , ' | sed ' s/, $//' )
114+ echo " chore: update files - ${SUMMARY} "
65115 exit 0
66116fi
67117
68- # Clean up - remove surrounding quotes if present, trim whitespace
69- COMMIT_MSG=$( echo " $COMMIT_MSG " | sed ' s/^["' " '" ' ]//;s/["' " '" ' ]$//' | xargs)
118+ # Clean up - strip code blocks, remove surrounding quotes, trim whitespace
119+ # Models sometimes wrap output in ```bash ... ``` or ```...```
120+ COMMIT_MSG=$( echo " $BEDROCK_OUTPUT " | \
121+ sed ' /^```/d' | \
122+ sed ' s/^["' " '" ' ]*//;s/["' " '" ' ]*$//' | \
123+ grep -v ' ^[[:space:]]*$' | \
124+ head -1 | \
125+ xargs)
126+
127+ debug " Final commit message: '$COMMIT_MSG '"
70128
71129echo " $COMMIT_MSG "
0 commit comments