| name | safe-rm |
| description | Safe file deletion for Git projects. Classifies files (ALLOW/TRASH/BLOCK), uses trash for untracked source, logs all operations. Use when deleting files, cleaning build artifacts, running rm -rf, or when the user mentions cleanup, trash, or deletion in repositories. |
| license | MIT |
| compatibility | Requires git, trash-cli; works with Cursor, VSCode, Claude Code |
| metadata | {"author":"kayaman","version":"1.0"} |
Safe RM - Protected File Deletion for Git Projects
Purpose
This skill teaches safe file deletion practices to prevent accidental loss of unstaged/untracked files in Git repositories, while allowing efficient cleanup of build artifacts and dependencies. It works seamlessly with AI coding agents and human users.
When to Use This Skill
Use this skill whenever you need to delete files or directories, especially:
- Cleaning up build artifacts or dependencies
- Removing temporary files in Git repositories
- Any
rm -rf operation in project directories
- Deleting files that might include untracked source code
Critical Safety Principles
1. NEVER directly delete in Git repositories without checking
Before any deletion operation in a Git repository, you MUST:
- Check if files are tracked, staged, or untracked
- Classify files by type and risk level
- Use appropriate deletion method based on classification
2. Classification System
Files fall into three categories:
AUTO_ALLOW (safe to delete directly, just log):
- Build artifacts:
dist/, build/, target/, out/, .next/, public/build/
- Dependencies:
node_modules/, vendor/, .venv/, venv/, __pycache__/
- Cache:
.cache/, .pytest_cache/, .mypy_cache/, .tox/
- Compiled:
*.pyc, *.class, *.o, *.so, *.dll
- Temp:
*.tmp, *.log, *.swp, .DS_Store, Thumbs.db
AUTO_TRASH (move to trash, highly recoverable):
- Untracked source code:
*.py, *.js, *.ts, *.jsx, *.tsx, *.rs, *.go, *.java, *.c, *.cpp, *.h
- Untracked docs:
*.md, *.txt, *.rst, *.adoc
- Untracked configs:
*.json, *.yaml, *.toml, *.ini (not in .gitignore)
- Local env files:
*.env.local, *.env.development, .env.*.local
- Any files modified in last 24 hours
- Any file with "important", "draft", "wip", "todo" in name
BLOCK (refuse deletion, return error):
.git/ directory
- Root directories:
/, /home, /etc, /usr, /var
- System directories:
/bin, /sbin, /lib
- Files matching protection patterns:
**/*important*/**, **/*backup*/**
Implementation Guide
Step 1: Detection (Required First Step)
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
IN_GIT_REPO=true
UNTRACKED=$(git ls-files --others --exclude-standard "$TARGET_PATH")
IGNORED=$(git check-ignore "$TARGET_PATH"/* 2>/dev/null)
fi
Step 2: Classification
classify_file() {
local file="$1"
local basename=$(basename "$file")
case "$file" in
.git|.git/*|/|/home|/etc|/usr|/var|/bin|/sbin|/lib)
echo "BLOCK"
return
;;
*important*|*backup*)
echo "BLOCK"
return
;;
esac
case "$basename" in
node_modules|dist|build|target|out|.next|__pycache__|.cache|vendor|.venv|venv)
echo "ALLOW"
return
;;
esac
case "$file" in
*.pyc|*.class|*.o|*.so|*.dll|*.tmp|*.log|*.swp|.DS_Store)
echo "ALLOW"
return
;;
esac
if git check-ignore -q "$file" 2>/dev/null; then
echo
[[ == ]] && | grep -q ;
*.py|*.js|*.ts|*.jsx|*.tsx|*.rs|*.go|*.java|*.c|*.cpp|*.h|*.md|*.txt)
;;
}
Step 3: Execution
delete_safely() {
local file="$1"
local classification=$(classify_file "$file")
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
case "$classification" in
BLOCK)
echo "ERROR: Blocked deletion of protected path: $file" >&2
echo "$timestamp | BLOCK | $file | REFUSED" >> ~/.local/state/safe-rm/audit.log
return 1
;;
ALLOW)
rm -rf "$file"
echo "$timestamp | ALLOW | $file | deleted" >> ~/.local/state/safe-rm/audit.log
;;
TRASH)
trash-put "$file"
echo "$timestamp | TRASH | $file | trashed" >> ~/.local/state/safe-rm/audit.log
;;
esac
}
Correct Usage Examples
Example 1: Clean Build Artifacts
git status --short
rm -rf node_modules/
echo "$(date '+%Y-%m-%d %H:%M:%S') | ALLOW | node_modules/ | deleted" >> ~/.local/state/safe-rm/audit.log
rm -rf dist/
echo "$(date '+%Y-%m-%d %H:%M:%S') | ALLOW | dist/ | deleted" >> ~/.local/state/safe-rm/audit.log
Example 2: Mixed Content Directory
UNTRACKED=$(git ls-files --others --exclude-standard src/)
for file in src/*; do
if [[ "$file" == *".pyc" ]] || [[ "$file" == *"__pycache__"* ]]; then
rm -rf "$file"
echo "$(date '+%Y-%m-%d %H:%M:%S') | ALLOW | $file | deleted" >> ~/.local/state/safe-rm/audit.log
elif echo "$UNTRACKED" | grep -q "^$file$"; then
trash-put "$file"
echo "$(date '+%Y-%m-%d %H:%M:%S') | TRASH | $file | trashed" >> ~/.local/state/safe-rm/audit.log
else
rm "$file"
echo "$(date '+%Y-%m-%d %H:%M:%S') | ALLOW | $file | deleted" >> ~/.local/state/safe-rm/audit.log
fi
done
Example 3: Complete Project Cleanup
git ls-files --others --exclude-standard | while read -r file; do
case "$file" in
*.py|*.js|*.ts|*.md|*.txt)
trash-put "$file"
echo "$(date '+%Y-%m-%d %H:%M:%S') | TRASH | $file | trashed (untracked source)" >> ~/.local/state/safe-rm/audit.log
;;
esac
done
rm -rf node_modules/ dist/ .cache/
echo "$(date '+%Y-%m-%d %H:%M:%S') | ALLOW | build-artifacts | deleted" >> ~/.local/state/safe-rm/audit.log
echo "Cleanup complete. Untracked source files moved to trash. Use 'trash-list' to review."
Recovery Procedures
View Trashed Files
trash-list
trash-list | grep "$(date '+%Y-%m-%d')"
trash-list | grep "filename"
Restore Trashed Files
trash-restore
trash-restore /path/to/file
Check Audit Log
tail -50 ~/.local/state/safe-rm/audit.log
grep "$(date '+%Y-%m-%d')" ~/.local/state/safe-rm/audit.log | grep TRASH
grep BLOCK ~/.local/state/safe-rm/audit.log
Integration with Btrfs Snapshots
On systems with btrfs and Snapper (like OpenSUSE Tumbleweed), you have an additional recovery layer:
sudo snapper list
sudo snapper diff <snapshot-number>..0
sudo snapper rollback <snapshot-number>
Common Patterns for AI Agents
Pattern 1: Dependency Cleanup
if [[ -d "node_modules" ]]; then
rm -rf node_modules/
echo "$(date '+%Y-%m-%d %H:%M:%S') | ALLOW | node_modules/ | deleted" >> ~/.local/state/safe-rm/audit.log
fi
Pattern 2: Build Cleanup
for dir in dist build target out .next; do
if [[ -d "$dir" ]]; then
rm -rf "$dir/"
echo "$(date '+%Y-%m-%d %H:%M:%S') | ALLOW | $dir/ | deleted" >> ~/.local/state/safe-rm/audit.log
fi
done
Pattern 3: Untracked File Check Before Deletion
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
UNTRACKED_SRC=$(git ls-files --others --exclude-standard | grep -E '\.(py|js|ts|md|txt)$')
if [[ -n "$UNTRACKED_SRC" ]]; then
echo "WARNING: Found untracked source files. Moving to trash:"
echo "$UNTRACKED_SRC"
echo "$UNTRACKED_SRC" | while read -r file; do
trash-put "$file"
echo "$(date '+%Y-%m-%d %H:%M:%S') | TRASH | $file | auto-protected" >> ~/.local/state/safe-rm/audit.log
done
fi
fi
Error Handling
If trash-cli is not available:
if ! command -v trash-put >/dev/null 2>&1; then
echo "ERROR: trash-cli not installed. Cannot safely delete files." >&2
echo "Install with: sudo zypper install trash-cli" >&2
exit 1
fi
If audit log is not writable:
AUDIT_LOG="${SAFE_RM_AUDIT_LOG:-${XDG_STATE_HOME:-$HOME/.local/state}/safe-rm/audit.log}"
if [[ ! -w "$AUDIT_LOG" ]] && [[ ! -w "$(dirname "$AUDIT_LOG")" ]]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') | WARNING | Could not write to audit log" >&2
fi
Performance Considerations
For AI agents running many operations:
- Cache Git repo detection:
git rev-parse --show-toplevel
- Batch operations where possible
- Use simple pattern matching before expensive Git commands
- Skip Git checks for known build directories
Critical Reminders
- ALWAYS log deletions to
~/.local/state/safe-rm/audit.log
- ALWAYS classify before deleting - never assume
- PREFER trash over rm when uncertain
- NEVER delete .git directory
- VERIFY untracked files before bulk operations
- TEST with dry-run when implementing new deletion logic
Testing Your Implementation
Before executing deletion code:
echo "Would delete: $TARGET"
classify_file "$TARGET"
Summary Decision Tree
File to delete?
├─ Is .git or system path? → BLOCK (error)
├─ Is build artifact/dependency? → ALLOW (delete, log)
├─ Is in .gitignore? → ALLOW (delete, log)
├─ Is untracked source code? → TRASH (move, log)
└─ Uncertain? → TRASH (safe default, log)
Tool Requirements
Required tools (check before execution):
git - for repository detection
trash-put - for safe deletion (from trash-cli package)
- Standard shell utilities:
grep, basename, date
Installation on OpenSUSE Tumbleweed:
sudo zypper install trash-cli git
Support and Maintenance
Audit log: ~/.local/state/safe-rm/audit.log (override with SAFE_RM_AUDIT_LOG)
Trash: ~/.local/share/Trash/
Configuration: ~/.config/safe-rm/safe-rm-rules.yaml (override with SAFE_RM_CONFIG_DIR)
Regular maintenance:
trash-empty 30
grep "$(date -d '1 month ago' '+%Y-%m')" ~/.local/state/safe-rm/audit.log | wc -l