Skip to main content
Glama

code-map

Generates a code-map for a TypeScript/JavaScript codebase: a JSON document that describes how one path through the code runs. It is built for agents first:

  • Agents read the document's system section: a plain-language sitemap of the code (summary, areas, every step with its file, lines, description, calls and data, and the links between services). CI can rebuild it to keep system docs current.

  • People open the same file in the viewer, which draws it as one page.

Make maps with the CLI or the MCP server (code-map-mcp); both write the same file. Open them in the hosted viewer (file upload, with the 20 most recent files kept in your browser; in a private window, or with site storage blocked, they last only until the page reloads), locally with code-map view, or as a standalone .html.

Install

code-map runs on Bun (1.1 or later); it is TypeScript run directly, with no build step.

# The MCP server, for Claude Code in every project
claude mcp add --scope user code-map -- bunx --package @eronsalling23/code-map code-map-mcp

# The CLI, without installing
bunx @eronsalling23/code-map <repo> -e <entry> -o map.json

# Or install both commands (code-map, code-map-mcp)
bun add -g @eronsalling23/code-map

The viewer shows

  1. What you're looking at. What the code does overall and where it sits in the system, from the repo's README and package description. Then the areas the path runs through, in order (e.g. deploy → gcs → client). Each area quotes what the repo's own docs say about it and names the source. The hand-offs between areas are listed under that.

  2. A system diagram. The Call path tab shows every function and method on an execution path, numbered by step and grouped into boxes per file. Data types (interfaces, type aliases, enums, classes) appear as cylinders. Solid arrows are calls. Dotted arrows show data a step takes (param), returns, creates or uses. The Files tab shows how the files on the path import each other.

  3. A walkthrough. The path the code takes when it runs: a depth-first walk of the call graph from the entry point, in source order. It follows every call it can reach within the scope you choose, so the map is complete and the same code gives the same file. Each step puts a plain-language description beside the code for that step. The call lines that lead to the next steps are highlighted and linked, and long functions are cut down to the signature plus the lines around those calls.

  4. Tabs beside each step. Code is the excerpt above. Data has one sub-tab per type the step takes, returns, creates or uses, showing its definition. Calls has one sub-tab per function it calls, showing a preview with a link to its step. Full source appears when the excerpt is cut. Clicking a data chip or a call name in the description opens the matching tab.

  5. A data section. Every data type the path touches, as a list on the left and the definition on the right, with links back to the steps that use it.

Diagram controls: zoom with the − / + / Fit / Entry buttons, Ctrl/⌘ + scroll, a trackpad pinch or the + - 0 keys. Drag to move around; ⤢ goes full screen, where a plain scroll pans. Hover a box to highlight what it calls (blue) and what calls it (orange), with everything else faded. Hover an arrow to highlight both of its ends. Click a box or arrow to jump to its step. As you scroll the walkthrough, the current step's box is highlighted.

The call path has three more controls:

  • Compact hides the data types and their dotted arrows, leaving only the functions and the calls between them.

  • Full path highlights the route the walkthrough takes: each step's box and the arrow that leads into it, from step 1 to the last. Everything else fades. It works in both modes.

  • ◀ / ▶ (or the ← / → keys) step through the functions and methods in walkthrough order. Each move highlights the arrow into the new step, keeps the arrows walked so far highlighted, and glides the view along that arrow until the box is centred. Click the step's name to read it in the walkthrough; × or Esc stops. While stepping, a panel beside the diagram shows the step's description, signature and calls, the same text as in the walkthrough; its "step N" links move the navigator there. Details or the panel's × hides it, and the page remembers that choice. The view centres each box in the space the panel leaves free.

The bar. A bar stays at the top of a map with its title, links to its sections (Overview, Map, Code, Data) and the levels control. In the hosted viewer, ← goes back to your recent files; open another file from there.

Levels. A map keeps every step, so a big one can be long. The Steps control in the bar (All, 1+, 3+, 5+, 10+, with the count shown, e.g. 14/73; hover it for an explanation) hides helper steps that have fewer than N steps beneath them, using each step's descendants. The entry and other starting points always show, and so does every step above a shown one. Step numbers stay the same, so a gap in the numbering means hidden steps. In the walkthrough, a step with hidden helpers has a "N helper steps hidden" button; in the call path, a dashed "N hidden" box. Either one shows that group. The viewer remembers the setting.

Each tab's content is built the first time the tab opens, so a type shown in ten steps doesn't make the page ten times bigger.

Related MCP server: clew

Where the descriptions come from

Areas and system context (src/context.ts). An area is the nearest folder above a file that has its own README.md, ARCHITECTURE.md or package.json. Without one, it's the first folder below src/, packages/, modules/ and similar. Each area's text comes from the first of these that exists:

  1. The intro of its own ARCHITECTURE.md or README.md (text before the first ##, or under an Overview/Purpose heading).

  2. The row, bullet or paragraph in the repo's README.md, ARCHITECTURE.md or docs/ that names it, such as a module map table.

  3. Its package.json description.

  4. The doc comment on a class in it.

  5. A file's header comment.

  6. The first paragraph of its own docs, with the section named.

A repo is described by its README's opening paragraph, falling back to its package.json description.

Steps and summary: By default the text is put together from what the analysis found, with no network calls (describeStep in src/describe.ts). It covers the first sentence of the doc comment (or the function name split into words), how the step is reached, the data it takes, returns, creates or uses, what it calls in order, and the outside code it calls. For better text, an agent or a person writes the descriptions into a template (see Writing the descriptions).

Usage

The CLI is the source of truth: every command is defined once in src/commands.ts, and the MCP server exposes the same commands as tools (see MCP server). code-map <command> --help lists a command's options; --json prints its result as JSON.

Command

MCP tool

build <dir>…

build

Build the map and write .json and/or .html. code-map <dir>… is short for this

trace <dir>…

trace

Trace the path and write the fill-in template (--template <file>)

entries <dir>…

list_entries

Likely entry points: functions nothing calls, ranked by how much code they reach

symbols <dir>… -s <spec>

list_symbols

What a spec or glob matches, with caller and callee counts

check <descriptions.json>

check_descriptions

The plain-English checker

read <map.json>

read_map

The system section of a map (--no-steps for the summary only)

guide <name>

guide

The agent guides in docs/agents/

view [<map.json>]

Serve the viewer on localhost (CLI only)

bun install
bun run src/cli.ts <dir> [<dir> ...] [options]     # writes code-map.json
bun run src/cli.ts view code-map.json              # opens it in the viewer on localhost
# Find likely entry points (functions nothing calls that reach the most code)
bun run src/cli.ts entries ../game-manager -i src

# Map one flow
bun run src/cli.ts ../game-manager -i src -e PromoteGameHandler.execute -o promote.json

# The same, plus a standalone page that opens anywhere
bun run src/cli.ts ../game-manager -i src -e PromoteGameHandler.execute -o promote.json -o promote.html

# Start from a file's top-level code
bun run src/cli.ts ./my-app -e src/server.ts

# Several repos as one system: calls into another repo's package link to its source
bun run src/cli.ts ../wolf-editor ../game-sdk -i src -i packages/editor-kit/src -e gameCommandKind

# Only an exact set of methods (file, inline list, or both)
bun run src/cli.ts ../wolf-editor ../game-sdk -s flow.txt -s "editor-kit/src/args.ts#*"

Options for build (trace, entries and symbols share the scope options):

Option

-e, --entry

file#name, Class.method, a bare name, or a file path. When omitted, the tool uses the uncalled function that reaches the most code.

-s, --select

Map only these symbols (repeatable). Takes a selection file or an inline comma-separated list; see below.

-o, --out

Where to write the map (repeatable). .json writes the code-map document (default code-map.json); .html writes the viewer with the map built in

-i, --include

Analyze only this sub-path (repeatable)

--tests

Include *.test.* / *.spec.* files

--max-hops

With --select: the most unselected functions to look through between two selected ones (default 6)

--descriptions <file>

Use a filled-in template's title, summary, role, overview, area and step descriptions, and links in place of generated text

--title <text>

Page title, when the descriptions file has none

--strict

With --descriptions: exit 1 on any warning, e.g. a step with no written description (for CI). CLI only

--raw <file>

Also write the raw analysis (every symbol, call and import) and walkthrough as JSON; the viewer opens this too. CLI only

The walk follows every call reachable from the entry or selection inside the analyzed files. It never drops steps, and there is no depth or step limit to tune (--max-depth and --max-steps were removed). To focus a map, narrow the scope: -i for fewer folders, -s for an exact set of functions, or -e for a later starting point. In the viewer, people hide small helpers with the levels filter. A walk over 2000 steps fails with an error rather than writing part of a map; narrow the scope and run it again.

Several repos

Pass more than one directory and they are analyzed as one system. Files are shown under each repo's folder name (wolf-editor/src/...), and the diagrams group them inside a box per repo. -i paths apply to every repo that has them.

The TypeScript compiler resolves a call into another repo's package (e.g. @wolfgames/editor-kit) to that package's built .d.ts, not its source. code-map reads every package.json in the analyzed repos, workspace packages included. When a call lands in one of those packages' type files, it is linked to the source function or method of the same name in that package. These links are drawn as thick arrows and tagged "by name" in the walkthrough.

Selecting an exact set of methods

--select limits the map to the symbols you list. Only those become steps. A call that passes through functions you did not select becomes one arrow labelled via a → b, and the highlight stays on the line in the selected function where the chain starts. Selected functions that no other selected one leads to start their own part of the walkthrough, so every selected function appears. Selected data types (interfaces, types, enums) appear in the diagram and the Data section. --entry still works with a selection: if the entry is not selected, the first selected functions it reaches become the starting points.

A selection file has one spec per line; lines starting with # or // are comments. It can also be a JSON array of specs. Any existing file that isn't a source file (.ts, .tsx, .js, …) is read as a selection file; a source file path selects everything in it.

# Checkout flow, without pricing internals
src/server.ts#handlePost
OrderService.*
OrderRepo.save
src/types.ts#Order
editor-kit/src/args.ts#*
src/api/payments.ts

Spec

Selects

src/server.ts#handlePost

One symbol; the path can be partial (server.ts#handlePost) and can start with the repo name

OrderRepo.save, handlePost

One symbol by name; ambiguous names are an error that lists the candidates

OrderService.*, src/api/*.ts#handle*

Every match of the glob (* stays inside one path segment, ** crosses them)

src/api/payments.ts

Everything in that file

Writing the descriptions yourself (or with an agent)

bun run src/cli.ts trace ../game-manager -i src -e PromoteGameHandler.execute --template promote.desc.json
# fill in the empty "description" fields (and title, summary, role, overview) in promote.desc.json
bun run src/cli.ts check promote.desc.json      # plain-English check: long sentences, jargon, copied docs
bun run src/cli.ts ../game-manager -i src -e PromoteGameHandler.execute -o promote.json --descriptions promote.desc.json

Steps are matched by function id, so the file still applies after the code changes, as long as those functions still exist. links adds connections the analysis can't see, such as an HTTP request between two services or a file one writes and another reads: {"from": 3, "to": 12, "label": "uploads bundle → CDN"}. They are drawn as dashed arrows and listed under "Connections outside the code". Any step, area or link that doesn't match prints a warning.

The MCP server runs this whole loop for any agent (see below). Its instructions and guide tool tell the agent how to pick the code (from paths, or from a system name like "dynamic data across game-manager and game-sdk"), read it, and write the descriptions in plain English.

The code-map document

{
  "format": "code-map", "version": 2,
  "generator": { "args": ["build", "../game-manager", "--include", "src", "--entry", "…"] },   // the CLI command that rebuilds it
  "system": {          // for agents: read this, stop before "render"
    "title", "summary", "role", "overview", "entry", "repos",
    "areas":  [{ "id", "name", "description", "doc", "docSource", "steps": [1, 2] }],
    "steps":  [{ "step", "id", "name", "kind", "file": "src/x.ts:12-40", "area", "signature",
                 "description", "calledBy", "parent", "descendants",
                 "calls": [{ "id", "name", "step" }], "data": [{ "id", "name", "relation" }],
                 "outsideCalls", "docComment" }],
    "data", "links", "handoffs"
  },
  "render": { … }     // for the viewer: the walkthrough, the source of every symbol shown, diagram sources
}

The file has no timestamp, so two builds from the same code and arguments are byte-identical. Each step's parent is the step number (from 1) of the step that first called it; descendants counts every step beneath it, at all levels. A step reached from several callers counts under its first caller only. The viewer also opens raw --raw dumps. Version-1 files are refused; build them again.

Viewer

One page (src/viewer/, a React app) draws any code-map document. Bun bundles it, with its CSS, into one self-contained HTML file; the only things it loads from the network are Mermaid and highlight.js from a CDN.

  • Hosted: bun run build:viewer writes dist/index.html, a single static file to put on any static host. People drop or pick a .json file; it's read in the browser, never uploaded. The last 20 files are kept in the browser's IndexedDB and listed on the start page (#map=<id> links reopen one).

  • Local: code-map view [map.json] [--port 4173] serves the viewer on localhost and opens the file. Reload to see a rebuilt map.

  • Standalone: -o map.html writes the viewer with the map built in.

MCP server

code-map-mcp speaks MCP over stdio. Add it to Claude Code with:

claude mcp add --scope user code-map -- bunx --package @eronsalling23/code-map code-map-mcp
# or, from a clone of this repo
claude mcp add --scope user code-map -- bun run /path/to/code-map/src/mcp.ts

Its instructions tell the agent the workflow: pick the code, trace, read every step's code, write the descriptions in plain English, check, build. Each tool is a CLI command (see Usage): its input schema is built from the command's options, and a call runs the CLI with --json, so a tool and its command can't drift apart. Options keep their CLI meaning under camelCase names (dirs, include, tests, entry, select, maxHops, templatePath for --template, descriptionsPath for --descriptions); CLI-only options like --strict aren't exposed.

Tool

guide

The workflow, the writing rules, and how to find the code for a named system (docs/agents/; also served as code-map://guide/<name> resources)

list_entries, list_symbols

Ranked entry points; what specs and globs match

trace

Runs the walk and writes the fill-in template (or returns it inline); reports the step count and the steps with the most steps beneath them

check_descriptions

The plain-English checker

build

Writes .json / .html; returns warnings and the map's outline. generator.args in the file is the equivalent CLI command

read_map

The system section of an existing map, for agents learning a system

Documenting systems in CI

Commit a selection file and a filled-in descriptions file next to the code, then rebuild the map on each change:

bun run src/cli.ts . -i src -s docs/maps/orders.selection.txt \
  --descriptions docs/maps/orders.desc.json -o docs/maps/orders.json --strict

--strict fails the build when a described function no longer exists or a new step has no description, so the map can't silently go stale. Agents read docs/maps/*.json (or call read_map) to learn the system; people open the same files in the viewer.

How it works

File

Role

src/analyze.ts

Builds a TypeScript program with the compiler API and indexes functions, arrow-function consts, class methods/constructors/accessors, object-literal methods and data types. It resolves every call through the type checker, following imports, this., new and re-exports, and records the data types each symbol takes, returns, creates or uses. Calls that resolve outside the analyzed sources are kept as "outside code".

src/trace.ts

Resolves the entry and the --select specs, ranks candidate entries, and walks the call graph depth-first in source order. A function reached a second time links back to its first step instead of being repeated. With a selection, it walks through unselected functions to find the next selected one.

src/context.ts

Groups the steps into areas, finds each area's docs, and lists the hand-offs between areas.

src/describe.ts

Heuristic descriptions built from doc comments, the caller, data relations, calls in order and outside calls, so no network is needed. They are the drafts that written descriptions replace.

src/model.ts

One traced path as a model: the map and walkthrough with the lookups the other steps share (step of a symbol, data types and their users, repo of a file), plus dataIdsOf and descendantCounts. Pure.

src/pagedata.ts

Builds the code-map document: the system sitemap for agents, plus the render data (Mermaid diagram sources, the route, the symbols shown). toDocument reads documents and raw dumps; pathView makes the call path for a subset of steps, for the levels filter. Pure, so the viewer uses it too.

src/pipeline.ts

The steps from folders to document and output files, used by the commands.

src/viewer/

The React viewer: the start page (upload, recent files in IndexedDB, ?src= and #map= loading), the map page (map/), the diagrams with zoom, hover and the step navigator (diagram/), the levels filter (levels/), and build.ts, which bundles it into one HTML file.

src/command.ts, src/commands.ts, src/cli.ts

The command definitions (one per command: options, types, help, MCP tool name) and the machinery that turns them into the CLI parser, the MCP input schemas and the recorded generator.args; cli.ts picks the command and prints its result.

src/mcp.ts

The MCP server: one tool per command with a tool name, each running the CLI with --json.

src/check.ts, src/serve.ts

The plain-English checker, and code-map view.

Limits

  • tsconfig paths aliases (e.g. @contract/...) aren't read, so calls through them show as outside code.

  • Linking by name picks the exported symbol with that name inside the package. If a package has two exported symbols with the same name, the link can land on the wrong one.

  • Calls are resolved statically. Calls through interfaces, dependency injection tokens, event buses or dynamic dispatch stop at the declaration the type checker finds. Those show up as outside code or as the interface method, not as the implementation.

  • Calls inside callbacks and nested functions are counted as calls made by the enclosing named function.

  • Only the first visit to a function becomes a step. Later visits link back to it.

Releasing and CI

Workflows

Workflow

Starts on

What it does

CI (ci.yml)

Push to main, and every pull request

Type check and tests

Publish (publish.yml)

Run by hand on main

Raises the version, runs the type check and tests, publishes to npm, then deploys the hosted viewer from the same commit

Pages (pages.yml)

Run by hand on main

Deploys only the hosted viewer, for a viewer fix between releases

The hosted viewer deploys with each release, not on every push. That way it always matches the latest version on npm, and a change on main can't stop it from opening maps that the released CLI writes. A viewer fix shipped with Pages between releases must still open those maps.

Making a release

Never edit the version in package.json by hand. Run Publish from the Actions tab on main and pick patch, minor or major, or:

gh workflow run publish.yml -f bump=minor

The workflow:

  1. sets the next version in package.json, and stops if that version is already on npm;

  2. runs the type check and tests;

  3. commits chore(release): vX.Y.Z and tags it vX.Y.Z, in the job's copy of the repo only;

  4. publishes to npm with a provenance statement, only if that commit succeeded;

  5. pushes the commit and the tag to main together, only if npm accepted the package;

  6. builds the viewer from the release commit and deploys it to GitHub Pages.

If a step fails, nothing after it runs. Nothing leaves the job until step 4, so a failure before that leaves npm and main unchanged; fix the cause and run it again. The one gap is a failed push after a successful publish, for example when main moved during the run. Then npm has the version but main doesn't: pull, set package.json to that version, commit chore(release): vX.Y.Z, tag it and push both. Pushing a tag yourself doesn't start a release.

One-time setup

npm trusted publishing. Publish has no npm token. npm trusts the workflow itself: GitHub gives the job a short-lived identity token (OpenID Connect, OIDC), and npm accepts it because the package names this workflow as its trusted publisher. On npmjs.com, open the package's Settings → Trusted Publisher → GitHub Actions and enter:

Field

Value

Organization or user

esalling23 (the GitHub owner, not the npm scope)

Repository

code-map

Workflow filename

publish.yml

Environment

leave it empty; the publish job uses none

Or run this from a terminal; npm opens the browser for a two-factor check:

npm trust github @eronsalling23/code-map --file publish.yml --repo esalling23/code-map --allow-publish
npm trust list @eronsalling23/code-map    # check it

The entry belongs to one package name. Renaming the package, or the workflow file, means adding it again. Trusted publishing needs npm 11.5.1 or later, which the workflow installs.

GitHub Pages. In the repo's Settings → Pages, the source is GitHub Actions. The github-pages environment only allows deployments from main. Both workflows that deploy run on main, so it needs no change.

Troubleshooting Publish

  • npm error 404 Not Found - PUT https://registry.npmjs.org/@eronsalling23%2fcode-map after the tests pass: npm found no trusted publisher that matches this run. Check the entry above, especially the owner and the workflow filename, then run Publish again.

  • "X.Y.Z is already on npm": that version exists. Pick a larger bump.

  • The deploy step is rejected by the environment: the run wasn't on main. Start it from main.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      Gives your AI coding agent a bounded map of an unfamiliar TypeScript SaaS repo: where the code lives, what's risky to touch, and where the money / auth / user flows are. Provides eight MCP tools for orientation, task focus, risk assessment, and SaaS-specific observations without AST or type checking.
      14 npm
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Provides coding agents with a compact map of a TypeScript/TSX codebase, enabling direct answers about symbol locations, imports, dependencies, and references while reducing token usage.
      5
      14 npm
      2
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables coding agents to explore TypeScript, JavaScript, and Prisma projects through local symbol, call, and model-usage graphs, while recalling persistent project-scoped decisions and returning focused source context.
      1
      MIT