Skip to main content

VersionDrift

Know which Git repositories are safe to advance—and which must not be touched.

Local-only observation. Fail-closed decisions. One deliberately narrow working-tree update: a verified git pull --ff-only for clean, behind-only repositories.

Release PyPI Python CI License: MIT

VersionDrift observes repositories locally, blocks dirty, ahead, diverged, unsupported, and unknown states, then reinspects, fast-forwards, and verifies only clean behind-only repositories.

Install

Requires Python 3.9+ and Git on PATH.

uv tool install version-drift
# or
pipx install version-drift

VersionDrift runs locally, sends no telemetry, and does not upload repository paths, remotes, or results.

The gate, in one minute

VersionDrift is a local-only gate that discovers repositories only below roots you provide. It inspects local Git facts, classifies drift, and keeps dirty, ambiguous, unsupported, or unknown states behind the gate. It is not a general multi-repository command runner.

# 1. Save roots (validates configuration; does not scan)
version-drift init ~/code ~/work

# 2. Observe
version-drift scan
version-drift inbox --fetch

# 3. Preview decisions (observation only)
version-drift sync ~/code --plan

# 4. Explicitly apply eligible fast-forwards
version-drift sync ~/code --apply

A sync --plan result is observation, never authorization. Plan evidence may be stale immediately. Apply fetches according to its own flags, takes a per-repository local lock, independently reinspects immediately before pulling, and verifies the result afterward.

Without --fetch, scan, inbox, and inspect compare existing local remote-tracking refs. sync fetches by default; choose explicitly with --fetch or disable it with --no-fetch.

Safety contract

What it does

  • Observes: bounded discovery under explicit/resolved roots; inspection of HEAD, upstream, relation, worktree, and required Git metadata.
  • Blocks by default: unknown or unreadable facts never become permission.
  • Allows one case: an attached, ordinary checkout that tracks an upstream, has a clean worktree, and is behind-only.
  • Applies one operation: exactly git pull --ff-only, after an immediate independent reinspection.
  • Verifies: pull success is not reported as applied until post-pull inspection confirms synchronized state.
  • Records locally: decision and lifecycle events support inbox, history, and diagnosis.

What it never does

  • reset, stash, or clean;
  • merge or rebase;
  • push or force operations;
  • guess an upstream;
  • execute arbitrary user-supplied Git commands;
  • treat a scan, dry run, plan, or lock as authorization.

Apply is blocked for dirty, ahead, diverged, missing-upstream, detached, in-progress, shallow, linked-worktree, submodule, and unreadable/unknown states. This includes submodule checkouts and repositories containing tracked submodule metadata. If the head, upstream, worktree fingerprint, eligibility, or required metadata changes before apply, the repository stays blocked.

Command map

All commands support the global --base-dir DIR; machine-readable commands offer --json.

  • init — save default roots, without scanning

    version-drift init ~/code ~/work [--json]
    

    Root precedence later is command-line roots, saved configuration, VERSION_DRIFT_ROOTS, then the current directory.

  • scan — bounded read-only checkup

    version-drift scan ~/code ~/work [--fetch] [--check] [--json]
    version-drift scan ~/code --max-depth 3
    

    --check exits 1 when drift is present. --root is a repeatable compatibility alias.

  • inbox — changes since the previous snapshot

    version-drift inbox [~/code] [--fetch] [--json]
    

    Reports states that are new, changed, or resolved. Snapshots are replaced atomically; corruption is preserved and fails closed rather than silently replacing the baseline.

  • history — newest-first local decision trail

    version-drift history
    version-drift history ~/code/project --event scan --limit 20 --json
    

    Read-only: never invokes Git, records events, updates snapshots, or creates missing state directories. Malformed JSONL lines are counted and skipped; an unreadable event file fails without modification.

  • inspect — inspect one repository

    version-drift inspect ~/code/project [--fetch] [--json]
    
  • explain — reasons and safe next actions

    version-drift explain ~/code/project [--json]
    

    Does not fetch, change Git state, record an event, or update the inbox snapshot.

  • sync — plan, preview, or apply the narrow gate

    version-drift sync ~/code --plan [--fetch | --no-fetch] [--json]
    version-drift sync ~/code               # dry-run preview
    version-drift sync ~/code --apply [--fetch | --no-fetch] [--json]
    

    Plan and dry run never update working files or local branches. Unless --no-fetch is supplied, they may refresh remote-tracking refs and tags; they also record local VersionDrift events. Apply uses a per-repository local lock, reinspects, runs only git pull --ff-only for eligible repositories, then verifies. Locks reduce duplicate local concurrency; they are not authorization.

  • doctor — read-only runtime and state diagnostics

    version-drift doctor [--json]
    

    Checks Python, Git, configuration, events, inbox snapshot, apply locks, and state-directory access. It reports issues but does not create, repair, truncate, or delete state. See the operations guide.

scan, inbox, explain, and sync also accept --max-depth N where applicable; the default discovery depth is 5.

Agent integration board (shipped MVP)

The integration board is a local coordination and observation surface for agents. Give every repository a stable, explicit --repository-id; use the same value for add, list, and board. No network access or fetch is required.

# Record an immutable request, pinning both refs to their current full commit OIDs.
version-drift integrate intent add ~/code/project \
  --repository-id project-1 --intent-id api-change --agent-id agent-api \
  --source refs/heads/agent/api --target refs/heads/main \
  --summary "Add the API endpoint"

# A dependent request; --depends-on is repeatable.
version-drift integrate intent add ~/code/project \
  --repository-id project-1 --intent-id ui-change --agent-id agent-ui \
  --source refs/heads/agent/ui --target refs/heads/main \
  --summary "Use the endpoint" --depends-on api-change

version-drift integrate intent list ~/code/project --repository-id project-1
version-drift integrate board ~/code/project \
  --repository-id project-1 --target refs/heads/main --json

intent add resolves source and target locally and writes only VersionDrift's external local intent state; an existing intent ID is never overwritten. intent list and board are strictly read-only: no branches are merged, no conflicts are resolved, and no Git refs, worktrees, or indexes are changed. The board reports a deterministic dependency order and stable reason codes. A ref moving away from its pinned OID makes an intent STALE; an unobservable ref or malformed store is UNKNOWN, and UNKNOWN = BLOCKED for policy purposes.

Board exit codes are exact: 0 for READY, 1 for BLOCKED or STALE, 2 for invalid CLI or repository arguments, and 3 for UNKNOWN (including an unobservable selected board ref or malformed state). Intent/list operational failures follow the general exit-code contract below.

This shipped MVP does not perform merge-tree analysis, propose or apply integrations, resolve conflicts, acquire leases, create sandboxes/worktrees, or invoke an LLM. Those are future ideas, not current capabilities.


Machine-readable contract

Frozen v1 schemas

The VersionDrift 1.x safety/report core freezes these schema identifiers and established fields:

  • inspection and event report: version-drift/1
  • scan envelope: version-drift/scan/1
  • sync envelope: version-drift/sync/1
  • plan envelope: version-drift/plan/1
  • doctor envelope: version-drift/doctor/1

Other command contracts are version-drift/config/1, version-drift/inbox/1, version-drift/explain/1, version-drift/history/1, version-drift/integration-intent/1, and version-drift/integration-board/1.

Legacy report fields are retained. Additive, orthogonal fields and new fail-closed reason/event values may appear in 1.x; consumers must ignore unknown fields and tolerate values that follow existing safety semantics. Fields are not removed, moved, renamed without retaining the legacy field, or given incompatible meaning during 1.x. Semantic breaks—including weaker apply checks—require a new major version and migration guidance. See the exact VersionDrift 1.x compatibility contract.

Outcomes

Scan, sync, and plan envelopes report:

  • complete — required observations/operations completed; repositories may still be policy-blocked.
  • partial — some observations/operations failed while others completed; for sync, at least one apply was verified.
  • failed — required observations/operations failed with no verified applicable success, as defined by the command.

Outcome is separate from repository relation and eligibility. Never infer authorization from complete.

Exit codes

  • 0 — command completed under its command-specific policy.
  • 1 — a reported non-operational condition or command failure, including scan --check drift, an unhealthy doctor, unsuccessful inspection, failed plan observation, or a single-repository sync policy block.
  • 2 — command-line usage or validation error, including argparse errors.
  • 3 — scan or sync operational partial or failed, including an uninspectable explicit scope, pull failure, or unverified pull outcome.

JSON consumers should use envelope outcome and per-repository reasons as well as the intentionally compressed process exit code.

State and privacy

Default state locations:

macOS: ~/Library/Application Support/VersionDrift/events.jsonl
Linux: ${XDG_STATE_HOME:-~/.local/state}/version-drift/events.jsonl

inbox_snapshot.json and locks/ live beside events.jsonl. Configuration lives at:

macOS: ~/Library/Application Support/VersionDrift/config.toml
Linux: ${XDG_CONFIG_HOME:-~/.config}/version-drift/config.toml

--base-dir and VERSION_DRIFT_DIR select an explicit state root; for compatibility, explicit roots use .version-drift/ beneath that root.

State can contain local paths, remote URLs, branches, and Git status facts. Treat it as private operational data, exclude it from public logs, and put no secrets in configuration. Event history is append-only during normal recording, but it is diagnostic—not tamper-evident. For recovery, held locks, and outcome_unknown, follow docs/OPERATIONS.md.

Boundaries and compatibility

VersionDrift is a local safety tool, not a security boundary, authorization system, sandbox, malware defense, transaction manager, or complete defense against data loss. It trusts the local OS, Python runtime, selected Git executable/configuration, and relevant filesystem behavior. Same-user writers and TOCTOU races remain possible; Git hooks, filters, helpers, remotes, and network behavior are not sandboxed. Keep independent repository backups.

Discovery stays below supplied/resolved roots and does not follow directory symlinks. Apply blocks unsupported topology rather than attempting to make it safe. Review the full threat model, compatibility contract, and operations guide before automation.

Development

git clone https://github.com/seojoonkim/version-drift.git
cd version-drift
python -m pip install -e .

Bug reports and focused pull requests are welcome. See CONTRIBUTING.md, the changelog, or open an issue.

VersionDrift is licensed under the MIT License.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

version_drift-1.1.1.tar.gz (75.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

version_drift-1.1.1-py3-none-any.whl (39.1 kB view details)

Uploaded Python 3

File details

Details for the file version_drift-1.1.1.tar.gz.

File metadata

  • Download URL: version_drift-1.1.1.tar.gz
  • Upload date:
  • Size: 75.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for version_drift-1.1.1.tar.gz
Algorithm Hash digest
SHA256 1027da7db6d68fffd8103c07781d74dd1efa18897b1c3c8bb625aefc5f7f6ef8
MD5 5ae5d9e9dfde08084f1637bea3d7509e
BLAKE2b-256 6f703362cf4670a684ccabf6434ee04970fc27f2f854a150321daeb2498ec627

See more details on using hashes here.

Provenance

The following attestation bundles were made for version_drift-1.1.1.tar.gz:

Publisher: release.yml on seojoonkim/version-drift

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file version_drift-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: version_drift-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 39.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for version_drift-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2c9894eb9145afc3d61fdd20fb57fb3ad17e550c7359bbde9b8c38fdbc615ff2
MD5 0ab8669128189ff8771e213b7859ff3f
BLAKE2b-256 6415f7a67130cd04219476bda8f575c455abd95504ffaee001f2c7ed299453a4

See more details on using hashes here.

Provenance

The following attestation bundles were made for version_drift-1.1.1-py3-none-any.whl:

Publisher: release.yml on seojoonkim/version-drift

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.1.1 This release

2 files

1.1.0

2 files

1.0.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page