Metadata-Version: 2.4
Name: driftcheck-py
Version: 0.1.50
Summary: Detect version drift between docs and toolchain files (README vs Dockerfile, build.gradle, rust-toolchain.toml, package.json, etc.)
Author-email: Yunare Maia <yunare@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/yunaremaia/driftcheck
Keywords: drift,docs,version,consistency,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: packaging>=23.0
Requires-Dist: tomli>=2.0; python_version < "3.11"
Dynamic: license-file

# driftcheck
[![CI](https://github.com/yunaremaia/driftcheck/actions/workflows/ci.yml/badge.svg)](https://github.com/yunaremaia/driftcheck/actions)
[![Release](https://img.shields.io/github/v/release/yunaremaia/driftcheck)](https://github.com/yunaremaia/driftcheck/releases/latest)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
[![License](https://img.shields.io/github/license/yunaremaia/driftcheck) ![Stars](https://img.shields.io/github/stars/yunaremaia/driftcheck)](https://github.com/yunaremaia/driftcheck/blob/main/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.

```bash
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:

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

Or with SARIF upload for GitHub Code Scanning:

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

SARIF output includes an `originalUriBaseIds.repoRoot` `file://` URI and
repo-relative artifact paths tagged with `uriBaseId: "repoRoot"`, so code
scanning can resolve findings correctly when driftcheck runs from a subdirectory.

### Reusable Workflow (Zero-Config CI)

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

```yaml
# .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:

```yaml
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:

```bash
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:

```bash
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`:

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

### Configuration (`.driftcheck.toml`)

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

```toml
[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:

```bash
# 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):**

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

**GitHub Actions (PR comparison against main):**

```yaml
- 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:**

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

### Checks (v0.1.47):

**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)
- **Cargo features**: `Cargo.toml` `[features]` vs explicit `features = [...]` lists and `--features` flags in README
- **npm workspaces**: same dependency declared at different ranges across workspace `package.json` files
- **Node**: `package.json` `engines.node` vs README
- **Bun**: `package.json` `engines.bun` vs README — major.minor comparison
- **Package version**: `package.json` `version` vs explicit npm badge URLs, install commands, and changelog headers in README/docs
- **Python**: `pyproject.toml` `requires-python` vs README
- **Python tool targets**: `[tool.ruff]`, `[tool.black]`, `[tool.mypy]`, `[tool.pyright]` vs `requires-python`
- **Python (legacy setup)**: `setup.py` / `setup.cfg` `python_requires` and explicit `install_requires` pins vs README/CONTRIBUTING
- **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
- **Deno**: `deno.json` version pins vs README
- **Python Version**: `.python-version` vs `pyproject.toml` `requires-python` floor

**Package managers & lockfiles:**
- **Pipfile**: `Pipfile` vs `Pipfile.lock` version mismatches
- **Conda**: `environment.yml` unpinned packages
- **Gradle Version Catalog**: `libs.versions.toml` vs README
- **Kotlin Multiplatform (KMP)**: `gradle/libs.versions.toml` KMP catalog vs README badges (kotlin, coroutines, compose, AGP, KSP)
- **Lockfile**: missing, stale, or orphaned lockfiles (package-lock.json, yarn.lock, Cargo.lock, go.sum, Gemfile.lock, composer.lock, poetry.lock, uv.lock) (informational)
- **package-lock integrity**: package.json dependency ranges vs package-lock.json v1/v2/v3 resolved versions; flags missing direct dependencies and semver range violations
- **Nix**: `flake.lock` nixpkgs pins vs README mentions
- **Engines**: `package.json` `engines` field consistency across package managers
- **Requirements**: `requirements.txt` unpinned packages vs known latest

**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 Bases**: floating/unpinned Dockerfile base images
- **Docker Multistage**: multi-stage Dockerfile `FROM` consistency across stages
- **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
- **EditorConfig**: `.editorconfig` `indent_size`/`indent_style` vs project convention
- **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

**Security:**
- **Typosquat**: suspicious package names similar to popular packages
- **package-lock integrity**: blocking supply-chain check for package-lock resolutions outside declared package.json ranges

**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:

```python
# .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 that limits work to detectors relevant to
the files staged for the next commit:

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

The published hook uses `--changed-only`, which reads the staged git diff and
runs only relevant detectors. You can use the same mode directly:

```bash
driftcheck --changed-only
```

To print the hook manifest entry for tooling or inspection:

```bash
driftcheck pre-commit
```

Or install pre-commit locally:

```bash
pip install pre-commit
pre-commit install
```

### Stats

- **85 detector modules** covering 50+ toolchains and file formats
- **74 registered drift keys** (including split environment detectors, lockfile variants, plugin system, and A2A protocol drift detection)
- **1511 tests** with >95% code coverage
- **SARIF 2.1.0** output for GitHub Code Scanning
- **Plugin system** for custom detectors
- **Pre-commit hook** support

### Python API

See the [API Reference](docs/api.md) for embedding driftcheck in scripts and CI pipelines.

### 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 |
| Kotlin | kotlin-multiplatform | `kmp_drifts` | Kotlin Multiplatform (KMP) version catalog drift |
| 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) |
| AI Agents | a2a | `a2a_drifts` | A2A agent card spec_version vs docs, capabilities, endpoints (informational) |
| Documentation | changelog | `changelog_drifts` | CHANGELOG.md presence/content vs CONTRIBUTING.md policy (informational) |
| Terraform | terraform | `terraform_drifts` | Terraform versions.tf provider |
| Terraform | terraform-version | `terraform_version_drifts` | .terraform-version vs README |

## Sponsoring / Treasury

driftcheck is MIT licensed and maintained in the open. If it saves you time, you can
support continued development through GitHub Sponsors or the Solana treasury below.

Funding details are declared in [`.github/FUNDING.yml`](.github/FUNDING.yml), which is
what GitHub reads to render the **Sponsor** button on this repository.

- **GitHub Sponsors:** [@yunaremaia](https://github.com/sponsors/yunaremaia)
- **Solana:** `Eeztv1nCYUt1fwGWpzKC948gaWfjejYCAuLtUMgzDWbW`

Use the Solana address only for intended donations. Anyone can generate a similar
address, so verify the address against `.github/FUNDING.yml` before sending funds.

If this tool is useful to you, a star helps other people find it.

## Related tools

- **[taintrace](https://github.com/yunaremaia/taintrace)** — trace and inspect AI agent execution
- **[depscan](https://github.com/yunaremaia/depscan)** — scan dependencies across multiple ecosystems
- **[agentcost](https://github.com/yunaremaia/agentcost)** — track and attribute LLM spend per agent
- **[mcp-guard](https://github.com/yunaremaia/mcp-guard)** — audit MCP servers for unsafe permissions

Part of a family of focused, single-purpose developer tools — each one does one thing
and does it well.
