docrot-cli
v0.2.5
Published
Find the lies in your docs — verifies every code example, import, link and config in your markdown against the code that actually ships.
Maintainers
Readme
docrot
Your docs are lying. Find out where — before your users (and their AI agents) copy-paste the lie.
docrot verifies every code example, import, link and anchor in your markdown against the code that actually ships. Zero config. One command. CI-ready. Fast: FastAPI's 4,868 code blocks verify in ~9s; axios in ~4s.
npx docrot-cliReal output from running docrot on axios — it found a dead link that has been sitting in the README and a config example that isn't valid JavaScript. Docs rot happens to everyone.
Why
Code moves on. Docs don't. Every library accumulates:
- Phantom exports — the README says
import { retry } from 'yourlib', butretrywas renamed two majors ago. - Broken examples — a TypeScript signature pasted into a runtime snippet, a
}that never got closed, JSON with a trailing comma. - Dead links and anchors — the guide that moved, the heading that got reworded, the image that never got committed.
- Stale commands —
npm run docswhen the script is nowdocs:build, install instructions for the package's old name.
It used to cost you a confused user. Now it costs more: LLMs and coding agents read your docs and reproduce the lie at scale. Your README is training data and agent context. Broken examples in, broken code out — in thousands of codebases you'll never see.
docrot makes "the docs are verified" a CI guarantee, like tests.
What it checks
| Check | What it catches | Severity |
| --- | --- | --- |
| missing-export | Example imports a name your package doesn't export (verified against your real types) | error |
| bad-subpath | Example imports pkg/sub that your exports map doesn't expose | error |
| syntax | Code blocks that don't parse (JS/TS/JSX/TSX, and Python when an interpreter is available) | error |
| data | JSON/YAML blocks that don't parse | error |
| broken-link / missing-image | Relative links/images pointing at files that don't exist | error |
| missing-anchor | #fragments that match no heading (GitHub slug rules) | warning |
| missing-script | npm run x in docs, but no x in package.json scripts | error |
| install-name / unknown-bin | Install/npx commands using a lookalike of your package's real name | warning |
| unknown-import | Example imports a package that isn't a dependency (often fine) | info |
What it deliberately does NOT flag
False positives kill linters. docrot is engineered to stay quiet about intentional things:
- Partial examples — blocks with
...,…,<YOUR_KEY>,{{ templates }}are skipped, not failed. - Fragments — API-reference notation (
axios.get(url: string): Promise), bare config objects, class members and function types all parse in their documented shape. - Console output — stack traces and error output fenced as code are recognized and skipped.
- Changelogs and test trees — historical documents legitimately reference old APIs, and test fixtures are deliberately rotten; both excluded by default.
- Docs-site conventions — mkdocs include directives, JSON Lines fences, i18n trees that fall back to the default language, doctest sessions and IPython magics in Python blocks.
- Docs-site routes —
/guide/configstyle links and extensionless routes resolve like your static site generator would.
If docrot says it's broken, it's broken.
Install
# nothing to install — run it right now
npx docrot-cli
# or keep it in the project (the command is `docrot`)
npm install -D docrot-cliRequires Node.js 18.17+.
CI
GitHub Actions (using the bundled action):
name: docs
on: [push, pull_request]
jobs:
docrot:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: getdocrot/docrot@v0
with:
fail-on: errorFindings show up as inline annotations on the exact markdown lines. Or run it anywhere:
npx docrot-cli --reporter=github --fail-on=errorUsage
docrot [path] [options]
--fix repair mechanical rot (see below)
--dry-run with --fix: show changes without writing
--reporter <pretty|github|json> output format (default: pretty)
--fail-on <error|warning|never> exit non-zero threshold (default: error)
--only <checks> comma list of: examples,links,pkg
--exclude <glob> extra ignore globs (repeatable)
--include-changelogs also scan CHANGELOG-style files
-v, --verbose show informational notesFixing
docrot repairs the mechanical rot itself:
npx docrot-cli --fix # apply high-confidence fixes
npx docrot-cli --fix --dry-run # preview without writingWhat it fixes: dead anchors (rewritten to the single unambiguous nearest heading),
lookalike script/install/bin names (npm run build-wasm → build:wasm), docs-root
style links, and missing commas in examples — a comma is only inserted where it
provably makes the whole block parse. After fixing, docrot re-scans: any file that
somehow scans worse is reverted automatically. Conservative by design; the long
tail stays human.
Ignoring a block
Add a comment before the fence, or docrot-ignore to the fence meta:
<!-- docrot-ignore -->
```js
deliberately broken example
```Config
docrot.config.json (or a "docrot" key in package.json):
{
"exclude": ["examples/legacy/**"],
"includeChangelogs": false
}Use it from your coding agent (MCP)
docrot ships an MCP server, so agents get deterministic docs verification as a tool instead of guessing:
claude mcp add docrot -- npx -y -p docrot-cli docrot-mcpOr in any MCP client config:
{
"mcpServers": {
"docrot": {
"command": "npx",
"args": ["-y", "-p", "docrot-cli", "docrot-mcp"]
}
}
}Tools: scan_docs (graded JSON findings) and fix_docs (mechanical repairs —
previews by default, writes only with dryRun: false).
Programmatic API
import { scan, healthGrade } from 'docrot-cli';
const result = await scan('.');
console.log(healthGrade(result), result.stats.errors);How the export check works
docrot resolves your package's real entry point (exports map → types → source), then type-checks a synthetic module that imports every name your docs claim exists. Renamed API? docrot tells you the line in the README and suggests the closest current name. When your types can't fully resolve locally (deps not installed), findings are downgraded to warnings instead of accusing you falsely.
Roadmap
- Go / Rust / shell example verification
--fixfor the long tail: AI-assisted rewrites (bring your own key)- Execution mode: actually run examples in a sandbox
llms.txtverification — keep your AI-facing docs honest too- Verified-docs badge service
- Semantic checks: does
client.users.list()exist on the type?
Contributing
npm install && npm test. The test fixtures are a tiny package with deliberately rotten docs — add your false-positive case there first, then make it pass.
License
MIT
