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

@danxbot/docs

v0.1.57

Published

Repo-agnostic docs package (DX-3336/DX-3337): a typed, versioned node-file format, a loader that builds an in-memory graph from a docs root, and a fail-loud `check` CLI.

Readme

@danxbot/docs

Repo-agnostic docs package for the PLN-12 documentation system. Nothing in this package is danxbot-specific (R-1): any repo can adopt it and store its documentation as typed node files, one file per node.

What the docs are for (schema v3)

Docs explain what a system does and why, in words a non-engineer can follow: its parts, the real things people ask about within them, how things happen, the rules and decisions behind them, and how to run things locally. They then point engineers at where to look. They never map individual classes, API routes, schemas or code contracts — that couples the docs to the code, guarantees drift, and repeats what the code already says (PLN-12 R-11).

A node is a substantial topic someone would look up — the system, a major part of it, or a real part of that part (an app, a component, a domain idea) — with a full page (PLN-12 R-13). A rule, a decision, a caveat, a sequence of steps or a set of states is never its own node: it is a section of the page it belongs to. A node that would hold only one sentence belongs as a bullet or a section inside its parent's page instead of a node of its own — check enforces a fifty-word floor on a System/Area/Topic body precisely so a docs tree can't quietly fill up with these thin, single-sentence nodes again.

Node file format

A node file is markdown with YAML frontmatter:

---
id: area.issues
type: Area
title: Issue cards
summary: Issue cards track work as it moves from an idea to finished and merged.
schema_version: 3
part_of: system.danxbot
look_in: [src/issues]
color: iris
rules:
  - Status is always derived from lifecycle timestamps, never written directly.
---

Issue cards are the unit of work danxbot hands to its agents...

Required fields: id (stable, kebab/dot form, e.g. area.issues or topic.ready-at), type, title, summary (capped at SUMMARY_MAX_LENGTH, 200 characters), schema_version (must be exactly SCHEMA_VERSION_MAX, currently 3), and part_of (a single parent node id — required on every type except System, and forbidden on System, the one root of the tree).

Node types

Each type has a plain-language label and a one-line explanation, exported as NODE_TYPE_INFO so every view shows those words instead of a bare type name.

| Type | Shown as | Answers | |---|---|---| | System | The system | What this system is, who it is for, and what it does for them | | Area | Part of the system | A major part of the system, and what it is responsible for | | Topic | Topic | A real part of an area people ask about — an app, a component or a domain idea — explained in full | | Guide | How to | Step-by-step instructions for a task | | Path | Learning path | A reading order through the docs for one kind of reader |

Concept, Flow, Lifecycle and Decision are gone as node types — see "Page sections" below for where their content now lives.

part_of — the one navigation relation

part_of is the only relation that shapes the tree every view lays out. It is a single required scalar (never a list), so "exactly one parent" is the shape itself, not a rule that has to count: a docs root has exactly one System (the root, with no part_of), and every other node has exactly one part_of parent, with no cycles. Which type may be part-of which is fixed (PART_OF_TARGET_TYPES):

| A … | may be part_of a … | |---|---| | Area | System only | | Topic | an Area, or another Topic | | Guide | a System, an Area, or a Topic | | Path | System only |

A Path is never anyone's part_of parent.

see_also — the one cross-link

see_also is a plain list of other node ids, rendered as inline "See also" text links on the page — never a ring node, never a typed relation. It replaces v2's uses, leads-to, explained-by and step-of.

Page sections

A page's structured content is data, not free markdown, so the app can render each section with the right component (a sequence diagram, a state diagram, ...). Every section below is optional, and (other than Path, whose only content is its steps[]) any of System/Area/Topic/Guide may carry any of them:

| Field | Shape | Renders as | |---|---|---| | why_it_matters | a plain string | a short callout | | how_it_works | {actor, action, target?, notes?}[] | a sequence diagram | | states | {subject, states: {id, label?}[], transitions: {from, to, trigger?, guard?}[]} | a state diagram — every transitions[].from/.to must name a declared states[].id | | rules | a non-empty string list | a rules list | | decisions | {choice, why, rejected?, trade_off?}[] | a "why it's built this way" list | | watch_out_for | a non-empty string list | a caveats list |

Optional fields

see_also[] (node ids), look_in[] (engineer pointers at area level: a directory, package or service name — a source file such as src/orders/service.ts, or a file-and-symbol pointer such as src/orders.ts#placeOrder, is refused), provenance ({plans?, cards?, commits?}), audience[] (developer | operator | integrator | agent), icon (an @danxbot/ui icon name), color (a key into @danxbot/ui's palette — PALETTE_KEYS: iris, aqua, mint, amber, rose, azure, slate).

Type-specific structured fields: System nodes may declare value_props[] ({icon, title, summary, color?}) — its own selling points, separate from its Areas; Path nodes require steps[] ({node, note?}, node referencing another node's id) and carry no page sections at all. Every other type carries only the common fields above plus the page sections table.

The full schema is defined once, in code, at src/schema.ts — that is the single source of truth for the validator, the loader and any later consumer (search, the docs app).

Only one schema version is read

There is no reader for older versions and no automatic migration. A file whose schema_version is not exactly the current one is refused by check with a message saying so — a v2 file (with Concept, Flow, Lifecycle or Decision nodes, or a relations[] list) and a v1 file (with Component, Contract, Invariant or Domain nodes, code anchors or verified_at) must both be rewritten as a v3 node (CLAUDE.md Core Principle 1 — fail loud, no legacy reader, no version-conditional logic).

Install

npm install @danxbot/docs

Usage

import { loadDocsGraph, loadDocsRoot, runChecks } from "@danxbot/docs";

// Fail-loud: throws DocsValidationError if anything is wrong.
const graph = loadDocsGraph("./docs");

// Or collect every problem yourself (this is what the CLI does):
const loadResult = loadDocsRoot("./docs");
const problems = runChecks(loadResult);
npx tsx src/cli.ts check ./docs                        # dev
danxbot-docs check ./docs                               # after `npm run build`
danxbot-docs search ./docs "how does X work" [--limit N]  # compact YAML, for agents
danxbot-docs tree ./docs [--root <id>] [--depth N]      # compact YAML of the part-of hierarchy, for agents
danxbot-docs show ./docs <id>                           # one node's full content, for agents
danxbot-docs serve ./docs [--port N]                    # the docs app, live (default port 4300)
danxbot-docs build ./docs ./out                         # the docs app, as a static site
danxbot-docs demo [--port N]                            # serves the bundled example project — see below
danxbot-docs promotion-guide                            # the generic "draft a changeset from a plan" protocol, for agents

For agents: tree → search → show (DX-3581, PLN-12 architecture §6)

The short instruction an agent is pointed at ships as AGENTS.md; this section is the longer explanation.

An agent gains context on a docs root in three steps, never by reading every file:

  1. tree to orient. danxbot-docs tree ./docs prints the whole part_of hierarchy at depth 2 (the System and its Areas and Topics) as compact YAML: id, type, title, summary, file and a real children count per node — enough to see the shape of the system and pick a branch, without reading any page body. --root <id> narrows to one branch (depth counted from that node); --depth N widens or narrows how far the walk descends. An unknown --root fails loud, naming the nearest real ids.
  2. search to find. danxbot-docs search ./docs "<query>" (see above) ranks nodes (BM25 over title, summary and body, with stop words dropped, words stemmed, a bonus for a phrase a page actually says, and a small boost for graph proximity), returning one line per match: id, type, title, summary, the page's file, its size in bytes and lines, and look_in when the page declares code pointers (DX-5582). No body and no ancestor or related lists: the size says whether to read the file whole or in slices. The same answer comes from the danx-dashboard MCP's docs_search for this checkout, another docs root or a team repo's hosted docs. Six matches by default; --limit N for more.
  3. show <id> to read. danxbot-docs show ./docs <id> prints that ONE node in full: its complete frontmatter (every section it carries — rules, decisions, how_it_works, states, watch_out_for, ...) and its markdown body, plus its ancestor chain and its direct children — both reduced to id + summary only, never expanded. Reading stays granular: show never pulls in a child's own content, so the next step is always another explicit tree/search/show call, not an ever-growing dump. An unknown id fails loud, naming the nearest real ids.

Read a source file directly only when show's look_in pointers say to look there — the docs themselves never name classes, routes or symbols (R-11), so there is nothing to dig for inside the page content itself.

For agents: promoting a plan into docs (DX-3674)

A separate, generic protocol from the read-side one above: how to turn a FINISHED PLAN into one docs changeset against an existing root — reading the whole plan, reading the landed commits, deciding create/update/merge/ supersede against the current graph, and validating with check before committing. It ships as PROMOTION.md; an agent reads it with danxbot-docs promotion-guide rather than needing to know where inside node_modules this package physically unpacked. This package is repo-agnostic and knows nothing about WHICH plan or WHERE to push the result — the dispatch that points an agent at this guide supplies that.

Demo (DX-3369)

The package ships a realistic example docs project — Ringlet, a fictional service for selling event tickets — at examples/ringlet/, separate from the neutral test fixtures. It is written at schema v3's granularity: a handful of substantial Areas, each with a few Topics, plus Guides and learning Paths — never a thin node holding just one sentence. It names no classes, routes or schemas; each area points engineers at where to look instead.

One command serves it:

npm run demo               # dev, via tsx — reads examples/ringlet live
node dist/cli.js demo      # after `npm run build` — same, from the built CLI

Both are exactly danxbot-docs demo [--port N] under the hood (default port 4300, same as serve) — a thin wrapper around serve pointed at the bundled examples/ringlet directory, resolved from the package's own install location rather than the current working directory, so it works from anywhere the package is installed or checked out.

The docs app (serve / build)

serve and build both need the app's own assets pre-built once via npm run build:app (or npm run build, which does both the CLI and the app) — they read dist/app rather than compiling anything on the fly. serve is a plain node:http static file server: no database, no framework. It re-reads the given docs root and rebuilds the embedded data on every request to /, so editing a node file shows up on the next browser reload with no restart. build writes the same app as a fully static site: the built assets plus one index.html with that docs root's data embedded, deployable to any static host. Both refuse (fail loud, R-3) to serve/build an invalid docs root — exactly the same validation check runs.

The app itself: a children-only focus web (a page's part_of children fan out around it; a cross-link is an inline "See also" text link, never a ring node), one page per node (zero-context summary first, then its page sections rendered with the matching component, "where to look" pointers and provenance), and search results rendered as small clickable graphs (the same engine search uses). Real chrome throughout — nav, part-of breadcrumbs, a command-palette search, appearance controls (theme/text size/contrast/ motion), deep links — built entirely from @danxbot/ui, responsive down to phone width. Source under src/app/ (the React app) and src/server/ (the CLI-facing serve/build layer); see src/app-data.ts for the one contract between them.

Check rules

check exits non-zero and lists every problem, one per line, with the file it was found in:

  • schema_violation — schema errors: missing/invalid frontmatter, a field that fails the schema, a removed node type, a part_of missing on a non-System node or present on a System, a look_in naming a source file or a symbol
  • unsupported_schema_version — a schema_version other than the current one (never auto-migrated — CLAUDE.md Core Principle 1)
  • unreadable_file — a .md file or a directory under the root that cannot be read (a dangling symlink, a permission error); it is reported and the rest of the tree still loads (DX-5597)
  • duplicate_id — the same id declared by more than one file
  • dangling_relation — a part_of or see_also pointing at an unknown id
  • part_of_cycle — a part_of cycle
  • part_of_target_type — a part_of parent of a type the tree's shape doesn't allow (see the part_of table above)
  • multiple_systems — more than one System in a docs root
  • summary_too_long — a summary over the 200-character cap
  • path_unknown_node — a Path step naming an unknown node id
  • unknown_icon_name — an icon (node-level or on a value_props entry) that isn't a real @danxbot/ui icon name
  • unknown_color — a color (node-level or on a value_props entry) that isn't a PALETTE_KEYS palette key
  • thin_page — a System/Area/Topic body under the fifty-word floor a real page needs (R-13); Guides and Paths are exempt
  • how_it_works_code_symbol — a how_it_works actor or target written as code (name::method, name(), name->method, or a source-file or file-and-symbol shape) instead of a role in the system (R-11); prose fields are never scanned, and a bare class name is left to PROMOTION.md's audience test

Tests

tests/fixtures/valid/ is a small neutral example project (every node type, every page section, look_in, color, icon and value_props each exercised at least once) that passes check cleanly. tests/fixtures/invalid/ has one fixture directory per check rule above, each proving that rule alone fires, plus: v1-node and v2-node (both retired schema versions, refused the same way), removed-type (a v3 file using a type v3 deleted), multiple-problems-one-file (two rules firing on the same file at once), and look-in-file-symbol (a schema-level look_in shape refusal). examples/ ringlet/ (see "Demo" above) is a separate, much larger project meant for a human to browse, not as a minimal test fixture — it is also covered by its own test asserting it passes check with zero problems, and by tree-show-ringlet.test.ts, which exercises tree and show against it (card AC "tests cover both commands on the Ringlet example").