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

@khkim3115/revet

v0.1.6

Published

Guardrails for AI coding agents on legacy codebases

Readme

revet

Guardrails for AI coding agents on legacy codebases.

Two commands. revet hook gates what an agent is about to do, from declarative YAML rules. revet doctor reads your .claude/ directory and tells you whether the gates you think you have are actually running.

$ revet doctor

  revet doctor

  coverage        ########..   75
  enforcement     ###.......   33
  resilience      ##........   20
  permissions     ..........    0
  context         ####......   40

  F / 32        findings: 1 critical - 2 high - 2 medium

  [critical] resilience: Hook command is not resolvable -- every gate silently passes.
      fix: Install the runtime locally and point settings.json at node_modules/.bin.
  [high] enforcement: Gates are warn-only. A harness that never blocks is theater.
      fix: Promote at least the destructive-command gate to a blocking verdict.
  [high] permissions: Overly broad allow rule(s): Bash(python *)
      fix: Narrow the matcher to the specific commands you actually need.
  [medium] coverage: 1 lifecycle event(s) not wired.
      fix: Add the missing hooks to .claude/settings.json.
  [medium] context: CLAUDE.md is 222 lines (limit 200).
      fix: Move detail into @docs/*.md and keep the entry file an index.

That is examples/bad-harness in this repository. Run it yourself.

revet gates its own repository with its own core pack, and scores A / 90 against its own scanner. The single finding it reports on itself is that SessionStart and Stop are unwired, which is accurate: briefing hooks are a v0.2 feature.

Why this exists

Agent hooks get written per project, as shell scripts, from scratch. On a legacy codebase there are a lot of them -- forbidden syntax, encoding, destructive commands, layering -- so they get copied between repositories and then drift apart.

The worse problem is that a hook that has stopped working looks exactly like a hook that is working. This is measured, not asserted. Point a hook at a command that does not exist:

command: "revet-does-not-exist hook pre-bash"

-> hook exits 127, "command not found"
-> the tool call runs anyway, tool_result is_error: false
-> the agent reports: "No errors or extra markers."

Every gate passes. Nothing surfaces. From inside the session the harness looks healthy, and it will keep looking healthy for as long as you leave it that way. Full measurements are in docs/hook-contract.md.

Most published harness repositories assume a greenfield TypeScript project. Java 6, PHP 5, mixed encodings, no tests, no CI is an empty space.

The three-layer model

| layer | mechanism | what it can do | |---|---|---| | declare | CLAUDE.md / AGENTS.md | states intent. The agent may or may not follow it. | | guide | subagents, skills | shapes the approach before work starts. | | enforce | hooks | the only layer that can actually stop something. |

Most setups have the first layer, some have the second, and almost none can prove the third is running. revet doctor scores all three, weighted toward the one that can fail silently.

Install

npm install --save-dev @khkim3115/revet

The package is scoped; the command it installs is not. npm declined the unscoped name as too close to existing packages (ret, raven, leven and others), so the scope is where that goes rather than into the CLI you type.

Then in .claude/settings.json -- no hook scripts in your repository, the settings file calls the runtime directly:

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [{ "type": "command", "command": "node_modules/.bin/revet hook pre-bash" }] },
      { "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "node_modules/.bin/revet hook pre-edit" }] }
    ],
    "PostToolUse": [
      { "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "node_modules/.bin/revet hook post-edit" }] }
    ]
  }
}

Point it at node_modules/.bin, not npx. npx re-resolves the package on every single Bash call.

Then check it:

npx revet doctor

Honest scoring, and why your score often DROPS on the second run

Most audit tools inflate. This one is built to do the opposite, because a score that only ever goes up is a score nobody can act on.

A second run frequently scores lower than the first. Usually nothing got worse: the first run had not looked hard enough yet, and problems that were already there became visible. Reporting that as a regression teaches people to stop running the tool.

So revet doctor never reports a bare number:

  • score, plus finding counts by severity, plus the delta against a baseline
  • a score rise is never labelled improved while the finding count is rising
  • --baseline makes measure-fix-remeasure a first-class loop
revet doctor --baseline .revet/baseline.json
  B / 84        findings: 1 high - 1 medium
  vs baseline:  score -6,  findings +1  (surfaced, not regressed -- the drop is new findings, not new scoring)

surfaced stops deliberately short of claiming the new findings were latent. Score and counts cannot distinguish a newly detected problem from a newly introduced one. What they can establish is that the score fell because findings rose rather than because the scoring moved, and that is all the label claims. The findings themselves tell you which kind it was.

Writing rules

Rules are data, not code, so they can be read and reviewed by people who will never open the source:

pack: core
version: 1
rules:
  - id: core/destructive-rm
    event: pre-bash
    match:
      command: '\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)\b'
    verdict: block
    message: "Recursive force delete blocked."
    why: >
      settings.json permissions.deny cannot see runtime-composed commands
      (pipes, subshells, variable expansion, globbing). This is the second line.
    fix: "If intentional, run it outside the agent session."

why is the field that matters. With it, this is a tool that teaches; without it, it is a linter that nags. revet explain core/destructive-rm prints it.

| event | matcher keys | |---|---| | pre-bash | command (regex) | | pre-edit, post-edit | path (glob), content (regex), added (regex, added lines only) |

added is the important one. On a legacy codebase, matching content flags every pre-existing violation in the file and drowns the signal. Gate what is being added, not what is already there.

Select packs and override verdicts in .claude/revet.yaml:

packs: [core, legacy-php5]
overrides:
  core/destructive-rm: warn        # downgrade
  legacy-php5/short-ternary: off   # disable
custom:
  - id: local/no-todo-in-src
    event: post-edit
    match: { path: "src/**", added: "TODO" }
    verdict: warn
    message: "New TODO added under src/."

Overrides are a first-class feature, not an escape hatch. Every legacy project has a different local standard, and if downgrading one rule is hard, people turn the whole tool off instead.

Bundled packs: core (destructive commands, history rewrites, path string comparison) and legacy-php5 (PHP 5 syntax limits).

One honest limitation: command patterns match the command line as a whole, including text inside quoted arguments, so a command that merely mentions a recursive delete is blocked alongside one that performs it. That is the deliberate trade -- a matcher narrow enough never to false-positive is also narrow enough to miss a delete assembled from variables at runtime -- and it is why per-rule overrides are a first-class feature rather than an afterthought.

Design notes

The warn verdict is not an exit code. On PreToolUse there is no exit code that both lets the call through and shows the message to the agent: exit 0 and exit 1 discard stderr silently, exit 2 blocks. warn therefore rides the JSON additionalContext channel on stdout, and never emits permissionDecision -- emitting "allow" there would grant the call and override the user's own permission rules. A guardrail must not widen permissions as a side effect of warning about something. All of this is measured; see docs/hook-contract.md.

Fail closed. Any internal failure -- unparseable payload, unknown event, missing rule pack -- exits 2 rather than 0. A broken revet has to be louder than an absent one, because an absent one is silent.

Every gate is audited, not just revet's own. doctor resolves the actual executable behind each hook command, and follows node, python, sh and friends through to the script they were told to run -- resolving node proves nothing when the script beside it has been deleted. A hook pointing at a shell script that no longer exists dies exactly the way revet would, and it is far likelier to be the one nobody notices.

Never compare paths as strings. Separator and case differences make string path comparison silently wrong on some platforms, and a gate built on it can look active while never firing once. It is an internal invariant here and a rule in the core pack.

~5ms of overhead. pre-bash runs in front of every Bash call the agent makes. Rule packs are precompiled to JSON at build time, the YAML parser is never loaded unless a revet.yaml actually exists, and the bundle is a single dependency-free CommonJS file. Measured overhead above a bare node start is about 5ms, enforced by test/perf.bench.ts in CI.

No network, no telemetry, zero runtime dependencies. doctor reads other people's .claude/ directories, so it is offline, read-only, and never executes a hook.

Roadmap

  • revet init scaffolding
  • briefing hooks (SessionStart, Stop) -- context injection rather than gating
  • more packs: legacy-java6, vue-migration
  • a js: escape hatch for rules regex cannot express
  • a Go rewrite if cold start ever needs to go below the Node floor

Documentation

License

MIT