Initial commit
This commit is contained in:
304
commands/message-generation/.scripts/body-composer.sh
Executable file
304
commands/message-generation/.scripts/body-composer.sh
Executable file
@@ -0,0 +1,304 @@
|
||||
#!/usr/bin/env bash
|
||||
# Script: body-composer.sh
|
||||
# Purpose: Compose commit message body with proper formatting and wrapping
|
||||
# Author: Git Commit Assistant Plugin
|
||||
# Version: 1.0.0
|
||||
#
|
||||
# Usage:
|
||||
# export CHANGES="change1,change2,change3"
|
||||
# export WRAP_LENGTH=72
|
||||
# export FORMAT=bullets
|
||||
# ./body-composer.sh
|
||||
#
|
||||
# Environment Variables:
|
||||
# CHANGES - Comma-separated list of changes or file paths
|
||||
# WRAP_LENGTH - Line wrap length (default: 72)
|
||||
# FORMAT - Output format: bullets or paragraphs (default: bullets)
|
||||
# WHY_CONTEXT - Optional context about why changes were made
|
||||
#
|
||||
# Returns:
|
||||
# Formatted body text to stdout
|
||||
# JSON summary to stderr (optional)
|
||||
#
|
||||
# Exit Codes:
|
||||
# 0 - Success
|
||||
# 1 - Invalid input
|
||||
# 2 - Processing error
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Default values
|
||||
WRAP_LENGTH="${WRAP_LENGTH:-72}"
|
||||
FORMAT="${FORMAT:-bullets}"
|
||||
WHY_CONTEXT="${WHY_CONTEXT:-}"
|
||||
|
||||
# Validate CHANGES is provided
|
||||
if [ -z "${CHANGES:-}" ]; then
|
||||
echo "ERROR: CHANGES environment variable is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate FORMAT
|
||||
if [ "$FORMAT" != "bullets" ] && [ "$FORMAT" != "paragraphs" ]; then
|
||||
echo "ERROR: FORMAT must be 'bullets' or 'paragraphs'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate WRAP_LENGTH
|
||||
if ! [[ "$WRAP_LENGTH" =~ ^[0-9]+$ ]]; then
|
||||
echo "ERROR: WRAP_LENGTH must be a positive integer" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to convert to imperative mood
|
||||
convert_to_imperative() {
|
||||
local text="$1"
|
||||
|
||||
# Common past tense -> imperative
|
||||
text=$(echo "$text" | sed -E 's/\b(added|adds)\b/add/gi')
|
||||
text=$(echo "$text" | sed -E 's/\b(fixed|fixes)\b/fix/gi')
|
||||
text=$(echo "$text" | sed -E 's/\b(updated|updates)\b/update/gi')
|
||||
text=$(echo "$text" | sed -E 's/\b(removed|removes)\b/remove/gi')
|
||||
text=$(echo "$text" | sed -E 's/\b(changed|changes)\b/change/gi')
|
||||
text=$(echo "$text" | sed -E 's/\b(improved|improves)\b/improve/gi')
|
||||
text=$(echo "$text" | sed -E 's/\b(refactored|refactors)\b/refactor/gi')
|
||||
text=$(echo "$text" | sed -E 's/\b(implemented|implements)\b/implement/gi')
|
||||
text=$(echo "$text" | sed -E 's/\b(created|creates)\b/create/gi')
|
||||
text=$(echo "$text" | sed -E 's/\b(deleted|deletes)\b/delete/gi')
|
||||
|
||||
# Lowercase first letter
|
||||
text="$(echo "${text:0:1}" | tr '[:upper:]' '[:lower:]')${text:1}"
|
||||
|
||||
echo "$text"
|
||||
}
|
||||
|
||||
# Function to wrap text at specified length
|
||||
wrap_text() {
|
||||
local text="$1"
|
||||
local width="$2"
|
||||
|
||||
echo "$text" | fold -s -w "$width"
|
||||
}
|
||||
|
||||
# Function to format file path as readable change
|
||||
format_file_path() {
|
||||
local filepath="$1"
|
||||
|
||||
# Extract filename and directory
|
||||
local filename=$(basename "$filepath")
|
||||
local dirname=$(dirname "$filepath")
|
||||
|
||||
# Remove extension
|
||||
local name_no_ext="${filename%.*}"
|
||||
|
||||
# Convert to readable format
|
||||
# Example: src/auth/oauth.js -> "add OAuth authentication module"
|
||||
# Example: tests/unit/user.test.js -> "add user unit tests"
|
||||
|
||||
if [[ "$filepath" == *"/test/"* ]] || [[ "$filepath" == *"/tests/"* ]] || [[ "$filename" == *".test."* ]] || [[ "$filename" == *".spec."* ]]; then
|
||||
echo "add ${name_no_ext} tests"
|
||||
elif [[ "$dirname" == "." ]]; then
|
||||
echo "update ${name_no_ext}"
|
||||
else
|
||||
# Extract meaningful part from path
|
||||
local component=$(echo "$dirname" | sed 's|.*/||')
|
||||
echo "update ${component} ${name_no_ext}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to generate bullet points
|
||||
generate_bullets() {
|
||||
local changes_list="$1"
|
||||
|
||||
# Split by comma
|
||||
IFS=',' read -ra items <<< "$changes_list"
|
||||
|
||||
local body=""
|
||||
local bullet_count=0
|
||||
|
||||
for item in "${items[@]}"; do
|
||||
# Trim whitespace
|
||||
item=$(echo "$item" | xargs)
|
||||
|
||||
if [ -z "$item" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if it's a file path
|
||||
if [[ "$item" == *"/"* ]] || [[ "$item" == *"."* ]]; then
|
||||
# Format as file path
|
||||
item=$(format_file_path "$item")
|
||||
fi
|
||||
|
||||
# Convert to imperative mood
|
||||
item=$(convert_to_imperative "$item")
|
||||
|
||||
# Ensure first letter is capitalized for bullet
|
||||
item="$(echo "${item:0:1}" | tr '[:lower:]' '[:upper:]')${item:1}"
|
||||
|
||||
# Wrap if needed (account for "- " prefix)
|
||||
local max_width=$((WRAP_LENGTH - 2))
|
||||
local wrapped=$(wrap_text "$item" "$max_width")
|
||||
|
||||
# Add bullet point
|
||||
echo "$wrapped" | while IFS= read -r line; do
|
||||
if [ "$bullet_count" -eq 0 ] || [ -z "$line" ]; then
|
||||
body="${body}- ${line}\n"
|
||||
else
|
||||
body="${body} ${line}\n" # Indent continuation lines
|
||||
fi
|
||||
done
|
||||
|
||||
bullet_count=$((bullet_count + 1))
|
||||
done
|
||||
|
||||
# Output body (remove trailing newline)
|
||||
echo -ne "$body"
|
||||
}
|
||||
|
||||
# Function to generate paragraphs
|
||||
generate_paragraphs() {
|
||||
local changes_list="$1"
|
||||
|
||||
# Split by comma and join into sentences
|
||||
IFS=',' read -ra items <<< "$changes_list"
|
||||
|
||||
local body=""
|
||||
|
||||
for item in "${items[@]}"; do
|
||||
# Trim whitespace
|
||||
item=$(echo "$item" | xargs)
|
||||
|
||||
if [ -z "$item" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if it's a file path
|
||||
if [[ "$item" == *"/"* ]] || [[ "$item" == *"."* ]]; then
|
||||
item=$(format_file_path "$item")
|
||||
fi
|
||||
|
||||
# Convert to imperative mood
|
||||
item=$(convert_to_imperative "$item")
|
||||
|
||||
# Ensure first letter is capitalized
|
||||
item="$(echo "${item:0:1}" | tr '[:lower:]' '[:upper:]')${item:1}"
|
||||
|
||||
# Add to body
|
||||
if [ -z "$body" ]; then
|
||||
body="$item"
|
||||
else
|
||||
body="${body}. ${item}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Add period at end if not present
|
||||
if [[ ! "$body" =~ \.$ ]]; then
|
||||
body="${body}."
|
||||
fi
|
||||
|
||||
# Wrap text
|
||||
wrapped=$(wrap_text "$body" "$WRAP_LENGTH")
|
||||
|
||||
echo "$wrapped"
|
||||
}
|
||||
|
||||
# Function to add context (why)
|
||||
add_context() {
|
||||
local body="$1"
|
||||
local context="$2"
|
||||
|
||||
if [ -z "$context" ]; then
|
||||
echo "$body"
|
||||
return
|
||||
fi
|
||||
|
||||
# Ensure first letter is capitalized
|
||||
context="$(echo "${context:0:1}" | tr '[:lower:]' '[:upper:]')${context:1}"
|
||||
|
||||
# Add period if not present
|
||||
if [[ ! "$context" =~ \.$ ]]; then
|
||||
context="${context}."
|
||||
fi
|
||||
|
||||
# Wrap context
|
||||
wrapped_context=$(wrap_text "$context" "$WRAP_LENGTH")
|
||||
|
||||
# Combine with blank line
|
||||
echo -e "${body}\n\n${wrapped_context}"
|
||||
}
|
||||
|
||||
# Main composition logic
|
||||
compose_body() {
|
||||
local body=""
|
||||
|
||||
# Generate based on format
|
||||
if [ "$FORMAT" = "bullets" ]; then
|
||||
body=$(generate_bullets "$CHANGES")
|
||||
else
|
||||
body=$(generate_paragraphs "$CHANGES")
|
||||
fi
|
||||
|
||||
# Add context if provided
|
||||
if [ -n "$WHY_CONTEXT" ]; then
|
||||
body=$(add_context "$body" "$WHY_CONTEXT")
|
||||
fi
|
||||
|
||||
echo "$body"
|
||||
}
|
||||
|
||||
# Validate body
|
||||
validate_body() {
|
||||
local body="$1"
|
||||
|
||||
local line_count=$(echo "$body" | wc -l)
|
||||
local longest_line=$(echo "$body" | awk '{ print length }' | sort -rn | head -1)
|
||||
local bullet_count=$(echo "$body" | grep -c '^- ' || true)
|
||||
|
||||
local warnings=()
|
||||
|
||||
# Check longest line
|
||||
if [ "$longest_line" -gt "$WRAP_LENGTH" ]; then
|
||||
warnings+=("Line exceeds $WRAP_LENGTH characters ($longest_line chars)")
|
||||
fi
|
||||
|
||||
# Check for empty lines at start
|
||||
if echo "$body" | head -1 | grep -q '^$'; then
|
||||
warnings+=("Body starts with empty line")
|
||||
fi
|
||||
|
||||
# Output validation summary to stderr
|
||||
{
|
||||
echo "{"
|
||||
echo " \"line_count\": $line_count,"
|
||||
echo " \"longest_line\": $longest_line,"
|
||||
echo " \"wrap_length\": $WRAP_LENGTH,"
|
||||
echo " \"bullet_count\": $bullet_count,"
|
||||
echo " \"format\": \"$FORMAT\","
|
||||
echo " \"has_context\": $([ -n "$WHY_CONTEXT" ] && echo "true" || echo "false"),"
|
||||
echo " \"warnings\": ["
|
||||
for i in "${!warnings[@]}"; do
|
||||
echo " \"${warnings[$i]}\"$([ $i -lt $((${#warnings[@]} - 1)) ] && echo "," || echo "")"
|
||||
done
|
||||
echo " ],"
|
||||
echo " \"valid\": $([ ${#warnings[@]} -eq 0 ] && echo "true" || echo "false")"
|
||||
echo "}"
|
||||
} >&2
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
# Compose body
|
||||
body=$(compose_body)
|
||||
|
||||
# Validate
|
||||
validate_body "$body"
|
||||
|
||||
# Output body to stdout
|
||||
echo "$body"
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Run main
|
||||
main
|
||||
292
commands/message-generation/.scripts/footer-builder.py
Executable file
292
commands/message-generation/.scripts/footer-builder.py
Executable file
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env python3
|
||||
# Script: footer-builder.py
|
||||
# Purpose: Build commit message footer with breaking changes and issue references
|
||||
# Author: Git Commit Assistant Plugin
|
||||
# Version: 1.0.0
|
||||
#
|
||||
# Usage:
|
||||
# echo '{"breaking":"API changed","closes":"123,456"}' | ./footer-builder.py
|
||||
# cat input.json | ./footer-builder.py
|
||||
#
|
||||
# Returns:
|
||||
# JSON: {"footer": "...", "components": {...}, "valid": true}
|
||||
#
|
||||
# Exit Codes:
|
||||
# 0 - Success
|
||||
# 1 - Invalid input
|
||||
# 2 - Processing error
|
||||
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
def wrap_text(text, width=72, subsequent_indent=''):
|
||||
"""Wrap text at specified width."""
|
||||
wrapper = textwrap.TextWrapper(
|
||||
width=width,
|
||||
subsequent_indent=subsequent_indent,
|
||||
break_long_words=False,
|
||||
break_on_hyphens=False
|
||||
)
|
||||
return wrapper.fill(text)
|
||||
|
||||
def format_breaking_change(description):
|
||||
"""Format breaking change notice."""
|
||||
if not description:
|
||||
return None
|
||||
|
||||
# Ensure BREAKING CHANGE is uppercase
|
||||
# Wrap at 72 characters with continuation indentation
|
||||
wrapped = wrap_text(
|
||||
description,
|
||||
width=72,
|
||||
subsequent_indent=''
|
||||
)
|
||||
|
||||
return f"BREAKING CHANGE: {wrapped}"
|
||||
|
||||
def parse_issue_numbers(issue_string):
|
||||
"""Parse comma-separated issue numbers into list."""
|
||||
if not issue_string:
|
||||
return []
|
||||
|
||||
# Remove any # symbols
|
||||
issue_string = issue_string.replace('#', '')
|
||||
|
||||
# Split by comma and clean
|
||||
issues = [num.strip() for num in issue_string.split(',') if num.strip()]
|
||||
|
||||
# Validate all are numbers
|
||||
valid_issues = []
|
||||
for issue in issues:
|
||||
if issue.isdigit():
|
||||
valid_issues.append(issue)
|
||||
else:
|
||||
# Try to extract number
|
||||
match = re.search(r'\d+', issue)
|
||||
if match:
|
||||
valid_issues.append(match.group())
|
||||
|
||||
return valid_issues
|
||||
|
||||
def format_issue_references(closes=None, fixes=None, refs=None):
|
||||
"""Format issue references."""
|
||||
lines = []
|
||||
|
||||
# Closes (for features/pull requests)
|
||||
if closes:
|
||||
issues = parse_issue_numbers(closes)
|
||||
if issues:
|
||||
if len(issues) == 1:
|
||||
lines.append(f"Closes #{issues[0]}")
|
||||
else:
|
||||
# Format as comma-separated list
|
||||
issue_refs = ', '.join([f"#{num}" for num in issues])
|
||||
lines.append(f"Closes {issue_refs}")
|
||||
|
||||
# Fixes (for bug fixes)
|
||||
if fixes:
|
||||
issues = parse_issue_numbers(fixes)
|
||||
if issues:
|
||||
if len(issues) == 1:
|
||||
lines.append(f"Fixes #{issues[0]}")
|
||||
else:
|
||||
issue_refs = ', '.join([f"#{num}" for num in issues])
|
||||
lines.append(f"Fixes {issue_refs}")
|
||||
|
||||
# Refs (for related issues)
|
||||
if refs:
|
||||
issues = parse_issue_numbers(refs)
|
||||
if issues:
|
||||
if len(issues) == 1:
|
||||
lines.append(f"Refs #{issues[0]}")
|
||||
else:
|
||||
issue_refs = ', '.join([f"#{num}" for num in issues])
|
||||
lines.append(f"Refs {issue_refs}")
|
||||
|
||||
return lines
|
||||
|
||||
def format_metadata(reviewed=None, signed=None):
|
||||
"""Format metadata like Reviewed-by and Signed-off-by."""
|
||||
lines = []
|
||||
|
||||
if reviewed:
|
||||
lines.append(f"Reviewed-by: {reviewed}")
|
||||
|
||||
if signed:
|
||||
# Validate email format
|
||||
if '@' in signed and '<' in signed and '>' in signed:
|
||||
lines.append(f"Signed-off-by: {signed}")
|
||||
else:
|
||||
# Try to format properly
|
||||
lines.append(f"Signed-off-by: {signed}")
|
||||
|
||||
return lines
|
||||
|
||||
def build_footer(data):
|
||||
"""
|
||||
Build commit message footer from input data.
|
||||
|
||||
Args:
|
||||
data: dict with keys: breaking, closes, fixes, refs, reviewed, signed
|
||||
|
||||
Returns:
|
||||
dict with footer, components, valid status
|
||||
"""
|
||||
|
||||
# Extract parameters
|
||||
breaking = data.get('breaking', '').strip()
|
||||
closes = data.get('closes', '').strip()
|
||||
fixes = data.get('fixes', '').strip()
|
||||
refs = data.get('refs', '').strip()
|
||||
reviewed = data.get('reviewed', '').strip()
|
||||
signed = data.get('signed', '').strip()
|
||||
|
||||
# Check if any parameter provided
|
||||
has_content = any([breaking, closes, fixes, refs, reviewed, signed])
|
||||
|
||||
if not has_content:
|
||||
return {
|
||||
'error': 'At least one footer component is required',
|
||||
'footer': None,
|
||||
'valid': False
|
||||
}
|
||||
|
||||
# Build footer components
|
||||
footer_lines = []
|
||||
components = {
|
||||
'breaking_change': False,
|
||||
'closes_issues': 0,
|
||||
'fixes_issues': 0,
|
||||
'refs_issues': 0,
|
||||
'reviewed_by': False,
|
||||
'signed_off': False
|
||||
}
|
||||
|
||||
# Breaking change (always first)
|
||||
if breaking:
|
||||
breaking_line = format_breaking_change(breaking)
|
||||
if breaking_line:
|
||||
footer_lines.append(breaking_line)
|
||||
components['breaking_change'] = True
|
||||
|
||||
# Issue references
|
||||
issue_lines = format_issue_references(closes, fixes, refs)
|
||||
footer_lines.extend(issue_lines)
|
||||
|
||||
# Count issues
|
||||
if closes:
|
||||
components['closes_issues'] = len(parse_issue_numbers(closes))
|
||||
if fixes:
|
||||
components['fixes_issues'] = len(parse_issue_numbers(fixes))
|
||||
if refs:
|
||||
components['refs_issues'] = len(parse_issue_numbers(refs))
|
||||
|
||||
# Metadata
|
||||
metadata_lines = format_metadata(reviewed, signed)
|
||||
footer_lines.extend(metadata_lines)
|
||||
|
||||
if reviewed:
|
||||
components['reviewed_by'] = True
|
||||
if signed:
|
||||
components['signed_off'] = True
|
||||
|
||||
# Join all lines
|
||||
footer = '\n'.join(footer_lines)
|
||||
|
||||
# Validate footer
|
||||
warnings = []
|
||||
|
||||
# Check breaking change format
|
||||
if breaking and not footer.startswith('BREAKING CHANGE:'):
|
||||
warnings.append('BREAKING CHANGE must be uppercase')
|
||||
|
||||
# Check issue reference format
|
||||
for line in footer_lines:
|
||||
if 'closes' in line.lower() and not line.startswith('Closes'):
|
||||
warnings.append('Use "Closes" (capitalized)')
|
||||
if 'fixes' in line.lower() and not line.startswith('Fixes'):
|
||||
warnings.append('Use "Fixes" (capitalized)')
|
||||
if 'refs' in line.lower() and not line.startswith('Refs'):
|
||||
warnings.append('Use "Refs" (capitalized)')
|
||||
|
||||
# Check for proper issue number format
|
||||
if any([closes, fixes, refs]):
|
||||
# Make sure all issue numbers are valid
|
||||
all_issues = parse_issue_numbers(closes) + parse_issue_numbers(fixes) + parse_issue_numbers(refs)
|
||||
if not all_issues:
|
||||
warnings.append('No valid issue numbers found')
|
||||
|
||||
# Build response
|
||||
response = {
|
||||
'footer': footer,
|
||||
'components': components,
|
||||
'line_count': len(footer_lines),
|
||||
'has_breaking': components['breaking_change'],
|
||||
'total_issues': components['closes_issues'] + components['fixes_issues'] + components['refs_issues'],
|
||||
'warnings': warnings,
|
||||
'valid': len(warnings) == 0
|
||||
}
|
||||
|
||||
# Add quality score
|
||||
score = 100
|
||||
if not components['breaking_change'] and breaking:
|
||||
score -= 10
|
||||
if warnings:
|
||||
score -= len(warnings) * 5
|
||||
|
||||
response['quality_score'] = max(0, score)
|
||||
|
||||
return response
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
|
||||
try:
|
||||
# Read JSON input from stdin
|
||||
input_data = sys.stdin.read()
|
||||
|
||||
if not input_data or not input_data.strip():
|
||||
print(json.dumps({
|
||||
'error': 'No input provided',
|
||||
'footer': None,
|
||||
'valid': False
|
||||
}))
|
||||
sys.exit(1)
|
||||
|
||||
# Parse JSON
|
||||
try:
|
||||
data = json.loads(input_data)
|
||||
except json.JSONDecodeError as e:
|
||||
print(json.dumps({
|
||||
'error': f'Invalid JSON: {str(e)}',
|
||||
'footer': None,
|
||||
'valid': False
|
||||
}))
|
||||
sys.exit(1)
|
||||
|
||||
# Build footer
|
||||
result = build_footer(data)
|
||||
|
||||
# Output result
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
# Exit code based on result
|
||||
if 'error' in result:
|
||||
sys.exit(2)
|
||||
elif not result.get('valid', False):
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
print(json.dumps({
|
||||
'error': f'Unexpected error: {str(e)}',
|
||||
'footer': None,
|
||||
'valid': False
|
||||
}))
|
||||
sys.exit(2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
335
commands/message-generation/.scripts/message-validator.sh
Executable file
335
commands/message-generation/.scripts/message-validator.sh
Executable file
@@ -0,0 +1,335 @@
|
||||
#!/usr/bin/env bash
|
||||
# Script: message-validator.sh
|
||||
# Purpose: Validate commit message against conventional commits standard
|
||||
# Author: Git Commit Assistant Plugin
|
||||
# Version: 1.0.0
|
||||
#
|
||||
# Usage:
|
||||
# export MESSAGE="feat: add feature"
|
||||
# export STRICT_MODE=false
|
||||
# export MAX_SUBJECT=50
|
||||
# export MAX_LINE=72
|
||||
# ./message-validator.sh
|
||||
#
|
||||
# Environment Variables:
|
||||
# MESSAGE - Commit message to validate (required)
|
||||
# STRICT_MODE - Enable strict validation (default: false)
|
||||
# MAX_SUBJECT - Maximum subject length (default: 50)
|
||||
# MAX_LINE - Maximum body line length (default: 72)
|
||||
#
|
||||
# Returns:
|
||||
# Validation report to stdout
|
||||
# Exit code indicates validation status
|
||||
#
|
||||
# Exit Codes:
|
||||
# 0 - Valid message
|
||||
# 1 - Invalid message (warnings in normal mode, any issue in strict mode)
|
||||
# 2 - Error (missing input, malformed message)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Default values
|
||||
STRICT_MODE="${STRICT_MODE:-false}"
|
||||
MAX_SUBJECT="${MAX_SUBJECT:-50}"
|
||||
MAX_LINE="${MAX_LINE:-72}"
|
||||
|
||||
# Validation counters
|
||||
declare -a ERRORS=()
|
||||
declare -a WARNINGS=()
|
||||
declare -a SUGGESTIONS=()
|
||||
|
||||
# Score tracking
|
||||
SCORE=100
|
||||
|
||||
# Validate MESSAGE is provided
|
||||
if [ -z "${MESSAGE:-}" ]; then
|
||||
echo "ERROR: MESSAGE environment variable is required" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Valid commit types
|
||||
VALID_TYPES="feat fix docs style refactor perf test build ci chore revert"
|
||||
|
||||
# Function to check commit type
|
||||
validate_type() {
|
||||
local subject="$1"
|
||||
|
||||
# Extract type (before colon or parenthesis)
|
||||
local type=$(echo "$subject" | grep -oP '^[a-z]+' || echo "")
|
||||
|
||||
if [ -z "$type" ]; then
|
||||
ERRORS+=("No commit type found")
|
||||
SCORE=$((SCORE - 20))
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if type is valid
|
||||
if ! echo "$VALID_TYPES" | grep -qw "$type"; then
|
||||
ERRORS+=("Invalid commit type: '$type'")
|
||||
ERRORS+=("Valid types: $VALID_TYPES")
|
||||
SCORE=$((SCORE - 20))
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to check scope format
|
||||
validate_scope() {
|
||||
local subject="$1"
|
||||
|
||||
# Check if scope exists
|
||||
if echo "$subject" | grep -qP '^\w+\([^)]+\):'; then
|
||||
local scope=$(echo "$subject" | grep -oP '^\w+\(\K[^)]+')
|
||||
|
||||
# Scope should be lowercase alphanumeric with hyphens
|
||||
if ! echo "$scope" | grep -qP '^[a-z0-9-]+$'; then
|
||||
WARNINGS+=("Scope should be lowercase alphanumeric with hyphens: '$scope'")
|
||||
SCORE=$((SCORE - 5))
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to validate subject line
|
||||
validate_subject() {
|
||||
local subject="$1"
|
||||
|
||||
# Check format: type(scope): description or type: description
|
||||
if ! echo "$subject" | grep -qP '^[a-z]+(\([a-z0-9-]+\))?: .+'; then
|
||||
ERRORS+=("Subject does not match conventional commits format")
|
||||
ERRORS+=("Expected: <type>(<scope>): <description> or <type>: <description>")
|
||||
SCORE=$((SCORE - 30))
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate type
|
||||
validate_type "$subject"
|
||||
|
||||
# Validate scope if present
|
||||
validate_scope "$subject"
|
||||
|
||||
# Extract description (after ": ")
|
||||
local description=$(echo "$subject" | sed 's/^[^:]*: //')
|
||||
|
||||
# Check length
|
||||
local length=${#subject}
|
||||
if [ "$length" -gt "$MAX_SUBJECT" ]; then
|
||||
if [ "$length" -gt 72 ]; then
|
||||
ERRORS+=("Subject exceeds hard limit of 72 characters ($length chars)")
|
||||
SCORE=$((SCORE - 30))
|
||||
else
|
||||
WARNINGS+=("Subject exceeds recommended $MAX_SUBJECT characters ($length chars)")
|
||||
SUGGESTIONS+=("Consider shortening subject or moving details to body")
|
||||
SCORE=$((SCORE - 10))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for capital letter after colon
|
||||
if echo "$description" | grep -qP '^[A-Z]'; then
|
||||
WARNINGS+=("Description should not start with capital letter")
|
||||
SUGGESTIONS+=("Use lowercase after colon: '$(echo "${description:0:1}" | tr '[:upper:]' '[:lower:]')${description:1}'")
|
||||
SCORE=$((SCORE - 5))
|
||||
fi
|
||||
|
||||
# Check for period at end
|
||||
if [[ "$description" =~ \.$ ]]; then
|
||||
WARNINGS+=("Subject should not end with period")
|
||||
SUGGESTIONS+=("Remove period at end")
|
||||
SCORE=$((SCORE - 3))
|
||||
fi
|
||||
|
||||
# Check for imperative mood (simple heuristics)
|
||||
if echo "$description" | grep -qP '\b(added|fixed|updated|removed|changed|improved|created|deleted)\b'; then
|
||||
WARNINGS+=("Use imperative mood (add, fix, update) not past tense")
|
||||
SCORE=$((SCORE - 5))
|
||||
fi
|
||||
|
||||
if echo "$description" | grep -qP '\b(adds|fixes|updates|removes|changes|improves|creates|deletes)\b'; then
|
||||
WARNINGS+=("Use imperative mood (add, fix, update) not present tense")
|
||||
SCORE=$((SCORE - 5))
|
||||
fi
|
||||
|
||||
# Check for vague descriptions
|
||||
if echo "$description" | grep -qiP '\b(update|change|fix|improve)\s+(code|file|stuff|thing)\b'; then
|
||||
SUGGESTIONS+=("Be more specific in description")
|
||||
SCORE=$((SCORE - 5))
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to validate body
|
||||
validate_body() {
|
||||
local body="$1"
|
||||
|
||||
if [ -z "$body" ]; then
|
||||
return 0 # Body is optional
|
||||
fi
|
||||
|
||||
# Check line lengths
|
||||
while IFS= read -r line; do
|
||||
local length=${#line}
|
||||
if [ "$length" -gt "$MAX_LINE" ]; then
|
||||
WARNINGS+=("Body line exceeds $MAX_LINE characters ($length chars)")
|
||||
SCORE=$((SCORE - 3))
|
||||
fi
|
||||
done <<< "$body"
|
||||
|
||||
# Check for imperative mood in body
|
||||
if echo "$body" | grep -qP '\b(added|fixed|updated|removed|changed|improved|created|deleted)\b'; then
|
||||
SUGGESTIONS+=("Consider using imperative mood in body")
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to validate footer
|
||||
validate_footer() {
|
||||
local footer="$1"
|
||||
|
||||
if [ -z "$footer" ]; then
|
||||
return 0 # Footer is optional
|
||||
fi
|
||||
|
||||
# Check for BREAKING CHANGE format
|
||||
if echo "$footer" | grep -qi "breaking change"; then
|
||||
if ! echo "$footer" | grep -q "^BREAKING CHANGE:"; then
|
||||
ERRORS+=("Use 'BREAKING CHANGE:' (uppercase, singular) not 'breaking change'")
|
||||
SCORE=$((SCORE - 15))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for issue references
|
||||
if echo "$footer" | grep -qiP '\b(close|fix|resolve)[sd]?\b'; then
|
||||
# Check format
|
||||
if ! echo "$footer" | grep -qP '^(Closes|Fixes|Resolves|Refs) #[0-9]'; then
|
||||
WARNINGS+=("Issue references should use proper format: 'Closes #123'")
|
||||
SUGGESTIONS+=("Capitalize keyword and use # prefix for issue numbers")
|
||||
SCORE=$((SCORE - 5))
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to check overall structure
|
||||
validate_structure() {
|
||||
local message="$1"
|
||||
|
||||
# Count lines
|
||||
local line_count=$(echo "$message" | wc -l)
|
||||
|
||||
# Split message into parts
|
||||
local subject=$(echo "$message" | head -1)
|
||||
local rest=$(echo "$message" | tail -n +2)
|
||||
|
||||
# Validate subject
|
||||
validate_subject "$subject"
|
||||
|
||||
# If multi-line, check for blank line after subject
|
||||
if [ "$line_count" -gt 1 ]; then
|
||||
local second_line=$(echo "$message" | sed -n '2p')
|
||||
|
||||
if [ -n "$second_line" ]; then
|
||||
ERRORS+=("Blank line required between subject and body")
|
||||
SCORE=$((SCORE - 10))
|
||||
fi
|
||||
|
||||
# Extract body (after blank line, before footer)
|
||||
local body=""
|
||||
local footer=""
|
||||
local in_footer=false
|
||||
|
||||
while IFS= read -r line; do
|
||||
# Check if line is footer token
|
||||
if echo "$line" | grep -qP '^(BREAKING CHANGE:|Closes|Fixes|Resolves|Refs|Reviewed-by|Signed-off-by)'; then
|
||||
in_footer=true
|
||||
fi
|
||||
|
||||
if [ "$in_footer" = true ]; then
|
||||
footer="${footer}${line}\n"
|
||||
else
|
||||
body="${body}${line}\n"
|
||||
fi
|
||||
done <<< "$rest"
|
||||
|
||||
# Remove leading blank line from body
|
||||
body=$(echo -e "$body" | sed '1{/^$/d;}')
|
||||
|
||||
# Validate body and footer
|
||||
validate_body "$body"
|
||||
validate_footer "$footer"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main validation logic
|
||||
main() {
|
||||
echo "COMMIT MESSAGE VALIDATION"
|
||||
echo "═══════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
echo "MESSAGE:"
|
||||
echo "───────────────────────────────────────────────"
|
||||
echo "$MESSAGE"
|
||||
echo ""
|
||||
|
||||
# Perform validation
|
||||
validate_structure "$MESSAGE"
|
||||
|
||||
# Calculate final status
|
||||
local status="VALID"
|
||||
if [ "${#ERRORS[@]}" -gt 0 ]; then
|
||||
status="INVALID"
|
||||
elif [ "$STRICT_MODE" = "true" ] && [ "${#WARNINGS[@]}" -gt 0 ]; then
|
||||
status="INVALID"
|
||||
fi
|
||||
|
||||
# Display results
|
||||
echo "VALIDATION RESULTS:"
|
||||
echo "───────────────────────────────────────────────"
|
||||
|
||||
if [ "${#ERRORS[@]}" -gt 0 ]; then
|
||||
echo "✗ ERRORS:"
|
||||
for error in "${ERRORS[@]}"; do
|
||||
echo " - $error"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ "${#WARNINGS[@]}" -gt 0 ]; then
|
||||
echo "⚠ WARNINGS:"
|
||||
for warning in "${WARNINGS[@]}"; do
|
||||
echo " - $warning"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ "${#SUGGESTIONS[@]}" -gt 0 ]; then
|
||||
echo "💡 SUGGESTIONS:"
|
||||
for suggestion in "${SUGGESTIONS[@]}"; do
|
||||
echo " - $suggestion"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ "${#ERRORS[@]}" -eq 0 ] && [ "${#WARNINGS[@]}" -eq 0 ]; then
|
||||
echo "✓ All checks passed"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "STATUS: $status"
|
||||
echo "SCORE: $SCORE/100"
|
||||
echo "STRICT MODE: $STRICT_MODE"
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════"
|
||||
|
||||
# Exit based on status
|
||||
if [ "$status" = "INVALID" ]; then
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main
|
||||
main
|
||||
354
commands/message-generation/.scripts/subject-generator.py
Executable file
354
commands/message-generation/.scripts/subject-generator.py
Executable file
@@ -0,0 +1,354 @@
|
||||
#!/usr/bin/env python3
|
||||
# Script: subject-generator.py
|
||||
# Purpose: Generate conventional commit subject line with validation
|
||||
# Author: Git Commit Assistant Plugin
|
||||
# Version: 1.0.0
|
||||
#
|
||||
# Usage:
|
||||
# echo '{"type":"feat","scope":"auth","description":"add OAuth"}' | ./subject-generator.py
|
||||
# cat input.json | ./subject-generator.py
|
||||
#
|
||||
# Returns:
|
||||
# JSON: {"subject": "feat(auth): add OAuth", "length": 22, "warnings": [], "suggestions": []}
|
||||
#
|
||||
# Exit Codes:
|
||||
# 0 - Success
|
||||
# 1 - Invalid input
|
||||
# 2 - Validation error
|
||||
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
|
||||
def enforce_imperative_mood(text):
|
||||
"""Convert common non-imperative forms to imperative mood."""
|
||||
|
||||
# Common past tense to imperative conversions
|
||||
conversions = {
|
||||
r'\badded\b': 'add',
|
||||
r'\bfixed\b': 'fix',
|
||||
r'\bupdated\b': 'update',
|
||||
r'\bremoved\b': 'remove',
|
||||
r'\bchanged\b': 'change',
|
||||
r'\bimproved\b': 'improve',
|
||||
r'\brefactored\b': 'refactor',
|
||||
r'\bimplemented\b': 'implement',
|
||||
r'\bcreated\b': 'create',
|
||||
r'\bdeleted\b': 'delete',
|
||||
r'\bmodified\b': 'modify',
|
||||
r'\boptimized\b': 'optimize',
|
||||
r'\bmoved\b': 'move',
|
||||
r'\brenamed\b': 'rename',
|
||||
r'\bcleaned\b': 'clean',
|
||||
r'\bintroduced\b': 'introduce',
|
||||
}
|
||||
|
||||
# Present tense (3rd person) to imperative
|
||||
present_conversions = {
|
||||
r'\badds\b': 'add',
|
||||
r'\bfixes\b': 'fix',
|
||||
r'\bupdates\b': 'update',
|
||||
r'\bremoves\b': 'remove',
|
||||
r'\bchanges\b': 'change',
|
||||
r'\bimproves\b': 'improve',
|
||||
r'\brefactors\b': 'refactor',
|
||||
r'\bimplements\b': 'implement',
|
||||
r'\bcreates\b': 'create',
|
||||
r'\bdeletes\b': 'delete',
|
||||
r'\bmodifies\b': 'modify',
|
||||
r'\boptimizes\b': 'optimize',
|
||||
r'\bmoves\b': 'move',
|
||||
r'\brenames\b': 'rename',
|
||||
r'\bcleans\b': 'clean',
|
||||
r'\bintroduces\b': 'introduce',
|
||||
}
|
||||
|
||||
original = text
|
||||
|
||||
# Apply conversions
|
||||
for pattern, replacement in conversions.items():
|
||||
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
|
||||
|
||||
for pattern, replacement in present_conversions.items():
|
||||
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
|
||||
|
||||
# Track if changes were made
|
||||
changed = (original != text)
|
||||
|
||||
return text, changed
|
||||
|
||||
def check_capitalization(text):
|
||||
"""Check if description starts with lowercase (should not be capitalized)."""
|
||||
if not text:
|
||||
return True, []
|
||||
|
||||
warnings = []
|
||||
if text[0].isupper():
|
||||
warnings.append({
|
||||
'type': 'capitalization',
|
||||
'message': 'Description should start with lowercase',
|
||||
'current': text,
|
||||
'suggested': text[0].lower() + text[1:]
|
||||
})
|
||||
return False, warnings
|
||||
|
||||
return True, warnings
|
||||
|
||||
def check_period_at_end(text):
|
||||
"""Check if description ends with period (should not)."""
|
||||
warnings = []
|
||||
if text.endswith('.'):
|
||||
warnings.append({
|
||||
'type': 'punctuation',
|
||||
'message': 'Subject should not end with period',
|
||||
'current': text,
|
||||
'suggested': text[:-1]
|
||||
})
|
||||
return False, warnings
|
||||
|
||||
return True, warnings
|
||||
|
||||
def shorten_description(description, max_length, type_scope_part):
|
||||
"""Attempt to shorten description to fit within max_length."""
|
||||
|
||||
# Calculate available space for description
|
||||
prefix_length = len(type_scope_part) + 2 # +2 for ": "
|
||||
available_length = max_length - prefix_length
|
||||
|
||||
if len(description) <= available_length:
|
||||
return description, []
|
||||
|
||||
suggestions = []
|
||||
|
||||
# Strategy 1: Remove filler words
|
||||
filler_words = ['a', 'an', 'the', 'some', 'very', 'really', 'just', 'quite']
|
||||
shortened = description
|
||||
for word in filler_words:
|
||||
shortened = re.sub(r'\b' + word + r'\b\s*', '', shortened, flags=re.IGNORECASE)
|
||||
shortened = shortened.strip()
|
||||
if len(shortened) <= available_length:
|
||||
suggestions.append({
|
||||
'strategy': 'remove_filler',
|
||||
'description': shortened,
|
||||
'saved': len(description) - len(shortened)
|
||||
})
|
||||
return shortened, suggestions
|
||||
|
||||
# Strategy 2: Truncate with ellipsis (not recommended but possible)
|
||||
if available_length > 3:
|
||||
truncated = description[:available_length - 3] + '...'
|
||||
suggestions.append({
|
||||
'strategy': 'truncate',
|
||||
'description': truncated,
|
||||
'warning': 'Truncation loses information - consider moving details to body'
|
||||
})
|
||||
|
||||
# Strategy 3: Suggest moving to body
|
||||
suggestions.append({
|
||||
'strategy': 'move_to_body',
|
||||
'description': description[:available_length],
|
||||
'remaining': description[available_length:],
|
||||
'warning': 'Move detailed information to commit body'
|
||||
})
|
||||
|
||||
return description, suggestions
|
||||
|
||||
def generate_subject(data):
|
||||
"""
|
||||
Generate commit subject line from input data.
|
||||
|
||||
Args:
|
||||
data: dict with keys: type, scope (optional), description, max_length (optional)
|
||||
|
||||
Returns:
|
||||
dict with subject, length, warnings, suggestions
|
||||
"""
|
||||
|
||||
# Extract parameters
|
||||
commit_type = data.get('type', '').strip().lower()
|
||||
scope = data.get('scope', '').strip()
|
||||
description = data.get('description', '').strip()
|
||||
max_length = int(data.get('max_length', 50))
|
||||
|
||||
# Validate required fields
|
||||
if not commit_type:
|
||||
return {
|
||||
'error': 'type is required',
|
||||
'subject': None
|
||||
}
|
||||
|
||||
if not description:
|
||||
return {
|
||||
'error': 'description is required',
|
||||
'subject': None
|
||||
}
|
||||
|
||||
# Validate type
|
||||
valid_types = ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert']
|
||||
if commit_type not in valid_types:
|
||||
return {
|
||||
'error': f'Invalid type "{commit_type}". Valid types: {", ".join(valid_types)}',
|
||||
'subject': None
|
||||
}
|
||||
|
||||
# Enforce imperative mood
|
||||
original_description = description
|
||||
description, mood_changed = enforce_imperative_mood(description)
|
||||
|
||||
# Ensure lowercase after colon
|
||||
if description and description[0].isupper():
|
||||
description = description[0].lower() + description[1:]
|
||||
|
||||
# Remove period at end
|
||||
if description.endswith('.'):
|
||||
description = description[:-1]
|
||||
|
||||
# Build type(scope) part
|
||||
if scope:
|
||||
type_scope_part = f"{commit_type}({scope})"
|
||||
else:
|
||||
type_scope_part = commit_type
|
||||
|
||||
# Build initial subject
|
||||
subject = f"{type_scope_part}: {description}"
|
||||
subject_length = len(subject)
|
||||
|
||||
# Collect warnings and suggestions
|
||||
warnings = []
|
||||
suggestions = []
|
||||
|
||||
# Check mood change
|
||||
if mood_changed:
|
||||
warnings.append({
|
||||
'type': 'mood',
|
||||
'message': 'Changed to imperative mood',
|
||||
'original': original_description,
|
||||
'corrected': description
|
||||
})
|
||||
|
||||
# Check length
|
||||
if subject_length > max_length:
|
||||
warnings.append({
|
||||
'type': 'length',
|
||||
'message': f'Subject exceeds {max_length} characters ({subject_length} chars)',
|
||||
'length': subject_length,
|
||||
'max': max_length,
|
||||
'excess': subject_length - max_length
|
||||
})
|
||||
|
||||
# Try to shorten
|
||||
shortened_desc, shorten_suggestions = shorten_description(description, max_length, type_scope_part)
|
||||
if shorten_suggestions:
|
||||
suggestions.extend(shorten_suggestions)
|
||||
|
||||
# If we successfully shortened, update subject
|
||||
if len(shortened_desc) < len(description):
|
||||
alternative_subject = f"{type_scope_part}: {shortened_desc}"
|
||||
if len(alternative_subject) <= max_length:
|
||||
suggestions.append({
|
||||
'type': 'shortened_subject',
|
||||
'subject': alternative_subject,
|
||||
'saved': subject_length - len(alternative_subject)
|
||||
})
|
||||
|
||||
# Warning if close to limit
|
||||
elif subject_length > 45 and max_length == 50:
|
||||
suggestions.append({
|
||||
'type': 'near_limit',
|
||||
'message': f'Subject is close to {max_length} character limit ({subject_length} chars)'
|
||||
})
|
||||
|
||||
# Check for common issues
|
||||
if ' and ' in description or ' & ' in description:
|
||||
suggestions.append({
|
||||
'type': 'multiple_changes',
|
||||
'message': 'Subject mentions multiple changes - consider splitting into multiple commits or using bullet points in body'
|
||||
})
|
||||
|
||||
# Check for filler words
|
||||
filler_pattern = r'\b(just|very|really|quite|some)\b'
|
||||
if re.search(filler_pattern, description, re.IGNORECASE):
|
||||
cleaned = re.sub(filler_pattern, '', description, flags=re.IGNORECASE)
|
||||
cleaned = re.sub(r'\s+', ' ', cleaned).strip()
|
||||
suggestions.append({
|
||||
'type': 'filler_words',
|
||||
'message': 'Remove filler words for clarity',
|
||||
'current': description,
|
||||
'suggested': cleaned
|
||||
})
|
||||
|
||||
# Build response
|
||||
response = {
|
||||
'subject': subject,
|
||||
'length': subject_length,
|
||||
'max_length': max_length,
|
||||
'type': commit_type,
|
||||
'scope': scope if scope else None,
|
||||
'description': description,
|
||||
'valid': subject_length <= max_length and subject_length <= 72, # 72 is hard limit
|
||||
'warnings': warnings,
|
||||
'suggestions': suggestions
|
||||
}
|
||||
|
||||
# Add quality score
|
||||
score = 100
|
||||
if subject_length > max_length:
|
||||
score -= 20
|
||||
if subject_length > 72:
|
||||
score -= 30 # Major penalty for exceeding hard limit
|
||||
if mood_changed:
|
||||
score -= 5
|
||||
if len(warnings) > 0:
|
||||
score -= len(warnings) * 3
|
||||
|
||||
response['quality_score'] = max(0, score)
|
||||
|
||||
return response
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
|
||||
try:
|
||||
# Read JSON input from stdin
|
||||
input_data = sys.stdin.read()
|
||||
|
||||
if not input_data or not input_data.strip():
|
||||
print(json.dumps({
|
||||
'error': 'No input provided',
|
||||
'subject': None
|
||||
}))
|
||||
sys.exit(1)
|
||||
|
||||
# Parse JSON
|
||||
try:
|
||||
data = json.loads(input_data)
|
||||
except json.JSONDecodeError as e:
|
||||
print(json.dumps({
|
||||
'error': f'Invalid JSON: {str(e)}',
|
||||
'subject': None
|
||||
}))
|
||||
sys.exit(1)
|
||||
|
||||
# Generate subject
|
||||
result = generate_subject(data)
|
||||
|
||||
# Output result
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
# Exit code based on result
|
||||
if 'error' in result:
|
||||
sys.exit(2)
|
||||
elif not result.get('valid', False):
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
print(json.dumps({
|
||||
'error': f'Unexpected error: {str(e)}',
|
||||
'subject': None
|
||||
}))
|
||||
sys.exit(2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
371
commands/message-generation/add-footer.md
Normal file
371
commands/message-generation/add-footer.md
Normal file
@@ -0,0 +1,371 @@
|
||||
---
|
||||
description: Add footer with breaking changes and issue references to commit message
|
||||
---
|
||||
|
||||
# Operation: Add Footer
|
||||
|
||||
Add a properly formatted footer to commit message with breaking changes, issue references, and other metadata.
|
||||
|
||||
## Parameters from $ARGUMENTS
|
||||
|
||||
**Optional (at least one required):**
|
||||
- `breaking:` - Breaking change description
|
||||
- `closes:` - Comma-separated issue numbers to close
|
||||
- `fixes:` - Comma-separated issue numbers to fix
|
||||
- `refs:` - Comma-separated issue numbers to reference
|
||||
- `reviewed:` - Reviewer name(s)
|
||||
- `signed:` - Signed-off-by name and email
|
||||
|
||||
**Format:** `footer breaking:"API changed" closes:123,456`
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Parse Parameters
|
||||
|
||||
Extract parameters from $ARGUMENTS:
|
||||
|
||||
```bash
|
||||
# Parse footer components
|
||||
breaking=$(echo "$ARGUMENTS" | grep -oP 'breaking:"\K[^"]+')
|
||||
closes=$(echo "$ARGUMENTS" | grep -oP 'closes:\K[0-9,]+')
|
||||
fixes=$(echo "$ARGUMENTS" | grep -oP 'fixes:\K[0-9,]+')
|
||||
refs=$(echo "$ARGUMENTS" | grep -oP 'refs:\K[0-9,]+')
|
||||
reviewed=$(echo "$ARGUMENTS" | grep -oP 'reviewed:"\K[^"]+')
|
||||
signed=$(echo "$ARGUMENTS" | grep -oP 'signed:"\K[^"]+')
|
||||
```
|
||||
|
||||
### Step 2: Validate Parameters
|
||||
|
||||
**Check at least one parameter provided:**
|
||||
```bash
|
||||
if [ -z "$breaking" ] && [ -z "$closes" ] && [ -z "$fixes" ] && [ -z "$refs" ] && [ -z "$reviewed" ] && [ -z "$signed" ]; then
|
||||
echo "ERROR: At least one footer parameter is required"
|
||||
echo "Usage: footer [breaking:\"<desc>\"] [closes:<nums>] [fixes:<nums>] [refs:<nums>]"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 3: Invoke Footer Builder Script
|
||||
|
||||
Pass parameters to the utility script for proper formatting:
|
||||
|
||||
```bash
|
||||
# Prepare JSON input
|
||||
cat <<EOF | /home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation/.scripts/footer-builder.py
|
||||
{
|
||||
"breaking": "$breaking",
|
||||
"closes": "$closes",
|
||||
"fixes": "$fixes",
|
||||
"refs": "$refs",
|
||||
"reviewed": "$reviewed",
|
||||
"signed": "$signed"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
The script will:
|
||||
- Format BREAKING CHANGE properly
|
||||
- Convert issue numbers to proper references
|
||||
- Order footer elements correctly
|
||||
- Ensure proper spacing and formatting
|
||||
|
||||
### Step 4: Format Output
|
||||
|
||||
Present the generated footer:
|
||||
|
||||
```
|
||||
FOOTER GENERATED
|
||||
═══════════════════════════════════════════════
|
||||
|
||||
FOOTER:
|
||||
───────────────────────────────────────────────
|
||||
<blank line>
|
||||
<formatted footer content>
|
||||
|
||||
VALIDATION:
|
||||
───────────────────────────────────────────────
|
||||
✓ Blank line before footer
|
||||
✓ BREAKING CHANGE format correct
|
||||
✓ Issue references valid
|
||||
✓ Proper formatting
|
||||
|
||||
COMPONENTS:
|
||||
───────────────────────────────────────────────
|
||||
Breaking Changes: <yes/no>
|
||||
Issues Closed: X
|
||||
Issues Fixed: X
|
||||
References: X
|
||||
Signed-off: <yes/no>
|
||||
|
||||
═══════════════════════════════════════════════
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
Return structured output:
|
||||
- Formatted footer text
|
||||
- Validation results
|
||||
- Component breakdown
|
||||
- Suggestions (if any)
|
||||
|
||||
## Error Handling
|
||||
|
||||
**No parameters provided:**
|
||||
```
|
||||
ERROR: At least one footer parameter is required
|
||||
Usage: footer [breaking:"<desc>"] [closes:<nums>] [fixes:<nums>]
|
||||
|
||||
Example: footer breaking:"authentication API changed" closes:123
|
||||
```
|
||||
|
||||
**Invalid issue number format:**
|
||||
```
|
||||
ERROR: Invalid issue number format
|
||||
Expected: closes:123 or closes:123,456
|
||||
Received: closes:abc
|
||||
```
|
||||
|
||||
**Missing breaking change description:**
|
||||
```
|
||||
ERROR: breaking parameter requires description
|
||||
Usage: footer breaking:"<description of breaking change>"
|
||||
|
||||
Example: footer breaking:"API endpoint /auth removed"
|
||||
```
|
||||
|
||||
## Footer Format Rules
|
||||
|
||||
**Order of Elements:**
|
||||
```
|
||||
<blank line>
|
||||
BREAKING CHANGE: <description>
|
||||
Closes #<issue>
|
||||
Fixes #<issue>
|
||||
Refs #<issue>
|
||||
Reviewed-by: <name>
|
||||
Signed-off-by: <name> <email>
|
||||
```
|
||||
|
||||
**Breaking Changes:**
|
||||
- Always use `BREAKING CHANGE:` (uppercase, singular)
|
||||
- Provide clear description
|
||||
- Can also use `!` in subject: `feat!: change API`
|
||||
|
||||
**Issue References:**
|
||||
- Use `Closes #123` for issues closed by this commit
|
||||
- Use `Fixes #123` for bugs fixed by this commit
|
||||
- Use `Refs #123` for related issues
|
||||
- Multiple issues: `Closes #123, #456`
|
||||
|
||||
**Metadata:**
|
||||
- `Reviewed-by:` for code review
|
||||
- `Signed-off-by:` for DCO compliance
|
||||
- Custom trailers as needed
|
||||
|
||||
## Integration with Agent
|
||||
|
||||
The commit-assistant agent uses this operation to:
|
||||
1. Add breaking change notices
|
||||
2. Link commits to issues
|
||||
3. Add review metadata
|
||||
4. Ensure proper footer formatting
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Breaking Change
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation footer breaking:"authentication API endpoint changed from /login to /auth/login"
|
||||
|
||||
# Output
|
||||
FOOTER:
|
||||
|
||||
BREAKING CHANGE: authentication API endpoint changed from /login to
|
||||
/auth/login
|
||||
```
|
||||
|
||||
### Example 2: Close Issues
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation footer closes:123,456,789
|
||||
|
||||
# Output
|
||||
FOOTER:
|
||||
|
||||
Closes #123, #456, #789
|
||||
```
|
||||
|
||||
### Example 3: Fix and Close
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation footer fixes:42 closes:100
|
||||
|
||||
# Output
|
||||
FOOTER:
|
||||
|
||||
Fixes #42
|
||||
Closes #100
|
||||
```
|
||||
|
||||
### Example 4: Complete Footer
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation footer breaking:"remove deprecated API" closes:200 signed:"John Doe <john@example.com>"
|
||||
|
||||
# Output
|
||||
FOOTER:
|
||||
|
||||
BREAKING CHANGE: remove deprecated API
|
||||
Closes #200
|
||||
Signed-off-by: John Doe <john@example.com>
|
||||
```
|
||||
|
||||
### Example 5: Multiple Issue Fixes
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation footer fixes:10,20,30 refs:100
|
||||
|
||||
# Output
|
||||
FOOTER:
|
||||
|
||||
Fixes #10, #20, #30
|
||||
Refs #100
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
**Breaking Changes:**
|
||||
- ✅ "BREAKING CHANGE: API endpoint changed"
|
||||
- ❌ "Breaking change: api endpoint changed"
|
||||
- ✅ Clear description of what broke
|
||||
- ❌ Vague "things changed"
|
||||
|
||||
**Issue References:**
|
||||
- ✅ "Closes #123" (actually closes)
|
||||
- ❌ "Closes #123" (just mentions)
|
||||
- ✅ "Refs #100" (related)
|
||||
- ❌ "See issue 100"
|
||||
|
||||
**Issue Linking Semantics:**
|
||||
- `Closes` - Pull request or feature complete
|
||||
- `Fixes` - Bug fix
|
||||
- `Refs` - Related but not closed
|
||||
- `Resolves` - Alternative to Closes
|
||||
|
||||
## When to Include a Footer
|
||||
|
||||
**Include footer when:**
|
||||
- Breaking changes introduced
|
||||
- Closes or fixes issues
|
||||
- Multiple reviewers
|
||||
- DCO/signing required
|
||||
- Related work references
|
||||
|
||||
**Omit footer when:**
|
||||
- No breaking changes
|
||||
- No issue tracking
|
||||
- No special metadata
|
||||
- Simple standalone commit
|
||||
|
||||
## Breaking Change Detection
|
||||
|
||||
**Patterns that indicate breaking changes:**
|
||||
- API endpoint changes
|
||||
- Function signature changes
|
||||
- Removed features
|
||||
- Changed behavior
|
||||
- Dependency major version bumps
|
||||
- Configuration format changes
|
||||
|
||||
**How to communicate breaking changes:**
|
||||
```
|
||||
BREAKING CHANGE: <brief description>
|
||||
|
||||
<longer explanation of what changed>
|
||||
<migration path if applicable>
|
||||
```
|
||||
|
||||
**Alternative notation:**
|
||||
```
|
||||
feat!: change API endpoint
|
||||
|
||||
# The ! indicates breaking change
|
||||
```
|
||||
|
||||
## Footer Templates
|
||||
|
||||
**Feature with Issue:**
|
||||
```
|
||||
Closes #<issue>
|
||||
```
|
||||
|
||||
**Bug Fix:**
|
||||
```
|
||||
Fixes #<issue>
|
||||
```
|
||||
|
||||
**Breaking Change with Migration:**
|
||||
```
|
||||
BREAKING CHANGE: <what changed>
|
||||
|
||||
Migration: <how to update>
|
||||
Closes #<issue>
|
||||
```
|
||||
|
||||
**Multiple Issues:**
|
||||
```
|
||||
Fixes #<bug1>, #<bug2>
|
||||
Closes #<feature>
|
||||
Refs #<related>
|
||||
```
|
||||
|
||||
**Signed Commit:**
|
||||
```
|
||||
Reviewed-by: <reviewer>
|
||||
Signed-off-by: <author> <email>
|
||||
```
|
||||
|
||||
## GitHub/GitLab Integration
|
||||
|
||||
**GitHub Keywords (auto-close issues):**
|
||||
- Closes, Closed, Close
|
||||
- Fixes, Fixed, Fix
|
||||
- Resolves, Resolved, Resolve
|
||||
|
||||
**GitLab Keywords:**
|
||||
- Closes, Closed, Close (same as GitHub)
|
||||
- Fixes, Fixed, Fix
|
||||
- Resolves, Resolved, Resolve
|
||||
- Implements, Implemented, Implement
|
||||
|
||||
**Format:**
|
||||
```
|
||||
Closes #123 # Same repository
|
||||
Closes user/repo#123 # Different repository
|
||||
Closes https://github.com/... # Full URL
|
||||
```
|
||||
|
||||
## Footer Validation
|
||||
|
||||
**Valid footer format:**
|
||||
```
|
||||
✓ Blank line before footer
|
||||
✓ BREAKING CHANGE in capitals
|
||||
✓ Issue numbers have # prefix
|
||||
✓ Proper token format (Closes, Fixes, etc.)
|
||||
✓ Valid email in Signed-off-by
|
||||
```
|
||||
|
||||
**Invalid footer format:**
|
||||
```
|
||||
✗ No blank line before footer
|
||||
✗ "Breaking change:" (lowercase)
|
||||
✗ "Closes 123" (missing #)
|
||||
✗ "Resolves issue 123" (wrong format)
|
||||
✗ Invalid email format
|
||||
```
|
||||
592
commands/message-generation/complete-message.md
Normal file
592
commands/message-generation/complete-message.md
Normal file
@@ -0,0 +1,592 @@
|
||||
---
|
||||
description: Generate complete commit message orchestrating subject, body, and footer
|
||||
---
|
||||
|
||||
# Operation: Complete Message
|
||||
|
||||
Generate a complete, well-formatted commit message by orchestrating subject generation, body composition, and footer creation.
|
||||
|
||||
## Parameters from $ARGUMENTS
|
||||
|
||||
**Required:**
|
||||
- `type:` - Commit type (feat, fix, docs, etc.)
|
||||
|
||||
**Optional:**
|
||||
- `scope:` - Affected module/component
|
||||
- `description:` - Brief description (if not provided, derived from files)
|
||||
- `files:` - Comma-separated file paths for context
|
||||
- `changes:` - Comma-separated list of changes
|
||||
- `why:` - Explanation of why changes were made
|
||||
- `breaking:` - Breaking change description
|
||||
- `closes:` - Issue numbers to close
|
||||
- `fixes:` - Issue numbers to fix
|
||||
- `include_body:` - Include body (true|false, default: true if multiple files)
|
||||
- `include_footer:` - Include footer (true|false, default: true if breaking/issues)
|
||||
|
||||
**Format:** `complete type:feat scope:auth files:"file1.js,file2.js" breaking:"API changed" closes:123`
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Parse All Parameters
|
||||
|
||||
Extract all parameters from $ARGUMENTS:
|
||||
|
||||
```bash
|
||||
# Required
|
||||
type=$(echo "$ARGUMENTS" | grep -oP 'type:\K[^ ]+')
|
||||
|
||||
# Optional
|
||||
scope=$(echo "$ARGUMENTS" | grep -oP 'scope:\K[^ ]+')
|
||||
description=$(echo "$ARGUMENTS" | grep -oP 'description:"\K[^"]+' || echo "$ARGUMENTS" | grep -oP 'description:\K[^ ]+')
|
||||
files=$(echo "$ARGUMENTS" | grep -oP 'files:"\K[^"]+' || echo "$ARGUMENTS" | grep -oP 'files:\K[^ ]+')
|
||||
changes=$(echo "$ARGUMENTS" | grep -oP 'changes:"\K[^"]+')
|
||||
why=$(echo "$ARGUMENTS" | grep -oP 'why:"\K[^"]+')
|
||||
breaking=$(echo "$ARGUMENTS" | grep -oP 'breaking:"\K[^"]+')
|
||||
closes=$(echo "$ARGUMENTS" | grep -oP 'closes:\K[0-9,]+')
|
||||
fixes=$(echo "$ARGUMENTS" | grep -oP 'fixes:\K[0-9,]+')
|
||||
include_body=$(echo "$ARGUMENTS" | grep -oP 'include_body:\K(true|false)' || echo "auto")
|
||||
include_footer=$(echo "$ARGUMENTS" | grep -oP 'include_footer:\K(true|false)' || echo "auto")
|
||||
```
|
||||
|
||||
### Step 2: Validate Required Parameters
|
||||
|
||||
**Check type is provided:**
|
||||
```bash
|
||||
if [ -z "$type" ]; then
|
||||
echo "ERROR: type parameter is required"
|
||||
echo "Usage: complete type:<type> [scope:<scope>] [files:\"<files>\"]"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Validate type:**
|
||||
```bash
|
||||
valid_types="feat fix docs style refactor perf test build ci chore revert"
|
||||
if ! echo "$valid_types" | grep -qw "$type"; then
|
||||
echo "ERROR: Invalid type '$type'"
|
||||
echo "Valid types: $valid_types"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 3: Derive Missing Parameters
|
||||
|
||||
**If description not provided, derive from files:**
|
||||
```bash
|
||||
if [ -z "$description" ] && [ -n "$files" ]; then
|
||||
# Analyze files to create description
|
||||
file_count=$(echo "$files" | tr ',' '\n' | wc -l)
|
||||
if [ $file_count -eq 1 ]; then
|
||||
description="update $(basename $files)"
|
||||
else
|
||||
# Extract common directory or feature
|
||||
description="update ${file_count} files"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
**Determine body inclusion:**
|
||||
```bash
|
||||
if [ "$include_body" = "auto" ]; then
|
||||
file_count=$(echo "$files" | tr ',' '\n' | wc -l)
|
||||
if [ $file_count -gt 1 ] || [ -n "$changes" ] || [ -n "$why" ]; then
|
||||
include_body="true"
|
||||
else
|
||||
include_body="false"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
**Determine footer inclusion:**
|
||||
```bash
|
||||
if [ "$include_footer" = "auto" ]; then
|
||||
if [ -n "$breaking" ] || [ -n "$closes" ] || [ -n "$fixes" ]; then
|
||||
include_footer="true"
|
||||
else
|
||||
include_footer="false"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 4: Generate Subject Line
|
||||
|
||||
**Read and execute generate-subject.md:**
|
||||
|
||||
```bash
|
||||
# Build subject arguments
|
||||
subject_args="subject type:$type"
|
||||
[ -n "$scope" ] && subject_args="$subject_args scope:$scope"
|
||||
[ -n "$description" ] && subject_args="$subject_args description:\"$description\""
|
||||
|
||||
# Invoke subject generation
|
||||
subject_result=$(bash -c "cd /home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation && cat generate-subject.md")
|
||||
|
||||
# Extract generated subject line
|
||||
subject_line="<generated subject>"
|
||||
```
|
||||
|
||||
**Store subject line:**
|
||||
```bash
|
||||
COMMIT_MESSAGE="$subject_line"
|
||||
```
|
||||
|
||||
### Step 5: Generate Body (if needed)
|
||||
|
||||
**If include_body is true, read and execute write-body.md:**
|
||||
|
||||
```bash
|
||||
if [ "$include_body" = "true" ]; then
|
||||
# Build body arguments
|
||||
body_args="body"
|
||||
|
||||
if [ -n "$changes" ]; then
|
||||
body_args="$body_args changes:\"$changes\""
|
||||
elif [ -n "$files" ]; then
|
||||
# Derive changes from files
|
||||
body_args="$body_args changes:\"$files\""
|
||||
fi
|
||||
|
||||
[ -n "$why" ] && body_args="$body_args why:\"$why\""
|
||||
|
||||
# Invoke body generation
|
||||
body_result=$(bash -c "cd /home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation && cat write-body.md")
|
||||
|
||||
# Extract generated body
|
||||
body_content="<generated body>"
|
||||
|
||||
# Append to message (with blank line)
|
||||
COMMIT_MESSAGE="$COMMIT_MESSAGE
|
||||
|
||||
$body_content"
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 6: Generate Footer (if needed)
|
||||
|
||||
**If include_footer is true, read and execute add-footer.md:**
|
||||
|
||||
```bash
|
||||
if [ "$include_footer" = "true" ]; then
|
||||
# Build footer arguments
|
||||
footer_args="footer"
|
||||
[ -n "$breaking" ] && footer_args="$footer_args breaking:\"$breaking\""
|
||||
[ -n "$closes" ] && footer_args="$footer_args closes:$closes"
|
||||
[ -n "$fixes" ] && footer_args="$footer_args fixes:$fixes"
|
||||
|
||||
# Invoke footer generation
|
||||
footer_result=$(bash -c "cd /home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation && cat add-footer.md")
|
||||
|
||||
# Extract generated footer
|
||||
footer_content="<generated footer>"
|
||||
|
||||
# Append to message (with blank line)
|
||||
COMMIT_MESSAGE="$COMMIT_MESSAGE
|
||||
|
||||
$footer_content"
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 7: Validate Complete Message
|
||||
|
||||
**Read and execute validate-message.md:**
|
||||
|
||||
```bash
|
||||
# Invoke validation
|
||||
validation_result=$(bash -c "cd /home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation && cat validate-message.md")
|
||||
|
||||
# Parse validation result
|
||||
validation_status="<VALID|INVALID>"
|
||||
validation_score="<score>"
|
||||
```
|
||||
|
||||
### Step 8: Format Final Output
|
||||
|
||||
Present the complete commit message:
|
||||
|
||||
```
|
||||
COMPLETE COMMIT MESSAGE GENERATED
|
||||
═══════════════════════════════════════════════
|
||||
|
||||
MESSAGE:
|
||||
───────────────────────────────────────────────
|
||||
<subject line>
|
||||
|
||||
<body content if present>
|
||||
|
||||
<footer content if present>
|
||||
───────────────────────────────────────────────
|
||||
|
||||
COMPONENTS:
|
||||
───────────────────────────────────────────────
|
||||
Subject: ✓ Generated (XX chars)
|
||||
Body: ✓ Generated (X lines) / ⊘ Omitted
|
||||
Footer: ✓ Generated / ⊘ Omitted
|
||||
|
||||
VALIDATION:
|
||||
───────────────────────────────────────────────
|
||||
Format: ✓ Conventional Commits
|
||||
Status: ✓ VALID
|
||||
Score: XX/100
|
||||
|
||||
STATISTICS:
|
||||
───────────────────────────────────────────────
|
||||
Total Lines: X
|
||||
Subject Length: XX/50 chars
|
||||
Body Lines: X (if present)
|
||||
Footer Elements: X (if present)
|
||||
|
||||
READY TO COMMIT:
|
||||
───────────────────────────────────────────────
|
||||
git commit -m "$(cat <<'EOF'
|
||||
<complete message here>
|
||||
EOF
|
||||
)"
|
||||
|
||||
═══════════════════════════════════════════════
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
Return structured output with:
|
||||
- Complete formatted message
|
||||
- Component breakdown
|
||||
- Validation results
|
||||
- Statistics
|
||||
- Ready-to-use git command
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Missing required parameters:**
|
||||
```
|
||||
ERROR: type parameter is required
|
||||
Usage: complete type:<type> [scope:<scope>] [files:"<files>"]
|
||||
|
||||
Example: complete type:feat scope:auth files:"auth.js,provider.js"
|
||||
```
|
||||
|
||||
**Invalid type:**
|
||||
```
|
||||
ERROR: Invalid type 'feature'
|
||||
Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
||||
```
|
||||
|
||||
**Validation fails:**
|
||||
```
|
||||
WARNING: Generated message has issues
|
||||
<validation errors>
|
||||
|
||||
Message generated but may need manual adjustment.
|
||||
```
|
||||
|
||||
**Insufficient context:**
|
||||
```
|
||||
ERROR: Insufficient information to generate message
|
||||
Provide either:
|
||||
- description: explicit description
|
||||
- files: files changed for context
|
||||
- changes: list of changes made
|
||||
|
||||
Example: complete type:feat description:"add authentication"
|
||||
```
|
||||
|
||||
## Integration with Agent
|
||||
|
||||
The commit-assistant agent uses this operation as the primary message generation workflow:
|
||||
|
||||
```
|
||||
1. User: "commit my changes"
|
||||
↓
|
||||
2. Agent: Analyze changes (/commit-analysis analyze)
|
||||
→ type=feat, scope=auth, files=[...]
|
||||
↓
|
||||
3. Agent: Generate complete message (THIS OPERATION)
|
||||
→ /message-generation complete type:feat scope:auth files:"..."
|
||||
↓
|
||||
4. Operation: Orchestrate generation
|
||||
→ Generate subject
|
||||
→ Generate body (if multiple files)
|
||||
→ Generate footer (if issues/breaking)
|
||||
→ Validate complete message
|
||||
↓
|
||||
5. Return: Complete, validated message
|
||||
↓
|
||||
6. Agent: Present to user for approval
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Simple Commit (Subject Only)
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation complete type:fix description:"resolve login crash"
|
||||
|
||||
# Output
|
||||
MESSAGE:
|
||||
fix: resolve login crash
|
||||
|
||||
COMPONENTS:
|
||||
Subject: ✓ (26/50 chars)
|
||||
Body: ⊘ Omitted (single change)
|
||||
Footer: ⊘ Omitted (no issues)
|
||||
|
||||
VALIDATION: ✓ VALID (100/100)
|
||||
```
|
||||
|
||||
### Example 2: Feature with Multiple Files
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation complete type:feat scope:auth files:"oauth.js,providers/google.js,providers/github.js"
|
||||
|
||||
# Output
|
||||
MESSAGE:
|
||||
feat(auth): add OAuth authentication
|
||||
|
||||
- Add OAuth2 authentication module
|
||||
- Implement Google provider
|
||||
- Implement GitHub provider
|
||||
|
||||
COMPONENTS:
|
||||
Subject: ✓ (36/50 chars)
|
||||
Body: ✓ Generated (3 lines)
|
||||
Footer: ⊘ Omitted
|
||||
|
||||
VALIDATION: ✓ VALID (95/100)
|
||||
```
|
||||
|
||||
### Example 3: Breaking Change with Issues
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation complete type:feat scope:api description:"redesign authentication API" breaking:"authentication endpoints changed" closes:100,101
|
||||
|
||||
# Output
|
||||
MESSAGE:
|
||||
feat(api): redesign authentication API
|
||||
|
||||
- Redesign authentication endpoints
|
||||
- Improve security with OAuth2
|
||||
- Simplify client integration
|
||||
|
||||
BREAKING CHANGE: authentication endpoints changed from /login and
|
||||
/logout to /auth/login and /auth/logout
|
||||
Closes #100, #101
|
||||
|
||||
COMPONENTS:
|
||||
Subject: ✓ (42/50 chars)
|
||||
Body: ✓ Generated (3 lines)
|
||||
Footer: ✓ Generated (breaking + issues)
|
||||
|
||||
VALIDATION: ✓ VALID (100/100)
|
||||
```
|
||||
|
||||
### Example 4: Bug Fix with Context
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation complete type:fix scope:api files:"user.js" why:"prevent null pointer when user not found" fixes:42
|
||||
|
||||
# Output
|
||||
MESSAGE:
|
||||
fix(api): resolve null pointer in user endpoint
|
||||
|
||||
- Add null check for user lookup
|
||||
- Return 404 when user not found
|
||||
- Add error handling
|
||||
|
||||
Prevent null pointer exception when user is not found in database.
|
||||
|
||||
Fixes #42
|
||||
|
||||
COMPONENTS:
|
||||
Subject: ✓ (46/50 chars)
|
||||
Body: ✓ Generated (5 lines with context)
|
||||
Footer: ✓ Generated (issue fix)
|
||||
|
||||
VALIDATION: ✓ VALID (100/100)
|
||||
```
|
||||
|
||||
### Example 5: Documentation Update
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation complete type:docs files:"README.md" description:"add installation instructions"
|
||||
|
||||
# Output
|
||||
MESSAGE:
|
||||
docs: add installation instructions
|
||||
|
||||
COMPONENTS:
|
||||
Subject: ✓ (36/50 chars)
|
||||
Body: ⊘ Omitted (docs only)
|
||||
Footer: ⊘ Omitted
|
||||
|
||||
VALIDATION: ✓ VALID (100/100)
|
||||
```
|
||||
|
||||
### Example 6: Explicit Body and Footer Control
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation complete type:refactor scope:database description:"optimize queries" include_body:true include_footer:false changes:"Add indexes,Optimize joins,Cache results"
|
||||
|
||||
# Output
|
||||
MESSAGE:
|
||||
refactor(database): optimize queries
|
||||
|
||||
- Add database indexes
|
||||
- Optimize query joins
|
||||
- Implement result caching
|
||||
|
||||
COMPONENTS:
|
||||
Subject: ✓ (35/50 chars)
|
||||
Body: ✓ Forced inclusion
|
||||
Footer: ⊘ Forced omission
|
||||
|
||||
VALIDATION: ✓ VALID (95/100)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
**Provide sufficient context:**
|
||||
- ✅ type + scope + files (agent can derive details)
|
||||
- ✅ type + description + changes (explicit)
|
||||
- ❌ type only (insufficient)
|
||||
|
||||
**Let automation work:**
|
||||
- ✅ Trust body/footer auto-inclusion logic
|
||||
- ❌ Manually force inclusion when not needed
|
||||
|
||||
**Use breaking and issues:**
|
||||
- ✅ Always include breaking changes
|
||||
- ✅ Always link to issues
|
||||
- ❌ Forget to document breaking changes
|
||||
|
||||
**Validate assumptions:**
|
||||
- ✅ Review generated message
|
||||
- ✅ Check validation results
|
||||
- ❌ Blindly commit generated message
|
||||
|
||||
## Decision Logic
|
||||
|
||||
**Body inclusion decision tree:**
|
||||
```
|
||||
if include_body explicitly set:
|
||||
use explicit value
|
||||
else if multiple files changed (>1):
|
||||
include body
|
||||
else if changes list provided:
|
||||
include body
|
||||
else if why context provided:
|
||||
include body
|
||||
else:
|
||||
omit body
|
||||
```
|
||||
|
||||
**Footer inclusion decision tree:**
|
||||
```
|
||||
if include_footer explicitly set:
|
||||
use explicit value
|
||||
else if breaking change provided:
|
||||
include footer
|
||||
else if issue numbers provided (closes/fixes):
|
||||
include footer
|
||||
else:
|
||||
omit footer
|
||||
```
|
||||
|
||||
## Orchestration Flow
|
||||
|
||||
**Step-by-step orchestration:**
|
||||
|
||||
```
|
||||
1. Parse parameters
|
||||
↓
|
||||
2. Validate required (type)
|
||||
↓
|
||||
3. Derive missing (description from files)
|
||||
↓
|
||||
4. Determine body/footer inclusion
|
||||
↓
|
||||
5. Generate subject (always)
|
||||
→ Invoke: generate-subject.md
|
||||
↓
|
||||
6. Generate body (if needed)
|
||||
→ Invoke: write-body.md
|
||||
↓
|
||||
7. Generate footer (if needed)
|
||||
→ Invoke: add-footer.md
|
||||
↓
|
||||
8. Combine all parts
|
||||
↓
|
||||
9. Validate complete message
|
||||
→ Invoke: validate-message.md
|
||||
↓
|
||||
10. Return complete message with validation
|
||||
```
|
||||
|
||||
## Error Recovery
|
||||
|
||||
**If subject generation fails:**
|
||||
- Return error immediately
|
||||
- Do not proceed to body/footer
|
||||
|
||||
**If body generation fails:**
|
||||
- Return subject only
|
||||
- Mark body as "generation failed"
|
||||
- Continue to footer if needed
|
||||
|
||||
**If footer generation fails:**
|
||||
- Return subject + body
|
||||
- Mark footer as "generation failed"
|
||||
|
||||
**If validation fails:**
|
||||
- Return message anyway
|
||||
- Include validation errors
|
||||
- Let user decide to fix or use
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
**Typical generation time:**
|
||||
- Subject only: <100ms
|
||||
- Subject + body: <200ms
|
||||
- Subject + body + footer: <300ms
|
||||
- With validation: +50ms
|
||||
|
||||
**Optimization:**
|
||||
- Cache repeated operations
|
||||
- Minimize script invocations
|
||||
- Stream output for large bodies
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
**Template-based generation:**
|
||||
```bash
|
||||
# Feature template
|
||||
complete type:feat scope:$SCOPE template:feature
|
||||
|
||||
# Bugfix template
|
||||
complete type:fix scope:$SCOPE template:bugfix fixes:$ISSUE
|
||||
```
|
||||
|
||||
**From analysis results:**
|
||||
```bash
|
||||
# Agent workflow
|
||||
analysis=$(/commit-analysis analyze)
|
||||
type=$(echo "$analysis" | jq -r '.type')
|
||||
scope=$(echo "$analysis" | jq -r '.scope')
|
||||
files=$(echo "$analysis" | jq -r '.files | join(",")')
|
||||
|
||||
/message-generation complete type:$type scope:$scope files:"$files"
|
||||
```
|
||||
|
||||
**Interactive refinement:**
|
||||
```bash
|
||||
# Generate initial
|
||||
msg=$(/message-generation complete type:feat scope:auth)
|
||||
|
||||
# User: "too long"
|
||||
# Regenerate with constraint
|
||||
msg=$(/message-generation complete type:feat scope:auth description:"add OAuth" include_body:false)
|
||||
```
|
||||
246
commands/message-generation/generate-subject.md
Normal file
246
commands/message-generation/generate-subject.md
Normal file
@@ -0,0 +1,246 @@
|
||||
---
|
||||
description: Generate conventional commit subject line with type, scope, and description
|
||||
---
|
||||
|
||||
# Operation: Generate Subject Line
|
||||
|
||||
Create a properly formatted subject line following the conventional commits standard: `<type>(<scope>): <description>`
|
||||
|
||||
## Parameters from $ARGUMENTS
|
||||
|
||||
**Required:**
|
||||
- `type:` - Commit type (feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert)
|
||||
- `description:` - Brief description of changes
|
||||
|
||||
**Optional:**
|
||||
- `scope:` - Affected module/component
|
||||
- `max_length:` - Maximum length (default: 50, hard limit: 72)
|
||||
|
||||
**Format:** `subject type:feat scope:auth description:"add OAuth authentication"`
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Parse Parameters
|
||||
|
||||
Extract parameters from $ARGUMENTS:
|
||||
|
||||
```bash
|
||||
# Parse key:value pairs
|
||||
type=$(echo "$ARGUMENTS" | grep -oP 'type:\K[^ ]+')
|
||||
scope=$(echo "$ARGUMENTS" | grep -oP 'scope:\K[^ ]+')
|
||||
description=$(echo "$ARGUMENTS" | grep -oP 'description:"\K[^"]+' || echo "$ARGUMENTS" | grep -oP 'description:\K[^ ]+')
|
||||
max_length=$(echo "$ARGUMENTS" | grep -oP 'max_length:\K[0-9]+' || echo "50")
|
||||
```
|
||||
|
||||
### Step 2: Validate Parameters
|
||||
|
||||
**Check required parameters:**
|
||||
```bash
|
||||
if [ -z "$type" ]; then
|
||||
echo "ERROR: type parameter is required"
|
||||
echo "Usage: subject type:<type> description:\"<desc>\" [scope:<scope>]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$description" ]; then
|
||||
echo "ERROR: description parameter is required"
|
||||
echo "Usage: subject type:<type> description:\"<desc>\" [scope:<scope>]"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Validate type:**
|
||||
```bash
|
||||
valid_types="feat fix docs style refactor perf test build ci chore revert"
|
||||
if ! echo "$valid_types" | grep -qw "$type"; then
|
||||
echo "ERROR: Invalid type '$type'"
|
||||
echo "Valid types: $valid_types"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 3: Invoke Subject Generator Script
|
||||
|
||||
Pass parameters to the utility script for intelligent formatting:
|
||||
|
||||
```bash
|
||||
# Prepare JSON input
|
||||
cat <<EOF | /home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation/.scripts/subject-generator.py
|
||||
{
|
||||
"type": "$type",
|
||||
"scope": "$scope",
|
||||
"description": "$description",
|
||||
"max_length": $max_length
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
The script will:
|
||||
- Format the subject line
|
||||
- Enforce imperative mood
|
||||
- Ensure proper capitalization
|
||||
- Check character limits
|
||||
- Suggest improvements if needed
|
||||
|
||||
### Step 4: Format Output
|
||||
|
||||
Present the generated subject line:
|
||||
|
||||
```
|
||||
SUBJECT LINE GENERATED
|
||||
═══════════════════════════════════════════════
|
||||
|
||||
Subject: <type>(<scope>): <description>
|
||||
Length: XX/50 characters
|
||||
|
||||
VALIDATION:
|
||||
───────────────────────────────────────────────
|
||||
✓ Type is valid
|
||||
✓ Imperative mood used
|
||||
✓ No capitalization after colon
|
||||
✓ No period at end
|
||||
✓ Within character limit
|
||||
|
||||
SUGGESTIONS:
|
||||
───────────────────────────────────────────────
|
||||
- <improvement 1 if applicable>
|
||||
- <improvement 2 if applicable>
|
||||
|
||||
═══════════════════════════════════════════════
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
Return structured output:
|
||||
- Generated subject line
|
||||
- Character count
|
||||
- Validation results
|
||||
- Suggestions for improvement (if any)
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Missing required parameters:**
|
||||
```
|
||||
ERROR: Missing required parameter 'type'
|
||||
Usage: subject type:<type> description:"<desc>" [scope:<scope>]
|
||||
|
||||
Example: subject type:feat description:"add user authentication"
|
||||
```
|
||||
|
||||
**Invalid type:**
|
||||
```
|
||||
ERROR: Invalid type 'feature'
|
||||
Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
||||
```
|
||||
|
||||
**Description too long:**
|
||||
```
|
||||
WARNING: Subject line exceeds 50 characters (XX chars)
|
||||
Current: <type>(<scope>): <very long description>
|
||||
|
||||
Suggestion: Shorten description or move details to body
|
||||
Recommended: <type>(<scope>): <shortened description>
|
||||
```
|
||||
|
||||
**Non-imperative mood:**
|
||||
```
|
||||
WARNING: Use imperative mood
|
||||
Current: "added authentication"
|
||||
Correct: "add authentication"
|
||||
```
|
||||
|
||||
## Subject Line Rules
|
||||
|
||||
**Imperative Mood:**
|
||||
- ✅ "add feature" (correct)
|
||||
- ❌ "added feature" (past tense)
|
||||
- ❌ "adds feature" (present tense)
|
||||
|
||||
**Capitalization:**
|
||||
- ✅ "feat: add login" (lowercase after colon)
|
||||
- ❌ "feat: Add login" (uppercase after colon)
|
||||
|
||||
**Punctuation:**
|
||||
- ✅ "fix: resolve crash" (no period)
|
||||
- ❌ "fix: resolve crash." (period at end)
|
||||
|
||||
**Length:**
|
||||
- Target: 50 characters maximum
|
||||
- Hard limit: 72 characters
|
||||
- Include type, scope, colon, and description
|
||||
|
||||
## Integration with Agent
|
||||
|
||||
The commit-assistant agent uses this operation to:
|
||||
1. Generate subject lines during commit message creation
|
||||
2. Validate subject line format
|
||||
3. Suggest improvements for clarity
|
||||
4. Ensure conventional commits compliance
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Basic Subject
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation subject type:feat description:"add user authentication"
|
||||
|
||||
# Output
|
||||
Subject: feat: add user authentication
|
||||
Length: 30/50 characters
|
||||
Status: ✓ Valid
|
||||
```
|
||||
|
||||
### Example 2: Subject with Scope
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation subject type:fix scope:api description:"resolve null pointer"
|
||||
|
||||
# Output
|
||||
Subject: fix(api): resolve null pointer
|
||||
Length: 30/50 characters
|
||||
Status: ✓ Valid
|
||||
```
|
||||
|
||||
### Example 3: Long Description Warning
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation subject type:feat description:"add comprehensive OAuth2 authentication with multiple providers"
|
||||
|
||||
# Output
|
||||
WARNING: Subject exceeds 50 characters (69 chars)
|
||||
Suggested: feat: add OAuth2 authentication
|
||||
Move details to body: "with multiple providers"
|
||||
```
|
||||
|
||||
### Example 4: Mood Correction
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation subject type:fix description:"fixed login bug"
|
||||
|
||||
# Output
|
||||
Subject: fix: fix login bug
|
||||
WARNING: Use imperative mood
|
||||
Suggested: fix: resolve login bug
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
**Be Specific:**
|
||||
- ✅ "add OAuth authentication"
|
||||
- ❌ "update code"
|
||||
|
||||
**Focus on What:**
|
||||
- ✅ "fix crash on login"
|
||||
- ❌ "fix issue with the login button that crashes when clicked"
|
||||
|
||||
**Omit Implementation:**
|
||||
- ✅ "improve query performance"
|
||||
- ❌ "add database index to users table"
|
||||
|
||||
**Use Conventional Types:**
|
||||
- ✅ "feat: add feature"
|
||||
- ❌ "feature: add feature"
|
||||
93
commands/message-generation/skill.md
Normal file
93
commands/message-generation/skill.md
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
description: Generate conventional commit messages following best practices
|
||||
---
|
||||
|
||||
# Message Generation Skill - Semantic Commit Message Generation
|
||||
|
||||
Generate well-formatted commit messages that follow the Conventional Commits standard with proper type, scope, subject, body, and footer.
|
||||
|
||||
## Operations
|
||||
|
||||
- **subject** - Generate subject line: `<type>(<scope>): <description>`
|
||||
- **body** - Compose commit body with bullet points
|
||||
- **footer** - Add footer with breaking changes and issue references
|
||||
- **validate** - Check conventional commits compliance
|
||||
- **complete** - Generate full commit message (subject + body + footer)
|
||||
|
||||
## Router Logic
|
||||
|
||||
Parse $ARGUMENTS to determine which operation to perform:
|
||||
|
||||
1. Extract operation from first word of $ARGUMENTS
|
||||
2. Extract remaining arguments as operation parameters
|
||||
3. Route to appropriate instruction file:
|
||||
- "subject" → Read `/home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation/generate-subject.md`
|
||||
- "body" → Read `/home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation/write-body.md`
|
||||
- "footer" → Read `/home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation/add-footer.md`
|
||||
- "validate" → Read `/home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation/validate-message.md`
|
||||
- "complete" → Read `/home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation/complete-message.md`
|
||||
|
||||
4. Execute instructions with parameters
|
||||
5. Return formatted commit message or validation results
|
||||
|
||||
## Error Handling
|
||||
|
||||
- If operation is unknown, list available operations
|
||||
- If required parameters are missing, show required format
|
||||
- If message validation fails, provide specific corrections
|
||||
- If character limits exceeded, suggest rewording
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```bash
|
||||
# Generate subject line only
|
||||
/message-generation subject type:feat scope:auth description:"add OAuth authentication"
|
||||
|
||||
# Write commit body
|
||||
/message-generation body changes:"Implement OAuth2 flow,Add provider support,Include middleware"
|
||||
|
||||
# Add footer with issue references
|
||||
/message-generation footer breaking:"authentication API changed" closes:123
|
||||
|
||||
# Validate existing message
|
||||
/message-generation validate message:"feat(auth): add OAuth"
|
||||
|
||||
# Generate complete commit message
|
||||
/message-generation complete type:feat scope:auth files:"src/auth/oauth.js,src/auth/providers.js"
|
||||
```
|
||||
|
||||
## Conventional Commits Format
|
||||
|
||||
**Message Structure:**
|
||||
```
|
||||
<type>(<scope>): <subject> ← Max 50 chars, imperative mood
|
||||
|
||||
<body> ← Optional, wrap at 72 chars
|
||||
- Bullet point describing change 1
|
||||
- Bullet point describing change 2
|
||||
|
||||
<footer> ← Optional
|
||||
BREAKING CHANGE: description
|
||||
Closes #123, #456
|
||||
```
|
||||
|
||||
**Valid Types (priority order):**
|
||||
1. feat - New feature
|
||||
2. fix - Bug fix
|
||||
3. docs - Documentation only
|
||||
4. style - Formatting (no code change)
|
||||
5. refactor - Code restructuring
|
||||
6. perf - Performance improvement
|
||||
7. test - Test additions/updates
|
||||
8. build - Build system or dependencies
|
||||
9. ci - CI/CD configuration
|
||||
10. chore - Other maintenance
|
||||
11. revert - Revert previous commit
|
||||
|
||||
---
|
||||
|
||||
**Base directory:** `/home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation`
|
||||
|
||||
**Current request:** $ARGUMENTS
|
||||
|
||||
Parse operation and route to appropriate instruction file.
|
||||
494
commands/message-generation/validate-message.md
Normal file
494
commands/message-generation/validate-message.md
Normal file
@@ -0,0 +1,494 @@
|
||||
---
|
||||
description: Validate commit message against conventional commits standard
|
||||
---
|
||||
|
||||
# Operation: Validate Message
|
||||
|
||||
Validate a commit message against the conventional commits standard, checking format, style, and best practices.
|
||||
|
||||
## Parameters from $ARGUMENTS
|
||||
|
||||
**Required:**
|
||||
- `message:` - Full commit message to validate (use quotes for multi-line)
|
||||
|
||||
**Optional:**
|
||||
- `strict:` - Enable strict mode (true|false, default: false)
|
||||
- `max_subject:` - Maximum subject length (default: 50)
|
||||
- `max_line:` - Maximum body line length (default: 72)
|
||||
|
||||
**Format:** `validate message:"feat: add feature"` or `validate message:"$(cat commit.txt)"`
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Parse Parameters
|
||||
|
||||
Extract message from $ARGUMENTS:
|
||||
|
||||
```bash
|
||||
# Parse message (supports quoted multi-line)
|
||||
message=$(echo "$ARGUMENTS" | sed -n 's/.*message:"\(.*\)".*/\1/p')
|
||||
if [ -z "$message" ]; then
|
||||
message=$(echo "$ARGUMENTS" | sed 's/^validate //')
|
||||
fi
|
||||
|
||||
strict=$(echo "$ARGUMENTS" | grep -oP 'strict:\K(true|false)' || echo "false")
|
||||
max_subject=$(echo "$ARGUMENTS" | grep -oP 'max_subject:\K[0-9]+' || echo "50")
|
||||
max_line=$(echo "$ARGUMENTS" | grep -oP 'max_line:\K[0-9]+' || echo "72")
|
||||
```
|
||||
|
||||
### Step 2: Validate Parameters
|
||||
|
||||
**Check message provided:**
|
||||
```bash
|
||||
if [ -z "$message" ]; then
|
||||
echo "ERROR: message parameter is required"
|
||||
echo "Usage: validate message:\"<commit message>\""
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 3: Invoke Message Validator Script
|
||||
|
||||
Pass message to validation script:
|
||||
|
||||
```bash
|
||||
# Export variables
|
||||
export MESSAGE="$message"
|
||||
export STRICT_MODE="$strict"
|
||||
export MAX_SUBJECT="$max_subject"
|
||||
export MAX_LINE="$max_line"
|
||||
|
||||
# Run validator
|
||||
/home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation/.scripts/message-validator.sh
|
||||
```
|
||||
|
||||
The script performs comprehensive validation:
|
||||
- Format compliance
|
||||
- Type validation
|
||||
- Subject line rules
|
||||
- Body formatting
|
||||
- Footer format
|
||||
- Character limits
|
||||
- Mood and style
|
||||
- Best practices
|
||||
|
||||
### Step 4: Format Validation Report
|
||||
|
||||
Present detailed validation results:
|
||||
|
||||
```
|
||||
COMMIT MESSAGE VALIDATION
|
||||
═══════════════════════════════════════════════
|
||||
|
||||
MESSAGE:
|
||||
───────────────────────────────────────────────
|
||||
<commit message displayed>
|
||||
|
||||
FORMAT VALIDATION:
|
||||
───────────────────────────────────────────────
|
||||
✓ Conventional commits format
|
||||
✓ Valid commit type
|
||||
✓ Scope format correct (if present)
|
||||
✓ Blank line before body (if present)
|
||||
✓ Blank line before footer (if present)
|
||||
|
||||
SUBJECT LINE:
|
||||
───────────────────────────────────────────────
|
||||
✓ Length: XX/50 characters
|
||||
✓ Imperative mood
|
||||
✓ No capitalization after colon
|
||||
✓ No period at end
|
||||
✓ Clear and descriptive
|
||||
|
||||
BODY (if present):
|
||||
───────────────────────────────────────────────
|
||||
✓ Blank line after subject
|
||||
✓ Lines wrapped at 72 characters
|
||||
✓ Bullet points formatted correctly
|
||||
✓ Explains what and why
|
||||
|
||||
FOOTER (if present):
|
||||
───────────────────────────────────────────────
|
||||
✓ Blank line before footer
|
||||
✓ BREAKING CHANGE format correct
|
||||
✓ Issue references valid
|
||||
✓ Metadata formatted properly
|
||||
|
||||
OVERALL:
|
||||
───────────────────────────────────────────────
|
||||
Status: ✓ VALID / ✗ INVALID
|
||||
Score: XX/100
|
||||
|
||||
WARNINGS (if any):
|
||||
───────────────────────────────────────────────
|
||||
- Subject line is long (consider shortening)
|
||||
- Body line exceeds 72 characters
|
||||
|
||||
ERRORS (if any):
|
||||
───────────────────────────────────────────────
|
||||
- Invalid commit type
|
||||
- Missing imperative mood
|
||||
|
||||
SUGGESTIONS:
|
||||
───────────────────────────────────────────────
|
||||
- <improvement 1>
|
||||
- <improvement 2>
|
||||
|
||||
═══════════════════════════════════════════════
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
Return structured validation report with:
|
||||
- Format compliance check
|
||||
- Subject line validation
|
||||
- Body validation (if present)
|
||||
- Footer validation (if present)
|
||||
- Overall score
|
||||
- Warnings (non-critical issues)
|
||||
- Errors (critical issues)
|
||||
- Suggestions for improvement
|
||||
|
||||
## Error Handling
|
||||
|
||||
**No message provided:**
|
||||
```
|
||||
ERROR: message parameter is required
|
||||
Usage: validate message:"<commit message>"
|
||||
|
||||
Example: validate message:"feat: add authentication"
|
||||
```
|
||||
|
||||
**Empty message:**
|
||||
```
|
||||
ERROR: Commit message is empty
|
||||
Provide a commit message to validate
|
||||
```
|
||||
|
||||
**Completely invalid format:**
|
||||
```
|
||||
VALIDATION FAILED
|
||||
Format does not match conventional commits standard
|
||||
|
||||
Expected: <type>(<scope>): <subject>
|
||||
Received: <message>
|
||||
|
||||
See: https://www.conventionalcommits.org/
|
||||
```
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### Subject Line Validation
|
||||
|
||||
**Format:** `<type>(<scope>): <subject>`
|
||||
|
||||
**Type validation:**
|
||||
- ✓ Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
||||
- ✗ Invalid types: feature, bugfix, update, change
|
||||
|
||||
**Scope validation:**
|
||||
- ✓ Lowercase alphanumeric
|
||||
- ✓ Hyphen allowed
|
||||
- ✗ Spaces not allowed
|
||||
- ✗ Special characters not allowed
|
||||
|
||||
**Subject validation:**
|
||||
- ✓ Imperative mood (add, fix, update)
|
||||
- ✗ Past tense (added, fixed)
|
||||
- ✗ Present tense (adds, fixes)
|
||||
- ✓ Lowercase after colon
|
||||
- ✗ Uppercase after colon
|
||||
- ✓ No period at end
|
||||
- ✗ Period at end
|
||||
- ✓ Length ≤ 50 chars (warning if > 50, error if > 72)
|
||||
|
||||
### Body Validation
|
||||
|
||||
**Structure:**
|
||||
- ✓ Blank line after subject
|
||||
- ✓ Lines wrapped at 72 characters
|
||||
- ✓ Bullet points start with `-` or `*`
|
||||
- ✓ Proper paragraph spacing
|
||||
|
||||
**Content:**
|
||||
- ✓ Explains what and why
|
||||
- ✓ Imperative mood
|
||||
- ✗ Implementation details
|
||||
- ✗ Overly verbose
|
||||
|
||||
### Footer Validation
|
||||
|
||||
**Format:**
|
||||
- ✓ Blank line before footer
|
||||
- ✓ `BREAKING CHANGE:` (uppercase, singular)
|
||||
- ✓ Issue references: `Closes #123`
|
||||
- ✓ Metadata format: `Key: value`
|
||||
|
||||
**Issue references:**
|
||||
- ✓ `Closes #123`
|
||||
- ✓ `Fixes #42`
|
||||
- ✓ `Refs #100`
|
||||
- ✗ `Closes 123` (missing #)
|
||||
- ✗ `closes #123` (lowercase)
|
||||
|
||||
## Integration with Agent
|
||||
|
||||
The commit-assistant agent uses this operation to:
|
||||
1. Validate messages before commit
|
||||
2. Check user-provided messages
|
||||
3. Verify generated messages
|
||||
4. Provide improvement suggestions
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Valid Message
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation validate message:"feat(auth): add OAuth authentication"
|
||||
|
||||
# Output
|
||||
✓ VALID
|
||||
Subject: feat(auth): add OAuth authentication (42/50 chars)
|
||||
All checks passed
|
||||
```
|
||||
|
||||
### Example 2: Message with Warnings
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation validate message:"feat: add a comprehensive OAuth2 authentication system"
|
||||
|
||||
# Output
|
||||
⚠ VALID WITH WARNINGS
|
||||
Subject exceeds 50 characters (57 chars)
|
||||
|
||||
Suggestion: Shorten subject or move details to body
|
||||
Example: "feat: add OAuth2 authentication"
|
||||
```
|
||||
|
||||
### Example 3: Invalid Type
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation validate message:"feature: add login"
|
||||
|
||||
# Output
|
||||
✗ INVALID
|
||||
Invalid commit type: 'feature'
|
||||
|
||||
Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
||||
Suggested: feat: add login
|
||||
```
|
||||
|
||||
### Example 4: Wrong Mood
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation validate message:"fix: fixed login bug"
|
||||
|
||||
# Output
|
||||
⚠ VALID WITH WARNINGS
|
||||
Use imperative mood in subject
|
||||
|
||||
Current: "fixed login bug"
|
||||
Correct: "fix login bug"
|
||||
```
|
||||
|
||||
### Example 5: Multi-line Message
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation validate message:"feat(auth): add OAuth
|
||||
|
||||
Implement OAuth2 authentication flow with Google and GitHub providers.
|
||||
|
||||
BREAKING CHANGE: authentication endpoint changed
|
||||
Closes #123"
|
||||
|
||||
# Output
|
||||
✓ VALID
|
||||
Subject: ✓ (28/50 chars)
|
||||
Body: ✓ (proper formatting)
|
||||
Footer: ✓ (BREAKING CHANGE and issue reference)
|
||||
Score: 100/100
|
||||
```
|
||||
|
||||
### Example 6: Invalid Footer
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation validate message:"feat: add feature
|
||||
|
||||
Breaking change: API changed"
|
||||
|
||||
# Output
|
||||
✗ INVALID - Footer format incorrect
|
||||
|
||||
Current: "Breaking change: API changed"
|
||||
Correct: "BREAKING CHANGE: API changed"
|
||||
|
||||
Footer tokens must be uppercase.
|
||||
```
|
||||
|
||||
## Validation Scoring
|
||||
|
||||
**Score breakdown (100 points total):**
|
||||
|
||||
**Subject (40 points):**
|
||||
- Valid type (10 pts)
|
||||
- Proper format (10 pts)
|
||||
- Imperative mood (10 pts)
|
||||
- Length ≤ 50 chars (10 pts)
|
||||
|
||||
**Body (30 points, if present):**
|
||||
- Blank line after subject (10 pts)
|
||||
- Proper wrapping (10 pts)
|
||||
- Clear explanation (10 pts)
|
||||
|
||||
**Footer (15 points, if present):**
|
||||
- Blank line before footer (5 pts)
|
||||
- Proper format (10 pts)
|
||||
|
||||
**Style (15 points):**
|
||||
- Consistent style (5 pts)
|
||||
- No typos (5 pts)
|
||||
- Professional tone (5 pts)
|
||||
|
||||
**Scoring thresholds:**
|
||||
- 90-100: Excellent
|
||||
- 75-89: Good
|
||||
- 60-74: Acceptable
|
||||
- Below 60: Needs improvement
|
||||
|
||||
## Strict Mode
|
||||
|
||||
**Normal mode (default):**
|
||||
- Warnings for non-critical issues
|
||||
- Accepts messages with minor issues
|
||||
- Suggests improvements
|
||||
|
||||
**Strict mode (strict:true):**
|
||||
- Errors for any deviation
|
||||
- Rejects messages with warnings
|
||||
- Enforces all best practices
|
||||
- Useful for pre-commit hooks
|
||||
|
||||
**Example difference:**
|
||||
|
||||
Normal mode:
|
||||
```
|
||||
⚠ Subject is 55 characters (warning)
|
||||
Status: VALID
|
||||
```
|
||||
|
||||
Strict mode:
|
||||
```
|
||||
✗ Subject exceeds 50 characters (error)
|
||||
Status: INVALID
|
||||
```
|
||||
|
||||
## Best Practices Validation
|
||||
|
||||
**Checks performed:**
|
||||
|
||||
**Subject best practices:**
|
||||
- Be specific (not "update code")
|
||||
- Focus on what (not how)
|
||||
- Avoid filler words
|
||||
- Use consistent terminology
|
||||
|
||||
**Body best practices:**
|
||||
- Explain motivation
|
||||
- Describe high-level approach
|
||||
- Mention side effects
|
||||
- Link to related work
|
||||
|
||||
**Footer best practices:**
|
||||
- Clear breaking change description
|
||||
- Accurate issue references
|
||||
- Proper DCO/sign-off
|
||||
- Relevant metadata only
|
||||
|
||||
## Common Validation Failures
|
||||
|
||||
**Invalid type:**
|
||||
```
|
||||
✗ Type "feature" is not valid
|
||||
→ Use "feat" instead
|
||||
```
|
||||
|
||||
**Past tense:**
|
||||
```
|
||||
✗ Use imperative: "add" not "added"
|
||||
→ Subject should use present tense
|
||||
```
|
||||
|
||||
**Capitalization:**
|
||||
```
|
||||
✗ Don't capitalize after colon
|
||||
→ "feat: Add feature" should be "feat: add feature"
|
||||
```
|
||||
|
||||
**Period at end:**
|
||||
```
|
||||
✗ No period at end of subject
|
||||
→ "feat: add feature." should be "feat: add feature"
|
||||
```
|
||||
|
||||
**No blank line:**
|
||||
```
|
||||
✗ Blank line required between subject and body
|
||||
→ Add empty line after subject
|
||||
```
|
||||
|
||||
**Line too long:**
|
||||
```
|
||||
✗ Body line exceeds 72 characters
|
||||
→ Wrap text at 72 characters
|
||||
```
|
||||
|
||||
**Invalid footer:**
|
||||
```
|
||||
✗ Footer format incorrect
|
||||
→ Use "BREAKING CHANGE:" not "Breaking change:"
|
||||
```
|
||||
|
||||
## Pre-commit Integration
|
||||
|
||||
This validation can be used in pre-commit hooks:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# .git/hooks/commit-msg
|
||||
|
||||
MESSAGE=$(cat "$1")
|
||||
|
||||
# Validate message
|
||||
result=$(/message-generation validate message:"$MESSAGE" strict:true)
|
||||
|
||||
if echo "$result" | grep -q "✗ INVALID"; then
|
||||
echo "$result"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## Validation vs Generation
|
||||
|
||||
**Validation:**
|
||||
- Checks existing messages
|
||||
- Identifies problems
|
||||
- Suggests corrections
|
||||
- Pass/fail result
|
||||
|
||||
**Generation:**
|
||||
- Creates new messages
|
||||
- Follows rules automatically
|
||||
- Optimized output
|
||||
- Always valid (when properly configured)
|
||||
|
||||
**Workflow:**
|
||||
```
|
||||
User writes message → Validate → If invalid → Suggest corrections
|
||||
Agent generates message → Validate → Should always pass
|
||||
```
|
||||
309
commands/message-generation/write-body.md
Normal file
309
commands/message-generation/write-body.md
Normal file
@@ -0,0 +1,309 @@
|
||||
---
|
||||
description: Compose commit message body with bullet points and proper formatting
|
||||
---
|
||||
|
||||
# Operation: Write Commit Body
|
||||
|
||||
Compose a well-formatted commit message body with bullet points, proper wrapping, and clear explanation of changes.
|
||||
|
||||
## Parameters from $ARGUMENTS
|
||||
|
||||
**Required:**
|
||||
- `changes:` - Comma-separated list of changes or file paths
|
||||
|
||||
**Optional:**
|
||||
- `wrap:` - Line wrap length (default: 72)
|
||||
- `format:` - Output format (bullets|paragraphs, default: bullets)
|
||||
- `why:` - Additional context about why changes were made
|
||||
|
||||
**Format:** `body changes:"Change 1,Change 2,Change 3" [why:"explanation"]`
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Parse Parameters
|
||||
|
||||
Extract parameters from $ARGUMENTS:
|
||||
|
||||
```bash
|
||||
# Parse changes (supports both quoted and comma-separated)
|
||||
changes=$(echo "$ARGUMENTS" | grep -oP 'changes:"\K[^"]+' || echo "$ARGUMENTS" | grep -oP 'changes:\K[^,]+')
|
||||
wrap=$(echo "$ARGUMENTS" | grep -oP 'wrap:\K[0-9]+' || echo "72")
|
||||
format=$(echo "$ARGUMENTS" | grep -oP 'format:\K[^ ]+' || echo "bullets")
|
||||
why=$(echo "$ARGUMENTS" | grep -oP 'why:"\K[^"]+')
|
||||
```
|
||||
|
||||
### Step 2: Validate Parameters
|
||||
|
||||
**Check required parameters:**
|
||||
```bash
|
||||
if [ -z "$changes" ]; then
|
||||
echo "ERROR: changes parameter is required"
|
||||
echo "Usage: body changes:\"<change1>,<change2>\" [why:\"<explanation>\"]"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Validate format:**
|
||||
```bash
|
||||
if [ "$format" != "bullets" ] && [ "$format" != "paragraphs" ]; then
|
||||
echo "ERROR: Invalid format '$format'"
|
||||
echo "Valid formats: bullets, paragraphs"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 3: Invoke Body Composer Script
|
||||
|
||||
Pass parameters to the utility script for intelligent formatting:
|
||||
|
||||
```bash
|
||||
# Export variables for script
|
||||
export CHANGES="$changes"
|
||||
export WRAP_LENGTH="$wrap"
|
||||
export FORMAT="$format"
|
||||
export WHY_CONTEXT="$why"
|
||||
|
||||
# Run composer
|
||||
/home/danie/projects/plugins/architect/open-plugins/plugins/git-commit-assistant/commands/message-generation/.scripts/body-composer.sh
|
||||
```
|
||||
|
||||
The script will:
|
||||
- Split changes into individual items
|
||||
- Format as bullet points or paragraphs
|
||||
- Wrap lines at specified length
|
||||
- Add context if provided
|
||||
- Ensure imperative mood
|
||||
|
||||
### Step 4: Format Output
|
||||
|
||||
Present the generated body:
|
||||
|
||||
```
|
||||
COMMIT BODY GENERATED
|
||||
═══════════════════════════════════════════════
|
||||
|
||||
BODY:
|
||||
───────────────────────────────────────────────
|
||||
<blank line>
|
||||
<formatted body content with bullet points>
|
||||
<wrapped at 72 characters>
|
||||
|
||||
VALIDATION:
|
||||
───────────────────────────────────────────────
|
||||
✓ Blank line before body
|
||||
✓ Lines wrapped at 72 characters
|
||||
✓ Bullet points used
|
||||
✓ Imperative mood
|
||||
✓ Proper formatting
|
||||
|
||||
STATISTICS:
|
||||
───────────────────────────────────────────────
|
||||
Lines: X
|
||||
Longest line: XX chars
|
||||
Bullet points: X
|
||||
|
||||
═══════════════════════════════════════════════
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
Return structured output:
|
||||
- Formatted body text
|
||||
- Validation results
|
||||
- Statistics (line count, wrapping)
|
||||
- Suggestions for improvement (if any)
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Missing required parameters:**
|
||||
```
|
||||
ERROR: Missing required parameter 'changes'
|
||||
Usage: body changes:"<change1>,<change2>" [why:"<explanation>"]
|
||||
|
||||
Example: body changes:"Implement OAuth2 flow,Add provider support"
|
||||
```
|
||||
|
||||
**Line too long:**
|
||||
```
|
||||
WARNING: Line exceeds 72 characters (XX chars)
|
||||
Line: "- Very long description that goes on and on..."
|
||||
|
||||
Suggestion: Split into multiple bullet points or wrap text
|
||||
```
|
||||
|
||||
**Non-imperative mood:**
|
||||
```
|
||||
WARNING: Use imperative mood in body
|
||||
Current: "- Added authentication"
|
||||
Correct: "- Add authentication"
|
||||
```
|
||||
|
||||
## Body Formatting Rules
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
<blank line required between subject and body>
|
||||
<body content>
|
||||
- Bullet point 1
|
||||
- Bullet point 2
|
||||
- Bullet point 3
|
||||
```
|
||||
|
||||
**Line Wrapping:**
|
||||
- Target: 72 characters per line
|
||||
- Hard limit: No line should exceed 80 characters
|
||||
- Use hard wraps, not soft wraps
|
||||
|
||||
**Bullet Points:**
|
||||
- Use `-` for bullet points
|
||||
- Consistent indentation
|
||||
- One thought per bullet
|
||||
- Imperative mood
|
||||
|
||||
**Content Focus:**
|
||||
- Explain WHAT and WHY, not HOW
|
||||
- Focus on user-facing changes
|
||||
- Provide context when needed
|
||||
- Avoid implementation details
|
||||
|
||||
## Integration with Agent
|
||||
|
||||
The commit-assistant agent uses this operation to:
|
||||
1. Generate body content from analyzed changes
|
||||
2. Format file lists into readable bullet points
|
||||
3. Add context about why changes were made
|
||||
4. Ensure proper formatting and wrapping
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Basic Body with Changes
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation body changes:"Implement OAuth2 flow,Add Google provider,Add GitHub provider,Include middleware"
|
||||
|
||||
# Output
|
||||
BODY:
|
||||
|
||||
- Implement OAuth2 flow
|
||||
- Add Google provider
|
||||
- Add GitHub provider
|
||||
- Include middleware
|
||||
```
|
||||
|
||||
### Example 2: Body with Context
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation body changes:"Refactor database queries,Add connection pooling" why:"Improve performance under load"
|
||||
|
||||
# Output
|
||||
BODY:
|
||||
|
||||
- Refactor database queries
|
||||
- Add connection pooling
|
||||
|
||||
Improve performance under high load conditions.
|
||||
```
|
||||
|
||||
### Example 3: Body from File List
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation body changes:"src/auth/oauth.js,src/auth/providers/google.js,src/auth/providers/github.js"
|
||||
|
||||
# Output
|
||||
BODY:
|
||||
|
||||
- Add OAuth authentication module
|
||||
- Implement Google provider
|
||||
- Implement GitHub provider
|
||||
- Add provider configuration
|
||||
```
|
||||
|
||||
### Example 4: Paragraph Format
|
||||
|
||||
```bash
|
||||
# Input
|
||||
/message-generation body changes:"Update authentication flow" why:"Previous implementation had security vulnerabilities" format:paragraphs
|
||||
|
||||
# Output
|
||||
BODY:
|
||||
|
||||
Update authentication flow to address security vulnerabilities
|
||||
discovered in the previous implementation. The new approach uses
|
||||
industry-standard OAuth2 protocol with secure token handling.
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
**Be Clear:**
|
||||
- ✅ "Add user authentication with OAuth2"
|
||||
- ❌ "Add stuff"
|
||||
|
||||
**Use Bullet Points:**
|
||||
- ✅ Multiple related changes as bullets
|
||||
- ❌ Long paragraphs of text
|
||||
|
||||
**Focus on What/Why:**
|
||||
- ✅ "Add caching to improve performance"
|
||||
- ❌ "Add Redis instance with 5-minute TTL"
|
||||
|
||||
**Keep It Concise:**
|
||||
- ✅ Brief, clear explanations
|
||||
- ❌ Essay-length descriptions
|
||||
|
||||
**Wrap Properly:**
|
||||
- ✅ "This is a properly wrapped line that doesn't exceed\nthe 72 character limit"
|
||||
- ❌ "This is a very long line that goes on and on and on and exceeds the character limit"
|
||||
|
||||
## When to Include a Body
|
||||
|
||||
**Include body when:**
|
||||
- Multiple files changed
|
||||
- Changes need explanation
|
||||
- Context is important
|
||||
- Implications not obvious
|
||||
|
||||
**Omit body when:**
|
||||
- Change is self-explanatory
|
||||
- Subject line is sufficient
|
||||
- Trivial change
|
||||
- Documentation only
|
||||
|
||||
## Body Templates
|
||||
|
||||
**Feature Addition:**
|
||||
```
|
||||
- Add <feature name>
|
||||
- Implement <capability 1>
|
||||
- Implement <capability 2>
|
||||
- Include <supporting feature>
|
||||
```
|
||||
|
||||
**Bug Fix:**
|
||||
```
|
||||
- Resolve <issue>
|
||||
- Add validation for <edge case>
|
||||
- Update error handling
|
||||
|
||||
Fixes issue where <description of bug>
|
||||
```
|
||||
|
||||
**Refactoring:**
|
||||
```
|
||||
- Extract <component>
|
||||
- Simplify <logic>
|
||||
- Improve <aspect>
|
||||
|
||||
No functional changes, improves code maintainability.
|
||||
```
|
||||
|
||||
**Performance:**
|
||||
```
|
||||
- Optimize <operation>
|
||||
- Add caching for <data>
|
||||
- Reduce <metric>
|
||||
|
||||
Improves performance by <measurement>.
|
||||
```
|
||||
Reference in New Issue
Block a user