Safely rollback a failed or unwanted feature with automatic artifact cleanup
Safely rollback failed or unwanted features with automatic artifact cleanup. Use this when you need to revert code changes, delete feature artifacts (spec.md, plan.md, tasks.md), and optionally remove the feature branch. Provides interactive strategy selection between safe history-preserving reverts or destructive resets, with comprehensive safety checks.
/plugin marketplace add MartyBonacci/specswarm/plugin install specswarm@specswarm-marketplace$ARGUMENTS
Safely rollback a feature that failed or is no longer wanted, including:
Provides two rollback strategies with comprehensive safety checks.
echo "đ Performing safety checks..."
echo ""
# Check 1: Get current branch
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ -z "$CURRENT_BRANCH" ]; then
echo "â Error: Not in a git repository"
exit 1
fi
# Check 2: Prevent rollback of main/master/develop
PROTECTED_BRANCHES=("main" "master" "develop" "production")
for protected in "${PROTECTED_BRANCHES[@]}"; do
if [ "$CURRENT_BRANCH" = "$protected" ]; then
echo "â Error: Cannot rollback protected branch '$CURRENT_BRANCH'"
echo ""
echo "đĄ Rollback is designed for feature branches only."
echo " If you need to revert changes on $CURRENT_BRANCH, use:"
echo " git revert <commit-hash>"
exit 1
fi
done
# Check 3: Check for uncommitted changes
DIRTY=$(git status --porcelain 2>/dev/null)
if [ -n "$DIRTY" ]; then
echo "â Error: Uncommitted changes detected"
echo ""
echo "You have uncommitted changes. Please commit or stash them first:"
git status --short
echo ""
echo "To commit:"
echo " git add ."
echo " git commit -m \"WIP: save before rollback\""
echo ""
echo "To stash:"
echo " git stash push -m \"Before rollback\""
exit 1
fi
# Check 4: Detect parent branch
# First try git config
PARENT_BRANCH=$(git config "branch.$CURRENT_BRANCH.parent" 2>/dev/null)
# Fallback to common parent branches
if [ -z "$PARENT_BRANCH" ]; then
for candidate in "develop" "development" "dev" "main" "master"; do
if git show-ref --verify --quiet "refs/heads/$candidate"; then
PARENT_BRANCH="$candidate"
break
fi
done
fi
# Last resort: ask user
if [ -z "$PARENT_BRANCH" ]; then
echo "â ī¸ Could not auto-detect parent branch"
PARENT_BRANCH="main" # default
fi
echo "â
Safety checks passed"
echo ""
echo "đ Analyzing feature branch: $CURRENT_BRANCH"
echo ""
# Find divergence point from parent
DIVERGE_POINT=$(git merge-base "$CURRENT_BRANCH" "$PARENT_BRANCH" 2>/dev/null)
if [ -z "$DIVERGE_POINT" ]; then
echo "â Error: Could not find divergence point with $PARENT_BRANCH"
echo " Please specify the correct parent branch."
exit 1
fi
# Count commits to rollback
COMMIT_COUNT=$(git rev-list --count "$DIVERGE_POINT..$CURRENT_BRANCH" 2>/dev/null)
COMMITS=$(git log --oneline "$DIVERGE_POINT..$CURRENT_BRANCH" 2>/dev/null)
# Check if already merged to parent
MERGED=$(git branch --merged "$PARENT_BRANCH" | grep -w "$CURRENT_BRANCH" || true)
# Check for feature artifacts
FEATURE_DIR=".feature"
ARTIFACTS_FOUND=false
ARTIFACT_LIST=()
if [ -d "$FEATURE_DIR" ]; then
if [ -f "$FEATURE_DIR/spec.md" ]; then
ARTIFACTS_FOUND=true
ARTIFACT_LIST+=("spec.md ($(wc -l < "$FEATURE_DIR/spec.md" 2>/dev/null || echo "0") lines)")
fi
if [ -f "$FEATURE_DIR/plan.md" ]; then
ARTIFACTS_FOUND=true
ARTIFACT_LIST+=("plan.md ($(wc -l < "$FEATURE_DIR/plan.md" 2>/dev/null || echo "0") lines)")
fi
if [ -f "$FEATURE_DIR/tasks.md" ]; then
ARTIFACTS_FOUND=true
TASK_COUNT=$(grep -c "^-" "$FEATURE_DIR/tasks.md" 2>/dev/null || echo "0")
TASK_COMPLETE=$(grep -c "^- \[x\]" "$FEATURE_DIR/tasks.md" 2>/dev/null || echo "0")
ARTIFACT_LIST+=("tasks.md ($TASK_COUNT tasks, $TASK_COMPLETE completed)")
fi
fi
# Check remote tracking
REMOTE_TRACKING=$(git rev-parse --abbrev-ref --symbolic-full-name "@{u}" 2>/dev/null || echo "")
# Display information
echo "âââââââââââââââââââââââââââââââââââââââââââââââââââ"
echo " ROLLBACK ANALYSIS"
echo "âââââââââââââââââââââââââââââââââââââââââââââââââââ"
echo ""
echo "Branch Information:"
echo " Current Branch: $CURRENT_BRANCH"
echo " Parent Branch: $PARENT_BRANCH"
echo " Commits: $COMMIT_COUNT"
echo " Merged Status: $([[ -n "$MERGED" ]] && echo "Already merged to $PARENT_BRANCH" || echo "Not merged")"
echo " Remote Tracking: ${REMOTE_TRACKING:-"None (local only)"}"
echo ""
if [ "$COMMIT_COUNT" -gt 0 ]; then
echo "Commits to Rollback:"
echo "$COMMITS" | head -5
if [ "$COMMIT_COUNT" -gt 5 ]; then
echo " ... and $((COMMIT_COUNT - 5)) more"
fi
echo ""
fi
if [ "$ARTIFACTS_FOUND" = true ]; then
echo "Feature Artifacts Found:"
for artifact in "${ARTIFACT_LIST[@]}"; do
echo " â $artifact"
done
echo ""
fi
if [ -n "$MERGED" ]; then
echo "â ī¸ WARNING: This branch has already been merged to $PARENT_BRANCH"
echo " Rollback will create revert commits on $PARENT_BRANCH"
echo ""
fi
if [ -n "$REMOTE_TRACKING" ]; then
echo "â ī¸ WARNING: This branch is pushed to remote ($REMOTE_TRACKING)"
echo " Other collaborators may have checked out this branch"
echo ""
fi
echo "âââââââââââââââââââââââââââââââââââââââââââââââââââ"
echo ""
If --dry-run flag is present, show what would happen and exit:
if echo "$ARGUMENTS" | grep -q "\-\-dry-run"; then
echo "đ DRY RUN MODE - No changes will be made"
echo ""
echo "The following actions WOULD be performed:"
echo ""
echo "1. Rollback Strategy: (User would choose)"
echo " Option A: Soft rollback (create $COMMIT_COUNT revert commits)"
echo " Option B: Hard rollback (reset to $PARENT_BRANCH)"
echo ""
echo "2. Artifact Cleanup:"
if [ "$ARTIFACTS_FOUND" = true ]; then
echo " - Delete or backup: ${ARTIFACT_LIST[*]}"
else
echo " - No artifacts to clean up"
fi
echo ""
echo "3. Branch Cleanup:"
echo " - Optionally delete branch: $CURRENT_BRANCH"
echo ""
echo "4. Final State:"
echo " - Switch to: $PARENT_BRANCH"
echo ""
echo "Run without --dry-run to execute rollback."
exit 0
fi
Skip if --force flag is present (use soft rollback defaults).
Use AskUserQuestion tool:
Question 1: "Choose rollback strategy"
Header: "Strategy"
Options:
1. "Soft rollback (revert commits)"
Description: "Creates revert commits, preserves history (RECOMMENDED)"
2. "Hard rollback (reset to parent)"
Description: "Deletes all commits, rewrites history (DANGEROUS)"
3. "Cancel rollback"
Description: "Abort and keep current state"
Store in $ROLLBACK_STRATEGY.
If $ROLLBACK_STRATEGY == "Cancel rollback", exit with message.
Question 2: "Delete feature branch after rollback?"
Header: "Branch Cleanup"
Options:
1. "Yes, delete branch"
Description: "Remove $CURRENT_BRANCH completely"
2. "No, keep branch"
Description: "Keep branch for reference (can delete later)"
Store in $DELETE_BRANCH.
Question 3 (if ARTIFACTS_FOUND=true): "What should we do with feature artifacts?"
Header: "Artifacts"
Options:
1. "Delete artifacts"
Description: "Remove spec.md, plan.md, tasks.md permanently"
2. "Backup artifacts"
Description: "Move to .feature.backup/$(date +%Y%m%d-%H%M%S)/"
3. "Keep artifacts"
Description: "Leave artifacts in place (not recommended)"
Store in $ARTIFACT_ACTION.
Question 4: "Final confirmation - Type 'rollback' to proceed"
Header: "Confirm"
Options:
- Text input required
- Must type exactly "rollback" (case-sensitive)
Store in $CONFIRMATION.
if [ "$CONFIRMATION" != "rollback" ]; then
echo "â Rollback cancelled - confirmation text did not match"
echo " You must type exactly: rollback"
exit 1
fi
echo ""
echo "đ Executing rollback..."
echo ""
# Step 5a: Handle artifacts first (before git operations)
if [ "$ARTIFACTS_FOUND" = true ]; then
case "$ARTIFACT_ACTION" in
"Delete artifacts")
echo "đī¸ Deleting feature artifacts..."
rm -rf "$FEATURE_DIR"
echo "â
Deleted artifacts"
;;
"Backup artifacts")
BACKUP_DIR=".feature.backup/$(date +%Y%m%d-%H%M%S)"
echo "đž Backing up artifacts to $BACKUP_DIR..."
mkdir -p "$BACKUP_DIR"
cp -r "$FEATURE_DIR"/* "$BACKUP_DIR/" 2>/dev/null
rm -rf "$FEATURE_DIR"
echo "â
Artifacts backed up to $BACKUP_DIR"
;;
"Keep artifacts")
echo "âšī¸ Keeping artifacts in place"
;;
esac
echo ""
fi
# Step 5b: Execute rollback strategy
case "$ROLLBACK_STRATEGY" in
"Soft rollback (revert commits)")
echo "đ Creating revert commits..."
if [ "$COMMIT_COUNT" -gt 0 ]; then
# Create revert commits in reverse order
git revert --no-edit "$DIVERGE_POINT..$CURRENT_BRANCH" 2>&1 || {
echo ""
echo "â ī¸ Revert encountered conflicts"
echo " Conflicts must be resolved manually:"
echo " 1. Fix conflicts in the listed files"
echo " 2. git add <resolved-files>"
echo " 3. git revert --continue"
echo ""
echo " Or to abort:"
echo " git revert --abort"
exit 1
}
echo "â
Created $COMMIT_COUNT revert commits"
else
echo "âšī¸ No commits to revert"
fi
# Stay on current branch for soft rollback
SWITCH_BRANCH=false
;;
"Hard rollback (reset to parent)")
echo "â ī¸ WARNING: Performing hard rollback (history will be lost)"
echo ""
# Switch to parent branch
echo "đ Switching to $PARENT_BRANCH..."
git checkout "$PARENT_BRANCH" 2>&1 || {
echo "â Failed to switch to $PARENT_BRANCH"
exit 1
}
echo "â
Switched to $PARENT_BRANCH"
SWITCH_BRANCH=true
;;
esac
echo ""
# Step 5c: Branch cleanup
if [ "$DELETE_BRANCH" = "Yes, delete branch" ] && [ "$SWITCH_BRANCH" = false ]; then
# For soft rollback, need to switch before deleting
echo "đ Switching to $PARENT_BRANCH before branch deletion..."
git checkout "$PARENT_BRANCH" 2>&1 || {
echo "â Failed to switch to $PARENT_BRANCH"
exit 1
}
SWITCH_BRANCH=true
fi
if [ "$DELETE_BRANCH" = "Yes, delete branch" ]; then
echo "đī¸ Deleting branch $CURRENT_BRANCH..."
# Check if branch has unmerged changes
UNMERGED=$(git branch --no-merged "$PARENT_BRANCH" | grep -w "$CURRENT_BRANCH" || true)
if [ -n "$UNMERGED" ] && [ "$ROLLBACK_STRATEGY" != "Hard rollback (reset to parent)" ]; then
# Force delete for unmerged branches
git branch -D "$CURRENT_BRANCH" 2>&1 || {
echo "â Failed to delete branch $CURRENT_BRANCH"
exit 1
}
else
git branch -d "$CURRENT_BRANCH" 2>&1 || {
# Try force delete if normal delete fails
git branch -D "$CURRENT_BRANCH" 2>&1 || {
echo "â Failed to delete branch $CURRENT_BRANCH"
exit 1
}
}
fi
echo "â
Deleted branch $CURRENT_BRANCH"
# Delete remote branch if exists
if [ -n "$REMOTE_TRACKING" ]; then
REMOTE_NAME=$(echo "$REMOTE_TRACKING" | cut -d'/' -f1)
echo ""
echo "â ī¸ Remote branch exists: $REMOTE_TRACKING"
echo " To delete remote branch, run:"
echo " git push $REMOTE_NAME --delete $CURRENT_BRANCH"
fi
fi
echo ""
echo "âââââââââââââââââââââââââââââââââââââââââââââââââââ"
echo " â
ROLLBACK COMPLETE"
echo "âââââââââââââââââââââââââââââââââââââââââââââââââââ"
echo ""
echo "Summary:"
echo " Strategy: $ROLLBACK_STRATEGY"
if [ "$COMMIT_COUNT" -gt 0 ]; then
if [ "$ROLLBACK_STRATEGY" = "Soft rollback (revert commits)" ]; then
echo " Commits: $COMMIT_COUNT reverted"
else
echo " Commits: $COMMIT_COUNT removed"
fi
fi
if [ "$ARTIFACTS_FOUND" = true ]; then
echo " Artifacts: $ARTIFACT_ACTION"
fi
if [ "$DELETE_BRANCH" = "Yes, delete branch" ]; then
echo " Branch: $CURRENT_BRANCH deleted"
else
echo " Branch: $CURRENT_BRANCH kept"
fi
echo " Current: $(git rev-parse --abbrev-ref HEAD)"
echo ""
if [ "$ROLLBACK_STRATEGY" = "Soft rollback (revert commits)" ] && [ "$SWITCH_BRANCH" = false ]; then
echo "đĄ Next Steps:"
echo " 1. Review the revert commits: git log"
echo " 2. Push to remote if needed: git push"
echo " 3. Merge to parent: /specswarm:complete"
echo ""
elif [ "$SWITCH_BRANCH" = true ]; then
echo "đĄ Next Steps:"
echo " 1. Review current state: git status"
echo " 2. Start a new feature: /specswarm:build \"feature\""
echo ""
fi
if [ -d ".feature.backup" ]; then
echo "đĻ Backups:"
echo " Feature artifacts backed up to:"
ls -1d .feature.backup/*/ 2>/dev/null | tail -1
echo ""
fi
echo "âââââââââââââââââââââââââââââââââââââââââââââââââââ"
Soft Rollback (Recommended):
Hard Rollback (Dangerous):
Use Soft Rollback when:
Use Hard Rollback when:
If branch is tracked remotely:
git push origin --delete feature-branch-name
If branch is already merged to parent:
/specswarm:deprecate for features in production/specswarm:rollback
# Guided through strategy selection
# Confirms with "rollback" text
/specswarm:rollback --dry-run
# Shows what would happen without executing
/specswarm:rollback --force
# Uses soft rollback, no confirmation
# NOT RECOMMENDED unless you know what you're doing
/specswarm:rollback --keep-artifacts
# Backs up artifacts automatically
# Find the revert commits
git log --oneline | grep "Revert"
# Revert the reverts to restore original state
git revert <revert-commit-hash>