What it draws
One HTML file, no server: the codanna index rendered as a pie chart of symbols. Each
top-level module owns a wedge sized by its share; inside a wedge symbols fill rings from
the centre outwards, best-connected first, so hubs sit near the middle and leaves on the
rim. Unlinked symbols form their own inner group. Above the disc a heatmap shows symbols
added per day (each symbol dated by the oldest surviving line of its span, via git
blame). Hover or click a symbol to see its edge
web and the connected symbols; the legend hides (eye) or highlights (label) modules and
submodules; the ribbon under the heatmap brushes a date range (drag its handles, use the
date fields, or click a year chip) and Refresh replays the codebase growing oldest-first.
Run
node ${CLAUDE_SKILL_DIR}/graph.mjs # whole index, Calls edges, default kinds
node ${CLAUDE_SKILL_DIR}/graph.mjs --root crate::indexing # one module family
node ${CLAUDE_SKILL_DIR}/graph.mjs --relation calls,uses,implements,extends
node ${CLAUDE_SKILL_DIR}/graph.mjs --kinds function,method,struct,trait --dates none --light
- Runs
codanna dump in the project (--from graph.jsonl reuses a saved dump, --binary PATH picks the binary)
- Output:
.codanna/visualizations/graph-disc-<timestamp>.html, opened in the browser (--no-open to skip, --out FILE to place it)
--group <wedge>[/<tint>] -- wedge axis module (default), language, or kind; tint axis module (default), kind, or visibility. The wedge is the angle (share, neighbours adjacent); the tint is the legend tree inside it (three biggest get their own shade, the rest pool). module/kind keeps the structure and shows what each module is made of; language/module for polyglot indexes; module/visibility for API surface per module; kind/module is a census (edges cross everywhere)
--root PREFIX scopes to a module prefix (crate:: is dropped, ./::// all separate segments); applies to the module segments whatever the wedge axis
--kinds defaults to Function, Method, Struct, Class, Trait, Enum, Interface, TypeAlias, Constant, Macro
--relation defaults to calls; any of calls, uses, implements, extends, defines, comma-separated
--dates blame|git|none -- blame (default) dates every symbol by the oldest surviving line of its span (git blame -M over the working tree; per-file JSON cache at .codanna/visualizations/dates-cache.json keyed by content hash, so warm runs spawn no blame -- delete the file to reset); git uses the file's first-commit day (cheap, but a refactor reads as mass birth); none leaves the timeline and heatmap empty
--unlinked include|drop -- drop removes symbols with no edge over the chosen relations at build time (smaller file, smaller disc); in the page, the legend eye on (unlinked) hides the same set with the cascade animation
--name NAME sets the title (default: project directory name); --light builds the light theme
Scoping and filters: what to apply when
Start whole-index once to see module shares and hubs, then scope. The disc is smooth to a
few thousand symbols; hover and filters fall to ~10 fps near 10,000 (the CLI says so
above 4,000). Measured on the codanna self index (14,003 symbols):
| Situation |
Apply |
Effect on self index |
| First look, whole codebase |
defaults |
9,983 symbols, 8,248 calls edges, 51% unlinked |
The hub hole dominates ((unlinked) is the biggest group) |
--unlinked drop |
4,890 symbols, same 8,248 edges; or hide it in-page with its legend eye |
| Many constants/aliases, few calls |
--kinds function,method,struct,class,trait,enum,interface |
8,453 symbols, unlinked 42% |
| Type-level structure matters (impls, inheritance, field types) |
--relation calls,uses,implements,extends |
11,003 edges, unlinked 41%; both together: 10,305 edges, 32% |
| Work inside one module family |
--root crate::indexing (or src/... for path-derived languages) |
701 symbols, 6 groups, smooth hover |
| "What is each module made of" (trait-heavy vs free functions) |
--group module/kind |
same wedges, tints = kinds |
| Polyglot repo / monorepo, languages first |
--group language/module |
15 language wedges on self, modules as tints |
| Public surface vs internals per module |
--group module/visibility |
tints = public / private / crate... |
| Census by symbol kind |
--group kind/module |
wedges = kinds; every call crosses the disc, read the counts |
| More than twelve top-level groups |
the twelve palette slots cycle; pin colours per module via the gear (persists in the browser) or right-click a legend row |
repeats stay separated by wedge, rim label and legend row |
| Heatmap/timeline empty or misleading (shallow clone, vendored code) |
--dates none |
both panels blank, disc unchanged |
| Re-render without re-reading the index |
--from graph.jsonl (from codanna dump > graph.jsonl) |
same data, no dump run |
defines as a relation is containment; the legend tree already carries it, so add it
only when you want container->member lines on the disc. --root matches module
segments, so crate::parsing::rust, parsing.rust and parsing/rust are the same
prefix; a symbol with no module_path falls back to its file path segments.
Reading it
- Wedge angle = module share of the drawn symbols; a wedge reaching the rim with few rings is sparse, many tight rings is dense
- Centre of a wedge = its hubs (highest degree over the chosen relations); the rim = leaves
- Hover: the blue web is the symbol's edges; the detail panel (click) shows the signature (syntax-highlighted via bundled highlight.js grammars for the index's languages), its
file:start-end, and the connected symbols grouped by relation and direction (Calls / Called by, Uses / Used by, Implements..., xN = several call sites). Hopping through those lists builds a breadcrumb trail at the top of the card (back arrow steps back, first crumb recovers the starting symbol; Alt+Left or Backspace = back, Esc = close, / = search). Pin to hub drags or pins a symbol into the centre
- Camera: drag to pan (the corner cluster's pan button toggles it), wheel or pinch to zoom toward the pointer, zoom in / zoom out / fit in the bottom-right cluster (fit recentres the whole disc; double-click does the same)
- Legend eye hides a module and the rest regrow into a full circle; legend label pushes the module out and rings it -- highlight and visibility are separate axes
- Heatmap day hover haloes the symbols added that day; click pins the day and fills those symbols in the neutral
--today colour. The ribbon below brushes the whole history; year chips jump to a year
- 51% of the default-kind symbols on a typical index have no Calls edge (constants, type aliases, trait items): they are the
(unlinked) group. Narrow --kinds, widen --relation, or --unlinked drop -- see the table above
Pick by question
- "what is the shape of this codebase / module family" -> this skill
- "what depends on this one symbol / what breaks" -> the sibling x-ray skill's
visualize-dump.js (2D layered call DAG by default; --3d for the force view):
node ${CLAUDE_SKILL_DIR}/../x-ray/visualize-dump.js <name> [depth]
- "how is one module organised" -> x-ray
visualize-tree.js (collapsible tree;
--radial for the poster); "which modules call which" -> x-ray
visualize-bundle.js --depth N
- The x-ray pages share this skill's design tokens and the light/dark toggle
Files
graph.mjs (CLI + assembly), lib/dump.mjs (dump reader), lib/adapter.mjs (dump -> disc data), lib/dates.mjs (blame line dates + cache, first-commit fallback, mtime), lib/vendor.mjs (vendored-bundle reader: strips unreachable network calls, count-gated),
shell.html + page.html + page.css + page.js + vendor/ (the vault-graph page, MIT -- see UPSTREAM.md for the pin and the tagged hunks),
vendor/hljs/ (highlight.js core + per-language grammars, BSD-3-Clause -- BUILD.md there has the recipe; only grammars for languages present in the dump are inlined),
assets/logo-mask.svg (the mark in the hub, painted with the wedge colours).
1---2name: graph3description: Whole-codebase structural map of the codanna index as one self-contained HTML disc. Wedges per top-level module, concentric rings with hubs at the centre, hover or click a symbol to light its edge web, search, hide or highlight modules, brush a date range on the ribbon timeline (symbols dated by git blame), heatmap of symbols added per day, per-module colour picker. Use when the question is about the shape of the whole codebase or a module family (what is central, which modules talk, where the hubs sit), not one symbol's neighbourhood. Needs codanna >= 0.14 (`codanna dump`).4---56## What it draws78One HTML file, no server: the codanna index rendered as a pie chart of symbols. Each9top-level module owns a wedge sized by its share; inside a wedge symbols fill rings from10the centre outwards, best-connected first, so hubs sit near the middle and leaves on the11rim. Unlinked symbols form their own inner group. Above the disc a heatmap shows symbols12added per day (each symbol dated by the oldest surviving line of its span, via git13blame). Hover or click a symbol to see its edge14web and the connected symbols; the legend hides (eye) or highlights (label) modules and15submodules; the ribbon under the heatmap brushes a date range (drag its handles, use the16date fields, or click a year chip) and Refresh replays the codebase growing oldest-first.1718## Run1920```bash21node ${CLAUDE_SKILL_DIR}/graph.mjs # whole index, Calls edges, default kinds22node ${CLAUDE_SKILL_DIR}/graph.mjs --root crate::indexing # one module family23node ${CLAUDE_SKILL_DIR}/graph.mjs --relation calls,uses,implements,extends24node ${CLAUDE_SKILL_DIR}/graph.mjs --kinds function,method,struct,trait --dates none --light25```2627- Runs `codanna dump` in the project (`--from graph.jsonl` reuses a saved dump, `--binary PATH` picks the binary)28- Output: `.codanna/visualizations/graph-disc-<timestamp>.html`, opened in the browser (`--no-open` to skip, `--out FILE` to place it)29- `--group <wedge>[/<tint>]` -- wedge axis `module` (default), `language`, or `kind`; tint axis `module` (default), `kind`, or `visibility`. The wedge is the angle (share, neighbours adjacent); the tint is the legend tree inside it (three biggest get their own shade, the rest pool). `module/kind` keeps the structure and shows what each module is made of; `language/module` for polyglot indexes; `module/visibility` for API surface per module; `kind/module` is a census (edges cross everywhere)30- `--root PREFIX` scopes to a module prefix (`crate::` is dropped, `.`/`::`/`/` all separate segments); applies to the module segments whatever the wedge axis31- `--kinds` defaults to Function, Method, Struct, Class, Trait, Enum, Interface, TypeAlias, Constant, Macro32- `--relation` defaults to `calls`; any of calls, uses, implements, extends, defines, comma-separated33- `--dates blame|git|none` -- `blame` (default) dates every symbol by the oldest surviving line of its span (`git blame -M` over the working tree; per-file JSON cache at `.codanna/visualizations/dates-cache.json` keyed by content hash, so warm runs spawn no blame -- delete the file to reset); `git` uses the file's first-commit day (cheap, but a refactor reads as mass birth); `none` leaves the timeline and heatmap empty34- `--unlinked include|drop` -- `drop` removes symbols with no edge over the chosen relations at build time (smaller file, smaller disc); in the page, the legend eye on `(unlinked)` hides the same set with the cascade animation35- `--name NAME` sets the title (default: project directory name); `--light` builds the light theme3637## Scoping and filters: what to apply when3839Start whole-index once to see module shares and hubs, then scope. The disc is smooth to a40few thousand symbols; hover and filters fall to ~10 fps near 10,000 (the CLI says so41above 4,000). Measured on the codanna self index (14,003 symbols):4243| Situation | Apply | Effect on self index |44|---|---|---|45| First look, whole codebase | defaults | 9,983 symbols, 8,248 calls edges, 51% unlinked |46| The hub hole dominates (`(unlinked)` is the biggest group) | `--unlinked drop` | 4,890 symbols, same 8,248 edges; or hide it in-page with its legend eye |47| Many constants/aliases, few calls | `--kinds function,method,struct,class,trait,enum,interface` | 8,453 symbols, unlinked 42% |48| Type-level structure matters (impls, inheritance, field types) | `--relation calls,uses,implements,extends` | 11,003 edges, unlinked 41%; both together: 10,305 edges, 32% |49| Work inside one module family | `--root crate::indexing` (or `src/...` for path-derived languages) | 701 symbols, 6 groups, smooth hover |50| "What is each module made of" (trait-heavy vs free functions) | `--group module/kind` | same wedges, tints = kinds |51| Polyglot repo / monorepo, languages first | `--group language/module` | 15 language wedges on self, modules as tints |52| Public surface vs internals per module | `--group module/visibility` | tints = public / private / crate... |53| Census by symbol kind | `--group kind/module` | wedges = kinds; every call crosses the disc, read the counts |54| More than twelve top-level groups | the twelve palette slots cycle; pin colours per module via the gear (persists in the browser) or right-click a legend row | repeats stay separated by wedge, rim label and legend row |55| Heatmap/timeline empty or misleading (shallow clone, vendored code) | `--dates none` | both panels blank, disc unchanged |56| Re-render without re-reading the index | `--from graph.jsonl` (from `codanna dump > graph.jsonl`) | same data, no dump run |5758`defines` as a relation is containment; the legend tree already carries it, so add it59only when you want container->member lines on the disc. `--root` matches module60segments, so `crate::parsing::rust`, `parsing.rust` and `parsing/rust` are the same61prefix; a symbol with no `module_path` falls back to its file path segments.6263## Reading it6465- Wedge angle = module share of the drawn symbols; a wedge reaching the rim with few rings is sparse, many tight rings is dense66- Centre of a wedge = its hubs (highest degree over the chosen relations); the rim = leaves67- Hover: the blue web is the symbol's edges; the detail panel (click) shows the signature (syntax-highlighted via bundled highlight.js grammars for the index's languages), its `file:start-end`, and the connected symbols grouped by relation and direction (Calls / Called by, Uses / Used by, Implements..., `xN` = several call sites). Hopping through those lists builds a breadcrumb trail at the top of the card (back arrow steps back, first crumb recovers the starting symbol; Alt+Left or Backspace = back, Esc = close, `/` = search). Pin to hub drags or pins a symbol into the centre68- Camera: drag to pan (the corner cluster's pan button toggles it), wheel or pinch to zoom toward the pointer, zoom in / zoom out / fit in the bottom-right cluster (fit recentres the whole disc; double-click does the same)69- Legend eye hides a module and the rest regrow into a full circle; legend label pushes the module out and rings it -- highlight and visibility are separate axes70- Heatmap day hover haloes the symbols added that day; click pins the day and fills those symbols in the neutral `--today` colour. The ribbon below brushes the whole history; year chips jump to a year71- 51% of the default-kind symbols on a typical index have no Calls edge (constants, type aliases, trait items): they are the `(unlinked)` group. Narrow `--kinds`, widen `--relation`, or `--unlinked drop` -- see the table above7273## Pick by question7475- "what is the shape of this codebase / module family" -> this skill76- "what depends on this one symbol / what breaks" -> the sibling x-ray skill's77 `visualize-dump.js` (2D layered call DAG by default; `--3d` for the force view):78 `node ${CLAUDE_SKILL_DIR}/../x-ray/visualize-dump.js <name> [depth]`79- "how is one module organised" -> x-ray `visualize-tree.js` (collapsible tree;80 `--radial` for the poster); "which modules call which" -> x-ray81 `visualize-bundle.js --depth N`82- The x-ray pages share this skill's design tokens and the light/dark toggle8384## Files8586`graph.mjs` (CLI + assembly), `lib/dump.mjs` (dump reader), `lib/adapter.mjs` (dump -> disc data), `lib/dates.mjs` (blame line dates + cache, first-commit fallback, mtime), `lib/vendor.mjs` (vendored-bundle reader: strips unreachable network calls, count-gated),87`shell.html` + `page.html` + `page.css` + `page.js` + `vendor/` (the vault-graph page, MIT -- see `UPSTREAM.md` for the pin and the tagged hunks),88`vendor/hljs/` (highlight.js core + per-language grammars, BSD-3-Clause -- `BUILD.md` there has the recipe; only grammars for languages present in the dump are inlined),89`assets/logo-mask.svg` (the mark in the hub, painted with the wedge colours).