Skip to content

Repository files navigation

driftcheck

CI PyPI License

Detect version drift between docs and toolchain files.

README.md says Rust 1.93.0 but rust-toolchain.toml pins 1.96.1? CONTRIBUTING.md says Node 18 but package.json engines says 24? go.mod says 1.23 but docs say 1.21? driftcheck catches it before your contributors hit a build failure.

pip install git+https://github.com/yunaremaia/driftcheck.git
driftcheck           # scan current repo
driftcheck --json    # machine-readable
driftcheck --fix     # auto-fix drifts in documentation files
driftcheck --sarif   # SARIF 2.1.0 output for GitHub Code Scanning
driftcheck --csv     # CSV output for spreadsheets/data pipelines
driftcheck --list-detectors  # show available detectors
driftcheck --only tool_versions_drifts  # run specific detectors
driftcheck --exclude nvmrc_drifts,lockfile_drifts  # exclude detectors
driftcheck --quiet   # only output drifts, suppress OK
driftcheck --no-informational  # skip informational drifts
driftcheck --version

GitHub Action

Add driftcheck to your CI with a single step:

- uses: yunaremaia/driftcheck@main
  with:
    fail-on-drift: true   # default
    args: "--no-informational"

Or with SARIF upload for GitHub Code Scanning:

- uses: yunaremaia/driftcheck@main
  with:
    sarif: true
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: driftcheck.sarif

Reusable Workflow (Zero-Config CI)

For the simplest setup, use driftcheck's reusable workflow β€” no YAML to write in your repo:

# .github/workflows/driftcheck.yml in YOUR repo
name: Driftcheck
on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  driftcheck:
    uses: yunaremaia/driftcheck/.github/workflows/driftcheck.yml@main
    with:
      fail-on-drift: true
      detectors: 'docker,compose,github-actions,node,python,rust'

With SARIF upload for GitHub Code Scanning alerts:

jobs:
  driftcheck:
    uses: yunaremaia/driftcheck/.github/workflows/driftcheck.yml@main
    with:
      fail-on-drift: true
      output-format: sarif
      sarif-upload: true
    permissions:
      contents: read
      security-events: write

CSV Export

Output drift findings as CSV β€” useful for spreadsheets, data pipelines, and CI artifact collection:

driftcheck --csv                     # CSV output to stdout
driftcheck --csv > drifts.csv        # save to file for CI artifact
driftcheck --csv --no-informational  # exclude informational drifts

Output columns: file, detector, doc_version, actual_version, severity, message.

Markdown Report

Generate a markdown summary for CI job summaries or PR comments:

driftcheck --report          # output markdown to stdout
driftcheck --report >> $GITHUB_STEP_SUMMARY  # post to GitHub Actions

The report includes a statistical summary at the top:

## driftcheck report

### πŸ“Š Summary

- **Total drifts:** 3
  - ❌ Blocking: 2
  - ℹ️  Informational: 1
- **Detectors fired:** 2
  - `node`: 2
  - `lockfile`: 1
- **Top files:**
  - `README.md`: 2 drift(s)

### ❌ Blocking drifts

**Node.js package.json engines vs README** (node_drifts):
- `README.md`: Node 18 β†’ should be 20

Initialize Config

Generate a starter .driftcheck.toml:

driftcheck --init            # creates .driftcheck.toml with examples

Configuration (.driftcheck.toml)

Place a .driftcheck.toml file in your repo root to customize detection:

[driftcheck]
# Exclude specific detectors (supports short names or drift keys)
exclude_detectors = ["lockfile", "nvmrc", "ci_os"]

# Treat informational drifts as blocking
fail_on_informational = false

# Custom doc paths β€” additional files to scan for version mentions
# Supports glob patterns (e.g., "docs/*.md")
doc_paths = ["docs/setup.md", "CHANGELOG.md"]

By default, informational drifts (such as missing lockfiles or orphaned configurations) are reported as non-blocking warnings and exit with code 0.

  • --fail-on-informational: Promotes informational drifts to blocking errors, causing driftcheck to exit with code 1. Useful in strict CI pipelines where you want to enforce rules like missing lockfiles or missing .gitattributes (* text=auto eol=lf).
  • --no-informational: Suppresses informational drifts from the output entirely.

You can also use CLI flags --only and --exclude to filter detectors at runtime.

Git-Mode (Incremental Scans)

For large repositories, scan only the detectors relevant to your latest changes:

# Only check files changed since the last commit
driftcheck --git-mode

# Compare against a specific base (e.g., main branch)
driftcheck --git-mode --git-base origin/main

This maps changed file paths to detector patterns (e.g., a package.json change runs only the Node.js and npm-related detectors) and skips the rest. Useful in CI where the full scan is overkill for a targeted PR.

CI/CD Integration

driftcheck is designed to run as a quality gate in CI pipelines. It exits with code 1 when blocking drifts are found, making it easy to fail a build or PR check.

Exit codes:

  • 0 β€” no blocking drifts (clean)
  • 1 β€” blocking drifts detected

GitHub Actions (full scan):

- name: Run driftcheck
  run: driftcheck --report >> $GITHUB_STEP_SUMMARY

GitHub Actions (PR comparison against main):

- uses: actions/checkout@v5
  with:
    fetch-depth: 0  # full history for git-base comparison

- name: Driftcheck PR
  run: |
    driftcheck --git-mode --git-base origin/main --report >> $GITHUB_STEP_SUMMARY

Comparing against a specific tag:

driftcheck --git-mode --git-base v1.0.0

Checks (v0.1.45):

Language runtimes:

  • Rust: rust-toolchain.toml channel and Cargo.toml rust-version vs README.md / docs/README*.md / CONTRIBUTING*.md β€” minor-aware (patch differences ignored)
  • Node: package.json engines.node vs README
  • Bun: package.json engines.bun vs README β€” major.minor comparison
  • Python: pyproject.toml requires-python vs README
  • Go: go.mod go directive vs README
  • PHP: composer.json require.php vs README β€” major.minor comparison
  • Ruby: Gemfile ruby "x.y.z" directive vs README β€” major.minor comparison
  • .NET/C#: *.csproj <TargetFramework> vs README β€” handles multi-targeting
  • Elixir: mix.exs elixir: version vs README
  • Kotlin: build.gradle.kts plugin version vs README
  • Swift: Package.swift swift-tools-version and dependency pins vs README
  • Dart/Flutter: pubspec.yaml environment.sdk constraint vs README

Package managers & lockfiles:

  • Pipfile: Pipfile vs Pipfile.lock version mismatches
  • Conda: environment.yml unpinned packages
  • Gradle Version Catalog: libs.versions.toml vs README
  • Lockfile: missing, stale, or orphaned lockfiles (package-lock.json, yarn.lock, Cargo.lock, go.sum, Gemfile.lock, composer.lock, poetry.lock, uv.lock) (informational)

CI/CD:

  • GitHub Actions: outdated uses: action@version β€” compares against known latest versions for 18 popular actions; detects deprecated Node 20 runtime
  • GitLab CI: .gitlab-ci.yml image tags vs README
  • CircleCI: .circleci/config.yml docker image tags vs README
  • Jenkins: Jenkinsfile tool versions (nodejs, python, docker.image) vs README
  • CI OS: deprecated GitHub Actions runners (ubuntu-18.04, macos-11, windows-2016)

Infrastructure:

  • Docker: Dockerfile FROM <image>:<tag> vs README
  • Docker Compose: docker-compose.yml/compose.yaml image tags vs README
  • Kubernetes: image tags in manifests vs README
  • Helm: Chart.yaml/values.yaml image tags vs README
  • Terraform: versions.tf required_providers block version vs README
  • Environment drift: .env.example vs .env, docker-compose.yml vs docker-compose.prod.yml, values.yaml vs values.prod.yaml

Build tools:

  • Bazel: .bazelversion, MODULE.bazel bazel_dep, and WORKSPACE.bazel http_archive pins vs README
  • Makefile: tool version variables (GCC_VERSION, CMAKE_VERSION, GO_VERSION, etc.)
  • CMake: CMakeLists.txt cmake_minimum_required version vs README
  • Maven: pom.xml java.version, maven.compiler.source, maven.compiler.target, release vs README
  • Java/Gradle: build.gradle sourceCompatibility, jvmTarget, JavaVersion.VERSION_* vs README

Configuration:

  • Tool versions: .tool-versions (asdf/mise) β€” detects drift for Node, Python, Go, Rust, Ruby, Java, PHP, .NET
  • Mise: mise.toml [tools] section vs README β€” supports string and dict specs (e.g., node = "22" or python = {version = "3.12"})
  • Version files: .ruby-version, .python-version, .node-version, .java-version, .terraform-version vs README
  • Devcontainer: .devcontainer/devcontainer.json image/tags vs README
  • Renovate: renovate.json configuration drift vs README
  • NVMRC: .nvmrc vs package.json engines.node (informational)
  • Dependabot: ecosystems used but not covered by .github/dependabot.yml (informational)
  • SARIF output: driftcheck --sarif generates SARIF 2.1.0 for GitHub Code Scanning

Other:

  • Line endings: missing * text=auto eol=lf in .gitattributes (informational)
  • External resources: third-party CDN dependencies that break offline rendering (informational)
  • Count: skills/ directory count vs README mentions of "N skills"
  • Plugins: custom drift detection via .driftcheck_plugins/ directory
  • NPMRC: .npmrc registry vs README mentions
  • Yarn RC: .yml Yarn version vs README mentions
  • PNPM workspace: pnpm-workspace.yaml packages vs package.json workspaces
  • Git Tag: latest git tag vs README version mentions β€” detects when README references a stale version
  • Pre-commit: .pre-commit-config.yaml rev: vs README mentions of pre-commit version

Plugins

driftcheck supports plugins for custom drift detection. Create a .driftcheck_plugins/ directory in your repo root and add Python files that define a register() function:

# .driftcheck_plugins/my_detector.py
import re

def register():
    return {"my_detector": find_my_drift}

MY_RE = re.compile(r'my_tool\s+(?P<ver>\d+\.\d+)')

def find_my_drift(root, docs):
    drifts = []
    for fname, content in docs.items():
        for m in MY_RE.finditer(content):
            drifts.append({
                "file": fname,
                "doc_version": m.group("ver"),
                "detail": f"my_tool {m.group('ver')} mentioned",
            })
    return drifts

Plugin results appear as plugin_<name>_drifts in JSON output and are printed in the CLI. Broken plugins are skipped with a warning β€” they won't crash driftcheck.

Pre-commit hook

driftcheck ships a pre-commit hook. Add to your .pre-commit-config.yaml:

repos:
  - repo: https://github.com/yunaremaia/driftcheck
    rev: v0.1.45
    hooks:
      - id: driftcheck
        args: ["--no-informational"]

Or use it locally:

pip install pre-commit
pre-commit install

Stats

  • 61 detector modules covering 50+ toolchains and file formats
  • 65 registered detectors (including split environment detectors, lockfile variants, and plugin system)
  • 1103 tests with >95% code coverage
  • SARIF 2.1.0 output for GitHub Code Scanning
  • Plugin system for custom detectors
  • Pre-commit hook support

Detector Comparison Matrix

Ecosystem Detector Drift Key Example Detected
.NET dotnet dotnet_drifts .NET csproj TargetFramework
C/C++ cmake cmake_drifts CMakeLists.txt cmake_minimum_required
C/C++ makefile makefile_drifts Makefile tool version pins (CC, CMAKE, GO)
CI/CD actions-outdated gh_actions_version_drifts GitHub Actions outdated versions
CI/CD ci-os ci_os_drifts Deprecated CI runner (e.g., ubuntu-20.04)
CI/CD circleci circleci_drifts CircleCI config.yml image
CI/CD gitlab gitlab_drifts GitLab CI image tag
CI/CD jenkins jenkins_drifts Jenkinsfile tool versions (nodejs, python, docker)
Docker compose dc_drifts Docker Compose image
Docker compose-override compose_override_drifts Docker Compose override file image drift
Docker docker docker_drifts Dockerfile FROM tag
Docker docker-bases docker_bases_drifts Floating/unpinned base images
Docker docker-multistage docker_multistage_drifts Multi-stage Dockerfile conflicting tags
Editor/IDE editorconfig editorconfig_drifts .editorconfig vs README/IDE indent
Editor/IDE vscode-ext vscode_ext_drifts VSCode extensions.json vs README recommendations
Elixir elixir elixir_drifts Elixir mix.exs version
Environment env env_drifts .env.example vs .env, compose overrides
Environment env-example env_example_drifts .env.example vs .env key drift
General count count_drifts Skills directory count vs README
General dart dart_drifts Dart pubspec.yaml SDK constraint
General deno deno_drifts Deno deno.json version field
General dependabot dependabot_drifts Dependabot coverage gaps (informational)
General external external_resource_drifts External CDN resources in HTML (informational)
General lineending lineending_drifts Missing .gitattributes line ending config
General lockfile lockfile_drifts Lockfile missing/stale/orphaned (informational)
General mise mise_drifts mise.toml tool versions
General nvmrc nvmrc_drifts .nvmrc vs package.json engines (informational)
General package-manager package_manager_drifts packageManager field vs lockfile
General swift swift_drifts Swift Package.swift version pins
General taskfile taskfile_drifts Taskfile.yml tool versions
General tool-versions tool_versions_drifts .tool-versions asdf/mise
Git git-tag git_tag_drifts Latest git tag vs README version mentions
Go go go_drifts Go go.mod directive
Gradle gradle java_drifts Gradle build.gradle sourceCompatibility
Gradle gradle-catalog gradle_catalog_drifts Gradle Version Catalog (libs.versions.toml)
Helm helm helm_drifts Helm Chart.yaml/values.yaml
Helm helm-values helm_values_drifts Helm values.yaml vs environment-specific values
Java java-version java_version_drifts .java-version vs README
Kotlin kotlin kotlin_drifts Kotlin build.gradle.kts plugin version
Kubernetes k8s k8s_drifts Kubernetes manifest image
Maven maven maven_drifts Maven pom.xml java.version
Node actions-node20 actions_drifts GitHub Actions Node 20 deprecation
Node bun bun_drifts Bun package.json engines.bun
Node node node_drifts Node.js package.json engines
Node node-version node_version_drifts .node-version vs README
Node npmrc npmrc_drifts .npmrc vs package.json settings
Node pnpm pnpm_workspace_drifts pnpm-workspace.yaml vs package.json workspaces
Node yarnrc yarnrc_drifts .yarnrc.yml Yarn version
PHP php php_drifts composer.json require.php
Python conda conda_drifts Conda environment.yml pinned versions
Python pipfile pipfile_drifts Pipfile vs Pipfile.lock version mismatches
Python poetry poetry_drifts Poetry pyproject.toml [tool.poetry] dependencies
Python python python_drifts Python pyproject.toml requires-python
Python python-version python_version_drifts .python-version vs README
Python requirements requirements_drifts requirements.txt package versions
Ruby ruby ruby_drifts Gemfile ruby directive
Ruby ruby-version ruby_version_drifts .ruby-version vs README
Rust rust-cargo rust_drifts Rust Cargo.toml rust-version
Rust rust-toolchain drifts Rust toolchain.toml channel
Security typosquat typosquat_drifts Typosquat detection in dependencies (informational)
Terraform terraform terraform_drifts Terraform versions.tf provider
Terraform terraform-version terraform_version_drifts .terraform-version vs README

About

Detect version drift between docs and toolchain files (README vs rust-toolchain.toml etc.)

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages