Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Comprehensive link checking and validation for documentation. Validate internal links, external URLs, anchors, detect redirects, monitor link rot, and generate sitemap validation reports.
.claude/skills/a5c-ai-link-validator/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 1906% | 0% |
| case-02 | ✗→✓ | ▲ Improved | -1% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 16% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 128% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 92% | 0% |
Comprehensive link checking and validation for documentation.
Invoke this skill when you need to:
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | inputPath | string | Yes | Path to documentation directory | | action | string | Yes | validate, monitor, fix-redirects | | checkExternal | boolean | No | Check external URLs (default: true) | | timeout | number | No | Request timeout in seconds | | retries | number | No | Retry count for failed requests | | allowedDomains | array | No | Domains to always allow | | blockedDomains | array | No | Domains to skip checking |
json{ "inputPath": "./docs", "action": "validate", "checkExternal": true, "timeout": 30, "retries": 3 }
json{ "summary": { "total": 342, "valid": 325, "broken": 12, "redirected": 5, "skipped": 0 }, "internal": { "total": 180, "valid": 178, "broken": 2 }, "external": { "total": 162, "valid": 147, "broken": 10, "redirected": 5 }, "issues": [ { "type": "broken", "url": "https://api.example.com/v1/docs", "status": 404, "source": { "file": "docs/api/authentication.md", "line": 42, "text": "[API Documentation](https://api.example.com/v1/docs)" }, "suggestion": { "archived": "https://web.archive.org/web/20250101/https://api.example.com/v1/docs", "alternative": null } }, { "type": "redirect", "url": "http://example.com/old-page", "redirectTo": "https://example.com/new-page", "status": 301, "source": { "file": "docs/guides/migration.md", "line": 15 }, "suggestion": "Update to: https://example.com/new-page" }, { "type": "anchor-missing", "url": "api/users.md#create-user", "source": { "file": "docs/quickstart.md", "line": 28 }, "suggestion": "Heading 'create-user' not found. Available: create, update, delete" } ], "performance": { "duration": 45.2, "requestsMade": 162, "avgResponseTime": 245 } }
json{ "input": "./docs", "output": "./reports/linkcheck.json", "options": { "checkExternal": true, "checkAnchors": true, "checkImages": true, "followRedirects": true, "timeout": 30000, "retries": 3, "retryDelay": 1000, "concurrency": 10, "userAgent": "Mozilla/5.0 LinkChecker/1.0" }, "allowed": { "statusCodes": [200, 201, 204], "domains": ["localhost", "127.0.0.1"], "patterns": ["^https://internal\\.example\\.com"] }, "blocked": { "domains": ["archive.org"], "patterns": ["^https://twitter\\.com"] }, "replacements": { "http://example.com": "https://example.com", "/docs/v1/": "/docs/v2/" } }
markdown<!-- Relative path links --> [Getting Started](./getting-started.md) [API Reference](../api/index.md) <!-- Anchor links --> [Configuration](#configuration) [API Users](./api/users.md#create-user) <!-- Image links --> 
markdown<!-- Standard external links --> [GitHub](https://github.com) [Documentation](https://docs.example.com/guide) <!-- Links with anchors --> [MDN Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#instance_methods)
javascriptconst internalRules = { // File must exist fileExists: { severity: 'error', check: (link, context) => { const resolvedPath = resolvePath(link, context.file); return fs.existsSync(resolvedPath); } }, // Anchor must exist in target file anchorExists: { severity: 'error', check: (link, context) => { const [file, anchor] = link.split('#'); if (!anchor) return true; const headings = extractHeadings(file); return headings.some(h => slugify(h) === anchor); } }, // Case sensitivity caseSensitive: { severity: 'warning', check: (link, context) => { const actual = findActualPath(link); return link === actual; } } };
javascriptconst externalRules = { // URL must return success status statusOk: { severity: 'error', check: async (url) => { const response = await fetch(url, { method: 'HEAD' }); return response.ok; } }, // HTTPS preferred httpsPreferred: { severity: 'warning', check: (url) => { return url.startsWith('https://') || isLocalhost(url); } }, // No redirects (or update to final URL) noRedirects: { severity: 'info', check: async (url) => { const response = await fetch(url, { redirect: 'manual' }); return !response.headers.get('location'); } } };
yaml# .github/workflows/link-check.yml name: Link Check on: schedule: - cron: '0 0 * * 0' # Weekly on Sunday workflow_dispatch: jobs: check-links: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check links uses: lycheeverse/lychee-action@v1 with: args: --verbose --no-progress './docs/**/*.md' fail: true - name: Create issue on failure if: failure() uses: actions/github-script@v7 with: script: | github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: 'Broken links detected', body: 'Weekly link check found broken links. See workflow run for details.', labels: ['documentation', 'bug'] })
json{ "history": [ { "date": "2026-01-24", "total": 342, "broken": 12, "new_broken": 3, "fixed": 1 }, { "date": "2026-01-17", "total": 340, "broken": 10, "new_broken": 2, "fixed": 0 } ], "trends": { "avg_broken_per_week": 2.5, "most_problematic_domains": [ { "domain": "api.example.com", "broken_count": 5 }, { "domain": "old-docs.example.com", "broken_count": 3 } ] } }
javascriptasync function findArchiveUrl(brokenUrl) { const archiveApi = `https://archive.org/wayback/available?url=${encodeURIComponent(brokenUrl)}`; try { const response = await fetch(archiveApi); const data = await response.json(); if (data.archived_snapshots?.closest) { return { available: true, url: data.archived_snapshots.closest.url, timestamp: data.archived_snapshots.closest.timestamp }; } } catch (error) { // Archive.org unavailable } return { available: false }; }
javascriptasync function validateSitemap(sitemapUrl) { const response = await fetch(sitemapUrl); const xml = await response.text(); const urls = parseSitemapXml(xml); const results = await Promise.all( urls.map(async (url) => { const check = await checkUrl(url.loc); return { url: url.loc, lastmod: url.lastmod, status: check.status, valid: check.valid }; }) ); return { total: urls.length, valid: results.filter(r => r.valid).length, invalid: results.filter(r => !r.valid), missingLastmod: results.filter(r => !r.lastmod).length }; }
json{ "devDependencies": { "linkinator": "^6.0.0", "markdown-link-check": "^3.11.0", "lychee": "^0.14.0", "node-fetch": "^3.3.0" } }
bash# Check all links npx linkinator ./docs --recurse --format json > report.json # Check with markdown-link-check find docs -name '*.md' -exec npx markdown-link-check {} \; # Use lychee (Rust-based, fast) lychee './docs/**/*.md' --format json --output report.json # Fix redirects automatically node scripts/fix-redirects.js --input docs/ --report report.json
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | fail→fail | 21,961 | 22,153 | +1% | 1 | 1 | 0% | 2,367 | 5,326 | +125% | 0 | 0 | — |
case-08 | fail→fail | 24,973 | 23,611 | -5% | 1 | 1 | 0% | 2,983 | 6,131 | +106% | 0 | 0 | — |
case-01 | fail→pass | 20,198 | 24,015 | +19% | 1 | 1 | 0% | 354 | 7,101 | +1906% | 0 | 0 | — |
case-02 | fail→pass | 32,396 | 19,027 | -41% | 1 | 1 | 0% | 5,335 | 5,259 | -1% | 0 | 0 | — |
case-03 | fail→pass | 40,347 | 33,281 | -18% | 1 | 1 | 0% | 6,721 | 7,788 | +16% | 0 | 0 | — |
case-04 | fail→pass | 17,369 | 15,475 | -11% | 1 | 1 | 0% | 2,324 | 5,306 | +128% | 0 | 0 | — |
case-05 | pass→pass | 10,971 | 14,743 | +34% | 1 | 1 | 0% | 2,012 | 4,492 | +123% | 0 | 0 | — |
case-06 | pass→pass | 12,378 | 8,225 | -34% | 1 | 1 | 0% | 1,098 | 3,531 | +222% | 0 | 0 | — |
case-09 | fail→fail | 36,474 | 13,602 | -63% | 1 | 1 | 0% | 2,551 | 5,236 | +105% | 0 | 0 | — |
case-10 | fail→pass | 24,482 | 17,369 | -29% | 1 | 1 | 0% | 3,055 | 5,872 | +92% | 0 | 0 | — |
case-11 | fail→pass | 7,396 | 3,476 | -53% | 1 | 1 | 0% | 1,150 | 3,581 | +211% | 0 | 0 | — |
case-12 | pass→pass | 11,631 | 7,621 | -34% | 1 | 1 | 0% | 1,109 | 3,422 | +209% | 0 | 0 | — |
case-13 | fail→pass | 13,512 | 8,943 | -34% | 1 | 1 | 0% | 1,533 | 3,736 | +144% | 0 | 0 | — |
case-14 | fail→pass | 11,476 | 8,599 | -25% | 1 | 1 | 0% | 2,169 | 3,730 | +72% | 0 | 0 | — |
case-15 | fail→pass | 13,796 | 4,477 | -68% | 1 | 1 | 0% | 1,558 | 3,692 | +137% | 0 | 0 | — |
case-16 | pass→pass | 12,949 | 16,787 | +30% | 1 | 1 | 0% | 2,276 | 5,908 | +160% | 0 | 0 | — |
case-17 | fail→pass | 28,997 | 7,563 | -74% | 1 | 1 | 0% | 3,685 | 3,412 | -7% | 0 | 0 | — |
case-18 | fail→pass | 17,194 | 2,457 | -86% | 1 | 1 | 0% | 2,372 | 3,388 | +43% | 0 | 0 | — |
case-19 | pass→pass | 19,810 | 4,997 | -75% | 1 | 1 | 0% | 2,203 | 3,951 | +79% | 0 | 0 | — |
case-20 | pass→pass | 25,289 | 14,147 | -44% | 1 | 1 | 0% | 3,089 | 5,740 | +86% | 0 | 0 | — |
case-21 | pass→pass | 22,021 | 12,733 | -42% | 1 | 1 | 0% | 2,460 | 5,464 | +122% | 0 | 0 | — |
case-22 | pass→pass | 10,463 | 8,815 | -16% | 1 | 1 | 0% | 2,066 | 4,696 | +127% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +50 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.