Summary
Add cross-doc anchor validation to CI via lychee (cross-file file.md#anchor resolution) plus markdownlint-cli2's MD051 rule (same-file anchor validation with GitHub-faithful slug semantics for edge cases like Unicode and emoji in headings). Today, broken cross-doc links are silent — a stale [See #523](ROADMAP.md#agent-integration) pointing at a renamed section ships unnoticed.
Why now
The repo has 30+ markdown files cross-referencing each other heavily (ROADMAP↔HISTORY↔CHANGELOG↔KNOWN_ISSUES↔SKILL↔CLAUDE↔AGENTS↔FAQ↔README all link to each other's section anchors). When a heading is renamed in one file, every link that points at it silently breaks. PR #518 surfaced one example of this; it'll happen again.
Proposed CI addition
# .github/workflows/ci.yml — new job, ~15 lines
- name: Validate markdown links
run: |
cargo install lychee --quiet
lychee --offline --include-fragments --cache --max-cache-age 1d \
--exclude 'github\.com/aallan/vera/(issues|pull|releases|tree|blob|compare|actions)' \
'./**/*.md'
Or pin to the official action lycheeverse/lychee-action@v2 for stability.
The --exclude pattern skips GitHub URLs that the offline mode can't resolve (we're not checking external link liveness; CodeRabbit and the typical PR-review eye catch those). The focus is cross-file file.md#anchor resolution, which is the silent-failure case.
For exact slug fidelity on edge cases (Unicode in headings, emoji, punctuation), pair with markdownlint-cli2's MD051 rule which faithfully reimplements GitHub's github-slugger algorithm but only for same-file anchors. Lychee's slug heuristic approximates GitHub but isn't byte-perfect for unusual headings.
- name: Validate same-file anchor slugs
uses: DavidAnson/markdownlint-cli2-action@v17
with:
config: '.markdownlint-cli2.jsonc'
globs: '**/*.md'
.markdownlint-cli2.jsonc enables only MD051 to start; can expand later.
Cost
- Setup: 15 minutes — add the CI step + the markdownlint config file
- Per-PR overhead: ~1 second (cached) once the cache warms; first run ~3 seconds
- No editing-workflow changes — pure additive gate
Estimated drift catch
Running locally on the current repo would likely surface 3-5 stale cross-doc anchors (estimate based on observed drift pattern; would tighten the number after first run).
Acceptance criteria
lychee --offline --include-fragments './**/*.md' exits 0
- New CI job runs on every PR and blocks merge on broken cross-doc anchors
- Documentation updated to note the new gate (TESTING.md "Validation scripts" section)
Source
Distilled from the doc-consolidation brief (Phase 4c) — see #528 for the full evaluation. Standalone change; doesn't depend on or require any of the brief's larger architectural shifts.
Summary
Add cross-doc anchor validation to CI via
lychee(cross-filefile.md#anchorresolution) plusmarkdownlint-cli2's MD051 rule (same-file anchor validation with GitHub-faithful slug semantics for edge cases like Unicode and emoji in headings). Today, broken cross-doc links are silent — a stale[See #523](ROADMAP.md#agent-integration)pointing at a renamed section ships unnoticed.Why now
The repo has 30+ markdown files cross-referencing each other heavily (ROADMAP↔HISTORY↔CHANGELOG↔KNOWN_ISSUES↔SKILL↔CLAUDE↔AGENTS↔FAQ↔README all link to each other's section anchors). When a heading is renamed in one file, every link that points at it silently breaks. PR #518 surfaced one example of this; it'll happen again.
Proposed CI addition
Or pin to the official action
lycheeverse/lychee-action@v2for stability.The
--excludepattern skips GitHub URLs that the offline mode can't resolve (we're not checking external link liveness; CodeRabbit and the typical PR-review eye catch those). The focus is cross-filefile.md#anchorresolution, which is the silent-failure case.For exact slug fidelity on edge cases (Unicode in headings, emoji, punctuation), pair with
markdownlint-cli2's MD051 rule which faithfully reimplements GitHub'sgithub-sluggeralgorithm but only for same-file anchors. Lychee's slug heuristic approximates GitHub but isn't byte-perfect for unusual headings..markdownlint-cli2.jsoncenables only MD051 to start; can expand later.Cost
Estimated drift catch
Running locally on the current repo would likely surface 3-5 stale cross-doc anchors (estimate based on observed drift pattern; would tighten the number after first run).
Acceptance criteria
lychee --offline --include-fragments './**/*.md'exits 0Source
Distilled from the doc-consolidation brief (Phase 4c) — see #528 for the full evaluation. Standalone change; doesn't depend on or require any of the brief's larger architectural shifts.