.agents/skills/link-checker/SKILL.md
Find and fix broken links in documentation and code using lychee. Scan files with --format json to identify broken URLs, then systematically repair or update them.
npx skillsauth add em-jones/staccato-toolkit link-checkerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill provides systematic workflows for finding and fixing broken links in your codebase using the lychee link checker. Lychee is a fast, async link checker written in Rust that can scan entire directory trees and output structured JSON for automated processing.
Ensure lychee is available in your environment:
# Via cargo (Rust)
cargo install lychee
# Via devbox (recommended)
echo "packages = [\"lychee\"]" >> devbox.json
devbox shell
# Via Homebrew (macOS)
brew install lychee
Verify installation:
lychee --version
--format json to collect all link datajq to categorize broken linksScan a directory tree for broken links:
lychee <directory>/**/*.{md,mdx,html,rst} --format json > link-report.json
| Extension | Type | Use case |
| ---------------------------- | --------------------- | ---------------------------------------- |
| .md, .mdx | Markdown | Documentation, READMEs, change artifacts |
| .html | HTML | Static sites, generated documentation |
| .rst | reStructuredText | Sphinx documentation |
| .go | Go source | Doc comments, URLs in strings (limited) |
| .ts, .tsx, .js, .jsx | TypeScript/JavaScript | Doc comments, URLs in objects |
For robust link checking (especially useful for external links):
lychee \
<directory>/**/*.md \
--timeout 30 \
--retry-wait-time 1 \
--max-retries 3 \
--format json > link-report.json
Skip slow-to-scan or irrelevant directories:
lychee \
<directory>/**/*.md \
--exclude "node_modules|.git|.devbox" \
--exclude-path "./vendor/*" \
--format json > link-report.json
When you only want to find broken internal paths:
lychee \
<directory>/**/*.md \
--offline \
--format json > link-report.json
The --offline flag skips HTTP requests; lychee only validates file paths.
Lychee outputs a JSON object with this structure:
{
"status": "success",
"stats": {
"total": 150,
"successful": 140,
"errors": 10,
"warnings": 0,
"timeouts": 0
},
"failures": [
{
"uri": "https://example.com/broken-page",
"source": "docs/guide.md:42",
"status": "Error fetching https://example.com/broken-page",
"code": 404
},
{
"uri": "./relative/path/file.md",
"source": "docs/overview.md:15",
"status": "FileNotFound",
"code": null
}
]
}
failures[] — Array of broken linksuri — The link itself (URL or file path)source — Where the link appears (file:line)status — Error message (e.g., "404 Not Found", "FileNotFound")code — HTTP status code (null for file errors)jq -r '.failures[] | "\(.source): \(.uri)"' link-report.json
Output:
docs/guide.md:42: https://example.com/broken-page
docs/overview.md:15: ./relative/path/file.md
jq '.failures | group_by(.status) | map({status: .[0].status, count: length})' link-report.json
jq -r '.failures[] | select(.code != null) | "\(.uri) (\(.code))"' link-report.json
jq -r '.failures[] | select(.code == null) | "\(.source): \(.uri)"' link-report.json
jq -r '.failures[] | [.source, .uri, .status] | @csv' link-report.json > broken-links.csv
When a file has been moved or renamed, update all references:
# Before fixing, locate the correct file
find . -name "correct-filename.md" -type f
# Update the link in the source file
# Old: ./relative/path/file.md
# New: ./new/path/correct-filename.md
Use the Edit tool to update each reference:
# Example: fixing a link in docs/guide.md at line 42
# Change: [See the overview](./relative/path/file.md)
# To: [See the overview](./docs/overview.md)
When external links return 404 or timeout:
Example flow:
# Broken: https://old-api.example.com/v1/docs
# Fixed: https://docs.example.com/api/v1
Some external services are flaky or behind auth. Use .lycheeignore:
Create .lycheeignore in the repo root:
# Skip flaky external service
https://flaky-service.example.com/.*
# Skip authenticated endpoints
https://api.github.com/repos/private/.*
# Skip email URLs
mailto:.*
Then re-scan:
lychee <directory>/**/*.md --format json > link-report.json
For dead external links with no modern equivalent:
# Old: https://old-docs.example.com/article
# New: https://web.archive.org/web/2023/old-docs.example.com/article
# Update in source file:
# [Old reference](https://web.archive.org/web/2023/old-docs.example.com/article) (archived)
After making corrections, re-scan to confirm:
lychee <directory>/**/*.md --format json > link-report-after.json
Compare the results:
# Before
jq '.stats.errors' link-report.json
# After
jq '.stats.errors' link-report-after.json
# Should be lower or zero
Check specific failures:
jq '.failures | length' link-report-after.json
#!/bin/bash
set -e
REPORT_FILE="link-report.json"
FAILED_LINKS_FILE="broken-links.csv"
echo "Scanning for broken links..."
lychee docs/**/*.md --format json > "$REPORT_FILE"
echo "Analyzing results..."
ERRORS=$(jq '.stats.errors' "$REPORT_FILE")
if [ "$ERRORS" -gt 0 ]; then
echo "Found $ERRORS broken links:"
jq -r '.failures[] | [.source, .uri, .status] | @csv' "$REPORT_FILE" > "$FAILED_LINKS_FILE"
cat "$FAILED_LINKS_FILE"
exit 1
else
echo "✓ All links valid"
exit 0
fi
.PHONY: check-links
check-links:
@echo "Checking documentation links..."
@lychee docs/**/*.md --format json | jq -r 'if .stats.errors > 0 then "FAILED: \(.stats.errors) broken links" else "OK" end'
.PHONY: list-broken-links
list-broken-links:
@lychee docs/**/*.md --format json | jq -r '.failures[] | "\(.source): \(.uri) (\(.status))"'
.PHONY: fix-links
fix-links: check-links
@echo "Review the list above and update links manually"
name: Link Checker
on: [pull_request, push]
jobs:
check-links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: lycheeverse/lychee-action@v1
with:
args: docs/**/*.md --format json
fail: true
format: json
When processing lychee output, use jq for reliable JSON parsing:
jq '.failures | group_by(.code) | map({code: .[0].code, count: length})' link-report.json
jq -r '.failures[].uri' link-report.json | sort -u
jq -r '.failures[] | select(.source | startswith("docs/guide.md")) | .uri' link-report.json
jq '{
total: .stats.total,
broken: .stats.errors,
by_type: (.failures | group_by(.status) | map({type: .[0].status, count: length}))
}' link-report.json
.lycheeignore sparingly — Exclusions should be for genuinely flaky services, not to hide problemsSolution: Increase timeout and reduce concurrent requests:
lychee \
docs/**/*.md \
--timeout 60 \
--max-concurrent-requests 1 \
--format json > link-report.json
Solution: Add exceptions in .lycheeignore:
# Dynamic paths that don't resolve without query params
https://example.com/api/.*
Solution: Update relative paths to be absolute within the repo:
# Before: ../../../other/file.md
# After: ../../docs/other/file.md
# Or use full path: /docs/other/file.md (if supported by your platform)
devbox installSolution: Ensure you're running in the devbox shell:
devbox shell
which lychee # Should show path
lychee --version
Create a lychee.toml file for reusable settings:
# Timeouts and retries
timeout = 30
max_retries = 3
retry_wait_time = 1
# Exclude patterns
exclude_paths = [
"node_modules",
".git",
"vendor"
]
# Headers for authenticated requests
headers = [
"Authorization: token ghp_xxx"
]
# Ignore patterns (similar to .lycheeignore)
ignore_paths = [
"https://api.github.com/repos/private/.*"
]
Then run with:
lychee --config lychee.toml docs/**/*.md --format json
tools
<!--VITE PLUS START--> # Using Vite+, the Unified Toolchain for the Web This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. Vite+ is distinct from Vite, but it invokes Vite through `vp dev` and `vp build`. ## Vite+ Workflow `vp` is a global binary that handles the full development lifecycle. Run `vp help` to pr
development
Guide for building performant data tables. Uses tanstack-table for table logic (sorting, filtering, pagination) and tanstack-virtual for rendering large datasets efficiently.
development
Expert guidance for building observable, expressive, and fault-tolerant TypeScript applications using the effect-ts/effect ecosystem. Covers Effect<A, E, R> type, error management, dependency injection via Layers, observability (logging, metrics, tracing), concurrency with Fibers, retry/scheduling, Schema validation, Streams, and Sinks.
tools
Complete E2E (end-to-end) and integration testing skill for TypeScript/NestJS projects using Jest, real infrastructure via Docker, and GWT pattern. ALWAYS use this skill when user needs to: **SETUP** - Initialize or configure E2E testing infrastructure: - Set up E2E testing for a new project - Configure docker-compose for testing (Kafka, PostgreSQL, MongoDB, Redis) - Create jest-e2e.config.ts or E2E Jest configuration - Set up test helpers for database, Kafka, or Redis - Configure .env.e2e environment variables - Create test/e2e directory structure **WRITE** - Create or add E2E/integration tests: - Write, create, add, or generate e2e tests or integration tests - Test API endpoints, workflows, or complete features end-to-end - Test with real databases, message brokers, or external services - Test Kafka consumers/producers, event-driven workflows - Working on any file ending in .e2e-spec.ts or in test/e2e/ directory - Use GWT (Given-When-Then) pattern for tests **REVIEW** - Audit or evaluate E2E tests: - Review existing E2E tests for quality - Check test isolation and cleanup patterns - Audit GWT pattern compliance - Evaluate assertion quality and specificity - Check for anti-patterns (multiple WHEN actions, conditional assertions) **RUN** - Execute or analyze E2E test results: - Run E2E tests - Start/stop Docker infrastructure for testing - Analyze E2E test results - Verify Docker services are healthy - Interpret test output and failures **DEBUG** - Fix failing or flaky E2E tests: - Fix failing E2E tests - Debug flaky tests or test isolation issues - Troubleshoot connection errors (database, Kafka, Redis) - Fix timeout issues or async operation failures - Diagnose race conditions or state leakage - Debug Kafka message consumption issues **OPTIMIZE** - Improve E2E test performance: - Speed up slow E2E tests - Optimize Docker infrastructure startup - Replace fixed waits with smart polling - Reduce beforeEach cleanup time - Improve test parallelization where safe Keywords: e2e, end-to-end, integration test, e2e-spec.ts, test/e2e, Jest, supertest, NestJS, Kafka, Redpanda, PostgreSQL, MongoDB, Redis, docker-compose, GWT pattern, Given-When-Then, real infrastructure, test isolation, flaky test, MSW, nock, waitForMessages, fix e2e, debug e2e, run e2e, review e2e, optimize e2e, setup e2e