plugins/claude/code/scripts/smart-commit.sh
Snider c9391635eb
feat(code): implement /core:commit smart commit command (#73)
Implements a new `/core:commit` command that analyzes staged changes to generate a conventional commit message.

The command supports three main modes of operation:
- `/core:commit`: Automatically generates a commit message based on the content of the staged files.
- `/core:commit "custom message"`: Uses the provided string as the full commit message.
- `/core:commit --amend`: Amends the last commit with the new message.

Message generation includes several heuristics:
- **Commit Type:** Determined by file paths (e.g., `_test.go` -> `test`) and diff content (e.g., keywords like `fix` or `refactor`).
- **Scope:** Inferred from the most common directory name among the staged files.
- **Summary:** Extracted from function or class names in the diff, or defaults to a file-based summary.
- **Co-Author:** A `Co-Authored-By` trailer is automatically appended.

This feature streamlines the development workflow by automating the creation of descriptive and conventional commit messages.
2026-02-02 07:16:29 +00:00

108 lines
3.1 KiB
Bash
Executable file

#!/bin/bash
# Smart commit script for /core:commit command
CUSTOM_MESSAGE=""
AMEND_FLAG=""
# Parse arguments
while (( "$#" )); do
case "$1" in
--amend)
AMEND_FLAG="--amend"
shift
;;
-*)
echo "Unsupported flag $1" >&2
exit 1
;;
*)
# The rest of the arguments are treated as the commit message
CUSTOM_MESSAGE="$@"
break
;;
esac
done
# Get staged changes
STAGED_FILES=$(git diff --staged --name-status)
if [ -z "$STAGED_FILES" ]; then
echo "No staged changes to commit."
exit 0
fi
# Determine commit type and scope
COMMIT_TYPE="chore" # Default to chore
SCOPE=""
# Get just the file paths
STAGED_FILE_PATHS=$(git diff --staged --name-only)
# Determine type from file paths/status
# Order is important here: test and docs are more specific than feat.
if echo "$STAGED_FILE_PATHS" | grep -q -E "(_test\.go|\.test\.js|/tests/|/spec/)"; then
COMMIT_TYPE="test"
elif echo "$STAGED_FILE_PATHS" | grep -q -E "(\.md|/docs/|README)"; then
COMMIT_TYPE="docs"
elif echo "$STAGED_FILES" | grep -q "^A"; then
COMMIT_TYPE="feat"
elif git diff --staged | grep -q -E "^\+.*(fix|bug|issue)"; then
COMMIT_TYPE="fix"
elif git diff --staged | grep -q -E "^\+.*(refactor|restructure)"; then
COMMIT_TYPE="refactor"
fi
# Determine scope from the most common path component
if [ -n "$STAGED_FILE_PATHS" ]; then
# Extract the second component of each path (e.g., 'code' from 'claude/code/file.md')
# This is a decent heuristic for module name.
# We filter for lines that have a second component.
POSSIBLE_SCOPES=$(echo "$STAGED_FILE_PATHS" | grep '/' | cut -d/ -f2)
if [ -n "$POSSIBLE_SCOPES" ]; then
SCOPE=$(echo "$POSSIBLE_SCOPES" | sort | uniq -c | sort -nr | head -n 1 | awk '{print $2}')
fi
# If no scope is found (e.g., all files are in root), SCOPE remains empty, which is valid.
fi
# Construct the commit message
if [ -n "$CUSTOM_MESSAGE" ]; then
COMMIT_MESSAGE="$CUSTOM_MESSAGE"
else
# Auto-generate a descriptive summary
DIFF_CONTENT=$(git diff --staged)
# Try to find a function or class name from the diff
# This is a simple heuristic that can be greatly expanded.
SUMMARY=$(echo "$DIFF_CONTENT" | grep -E -o "(function|class|def) \w+" | head -n 1 | sed -e 's/function //g' -e 's/class //g' -e 's/def //g')
if [ -z "$SUMMARY" ]; then
if [ $(echo "$STAGED_FILE_PATHS" | wc -l) -eq 1 ]; then
FIRST_FILE=$(echo "$STAGED_FILE_PATHS" | head -n 1)
SUMMARY="update $(basename "$FIRST_FILE")"
else
SUMMARY="update multiple files"
fi
else
SUMMARY="update $SUMMARY"
fi
SUBJECT="$COMMIT_TYPE($SCOPE): $SUMMARY"
BODY=$(echo "$DIFF_CONTENT" | grep -E "^\+" | sed -e 's/^+//' | head -n 5 | sed 's/^/ - /')
COMMIT_MESSAGE="$SUBJECT\n\n$BODY"
fi
# Add Co-Authored-By trailer
CO_AUTHOR="Co-Authored-By: Claude <noreply@anthropic.com>"
if ! echo "$COMMIT_MESSAGE" | grep -q "$CO_AUTHOR"; then
COMMIT_MESSAGE="$COMMIT_MESSAGE\n\n$CO_AUTHOR"
fi
# Execute the commit
git commit $AMEND_FLAG -m "$(echo -e "$COMMIT_MESSAGE")"
if [ $? -eq 0 ]; then
echo "Commit successful."
else
echo "Commit failed."
exit 1
fi