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 --versionAdd 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.sarifFor 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: writeOutput 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 driftsOutput columns: file, detector, doc_version, actual_version, severity, message.
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 ActionsThe 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
Generate a starter .driftcheck.toml:
driftcheck --init # creates .driftcheck.toml with examplesPlace 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, causingdriftcheckto exit with code1. 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.
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/mainThis 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.
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_SUMMARYGitHub 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_SUMMARYComparing against a specific tag:
driftcheck --git-mode --git-base v1.0.0Language runtimes:
- Rust:
rust-toolchain.tomlchannelandCargo.tomlrust-versionvsREADME.md/docs/README*.md/CONTRIBUTING*.mdβ minor-aware (patch differences ignored) - Node:
package.jsonengines.nodevs README - Bun:
package.jsonengines.bunvs README β major.minor comparison - Python:
pyproject.tomlrequires-pythonvs README - Go:
go.modgodirective vs README - PHP:
composer.jsonrequire.phpvs README β major.minor comparison - Ruby:
Gemfileruby "x.y.z"directive vs README β major.minor comparison - .NET/C#:
*.csproj<TargetFramework>vs README β handles multi-targeting - Elixir:
mix.exselixir:version vs README - Kotlin:
build.gradle.ktsplugin version vs README - Swift:
Package.swiftswift-tools-versionand dependency pins vs README - Dart/Flutter:
pubspec.yamlenvironment.sdkconstraint vs README
Package managers & lockfiles:
- Pipfile:
PipfilevsPipfile.lockversion mismatches - Conda:
environment.ymlunpinned packages - Gradle Version Catalog:
libs.versions.tomlvs 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.ymlimage tags vs README - CircleCI:
.circleci/config.ymldocker image tags vs README - Jenkins:
Jenkinsfiletool versions (nodejs,python,docker.image) vs README - CI OS: deprecated GitHub Actions runners (ubuntu-18.04, macos-11, windows-2016)
Infrastructure:
- Docker:
DockerfileFROM <image>:<tag>vs README - Docker Compose:
docker-compose.yml/compose.yamlimage tags vs README - Kubernetes: image tags in manifests vs README
- Helm:
Chart.yaml/values.yamlimage tags vs README - Terraform:
versions.tfrequired_providersblockversionvs README - Environment drift:
.env.examplevs.env,docker-compose.ymlvsdocker-compose.prod.yml,values.yamlvsvalues.prod.yaml
Build tools:
- Bazel:
.bazelversion,MODULE.bazelbazel_dep, andWORKSPACE.bazelhttp_archivepins vs README - Makefile: tool version variables (
GCC_VERSION,CMAKE_VERSION,GO_VERSION, etc.) - CMake:
CMakeLists.txtcmake_minimum_requiredversion vs README - Maven:
pom.xmljava.version,maven.compiler.source,maven.compiler.target,releasevs README - Java/Gradle:
build.gradlesourceCompatibility,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"orpython = {version = "3.12"}) - Version files:
.ruby-version,.python-version,.node-version,.java-version,.terraform-versionvs README - Devcontainer:
.devcontainer/devcontainer.jsonimage/tags vs README - Renovate:
renovate.jsonconfiguration drift vs README - NVMRC:
.nvmrcvspackage.jsonengines.node (informational) - Dependabot: ecosystems used but not covered by
.github/dependabot.yml(informational) - SARIF output:
driftcheck --sarifgenerates SARIF 2.1.0 for GitHub Code Scanning
Other:
- Line endings: missing
* text=auto eol=lfin.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:
.npmrcregistry vs README mentions - Yarn RC:
.ymlYarn version vs README mentions - PNPM workspace:
pnpm-workspace.yamlpackages vspackage.jsonworkspaces - Git Tag: latest git tag vs README version mentions β detects when README references a stale version
- Pre-commit:
.pre-commit-config.yamlrev:vs README mentions of pre-commit version
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 driftsPlugin 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.
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- 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
| 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 |