Skip to main content

pushkin

Schema-first enforcement harness that gates AI coding agents' file writes against project contracts, deterministically. An agent write that violates a contract, touches protected surface, or edits a committed test suite is denied at the hook — with the reason, the fix hint, and an auditable event trail. Humans stay un-gated where it matters; everything fails open loudly, never silently.

Documentation map

document what it covers
DESIGN-FINDINGS.md canonical spec, the research it rests on, and the F-Ledger
docs/TESTING.md testing design and infrastructure — the layers, the red→green protocol, read-only suites, fixtures, the floor and its accounting, CI, and the evidence for why it is shaped this way
docs/TESTING-RUST-FOR-PYTHON-ENGINEERS.md the toolchain and test model taught from a Python baseline; start here if cargo and clippy are new
docs/RESEARCH-SOURCES.md every external figure this repo cites, with URLs and verification dates — and an honest list of the claims that still have no source
AGENT-INSTRUCTIONS.md builder conduct — the N-rules and the pass protocol
docs/STANDING-RULES.md rules that apply to every pass (V2, V3, V5, N10/§4.1)
docs/CONTINUATION-GUIDE.md area-by-area orientation for someone picking the repo up
docs/ROADMAP.md current state, open findings, what is queued
PHASE-LOG.md append-only record of every pass
PUSHKIN-AGENT-INTEGRATION.md per-agent hook mechanics for all five adapters

Install

cargo install pushkin

Or from source:

cargo install --path crates/pushkin-cli

No Rust toolchain? The same binary ships on PyPI as pushkin-cli (macOS and Linux wheels, nothing but the executable inside):

uv tool install pushkin-cli     # or: pipx install pushkin-cli / pip install pushkin-cli

Use the installed binary for hooks, not uvx pushkin-cliuvx resolves an environment on every call, which the warm-path latency gate does not budget for.

Requires the binary on PATH (hooks resolve pushkin from PATH by design — nothing bakes an absolute path). pushkin doctor names it if resolution would fail.

After any change to gate behaviour, reinstall. A binary older than the manifest schema fails every manifest-reading verb at exit 1 before any gate logic runs, and a hook exiting 1 is non-blocking — so version skew silently disables the write-time gate while doctor still reports healthy. Tracked as F71; see docs/ROADMAP.md.

Quick start

cd your-repo                  # carrying a pushkin.toml manifest
pushkin init                  # Claude Code hooks + versioned consent
pushkin init --agent git      # native pre-commit shim (no other tools needed)
pushkin doctor                # verify the install; --repair fixes what it owns
pushkin floor                 # run the repo's declared mechanical floor

Which manifest governs

Resolution is pinned to the repository, not to your shell's working directory:

  1. PUSHKIN_MANIFEST, if set — that file, wherever it is. If it names a file that cannot be read, pushkin fails with a named error; it never falls back to the repo's manifest behind your back.
  2. Otherwise <git rev-parse --show-toplevel>/pushkin.toml — the repository root's manifest, whatever directory you invoke from.
  3. Otherwise ./pushkin.toml, and pushkin says so on stderr.

Case 3 is the honest edge: where git is missing or the tree is not a repository, pushkin cannot tell whether a root exists, so it uses the working directory and tells you it did. In that situation a pushkin.toml in a subdirectory still governs — pushkin doctor names every nested manifest it finds, and carries a standing info line when git is unavailable.

Every verdict names the manifest it was decided under: repo-relative in the agent-facing prose, absolute in --json and in the pushkin floor header.

Adapters: --agent claude | codex | auggie | hermes | opencode, plus two pre-commit floors and a managed instructions block (--agent agents-md). --remove-agent <name> uninstalls only what pushkin owns.

The pre-commit floors

Two interchangeable floors run pushkin check --staged --json before every commit; both carry the same guard: fail open only on positively probed absence (no binary, or no pushkin.toml), with a loud actionable notice — a teammate who never installed pushkin is not blocked by a hook they never opted into, and a check that RUNS is never second-guessed.

  • pushkin init --agent lefthook — merges a marker-bracketed block into lefthook.yml, preserving every foreign command byte-for-byte. Any prior pushkin marker generation upgrades in place; uninstall leaves no litter.
  • pushkin init --agent git — writes .git/hooks/pre-commit directly, no hook-manager dependency. Detects core.hooksPath overrides and foreign hooks and prints the integration snippet instead of clobbering.

Team enforcement: CI is the backstop

The hooks above bind only machines that opted in. A teammate who never installed pushkin (or lefthook) commits ungated — the fail-open floor is deliberate, so local hooks alone cannot enforce the gate team-wide. The backstop is CI: run the gate as a required status check with the pushkin-gate composite action this repo ships (.github/actions/pushkin-gate), and unprovisioned pushes are caught at the pull request instead of landing silently.

name: pushkin-gate
on: [pull_request]
jobs:
  gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0            # affected mode needs history (F19)
      - uses: acoletti/pushkin/.github/actions/pushkin-gate@<TAG-OR-SHA>
        with:
          mode: affected            # or `check` for the full repo
          base-ref: origin/main
          pushkin-ref: <TAG-OR-SHA> # pin; a moving ref is non-reproducible

affected gates only the diff against base-ref; check sweeps every mapped file. Optional db-drift / db-rls inputs add the schema gates (see the action's own docs for their prerequisites). Make the job a required check on the default branch and local provisioning becomes a latency optimization, not the enforcement boundary.

What the gate enforces

  • Contract conformance at mapped boundaries (contract.boundary.*).
  • Protected paths (pushkin.protected_path): the manifest, schemas, CI, the floors — agent-denied at write time, unwaivable by construction. Staged protected changes draw a loud advisory at the floor, never a block (the floor runs for humans too).
  • Committed tests are read-only (pushkin.read_only_path): files in git HEAD under read_only_paths globs deny agent edits; NEW test files and uncommitted iteration flow freely. Unwaivable.
  • Suppression comments (pushkin.suppression.new) per the manifest.
  • Waivers (pushkin waive): scoped, expiring, decision-logged — for rules that allow them; doctor lints stale grants.

Read-only test surfaces are not a stylistic preference. Public measurement of frontier models finds test modification is one of four systematic cheating strategies, and that "Read-only access provides a middle ground: it restores legitimate performance while preventing test modification attempts" where hiding the tests degrades performance on the original benchmark (ImpossibleBench, arXiv:2510.20270). docs/TESTING.md §2 collects that evidence alongside this repo's own recorded incidents; docs/RESEARCH-SOURCES.md carries the full citations.

The mechanical floor

pushkin floor runs the [floor] table declared in pushkin.tomlone committed list of commands that pushkin floor, the Makefile, scripts/floor.sh and CI all read, so "CI runs the same checks you do" is a fact rather than a promise someone has to remember.

make floor                    # the canonical green — cite this, not a cargo count
pushkin floor --skip bench    # named omission; prints EXCLUDED, NOT a full floor

Three properties worth knowing:

  • Ignored tests are accounted for. A command declaring reconcile_ignored must have its ignored count matched exactly by a command declaring covers_ignored_of, or the floor is RED. Self-coverage is rejected at parse time, and a coverer excluded by --skip covers nothing. This exists because a cited floor number once silently omitted two #[ignore]d benchmarks — one of which was red at the time.
  • The exit contract is inverted relative to ESLint/ruff, and disclosed in --help: 0 clean, 2 findings, 1 the gate itself could not run.
  • Network-dependent verdicts are disclosed. inputs = "network" on a command (e.g. cargo deny check, which consults the RustSec advisory DB) prints a note that the verdict is not a pure function of the commit.

docs/TESTING.md §6–§7 documents the table, the accounting and the exit codes in full.

The schema-only tier (no JS runtime)

Gate-time enforcement is pure Rust. A repo that commits its canonical schemas needs no bun, node, or JS toolchain to be gated: install the binary (cargo install pushkin, or uv tool install pushkin-cli with no Rust toolchain either), run pushkin init, done. Bun is a compile-time tool only — pushkin compile regenerates canonical schemas and bindings from Zod sources, and repos that author schemas directly (or vendor them) never need it. If you never run compile, you never need a JS runtime.

Doctor

pushkin doctor verifies every installed surface (adapters, both floors, binding epochs) and exits 1 on findings; --repair regenerates only what pushkin owns — foreign configs are reported, never rewritten, and advisory findings (like an unresolvable binary) never trigger a rewrite. Repair exit code: 0 when every repair that could run succeeded; deferral messages name the manual remedy without failing the run.

Release status

pushkin is published on crates.io — cargo install pushkin installs the latest release. All four crates version in lockstep with exact internal pins (=0.2.1 at the current workspace version), and Cargo.lock is a release artifact: cargo install --locked reproduces the exact dependency graph a release was tested against.

Published versions are immutable. A correction to shipped content — including this README, which renders on the crate page from the published tarball — requires a new patch release; it cannot be fixed by editing develop.

Versioning policy:

  • Patch (0.1.x) — fixes that do not change emitted output: docs, CI, the gate action, internal refactors.
  • Minor (0.x.0) — new schema capability or changed emitter output. Any change under crates/pushkin-compiler/src/** is epoch-sensitive and bumps schema_epoch in pushkin.toml, so it is at least a minor.
  • Tags — every published version carries an annotated vX.Y.Z tag on the exact commit whose tree was published.

Contributing

Tests are written first, committed before the implementation, and read-only once committed. make floor must be green at every commit. The full protocol, the fixture patterns, and the reasoning are in docs/TESTING.md; if Rust is new to you, read docs/TESTING-RUST-FOR-PYTHON-ENGINEERS.md first.

Phase 0 reference implementation

The original Bun/TS spike (Claude Code gate) still lives in src/ and tests/: make test && make lint. The Rust workspace is the product; make floor runs its full declared mechanical floor — currently seven commands: fmt, clippy, test, bench, deny, tsc, bun-test.

Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

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

pushkin_cli-0.2.1-py3-none-musllinux_1_2_x86_64.whl (5.5 MB view details)

Uploaded Python 3musllinux: musl 1.2+ x86-64

pushkin_cli-0.2.1-py3-none-musllinux_1_2_aarch64.whl (5.1 MB view details)

Uploaded Python 3musllinux: musl 1.2+ ARM64

pushkin_cli-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (5.4 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

pushkin_cli-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (5.0 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

pushkin_cli-0.2.1-py3-none-macosx_11_0_arm64.whl (5.0 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

pushkin_cli-0.2.1-py3-none-macosx_10_12_x86_64.whl (5.2 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file pushkin_cli-0.2.1-py3-none-musllinux_1_2_x86_64.whl.

File metadata

  • Download URL: pushkin_cli-0.2.1-py3-none-musllinux_1_2_x86_64.whl
  • Upload date:
  • Size: 5.5 MB
  • Tags: Python 3, musllinux: musl 1.2+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pushkin_cli-0.2.1-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 c5ce6f65641dbe01b0670c8793d25118a8892363a628c9e9a617d0946304c415
MD5 637fd847cfd635b4d0a18b355be2b302
BLAKE2b-256 74f9abbadd52e8ce39fab31b5868349906da53b46f90b07d74afd927c539f666

See more details on using hashes here.

File details

Details for the file pushkin_cli-0.2.1-py3-none-musllinux_1_2_aarch64.whl.

File metadata

  • Download URL: pushkin_cli-0.2.1-py3-none-musllinux_1_2_aarch64.whl
  • Upload date:
  • Size: 5.1 MB
  • Tags: Python 3, musllinux: musl 1.2+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pushkin_cli-0.2.1-py3-none-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 3329523ac53b14a5e8fe7ca7fc8a8c312f23d29de299d49534ec129f17eeb45d
MD5 ea898c7bd72f6914df28c3b4c6fe9405
BLAKE2b-256 6e240d5d573c564b4abe09dc890f20e81d722dea1a87cbd86383379760b37351

See more details on using hashes here.

File details

Details for the file pushkin_cli-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: pushkin_cli-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 5.4 MB
  • Tags: Python 3, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pushkin_cli-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 5e27f6f39bb5f5b537752c382056e7e8500998e41a505eb7235d4e486eedeea6
MD5 1f1f6174f1999cb0869f95def5a1bbf2
BLAKE2b-256 18b4ee9b2aac95b30d5ab6a133737feca5377a7e7732be7c8aca8324cd2a2159

See more details on using hashes here.

File details

Details for the file pushkin_cli-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

  • Download URL: pushkin_cli-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
  • Upload date:
  • Size: 5.0 MB
  • Tags: Python 3, manylinux: glibc 2.17+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pushkin_cli-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 3bd6cb098389187c582c6ddf33986d682d431c3d8fea20d2336eda7ff4f5824e
MD5 2055517222f79bcf6c39f309ea0e290e
BLAKE2b-256 8ca41d1d6615d20aba981490ff23fb20fa68e52791a5b8691ae7ef3fc860f4d5

See more details on using hashes here.

File details

Details for the file pushkin_cli-0.2.1-py3-none-macosx_11_0_arm64.whl.

File metadata

  • Download URL: pushkin_cli-0.2.1-py3-none-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 5.0 MB
  • Tags: Python 3, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pushkin_cli-0.2.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 cbe96f706895ecb6c5ea2f4aca28a10a9f8a539f15c628d4291e2d833af860c2
MD5 b8d33d7f5d98d6dbcc9c8b2426b7c198
BLAKE2b-256 fdfc16151beaa68b8e7abf488244415f30f1f66ce9fa81ce80b6d73a7c0ce40c

See more details on using hashes here.

File details

Details for the file pushkin_cli-0.2.1-py3-none-macosx_10_12_x86_64.whl.

File metadata

  • Download URL: pushkin_cli-0.2.1-py3-none-macosx_10_12_x86_64.whl
  • Upload date:
  • Size: 5.2 MB
  • Tags: Python 3, macOS 10.12+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pushkin_cli-0.2.1-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 ee06dc4bc2cb0d769f3ea556fdd068d54e1e6085526cc087858cd2845f02ba09
MD5 02007bfd6fde5fc18b3de523509776f2
BLAKE2b-256 88a5accfae45561a93e322c3c98a4c9ed3714a0f29313f67f30657194cc55dfc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page