Skip to main content
ClaudeWave
Skill510 estrellas del repoactualizado today

fork-skill-gap

fork-skill-gap identifies which Claude Code skills are enabled in the parent repository but not yet adopted by its active forks, surfacing adoption gaps per fork and flagging which new skills gain traction across the fleet. Use this when managing a fork network to detect skill drift, prioritize downstream adoption blockers, and measure upstream skill launch velocity across your fork cohort.

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/aaronjmars/aeon /tmp/fork-skill-gap && cp -r /tmp/fork-skill-gap/skills/fork-skill-gap ~/.claude/skills/fork-skill-gap
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

SKILL.md

> **${var}** — Optional. Pass `dry-run` to skip notify (state and article still write). Pass `owner/repo` to override the parent repo. Combine with a space (`dry-run owner/repo`) for both.

Today is ${today}. Three fork-intelligence skills already exist: `fork-cohort` answers *is the fork alive?* (workflow runs in 7d), `fork-release-tracker` answers *has any fork shipped a versioned artifact?*, `contributor-spotlight` answers *who's pushing the most code?* None of them answer the obvious operator question: **"what's in upstream that I haven't adopted yet?"** This skill closes that layer.

## Why this exists

A fork that activates the agent on day one and never re-syncs accumulates an invisible drift — upstream keeps shipping skills, the fork stays at its activation-day skill count, and the operator has no surface that flags the gap. Skill drift is silent. The first sign is usually a fork operator noticing six months later that everyone else's agent is doing something theirs isn't.

The gap also tells upstream something: which new skills are getting picked up by the fleet, and which are launching into silence. A skill that ships and is never adopted by any fork in 8 weeks is a skill worth re-examining.

## Scope and inputs

Reads from two places, with graceful degradation if either is missing:

1. **`memory/topics/fork-cohort-state.json`** (optional accelerator) — gives the POWER + ACTIVE fork list, classification, and `enabled_count`. When present, this skill targets only POWER + ACTIVE forks (the audience that actually cares about gaps — STALE/COLD forks aren't running anything anyway).
2. **`gh api repos/{parent}/forks`** (always called as fallback / first run) — when fork-cohort state is absent, missing the forks list, or older than 8 days, this skill builds its own POWER + ACTIVE list from scratch using the same activation rule (≥1 workflow run in last 7d).

The intent is: when fork-cohort is enabled and runs Sunday 19:00, fork-skill-gap at 21:00 reuses its work. When fork-cohort hasn't been enabled yet, fork-skill-gap still works, it's just slower.

## Steps

### 0. Bootstrap

```bash
mkdir -p memory/topics articles
[ -f memory/topics/fork-skill-gap-state.json ] || cat > memory/topics/fork-skill-gap-state.json <<'EOF'
{"parent":null,"last_run":null,"last_status":null,"upstream_skill_count":null,"forks":{}}
EOF
```

`forks` is a map keyed by `owner/repo`. Each entry holds `{missing_count, missing_slugs (cap 50), top_missing_categories, last_seen, classification_source}`. Old entries (fork no longer in POWER+ACTIVE) are evicted on each run.

### 1. Parse var

- Split `${var}` on whitespace. Tokens: `dry-run`, anything matching `^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$` (treated as `PARENT_OVERRIDE`), anything else.
- If any unknown token is present → log `FORK_SKILL_GAP_BAD_VAR: ${var}` and exit (no notify).
- `MODE=dry-run` if `dry-run` token present, else `execute`.

### 2. Resolve parent repo

```bash
if [ -n "$PARENT_OVERRIDE" ]; then
  PARENT_REPO="$PARENT_OVERRIDE"
else
  PARENT_REPO=$(gh api "repos/$(gh repo view --json nameWithOwner -q .nameWithOwner)" --jq '.parent.full_name // .full_name')
fi
PARENT_OWNER="${PARENT_REPO%%/*}"
```

If `state.parent` is set and differs from the resolved `PARENT_REPO` → log `FORK_SKILL_GAP_PARENT_CHANGED`, reset `forks` to `{}`, update `state.parent`.

### 3. Read upstream skills.json (parent baseline)

```bash
gh api "repos/${PARENT_REPO}/contents/skills.json" \
  --jq '.content' 2>/dev/null | base64 -d > /tmp/fsg-upstream.json
UPSTREAM_SLUGS=$(jq -r '.skills[].slug' /tmp/fsg-upstream.json | sort -u)
UPSTREAM_COUNT=$(echo "$UPSTREAM_SLUGS" | wc -l | tr -d ' ')
```

Also pull a slug→category map for the missing-categories rollup:

```bash
jq -r '.skills[] | "\(.slug)\t\(.category // "other")"' /tmp/fsg-upstream.json > /tmp/fsg-categories.tsv
```

If skills.json is missing from upstream (vanishingly unlikely on an Aeon-shaped parent) → log `FORK_SKILL_GAP_NO_UPSTREAM_MANIFEST`, exit (no notify).

### 4. Build the POWER + ACTIVE fork list

Try the cached path first:

```bash
COHORT_STATE=memory/topics/fork-cohort-state.json
COHORT_FRESH=false
if [ -f "$COHORT_STATE" ]; then
  COHORT_DATE=$(jq -r '.last_run // empty' "$COHORT_STATE")
  if [ -n "$COHORT_DATE" ]; then
    # within 8 days = fresh enough (handles weekly Sunday cadence + 1d grace)
    AGE_DAYS=$(( ($(date -u +%s) - $(date -u -d "$COHORT_DATE" +%s)) / 86400 ))
    [ "$AGE_DAYS" -le 8 ] && COHORT_FRESH=true
  fi
fi
```

- If `COHORT_FRESH=true`: read POWER + ACTIVE forks from `state.forks` (`jq -r '.forks | to_entries[] | select(.value.bucket == "POWER" or .value.bucket == "ACTIVE") | .key'`). Set `classification_source=cohort`.
- If `COHORT_FRESH=false`: fall back to live API. For each fork in `gh api "repos/${PARENT_REPO}/forks" --paginate`, check `gh api "repos/${FORK}/actions/runs?per_page=1" --jq '.workflow_runs[0].updated_at // empty'`. Include forks with a run in the last 7 days. Set `classification_source=live`. Apply retry-once-then-skip on 403/5xx, same policy as `fork-cohort`.

Cap at 80 forks per run. If exceeded, sort by stargazers desc and trim (log `truncated_at=80`).

If the resulting list is empty:
- If `classification_source=cohort` and the cohort state has zero POWER+ACTIVE forks → exit `FORK_SKILL_GAP_NO_ACTIVE` (no notify, log only).
- If `classification_source=live` and the live check found zero active forks → exit `FORK_SKILL_GAP_NO_ACTIVE` (no notify).
- If the forks listing itself failed → exit `FORK_SKILL_GAP_API_FAIL` (single-line error notify).

### 5. Per-fork: read fork's skills.json

For each fork in the active list:

```bash
gh api "repos/${FORK_FULL_NAME}/contents/skills.json?ref=${FORK_DEFAULT_BRANCH:-main}" \
  --jq '.content' 2>/dev/null | base64 -d > /tmp/fsg-fork.json
```

If the call returns 404 or the file is missing/empty/invalid JSON: the fork has stripped `skills.json` or is on a non-default branch we couldn't infer. Mark `unreadable=true` for that for