Skip to content

clanker-lover/doc-drift

doc-drift

Crates.io License

Catch when your markdown docs drift from your Rust source code.

What It Does

doc-drift verifies that external markdown documentation (the kind in docs/modules/, docs/architecture/, etc.) accurately reflects Rust source code. It loads your project using rust-analyzer's semantic analysis, extracts every public item, attribute, dependency, struct field, and enum variant, then compares all of that against your markdown docs.

No existing tool does this. #![deny(missing_docs)] and rustdoc only check inline /// comments. doc-drift checks your .md files.

Why

External markdown docs are the first thing a new contributor reads and the first thing an AI assistant loads for context. When they drift from the code — missing items, stale references, undocumented dependencies — debugging and onboarding break silently. doc-drift makes that drift visible and CI-enforceable.

Installation

cargo install doc-drift

Quick Start

Create a doc-drift.toml in your project root:

[sources]
paths = ["src/"]
exclude = ["main.rs"]

[docs]
module_paths = ["docs/modules/"]

[mapping]
style = "flat"
separator = "-"

Run it:

doc-drift

Output:

FAIL src/memory/store/episodic.rs
  [missing-from-doc] public struct `EpisodicEntry` not found in doc
  [missing-attribute] derive `Serialize` on `EpisodicEntry` not documented
  [missing-dependency] dependency `crate::memory::store::types` not mentioned in doc

FAIL src/repair/orchestrator.rs
  [no-doc] no matching doc found (expected: repair-orchestrator.md)

Summary: 2 failed, 4 diagnostics

Exit code 0 if all checks pass, 1 if any fail, 2 on errors.

Eleven Checks

Check What It Catches
Forward Public item in code not mentioned in doc
Reverse Identifier in doc that doesn't exist in code (stale reference)
Dependencies use crate:: dependency not documented
Attributes Significant derive/macro attribute not documented
Sections Required heading missing from doc
Freshness Source file newer than its doc
Content Doc below minimum word count (stub detection)
Parameters Function parameter name or type not mentioned in doc
Return Types Custom return type not mentioned in doc
Variants Enum variant not mentioned in doc
Fields Struct field not mentioned in doc

The first seven checks verify items exist in docs. The last four (depth checks) verify docs describe what items do — their parameters, return values, variants, and fields.

How It Works

  1. Loads your Cargo project using ra_ap_* crates (rust-analyzer's published internals)
  2. Walks the module tree with full macro expansion (sees #[derive(...)] output, proc macro attributes)
  3. Extracts all items (pub and internal), struct fields, enum variants, module names
  4. Parses Cargo.toml for dependency crate names
  5. Parses markdown docs using pulldown-cmark (identifiers from inline code spans, headings, word count)
  6. Compares code facts against doc facts with intelligent filtering

The reverse check automatically knows about:

  • All items in your project (any visibility)
  • All struct fields and enum variants
  • All module names
  • All Cargo.toml dependency names (both hyphenated and underscored)
  • All public items from every dependency crate
  • 100+ Rust keywords, built-in types, and standard traits

No whitelist needed for standard Rust projects. Produces minimal false positives on typical Rust codebases with default configuration.

Configuration

Full configuration reference
[sources]
# Directories to scan for .rs files
paths = ["src/"]
# Filenames to skip
exclude = ["main.rs", "connection_tests.rs"]

[docs]
# Directories containing module docs
module_paths = ["docs/modules/"]
# Directories containing cluster/group docs
cluster_paths = ["docs/clusters/"]

[checks]
# Headings that must appear in every doc
required_sections = ["What It Does", "How It Works", "Dependencies"]
# Minimum word count (catches empty stubs)
min_word_count = 50
# Check if source is newer than doc
freshness = true
# Attributes to not report (e.g., test-only attributes)
excluded_attributes = ["cfg(test)"]
# Additional identifiers to skip in reverse check
identifier_whitelist = ["OLLAMA_URL"]

[checks.depth]
# Verify function parameters are documented
parameters = true
# Verify custom return types are documented
return_types = true
# Verify enum variants are documented
variants = true
# Verify struct fields are documented
fields = true

[mapping]
# "flat" = src/memory/store/episodic.rs -> memory-store-episodic.md
# "nested" = src/memory/store/episodic.rs -> memory/store/episodic.md
style = "flat"
# Separator for flat style
separator = "-"
# Drop N leading path segments (e.g., 1 drops cluster prefix:
# src/scheduling/heartbeat.rs -> heartbeat.md instead of scheduling-heartbeat.md)
strip_prefix = 0

CLI Options

doc-drift [OPTIONS] [PROJECT_ROOT]

Arguments:
  [PROJECT_ROOT]  Path to the Rust project (default: current directory)

Options:
  -c, --config <FILE>   Config file path (default: <PROJECT_ROOT>/doc-drift.toml)
  -f, --format <FMT>    Output format: human, json (default: human)
  -h, --help            Print help
  -V, --version         Print version

Use --format json for CI integration:

doc-drift --format json | jq '.summary'

Requirements

  • Stable Rust toolchain (no nightly required)
  • A Cargo project with a Cargo.toml

License

Licensed under either of

at your option.

About

Verify external markdown documentation against Rust source code

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages