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

whatibuilt

v0.1.6

Published

A wiki that writes itself while you vibe code. Explains your project in plain English, for people shipping code they can't read.

Readme

whatibuilt

A visual wiki that builds itself while you vibe code.

You keep working in the terminal. A Stop hook quietly reads each finished turn and maintains a record of the project on three levels. A local Next.js canvas renders it on your second monitor. The terminal never gets a wall of text.

Who this is for

Someone who has never written code professionally, building real software by directing an AI agent. It works — but they cannot read what the agent wrote, so they end up owning software they do not understand.

That audience is a hard constraint, not a nicety, and it decides how everything here is written:

  • Every card and every decision leads with plain language. The technical detail is behind a toggle, so you can stop reading at the point you understand.
  • Jargon gets defined on sight. "Hook", "schema", "endpoint" mean nothing until they do, and the glossary updates every single turn.
  • "A programmer would find this obvious" is not a reason to skip something. That reader is not who this is for.

The claim worth testing: this captures rationale at the moment of the decision — the thing a README written afterwards quietly loses, that version history never had, and that the raw transcript buries under 40,000 lines of noise.

Install

Two pieces: a plugin that records, and a canvas that renders. Neither needs a checkout of this repo.

1. Install the plugin — once, in any Claude Code session:

/plugin marketplace add kasperzhang/whatibuilt
/plugin install whatibuilt@whatibuilt

2. Arm it — in the project you want recorded:

/whatibuilt:learn

3. Open the canvas — in that same project directory:

npx whatibuilt --detach   # prints the URL and hands the prompt back
npx whatibuilt --stop     # when you are done

Use --detach when you are starting it from inside a coding session, which is almost always. In the foreground the server belongs to that turn: the shell suspends or kills it when the turn ends, and all you see afterwards is a page that will not load. Detached, it returns only once the canvas actually answers for this project, and prints the port it really bound.

Drop the flag if you want it in the foreground with Ctrl+C to stop it.

The canvas serves the zone of the directory you start it in, so run it in your project, not in a whatibuilt checkout. Put it on your second monitor and go back to work.

The hook is installed globally but stays inert everywhere you have not run /whatibuilt:learn — it exits immediately unless it finds .whatibuilt/enabled in the project you are coding in. Installing it does not opt every repo in.

Commands

/whatibuilt:learn                    # arm the extractor for this project
/whatibuilt:learn status
/whatibuilt:learn phase "Billing"    # force a phase boundary
/whatibuilt:learn backfill           # build a zone from transcripts you already have
/whatibuilt:learn doctor             # check the zone against the filesystem
/whatibuilt:learn map                # plain-language map of the codebase
/whatibuilt:learn polish             # rewrite text that lapsed into jargon
/whatibuilt:learn off

Canvas options

npx whatibuilt --port 5000
npx whatibuilt --zone ~/code/other-app/.whatibuilt

More than one project at once

Run npx whatibuilt in a second project while the first canvas is still up and it moves to the next free port and says so. Run it twice in the same project and it points at the canvas already serving that zone rather than starting a second one.

Both cases work by asking whoever holds the port who they are, via /api/whoami. A canvas serving a different project answers on the same port and is indistinguishable over plain HTTP, so "something responded on 4111" is never treated as "the canvas for this project is up" — that check is what sent an early tester to another project's wiki.

Occupancy is decided by connecting on every loopback address, not by binding one of them. localhost resolves to ::1 before 127.0.0.1 on macOS, and a server holding *:4111 does not stop anything else binding 127.0.0.1:4111 — so a bind probe reports free for a port the browser cannot reach, and you end up with two projects answering on one port number depending on which address the client picked. The server then binds whatever localhost resolves to, so the URL printed is the URL that works.

If you are ever unsure which project a canvas is showing:

curl -s localhost:4111/api/whoami

Working on whatibuilt itself

Only needed if you are changing this tool, not using it:

git clone https://github.com/kasperzhang/whatibuilt
cd whatibuilt
npm install
npm run dev            # http://localhost:4111, watching this repo's own zone
npm test
npm run build:dist     # assemble the publishable canvas into dist/

The repo records itself: .claude/settings.json wires the Stop hook to hooks/extract.mjs directly, so a checkout dogfoods without the plugin installed. The committed .whatibuilt/ zone is this project explaining its own construction — clone it and the canvas has something real in it before you write a line.

Backfill

If the project predates the hook, the zone starts empty while the history sits in Claude Code's transcripts. Replay it:

/whatibuilt:learn backfill

Or drive the script directly, from the project whose zone you are filling:

node "${CLAUDE_PLUGIN_ROOT}/hooks/backfill.mjs" \
  ~/.claude/projects/<project>/<session>.jsonl --dry-run

Drop --dry-run to execute. It feeds the extractor a growing prefix of the transcript under one session id, so the cursor advances exactly as it would have live — same phase decisions, made in order, without knowing what came next. Oversized turns are subdivided, which is the one place backfill diverges from the live hook: the digest cap exists to bound latency, and a one-time batch has no reason to pay it.

Budget one or two Haiku calls per chunk.

The three layers

The layers differ by write mode, and that difference is the whole design.

| Layer | File | Mode | Reader | | --- | --- | --- | --- | | Atlas | atlas.json | rewritten | a stranger, first | | Arc | arc.json | revised | anyone, for orientation | | Cards | stream.json | appended | you, later |

Atlas — what the project is, right now. It opens with the premise: what was broken before this existed, who it is for, and the core idea in one repeatable sentence. Then a glossary, then the decisions with their reasons and rejected alternatives, then the parts.

The premise came last and should have come first. Without it a reader could finish the atlas knowing the entire architecture and still not know what the project was for — engineering rationale answers "why is it built this way", and only the premise answers "why does it exist". If your atlas reads as a list of technical choices, that is the failure mode. Rewritten on revision, so a reversed decision is replaced rather than left standing next to its replacement. An append-only log cannot do this, which is why it exists.

Arc — the phases the project moved through. ScaffoldingAuth flowThe SSE reconnect bug. Five to fifteen entries for a whole project. Phase is not just a display grouping: it steers the extractor, because scaffolding turns produce architecture and bug hunts produce gotchas.

Cards — the individual things worth retaining. Filtered hard for novelty. Each leads with a plain-language line; the technical detail sits behind a toggle, so you can stop reading at the point you understand.

The glossary is maintained by the card call, not the atlas call. Jargon lands on this reader every turn, but the atlas rewrite is gated on structural change — so terms used to arrive only on the turns that happened to reshape the project. Folding them into a call that already runs costs nothing extra.

A fourth file, pulse.json, records one beat per armed turn whether or not it produced anything. Nothing displays it, and it is deliberately kept off the live feed so writing a beat cannot wake the browser. It survives because it is the only thing that separates "the hook ran and correctly found nothing" from "the hook never ran at all".

How it fits together

Claude Code turn ──Stop hook──▶ extract.mjs ──claude -p --model haiku──▶ phase + cards
                                     │                                        │
                                     │                       structural? ─────┤
                                     │                                        ▼
                                     │                            second call: atlas rewrite
                                     ▼  atomic writes
                     .whatibuilt/{atlas,arc,stream,pulse}.json
                                     │
                                     ▼  fs.watch + 2s poll
                            /api/stream (SSE) ──▶ Canvas

The files are the entire protocol. Neither side knows the other exists, so either can be restarted, rewritten, or simply not running.

What is deliberately not here

This is not generative UI. The model does not emit React. It picks a type from lib/schema.ts and fills props for a component that already exists. Cards that fail validation are dropped, not rendered — and a malformed atlas rewrite falls back to the previous one, because a bad atlas is worse than a stale one.

The extractor is not in your main loop. It runs as a separate claude -p process on Haiku, so extraction never spends your context or your latency.

Most turns produce nothing. The extractor's real job is the novelty filter — "is this new to this developer?" — not "is this a concept?". A routine rename emits zero cards. If the canvas fills with things you already know, tighten EXTRACT_SYSTEM in hooks/extract.mjs, not the UI.

The atlas is not novelty-filtered. Its reader knows nothing about the project, so it should state the obvious. Same data, opposite filters — that is why the two layers cannot be one layer.

The atlas call is gated. It carries the whole atlas as input and costs roughly twice the card extraction, so it runs only when a turn is flagged structural or a phase boundary opens. Most turns cost one Haiku call.

Layout

| Path | What it is | | --- | --- | | .claude-plugin/plugin.json | Plugin manifest. Namespaces the skill as /whatibuilt:learn. | | .claude-plugin/marketplace.json | Makes this repo installable with /plugin marketplace add. | | hooks/hooks.json | Wires the Stop hook when installed as a plugin. | | bin/whatibuilt.mjs | The npx whatibuilt launcher. Resolves the zone from your cwd. | | bin/build-dist.mjs | Folds static assets back into the standalone build. | | lib/schema.ts | The contract. Add a card type here first. | | hooks/extract.mjs | Stop hook. Sole writer of atlas.json and stream.json. | | hooks/phase.mjs | Manual phase boundary, used by /whatibuilt:learn phase. | | hooks/backfill.mjs | Replays an existing transcript through the extractor. | | hooks/doctor.mjs | Checks the zone against the filesystem. | | hooks/map.mjs | Parses the code's structure, describes each file. | | hooks/polish.mjs | Rewrites plain-language fields that lapsed into jargon. | | hooks/jargon.mjs | The shared word list, so check and repair agree. | | skills/learn/SKILL.md | The /whatibuilt:learn command. | | app/api/stream/route.ts | Watches the zone, pushes a full snapshot over SSE. | | app/api/phase/route.ts | Phase rename, from the spine. | | components/Canvas.tsx | Composes atlas → arc → cards, with scroll-spy. | | components/Atlas.tsx | The macro layer. | | components/Spine.tsx | The sidebar. Double-click a phase to rename it. | | components/CodeMap.tsx | The code map page and its diagrams. | | components/HowItWorks.tsx | Prose describing the extractor. | | lib/graph.ts | Turns the parsed map into diagrams. No model involved. | | tests/ | node:test suite. Excluded from the code map on purpose — see below. | | components/cards/ | One renderer per card type. |

Adding a card type

  1. Define it in lib/schema.ts and add it to the Card union.
  2. Write a renderer in components/cards/ and register it in Canvas.tsx.
  3. Describe its JSON shape in EXTRACT_SYSTEM in extract.mjs.
  4. Add its required-field check to validateCards() in the same file — every card needs a plain line, plus whatever its own kind cannot do without.

Skipping step 4 means malformed cards render as blank boxes instead of being dropped.

The code map

map.json is built in one pass over the source rather than turn by turn, and it is the clearest example of the rule the whole project runs on:

Structure is parsed. Only prose is generated.

Everything about shape — which files exist, what imports what, which files read and write which saved files, what nothing imports — comes from reading the source, so it is true by construction. The model is never asked what connects to what. It is handed a file that definitely exists and asked what happens inside it, in language someone who cannot read code can follow.

node hooks/map.mjs --dry-run   # what would be described
node hooks/map.mjs             # build it

Two details worth knowing:

  • Import edges are not enough. The four hooks are the heart of this project and nothing imports them — Claude Code starts them. Their real connection to the app runs through the saved files. So data-flow edges are parsed too, line by line, because a file that both reads and writes the zone must not be flattened into one direction.
  • Prose is cached by file content hash. A first run pays for every chosen file; later runs pay only for files that actually changed, which is usually one or none.

Tests

npm test          # everything, including a real server
npm run test:unit # just the fast ones

74 tests, no framework beyond node:test. What they cover, and why each exists:

| File | Guards | | --- | --- | | tests/schema.test.mjs | A malformed card is dropped without taking the good ones with it. | | tests/graph.test.mjs | Every diagram is well formed and no edge points at a node that was never declared. | | tests/extract.test.mjs | Digest truncation keeps the newest end; card and atlas validation refuse what they should. | | tests/map.test.mjs | Imports resolve, importedBy is the exact inverse of imports, and a saved file written through a variable is still found. | | tests/doctor.test.mjs | Runs the real script against synthetic zones — fabricated paths, two open phases, duplicate subjects, jargon, README drift. | | tests/hook.test.mjs | The guard rails: unarmed, recursing, locked, stale-locked, and model-unreachable all end without corrupting anything. | | tests/api.test.mjs | Starts a real server: the zone and map arrive as separate events, a pulse write pushes nothing, a half-written file never reaches the browser. |

The suite exists because writing it found four real bugs, listed below.

Bugs the tests found

  • Good cards were lost behind bad ones. Cards were capped at two before validation, so a turn returning two malformed cards and one good one kept nothing. Now capped after.
  • Path containment was prefix matching. abs.startsWith(root) accepts /tmp/proj-evil/x.ts when the project is /tmp/proj, so a fabricated module path pointing at a sibling folder was treated as real. Fixed in both the extractor and the doctor.
  • Clearing a stale lock consumed the only attempt. A lock left by a killed process always blocked the very next run too, and was only cleared by the one after that.
  • A card citing two files was reported as broken. The doctor tested the whole source string as a single path, so real files were reported missing.

Keeping the zone honest

The atlas is the layer a stranger trusts without reading the code, which makes a wrong entry in it worse than a missing one. Two things put wrong entries there: the extractor confabulates plausible file paths that were never written (a lib/novelty.ts for a filter that only ever lived in a prompt — never written), and real files get renamed out from under an atlas with no way to notice.

Module paths are now checked against the filesystem at write time — a path that does not resolve inside the repo never reaches atlas.json. For drift that happens later:

node hooks/doctor.mjs          # report
node hooks/doctor.mjs --fix    # repair what is safely repairable

It also enforces the arc's structural invariants: one open phase, no cards pointing at deleted phases, no duplicate slugs. A card's source is reported but never deleted — that file may have been legitimately renamed since, and the card's lesson survives its citation.

Deterministic, no model calls. Run it after any refactor.

When jargon creeps back

doctor can find jargon but not repair it — rewriting prose is not something a deterministic pass can do. polish closes that loop without giving the doctor a model:

node hooks/polish.mjs --dry-run   # which fields lapsed
node hooks/polish.mjs             # rewrite them

Both use the same word list, in hooks/jargon.mjs, so finding and fixing can never disagree. Polish rewrites sentences only, and discards any rewrite that did not actually remove jargon — the original at least came from a pass a human reviewed.

Concurrency and debugging

Two Claude sessions in one repo, or a live hook firing during a backfill, would otherwise interleave their read-modify-write of arc.json and silently lose a phase. The extractor takes an exclusive lock covering both the arc and the transcript cursor.

The two callers want opposite things from contention, so the wait is a knob:

| Caller | Behaviour | Why | | --- | --- | --- | | Live Stop hook | skips immediately | The next turn covers it. Burning the hook's timeout to skip anyway helps nobody. | | backfill.mjs | waits up to ~75s | A skipped chunk is lost history, not deferred history. |

A blocked writer touches nothing — no card, no beat, and the cursor does not advance, so the turn it skipped is still pending for whoever runs next.

| Variable | Default | Effect | | --- | --- | --- | | WHATIBUILT_DEBUG | unset | Write extractor decisions to stderr. | | WHATIBUILT_MODEL | haiku | Model used for both calls. | | WHATIBUILT_MAX_DIGEST | 12000 | Transcript characters per turn. Backfill raises it to 40000. | | WHATIBUILT_LOCK_WAIT | 1 | Lock acquisition attempts, 3s apart. | | WHATIBUILT_ZONE | ./.whatibuilt | Zone directory, for the hook and the canvas alike. |

The hook swallows every error by design — a broken extractor must never break a coding session — which also makes it invisible when it misbehaves. Set WHATIBUILT_DEBUG=1 to get the reasoning back.

What it will not record

A turn that names files, all of them inside .whatibuilt/, is treated as work on the tool rather than on your project, and is dropped before any model call: no card, no phase, no turn counted.

The rule only fires on positive evidence. A turn that names no files at all is always kept — an agent can build an entire application through Bash heredocs and leave no file path anywhere in the transcript, and an earlier version of this check threw away exactly that: a fourteen-minute build of a whole app, recorded as "nothing extracted; the work was routine".

Arming the zone, chasing a canvas that will not load, killing a stuck server — none of that is something to teach you about your own code, and recording it means the first card a new user reads is about installing whatibuilt. It also ages badly. The first such card written advised nohup, which was reasonable at the time and wrong within a day of --detach shipping.

The check is deterministic and never asks the model, so a turn that touched real code is never dropped on a judgement call.

Known rough edges

  • The KNOWN list is passed as slugs and grows unbounded. Past a few hundred cards, expect the novelty filter to start missing duplicates. The fix is embedding-based dedup, not a longer prompt.
  • Phase detection is the most fragile layer. Its first real test produced a one-phase arc across sixteen turns: the opening thesis was broad enough to contain the whole project, and a continue-bias then made it an absorbing state. The prompt now demands a narrow, completable thesis and applies pressure past six turns — but if you see phases stop turning over, that is the failure mode, and it looks like success until you count. Double-click any phase in the spine to rename it; use /whatibuilt:learn phase to force a boundary.
  • The atlas can drift on a bad rewrite. Validation catches structural damage — a rewrite that drops the whole module map is rejected — but not a subtly wrong claim.

License

MIT — see LICENSE.