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

gro-nass

v0.5.0

Published

A change harness: scan, pre-flight, verify out of context, and write a scored ledger row. No database, no model.

Readme

gro-nass

A commit driver that refuses. It reads the message and the staged tree, runs the repository's own verification in a clean worktree somewhere else, writes a row either way, and only then commits.

The row is the product. A refusal is a row; a landing is a row and a stamp. Nothing is decided by parsing output — the exit code is the verdict.

The twenty-six rules, each with the refusal or the measurement that wrote it, are in RULES.md.

Install

From inside the repository you want to guard:

npx gro-nass init

That adds the package to your devDependencies, writes .githooks/commit-msg and .githooks/pre-push, sets core.hooksPath, and merges the rule set into AGENTS.md, CLAUDE.md and any vendor file your repository already uses. It prints every path it wrote. Run it twice and the second run reports unchanged.

The entry it writes into your manifest is the registry package at the version of the checkout that installed it — "gro-nass": "^0.3.9" — and never a filesystem path. npm, pnpm and yarn all resolve that anonymously, on any machine, with no credential. The git+ssh:// form it used to write is retired: a build machine has no ssh key for a private repository, and on 2026-09-19 that blocked a customer's deploys for 49 minutes. --from <path> still takes a local checkout for testing this package against itself, and says so in what it prints, because a local spec resolves on exactly one machine.

Your lockfile picks the installer. pnpm-lock.yaml → pnpm add -D, yarn.lock → yarn add -D, package-lock.json or no lockfile → npm install --save-dev. The chosen installer is printed. This is not a convenience: running npm in a pnpm repository writes a package-lock.json nobody asked for, and npm refuses peer-dependency trees that pnpm installs without complaint.

A failed install does not stop the gate going in. If your dependency tree has a peer conflict — yours, not this package's — init prints the installer's own message verbatim, writes the manifest entry anyway, and continues. The hooks resolve the driver through .harness/config.json, not through node_modules, so they work either way. A conflict in your tree is a finding about your tree, not a reason this tool cannot be installed.

Removal

npx gro-nass remove

One line out. It deletes the hooks it wrote, unsets core.hooksPath if it was the one that set it, takes its section back out of your rules files — deleting a file only when it created it — and removes the dependency. A hook without its marker is yours and is named rather than deleted; a file you wrote keeps every byte you wrote. .harness/ survives unless you pass --purge, because removing the tool is not removing the ledger.

That symmetry is a test, not a promise. test/init-remove.test.ts hashes every file in a throwaway repository, runs init, runs remove, and compares both the hashes and the file list. A byte changed or a file left behind fails the suite. Rule 21.

Developing against a checkout

git clone <remote> gro-nass && cd gro-nass
npm ci
git config core.hooksPath .githooks

The installed hook prefers node_modules/.bin/gro-nass and falls back to GRO_NASS_HOME or .harness/config.json for this case. If it can find neither it exits 2 and says so, rather than passing quietly — "could not look" is not "looked and it was clean".

Use

bin/gro-nass wrap --check-message MSG.md   # the five message checks alone: no worktree, no suite
bin/gro-nass wrap -F MSG.md --dry          # every check, verify included; commits nothing
bin/gro-nass wrap -F MSG.md                # …and commits if every check passes
bin/gro-nass wrap --stats                  # read the ledger for this repository
bin/gro-nass replay --last 20              # run the checks over history; writes its own file

--check-message is the fast half — pre-registration, binding, verification lines, stamps, paths — and it calls the same function the driver calls. It says in words what it did not run.

replay records and never refuses: every commit in history predates the rules, so enforcing them backwards would measure the date a rule was written, not the commits.

Exit codes are the interface: 0 allowed, 1 refused, 2 the command could not be understood.

Environment

| | | |---|---| | HARNESS_REPO | the repository to wrap (default: the current directory) | | HARNESS_AGENT | who is committing (default: unknown) |

Where the rows live

.harness/ledger.jsonl in the wrapped repository, append-only. A verdict row carries the tree, the staged files, the pre-registration and its binding, every check and every defect; a landing adds a separate one-line commit-stamp. replay writes .harness/replay.jsonl and never touches the ledger.

Scoring (paid tier)

gro-nass wrap --stats prints a calibration section per author when @winans/harness-scoring is installed (npm i @winans/harness-scoring), and the line scoring: not installed — paid tier when it is not. Nothing on the write path reads it: the gate's verdicts are identical either way.

Install the gate in your repository

From the repository you want guarded:

gro-nass install-hook

It also writes the agent-facing rules — AGENTS.md and CLAUDE.md always, plus .cursor/rules/harness.mdc and .github/copilot-instructions.md where that convention is already present — generated from RULES.md into a marked section, appended to any file that exists and never overwriting a line it did not write.

It writes .githooks/commit-msg there, sets core.hooksPath to .githooks, and records the path to this checkout in .harness/config.json (or set GRO_NASS_HOME). The hook forwards to this package's checker — the rule is never copied into your repo. A second run reports unchanged, and a commit-msg this tool did not write is refused rather than overwritten.

What leaves the machine

gro-nass report writes a bundle of claims: counts, verdicts, check names, defect classes, and each pre-registered item's probe, comparator, predicted value and whether it hit. It carries no commit subjects, no message bodies, no file paths, no defect surfaces, no evidence strings, no command output and no diffs — those fields have no name in the schema, so they cannot travel. The repository key and the developer are hashed with a salt kept in that repository's own config unless you pass --identify. A canary test seeds every free-text field in the ledger with a unique token and asserts none of them appears in the serialized bundle. The one honest limit: a value you type into your own pre-registration is your text, and it comes home with the claim.

When it leaves, and the two commands

Only if you set a home. A repository with no home posts nothing, ever. You set one at install:

gro-nass init --home https://example.invalid --token <token>

Both are stored in .harness/config.json. The token is not printed again — it travels in an Authorization header and appears in no output of this package.

After every landed commit, and never before one. The driver builds ONE row — the same whitelist as the bundle, the same function — POSTs it to <home>/api/claims, and prints a line:

✓ committed 3461731ed4
  row reported home

If the post fails for any reason the claim is appended to .harness/outbox.jsonl, the line reads row queued — <reason>, and the next commit drains the outbox oldest-first before sending its own row. A failed post never changes a verdict and never delays one past a three-second timeout. The commit is already made and the row is already on disk before any of this runs; a gate that refused commits because a server somewhere was down would not be worth keeping installed.

The two commands:

gro-nass report --show     # the exact bytes that would be sent, for the last row
gro-nass report --off      # stop sending; prints what it turned off. --on restores it

--show writes the body to stdout with no framing, and the suite asserts it is byte-identical to what the driver posts. Read it before you decide. --off keeps the URL and token so --on works without a new token; remove --purge deletes them with the rest of .harness/.