Skip to main content
ClaudeWave
Skill510 repo starsupdated today

contributor-reward

The contributor-reward skill converts a weekly contributor leaderboard ranking into a tiered USDC payout plan, writes it to memory/distributions.yml, and prepares a command for the distribute-tokens skill to execute. Use this after running contributor-leaderboard to gate rewards behind human review while automating the calculation of who gets paid and how much based on rank, with bonuses for first-time contributors.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/aaronjmars/aeon /tmp/contributor-reward && cp -r /tmp/contributor-reward/skills/contributor-reward ~/.claude/skills/contributor-reward
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

> **${var}** — Optional override. Pass `dry-run` to print the plan without writing to `memory/distributions.yml` or sending a notification. Pass an explicit ISO week (e.g. `2026-W17`) to force-process that week instead of the most recent leaderboard. Empty = process the most recent leaderboard.

Today is ${today}. Closes the loop from `contributor-leaderboard` to `distribute-tokens`: read this week's contributor ranking, price each eligible contributor against a tier table, write a labelled list into `memory/distributions.yml`, and notify the operator with a one-command run line. Humans still gate the actual send (`distribute-tokens` execution stays a manual or chained step) — this skill's job is plan generation, not money movement.

## Why this design

The contributor-leaderboard already names the people moving the project. The distribute-tokens skill already moves tokens with idempotency, balance preflight, and dry-run. The gap was the wiring between them: a contributor's score on Sunday's leaderboard had no path to a wallet credit. This skill is the wiring — and only the wiring.

It deliberately stops short of executing transfers because (a) `distribute-tokens` is the only sanctioned transfer path and re-implementing it here would fragment the idempotency state, and (b) keeping a human-visible diff on `memory/distributions.yml` between plan and execution is the cheapest possible audit trail when real money is involved. The plan lands in git; the operator (or a chained step) runs `distribute-tokens contributors-${ISO_WEEK}` next.

## Tier pricing

| Rank in leaderboard | Reward (USDC) |
|---------------------|---------------|
| 1                   | 25            |
| 2                   | 15            |
| 3                   | 10            |
| 4                   | 5             |
| 5                   | 5             |

**First-PR bonus:** +5 USDC, additive, applied once-ever per login (tracked in state file). Rewards landing your first merged upstream PR — the highest-leverage signal in the leaderboard scoring.

**Eligibility floor:** score ≥ 10 AND the contributor must own a non-empty `@handle` (logins without `@` prefix in the table are skipped — bots and parsing artifacts).

Default `token: USDC` on Base, matching `distribute-tokens` defaults. Operator can override per-recipient amounts in `memory/distributions.yml` after the plan is written if a special bonus is warranted.

## Config

No new config files. Reads:

- `articles/contributor-leaderboard-${MOST_RECENT}.md` — the source-of-truth ranking
- `memory/state/contributor-reward-state.json` — idempotency + first-PR-bonus history (created on first run)
- `memory/distributions.yml` — the file `distribute-tokens` reads (created/updated by this skill)

No new secrets. No new external API calls. No curl. All work is local file I/O plus one optional `gh api` for the upstream default branch.

## Steps

### 1. Parse var and resolve week

- If `${var}` starts with `dry-run`, set `MODE=dry-run`. Strip the `dry-run` prefix; remainder (if any) is treated as the week override.
- Otherwise `MODE=execute`.
- If the remaining var matches `^\d{4}-W\d{2}$`, set `TARGET_WEEK=${var}` and `LEADERBOARD_GLOB="articles/contributor-leaderboard-*.md"` (will pick the latest file regardless of date — operator is asserting they know which file maps to that week).
- Otherwise compute `TARGET_WEEK` from today: `TARGET_WEEK=$(date -u +%G-W%V)` (ISO-8601 week-numbering year + week — `%G/%V` not `%Y/%U`, so Monday-anchored weeks roll over correctly across years).

### 2. Find and validate the source leaderboard article

- `LEADERBOARD_FILE=$(ls -1t articles/contributor-leaderboard-*.md 2>/dev/null | head -1)`
- If no file → log `CONTRIBUTOR_REWARD_NO_LEADERBOARD` to `memory/logs/${today}.md`, exit silently (no notify). The leaderboard skill is the upstream dependency; if it didn't run, this skill has nothing to do.
- Compute the file's age in days from its filename suffix (`contributor-leaderboard-YYYY-MM-DD.md`). If age > 8 days → log `CONTRIBUTOR_REWARD_STALE_LEADERBOARD — last leaderboard ${age}d old`, notify the operator that the upstream leaderboard hasn't run, exit. Don't reward against a fortnight-old ranking.

### 3. Parse the Top Contributors table

The leaderboard's `## Top Contributors` section uses this column layout (from the upstream skill spec):

```
| Rank | Contributor | Score | Merged PRs | Open PRs | Reviews | Fork Commits | New Skills | First PR? | Change |
```

Extract rows with a tolerant regex: `^\|\s*(\d+)\s*\|\s*@(\S+)\s*\|\s*(\d+)\s*\|.*?\|\s*(✨|—|\s*)\s*\|\s*[^|]*\|\s*$`

Capture: `rank`, `login` (without `@`), `score`, `first_pr_marker` (✨ if present, else absent).

If zero rows extracted (leaderboard format drift) → log `CONTRIBUTOR_REWARD_PARSE_FAIL — extracted 0 rows from ${LEADERBOARD_FILE}`, notify with the file path so the operator can inspect, exit.

### 4. Load idempotency state

```json
// memory/state/contributor-reward-state.json
{
  "weeks": {
    "2026-W17": {
      "written_at": "2026-04-26T09:00:00Z",
      "label": "contributors-2026-W17",
      "leaderboard_file": "articles/contributor-leaderboard-2026-04-26.md",
      "rewards": [
        { "login": "alice_dev", "rank": 1, "score": 47, "amount": "25", "first_pr_bonus": false },
        { "login": "bob_builder", "rank": 2, "score": 31, "amount": "20", "first_pr_bonus": true }
      ]
    }
  },
  "first_pr_bonus_paid": ["bob_builder", "carol_eng"]
}
```

Bootstrap with `{"weeks": {}, "first_pr_bonus_paid": []}` if the file doesn't exist.

### 5. Compute the plan

For each parsed row with `rank ≤ 5` AND `score ≥ 10`:

- Look up `base_amount` from the tier table (rank 1→25, 2→15, 3→10, 4-5→5).
- If `first_pr_marker == "✨"` AND `login ∉ first_pr_bonus_paid` → set `first_pr_bonus = true`, `amount = base_amount + 5`. Otherwise `first_pr_bonus = false`, `amount = base_amount`.
- Build row: `{ rank, login, score, base_amount, first_pr_bonus, amount }`.

If `weeks[TARGET_