Initial commit
This commit is contained in:
342
commands/atomic-commit/.scripts/commit-planner.py
Executable file
342
commands/atomic-commit/.scripts/commit-planner.py
Executable 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()
|
||||
328
commands/atomic-commit/.scripts/dependency-checker.sh
Executable file
328
commands/atomic-commit/.scripts/dependency-checker.sh
Executable 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
|
||||
395
commands/atomic-commit/.scripts/file-grouper.sh
Executable file
395
commands/atomic-commit/.scripts/file-grouper.sh
Executable 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
|
||||
283
commands/atomic-commit/.scripts/split-analyzer.py
Executable file
283
commands/atomic-commit/.scripts/split-analyzer.py
Executable 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()
|
||||
138
commands/atomic-commit/analyze-splitting.md
Normal file
138
commands/atomic-commit/analyze-splitting.md
Normal 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
|
||||
```
|
||||
352
commands/atomic-commit/create-sequence.md
Normal file
352
commands/atomic-commit/create-sequence.md
Normal 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.
|
||||
209
commands/atomic-commit/group-files.md
Normal file
209
commands/atomic-commit/group-files.md
Normal 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.
|
||||
455
commands/atomic-commit/interactive-split.md
Normal file
455
commands/atomic-commit/interactive-split.md
Normal 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.
|
||||
99
commands/atomic-commit/skill.md
Normal file
99
commands/atomic-commit/skill.md
Normal 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
|
||||
```
|
||||
272
commands/atomic-commit/suggest-commits.md
Normal file
272
commands/atomic-commit/suggest-commits.md
Normal 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.
|
||||
Reference in New Issue
Block a user