Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Amiss and link checkers

Link checkers and Amiss solve different halves of one problem, and the halves compose. A link checker asks whether destinations are alive, most valuably the external ones: it fetches URLs, follows redirects, and reports the dead. Amiss asks whether the repository agrees with its own prose: it resolves every in-repository reference against an exact Git snapshot, compares two snapshots to see what moved under what, and gates the change that broke the agreement.

lychee is the strongest of the checkers and the one worth comparing against honestly. It is fast, async, and reads Markdown, HTML, and reStructuredText; it checks external URLs, which Amiss never fetches by design; it checks local file links, and with --include-fragments it verifies heading anchors, which Amiss also does, against ten pinned renderer rules. If your failure mode is dead links on a published site, lychee alone is the right tool, and nothing here argues otherwise.

The composition is literal rather than aspirational. Every external destination is recorded in the report after the format’s own decoding, the address a fetcher would request, and the external plan turns a written report into the delta a checker actually wants: the destinations this change introduced, not the whole corpus every run.

amiss check --repo . --object-format sha1 --base "$BASE" --candidate HEAD \
  --profile observe --format json > report.json
amiss external-plan --report report.json --format json |
  jq -r '.payload.introduced[].destination' | lychee -

Checking only what the change added is what keeps a checker tolerable in a gate: the author can fix a link they just wrote, and the pre-existing corpus rots on its own schedule instead of failing every pull request.

What a link checker cannot see is change. It examines one state of the world, so it can say a target is missing but not who removed it, whether it was missing before your pull request, or that a target still resolves while its content moved out from under the paragraph citing it. Those questions need two snapshots and exact comparison, and they are where Amiss lives:

Amisslychee
Checks external URLsnever fetches, lists them for youyes
Checks heading anchorsagainst ten pinned renderer ruleswith --include-fragments
Compares two snapshotsalwaysno
Attributes a finding to the changeintroduced, pre-existing, resolvedno
Reports changed content under unchanged proseyes, as advisoryno
Checks the staged index before commit--indexno
Policy can loosen the gatenever, loosening is itself a findingconfiguration is open
Byte-identical reports with digestsyesno

The smaller checkers sit on the same side of the line as lychee with less reach: markdown-link-check and linkinator examine files one state at a time from JavaScript, and mdbook-linkcheck is scoped to mdBook books. None of them compares snapshots.

For a repository with a published site the honest answer is both tools: lychee for the web, Amiss for the tree. For a repository whose documentation points mostly at itself, which is most repositories, Amiss covers the surface that actually breaks and notices the thing no checker looks for: the code moved and the prose did not. Every row of that report explains itself.

Last change: , commit: 515e6be0