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

@isonimus/stele

v0.4.1

Published

A linted, installable ADR workflow for git projects using Claude as an assistant

Readme

Stele

A zero-dependency workflow that keeps a codebase's decisions, work, and definition of done honest — enforced by a git hook and CI, not by anyone remembering to.

It is meant to be vendored into any git repo: one command drops in a decision-record tree, a worklist, a linter, and a pre-commit hook, and from then on the repo refuses commits that let its own records rot.

This README is a live doc — the how-to and the vision. The why behind every rule lives in adr/, and each section below links to the decision that governs it. Where the two ever disagree, the ADR wins; tell us so we can fix the README.


The problem

Conventions kept in a model's memory, a wiki, or someone's head hold until the first busy afternoon. They are invisible in review, unversioned, and gone on a change of machine. Three failures recur:

  • Decisions drift. A choice recorded as prose gets quietly edited until the record no longer says what was actually decided, and the reasoning that made it right is lost.
  • Work-in-progress lies. A doc written in the present tense ("the system does X") is true the day it is written and slowly false forever after.
  • "Done" is assumed, not defined. A feature with three required outputs and no written acceptance criteria ships with two of them, because the intent was never in a form that made the omission visible.

Stele's answer is a single principle: if a convention matters, it is executable; if it genuinely cannot be checked, that limit is stated out loud rather than trusted.


The mental model — four kinds of document

Every document is exactly one kind. There is no fifth. (ADR-0010, superseding ADR-0001)

| Kind | Files | Rule | |---|---|---| | Immutable | adr/*.md, slices/*.md | Written once. Body prose is never edited — only status/supersession fields change. | | Generated | adr/INDEX.md, and this repo's CHANGELOG.md | Built by script — from frontmatter, or from git tags. Never hand-edited. | | Mutable | LEDGER.md | Exactly one per repo. The only file maintained by hand. | | Live | README.md, docs/* | Describes how something behaves now; updated in the same change as the code it describes. |

  • An ADR records a decision later work must obey: "on date X we chose Y because Z" — a historical claim, true forever.
  • A slice is one feature work-unit, written before implementation and frozen to past tense at merge ("this is what shipped") — which converts a going-stale claim into a never-stale one.

Single writer, one direction. An ADR records a deferral once, as a fact. LEDGER.md cites the ADR. You never reach back into an ADR to close a ledger item — closing an item means deleting its line from the ledger. Changing your mind means a new ADR that supersedes the old one and says why the old reasoning was wrong — that record is the most valuable thing this workflow produces, and an in-place edit destroys it.

This one is enforced, not merely stated: a committed immutable document's body may gain lines — an appended ## Amendment, a correction marker placed at the claim it corrects — and may never lose or rewrite one (ADR-0019). Frontmatter stays free to change, because status and supersession are how a record announces it was superseded.


The three formats

Everything the linter enforces is one of these. They are small on purpose: a schema you can parse by hand is one you can also fix by hand at 6pm.

An ADR's frontmatter. Eight fields, all required, closed vocabularies for type and status (R1–R3):

---
id: '0007'
title: "Sessions are signed, not stored"
type: architecture          # architecture | slice | batch
status: accepted            # accepted | proposed | superseded | amended
date: 2026-03-14            # a real calendar date; 2026-02-30 is rejected
supersedes: []              # [0003] — and 0003 must point back (R4–R7)
superseded_by: []
---

# ADR-0007 — Sessions are signed, not stored

## Context
## Decision
## Consequences

A ledger line. One per item, and closing it means deleting the line (R8 checks the citation resolves):

- [bug] Token refresh races when two tabs renew at once; the loser gets a 401 and no retry.
  Reproduced on staging 2026-03-02 (ADR-0007).

type is bug | feature | deferred | audit.

A Definition of Done. Acceptance criteria as Given/When/Then, written before the code (R13 requires a full triad, R12 requires ## Verification alongside it):

## Definition of Done

- **Given** a session signed 25 hours ago
  **When** the client calls any authenticated endpoint
  **Then** the response is 401 and the body names `token_expired`

## Verification

`test/session-expiry.test.mjs`, plus `scripts/session-verify.mjs` for the clock-skew case a
unit test cannot assert — wired into `package.json`, or R11 fails the commit.

Quickstart

Install into a git repo — dry-run first, always (ADR-0006). Stele is delivered by npx, then vendored into the repo; it is never a runtime dependency (ADR-0015):

npx @isonimus/stele <repo-root>            # dry run: shows what it would do
npx @isonimus/stele <repo-root> --apply    # install the kit
npx @isonimus/stele <repo-root> --check    # verify an install is intact
npx @isonimus/stele <repo-root> --update   # re-sync vendored machinery

--update lints the corpus once the new machinery is in place and reports anything the updated linter calls red. A release that adds a rule can turn a green repo red behind a hook that is already installed, and the failure otherwise shows up as an unexplained blocked commit some time later (ADR-0021).

It also keeps any slash command this repo adapted. .claude/.stele-vendored.json records what the toolkit last handed over, which is what lets an update tell a stale copy from a deliberate edit — before that record existed the two were the same observation, so taking a fix meant discarding every adaptation (ADR-0023). Commit that file. --update --force discards adaptations, and has to be typed.

From a clone of this repo, the same entry point runs directly:

node scripts/init-method.mjs <repo-root> --apply

It installs CLAUDE.md, LEDGER.md, docs/quality-bar.md, the linter, the index builder, and a pre-commit hook — and refuses to install the hook on a linter-red corpus, because a hook that blocks every commit is the tool bricking the repo it was meant to protect. If a hook framework already owns the pre-commit slot, the doc checks join it rather than fight for the file (ADR-0008).

It also refuses, by name, when a path it manages is not a file it can read and write — a directory where CLAUDE.md should be, or a symlink that does not resolve. Nothing is written in that state, in any mode: the alternative was an EISDIR stack trace naming readFileSync, or, for an unresolvable link, silently writing through it.

The linter and slash commands are vendored per repo and a repo's local edits to the commands survive re-runs — including across an --update (ADR-0023, superseding ADR-0007).

docs/quality-bar.md rides the same rules. It is the standard a slice's ## Definition of Done is measured against — no any, fail loud, no magic values, the testing rule that a test derives from the spec and never from the code (ADR-0024), and the rule that where a well-tested library or standard already covers the need it is proposed by name before any bespoke design, with a departure argued from a property it would cost and a measured build cost (ADR-0025). Shipping the demand for a Definition of Done without the standard it is judged by left every consumer to supply the standard themselves. Adapt it freely: a repo whose stack has no any should cut that rule, and an --update keeps the cut.

Almost all of it is review-only and says so in the file. No linter here can tell whether a test was derived from a specification or from the implementation it tests; that is what the adversarial pass in /wrap-up is for, and claiming otherwise would be the failure this kit exists to remove. One subset is mechanical and the bar says so: a test asserting a constant it imported from the module under test is the assertion K === K, it passes whatever K is, and an AST linter can find it. Catching some instances of a defect beats catching none — the original claim that this was not worth checking was withdrawn by amendment after a wider count found six sites in one suite.


Commands

Run in Claude Code as /<name>.

| Command | What it does | |---|---| | /adr <title> | Scaffold a new Architecture Decision Record, frontmatter pre-filled. | | /slice <title> | Scaffold a new slice (one feature work-unit). | | /audit | Full-corpus health check — run every invariant, surface warnings and drift. | | /wrap-up | End-of-task gate — run the checks, run the mutation check and adversarial pass when they trigger, and ask the four questions that get forgotten. | | /remember <fact> | Route a fact to the destination that governs it (see Where things live). | | /init-method | Install the kit into a git repo. |

Under the hood, the npm scripts are the enforcement surface:

npm run lint       # node scripts/lint-docs.mjs .   — the invariant checker
npm run index      # regenerate adr/INDEX.md
npm run immutable  # immutable bodies only gained lines since HEAD
npm run mutants    # do the tests actually bite? (mutation check)
npm run changelog  # regenerate CHANGELOG.md from git tags (this repo only)
npm test           # the regression suite (every rule has a fixture)

Scenarios

Record a decision. /adr "Use a hand-rolled frontmatter parser". Fill Context / Decision / Consequences, cite measured numbers where a question has a measurable answer, and commit. Never edit it afterward.

A decision changed. Do not edit the old ADR. /adr a new one, set supersedes: [NNNN] on it and superseded_by on the old, and spend a paragraph on why the old reasoning was wrong. The linter enforces that the supersession is bidirectional and that a superseded ADR no longer reads as "accepted" (rules R4–R7).

Start a feature. /slice "CSV export". Before you write code, fill:

  • ## Goal — what ships and why;
  • ## Definition of Done — the acceptance criteria as Given / When / Then scenarios (ADR-0011);
  • ## Design — the approach, citing probe numbers not estimates;
  • ## Verification — the unit tests, or a scripts/<slice>-verify.mjs for behaviour a unit test can't assert.

At merge, freeze ## As built in past tense, confirming each Definition-of-Done scenario was met. The linter requires both ## Verification (R12) and a ## Definition of Done holding a real Given/When/Then triad (R13).

Behaviour a unit test can't assert (rendering, worldgen, physics, timing). Write scripts/<slice>-verify.mjs that drives the real system headlessly, fails on any console error, and writes artifacts (screenshots, numbers) for human review. Name it in ## Verification and wire it into package.json — an unwired verify script runs once and is dead thereafter, so the linter fails if any is unwired (R11). (ADR-0004)

Cite a decision. Write ADR-NNNN for one in this repo and <repo>:ADR-NNNN for one in another — a bare citation always means "here", which is what lets the linter treat a citation that doesn't resolve as a real defect rather than a maybe (ADR-0009). Text you expect to be copied into another repo — a template, a slash command — must use the qualified form even for your own decisions, or the copy silently names the other repo's decision of that number. Naming your own repo costs nothing: the linter resolves <this-repo>:ADR-NNNN locally, so those citations stay checked here and stay skipped downstream (ADR-0020).

Adopt into an existing repo. /init-method <repo> --apply. Migrate the corpus and hand- fix any red supersession pairs before the hook goes on. Legacy documents that predate a rule warn rather than error, so adoption is never blocked by history.

A rule you must break, with reason. Don't take a silent exception. Record the justification as a new or superseding ADR — in this workflow, a justified violation is a decision, and decisions are immutable records.


Enforcement, in three honest layers

The linter (scripts/lint-docs.mjs) runs from the pre-commit hook and in CI. The hook checks the commit, not the files on disk — it materialises the staged tree and runs the checks against that, so a fix you forgot to git add cannot green a commit that lands red (ADR-0018). Rules are graded by what can actually be mechanised (ADR-0003):

  1. Machine-checked (error — blocks the commit). Frontmatter shape, completeness and a real ISO date (R1), id/filename agreement and uniqueness (R2), closed status/type vocabulary (R3), the supersession graph (R4–R7), ledger citations resolve (R8), the linter isn't pointed at an empty corpus (R10), verify scripts are wired (R11), slices carry their required sections in their own prose — a heading quoted inside a code fence is not compliance (R12/R13) — and citations and links resolve in the prose read as instruction: CLAUDE.md, README.md, docs/, the slash commands (R14/R15, ADR-0020).

    Every rule reads inside one declared scope, which is exactly what the hook copies out of the staged tree — so a rule cannot pass in CI and do nothing in the hook, and a link check cannot depend on where it ran (ADR-0021).

  2. Legacy-aware (warning, not error). Bare prose cross-references (R9) and slice-section rules on documents that predate them warn instead of failing, so a repo's history never blocks its next commit — while new work is held to the full bar.

  3. Coverage — unenforceable, and said so. Whether a verify script tests something true, whether a §4 invariant actually holds, whether the acceptance scenarios are complete — none can be decided by reading one version of a file. These are surfaced by /wrap-up for a human read-through, never claimed as guaranteed. A linter that pretended to check them would be a false green, the exact failure this project exists to prevent.

    This layer has two instruments, and neither is a linter. The first is the mutation check (npm run mutants), which answers one narrow question mechanically: do the tests bite? It applies a curated list of small behaviour changes to the linter's pure predicates and requires each to break a test. A survivor is not a bug — it is correct behaviour nothing is watching, which a later refactor can reverse in silence. Measured on adoption: 3 of 10 mutants died, and the seven survivors were all correct-but-unpinned behaviour, now covered. It is deliberately not a framework — that would cost the zero-dependency property, and curating the list is the work anyway.

    Its limit is stated rather than discovered: it perturbs code and asks whether tests notice, so it is blind to an input nobody wrote and to a rule that was wrong from the start. None of the three defects this project has actually suffered would have been caught by it. It buys regression durability, not correctness.

    The second instrument is the one that covers exactly that gap: the adversarial pass (ADR-0017). Every coverage question has the form "is what you wrote sufficient?" and is addressed to the person who just wrote it — who already believes it is, which is why the code looks that way. So /wrap-up spawns a reviewer that is blind to intent but aware of law: it gets the diff, CLAUDE.md and the ADR index, never the rationale. Findings must state a concrete failure, get reproduced before they are acted on, and — when correctly rejected — get written at the code site (ADR-0012) so the next fresh reader does not raise them again.

The dividing line is the whole point: a rule is enforced, legacy-tolerated, or explicitly declared uncheckable — never silently trusted.


Where things live

/remember routes a fact to whatever governs it, never to wherever the conversation happened (ADR-0005):

| The fact governs… | Goes in | |---|---| | A codebase — invariants, architecture, definition-of-done | that repo's CLAUDE.md | | How you work, everywhere | global ~/.claude/CLAUDE.md | | Open work — deferrals, defects, follow-ups | that repo's LEDGER.md | | Operator-personal, cross-session facts (e.g. git identity) | assistant memory |

A rule that governs a codebase never belongs in assistant memory: memory is invisible to every other reader of the repo, unversioned, and lost on a change of machine.

One artifact moved off row two. General working practice — the quality bar — now ships with the method as docs/quality-bar.md rather than living only in a personal global file (ADR-0024). ADR-0005 rejected per-repo copies for having no reconciliation point; the vendoring record built in ADR-0023 is that point, so the rejection is answered rather than overruled. The table itself still governs everything else.


What this kit does not install

Stele publishes itself to npm, so it carries release machinery: annotated tags as release boundaries, a CHANGELOG.md generated from them, and docs/releasing.md for the order of the steps. None of it is installed into your repo, and no rule requires any of it. Nor are CONTRIBUTING.md, SECURITY.md, a code of conduct or a PR template scaffolded (ADR-0026).

Three reasons, and the third is the one that decides it. Most repos using this kit publish nothing, so a release convention there is ceremony against nobody. CLAUDE.md and LEDGER.md are scaffolded because the method reads and writes them — community-health files are load-bearing for nothing it does, and adding them would make this a repo scaffolder, which GitHub already ships and tests better than we would. And SECURITY.md is a promise — a disclosure address, a response window. A missing one says "no stated policy", which is true; a templated one says something false to whoever reads it in the moment they rely on it.


Design principles

  • Zero dependencies. The whole kit is Node's standard library. It drops into any repo regardless of package manager, and the frontmatter schema is small enough to parse by hand.
  • Enforce with a hook, remember nothing. A convention that matters becomes a rule; one that can't be checked is stated as such, out loud.
  • Grammar over toolchain. Given/When/Then is adopted as writing discipline, not a test framework — the verify scripts are the executable layer.
  • The diff is the audit trail. Immutable records, generated indexes, and single-writer ledgers mean the git log is the history. This repo's own CHANGELOG.md follows from that rather than sitting beside it: it is generated from annotated tags, never hand-written, so there is no second copy of the facts to drift (ADR-0026).

For the reasoning behind any of these, read the ADR it links to. That is what the ADRs are for.