Skip to main content
ClaudeWave
Skill510 repo starsupdated today

ecosystem-links

The ecosystem-links skill performs a weekly Monday URL-health audit of every link in ECOSYSTEM.md, checking GitHub repos for archived or disabled status and validating all project URLs for HTTP errors, redirect chains, or dead endpoints. Use it to catch stale entries before casual visitors encounter broken links, closing the ecosystem feedback loop alongside ecosystem-pulse (liveness of known repos) and ecosystem-entrants (new arrivals). The skill reads the catalog and prior logs but keeps curation decisions to human PR review.

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

SKILL.md

> **${var}** — Optional. `dry-run` skips notify (state still updates and article still writes). Empty = normal run.

Today is ${today}. `ECOSYSTEM.md` is the curated catalog of projects, agents, and products building on top of Aeon — 30+ entries today, growing in irregular bursts. `ecosystem-pulse` measures activity for projects that already resolve to a GitHub repo. `ecosystem-entrants` reports week-over-week arrivals and departures. Neither catches entries whose URLs have gone 404, whose GitHub repo got archived, or whose custom domain lapsed. The first time a casual visitor clicks an ecosystem row and hits a dead page, the catalog stops being trustworthy.

This skill closes that gap. It is a weekly Monday URL-health audit of every link in `ECOSYSTEM.md` — GitHub repos, X handles, custom project domains, anything in the links column. Read-only against `ECOSYSTEM.md`; curation stays a human PR decision per the file's own "Add your project" rules.

Read `memory/MEMORY.md` for context.
Read the last 8 days of `memory/logs/` for prior-run context.
Read `soul/SOUL.md` + `soul/STYLE.md` if populated to match voice in the notification and article.

## Why a separate skill from ecosystem-pulse and ecosystem-entrants

| Skill | Question answered | Cadence | Slot |
|-------|-------------------|---------|------|
| `ecosystem-pulse` | "Are listed projects shipping this week?" | Weekly (Mon 11:00 UTC) | Liveness of *known-good* GitHub repos |
| `ecosystem-entrants` | "What was added to ECOSYSTEM.md this week?" | Weekly (Mon 11:45 UTC) | First-touch of new entries |
| **`ecosystem-links`** | **"Do every row's URLs still resolve?"** | **Weekly (Mon 11:55 UTC)** | **URL validity across the full catalog** |

The three skills compose into a closed feedback loop on the ecosystem catalog: arrivals → liveness → link integrity. Together they catch the three failure modes a static list can hide: a project that landed and was never noticed, a project that listed and then went silent, and a project whose URLs went stale. Building any of them into the others would entangle three different questions (binary added/removed vs. gradated activity vs. binary URL state) — keeping each skill structurally simple is the point.

`ecosystem-pulse` already calls `gh api repos/{owner}/{repo}` for projects that resolve to a GitHub repo. It does **not** check `archived` or `disabled` flags (it cares about `pushed_at` recency), and it never touches the non-GitHub URLs in the row. `ecosystem-links` fills the URL-validity gap without re-doing pulse's recency work.

## Inputs

| Source | Purpose | Auth |
|--------|---------|------|
| `ECOSYSTEM.md` (repo root) | Project list — all URLs parsed from the markdown table | Local file |
| `memory/topics/ecosystem-links-state.json` | Prior-week per-URL snapshot for week-over-week transition detection | Local file |
| `gh api repos/{owner}/{repo}` | Read `archived`, `disabled`, `html_url` for GitHub URLs | `GH_TOKEN` |
| `curl -sI --max-time 10 --location {url}` (with `WebFetch` fallback) | HTTP status + redirect chain for non-GitHub URLs | None / public web |

No new secrets. GitHub access uses the `gh` CLI (`GH_TOKEN`), which handles auth internally — see Sandbox note. All non-GitHub URLs are read with the public web — no Aeon credentials are ever sent to a third-party domain.

Writes:
- `memory/topics/ecosystem-links-state.json` — per-URL snapshot keyed by the canonical URL
- `articles/ecosystem-links-${today}.md` — digest on every non-error run (including QUIET; the article is the durable record even when the notification is suppressed)
- `memory/logs/${today}.md` — one log block per run
- Notification via `./notify` — only when ≥1 DEAD or newly-ARCHIVED URL has surfaced since the last run (see step 7)

## URL extraction

Each `ECOSYSTEM.md` row exposes a third pipe-delimited cell containing one or more Markdown links: `[label](url) · [label2](url2)`. Logo URLs in the first cell (`<img src="...">`) are **out of scope** — they are CDN-hosted by Twitter/CoinGecko and their freshness is not a curation signal. We check the operator-curated outbound links only.

For each accepted row:

1. Extract the project **name** from the second pipe-delimited cell.
2. Extract every `[label](url)` match in the third cell, in order. Keep the raw URL string verbatim — no normalisation (case + trailing slash matter for cache keys).
3. **Classify** each URL by host:
   - `github.com/{owner}/{repo}[/...]` → kind=`github`, target=`{owner}/{repo}` (strip path tail beyond the repo).
   - `x.com/{handle}` or `twitter.com/{handle}` → kind=`x`. **Not checked**: X aggressively rate-limits unauthenticated HEAD requests and a 429/403 from X would generate noise indistinguishable from a real dead handle. Recorded for completeness; status frozen as `XONLY`.
   - Any other `http(s)://` host → kind=`web`, target=full URL.
4. Skip non-HTTP schemes (mailto, telegram, discord invites with their own auth flows). Recorded as `kind=other`, status frozen as `OTHER`.

Within a single row, deduplicate URLs after classification — a row that lists the same GitHub repo twice (once in handle column, once standalone) doesn't get checked twice. Across rows, the same URL appearing in two projects is checked once and the result fans out to both.

## Buckets

Every checked URL is bucketed by the result of its check this run:

| Bucket | Rule | Notify? |
|--------|------|---------|
| `OK` | HTTP 2xx on direct hit; or final URL after redirect chain shares the same registrable host as the source URL. For GitHub: repo lookup succeeded and `archived=false`, `disabled=false`. | No |
| `ARCHIVED` | GitHub repo lookup succeeded and `archived=true`. Includes `disabled=true` (treated as the more severe form of "this repo is no longer maintained"). | Yes (when newly transitioned) |
| `MOVED` | Redirect chain terminates on a *different* registrable host than the source URL (e.g. `oldproject.io` → `newowner.tech` or to a parked domain). Logged separ