npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

changesafe

v0.5.0

Published

Gate AI-proposed infrastructure changes from the terminal or CI: deterministic policies, honest exit codes, hashed receipts. The gate itself never calls a model.

Readme

changesafe (CLI)

Gate AI-proposed infrastructure changes from a terminal or a CI job.

The CLI runs the same deterministic engine as the ChangeSafe console.

The gate has no AI dependency. gate, verify, and scenario never call a model, so gating costs nothing, needs no credential, works offline, and cannot be influenced by a model. Two commands do call one — analyze (ask a model for a proposal, then gate it) and eval (measure a model) — and both require you to say so explicitly. Neither can change a verdict.

npm run build:cli                                   # build the bundled binary
node packages/cli/dist/changesafe.js gate --scenario scenarios/network/scenario-b-route-leak
  ChangeSafe gate · domain network · input inc-route-leak-4977
  proposal provenance: authored_red_team

  PASS   PATCH_SCHEMA           All operations are valid declarative patches
  BLOCK  MGMT_REACHABILITY      Change severs management reachability
         After applying the proposal to a sandboxed copy, the management
         origin "mgmt-01" can no longer reach: dist-fw-01 …
  BLOCK  PROTECTED_RESOURCE     Change removes or disables a protected resource
  …
  4 PASS · 1 WARN · 2 BLOCK   risk: CRITICAL

  BLOCKED — this change cannot be approved.

Terraform plans

terraform show -json tfplan > tfplan.json
changesafe gate --domain terraform --input tfplan.json --context pr-body.txt

The Terraform domain derives the proposal from the plan itself — the plan already states what will change — so --proposal is neither needed nor accepted. --context carries text that travelled with the change (a pull request body); it is scanned for injected instructions and never obeyed.

Its policy set differs from the network domain's, and says why: ROLLBACK_COMPLETE is replaced by REVERSIBILITY because a plan has no inverse operations to verify, and VERIFICATION_REQUIRED is skipped because plan JSON contains no verification plan — in this workflow the pull request review is that step.

Analysing and gating in one step

analyze asks a provider for a proposal and hands it straight to the same gate code path — same policies, same order, same exit codes. A model proposal gets no shortcut for having been produced locally.

export ANTHROPIC_API_KEY=...
changesafe analyze --scenario scenarios/network/scenario-b-route-leak --provider anthropic

A failed or unusable model call exits 2, never 0: an analysis that did not happen is a missing verdict, not a clean one. --capture writes a replay fixture stamped with the model that produced it and when — the schema refuses a captured claim without both, and refuses to let an authored fixture name a model at all.

eval runs the whole scenario suite against a model and reports what fraction of its answers were schema-valid, evidence-grounded, and blocked by the gate. It requires an explicit --provider because it spends real credit, and nothing in CI runs it.

It gates; it never approves

There is no --auto-approve, and there never will be. A clean run means no policy blocked this change — an input to a human decision, which in CI is the pull request review. Receipts written by the CLI record decision: "gate_only" or "blocked", never "approved".

Exit codes

| Code | Meaning | | --- | --- | | 0 | Evaluated; nothing blocking | | 1 | Evaluated; blocked | | 2 | Could not evaluate: bad usage, unreadable input, failed validation |

The gap between 1 and 2 is deliberate. A 1 is a verdict. A 2 is a missing verdict and must never be treated as approval.

Commands

changesafe gate --scenario <dir>              # or: --input <file> --proposal <file>
                [--domain network]
                [--policy-pack <file>]        # typed threshold overrides
                [--receipt <out.json>]        # write a hashed record
                [--format pretty|json]

changesafe analyze --scenario <dir>           # ask a model, then gate the answer
                [--provider openai|anthropic|ollama]
                [--model <id>]
                [--out <proposal.json>]       # the accepted proposal
                [--capture <fixture.json>]    # a provenance-stamped fixture
                [... every gate option above]

changesafe eval --provider <id>               # measure a model on the suite
                [--model <id>] [--dir scenarios] [--runs <n>]

changesafe verify <receipt.json>              # recompute hashes and signature
                [--input <file>] [--proposal <file>]   # and check it describes these
                [--public-key <file>]         # check who signed it
                [--skip-signature]            # integrity only, authorship unchecked

changesafe keygen [--out <path>] [--force]    # Ed25519 receipt signing key

changesafe ledger append <receipt.json>       # record a decision
changesafe ledger list                        # newest first
changesafe ledger verify                      # recompute the hash chain
                [--db <file>] [--source-id <id>] [--decision <kind>] [--limit <n>]

changesafe scenario check [dir]               # scenarios vs their expectations.json
changesafe scenario init <name>               # scaffold a scenario that passes its own check

--proposal accepts either a bare proposal or a replay fixture wrapping one; when it is a fixture, the declared provenance is carried into the receipt.

Policy packs

A pack tunes thresholds teams legitimately disagree about. It is deliberately not a policy language — no expressions, no interpreter — so a pack cannot make the gate unsound.

{
  "name": "strict-change-window",
  "blastRadius": { "warnAt": 1, "blockAbove": 2 },
  "verification": { "requirePrecondition": true, "requirePostcheck": true }
}

The decision ledger

CI writes a receipt per run; a ledger keeps them, and keeps them honest.

changesafe ledger append r.json --db decisions.db
changesafe ledger list   --db decisions.db
changesafe ledger verify --db decisions.db     # exits 1 on any break

Append-only is enforced by SQLite triggers, so UPDATE and DELETE abort even from raw SQL. A hash chain covers the case triggers cannot: someone who owns the file can drop a trigger and delete a row, and the remaining entries still look perfect — but verify recomputes the chain from genesis and reports the gap. Publish the printed chain head somewhere else and a rewrite of the whole file becomes visible too.

Storage is node:sqlite, so this adds no dependency and no native build. See @changesafe/ledger.

In CI

The bundle is committed, so a checkout is all it takes — there is nothing to install and nothing to build:

- run: node packages/cli/dist/changesafe.js gate --scenario ./change --receipt receipt.json
- uses: actions/upload-artifact@v4
  with:
    name: changesafe-receipt
    path: receipt.json

(The Action wraps exactly this. Working from a clone rather than a checkout of this repository? npm run build:cli regenerates the same file.)

A blocking finding fails the step. The receipt is the durable evidence of what the gate saw, verifiable later with changesafe verify.

Integrity, and then authorship

verify recomputes hashes from canonical serializations and reports whether a receipt still describes what it claims. That is integrity: it detects alteration. It says nothing about who produced the receipt, because anyone with the codebase can build an internally consistent one.

Signing closes that gap:

changesafe keygen --out ci-signing-key          # writes .pem (0600) and .pub.pem
changesafe gate --scenario ./change --receipt r.json --sign-key ci-signing-key.pem
changesafe verify r.json --public-key ci-signing-key.pub.pem

An Ed25519 signature covers the canonical receipt, hash included. The value over hashing alone: someone who edits a receipt can simply recompute receiptSha256, and the hash check will pass — the signature will not, because they cannot produce one without the private key.

Three rules make the result mean something:

  • The envelope carries no public key. Only a key fingerprint travels with the receipt. A verifier must obtain the real key out of band, so a receipt can never vouch for itself.
  • An unchecked signature is not a pass. Verifying a signed receipt without --public-key exits 2 — a missing verdict about authorship, not a clean one. Pass --skip-signature to check integrity alone and have the signature reported as unverified.
  • Someone else's valid signature fails. A receipt signed by a different key reports key_mismatch, never a near-miss.

The private key never leaves the machine that signs; keygen writes it mode 0600 and refuses to overwrite an existing key without --force, because replacing a key invalidates every receipt already signed with it.

License

MIT — see the repository root.

Kubernetes

changesafe kubernetes collect --namespace demo --out current.snapshot.json
changesafe gate --domain kubernetes --input current.snapshot.json --proposal proposed.yaml

Collection is the only networked command and is read-only. gate accepts offline JSON/YAML manifests and never applies them.