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.
This commit is contained in:
parent
dc4a5ed329
commit
c9391635eb
2 changed files with 161 additions and 0 deletions
53
claude/code/commands/commit.md
Normal file
53
claude/code/commands/commit.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
name: commit
|
||||
plugin: code
|
||||
description: Generate a conventional commit message for staged changes
|
||||
args: "[message]"
|
||||
flags:
|
||||
- --amend
|
||||
hooks:
|
||||
Before:
|
||||
- hooks:
|
||||
- type: command
|
||||
command: "${CLAUDE_PLUGIN_ROOT}/scripts/smart-commit.sh"
|
||||
---
|
||||
|
||||
# Smart Commit
|
||||
|
||||
Generate a conventional commit message for staged changes.
|
||||
|
||||
## Usage
|
||||
|
||||
Generate message automatically:
|
||||
`/core:commit`
|
||||
|
||||
Provide a custom message:
|
||||
`/core:commit "feat(auth): add token validation"`
|
||||
|
||||
Amend the previous commit:
|
||||
`/core:commit --amend`
|
||||
|
||||
## Behavior
|
||||
|
||||
1. **Analyze Staged Changes**: Examines the `git diff --staged` to understand the nature of the changes.
|
||||
2. **Generate Conventional Commit Message**:
|
||||
- `feat`: For new files, functions, or features.
|
||||
- `fix`: For bug fixes.
|
||||
- `refactor`: For code restructuring without changing external behavior.
|
||||
- `docs`: For changes to documentation.
|
||||
- `test`: For adding or modifying tests.
|
||||
- `chore`: For routine maintenance tasks.
|
||||
3. **Determine Scope**: Infers the scope from the affected module's file paths (e.g., `auth`, `payment`, `ui`).
|
||||
4. **Add Co-Authored-By Trailer**: Appends `Co-Authored-By: Claude <noreply@anthropic.com>` to the commit message.
|
||||
|
||||
## Message Generation Example
|
||||
|
||||
```
|
||||
feat(auth): add JWT token validation
|
||||
|
||||
- Add validateToken() function
|
||||
- Add token expiry check
|
||||
- Add unit tests for validation
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
```
|
||||
108
claude/code/scripts/smart-commit.sh
Executable file
108
claude/code/scripts/smart-commit.sh
Executable file
|
|
@ -0,0 +1,108 @@
|
|||
#!/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
|
||||
Loading…
Add table
Reference in a new issue