Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:20:25 +08:00
commit 0d6226e0d8
69 changed files with 20934 additions and 0 deletions

View 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

View 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()

View 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

View 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()

View 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
```

View 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)
```

View 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"

View 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.

View 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
```

View 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>.
```