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

@zenginui/hook

v0.3.0

Published

Claude Code PostToolUse hook that checks every file write against the design system.

Readme

@zenginui/hook

Claude Code PostToolUse hook. Every time the agent writes or edits a file, the engine checks it and the violations go straight back to the agent as the blocking reason. This is the surface that catches the agent that never called MCP.

What it does

  1. Claude Code runs zengin-hook after Write, Edit, MultiEdit or Bash, with the tool payload on stdin.
  2. For a file-writing tool the hook finds the nearest zengin.config.yaml above the written file, reads the file from disk, and runs the engine on it. 2b. A Bash payload names no file, so the hook asks the working tree instead: git for what changed, and a content hash per file for what changed since it last looked. Without this an agent told to use sed for small edits leaves the loop entirely, and silently.
  3. Clean file: exit 0, no output, nothing in the transcript.
  4. Violations at or above --block-on (default error): exit 2 with the violations on stderr. Claude Code shows them to the agent as the reason the write was flagged, and the agent corrects.
  5. Violations below the threshold, or any violations with --block-on never: exit 0 with additionalContext JSON on stdout, so the agent sees them without being blocked.

On Edit and MultiEdit, only violations on the lines the edit touched are reported (--scope changed, the default). A one-line edit in a file with forty pre-existing problems is not blocked by the forty; the hook says how many it left out. --scope file reports everything. Write always reports the whole file, because the agent wrote all of it.

The hook never blocks for reasons unrelated to the design system: no config, a file outside scope, a non-code file, or an internal error all exit without blocking.

Install

pnpm build
npm link ./packages/hook      # or reference dist/index.js by absolute path

Print the settings fragment and merge it into .claude/settings.json (project) or ~/.claude/settings.json (user):

zengin-hook settings
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit|Bash",
        "hooks": [
          { "type": "command", "command": "npx --no-install zengin-hook", "timeout": 30, "statusMessage": "Checking against the design system..." }
        ]
      }
    ]
  }
}

The command goes through npx --no-install, and the whole command is one string. zengin-hook lives in node_modules/.bin, which is on PATH inside an npm script and nowhere else, and an agent launches the hook directly — so a bare zengin-hook fails to start and says nothing, which looks exactly like a project that never drifts. The hook schema has one command field and no args, so a command split across command and args runs node with the rest dropped, silently, the same way.

Working on Zengin itself, point it at the build in your checkout instead: "command": "node packages/hook/dist/index.js". Options go on the end of that same string.

This is what zengin create writes and what zengin doctor checks, from one definition in the engine.

Options

| Option | Values | Default | Effect | | --- | --- | --- | --- | | --config <path> | | nearest above the file | Which zengin.config.yaml to use. | | --block-on | error, warn, info, never | error | Lowest severity that blocks. never always reports as context. | | --scope | changed, file | changed | On Edit, report only touched lines or the whole file. | | --max <n> | | 50 | Maximum violations rendered. |

The three surfaces

| Surface | Fires | Bypassable | | --- | --- | --- | | MCP | when the agent asks | yes | | Hook | on every file write | not locally | | CLI and CI | on commit and pull request | no |

Same engine, same violation shape, same fixes. The hook and the MCP server render violations through the engine's shared formatter, so the agent sees identical text either way.

Tests

pnpm test

Tests copy the engine's plain-CSS fixture project to a temp directory, perform real writes and edits, and drive the hook in-process. The built binary is also exercised by hand from a foreign working directory before release.