diff --git a/claude/code/commands/commit.md b/claude/code/commands/commit.md new file mode 100644 index 0000000..24fc574 --- /dev/null +++ b/claude/code/commands/commit.md @@ -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 ` 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 +``` diff --git a/claude/code/scripts/smart-commit.sh b/claude/code/scripts/smart-commit.sh new file mode 100755 index 0000000..4f77c12 --- /dev/null +++ b/claude/code/scripts/smart-commit.sh @@ -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 " +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