Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/lychee.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Shared lychee configuration.
#
# Used by:
# .github/workflows/validate.yml (--offline, blocks PRs)
# .github/workflows/external-reference-check.yml (online, informational + scheduled)
#
# Both workflows pass `--config .github/lychee.toml` explicitly because lychee
# only auto-discovers `lychee.toml` in the current working directory.

# Cache successful checks across the run so we don't hammer the same host.
cache = true
max_cache_age = "1d"

# Be polite when retrying transient failures.
max_retries = 2
retry_wait_time = 2

# A 429 means the host exists but rate-limited us; treat as success rather
# than as a broken reference.
accept = ["200..=206", "429"]

# URL regex patterns to skip. Use this for hosts that are flaky, auth-gated,
# or routinely block CI runners. Prefer narrow patterns over broad ones so
# real link rot keeps getting caught.
#
# Examples:
# '^https://intranet\.example\.com/',
# '^https://github\.com/[^/]+/[^/]+/pull/',
exclude = [
# openai.com blocks GitHub-hosted runner IPs with 403; the page is fine
'^https://openai\.com/codex/?$',
]
93 changes: 93 additions & 0 deletions .github/workflows/external-reference-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: external-reference-check

# Reach out to every external URL referenced from our markdown and report
# anything that's unreachable. This is intentionally split from `validate`
# because external checks are flaky (transient 5xx, rate limits, runner IP
# blocks, DNS hiccups). The workflow fails on PR and manual runs so broken
# links are visible; whether they actually block merge is controlled by
# whether this check is marked required in branch protection (recommended:
# leave it not-required so a transient hiccup can't block a merge).
#
# Internal references and anchors are checked by `validate.yml` with
# `lychee --offline`.

on:
pull_request:
paths:
- "**/*.md"
- ".github/lychee.toml"
- ".github/workflows/external-reference-check.yml"
schedule:
# Mondays at 12:00 UTC. Catches link rot in unchanged content.
- cron: "0 12 * * 1"
workflow_dispatch:

permissions:
contents: read
issues: write

jobs:
external-references:
name: Check external references in skills
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Check external references
id: lychee
uses: lycheeverse/lychee-action@v2
with:
args: --config .github/lychee.toml --no-progress "./**/*.md"
# Fail the workflow on PR and manual runs so broken external
# references show up as a red check (mark this workflow as
# not-required in branch protection if you don't want it to
# block merges).
#
# On the scheduled cron run we deliberately do NOT fail here,
# so the issue-filing step below can still execute.
fail: ${{ github.event_name != 'schedule' }}

# Scheduled runs file (or update) an issue when something is rotten,
# so unchanged-but-broken links don't silently sit in main.
- name: File or update link-rot issue on scheduled failure
if: github.event_name == 'schedule' && steps.lychee.outputs.exit_code != 0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
SERVER_URL: ${{ github.server_url }}
RUN_ID: ${{ github.run_id }}
run: |
set -euo pipefail

# Create the label on first use; ignore if it already exists.
gh label create external-reference-rot \
--color ffaa00 \
--description "Unreachable external references found by scheduled lychee run" \
2>/dev/null || true

run_url="${SERVER_URL}/${REPO}/actions/runs/${RUN_ID}"

if [ ! -s ./lychee/out.md ]; then
echo "lychee report file is missing or empty; nothing to file." >&2
exit 0
fi

existing=$(gh issue list \
--label external-reference-rot \
--state open \
--json number \
--jq '.[0].number // empty')

if [ -n "$existing" ]; then
echo "Updating existing issue #$existing"
gh issue edit "$existing" --body-file ./lychee/out.md
gh issue comment "$existing" \
--body "Refreshed by scheduled run: $run_url"
else
echo "Filing new issue"
gh issue create \
--title "External reference rot detected" \
--label external-reference-rot \
--body-file ./lychee/out.md
fi
13 changes: 12 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ on:
pull_request:
paths:
- "scripts/**"
- "**/SKILL.md"
- "**/*.md"
- "skills/**"
- ".claude-plugin/**"
- ".cursor-plugin/**"
- ".github/workflows/validate.yml"
- ".github/lychee.toml"
workflow_dispatch:

jobs:
Expand All @@ -26,3 +27,13 @@ jobs:

- name: Validate skills and generated manifests
run: ./scripts/check.sh

# Deterministic, offline-only reference check: relative paths and
# heading anchors. External URLs are intentionally not checked here
# because their failure modes are flaky; see
# `.github/workflows/external-reference-check.yml` for that.
- name: Check internal references and anchors
uses: lycheeverse/lychee-action@v2
with:
args: --config .github/lychee.toml --offline --include-fragments --no-progress "./**/*.md"
fail: true
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[![Cursor](https://img.shields.io/badge/Cursor-Compatible-000000?logo=cursor&logoColor=white)](https://cursor.com)
[![Claude Code](https://img.shields.io/badge/Claude_Code-Compatible-F07535?logo=claude&logoColor=white)](https://www.anthropic.com/claude-code)
[![OpenAI Codex](https://img.shields.io/badge/OpenAI_Codex-Compatible-412991?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0yMi4yODIgOS44MjFhNS45ODUgNS45ODUgMCAwIDAtLjUxNi00LjkxIDYuMDQ2IDYuMDQ2IDAgMCAwLTYuNTEtMi45QTYuMDY1IDYuMDY1IDAgMCAwIDQuOTgxIDQuMThhNS45ODUgNS45ODUgMCAwIDAtMy45OTggMi45IDYuMDQ2IDYuMDQ2IDAgMCAwIC43NDMgNy4wOTcgNS45OCA1Ljk4IDAgMCAwIC41MSA0LjkxMSA2LjA1MSA2LjA1MSAwIDAgMCA2LjUxNSAyLjlBNS45ODUgNS45ODUgMCAwIDAgMTMuMjYgMjRhNi4wNTYgNi4wNTYgMCAwIDAgNS43NzItNC4yMDUgNS45OSA1Ljk5IDAgMCAwIDMuOTk3LTIuOSA2LjA1NiA2LjA1NiAwIDAgMC0uNzQ3LTcuMDc0ek0xMy4yNiAyMi40M2E0LjQ3NiA0LjQ3NiAwIDAgMS0yLjg3Ni0xLjA0bC4xNDEtLjA4MSA0Ljc3OS0yLjc1OGEuNzk1Ljc5NSAwIDAgMCAuMzkyLS42ODF2LTYuNzM3bDIuMDIgMS4xNjhhLjA3MS4wNzEgMCAwIDEgLjAzOC4wNTJ2NS41ODNhNC41MDQgNC41MDQgMCAwIDEtNC40OTQgNC40OTR6TTMuNiAxOC4zMDRhNC40NyA0LjQ3IDAgMCAxLS41MzUtMy4wMTRsLjE0Mi4wODUgNC43ODMgMi43NTlhLjc3MS43NzEgMCAwIDAgLjc4IDBsNS44NDMtMy4zNjl2Mi4zMzJhLjA4LjA4IDAgMCAxLS4wMzMuMDYyTDkuNzQgMTkuOTVhNC41IDQuNSAwIDAgMS02LjE0LTEuNjQ2ek0yLjM0IDcuODk2YTQuNDg1IDQuNDg1IDAgMCAxIDIuMzY2LTEuOTczVjExLjZhLjc2Ni43NjYgMCAwIDAgLjM4OC42NzdsNS44MTUgMy4zNTUtMi4wMiAxLjE2OGEuMDc2LjA3NiAwIDAgMS0uMDcxIDBsLTQuODMtMi43ODZBNC41MDQgNC41MDQgMCAwIDEgMi4zNCA3Ljg3MnptMTYuNTk3IDMuODU1bC01LjgzMy0zLjM4N0wxNS4xMTkgNy4yYS4wNzYuMDc2IDAgMCAxIC4wNzEgMGw0LjgzIDIuNzkxYTQuNDk0IDQuNDk0IDAgMCAxLS42NzYgOC4xMDV2LTUuNjc4YS43OS43OSAwIDAgMC0uNDA3LS42Njd6bTIuMDEtMy4wMjNsLS4xNDEtLjA4NS00Ljc3NC0yLjc4MmEuNzc2Ljc3NiAwIDAgMC0uNzg1IDBMOS40MDkgOS4yM1Y2Ljg5N2EuMDY2LjA2NiAwIDAgMSAuMDI4LS4wNjFsNC44My0yLjc4N2E0LjUgNC41IDAgMCAxIDYuNjggNC42NnptLTEyLjY0IDQuMTM1bC0yLjAyLTEuMTY0YS4wOC4wOCAwIDAgMS0uMDM4LS4wNTdWNi4wNzVhNC41IDQuNSAwIDAgMSA3LjM3NS0zLjQ1M2wtLjE0Mi4wOC00Ljc3OCAyLjc1OGEuNzk1Ljc5NSAwIDAgMC0uMzkzLjY4MXptMS4wOTctMi4zNjVsMi42MDItMS41IDIuNjA3IDEuNXYyLjk5OWwtMi41OTcgMS41LTIuNjA3LTEuNXoiLz48L3N2Zz4=)](https://openai.com/codex/)
[![Gemini CLI](https://img.shields.io/badge/Gemini_CLI-Compatible-4285F4?logo=googlegemini&logoColor=white)](https://ai.google.dev/gemini-api/docs/cli)
[![Gemini CLI](https://img.shields.io/badge/Gemini_CLI-Compatible-4285F4?logo=googlegemini&logoColor=white)](https://ai.google.dev/gemini-api/docs)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

<img src="assets/banner.gif" alt="AMD Skills"/>
Expand Down
Loading