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

gandalf-lessons

v1.0.0

Published

Teaches the code changes behind each commit/ticket — multi-level, scroll-narrated lessons generated by Claude Code.

Readme

gandalf

Teaches the code changes behind each commit: multi-level, scroll-narrated lessons generated by Claude Code.

A raw git diff shows what changed line by line, but not what changed behaviorally, or why. gandalf takes a diff (any two refs, or HEAD vs your working tree) and drives Claude Code itself, headless, to analyze it across four lenses: behavioral, module-dependency, contract, and data flow. On top of those it adds complexity, engineering patterns, and active-recall material, then renders everything as an interactive, scroll-narrated web app. After a lesson you understand the change at every altitude, not just the diff.

Every metric is computed deterministically in Node and fed to Claude as ground truth. Claude interprets the numbers, it never invents them, and every judgment is anchored to quoted lines.

See a sample lesson →, generated from a Claude-co-authored commit in Simon Willison's datasette (591b909a, "Escape table names with [square] brackets"). No install needed.

The techniques behind each format, and the program-comprehension, multimedia-learning, and retention research backing them, are documented in docs/RESEARCH.md.


1. Installation

Prerequisites

| Requirement | Why | |---|---| | Node.js ≥ 20 | runtime (ESM + tsx) | | git | gandalf reads diffs and history from a git repo | | Claude Code CLI, installed and logged in | gandalf drives claude -p in headless mode and reuses your existing Claude Code login. It does not use an ANTHROPIC_API_KEY |

Optional tools sharpen the deterministic metrics if present on your PATH (gandalf degrades gracefully without them): scc (size/COCOMO), lizard (per-function cyclomatic complexity, incl. Swift), swiftlint (Swift), difftastic.

Install

Nothing to build — the package ships with the viewer prebuilt:

npx gandalf-lessons generate     # teach the current repo's HEAD vs working tree
npx gandalf-lessons serve        # browse the lessons at localhost:4173

Or install it once so the command is just gandalf:

npm install -g gandalf-lessons
gandalf generate

Confirm Claude Code is ready:

claude --version         # should print a version; if not, install + log in to Claude Code first

Throughout this README, gandalf <cmd> and npx gandalf-lessons <cmd> are interchangeable.

Install from source (contributors)

git clone https://github.com/midhunkrishna/gandalf.git
cd gandalf
npm install
npm run build:web        # build the viewer; required for `gandalf serve` and `gandalf build`

Optional: scripts/install.sh drops a gandalf launcher for the checkout on your PATH:

./scripts/install.sh                       # installs `gandalf` to ~/.local/bin
GANDALF_BIN_DIR=/usr/local/bin ./scripts/install.sh   # …or elsewhere

The launcher resolves Node itself (handy with mise/nvm), so gandalf works from any directory, including the non-interactive shells used by automation.


2. Running gandalf

Generate a lesson

# default: HEAD vs the uncommitted working tree, analyzing the current repo
npm run gandalf -- generate

# any two refs
npm run gandalf -- generate --from <baseRef> --to <targetRef>

# scope to a ticket (overlays the ticket's intent as the "why")
npm run gandalf -- generate --ticket P1-003

Generation drives several claude -p calls (per-file passes plus a fan-out of focused synthesis passes), so it takes a few minutes and consumes Claude usage. The resulting lesson is saved to:

<repo>/.gandalf/lessons/<id>/lesson.json

generate options

| Flag | Default | Meaning | |---|---|---| | --from <ref> | HEAD | base ref | | --to <ref> | (working tree) | target ref; omit to diff uncommitted changes | | --ticket <id> | (none) | force a ticket id for the intent overlay | | --cwd <dir> | current dir | repository to analyze | | --out-dir <dir> | (configured location) | where to write the lesson; overrides lesson_location (see Configuration) | | --lite / --full | --full | generation profile (see below) | | --model-file <m> | (profile: sonnet full, haiku lite) | model for per-file passes; overrides the profile | | --model-synth <m> | (profile: opus full, sonnet lite) | model for the synthesis pass; overrides the profile | | --concurrency <n> | 4 | parallel per-file passes |

Generation profiles

A full lesson is expensive. Five changed files cost about $3.28, and three quarters of that is the seven opus synthesis passes. That is fine for a commit worth studying and far too much for every commit that lands during a work day.

The lite profile buys volume instead of depth. Per-file passes run on haiku, escalating to sonnet for a file with more than 150 changed lines or one of the repo's top three hotspots, and the seven synthesis passes collapse into one sonnet pass. On the same five-file sample commit that measured about $0.50, roughly a sixth of the full cost.

| | Full | Lite | |---|---|---| | Keeps | every lens | Overview, Dependencies, Walkthrough (with beacons), Behavioral, Contracts, Complexity | | Drops | nothing | Patterns, Data flow, Recall, and the tiered "Explain for" text | | File passes | sonnet | haiku, sonnet on heavy files | | Synthesis | 7 passes on opus | 1 merged pass on sonnet |

Everything deterministic is unaffected: contracts, beacons, TL;DRs, complexity metrics and the dependency graph's node set come from the file passes and the evidence bundle, not from the synthesis pass.

Resolution is flag > config > command default, and gandalf generate defaults to full while gandalf watch defaults to lite (watch teaches every commit, so volume decides). Watch prints the profile it resolved at startup.

gandalf generate --lite          # cheap lesson for this diff
gandalf watch --full             # spend the money on every commit

Set generation_profile: "lite" (or "full") in ~/.gandalf/config.yaml to pin both commands to one profile. A lite lesson is marked lite in the viewer's header and lesson list, and the lenses it never generated are hidden rather than shown empty.

The honest framing: lite is for watch-mode volume, so a day of twenty commits costs about ten dollars instead of sixty-five. When a commit is worth studying, re-run it with gandalf generate --full.

Browse lessons in the viewer

npm run gandalf -- serve            # http://localhost:4173
npm run gandalf -- serve --port 4300

Open the printed URL. The viewer lists every persisted lesson (newest first) in the Lessons menu.

List persisted lessons

npm run gandalf -- list

Check lesson integrity

npm run gandalf -- doctor              # all lessons
npm run gandalf -- doctor --lesson <id>
npm run gandalf -- doctor --fix        # apply the graph repairs it reports

Reports cross-section drift (graph nodes that resolve to no file, contract/evidence references to unknown files, beacons past EOF, malformed diagrams). generate runs the same check automatically and prints warnings. Exit code 1 when errors are found.

Graph repairs are printed for every run and written only with --fix: an edge pointing at a missing node gains that node (status unchanged) when the id reads like a module, and is dropped when it does not. --fix rewrites the stored lesson.json in place and never deletes a lesson.

Export a self-contained lesson

npm run gandalf -- build --out lesson.html          # newest lesson
npm run gandalf -- build --lesson <id> --out lesson.html

See §4.


3. Configuration

gandalf reads an optional user-level config from ~/.gandalf/config.yaml (created by scripts/install.sh; never overwritten on re-install). The file is self-documenting: every key ships with an inline comment, and future keys ship commented-out (disabled). Delete any key (or the whole file) to fall back to the documented default. GANDALF_HOME_DIR relocates ~/.gandalf (used by tests).

| key | default | meaning | |---|---|---| | generation_profile | (unset) | Which profile to use when neither --lite nor --full is given (see Generation profiles). Ships commented out: while it is unset, generate runs full and watch runs lite. Set it to "lite" or "full" to pin both commands. | | lesson_location | "home-dir" | Where lesson libraries live. "home-dir": ~/.gandalf/<project-name>-<root-commit-sha12>/lessons. Lessons never touch the analyzed repo (no lesson commits, no working-tree writes), and the store key uses the repo's root (first) commit, so it survives moving or renaming the project directory. "project-wd": <repo>/.gandalf/lessons, the pre-config in-repo behavior. |

Precedence everywhere: --out-dir flag > lesson_location > default. gandalf doctor prints the resolved config and store location for a repo.

4. Watch mode

Teach commits as they land, orthogonally to whatever produces them. Open a side shell and leave it running:

gandalf watch --cwd <path-to-project>     # foreground; Ctrl-C to stop

The watcher polls HEAD (default every 5s), waits for a quiet period (debounce, default 5s; rebases and merges in progress are waited out), then teaches each new first-parent commit serially, in commit order. Progress and errors stream to stdout (watch defaults to --log-level debug).

State lives in watch-journal.json beside the lesson store, never in the analyzed repo. One watcher per store (a lockfile enforces it).

gandalf watch --once                  # process the pending backlog, then exit (exit 1 if any failed)
gandalf watch --once --retry-failed   # also re-attempt previously failed commits
gandalf watch --status                # journal summary: frontier, counts, last failures
gandalf watch --from HEAD~10          # first run only: backfill baseline (default: HEAD = future commits only)

Semantics worth knowing:

  • Commit subjects like E1-033: … are parsed as ticket ids (nicer lesson ids plus the intent overlay); --no-infer-ticket disables this.
  • Skipped, not failed: merge commits (unless --include-merges, in which case the first-parent diff is taught), empty diffs, and commits touching only ignored paths (dist/, lockfiles, .gandalf/, and so on).
  • A failure is terminal: the frontier advances so one bad commit never wedges the watcher. --retry-failed re-enqueues failures explicitly (re-generating overwrites that commit's lesson, which is intentional).
  • Amend/rebase: rewritten history is not re-taught; the watcher resumes from the merge-base and records a rewrite event. Lessons for pre-rewrite commits stay in the library (no pruning yet). With no common history (or a shallow clone boundary) the baseline resets to HEAD with a warning; gandalf never guesses.
  • Lite by default: watch teaches every commit, so it uses the lite profile unless you pass --full or set generation_profile in the config. The resolved profile is logged at startup. See Generation profiles.
  • Cost valve: a first plan with >25 pending commits refuses and asks for --max <n> or --from <ref>, because each lesson costs minutes of claude usage.
  • Ctrl-C once finishes the in-flight lesson (it is minutes of paid work); twice aborts (exit 130). Claude subprocesses have no abort plumbing, so detached/nohup use can orphan them; run watch in a foreground shell.

5. Using / reading the lessons

Open a lesson in gandalf serve. Each lesson is a set of lenses (tabs), presented top-down: overview → zoom & filter → details on demand.

| Lens | What it answers | |---|---| | Overview | Verdict (behavioral vs refactor-only), breaking count, changed files, top hotspots | | Dependencies | A module-dependency graph (color-coded by change). Click a module for its before/after TL;DR, contract deltas, and the diff | | Walkthrough | A scroll-narrated tour: stepping prose on the left, a pinned code panel on the right with diff colouring (added = green, modified = yellow, removed = red marker) | | Behavioral | The behavioral verdict, a one-line conditional-equivalence statement, and illustrative Trace Cards (input → before/after) | | Contracts | Changed signatures with a Design-by-Contract Safe / Breaking verdict and pre/postcondition deltas | | Data flow | A Mermaid sequence diagram + a Sankey of quantitative flow + before/after narrative | | Complexity | Δ-scorecard, per-function before→after slope charts, and a hotspot treemap (area = churn, intensity = complexity) | | Patterns | Design/architecture patterns & smells as a before→after delta, plus an ADR with "Considered Options" | | Recall | Retrieval-practice questions to test (and retain) the change |

Controls

  • Explain for (ELI5 / Junior / Senior / Architect): re-explains a lens at the chosen altitude. Shown only on lenses that have tiered text.
  • Quiz (default on): predict-then-reveal. Guess the after-output / Safe-or-Breaking before it's shown, or toggle off to read passively.
  • Review: spaced repetition over recall questions you've rated, across your whole lesson library.
  • +N / All files: config/lockfile/dotfile noise is hidden by default; reveal it with this header toggle. Hidden files are also excluded from the complexity & hotspot metrics.
  • Maximize / Split (Dependencies): make the diff full-width, or switch to a GitHub-style before|after split.
  • Maximize (Mermaid diagrams): open a pan/zoom overlay (scroll to zoom, drag to pan).
  • Collapse hero (chevron): hide the title block to give the lenses more vertical room.
  • Dark mode and the Lessons timeline live in the header (theme + depth tier persist).
  • Addressable views: the URL tracks where you are (#/lesson/<id>/<tab>, plus the selected module on Dependencies, #/library, #/tokens), so reloads keep your place and links can be pasted and shared. Hash-based, so it also works in the offline single-file export.
  • Contract ↔ diff linking: click a contract's symbol to open its exact lines in the Dependencies diff (?l=<line>); a pinned "← Back to " chip returns you to that contract row. Anchors are content-addressed (file::symbol), so stale links degrade to the right file or tab, never a wrong target.
  • Library (header toggle): a gallery landing page of every lesson as a cover card; click to open.
  • Share card (in the lesson hero): downloads a 1200×630 branded PNG of the lesson for posting.
  • Esc resets any non-default view state (closes the diagram zoom, un-maximizes the diff).

Honesty note: Trace Cards are reasoned from the code, not executed; they're labelled "illustrative." Every pattern/behavior claim carries quoted-line evidence and a confidence level.


6. Exporting a static asset

gandalf build inlines the viewer plus a lesson into one self-contained HTML file that renders fully offline, with no server and no network (fonts fall back to system fonts offline):

npm run gandalf -- build --lesson <id> --out lesson.html

Attaching it to a GitHub PR / MR

GitHub (and GitLab) don't render arbitrary HTML inline in a review, and GitHub comment attachments reject raw .html. Practical options:

  1. Zip & attach: zip lesson.zip lesson.html, then drag the .zip into a PR/MR comment. Reviewers download, unzip, and open lesson.html in any browser (works offline).
  2. CI artifact: in CI, run gandalf build and upload lesson.html with actions/upload-artifact; reviewers download it from the run summary.
  3. Static host / Pages: publish the file (GitHub Pages, S3, Netlify, …) and link it from the PR description.

Notes:

  • The export embeds a single lesson; the spaced-Review feature in an export only sees that lesson.
  • The file is large (everything is inlined). Prefer zipping for attachments.

7. Contributing

Layout

bin/gandalf.ts        # commander CLI: generate | serve | build | list | doctor
src/core/             # engine: git · evidence · noise · tickets · claude · schemas · prompts · pipeline · lesson
src/server/serve.ts   # Hono server: static viewer + lesson API
web/                  # the React + Vite viewer (lenses, components, design tokens)
test/                 # engine unit tests (vitest)
scripts/              # Playwright visual-sanity harnesses

Dev loop

npm run dev                       # Vite dev viewer (uses a bundled sample lesson; no server needed)
npm run typecheck                 # engine (tsc)
npm run typecheck:web             # viewer (tsc)
npm test                          # engine + lib unit tests (modules / noise / tickets / validate / claude shim / diff parser)
npm run build:web                 # production build of the viewer

The scripts/*.mjs Playwright harnesses screenshot the running viewer for visual review (they expect a gandalf serve instance and use the system Chrome).

Conventions & constraints

  • TypeScript ESM throughout; match the surrounding code's style.
  • Drive Claude only via the Claude Code CLI (claude -p). Don't add the Agent SDK or the raw Messages API; both require an API key, which breaks the "reuse your Claude Code login" contract.
  • Two-plane analysis: compute hard numbers deterministically in src/core/evidence.ts; Claude interprets them but never invents metrics. Keep judgments evidence-anchored.
  • Design system: the viewer uses warm-editorial CSS-variable tokens. Stay on the type/spacing/color/motion scales, keep AA contrast in both themes, honor prefers-reduced-motion, and keep new surfaces dark-mode safe (theme via CSS vars; re-render Shiki/Mermaid on theme change via useIsDark).
  • Before opening a PR: typecheck + typecheck:web + the engine tests + build:web should pass, and a quick visual pass via the sanity scripts is appreciated.

8. License

Released under the MIT License; see LICENSE.

MIT © Midhun Krishna