4 minutes
Jira compliant and Claude powered commits
Using the same jira-buddy from earlier and combining it with claude code, we can stop writing commits entirely and let the LLMs do it.
This gist
suggests two bash functions to add to your bashrc
to never write commits again!
This first function, aicommit
, passes:
- the staged changes (whatever you
git add
‘ed) - the current directory’s name (you’ll want to run this at the root of the repo, so this would likely be your project name, which adds some context to your change)
- anything you pass to the command as extra context
to claude code
and asks it for a concise commit message.
For example:
host:~/dev/superapp> aicommit ai powered security
aicommit() {
local profile="${DEFAULT_CLAUDE_COMMIT_PROFILE:-claude}"
local context="$@"
echo "Generating commit message with Claude using profile '$profile'... with extra context: '$context'"
local project_dir_name="${PWD##*/}"
local commit_message
local prompt="Analyze the staged git changes for the '$project_dir_name' project and generate a concise commit message following conventional commit format (type: description). Keep the message under 72 characters for the first line. Only output the commit message, nothing else - the output goes directly to git commit -m."
if [[ -n "$context" ]]; then
prompt="$prompt
Additional context: $context"
fi
commit_message=$(docker run --rm \
--volume "$PWD:/home/node/dev/$project_dir_name" \
-w "/home/node/dev/$project_dir_name" \
--volume "$HOME/.${profile}:/home/node/.claude" \
--volume "$HOME/.${profile}.json:/home/node/.claude.json" \
nbr23/claudecode -p "${prompt}" | awk '{$1=$1;print}')
if [[ -z "$commit_message" ]]; then
echo "Error: Failed to generate a commit message from Claude. Aborting."
return 1
fi
echo "Committing..."
git commit -m "$commit_message" && git commit --amend
}
aijiracommit
works similarly to aicommit
but also calls jira-buddy
to fetch the list of open tickets assigned to ourselves on Jira. It passes this list as extra context to claude code
, and asks it to delivery a commit message prepended with the most appropriate ticket ID.
aijiracommit() {
local profile="${DEFAULT_CLAUDE_COMMIT_PROFILE:-claude}"
local context="$@"
echo "Generating Jira-aware commit message with Claude using profile '$profile'... with extra context: '$context'"
local project_dir_name="${PWD##*/}"
local commit_message
local jira_tickets
jira_tickets=$(uvx jira-buddy --own --json --fields key,summary,status,issuetype)
if [[ -z "$jira_tickets" ]]; then
echo "Error: Failed to fetch Jira tickets."
return
fi
local prompt="CRITICAL INSTRUCTIONS: You MUST respond with ONLY a single line containing the commit message. NO explanations, NO reasoning, NO additional text, NO line breaks.
Analyze the staged git changes for the '$project_dir_name' project. Based on the changes and the following Jira tickets JSON, pick the most appropriate ticket and generate a commit message in the format 'TICKET-KEY - commit message'. Keep under 72 characters total.
IMPORTANT: Only pick a ticket if you have confidence it matches the changes or that the changes could be a subtask of. If no ticket clearly matches, respond with exactly 'None' instead."
if [[ -n "$context" ]]; then
prompt="$prompt
Additional context: $context"
fi
prompt="$prompt
Jira tickets:
$jira_tickets
RESPOND WITH ONLY THE COMMIT MESSAGE LINE. NOTHING ELSE."
local claude_response
claude_response=$(docker run --rm \
--volume "$PWD:/home/node/dev/$project_dir_name" \
-w "/home/node/dev/$project_dir_name" \
--volume "$HOME/.${profile}:/home/node/.claude" \
--volume "$HOME/.${profile}.json:/home/node/.claude.json" \
nbr23/claudecode -p "$prompt" | awk '{$1=$1;print}')
if [[ -z "$claude_response" ]]; then
echo "Error: Failed to generate a commit message from Claude. Aborting."
return 1
fi
if [[ "$claude_response" == "None" ]]; then
echo "No appropriate Jira ticket found."
return
fi
commit_message=$(echo "$claude_response" | grep -oE '^[A-Z]+-[0-9]+ - .*$' | head -n 1)
if [[ -z "$commit_message" ]]; then
echo "Error: Claude response doesn't match expected commit message format."
echo "Claude response: $claude_response"
return
fi
local line_count
line_count=$(echo "$commit_message" | wc -l)
if [[ $line_count -gt 1 ]]; then
echo "Error: Commit message must be only one line long."
echo "Generated message:"
echo "$commit_message"
return 1
fi
local ticket_key
ticket_key=$(echo "$commit_message" | grep -oE '^[A-Z]+-[0-9]+' | head -n 1)
if [[ -z "$ticket_key" ]]; then
echo "Error: Commit message must start with a ticket number (e.g., ABC-123)."
echo "Generated message: $commit_message"
return 1
fi
echo "Selected Jira ticket: $ticket_key"
echo "$jira_tickets" | jq -r ".[] | select(.key == \"$ticket_key\") | \"Key: \(.key)\nSummary: \(.summary)\nStatus: \(.status)\nIssue Type: \(.issuetype)\""
echo
echo "Committing..."
git commit -m "$commit_message"
}
Both run claude inside docker , but can be easily modified to run natively.
YMMV, and sometimes you need to run it a couple of times before it accepts to deliver a commit message and not a monologue on git (despite the ALL CAPS instructions!!!), or figures out which LLM-generated ticket your PM created matches the LLM-generated code you staged.