linkcheck

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: LGPL-3.0 Imports: 10 Imported by: 0

README

linkcheck

Fails a build when a relative link in a repository's Markdown does not resolve.

$ linkcheck -root .
docs/design/ADR-011.md:42: broken link -> ../srd/SRD-999-missing.md
exit status 1

Install

go install github.com/dr-dobermann/linkcheck/cmd/linkcheck@latest

Why it exists

Documentation links rot silently. Nothing fails when a doc is renamed, so the breakage is discovered by a reader, months later, one link at a time. A single sweep of one repository turned up 78 dead cross-references accumulated across several refactors — including its README and its top-level architecture document.

It is a small Go program rather than an off-the-shelf checker because a CI gate should be pinned, offline and installable with go install. Alternatives add a non-Go toolchain plus a network dependency that reddens the gate for reasons unrelated to the change under test.

Use in CI

Do not install with @latest in a gate: a floating version changes what your build enforces without anything in your repository changing. Pin it, and make a missing binary fail loudly rather than skip:

LINKCHECK_VERSION := v0.1.1

# One-time per machine / CI image.
tools:
	go install github.com/dr-dobermann/linkcheck/cmd/linkcheck@$(LINKCHECK_VERSION)
.PHONY: tools

link-check:
	@command -v linkcheck >/dev/null 2>&1 || { 		echo "ERROR: 'linkcheck' not in PATH. Run 'make tools'."; exit 1; }
	linkcheck -root .
.PHONY: link-check

ci: link-check   # ... alongside your other gate steps

The guard matters more than it looks. Without it, a missing binary makes the step a silent no-op — the gate "passes" locally because the tool was never installed, and fails in CI where it was. That failure mode is why the check is worth wiring as a hard requirement rather than a best-effort convenience.

Run it blocking, not advisory. Dead links accumulate precisely because nothing fails; a warning nobody must act on is how the 78 got there.

What it checks

Every relative link target in every .md file under -root, including reference-style definitions ([id]: path). A target that does not resolve to an existing file is reported as file:line.

What it deliberately ignores

Both exclusions are requirements learned from real false positives, not conveniences:

  • Fenced and inline code. A Go generic signature such as `values.NewArray[T](vals…)` is indistinguishable from a Markdown link to a naive regex. One repository had eight such spans.
  • Absolute and protocol-relative URLs, mailto:, and same-document anchors. They rot for reasons outside the repository, and verifying them would make the gate depend on the network.

Targets are percent-decoded before resolution, or every correct link to a file whose name contains a space reports as broken. Bracketed targets ([text](<path with spaces>)) and titles ([text](path "title")) are handled.

Exit codes

Code Meaning
0 every relative link resolves
1 at least one link is broken (each printed as file:line)
2 usage error — an unreadable root, a bad flag

License

LGPL-3.0, the same as the project it was extracted from.

Documentation

Overview

Package linkcheck verifies that every relative Markdown link in a directory tree resolves to a file that exists.

It exists because nothing did: a single sweep of one repository turned up 78 dead cross-references that had accumulated across several refactors, including in both READMEs and the SAD, and two of its three causes — a retired document and a renamed ADR — are exactly what a checker catches for free.

It is deliberately a small Go package rather than an off-the-shelf tool: the repository's parity rule requires every CI tool to be pinned and installed by `make tools`, and adding a Node or Rust toolchain for this would also add a network dependency that makes the gate flaky for reasons unrelated to the change under test. Relative links only, offline, deterministic.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(args []string, stdout, stderr io.Writer) int

Run is the command's body with its I/O and exit code as values, so the tool's behavior — what it prints, and which of its three exit codes it chooses — is testable without spawning a process, and is measured by the coverage gate. 0 clean, 1 dead links, 2 unusable root.

Types

This section is empty.

Directories

Path Synopsis
cmd
linkcheck command
Command linkcheck verifies that every relative link in a repository's Markdown resolves to a file that exists.
Command linkcheck verifies that every relative link in a repository's Markdown resolves to a file that exists.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL