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

forma-arch

v0.12.0

Published

Forma — present your architecture instead of slides. An interactive, stack-agnostic C4 explorer (context → container → component → leaf) generated from your code and kept true to it by a deterministic drift check.

Downloads

2,781

Readme

Forma

npm version npm downloads license

Present your architecture instead of slides. Forma turns any codebase into an interactive, stack-agnostic C4 explorer — big-picture → drill-down to the leaf — generated from the code and kept true to it by a deterministic drift check. No more slide decks that lie the day after you draw them.

Forma is the companion to arbiter: arbiter governs the process, Forma shows the system and guarantees the picture matches reality.

▶ Live demonot this repo. It is haben, a private 53-package Go application, generated by forma at haben commit 4c9b6880: six domains over 53 packages, 193 edges read from real import blocks, and box text quoted from that repo's own feature matrix. Its five-checkpoint architecture timeline is governed by haben's current product boundary: the 63-node AS-IS baseline grows cumulatively to 66 nodes and 196 edges, while the final operational checkpoint honestly reports no architecture change. A tool proving itself on its own 28 nodes proves nothing you care about.

What you are looking at is checked, not asserted: at that commit node scripts/presentable.mjs exits 0 on the model — every level under 24 boxes, every box carrying prose rather than a file count, every level with more than one box drawing at least one arrow — and forma check exits 0 against haben's source at the same commit. The curation that turns 53 flat packages into six domains and maps the governed boundary to typed timeline patches is one human decision, checked in at docs/demo/curate.mjs with the commands to rebuild it.

The one thing the demo does not do is regenerate itself: haben is private, so Pages publishes a snapshot committed from a local run rather than cloning it in CI. Nothing automated will notice if it goes stale. Forma's own model lives in docs/architecture/ and is drift-checked on every push.

Why

A hand-drawn architecture diagram is stale the moment code changes. Forma walks your source for the real structure, infers relationships from cross-references to exported symbol names (heuristic, additive), and fails a check when the model and the code disagree. What you present is what actually exists.

Where the language declares its architecture, forma reads the declaration instead of guessing. On Go the container and the leaf are the package (any directory with a non-test .go, however deeply nested), _test.go files are not architecture, and every edge comes from an import block — so the direction is right by construction. Every other stack uses the heuristic above.

Install

npx forma-arch <command>        # or: npm i -D forma-arch

Commands

| Command | What it does | |---|---| | forma init | Seed docs/architecture/c4-topology.json from your source dirs (best-effort; then curate) | | forma gen | Walk src/ leaves + derive container edges from cross-references; fill box text from docstrings/READMEs; group flat containers into components → c4-model.json | | forma check | Deterministic drift check — fails if the model no longer matches the code | | forma doc | Project the arc42 scaffold (ARCHITECTURE.scaffold.md), or --attach <file> to inject a governed block into an existing doc | | forma serve | Open the live explorer at http://localhost:4173 | | forma verify | Refresh status from live GitHub issues through your gh CLI — the only networked command |

Box text comes from your docs. gen fills each box with the module's docstring (Python """…""", JS/TS leading block), else the directory README.md, else a mapped arc42 section — so the explorer shows meaning, not a list of symbols. On a flat directory of many foo_* files it also synthesizes a component layer, described from its children's docs (--no-cluster to disable; --cluster-min <n> = leaves before a container is clustered, default 8; --group-min <n> = files sharing a prefix before they become a component, default 3).

Above the leaf, your feature matrix outranks the code. A docstring is the right answer for one file and the wrong one for a whole container: a stakeholder does not ask which docstring the first file inside it has, they ask what that part of the product does for the user — and in a governed repo that sentence is already written in a capability table. forma init finds those tables and lists them under docSources; gen joins each row to the nodes its code references name, and quotes the row verbatim (descSource: "docmap"). Nothing is composed or paraphrased. A node named by more than three rows is not described by the matrix but merely touched by it, so it yields nothing and the code chain runs instead.

"docSources": ["docs/FEATURE_MATRIX.md",
               { "path": "docs/spec.md", "describe": "capability", "ref": "code_ref", "status": "status" }]

Column roles are detected by header name (capability/feature/description…, code_ref/path/module…, status/state) — name them explicitly when yours differ. Auto-detection additionally requires a status column, because "feature + file" is also the shape of a refactor plan and a task line does not belong in a stakeholder's box; a source you list by hand is trusted as written.

Progress can be generated, not only hand-written. Where those rows carry a status, gen derives the node's status2 and completion from them — one of two capabilities shipped is in-progress at 50%, and verify.source names the document and the tally. This is derived, so it is never trusted: forma check re-reads the document and fails if the committed model claims a number the document no longer supports, exactly as it re-walks src/ for structure. A node no document names stays unknown — the honest blank, not a made-up zero.

Programme state is curated, not guessed. Code shows what exists, never how far along it is. Drop a docs/architecture/c4-status.json (--status <path> to move it) and gen decorates nodes by id with status2, completion, statusWord, current, target, verify, issues — never func, which belongs to the docs. gen validates the form (ids resolve, fields known, enums and issue numbers well-shaped) and never the prose; forma check fails if the overlay decorates a node the model no longer has.

{ "nodes": { "engine": { "status2": "in-progress", "completion": 60, "statusWord": "v2 in progress",
  "current": "Live on ACA: RAG + citations. Hardening this week.",
  "target": "Multi-surface substrate with client-ready output.",
  "verify": { "source": "ADR-040 on main" }, "issues": ["#534"] } } }

The overlay is the authority: every field it sets wins over anything derived, and check stops re-deriving that field. You do not have to hand-edit that JSON — forma gen --status-apply <file> merges {"nodes":{"<id>":{…}}} into it, validating every patch before it touches disk, so a rejected fill leaves the committed overlay untouched. It is the state counterpart of --enrich-apply, for the fields no document can supply. To describe one box by hand without a table, put the sentence in descriptions, keyed "<containerId>/<node-name-without-extension>""core/alpha" for the leaf alpha.js in container core, "core/core" for the container itself, since a container is its own container. It outranks every other source.

Future architecture can be a governed timeline. Add an optional timeline to the curated c4-topology.json when one binary target is not enough. The generated nodes and edges remain the code-verified AS-IS; each checkpoint applies a compact typed patch over every checkpoint before it. Forma never stores a second complete graph, never reads a board count as architecture, and validates every cumulative state before replacing the last model.

{
  "timeline": {
    "source": "docs/architecture/FUTURE.md",
    "checkpoints": [{
      "id": "g1", "label": "G1 · UAT", "badge": "9 board · 1 P0",
      "patch": {
        "nodes": {
          "add": [{ "node": {
            "id": "mcp", "level": "container", "parent": "system",
            "kind": "container", "name": "MCP channel", "status": "planned"
          }, "change": "Add the MCP surface." }],
          "update": [{ "id": "engine", "set": {
            "current": "Serves HTTP and MCP through one core."
          }, "change": "Extend the existing engine." }]
        },
        "edges": {
          "add": [{ "edge": {
            "from": "mcp", "to": "engine", "label": "delegates",
            "estatus": "to-build"
          }, "change": "Connect the new surface to the engine." }]
        }
      }
    }]
  }
}

Node patches support add, non-structural update, and child-first remove; edge patches support add, exact-match rewire, and remove. Every operation carries governed change prose; a live node changed locally exposes it as “Change from previous”, while the graph accents changed nodes and relations without keeping a second change log or drawing ghosts for removals. IDs, parents, levels and kinds cannot be edited in place, ambiguous edge selectors fail, and a node cannot disappear while children or incident relations survive. The source path must exist. AS-IS is implicit and reserved; the last checkpoint is the target, so target is forbidden inside timeline patches. With a timeline the viewer replaces CURRENT/TARGET with the checkpoints, supports ?checkpoint=g1, accents only the local patch, and keeps drill, layout, re-verification and export working on the cumulative graph. A future checkpoint with no local patch says “no architecture changes”; its display-only badge is not interpreted. Without a timeline, the legacy controls and model are unchanged.

Curated state, verified against reality. forma verify asks your gh CLI for the state of every issue the model references (--gh-repo owner/repo, or meta.ghRepo in the topology), marks the nodes whose issues are closed as done, and prefixes their current with dated evidence. It touches state, never structure, and re-running it never stacks the evidence. It is opt-in and separate on purpose: gen and check never open a socket. In the served viewer, RE-VERIFY re-reads the model without losing your level, layout or mode.

One source of truth. forma doc --attach docs/architecture/arc42.md injects the generated diagrams/tables between <!-- forma:begin --> / <!-- forma:end --> markers in your existing doc; your prose lives outside them, and forma check fails if that block drifts. Attached files are recorded in source.attachedDocs, so the gate governs every doc you attach — not just the model's docPath; deleting the markers (or the file) from a registered doc fails the check rather than quietly un-governing it. That registry lives in c4-model.json, so commit the model — a lost model takes the registry with it. Where a repo lacks docs, forma gen --enrich can fill the remaining box holes with an LLM — opt-in, cached, never on the deterministic gate:

| --enricher | Use it when | Network | |---|---|---| | agent | An agent is driving forma. Writes enrich-plan.json with the holes; the agent writes the sentences (reading the sources if it wants) and gen --enrich-apply <file> applies them with the same cache and provenance. | none | | anthropic | Headless / CI, with ANTHROPIC_API_KEY. | REST | | openai | Same, with OPENAI_API_KEY. | REST | | ollama | Sensitive repos: a local model, nothing leaves the machine. | localhost |

--enricher has no default: forma gen --enrich on its own fails loud and lists what it accepts — the four above, plus echo, an offline stub the test suite uses and you should not. A default provider is a silent choice about your network and your API keys — and the old default (anthropic) meant that anyone without ANTHROPIC_API_KEY exported got a skip line, exit 0 and the same empty boxes they ran --enrich to fill.

How it fits together

        code  ──►  forma gen  ──►  c4-model.json  ──┬──►  c4-viewer.html   (present / explore)
                                    (single source)  ├──►  ARCHITECTURE.md  (arc42, via forma doc)
                                                      └──►  forma check     (gate: model == code?)

One source of truth (c4-model.json); two renderings (the interactive viewer and the arc42 doc); one deterministic check that keeps them honest. The file contract is lib/schema/c4-model.schema.json.

Working on forma itself? docs/ORIENTATION.md walks what each pass of gen/check actually decides, with path:line, and audits which mechanisms nothing in the repo currently feeds.

Model-agnostic by design

The engine is plain Node — no LLM required. Structure is auto-walked, relationships are derived from code, the check is deterministic. The only human (or agent) step is curating the topology groupings and writing the arc42 prose — and any model can do that against the documented JSON/Markdown contract. The Claude skill in adapters/ is a thin wrapper, not the product.

Explore

The viewer is a live C4 map, not a static picture:

  • Click any box to read its explanation — what it does, current state/target on legacy models, or the state and local change at the selected checkpoint — at every level, from context down to a leaf.
  • Double-click a box (or its [+] DRILL) to descend into it; BACK, the breadcrumb, or ESC climb back out.
  • Drag boxes to lay out the view your way; RESET LAYOUT restores the arrangement (your curated hints if the topology has them, the automatic one otherwise). To keep a layout, drag it, pick Export layout JSON, and paste the result under "layout" in the topology — gen carries it into meta.layout and the viewer pins those boxes, auto-arranging everything else clear of them.
  • Arrow labels are painted on the diagram while the level stays readable (≤14 arrows) and turn off above that; LABELS forces them on or off, and hovering an arrow always reveals its label.
  • PRINT / EXPORT to SVG or PNG for docs and slides — exported arrows carry their labels.
  • The breadcrumb names the C4 level you are on (C4-L1 · CONTEXTC4-L3 · COMPONENTS) and, when the level carries curated state, tallies it: a dot per status with its count plus mean completion, so a level reads as a programme board and not just a diagram.
  • Every level shrink-wraps its own content — a context with four boxes renders zoomed and dense instead of adrift in a fixed canvas.

Skins

The viewer ships with swappable skins (holo, blueprint) via a dropdown or ?skin=. Themes are CSS variables; the engine is decoupled from the look.

License

Apache-2.0. Not affiliated with C4 or arc42 — see NOTICE.