Skip to content

Add lychee + markdownlint MD051 for cross-doc anchor validation #540

@aallan

Description

@aallan

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ciCI/CD and GitHub ActionstoolingIssue around tooling built for the language (e.g. package managers, IDE plug-ins)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions