9.6 KiB
Ranking Algorithm for Claude Skills
Comprehensive scoring system to rank skills by popularity, freshness, and quality.
Core Ranking Formula
final_score = (base_stars * freshness_multiplier * quality_bonus)
Component 1: Base Stars
Direct indicator of community validation and popularity.
base_stars = repository.stargazers_count
# Minimum threshold: 10 stars (filter out experiments)
if [ $base_stars -lt 10 ]; then
skip_result=true
fi
Component 2: Freshness Multiplier
Recent updates indicate active maintenance and modern practices.
Calculate Days Since Last Update
# Get pushed_at timestamp from repository
pushed_at="2025-10-28T12:00:00Z"
# Calculate days old (macOS)
days_old=$(( ($(date +%s) - $(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$pushed_at" +%s)) / 86400 ))
# Calculate days old (Linux)
days_old=$(( ($(date +%s) - $(date -d "$pushed_at" +%s)) / 86400 ))
Apply Freshness Multiplier
if [ $days_old -lt 30 ]; then
freshness_multiplier=1.5
freshness_badge="🔥"
freshness_label="Hot"
elif [ $days_old -lt 90 ]; then
freshness_multiplier=1.2
freshness_badge="📅"
freshness_label="Recent"
elif [ $days_old -lt 180 ]; then
freshness_multiplier=1.0
freshness_badge="📆"
freshness_label="Active"
else
freshness_multiplier=0.5
freshness_badge="⏰"
freshness_label="Older"
fi
Freshness Tiers
| Age Range | Multiplier | Badge | Label | Reasoning |
|---|---|---|---|---|
| < 30 days | 1.5x | 🔥 | Hot | Very active, likely works with latest Claude |
| 30-90 days | 1.2x | 📅 | Recent | Active maintenance |
| 90-180 days | 1.0x | 📆 | Active | Stable, still maintained |
| > 180 days | 0.5x | ⏰ | Older | May be outdated or abandoned |
Component 3: Quality Bonus (Optional)
Additional signals of skill quality beyond stars and freshness.
Quality Signals
quality_bonus=1.0 # Start at neutral
# Has comprehensive description
if [ ${#description} -gt 100 ]; then
quality_bonus=$(echo "$quality_bonus + 0.1" | bc)
fi
# Has reference files
reference_count=$(gh api "repos/$repo/contents" | jq '[.[] | select(.name | test("references|docs|examples"))] | length')
if [ $reference_count -gt 0 ]; then
quality_bonus=$(echo "$quality_bonus + 0.1" | bc)
fi
# Has dependencies documentation
if gh api "repos/$repo/contents" | jq -e '.[] | select(.name == "package.json")' > /dev/null; then
quality_bonus=$(echo "$quality_bonus + 0.05" | bc)
fi
# Active issues/PRs indicate engagement
open_issues=$(gh api "repos/$repo" | jq '.open_issues_count')
if [ $open_issues -gt 0 ] && [ $open_issues -lt 20 ]; then
quality_bonus=$(echo "$quality_bonus + 0.05" | bc)
fi
# Has recent commits (beyond just pushed_at)
recent_commits=$(gh api "repos/$repo/commits?per_page=10" | jq 'length')
if [ $recent_commits -ge 5 ]; then
quality_bonus=$(echo "$quality_bonus + 0.1" | bc)
fi
# Cap quality bonus at 1.5x
if (( $(echo "$quality_bonus > 1.5" | bc -l) )); then
quality_bonus=1.5
fi
Quality Tier Examples
| Quality Bonus | Characteristics |
|---|---|
| 1.0x (baseline) | Basic SKILL.md only |
| 1.1x | + Good description |
| 1.2x | + Reference files |
| 1.3x | + Dependencies documented |
| 1.4x | + Active community (issues/PRs) |
| 1.5x (max) | All of the above |
Complete Scoring Implementation
Bash Implementation
#!/bin/bash
calculate_score() {
local repo=$1
local stars=$2
local pushed_at=$3
# Calculate days since last update
days_old=$(( ($(date +%s) - $(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$pushed_at" +%s)) / 86400 ))
# Freshness multiplier
if [ $days_old -lt 30 ]; then
freshness=1.5
badge="🔥"
elif [ $days_old -lt 90 ]; then
freshness=1.2
badge="📅"
elif [ $days_old -lt 180 ]; then
freshness=1.0
badge="📆"
else
freshness=0.5
badge="⏰"
fi
# Calculate final score
score=$(echo "$stars * $freshness" | bc)
echo "$score|$badge|$days_old"
}
# Example usage
result=$(calculate_score "lackeyjb/playwright-skill" 612 "2025-10-28T12:00:00Z")
score=$(echo "$result" | cut -d'|' -f1)
badge=$(echo "$result" | cut -d'|' -f2)
days=$(echo "$result" | cut -d'|' -f3)
echo "Score: $score, Badge: $badge, Days: $days"
# Output: Score: 918.0, Badge: 🔥, Days: 2
JQ Implementation (More Portable)
gh search repos "claude skills" --json name,stargazersCount,pushedAt --limit 20 | \
jq -r --arg now "$(date -u +%s)" '.[] |
. as $repo |
($repo.pushedAt | fromdateiso8601) as $pushed |
(($now | tonumber) - $pushed) / 86400 | floor as $days |
(if $days < 30 then 1.5 elif $days < 90 then 1.2 elif $days < 180 then 1.0 else 0.5 end) as $multiplier |
(if $days < 30 then "🔥" elif $days < 90 then "📅" elif $days < 180 then "📆" else "⏰" end) as $badge |
($repo.stargazersCount * $multiplier) as $score |
"\($score)|\($repo.name)|\($repo.stargazersCount)|\($badge)|\($days)"
' | sort -t'|' -k1 -nr | head -10
Ranking Examples
Real-World Scores
| Skill | Stars | Days Old | Freshness | Multiplier | Final Score | Rank |
|---|---|---|---|---|---|---|
| playwright-skill | 612 | 2 | 🔥 | 1.5x | 918 | #1 |
| agent-skill-creator | 96 | 5 | 🔥 | 1.5x | 144 | #2 |
| skill-codex | 153 | 9 | 🔥 | 1.5x | 229.5 | Would be #2, but bumped by age |
| ios-simulator-skill | 77 | 2 | 🔥 | 1.5x | 115.5 | #3 |
| claude-skills-mcp | 85 | 2 | 🔥 | 1.5x | 127.5 | #4 |
With hot multiplier, even 77 stars beats 96 stars from 30+ days ago
Score Comparison Scenarios
Scenario 1: Fresh skill vs. Popular old skill
- Skill A: 50 stars, 10 days old → 50 × 1.5 = 75 points 🔥
- Skill B: 100 stars, 200 days old → 100 × 0.5 = 50 points ⏰
- Winner: Skill A (freshness wins)
Scenario 2: Very popular but older skill
- Skill A: 1000 stars, 365 days old → 1000 × 0.5 = 500 points ⏰
- Skill B: 200 stars, 15 days old → 200 × 1.5 = 300 points 🔥
- Winner: Skill A (massive popularity overcomes age)
Scenario 3: Moderate skill, regularly updated
- Skill A: 50 stars, 85 days old → 50 × 1.2 = 60 points 📅
- Skill B: 60 stars, 95 days old → 60 × 1.0 = 60 points 📆
- Tie (freshness threshold matters)
Handling Edge Cases
Newly Created Skills (< 7 days old)
# New skills may have inflated scores, add small penalty
if [ $days_old -lt 7 ]; then
new_skill_penalty=0.9
score=$(echo "$score * $new_skill_penalty" | bc)
note="⚠️ Very new skill - limited validation"
fi
Archived or Deprecated Skills
# Check if repository is archived
is_archived=$(gh api "repos/$repo" | jq -r '.archived')
if [ "$is_archived" = "true" ]; then
score=0 # Exclude from results
note="🔒 Archived - no longer maintained"
fi
Fork vs. Original
# Check if repository is a fork
is_fork=$(gh api "repos/$repo" | jq -r '.fork')
if [ "$is_fork" = "true" ]; then
# Check if fork has more stars than parent
parent_stars=$(gh api "repos/$repo" | jq -r '.parent.stargazers_count')
if [ $stars -gt $parent_stars ]; then
# Fork is more popular, keep it
note="🍴 Popular fork"
else
# Prefer original
score=$(echo "$score * 0.8" | bc)
note="🍴 Fork - see original"
fi
fi
Sorting and Display
Final Sort Order
# Sort by score (descending), then by stars (descending)
results | sort -t'|' -k1,1nr -k3,3nr | head -10
Ties
When scores are identical:
- Sort by stars (higher first)
- If still tied, sort by freshness (newer first)
- If still tied, sort alphabetically
# Multi-level sort
jq -r '.[] | "\(.score)|\(.stars)|\(.days_old)|\(.name)"' | \
sort -t'|' -k1,1nr -k2,2nr -k3,3n -k4,4
Performance Optimization
Bulk Scoring
# Score all repos in one pass
gh search repos "claude skills" --json name,stargazersCount,pushedAt,fullName --limit 50 | \
jq -r --arg now "$(date -u +%s)" '
.[] |
. as $repo |
($repo.pushedAt | fromdateiso8601) as $pushed |
(($now | tonumber) - $pushed) / 86400 | floor as $days |
(if $days < 30 then 1.5 elif $days < 90 then 1.2 elif $days < 180 then 1.0 else 0.5 end) as $mult |
($repo.stargazersCount * $mult) as $score |
{
name: $repo.name,
full_name: $repo.fullName,
stars: $repo.stargazersCount,
days_old: $days,
score: $score
}
' | jq -s 'sort_by(-.score) | .[0:10]'
Caching Scores
# Cache scores to avoid recalculation
score_cache=".skill-scores.json"
# Generate or load cache
if [ ! -f "$score_cache" ] || [ $(($(date +%s) - $(stat -f %m "$score_cache"))) -gt 3600 ]; then
# Regenerate cache (older than 1 hour)
calculate_all_scores > "$score_cache"
fi
# Use cached scores
cat "$score_cache" | jq '.[] | select(.score > 50)'
Customization
User-Defined Weights
Allow users to adjust ranking preferences:
# Default weights
STAR_WEIGHT=1.0
FRESHNESS_WEIGHT=1.0
QUALITY_WEIGHT=0.5
# Calculate weighted score
weighted_score=$(echo "$stars * $STAR_WEIGHT + $freshness_score * $FRESHNESS_WEIGHT + $quality_score * $QUALITY_WEIGHT" | bc)
Category-Specific Ranking
Different categories may value different factors:
# For automation skills: prioritize quality (working code)
if [ "$category" = "automation" ]; then
QUALITY_WEIGHT=1.5
fi
# For research skills: prioritize freshness (up-to-date sources)
if [ "$category" = "research" ]; then
FRESHNESS_WEIGHT=1.5
fi
Summary: The ranking algorithm balances popularity (stars) with recency (freshness), ensuring users see both well-established skills and cutting-edge new capabilities. Quality signals provide additional nuance for edge cases.