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

project-map-mcp

v0.1.0

Published

MCP server giving coding agents persistent, queryable architectural memory of a codebase.

Readme

project-map-mcp

An MCP (Model Context Protocol) server that gives a coding agent persistent, queryable architectural memory of a codebase.

The map is a graph of small linked YAML files stored in your repo under .projectmap/. The server reads and updates it — it never invents it from scratch on each run.

This is a memory/context product, not a validation product. Change-checking is advisory signal for a human or agent to act on, never an automated hard gate.

How it works

  1. Read the relevant slice of the map before working on something (get_context).
  2. Check a diff against declared boundaries and invariants after acting (check_changes).
  3. Propose updates to the map when a change is architectural, so the map stays current instead of rotting (propose_map_update).

Install

npm install
npm run build

The map on disk

.projectmap/
  index.yaml            # node registry + edge list (the graph spine)
  modules/
    billing.yaml         # one file per module node
    auth.yaml
    ledger.yaml
  invariants/
    payments.yaml         # reusable invariant sets, referenced by nodes
  workflows/
    checkout.yaml         # optional: key end-to-end workflows
  symbols/
    billing.yaml           # generated by describe_symbols — never hand-edit

Bootstrap one from an existing codebase with:

npx project-map init --root /path/to/repo

This scans top-level source directories, fills in owned_paths automatically, and derives depends_on edges from the import graph. responsibility, invariants, and sensitive are left as TODO placeholders — write those by hand; they're the semantic layer no tool can infer.

Re-run drift detection any time with:

npx project-map sync --root /path/to/repo

Running the MCP server

PROJECTMAP_ROOT=/path/to/repo npx project-map-mcp

(or pass the repo root as the first CLI argument; defaults to process.cwd()). Registers five tools over stdio: get_context, check_changes, propose_map_update, impact_of_change, describe_symbols.

check_changes's optional LLM summary and describe_symbols's purpose generation use the Anthropic API — set ANTHROPIC_API_KEY, or call check_changes with includeLlmSummary: false. describe_symbols only needs a key if some of your exported symbols have no JSDoc; JSDoc'd symbols never touch the API.

impact_of_change — the blast radius of a change

get_context answers "what should I know before editing this" with a 1-hop, bidirectional neighbourhood. impact_of_change answers a different question — "what breaks if I change this" — by walking depends_on edges transitively, in the direction of dependents only, as far as the graph goes (or maxDepth hops, if you cap it). Each impacted node comes back with its shortest dependency chain back to the target and its sensitive flag, so a widely depended-on, sensitive node stands out immediately. It also flags any depends_on cycles touching the impact set — those are a real architectural smell (the dependency graph is supposed to be a DAG) that's easy to miss by reading code but falls straight out of this traversal.

Call it before touching a node other things depend on, not after — that's the point: know the blast radius before you make the change, not once check_changes tells you what broke.

describe_symbols — the function/method dictionary

The map alone tells an agent which module owns something, not what's callable in it — so get_context still meant grepping the actual files to find out. describe_symbols closes that gap: it extracts every exported function/class/method in a module (name, signature, file:line) via a deterministic TypeScript AST parse — no type-checker, no tsconfig resolution, so it's robust against any target repo — and attaches a one-line purpose to each one.

That purpose comes from two places, in order:

  1. Existing JSDoc, read straight from the source. Free, instant, no API call.
  2. An LLM call, only for exported symbols that have no JSDoc.

Both structural facts (signature, location) and purpose are cached in .projectmap/symbols/<node>.yaml by content hash — a symbol's hash changes if its signature or body changes, so re-running describe_symbols after edits only re-describes what actually changed (or newly lacks JSDoc), never a full rescan-and-redescribe. This is a generated file; don't hand-edit it.

get_context always shows a module's symbol surface (includeSymbols: true by default) — signatures live, straight from the AST, every call; purposes from whatever's cached. It never triggers an LLM call itself. describe_symbols is the one tool in this package that costs API calls, so it's a separate, deliberate step — run it after a batch of changes, not as a side effect of every get_context.

CLI

project-map init                    # scaffold .projectmap/ from repo structure
project-map sync                    # report drift, write nothing
project-map check [--strict]        # run check_changes against staged git changes
project-map impact <node-id> [--max-depth N]
                                     # print the blast radius of changing a node
project-map describe-symbols [<node-id>]
                                     # build/refresh the exported-symbol dictionary
project-map install-hook [--pre-commit]
                                     # install the advisory git hook (default: pre-push)

--strict exits non-zero when check_changes reports a severity: error finding. Without it, the CLI (and the installed git hook) is always advisory: it prints findings and exits 0.

Core design principles

  • The graph is dumb; the tools are smart. Nodes, edges, and path ownership only. All intelligence lives in the tools that read the graph.
  • Partial retrieval is the whole point. Agents load a subgraph (a node plus its 1-hop neighbours), never the entire map.
  • Structural facts are auto-derived; semantic facts are hand-written. Never ask an LLM to infer what can be read deterministically from the codebase.
  • Deterministic first, LLM second. Boundary/invariant checks are deterministic graph traversal. The LLM only summarises semantic change for humans, and its output is advisory — it never sets the verdict and never invents invariants not present in the map.

Non-goals / guardrails

  • check_changes's verdict gates only on deterministic severity: error findings, and only under --strict. The tool never blocks work by default.
  • The LLM never sets the verdict and never invents invariants.
  • The map is never auto-derived wholesale by the LLM at runtime; structural sync (init/sync) is deterministic, semantic content is human-authored.
  • init/sync's import-graph derivation targets TypeScript/JavaScript repos. Other languages can still use the map (paths + hand-written semantics); only that specific derivation is JS/TS-specific.
  • No VS Code extension in v1 — the logic lives in src/core and src/tools; a future extension can wrap it.
  • describe_symbols never uses the LLM for signatures, names, or locations — those come from a deterministic AST parse and are always ground truth. The LLM only ever supplies a one-line purpose, and only for symbols with no JSDoc; that purpose is advisory, same as check_changes's summary.

Repo layout

src/
  schema/    # zod defs + types for nodes, edges, the graph, the symbol cache
  core/      # loadGraph, resolveFiles, neighborhood, invariantsFor, getDiff,
             # impact (blast radius + cycle detection), symbols (AST extraction),
             # symbolCache (hash-diff + persistence), ...
  tools/     # get_context, check_changes, propose_map_update, impact_of_change,
             # describe_symbols
  mcp/       # MCP server, stdio transport
  llm/       # Anthropic wrappers + prompt templates (advisory summaries/purposes only)
  cli.ts
test/
  fixtures/.projectmap/   # billing/ledger/auth/checkout-api + payments invariant set
  fixtures/src/           # small real .ts files backing the symbol-extraction tests
  *.test.ts

Testing

npm test