The cross-layer updater for your agent arsenal. Skills, MCP servers, and plugins — one drift picture, one update path, one command to undo any of it.
$ driftcheck check
SKILLS 104 scanned (adopted 1, git 2, orphans 101)
[adopted] grilling up to date
[git] lieflat-charts 9 behind <- UPDATE
[orphan] 101 skills with no provenance
MCP floating 7 · outdated 0 · current 0 <- PIN
PLUGINS marketplaces refreshed 46d ago <- REFRESH
SUMMARY drift items: 10
resolve: driftcheck update undo: driftcheck restore latest
Every manager owns one layer and pretends the others don't exist:
| Tool | Skills | MCPs | Plugins | Orphan provenance | Rollback |
|---|---|---|---|---|---|
skills.sh / gh skill |
✅ | — | — | ❌ (creates orphans) | — |
| Smithery / mcpm | — | ✅ | — | — | — |
claude plugin update |
— | — | ✅ | — | — |
| driftcheck | ✅ | ✅ | ✅ | ✅ adopt |
✅ snapshots |
Most real setups have drift in all three layers at once — and copy-installed skills with no origin, so they can never be updated or audited. driftcheck is the one tool that sees the whole picture and never leaves you stranded.
- Reporters never touch your state.
checkis fully read-only (it does not evengit fetch: git skills are reported against already-local refs, honestly labelled).inventorywrites only driftcheck's own state file. - Mutators default to dry-run.
update,pin, andadoptshow a plan first;--writeis the explicit gate. - One batch, one snapshot, no exceptions. Every mutation batch snapshots every file, directory, and lock it may touch — before touching anything, including files the batch will create. If the snapshot fails, the batch aborts with nothing changed.
- One command undoes the whole batch.
driftcheck restore latest— or any past snapshot. Restore also deletes files the batch created, so "undo" really means back to before, not mostly back.
Python 3.9+, standard library only — zero dependencies, zero supply-chain surface.
git clone https://github.com/kinti/driftcheck.git && cd driftcheck
./driftcheck check # or: uvx --from . driftcheck check (once packaged)Publishing to PyPI (
uvx --from driftcheck driftcheck) is pending; run from a checkout until then.
| Command | Type | What it does |
|---|---|---|
check |
reporter | Full three-layer drift report, fully read-only. Exit 1 when drift exists (cron-friendly). --json available. |
inventory |
reporter* | Scan skill dirs, classify each (adopted / git-remote / git-local / orphan), write driftcheck's own state file. *Touches nothing outside ~/.config/driftcheck/. |
adopt <skill> <owner/repo> --subpath <p> [--write] |
mutator | Give an orphan skill provenance (git clones are rejected). Records the upstream HEAD at adoption time — not the historical install commit, which is unknowable; the first refresh after adoption may therefore bring changes older than your copy. Lock write is snapshotted. |
update [--write] |
mutator | The whole plan as ONE snapshotted batch: git pull git skills (which also refreshes their refs), replace adopted skills from their origin tarball at the exact target SHA (staging + swap — upstream deletions apply), pin/bump MCP servers in every config check reads, refresh plugin marketplaces (CLI refresh and our own clone pull — the CLI reports success without pulling). |
pin [--write] |
mutator | Pin floating npx/uvx servers to their latest version, in the config file each entry lives in. |
restore [snapshot] |
safety net | Put a whole batch back: changed files copied back, created files deleted. restore latest undoes the last batch. |
selftest |
— | Run the bundled offline unit tests (each one named after the promise it guards). |
- Skills:
~/.zcode/skills/,~/.agents/skills/(git repos, adopted entries, plain orphans). Parked dirs (skills.disabled, archives) are counted, not scanned. - MCP servers:
~/.claude.json(user + project scopes) and./.mcp.json—updaterewrites the exact file each entry came from. - Plugins: Claude-Code-style plugin/marketplace registries under
~/.zcode/cli/plugins/. - Network: npm/PyPI registry metadata, GitHub API + tarballs, git remotes. Nothing is sent anywhere; no telemetry, ever.
See docs/adr/ — cross-layer scope,
the never-breaks contract (includes the
hardening amendments from the first external review),
stdlib + uvx, and
adoption over re-cloning.
The project vocabulary lives in CONTEXT.md.
- PyPI packaging (
uvx driftcheck). - Multi-client MCP adapters (Codex, Cursor config formats).
- Snapshot housekeeping (
restore --prune). - Heuristic
adopt --scan(fingerprint orphan SKILL.md against GitHub search).
- Bugs and dishonest reports → open an issue.
If a mutator misbehaved,
driftcheck restore latestis the safest first move. - Ideas → feature request (mind the four rules in CONTRIBUTING.md).
- Talking it through → Discussions.
- Security → private vulnerability reporting; see SECURITY.md for the trust model (local trust, remote caution, zero dependencies).
- History → CHANGELOG.md and releases.
Validated against a real arsenal (104 skills, 102 orphans, 7 MCP servers, 46-day-stale
marketplaces), hardened by an adversarial two-axis external review (each finding
guarded by a named unit test), and then taken from zero through an old-school E2E
campaign (tests/e2e_campaign.py): 15 scenarios running the real CLI inside a
sandboxed HOME against a fabricated arsenal with real GitHub/npm traffic — covering
empty worlds, full censuses, byte-level dry-run purity (including .git, so no
hidden fetches survive), single-snapshot batches, whole-batch byte-identical
restores, the complete adopted lifecycle, every rejection path, abort-on-
snapshot-failure with zero mutations, corrupt inputs, offline degradation, and
execution under the system Python 3.9. The campaign itself caught and killed three
more real bugs before first publish.