pushkin 0.1.1

Schema-first enforcement harness that gates AI coding agents' file writes against project contracts
pushkin-0.1.1 is not a library.

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.

Spec: DESIGN-FINDINGS.md · builder conduct: AGENT-INSTRUCTIONS.md · progress: PHASE-LOG.md · agent integration: PUSHKIN-AGENT-INTEGRATION.md.

Install

cargo install pushkin

Or from source:

cargo install --path crates/pushkin-cli

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.

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

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.

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, 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.1.0), 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.

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 mechanical floor (fmt, clippy, tests, deny).