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,342 @@
#!/usr/bin/env python3
"""
Commit Planner - Create optimal commit sequence
Purpose: Generate optimal commit sequence from file groups
Version: 1.0.0
Usage: ./commit-planner.py [--input FILE] [--output plan|script]
Returns:
Exit 0: Success
Exit 1: Error
Exit 2: Invalid parameters
Dependencies: git, python3
"""
import sys
import json
import subprocess
from collections import defaultdict, deque
from typing import List, Dict, Set, Tuple, Optional
# Type priority for ordering
TYPE_PRIORITY = {
'feat': 1, # Features enable other changes
'fix': 2, # Fixes should be applied early
'refactor': 3, # Restructuring before additions
'perf': 4, # Performance after stability
'test': 5, # Tests after implementation
'docs': 6, # Documentation last
'style': 7, # Style changes last
'chore': 8, # Housekeeping last
'ci': 9, # CI changes last
'build': 10 # Build changes last
}
class CommitPlanner:
def __init__(self, verbose: bool = False):
self.verbose = verbose
self.commits = []
self.dependencies = defaultdict(set)
def log(self, message: str):
"""Print message if verbose mode enabled"""
if self.verbose:
print(f"[DEBUG] {message}", file=sys.stderr)
def run_git_command(self, args: List[str]) -> str:
"""Execute git command and return output"""
try:
result = subprocess.run(
['git'] + args,
capture_output=True,
text=True,
check=True
)
return result.stdout
except subprocess.CalledProcessError as e:
print(f"Error running git command: {e}", file=sys.stderr)
return ""
def load_suggestions(self, input_file: Optional[str] = None) -> List[Dict]:
"""Load commit suggestions from file or stdin"""
try:
if input_file:
with open(input_file, 'r') as f:
data = json.load(f)
else:
# Read from stdin
data = json.load(sys.stdin)
return data.get('suggestions', [])
except Exception as e:
print(f"Error loading suggestions: {e}", file=sys.stderr)
sys.exit(1)
def detect_dependencies(self, commit1: Dict, commit2: Dict) -> bool:
"""Check if commit2 depends on commit1"""
# Test files depend on implementation files
if commit1['type'] != 'test' and commit2['type'] == 'test':
# Check if test scope matches implementation scope
if commit1.get('scope') == commit2.get('scope'):
return True
# Docs depend on features they document
if commit1['type'] == 'feat' and commit2['type'] == 'docs':
# Check if docs reference the feature scope
if commit1.get('scope') in commit2.get('subject', ''):
return True
# Fixes may depend on features
if commit1['type'] == 'feat' and commit2['type'] == 'fix':
# Check if same scope
if commit1.get('scope') == commit2.get('scope'):
return True
# Check file dependencies (imports)
files1 = set(commit1.get('files', []))
files2 = set(commit2.get('files', []))
# If commit2 files might import from commit1 files
for file2 in files2:
for file1 in files1:
if self.has_import_dependency(file1, file2):
return True
return False
def has_import_dependency(self, source_file: str, target_file: str) -> bool:
"""Check if target_file imports from source_file"""
try:
# Get content of target file
content = self.run_git_command(['show', f':{target_file}'])
# Extract module path from source file
source_module = source_file.replace('/', '.').replace('.py', '').replace('.js', '').replace('.ts', '')
# Check for import statements
if any(imp in content for imp in [f'import {source_module}', f'from {source_module}', f"require('{source_module}"]):
return True
except:
pass
return False
def build_dependency_graph(self, commits: List[Dict]) -> Dict[int, Set[int]]:
"""Build dependency graph between commits"""
graph = defaultdict(set)
for i, commit1 in enumerate(commits):
for j, commit2 in enumerate(commits):
if i != j and self.detect_dependencies(commit1, commit2):
# commit2 depends on commit1
graph[j].add(i)
self.log(f"Dependency: Commit {j+1} depends on Commit {i+1}")
return graph
def topological_sort(self, commits: List[Dict], dependencies: Dict[int, Set[int]]) -> List[int]:
"""Perform topological sort to respect dependencies"""
# Calculate in-degree for each node
in_degree = defaultdict(int)
for node in range(len(commits)):
in_degree[node] = len(dependencies[node])
# Queue of nodes with no dependencies
queue = deque([node for node in range(len(commits)) if in_degree[node] == 0])
result = []
while queue:
# Sort queue by priority (type priority)
queue_list = list(queue)
queue_list.sort(key=lambda x: TYPE_PRIORITY.get(commits[x]['type'], 99))
queue = deque(queue_list)
node = queue.popleft()
result.append(node)
# Update dependencies
for other_node in range(len(commits)):
if node in dependencies[other_node]:
dependencies[other_node].remove(node)
in_degree[other_node] -= 1
if in_degree[other_node] == 0:
queue.append(other_node)
# Check for cycles
if len(result) != len(commits):
print("Error: Circular dependency detected", file=sys.stderr)
sys.exit(1)
return result
def create_sequence(self, commits: List[Dict]) -> List[Dict]:
"""Create optimal commit sequence"""
self.log(f"Planning sequence for {len(commits)} commits")
# Build dependency graph
dependencies = self.build_dependency_graph(commits)
# Topological sort
order = self.topological_sort(commits, dependencies)
# Create ordered sequence
sequence = []
for idx, commit_idx in enumerate(order):
commit = commits[commit_idx].copy()
commit['order'] = idx + 1
commit['commit_id'] = commit_idx + 1
commit['original_id'] = commit_idx
# Determine when can execute
deps = dependencies[commit_idx]
if not deps:
commit['can_execute'] = 'now'
else:
dep_ids = [order.index(d) + 1 for d in deps]
commit['can_execute'] = f"after commit {min(dep_ids)}"
sequence.append(commit)
return sequence
def format_plan(self, sequence: List[Dict]) -> str:
"""Format sequence as readable plan"""
lines = []
lines.append("=" * 60)
lines.append("COMMIT SEQUENCE PLAN")
lines.append("=" * 60)
lines.append("")
lines.append(f"Execution Order: {len(sequence)} commits in sequence")
lines.append("")
for commit in sequence:
lines.append("" * 60)
lines.append(f"COMMIT {commit['order']}: {commit['type']}" +
(f"({commit.get('scope', '')})" if commit.get('scope') else ""))
lines.append(f"Files: {len(commit['files'])}")
lines.append(f"Can execute: {commit['can_execute']}")
lines.append("" * 60)
lines.append("")
# Message
lines.append("Message:")
lines.append(f" {commit['subject']}")
if commit.get('body'):
lines.append("")
for line in commit['body'].split('\n'):
lines.append(f" {line}")
lines.append("")
# Files to stage
lines.append("Files to stage:")
for file in commit['files']:
lines.append(f" git add {file}")
lines.append("")
# Commit command
commit_msg = commit['subject']
if commit.get('body'):
body = commit['body'].replace('"', '\\"')
commit_cmd = f'git commit -m "{commit_msg}" -m "{body}"'
else:
commit_cmd = f'git commit -m "{commit_msg}"'
lines.append("Command:")
lines.append(f" {commit_cmd}")
lines.append("")
lines.append("=" * 60)
lines.append(f"Total commits: {len(sequence)}")
lines.append(f"Total files: {sum(len(c['files']) for c in sequence)}")
lines.append("=" * 60)
return '\n'.join(lines)
def format_script(self, sequence: List[Dict]) -> str:
"""Format sequence as executable bash script"""
lines = [
"#!/bin/bash",
"# Atomic commit sequence",
f"# Generated: {subprocess.run(['date'], capture_output=True, text=True).stdout.strip()}",
f"# Total commits: {len(sequence)}",
"",
"set -e # Exit on error",
"",
'echo "🚀 Starting commit sequence..."',
""
]
for commit in sequence:
lines.append(f"# Commit {commit['order']}: {commit['type']}" +
(f"({commit.get('scope', '')})" if commit.get('scope') else ""))
lines.append('echo ""')
lines.append(f'echo "📝 Commit {commit["order"]}/{len(sequence)}: {commit["type"]}"')
# Stage files
for file in commit['files']:
lines.append(f'git add "{file}"')
# Commit
commit_msg = commit['subject']
if commit.get('body'):
body = commit['body'].replace('"', '\\"').replace('\n', ' ')
lines.append(f'git commit -m "{commit_msg}" -m "{body}"')
else:
lines.append(f'git commit -m "{commit_msg}"')
lines.append(f'echo "✅ Commit {commit["order"]} complete"')
lines.append("")
lines.append('echo ""')
lines.append('echo "🎉 All commits completed successfully!"')
lines.append(f'echo "Total commits: {len(sequence)}"')
lines.append(f'echo "Total files: {sum(len(c["files"]) for c in sequence)}"')
return '\n'.join(lines)
def main():
import argparse
parser = argparse.ArgumentParser(description='Create optimal commit sequence')
parser.add_argument('--input', help='Input JSON file (default: stdin)')
parser.add_argument('--output', choices=['plan', 'script', 'json'], default='plan',
help='Output format')
parser.add_argument('--verbose', action='store_true', help='Verbose output')
args = parser.parse_args()
planner = CommitPlanner(verbose=args.verbose)
# Load suggestions
commits = planner.load_suggestions(args.input)
if not commits:
print("No commit suggestions provided", file=sys.stderr)
sys.exit(1)
# Create sequence
sequence = planner.create_sequence(commits)
# Output
if args.output == 'json':
result = {
'sequence': sequence,
'summary': {
'total_commits': len(sequence),
'total_files': sum(len(c['files']) for c in sequence)
}
}
print(json.dumps(result, indent=2))
elif args.output == 'script':
print(planner.format_script(sequence))
else: # plan
print(planner.format_plan(sequence))
sys.exit(0)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,328 @@
#!/bin/bash
# Dependency Checker - Analyze file dependencies
#
# Purpose: Check for dependencies between files to inform commit ordering
# Version: 1.0.0
# Usage: ./dependency-checker.sh [files...]
# If no files specified, analyzes all changed files
# Returns:
# Exit 0: Success
# Exit 1: Error
# Exit 2: Invalid parameters
#
# Dependencies: git, bash 4.0+, grep
set -euo pipefail
VERBOSE=false
OUTPUT_FORMAT="text"
# Parse options
while [[ $# -gt 0 ]]; do
case "$1" in
--verbose)
VERBOSE=true
shift
;;
--format)
OUTPUT_FORMAT="${2:-text}"
shift 2
;;
*)
break
;;
esac
done
# Logging
log() {
if [[ "$VERBOSE" == "true" ]]; then
echo "[DEBUG] $*" >&2
fi
}
# Get changed files if not specified
FILES=()
if [[ $# -eq 0 ]]; then
log "Getting changed files from git..."
while IFS= read -r file; do
[[ -n "$file" ]] && FILES+=("$file")
done < <(git diff --cached --name-only; git diff --name-only)
else
FILES=("$@")
fi
# Remove duplicates
mapfile -t FILES < <(printf '%s\n' "${FILES[@]}" | sort -u)
if [[ ${#FILES[@]} -eq 0 ]]; then
echo "No files to analyze" >&2
exit 1
fi
log "Analyzing ${#FILES[@]} files for dependencies..."
# Dependency storage
declare -A dependencies
declare -A file_types
# Detect file type
detect_file_type() {
local file="$1"
case "$file" in
*.py)
echo "python"
;;
*.js|*.jsx)
echo "javascript"
;;
*.ts|*.tsx)
echo "typescript"
;;
*.go)
echo "go"
;;
*.java)
echo "java"
;;
*.rb)
echo "ruby"
;;
*.rs)
echo "rust"
;;
*.md|*.txt|*.rst)
echo "docs"
;;
*test*|*spec*)
echo "test"
;;
*)
echo "unknown"
;;
esac
}
# Extract imports from Python file
extract_python_imports() {
local file="$1"
local content
if [[ -f "$file" ]]; then
content=$(cat "$file")
else
# Try to get from git
content=$(git show :"$file" 2>/dev/null || echo "")
fi
# Match: import module, from module import
echo "$content" | grep -E "^(import|from) " | sed -E 's/^(import|from) ([a-zA-Z0-9_.]+).*/\2/' || true
}
# Extract imports from JavaScript/TypeScript
extract_js_imports() {
local file="$1"
local content
if [[ -f "$file" ]]; then
content=$(cat "$file")
else
content=$(git show :"$file" 2>/dev/null || echo "")
fi
# Match: import from, require()
{
echo "$content" | grep -oE "import .* from ['\"]([^'\"]+)['\"]" | sed -E "s/.*from ['\"](.*)['\"].*/\1/" || true
echo "$content" | grep -oE "require\(['\"]([^'\"]+)['\"]\)" | sed -E "s/.*require\(['\"]([^'\"]+)['\"]\).*/\1/" || true
} | sort -u
}
# Extract imports from Go
extract_go_imports() {
local file="$1"
local content
if [[ -f "$file" ]]; then
content=$(cat "$file")
else
content=$(git show :"$file" 2>/dev/null || echo "")
fi
# Match: import "module" or import ( "module" )
echo "$content" | grep -oE 'import +"[^"]+"' | sed -E 's/import +"([^"]+)".*/\1/' || true
echo "$content" | sed -n '/^import (/,/^)/p' | grep -oE '"[^"]+"' | tr -d '"' || true
}
# Convert import path to file path
import_to_file() {
local import_path="$1"
local file_type="$2"
case "$file_type" in
python)
# module.submodule -> module/submodule.py
echo "$import_path" | tr '.' '/' | sed 's|$|.py|'
;;
javascript|typescript)
# Handle relative imports
if [[ "$import_path" == ./* ]] || [[ "$import_path" == ../* ]]; then
echo "$import_path"
else
# node_modules - not a local file
echo ""
fi
;;
go)
# Package imports - check if local
if [[ "$import_path" == github.com/* ]]; then
echo ""
else
echo "$import_path"
fi
;;
*)
echo ""
;;
esac
}
# Find dependencies for each file
log "Extracting imports and dependencies..."
for file in "${FILES[@]}"; do
file_type=$(detect_file_type "$file")
file_types["$file"]="$file_type"
log " $file: type=$file_type"
case "$file_type" in
python)
imports=$(extract_python_imports "$file")
;;
javascript|typescript)
imports=$(extract_js_imports "$file")
;;
go)
imports=$(extract_go_imports "$file")
;;
*)
imports=""
;;
esac
if [[ -n "$imports" ]]; then
log " Imports:"
while IFS= read -r import_path; do
[[ -z "$import_path" ]] && continue
log " - $import_path"
# Convert import to file path
imported_file=$(import_to_file "$import_path" "$file_type")
# Check if imported file is in our file list
if [[ -n "$imported_file" ]]; then
for other_file in "${FILES[@]}"; do
if [[ "$other_file" == *"$imported_file"* ]]; then
# file depends on other_file
if [[ -z "${dependencies[$file]:-}" ]]; then
dependencies["$file"]="$other_file"
else
dependencies["$file"]="${dependencies[$file]},$other_file"
fi
log " Dependency: $file -> $other_file"
fi
done
fi
done <<< "$imports"
fi
done
# Detect test dependencies
log "Detecting test dependencies..."
for file in "${FILES[@]}"; do
if [[ "${file_types[$file]}" == "test" ]]; then
# Test file depends on implementation file
impl_file="${file//test/}"
impl_file="${impl_file//.test/}"
impl_file="${impl_file//.spec/}"
impl_file="${impl_file//tests\//}"
impl_file="${impl_file//spec\//}"
for other_file in "${FILES[@]}"; do
if [[ "$other_file" == *"$impl_file"* ]] && [[ "$other_file" != "$file" ]]; then
if [[ -z "${dependencies[$file]:-}" ]]; then
dependencies["$file"]="$other_file"
else
dependencies["$file"]="${dependencies[$file]},$other_file"
fi
log " Test dependency: $file -> $other_file"
fi
done
fi
done
# Output results
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
echo "{"
echo " \"files\": ["
local first=true
for file in "${FILES[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo ","
fi
echo -n " {\"file\": \"$file\", \"type\": \"${file_types[$file]}\""
if [[ -n "${dependencies[$file]:-}" ]]; then
IFS=',' read -ra deps <<< "${dependencies[$file]}"
echo -n ", \"depends_on\": ["
local first_dep=true
for dep in "${deps[@]}"; do
if [[ "$first_dep" == "true" ]]; then
first_dep=false
else
echo -n ", "
fi
echo -n "\"$dep\""
done
echo -n "]"
fi
echo -n "}"
done
echo ""
echo " ]"
echo "}"
else
echo "=== FILE DEPENDENCIES ==="
echo ""
echo "Files analyzed: ${#FILES[@]}"
echo ""
local has_dependencies=false
for file in "${FILES[@]}"; do
if [[ -n "${dependencies[$file]:-}" ]]; then
has_dependencies=true
echo "File: $file"
echo " Type: ${file_types[$file]}"
echo " Depends on:"
IFS=',' read -ra deps <<< "${dependencies[$file]}"
for dep in "${deps[@]}"; do
echo " - $dep"
done
echo ""
fi
done
if [[ "$has_dependencies" == "false" ]]; then
echo "No dependencies detected."
echo "All files can be committed independently."
else
echo "Recommendation:"
echo " Commit dependencies before dependent files."
echo " Group dependent files in same commit if they form atomic unit."
fi
fi
log "Dependency analysis complete"
exit 0

View File

@@ -0,0 +1,395 @@
#!/bin/bash
# File Grouper - Group changed files by type, scope, or feature
#
# Purpose: Group git changed files using different strategies
# Version: 1.0.0
# Usage: ./file-grouper.sh <strategy> [options]
# strategy: type|scope|feature
# options: --verbose, --format json|text
# Returns:
# Exit 0: Success
# Exit 1: No changes
# Exit 2: Invalid parameters
#
# Dependencies: git, bash 4.0+
set -euo pipefail
# Configuration
STRATEGY="${1:-type}"
VERBOSE=false
FORMAT="text"
# Parse arguments
shift || true
while [[ $# -gt 0 ]]; do
case "$1" in
--verbose)
VERBOSE=true
shift
;;
--format)
FORMAT="${2:-text}"
shift 2
;;
*)
echo "Unknown option: $1" >&2
exit 2
;;
esac
done
# Logging function
log() {
if [[ "$VERBOSE" == "true" ]]; then
echo "[DEBUG] $*" >&2
fi
}
# Get changed files
get_changed_files() {
local files=()
# Staged files
while IFS= read -r file; do
[[ -n "$file" ]] && files+=("$file")
done < <(git diff --cached --name-only)
# Unstaged files
while IFS= read -r file; do
[[ -n "$file" ]] && files+=("$file")
done < <(git diff --name-only)
# Remove duplicates
printf '%s\n' "${files[@]}" | sort -u
}
# Detect commit type from file
detect_type() {
local file="$1"
local diff
# Get diff for analysis
diff=$(git diff --cached "$file" 2>/dev/null || git diff "$file" 2>/dev/null || echo "")
# Documentation files
if [[ "$file" =~ \.(md|txt|rst|adoc)$ ]]; then
echo "docs"
return
fi
# Test files
if [[ "$file" =~ (test|spec|__tests__)/ ]] || [[ "$file" =~ \.(test|spec)\. ]]; then
echo "test"
return
fi
# CI/CD files
if [[ "$file" =~ \.github/|\.gitlab-ci|jenkins|\.circleci ]]; then
echo "ci"
return
fi
# Build files
if [[ "$file" =~ (package\.json|pom\.xml|build\.gradle|Makefile|CMakeLists\.txt)$ ]]; then
echo "build"
return
fi
# Analyze diff content
if [[ -z "$diff" ]]; then
echo "chore"
return
fi
# Check for new functionality
if echo "$diff" | grep -q "^+.*\(function\|class\|def\|const\|let\)"; then
if echo "$diff" | grep -iq "^+.*\(new\|add\|implement\|create\)"; then
echo "feat"
return
fi
fi
# Check for bug fixes
if echo "$diff" | grep -iq "^+.*\(fix\|bug\|error\|issue\|null\|undefined\)"; then
echo "fix"
return
fi
# Check for refactoring
if echo "$diff" | grep -iq "^+.*\(refactor\|rename\|move\|extract\)"; then
echo "refactor"
return
fi
# Check for performance
if echo "$diff" | grep -iq "^+.*\(performance\|optimize\|cache\|memoize\)"; then
echo "perf"
return
fi
# Default to chore
echo "chore"
}
# Extract scope from file path
extract_scope() {
local file="$1"
local scope
# Remove leading ./
file="${file#./}"
# Split path
IFS='/' read -ra parts <<< "$file"
# Skip common prefixes
for part in "${parts[@]}"; do
case "$part" in
src|lib|app|packages|tests|test|.)
continue
;;
*)
# Remove file extension
scope="${part%%.*}"
echo "$scope"
return
;;
esac
done
echo "root"
}
# Group by type
group_by_type() {
log "Grouping by type..."
declare -A groups
local files
mapfile -t files < <(get_changed_files)
if [[ ${#files[@]} -eq 0 ]]; then
echo "No changes detected" >&2
exit 1
fi
# Group files by type
for file in "${files[@]}"; do
local type
type=$(detect_type "$file")
log " $file -> type:$type"
if [[ -z "${groups[$type]:-}" ]]; then
groups[$type]="$file"
else
groups[$type]="${groups[$type]},$file"
fi
done
# Output results
if [[ "$FORMAT" == "json" ]]; then
echo "{"
echo " \"strategy\": \"type\","
echo " \"groups\": {"
local first=true
for type in "${!groups[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo ","
fi
IFS=',' read -ra file_list <<< "${groups[$type]}"
echo -n " \"$type\": ["
local first_file=true
for f in "${file_list[@]}"; do
if [[ "$first_file" == "true" ]]; then
first_file=false
else
echo -n ", "
fi
echo -n "\"$f\""
done
echo -n "]"
done
echo ""
echo " }"
echo "}"
else
echo "=== FILE GROUPS (strategy: type) ==="
echo ""
local group_num=1
for type in "${!groups[@]}"; do
IFS=',' read -ra file_list <<< "${groups[$type]}"
echo "Group $group_num: $type (${#file_list[@]} files)"
for file in "${file_list[@]}"; do
echo " $file"
done
echo ""
((group_num++))
done
fi
}
# Group by scope
group_by_scope() {
log "Grouping by scope..."
declare -A groups
local files
mapfile -t files < <(get_changed_files)
if [[ ${#files[@]} -eq 0 ]]; then
echo "No changes detected" >&2
exit 1
fi
# Group files by scope
for file in "${files[@]}"; do
local scope
scope=$(extract_scope "$file")
log " $file -> scope:$scope"
if [[ -z "${groups[$scope]:-}" ]]; then
groups[$scope]="$file"
else
groups[$scope]="${groups[$scope]},$file"
fi
done
# Output results
if [[ "$FORMAT" == "json" ]]; then
echo "{"
echo " \"strategy\": \"scope\","
echo " \"groups\": {"
local first=true
for scope in "${!groups[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo ","
fi
IFS=',' read -ra file_list <<< "${groups[$scope]}"
echo -n " \"$scope\": ["
local first_file=true
for f in "${file_list[@]}"; do
if [[ "$first_file" == "true" ]]; then
first_file=false
else
echo -n ", "
fi
echo -n "\"$f\""
done
echo -n "]"
done
echo ""
echo " }"
echo "}"
else
echo "=== FILE GROUPS (strategy: scope) ==="
echo ""
local group_num=1
for scope in "${!groups[@]}"; do
IFS=',' read -ra file_list <<< "${groups[$scope]}"
echo "Group $group_num: $scope (${#file_list[@]} files)"
for file in "${file_list[@]}"; do
echo " $file"
done
echo ""
((group_num++))
done
fi
}
# Group by feature (simplified - uses type and scope combination)
group_by_feature() {
log "Grouping by feature..."
declare -A groups
local files
mapfile -t files < <(get_changed_files)
if [[ ${#files[@]} -eq 0 ]]; then
echo "No changes detected" >&2
exit 1
fi
# Group files by type+scope combination
for file in "${files[@]}"; do
local type scope feature
type=$(detect_type "$file")
scope=$(extract_scope "$file")
feature="${type}_${scope}"
log " $file -> feature:$feature"
if [[ -z "${groups[$feature]:-}" ]]; then
groups[$feature]="$file"
else
groups[$feature]="${groups[$feature]},$file"
fi
done
# Output results
if [[ "$FORMAT" == "json" ]]; then
echo "{"
echo " \"strategy\": \"feature\","
echo " \"groups\": {"
local first=true
for feature in "${!groups[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo ","
fi
IFS=',' read -ra file_list <<< "${groups[$feature]}"
echo -n " \"$feature\": ["
local first_file=true
for f in "${file_list[@]}"; do
if [[ "$first_file" == "true" ]]; then
first_file=false
else
echo -n ", "
fi
echo -n "\"$f\""
done
echo -n "]"
done
echo ""
echo " }"
echo "}"
else
echo "=== FILE GROUPS (strategy: feature) ==="
echo ""
local group_num=1
for feature in "${!groups[@]}"; do
IFS=',' read -ra file_list <<< "${groups[$feature]}"
IFS='_' read -ra feature_parts <<< "$feature"
local type="${feature_parts[0]}"
local scope="${feature_parts[1]}"
echo "Group $group_num: $type($scope) (${#file_list[@]} files)"
for file in "${file_list[@]}"; do
echo " $file"
done
echo ""
((group_num++))
done
fi
}
# Main execution
case "$STRATEGY" in
type)
group_by_type
;;
scope)
group_by_scope
;;
feature)
group_by_feature
;;
*)
echo "Invalid strategy: $STRATEGY" >&2
echo "Valid strategies: type, scope, feature" >&2
exit 2
;;
esac

View File

@@ -0,0 +1,283 @@
#!/usr/bin/env python3
"""
Split Analyzer - Determine if changes should be split
Purpose: Analyze git changes to determine if they should be split into multiple commits
Version: 1.0.0
Usage: ./split-analyzer.py [--verbose] [--threshold N]
Returns:
Exit 0: Should split
Exit 1: Already atomic
Exit 2: Error occurred
Dependencies: git, python3
"""
import sys
import subprocess
import re
import json
from collections import defaultdict
from typing import List, Dict, Tuple, Optional
# Conventional commit types
COMMIT_TYPES = ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'perf', 'ci', 'build']
class SplitAnalyzer:
def __init__(self, threshold: int = 10, verbose: bool = False):
self.threshold = threshold
self.verbose = verbose
self.files = []
self.types = defaultdict(list)
self.scopes = defaultdict(list)
self.concerns = []
def log(self, message: str):
"""Print message if verbose mode enabled"""
if self.verbose:
print(f"[DEBUG] {message}", file=sys.stderr)
def run_git_command(self, args: List[str]) -> str:
"""Execute git command and return output"""
try:
result = subprocess.run(
['git'] + args,
capture_output=True,
text=True,
check=True
)
return result.stdout
except subprocess.CalledProcessError as e:
print(f"Error running git command: {e}", file=sys.stderr)
sys.exit(2)
def get_changed_files(self) -> List[str]:
"""Get list of changed files (staged and unstaged)"""
# Staged files
staged = self.run_git_command(['diff', '--cached', '--name-only'])
# Unstaged files
unstaged = self.run_git_command(['diff', '--name-only'])
files = set()
files.update(filter(None, staged.split('\n')))
files.update(filter(None, unstaged.split('\n')))
return list(files)
def get_file_diff(self, file_path: str) -> str:
"""Get diff for a specific file"""
try:
# Try staged first
diff = self.run_git_command(['diff', '--cached', file_path])
if not diff:
# Try unstaged
diff = self.run_git_command(['diff', file_path])
return diff
except:
return ""
def detect_type_from_diff(self, file_path: str, diff: str) -> str:
"""Detect commit type from file path and diff content"""
# Documentation files
if any(file_path.endswith(ext) for ext in ['.md', '.txt', '.rst', '.adoc']):
return 'docs'
# Test files
if any(pattern in file_path for pattern in ['test/', 'tests/', 'spec/', '__tests__', '.test.', '.spec.']):
return 'test'
# CI/CD files
if any(pattern in file_path for pattern in ['.github/', '.gitlab-ci', 'jenkins', '.circleci']):
return 'ci'
# Build files
if any(file_path.endswith(ext) for ext in ['package.json', 'pom.xml', 'build.gradle', 'Makefile', 'CMakeLists.txt']):
return 'build'
# Analyze diff content
if not diff:
return 'chore'
# Look for new functionality
added_lines = [line for line in diff.split('\n') if line.startswith('+') and not line.startswith('+++')]
# Check for function/class additions (new features)
if any(keyword in ' '.join(added_lines) for keyword in ['function ', 'class ', 'def ', 'const ', 'let ', 'var ']):
if any(keyword in ' '.join(added_lines) for keyword in ['new ', 'add', 'implement', 'create']):
return 'feat'
# Check for bug fix patterns
if any(keyword in ' '.join(added_lines).lower() for keyword in ['fix', 'bug', 'error', 'issue', 'null', 'undefined']):
return 'fix'
# Check for refactoring
if any(keyword in ' '.join(added_lines).lower() for keyword in ['refactor', 'rename', 'move', 'extract']):
return 'refactor'
# Check for performance
if any(keyword in ' '.join(added_lines).lower() for keyword in ['performance', 'optimize', 'cache', 'memoize']):
return 'perf'
# Check for style changes (formatting only)
removed_lines = [line for line in diff.split('\n') if line.startswith('-') and not line.startswith('---')]
if len(added_lines) == len(removed_lines):
# Similar number of additions and deletions might indicate formatting
return 'style'
# Default to feat for new code, chore for modifications
if len(added_lines) > len(removed_lines) * 2:
return 'feat'
return 'chore'
def extract_scope_from_path(self, file_path: str) -> str:
"""Extract scope from file path"""
parts = file_path.split('/')
# Skip common prefixes
skip_prefixes = ['src', 'lib', 'app', 'packages', 'tests', 'test']
for part in parts:
if part not in skip_prefixes and part != '.' and part != '..':
# Remove file extension
scope = part.split('.')[0]
return scope
return 'root'
def detect_mixed_concerns(self) -> List[str]:
"""Detect mixed concerns in changes"""
concerns = []
# Check for feature + unrelated changes
has_feature = 'feat' in self.types
has_refactor = 'refactor' in self.types
has_style = 'style' in self.types
if has_feature and has_refactor:
concerns.append("Feature implementation mixed with refactoring")
if has_feature and has_style:
concerns.append("Feature implementation mixed with style changes")
# Check for test + implementation in separate modules
if 'test' in self.types:
test_scopes = set(self.scopes[scope] for scope in self.scopes if 'test' in scope)
impl_scopes = set(self.scopes[scope] for scope in self.scopes if 'test' not in scope)
if test_scopes != impl_scopes and len(impl_scopes) > 1:
concerns.append("Tests for multiple unrelated implementations")
return concerns
def analyze(self) -> Tuple[bool, str, Dict]:
"""Analyze changes and determine if should split"""
# Get changed files
self.files = self.get_changed_files()
self.log(f"Found {len(self.files)} changed files")
if not self.files:
return False, "No changes detected", {}
# Analyze each file
for file_path in self.files:
self.log(f"Analyzing: {file_path}")
diff = self.get_file_diff(file_path)
file_type = self.detect_type_from_diff(file_path, diff)
scope = self.extract_scope_from_path(file_path)
self.types[file_type].append(file_path)
self.scopes[scope].append(file_path)
self.log(f" Type: {file_type}, Scope: {scope}")
# Check splitting criteria
reasons = []
# Check 1: Multiple types
if len(self.types) > 1:
type_list = ', '.join(self.types.keys())
reasons.append(f"Multiple types detected: {type_list}")
self.log(f"SPLIT REASON: Multiple types: {type_list}")
# Check 2: Multiple scopes
if len(self.scopes) > 1:
scope_list = ', '.join(self.scopes.keys())
reasons.append(f"Multiple scopes detected: {scope_list}")
self.log(f"SPLIT REASON: Multiple scopes: {scope_list}")
# Check 3: Too many files
if len(self.files) > self.threshold:
reasons.append(f"Large change: {len(self.files)} files (threshold: {self.threshold})")
self.log(f"SPLIT REASON: Too many files: {len(self.files)} > {self.threshold}")
# Check 4: Mixed concerns
self.concerns = self.detect_mixed_concerns()
if self.concerns:
reasons.append(f"Mixed concerns: {'; '.join(self.concerns)}")
self.log(f"SPLIT REASON: Mixed concerns detected")
# Prepare detailed metrics
metrics = {
'file_count': len(self.files),
'types_detected': list(self.types.keys()),
'type_counts': {t: len(files) for t, files in self.types.items()},
'scopes_detected': list(self.scopes.keys()),
'scope_counts': {s: len(files) for s, files in self.scopes.items()},
'concerns': self.concerns,
'threshold': self.threshold
}
# Determine result
if reasons:
should_split = True
reason = '; '.join(reasons)
self.log(f"RECOMMENDATION: Should split - {reason}")
else:
should_split = False
reason = "Changes are atomic - single logical unit"
self.log(f"RECOMMENDATION: Already atomic")
return should_split, reason, metrics
def main():
import argparse
parser = argparse.ArgumentParser(description='Analyze if git changes should be split')
parser.add_argument('--verbose', action='store_true', help='Verbose output')
parser.add_argument('--threshold', type=int, default=10, help='File count threshold')
parser.add_argument('--json', action='store_true', help='Output JSON format')
args = parser.parse_args()
analyzer = SplitAnalyzer(threshold=args.threshold, verbose=args.verbose)
should_split, reason, metrics = analyzer.analyze()
if args.json:
# Output JSON format
result = {
'should_split': should_split,
'reason': reason,
'metrics': metrics,
'recommendation': 'split' if should_split else 'atomic'
}
print(json.dumps(result, indent=2))
else:
# Output human-readable format
print(f"Should split: {'YES' if should_split else 'NO'}")
print(f"Reason: {reason}")
print(f"\nMetrics:")
print(f" Files: {metrics['file_count']}")
print(f" Types: {', '.join(metrics['types_detected'])}")
print(f" Scopes: {', '.join(metrics['scopes_detected'])}")
if metrics['concerns']:
print(f" Concerns: {', '.join(metrics['concerns'])}")
# Exit code: 0 = should split, 1 = atomic
sys.exit(0 if should_split else 1)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,138 @@
# Operation: Analyze Splitting
Determine if current changes should be split into multiple commits.
## Parameters from $ARGUMENTS
- `verbose:true|false` - Detailed analysis output (default: false)
- `threshold:number` - File count threshold for splitting (default: 10)
## Workflow
### Step 1: Get Current Changes
Invoke Bash to get git status and diff:
```bash
git status --porcelain
git diff --cached --stat
git diff --cached --numstat
```
Capture:
- List of modified files
- Number of files changed
- Additions/deletions per file
### Step 2: Run Split Analyzer
Execute the split analyzer script:
```bash
.claude/plugins/git-commit-assistant/commands/atomic-commit/.scripts/split-analyzer.py
```
The script analyzes:
1. **Type diversity**: Multiple commit types (feat, fix, docs, etc.)
2. **Scope diversity**: Multiple modules/components affected
3. **File count**: Too many files changed
4. **Concern separation**: Mixed concerns in changes
### Step 3: Analyze Results
Parse analyzer output to determine:
**Should Split if:**
- Multiple commit types detected (feat + fix + docs)
- Multiple scopes detected (auth + api + ui)
- File count exceeds threshold (>10 files)
- Mixed concerns detected (feature + unrelated refactoring)
**Keep Together if:**
- Single logical change
- All files serve same purpose
- Interdependent changes
- Reasonable file count (≤10)
### Step 4: Generate Recommendation
Create actionable recommendation:
**If should split:**
```
🔀 SPLIT RECOMMENDED
Reason: Multiple types detected (feat, fix, docs)
Files affected: 15
Detected types: feat (8 files), fix (5 files), docs (2 files)
Recommendation: Split into 3 commits
- Commit 1: feat changes (8 files)
- Commit 2: fix changes (5 files)
- Commit 3: docs changes (2 files)
Next steps:
1. Run: /atomic-commit group strategy:type
2. Review groupings
3. Run: /atomic-commit suggest
```
**If already atomic:**
```
✅ ATOMIC COMMIT
Changes represent single logical unit:
- Single type: feat
- Single scope: auth
- File count: 5 files
- Logically cohesive: OAuth implementation
No splitting needed. Proceed with commit.
```
## Output Format
Return structured analysis:
```yaml
should_split: true|false
reason: "Primary reason for recommendation"
metrics:
file_count: number
types_detected: [list]
scopes_detected: [list]
concerns: [list]
recommendation: "Action to take"
next_steps: [ordered list of commands]
```
## Error Handling
- **No changes**: "No staged or unstaged changes detected. Make changes first."
- **Script failure**: "Analysis failed. Check git repository status."
- **Invalid parameters**: "Invalid parameter. Use verbose:true or threshold:number"
## MCP Tool Integration
Use workspace-mcp for script execution:
- Spawn isolated workspace if needed
- Execute analyzer script
- Parse and return results
- Clean up temporary files
## Examples
**Example 1: Large feature**
```bash
/atomic-commit analyze
→ Should split: 15 files, multiple types
```
**Example 2: Bug fix**
```bash
/atomic-commit analyze verbose:true
→ Atomic: 3 files, single fix
```
**Example 3: Custom threshold**
```bash
/atomic-commit analyze threshold:5
→ Should split: 8 files exceeds threshold
```

View File

@@ -0,0 +1,352 @@
# Operation: Create Sequence
Generate executable commit sequence plan.
## Parameters from $ARGUMENTS
- `groups:string` - Group specifications (e.g., "feat:5,fix:2,docs:1")
- `output:plan|script` - Output format (default: plan)
- `auto_stage:true|false` - Auto-stage files (default: false)
## Workflow
### Step 1: Get Commit Suggestions
If not provided, invoke commit suggestion:
```bash
/atomic-commit suggest
```
Parse suggestions to get commit details.
### Step 2: Analyze Dependencies
Execute dependency checker:
```bash
.claude/plugins/git-commit-assistant/commands/atomic-commit/.scripts/dependency-checker.sh
```
Identify dependencies between commits:
- File dependencies (imports, references)
- Logical dependencies (feature order)
- Test dependencies (code → tests)
Build dependency graph:
```
feat(auth) → test(auth) → docs(auth)
fix(api) → test(api)
```
### Step 3: Determine Commit Order
Apply ordering rules:
**Priority 1: Dependencies**
- Commits with dependencies come first
- Tests after implementation
- Docs after features
**Priority 2: Type Order**
Standard order:
1. feat (features enable other changes)
2. fix (fixes should be applied early)
3. refactor (restructuring before additions)
4. perf (performance after stability)
5. test (tests after implementation)
6. docs (documentation last)
7. chore (housekeeping last)
**Priority 3: Scope Grouping**
- Related scopes together
- Independent scopes can be parallel
**Priority 4: Impact**
- High-impact changes first
- Low-risk changes can be later
### Step 4: Generate Sequence Plan
Create executable sequence:
```
📋 COMMIT SEQUENCE PLAN
Execution Order: 3 commits in sequence
┌─────────────────────────────────────────────┐
│ COMMIT 1: feat(auth) │
│ Files: 8 │
│ Dependencies: None │
│ Can execute: ✅ Now │
└─────────────────────────────────────────────┘
Message:
feat(auth): implement OAuth 2.0 authentication
Add complete OAuth 2.0 authentication flow with
support for multiple providers (GitHub, Google).
Files to stage:
git add src/auth/oauth.ts
git add src/auth/tokens.ts
git add src/auth/providers/github.ts
git add src/auth/providers/google.ts
git add src/config/oauth.config.ts
git add src/types/auth.types.ts
git add tests/auth/oauth.test.ts
git add tests/auth/tokens.test.ts
Command:
git commit -m "feat(auth): implement OAuth 2.0 authentication" -m "Add complete OAuth 2.0 authentication flow with support for multiple providers (GitHub, Google). Includes token management, refresh handling, and comprehensive test coverage."
─────────────────────────────────────────────
┌─────────────────────────────────────────────┐
│ COMMIT 2: fix(api) │
│ Files: 3 │
│ Dependencies: None │
│ Can execute: ✅ After commit 1 │
└─────────────────────────────────────────────┘
Message:
fix(api): handle null pointer in user endpoint
Fix null pointer exception when user profile
is incomplete.
Files to stage:
git add src/api/endpoints.ts
git add src/api/validation.ts
git add tests/api.test.ts
Command:
git commit -m "fix(api): handle null pointer in user endpoint" -m "Fix null pointer exception when user profile is incomplete. Add validation to check for required fields before access."
─────────────────────────────────────────────
┌─────────────────────────────────────────────┐
│ COMMIT 3: docs │
│ Files: 2 │
│ Dependencies: Commit 1 (feat(auth)) │
│ Can execute: ✅ After commit 1 │
└─────────────────────────────────────────────┘
Message:
docs: add OAuth authentication guide
Add comprehensive documentation for OAuth 2.0
authentication setup and usage.
Files to stage:
git add README.md
git add docs/authentication.md
Command:
git commit -m "docs: add OAuth authentication guide" -m "Add comprehensive documentation for OAuth 2.0 authentication setup and usage. Includes configuration examples and provider setup."
─────────────────────────────────────────────
Summary:
Total commits: 3
Total files: 13
Execution time: ~3 minutes
All dependencies resolved: ✅ Yes
```
### Step 5: Generate Script (if output:script)
Create executable bash script:
```bash
#!/bin/bash
# Atomic commit sequence
# Generated: 2025-10-13
# Total commits: 3
set -e # Exit on error
echo "🚀 Starting commit sequence..."
# Commit 1: feat(auth)
echo ""
echo "📝 Commit 1/3: feat(auth)"
git add src/auth/oauth.ts
git add src/auth/tokens.ts
git add src/auth/providers/github.ts
git add src/auth/providers/google.ts
git add src/config/oauth.config.ts
git add src/types/auth.types.ts
git add tests/auth/oauth.test.ts
git add tests/auth/tokens.test.ts
git commit -m "feat(auth): implement OAuth 2.0 authentication" -m "Add complete OAuth 2.0 authentication flow with support for multiple providers (GitHub, Google). Includes token management, refresh handling, and comprehensive test coverage."
echo "✅ Commit 1 complete"
# Commit 2: fix(api)
echo ""
echo "📝 Commit 2/3: fix(api)"
git add src/api/endpoints.ts
git add src/api/validation.ts
git add tests/api.test.ts
git commit -m "fix(api): handle null pointer in user endpoint" -m "Fix null pointer exception when user profile is incomplete. Add validation to check for required fields before access."
echo "✅ Commit 2 complete"
# Commit 3: docs
echo ""
echo "📝 Commit 3/3: docs"
git add README.md
git add docs/authentication.md
git commit -m "docs: add OAuth authentication guide" -m "Add comprehensive documentation for OAuth 2.0 authentication setup and usage. Includes configuration examples and provider setup."
echo "✅ Commit 3 complete"
echo ""
echo "🎉 All commits completed successfully!"
echo "Total commits: 3"
echo "Total files: 13"
```
### Step 6: Validate Sequence
Check sequence for issues:
- All files accounted for
- No file appears in multiple commits
- Dependencies resolved
- Logical ordering
- Commands are valid
### Step 7: Provide Execution Options
Offer execution methods:
**Option 1: Manual execution**
Copy-paste commands one by one
**Option 2: Script execution**
Save script and run: `bash commit-sequence.sh`
**Option 3: Interactive**
Execute with guidance: `/atomic-commit interactive`
**Option 4: Agent execution**
Let agent execute sequence
## Output Format
Return structured sequence:
```yaml
sequence:
- commit_id: 1
order: 1
type: feat
scope: auth
message:
subject: "Brief description"
body: "Detailed explanation"
files: [list]
dependencies: []
can_execute: "now|after:<id>"
commands:
stage: [git add commands]
commit: "git commit command"
- commit_id: 2
order: 2
...
summary:
total_commits: number
total_files: number
estimated_time: "minutes"
dependencies_resolved: true|false
execution:
manual: "Copy commands"
script: "Use generated script"
interactive: "/atomic-commit interactive"
agent: "Let agent execute"
script: "Bash script content (if output:script)"
```
## Error Handling
- **No suggestions**: "No commit suggestions. Run: /atomic-commit suggest"
- **Circular dependencies**: "Cannot resolve: circular dependency detected"
- **Invalid group spec**: "Invalid group specification: {spec}"
- **File conflicts**: "File appears in multiple commits: {file}"
## Dependency Analysis
The dependency checker identifies:
**Import dependencies:**
- File A imports from File B
- B must be committed before A
**Test dependencies:**
- Test file tests code file
- Code must be committed before tests
**Logical dependencies:**
- Feature depends on another feature
- Base feature first, dependent after
**Type dependencies:**
- Fixes may depend on features
- Docs depend on implementations
## Execution Planning
### Commit Planner Script
The commit planner creates optimal sequence:
```python
def create_sequence(suggestions, dependencies):
# Build dependency graph
graph = build_graph(suggestions, dependencies)
# Topological sort for dependency order
ordered = topological_sort(graph)
# Apply type priority within independent groups
prioritized = apply_type_priority(ordered)
# Group by scope for related commits
sequenced = group_by_scope(prioritized)
return sequenced
```
## Examples
**Example 1: Auto-generate plan**
```bash
/atomic-commit sequence
→ Creates sequence from current suggestions
```
**Example 2: Custom groups**
```bash
/atomic-commit sequence groups:"feat:5,fix:2,docs:1"
→ Creates sequence for specified groups
```
**Example 3: Generate script**
```bash
/atomic-commit sequence output:script
→ Outputs executable bash script
```
**Example 4: Auto-stage**
```bash
/atomic-commit sequence auto_stage:true
→ Automatically stages files during execution
```
## Integration Notes
This operation:
1. Uses results from `suggest-commits`
2. Requires `dependency-checker.sh` script
3. Uses `commit-planner.py` for optimization
4. Feeds into execution workflows
5. Can output multiple formats
The sequence ensures atomic commits in optimal order.

View File

@@ -0,0 +1,209 @@
# Operation: Group Files
Group related files together for atomic commits.
## Parameters from $ARGUMENTS
- `strategy:type|scope|feature` - Grouping strategy (default: type)
- `show:all|summary` - Output detail level (default: summary)
## Workflow
### Step 1: Get Changed Files
Invoke Bash to get file list:
```bash
git status --porcelain
git diff --cached --name-only
git diff --name-only
```
Capture complete list of changed files.
### Step 2: Execute File Grouper
Run the file grouper script with selected strategy:
```bash
.claude/plugins/git-commit-assistant/commands/atomic-commit/.scripts/file-grouper.sh <strategy>
```
### Step 3: Parse Grouping Results
Process grouper output based on strategy:
**Strategy: type**
Groups files by commit type:
- `feat`: New features or enhancements
- `fix`: Bug fixes
- `docs`: Documentation changes
- `style`: Formatting, whitespace
- `refactor`: Code restructuring
- `test`: Test additions or changes
- `chore`: Build, dependencies, tooling
**Strategy: scope**
Groups files by module/component:
- Extract scope from file path
- Group by common directory structure
- Identify module boundaries
- Example: `auth/`, `api/`, `ui/`, `utils/`
**Strategy: feature**
Groups files by related functionality:
- Analyze dependencies between files
- Group interdependent changes
- Identify feature boundaries
- Requires dependency analysis
### Step 4: Validate Groups
Check each group for atomicity:
- Single logical purpose
- Reasonable size (≤10 files per group)
- Clear scope boundary
- Independent from other groups
### Step 5: Generate Output
Create structured grouping report:
**Summary format:**
```
📦 FILE GROUPS (strategy: type)
Group 1: feat (8 files)
src/auth/oauth.ts
src/auth/tokens.ts
src/config/oauth.config.ts
...
Group 2: fix (3 files)
src/api/endpoints.ts
src/api/validation.ts
tests/api.test.ts
Group 3: docs (2 files)
README.md
docs/authentication.md
Total: 3 groups, 13 files
```
**Detailed format (show:all):**
```
📦 GROUP 1: feat (8 files)
Files:
✓ src/auth/oauth.ts (+145, -0)
✓ src/auth/tokens.ts (+78, -0)
✓ src/auth/providers/github.ts (+95, -0)
✓ src/auth/providers/google.ts (+92, -0)
✓ src/config/oauth.config.ts (+34, -0)
✓ src/types/auth.types.ts (+56, -0)
✓ tests/auth/oauth.test.ts (+123, -0)
✓ tests/auth/tokens.test.ts (+67, -0)
Scope: auth
Purpose: OAuth 2.0 implementation
Dependencies: None
Atomic: ✅ Yes
Suggested commit:
feat(auth): implement OAuth 2.0 authentication
```
## Output Format
Return structured groupings:
```yaml
strategy: type|scope|feature
groups:
- id: 1
type: feat|fix|docs|etc
scope: module_name
files: [list]
stats:
file_count: number
additions: number
deletions: number
atomic: true|false
suggested_message: "commit message"
summary:
total_groups: number
total_files: number
ready_for_commit: true|false
```
## Error Handling
- **No changes**: "No files to group. Make changes first."
- **Invalid strategy**: "Invalid strategy. Use: type, scope, or feature"
- **Grouping failed**: "Could not group files. Check git status."
- **Too many groups**: "Warning: 10+ groups detected. Consider broader grouping."
## Grouping Strategies Explained
### Type-based Grouping
Groups files by conventional commit type. Best for:
- Mixed-type changes
- Clear type boundaries
- Standard workflow
Algorithm:
1. Analyze diff content for each file
2. Detect commit type from changes
3. Group files of same type
4. Validate each group
### Scope-based Grouping
Groups files by module/component. Best for:
- Changes across multiple modules
- Modular codebase structure
- Component isolation
Algorithm:
1. Extract directory structure
2. Identify module boundaries
3. Group files by module
4. Validate scope coherence
### Feature-based Grouping
Groups files by related functionality. Best for:
- Complex feature implementation
- Interdependent changes
- Logical feature units
Algorithm:
1. Analyze file dependencies
2. Build dependency graph
3. Group connected components
4. Validate feature boundaries
## Examples
**Example 1: Type grouping**
```bash
/atomic-commit group strategy:type
→ Groups: feat (5), fix (2), docs (1)
```
**Example 2: Scope grouping**
```bash
/atomic-commit group strategy:scope show:all
→ Detailed groups by module
```
**Example 3: Feature grouping**
```bash
/atomic-commit group strategy:feature
→ Groups by related functionality
```
## Integration Notes
This operation is typically called:
1. After `analyze-splitting` recommends splitting
2. Before `suggest-commits` generates messages
3. As part of `interactive-split` workflow
Results feed into commit suggestion and sequence planning.

View File

@@ -0,0 +1,455 @@
# Operation: Interactive Split
Interactive guided workflow for splitting commits.
## Parameters from $ARGUMENTS
- `step:number` - Start at specific step (default: 1)
- `auto:true|false` - Auto-advance through steps (default: false)
## Workflow
This operation provides step-by-step guided experience through the entire atomic commit splitting process.
### Step 1: Analyze Current Changes
**Display:**
```
🔍 STEP 1: ANALYZE CHANGES
─────────────────────────────────────
Analyzing your current changes...
```
**Execute:**
```bash
/atomic-commit analyze verbose:true
```
**Parse results and show:**
```
Current status:
Files changed: 13
Types detected: feat, fix, docs
Scopes detected: auth, api
Recommendation: Should split
Analysis:
⚠️ Multiple types detected (feat, fix, docs)
⚠️ Multiple scopes detected (auth, api)
✅ File count manageable (13 files)
Recommendation:
Split into multiple atomic commits for:
- Easier code review
- Safer reverts
- Clearer history
```
**Prompt user:**
```
Options:
[1] Continue to grouping (recommended)
[2] Review analysis details
[3] Exit (keep as one commit)
Your choice:
```
### Step 2: Group Files
**Display:**
```
📦 STEP 2: GROUP FILES
─────────────────────────────────────
How would you like to group files?
```
**Show grouping strategies:**
```
Strategies:
[1] By type (feat, fix, docs)
Best for: Mixed-type changes
Groups: ~3 groups expected
[2] By scope (auth, api, docs)
Best for: Changes across modules
Groups: ~3 groups expected
[3] By feature (related functionality)
Best for: Complex feature work
Groups: ~2-4 groups expected
[4] Auto-select (recommended)
Let me choose the best strategy
Your choice:
```
**Execute grouping:**
```bash
/atomic-commit group strategy:<selected> show:all
```
**Show results:**
```
Groups created:
📦 Group 1: feat(auth) - 8 files
Purpose: OAuth 2.0 implementation
Files: src/auth/*, tests/auth/*
Atomic: ✅ Yes
📦 Group 2: fix(api) - 3 files
Purpose: Null pointer fix
Files: src/api/*, tests/api/*
Atomic: ✅ Yes
📦 Group 3: docs - 2 files
Purpose: Authentication guide
Files: README.md, docs/authentication.md
Atomic: ✅ Yes
All groups are atomic: ✅ Yes
```
**Prompt user:**
```
Options:
[1] Continue to commit suggestions
[2] Adjust grouping
[3] Try different strategy
[4] Back to step 1
Your choice:
```
### Step 3: Review Commit Suggestions
**Display:**
```
💬 STEP 3: COMMIT SUGGESTIONS
─────────────────────────────────────
Generating commit messages...
```
**Execute:**
```bash
/atomic-commit suggest format:conventional include_body:true
```
**Show suggestions with review interface:**
```
Commit 1 of 3:
┌─────────────────────────────────────────────┐
│ feat(auth): implement OAuth 2.0 auth │
│ │
│ Add complete OAuth 2.0 authentication flow │
│ with support for multiple providers │
│ (GitHub, Google). Includes token │
│ management, refresh handling, and │
│ comprehensive test coverage. │
│ │
│ Files: 8 │
│ Atomic: ✅ Yes │
└─────────────────────────────────────────────┘
Options for this commit:
[a] Accept as-is
[e] Edit message
[s] Skip this commit
[v] View files in commit
Your choice:
```
**If user edits, provide interface:**
```
Edit commit message:
Subject (max 50 chars):
> feat(auth): implement OAuth 2.0 authentication
Body (optional):
> Add complete OAuth 2.0 authentication flow with
> support for multiple providers (GitHub, Google).
> Includes token management, refresh handling, and
> comprehensive test coverage.
Footer (optional):
>
[s] Save [c] Cancel [r] Reset
```
**After reviewing all commits:**
```
All commits reviewed:
✅ Commit 1: feat(auth) - Accepted
✅ Commit 2: fix(api) - Edited
✅ Commit 3: docs - Accepted
Options:
[1] Continue to sequence planning
[2] Review commits again
[3] Back to grouping
Your choice:
```
### Step 4: Create Commit Sequence
**Display:**
```
📋 STEP 4: COMMIT SEQUENCE
─────────────────────────────────────
Creating execution plan...
```
**Execute:**
```bash
/atomic-commit sequence output:plan
```
**Show sequence:**
```
Commit Order:
1⃣ feat(auth) - 8 files
Dependencies: None
Can commit: ✅ Now
2⃣ fix(api) - 3 files
Dependencies: None
Can commit: ✅ After commit 1
3⃣ docs - 2 files
Dependencies: Commit 1 (documents auth)
Can commit: ✅ After commit 1
Execution time: ~3 minutes
All dependencies resolved: ✅ Yes
```
**Prompt user:**
```
Options:
[1] Show execution commands
[2] Generate script
[3] Execute with guidance (recommended)
[4] Let agent execute
[5] Back to step 3
Your choice:
```
### Step 5: Execute Commits
**If user chooses guided execution:**
```
🚀 STEP 5: EXECUTE COMMITS
─────────────────────────────────────
Executing commit 1 of 3...
┌─────────────────────────────────────────────┐
│ COMMIT 1: feat(auth) │
└─────────────────────────────────────────────┘
Staging files:
✅ src/auth/oauth.ts
✅ src/auth/tokens.ts
✅ src/auth/providers/github.ts
✅ src/auth/providers/google.ts
✅ src/config/oauth.config.ts
✅ src/types/auth.types.ts
✅ tests/auth/oauth.test.ts
✅ tests/auth/tokens.test.ts
Creating commit:
Message: feat(auth): implement OAuth 2.0 authentication
Files: 8
Status: ✅ Success
Commit created: a1b2c3d
─────────────────────────────────────────────
Proceed to commit 2?
[y] Yes, continue
[r] Review what was just committed
[p] Pause (save progress)
[a] Abort remaining commits
Your choice:
```
**Continue through all commits...**
**Final summary:**
```
✨ COMPLETE: ALL COMMITS CREATED
─────────────────────────────────────
Summary:
✅ Commit 1: feat(auth) - a1b2c3d
✅ Commit 2: fix(api) - b2c3d4e
✅ Commit 3: docs - c3d4e5f
Total commits: 3
Total files: 13
Time elapsed: 2m 45s
Your git history is now atomic! 🎉
Next steps:
• Review commits: git log -3
• Push commits: git push
• Create PR: gh pr create
```
## Progress Tracking
The interactive workflow maintains state:
```yaml
session:
step: 1-5
completed_steps: [list]
current_groups: [...]
current_suggestions: [...]
current_sequence: [...]
created_commits: [...]
can_resume: true|false
```
Users can:
- Pause at any step
- Resume from where they left off
- Go back to previous steps
- Skip steps if desired
## Navigation Commands
Throughout the interactive workflow:
- **[n]** Next
- **[b]** Back
- **[h]** Help
- **[q]** Quit
- **[r]** Restart
- **[s]** Status
## Error Handling
**During analysis:**
- No changes detected → Guide to make changes first
- Git errors → Provide troubleshooting steps
**During grouping:**
- Cannot group files → Suggest manual review
- Grouping unclear → Offer alternative strategies
**During suggestions:**
- Cannot generate message → Provide template
- User unsure → Offer examples and guidance
**During execution:**
- Staging fails → Show file status and retry
- Commit fails → Preserve work, show error
- Partial completion → Save progress, allow resume
## Auto Mode
When `auto:true` is specified:
- Automatically selects recommended options
- Shows results at each step
- Pauses for user confirmation at commit execution
- Useful for experienced users who trust recommendations
Example:
```bash
/atomic-commit interactive auto:true
→ Analyzes → Groups by best strategy → Suggests → Creates sequence → Waits for confirmation
```
## Resume Capability
If interrupted, users can resume:
```bash
/atomic-commit interactive step:3
→ Resumes from step 3 (commit suggestions)
```
Session data is preserved across invocations.
## Help System
At any point, user can type `[h]` for context-sensitive help:
```
📚 HELP - Step 2: Grouping
─────────────────────────────────────
Grouping strategies:
• Type: Groups by commit type (feat, fix, docs)
• Scope: Groups by module (auth, api, ui)
• Feature: Groups by related functionality
Tips:
• Choose "type" for mixed-type changes
• Choose "scope" for module-based work
• Choose "feature" for complex features
• Choose "auto" if unsure
Examples:
Type grouping: feat files | fix files | docs files
Scope grouping: auth files | api files | ui files
Press any key to continue...
```
## Output Format
The interactive workflow uses:
- Clear step indicators
- Progress tracking
- Visual separators
- Emoji for status
- Color coding (if supported)
- Consistent option menus
- Helpful prompts
## Examples
**Example 1: Full interactive workflow**
```bash
/atomic-commit interactive
→ Guides through all steps with prompts
```
**Example 2: Auto mode**
```bash
/atomic-commit interactive auto:true
→ Auto-selects best options, confirms before execution
```
**Example 3: Resume from step**
```bash
/atomic-commit interactive step:3
→ Resumes from commit suggestions step
```
## Integration with Agent
The commit-assistant agent can:
1. Recommend interactive mode for complex splits
2. Guide users through steps
3. Answer questions during workflow
4. Explain recommendations
5. Execute commits on behalf of user (with approval)
The interactive workflow provides the best user experience for learning atomic commit practices.

View File

@@ -0,0 +1,99 @@
---
description: Guide splitting large commits into atomic, focused commits
---
# Atomic Commit Skill
Help users create atomic commits - one logical change per commit.
## Available Operations
- **analyze** → Determine if changes should be split
- **group** → Group related files together
- **suggest** → Recommend commit breakdown
- **sequence** → Generate commit sequence plan
- **interactive** → Interactive splitting guidance
## Usage Examples
```bash
# Analyze if splitting needed
/atomic-commit analyze
# Group files by type and scope
/atomic-commit group strategy:type
# Suggest commit breakdown
/atomic-commit suggest
# Create commit sequence
/atomic-commit sequence groups:"feat:5,fix:2,docs:1"
# Interactive splitting
/atomic-commit interactive
```
## Atomic Commit Principles
**One logical change per commit:**
- ✅ Single type (all feat, or all fix)
- ✅ Single scope (all auth, or all api)
- ✅ Reasonable size (≤10 files)
- ✅ Logically cohesive
- ✅ Can be reverted independently
## Router Logic
Parse $ARGUMENTS to determine operation:
1. Extract first word as operation name
2. Parse remaining parameters as key:value pairs
3. Route to appropriate operation file
4. Handle errors gracefully
**Available operations:**
- `analyze` → Read `commands/atomic-commit/analyze-splitting.md`
- `group` → Read `commands/atomic-commit/group-files.md`
- `suggest` → Read `commands/atomic-commit/suggest-commits.md`
- `sequence` → Read `commands/atomic-commit/create-sequence.md`
- `interactive` → Read `commands/atomic-commit/interactive-split.md`
**Error Handling:**
- Unknown operation → List available operations with examples
- No changes detected → Prompt user to make changes first
- Already atomic → Confirm no split needed
**Base directory**: `commands/atomic-commit/`
**Current request**: $ARGUMENTS
### Processing Steps
1. Parse operation from $ARGUMENTS
2. Validate operation exists
3. Extract parameters
4. Read corresponding operation file
5. Execute operation with parameters
6. Return results with actionable guidance
### Example Flows
**Quick analysis:**
```
/atomic-commit analyze
→ Analyzes current changes
→ Returns split recommendation with reasoning
```
**Interactive workflow:**
```
/atomic-commit interactive
→ Guides step-by-step through splitting
→ Shows groupings, suggests commits, creates plan
```
**Custom grouping:**
```
/atomic-commit group strategy:scope
→ Groups files by module/scope
→ Returns groupings for review
```

View File

@@ -0,0 +1,272 @@
# Operation: Suggest Commits
Generate commit message suggestions for file groups.
## Parameters from $ARGUMENTS
- `groups:string` - Comma-separated group IDs to process (default: all)
- `format:conventional|simple` - Message format (default: conventional)
- `include_body:true|false` - Include commit body (default: true)
## Workflow
### Step 1: Get File Groups
If groups not provided, invoke file grouping:
```bash
/atomic-commit group strategy:type
```
Parse grouping results to identify distinct commit groups.
### Step 2: Analyze Each Group
For each file group:
1. Get file diffs
2. Analyze changes
3. Identify commit type
4. Determine scope
5. Extract key changes
Invoke Bash to get detailed diffs:
```bash
git diff --cached <files>
git diff <files>
```
### Step 3: Generate Commit Messages
For each group, create commit message following conventions:
**Conventional format:**
```
<type>(<scope>): <subject>
<body>
<footer>
```
**Components:**
- **type**: feat|fix|docs|style|refactor|test|chore
- **scope**: Module or component affected
- **subject**: Brief description (≤50 chars)
- **body**: Detailed explanation (optional)
- **footer**: Breaking changes, issue references (optional)
**Example:**
```
feat(auth): implement OAuth 2.0 authentication
Add OAuth 2.0 authentication flow with support for:
- GitHub provider
- Google provider
- Token management
- Refresh token handling
Includes comprehensive test coverage.
```
### Step 4: Validate Messages
Check each message for:
- Type correctness
- Scope accuracy
- Subject clarity
- Body completeness
- Footer requirements
Apply commit message best practices:
- Subject in imperative mood
- Subject ≤50 characters
- Body wrapped at 72 characters
- Clear explanation of "why"
- Reference related issues
### Step 5: Rank Suggestions
Order commits by:
1. **Dependency order**: Dependencies first
2. **Type priority**: feat → fix → refactor → docs → chore
3. **Scope cohesion**: Related scopes together
4. **Logical flow**: Natural progression
### Step 6: Generate Output
Create comprehensive suggestion report:
```
💬 COMMIT SUGGESTIONS
Commit 1 of 3: feat(auth) - 8 files
─────────────────────────────────────
feat(auth): implement OAuth 2.0 authentication
Add complete OAuth 2.0 authentication flow with
support for multiple providers (GitHub, Google).
Includes token management, refresh handling, and
comprehensive test coverage.
Files:
• src/auth/oauth.ts
• src/auth/tokens.ts
• src/auth/providers/github.ts
• src/auth/providers/google.ts
• src/config/oauth.config.ts
• src/types/auth.types.ts
• tests/auth/oauth.test.ts
• tests/auth/tokens.test.ts
Atomic: ✅ Yes
Ready: ✅ Can commit
─────────────────────────────────────
Commit 2 of 3: fix(api) - 3 files
─────────────────────────────────────
fix(api): handle null pointer in user endpoint
Fix null pointer exception when user profile is
incomplete. Add validation to check for required
fields before access.
Files:
• src/api/endpoints.ts
• src/api/validation.ts
• tests/api.test.ts
Atomic: ✅ Yes
Ready: ✅ Can commit
─────────────────────────────────────
Commit 3 of 3: docs - 2 files
─────────────────────────────────────
docs: add OAuth authentication guide
Add comprehensive documentation for OAuth 2.0
authentication setup and usage. Includes
configuration examples and provider setup.
Files:
• README.md
• docs/authentication.md
Atomic: ✅ Yes
Ready: ✅ Can commit
─────────────────────────────────────
Summary:
Total commits: 3
Total files: 13
All atomic: ✅ Yes
Ready to commit: ✅ Yes
Next steps:
1. Review suggestions above
2. Run: /atomic-commit sequence (to create plan)
3. Or manually stage and commit each group
```
## Output Format
Return structured suggestions:
```yaml
suggestions:
- commit_id: 1
type: feat|fix|docs|etc
scope: module_name
subject: "Brief description"
body: "Detailed explanation"
footer: "Breaking changes, refs"
files: [list]
stats:
file_count: number
additions: number
deletions: number
atomic: true|false
ready: true|false
dependencies: [commit_ids]
summary:
total_commits: number
total_files: number
all_atomic: true|false
ready_to_commit: true|false
next_steps: [ordered actions]
```
## Message Generation Rules
### Type Detection
- `feat`: New features, capabilities, enhancements
- `fix`: Bug fixes, error corrections
- `docs`: Documentation only
- `style`: Formatting, whitespace, semicolons
- `refactor`: Code restructuring without behavior change
- `test`: Test additions or modifications
- `chore`: Build, dependencies, tooling
- `perf`: Performance improvements
- `ci`: CI/CD configuration changes
### Scope Extraction
1. Analyze file paths for common prefix
2. Identify module name from structure
3. Use meaningful component names
4. Keep scopes consistent across project
### Subject Crafting
1. Use imperative mood: "add" not "added"
2. No capitalization of first letter
3. No period at the end
4. Be specific but concise
5. Maximum 50 characters
### Body Creation
1. Explain "why" not "what"
2. Provide context for changes
3. Wrap at 72 characters
4. Use bullet points for lists
5. Include relevant details
### Footer Guidelines
- **Breaking changes**: Start with "BREAKING CHANGE:"
- **Issue references**: "Fixes #123", "Closes #456"
- **Related issues**: "Related to #789"
## Error Handling
- **No groups**: "No file groups found. Run: /atomic-commit group"
- **Invalid group**: "Group ID not found: {id}"
- **Cannot analyze**: "Failed to analyze changes for group {id}"
- **Message generation failed**: "Could not generate message. Check diffs."
## Examples
**Example 1: All groups**
```bash
/atomic-commit suggest
→ Suggests commits for all detected groups
```
**Example 2: Specific groups**
```bash
/atomic-commit suggest groups:"1,3"
→ Suggests commits only for groups 1 and 3
```
**Example 3: Simple format**
```bash
/atomic-commit suggest format:simple include_body:false
→ Simple messages without detailed bodies
```
## Integration Notes
This operation:
1. Uses results from `group-files`
2. Feeds into `create-sequence`
3. Is part of `interactive-split` workflow
4. Can be used standalone for quick suggestions
Suggestions are not final - user can review and modify before committing.