
Rules Distill
Index every rule file under ~/.claude/rules and emit structured JSON so you can distill, archive, or merge overlapping agent rules.
Overview
rules-distill is an agent skill most often used in Build (also Operate) that inventories ~/.claude/rules markdown files and outputs a JSON heading index for distillation.
Install
npx skills add https://github.com/affaan-m/everything-claude-code --skill rules-distillWhat is this skill?
- Scans ~/.claude/rules for all .md files while skipping _archived/
- Extracts H2 headings per file into a JSON index with line counts
- Outputs machine-readable catalogs via scan-rules.sh for downstream distillation
- Supports RULES_DISTILL_DIR override for testing non-default paths
- Fails fast with a clear JSON error when the rules directory is missing
Adoption & trust: 3.8k installs on skills.sh; 210k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your Claude rules sprawled across dozens of markdown files and you cannot see overlaps, stale sections, or what to merge without manually opening each file.
Who is it for?
Solo builders curating ~/.claude/rules before a cleanup sprint or when syncing agent behavior across projects.
Skip if: Teams with no local Claude rules directory or anyone who only needs one-off chat instructions without persistent rule files.
When should I use this skill?
You need to enumerate Claude rule files and extract H2 headings before distilling, archiving, or reorganizing ~/.claude/rules.
What do I get? / Deliverables
You get a complete JSON catalog of rule files, line counts, and H2 headings so you can archive, merge, or rewrite rules with a clear map of what exists.
- JSON catalog of rule files with paths, line counts, and H2 headings
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Canonical shelf is Build → agent-tooling because the skill operates on Claude Code rule artifacts that define how your coding agent behaves. rules-distill is inventory and structure for agent rules—not app UI or backend code—so agent-tooling is the primary browse facet.
Where it fits
Run a heading index before merging five overlapping coding-style rules into one canonical file.
Re-scan rules after a production incident to see which operate-phase guidance grew without structure.
Attach the JSON inventory to a PR that only touches ~/.claude/rules so reviewers see scope at a glance.
How it compares
Use as a structured inventory step instead of hand-searching rule folders in the editor.
Common Questions / FAQ
Who is rules-distill for?
Solo and indie builders who maintain Claude Code rule markdown under ~/.claude/rules and need a factual index before editing or deduplicating guidance.
When should I use rules-distill?
Use it in Build when expanding agent-tooling, in Operate when iterating on production agent behavior, and anytime you refactor rules—after adding files, before archiving duplicates, or when onboarding a new dev environment.
Is rules-distill safe to install?
It reads local rule files under your chosen directory and prints JSON to stdout; review the Security Audits panel on this page and inspect scan-rules.sh before running in CI or shared machines.
SKILL.md
READMESKILL.md - Rules Distill
#!/usr/bin/env bash # scan-rules.sh — enumerate rule files and extract H2 heading index # Usage: scan-rules.sh [RULES_DIR] # Output: JSON to stdout # # Environment: # RULES_DISTILL_DIR Override ~/.claude/rules (for testing only) set -euo pipefail RULES_DIR="${RULES_DISTILL_DIR:-${1:-$HOME/.claude/rules}}" if [[ ! -d "$RULES_DIR" ]]; then jq -n --arg path "$RULES_DIR" '{"error":"rules directory not found","path":$path}' >&2 exit 1 fi # Collect all .md files (excluding _archived/) files=() while IFS= read -r f; do files+=("$f") done < <(find "$RULES_DIR" -name '*.md' -not -path '*/_archived/*' -print | sort) total=${#files[@]} tmpdir=$(mktemp -d) _rules_cleanup() { rm -rf "$tmpdir"; } trap _rules_cleanup EXIT for i in "${!files[@]}"; do file="${files[$i]}" rel_path="${file#"$HOME"/}" rel_path="~/$rel_path" # Extract H2 headings (## Title) into a JSON array via jq headings_json=$({ grep -E '^## ' "$file" 2>/dev/null || true; } | sed 's/^## //' | jq -R . | jq -s '.') # Get line count line_count=$(wc -l < "$file" | tr -d ' ') jq -n \ --arg path "$rel_path" \ --arg file "$(basename "$file")" \ --argjson lines "$line_count" \ --argjson headings "$headings_json" \ '{path:$path,file:$file,lines:$lines,headings:$headings}' \ > "$tmpdir/$i.json" done if [[ ${#files[@]} -eq 0 ]]; then jq -n --arg dir "$RULES_DIR" '{rules_dir:$dir,total:0,rules:[]}' else jq -n \ --arg dir "$RULES_DIR" \ --argjson total "$total" \ --argjson rules "$(jq -s '.' "$tmpdir"/*.json)" \ '{rules_dir:$dir,total:$total,rules:$rules}' fi #!/usr/bin/env bash # scan-skills.sh — enumerate skill files, extract frontmatter and UTC mtime # Usage: scan-skills.sh [CWD_SKILLS_DIR] # Output: JSON to stdout # # When CWD_SKILLS_DIR is omitted, defaults to $PWD/.claude/skills so the # script always picks up project-level skills without relying on the caller. # # Environment: # RULES_DISTILL_GLOBAL_DIR Override ~/.claude/skills (for testing only; # do not set in production — intended for bats tests) # RULES_DISTILL_PROJECT_DIR Override project dir detection (for testing only) set -euo pipefail GLOBAL_DIR="${RULES_DISTILL_GLOBAL_DIR:-$HOME/.claude/skills}" CWD_SKILLS_DIR="${RULES_DISTILL_PROJECT_DIR:-${1:-$PWD/.claude/skills}}" # Validate CWD_SKILLS_DIR looks like a .claude/skills path (defense-in-depth). # Only warn when the path exists — a nonexistent path poses no traversal risk. if [[ -n "$CWD_SKILLS_DIR" && -d "$CWD_SKILLS_DIR" && "$CWD_SKILLS_DIR" != */.claude/skills* ]]; then echo "Warning: CWD_SKILLS_DIR does not look like a .claude/skills path: $CWD_SKILLS_DIR" >&2 fi # Extract a frontmatter field (handles both quoted and unquoted single-line values). # Does NOT support multi-line YAML blocks (| or >) or nested YAML keys. extract_field() { local file="$1" field="$2" awk -v f="$field" ' BEGIN { fm=0 } /^---$/ { fm++; next } fm==1 { n = length(f) + 2 if (substr($0, 1, n) == f ": ") { val = substr($0, n+1) gsub(/^"/, "", val) gsub(/"$/, "", val) print val exit } } fm>=2 { exit } ' "$file" } # Get file mtime in UTC ISO8601 (portable: GNU and BSD) get_mtime() { local file="$1" local secs secs=$(stat -c %Y "$file" 2>/dev/null || stat -f %m "$file" 2>/dev/null) || return 1 date -u -d "@$secs" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -r "$secs" +%Y-%m-%dT%H:%M:%SZ } # Scan a directory and produce a JSON array of skill objects scan_dir_to_json() { local dir="$1" local tmpdir tmpdir=$(mktemp -d) local _scan_tmpdir="$tmpdir" _scan_cleanup() { rm -rf "$_scan_tmpdir"; } trap _scan_cleanup RETURN local i=0 while IFS= read -r file; do local name desc mtime dp name=$(extract_field "$file" "name") desc=$(extract_field "$file" "description") mtime=$(get_mtime "$file") dp="${file/#$HOME/~}" jq -n \ --arg path "$dp" \ --ar