io.github.raymondchins/agentmap
The TS/JS-accurate repo map for coding agents: ts-morph import/symbol/call graph + MCP query tools
Open source Open in the app JSON README (API)
About
The TS/JS-accurate repo map for coding agents: ts-morph import/symbol/call graph + MCP query tools
Details
- Kind
- MCP servers
- Topic
- Maps, weather & travel
- Publisher
- raymondchins
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 0.22.0
- Stars
- 48
- Forks
- 11
- Open pull requests
- 2
- Last push
- 2026-09-05T09:32:38Z
- Repository state
- ativo
- Language
- JavaScript
- License
- MIT
- Added
- 2026-08-29 04:01:16
- Updated
- 2026-08-29 04:01:16
- Origin id
io.github.raymondchins/agentmap
README
<p align="center">
<img src="assets/hero.png" alt="agentmap — 98% fewer tokens for a coding agent to find your code" width="100%">
</p>
<p align="center">
<a href="https://www.npmjs.com/package/@raymondchins/agentmap"><img src="https://img.shields.io/npm/v/@raymondchins/agentmap" alt="npm"></a>
<a href="https://github.com/raymondchins/agentmap/actions/workflows/ci.yml"><img src="https://github.com/raymondchins/agentmap/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-green" alt="License: MIT"></a>
<a href="#"><img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node >= 20"></a>
<a href="#"><img src="https://img.shields.io/badge/runtime%20deps-1-2EE0A8" alt="1 runtime dependency"></a>
<a href="#"><img src="https://img.shields.io/badge/network%20calls-0-2EE0A8" alt="zero network calls"></a>
</p>
# agentmap
### Your agent burns most of its context just finding code. This gives it the answer in one line.
```bash
npx @raymondchins/agentmap --relates lib/db/schema.ts
```
```
relates: lib/db/schema.ts (pr 0.073744)
dependents (21): lib/types.ts, lib/utils.ts, lib/db/queries.ts,
components/chat/message.tsx, app/(chat)/api/chat/route.ts, …
```
Every file on that list really imports it. `grep` gets **40% of them wrong**.
---
## 💸 What it saves
Token cost of the hidden first step in every agent task — *find the relevant code* —
on a real 154-file Next.js app ([vercel/ai-chatbot](https://github.com/vercel/ai-chatbot), sha `2becdb4`):
| The agent needs to know… | Reading files | agentmap | Saved |
|---|---:|---:|:---:|
| Does a helper for this already exist? | 14,740 | 19 | **99.9%** |
| Load the whole repo into context | 150,281 | 1,127 | **99.3%** |
| What breaks if I change this file? | 81,038 | 616 | **99.2%** |
| Where is this symbol defined? | 1,950 | 20 | **99%** |
| What files make up this feature? | 6,121 | 1,025 | **83.3%** |
| Give me a repo overview | 3,065 | 1,127 | **63.2%** |
| What does this one file import? | 583 | 517 | **11.3%** |
| **All 7 combined** | **257,778** | **4,451** | **98.3%** |
Holds on [zod](https://github.com/colinhacks/zod) too (367 files, **99.2%**) and
[taxonomy](https://github.com/shadcn-ui/taxonomy) (125 files, **96.0%**).
Captured output, pinned shas → [`benchmark/RESULTS.md`](./benchmark/RESULTS.md)
## 🎯 …and it's still right
Fewer tokens is worthless if they're the wrong ones. Separate eval, ground truth derived
live from real repos:
| | agentmap | `git grep` |
|---|:---:|:---:|
| What depends on this file? | **100%** precision | 59.9% precision |
| Where is this defined? *(top-1)* | **100%** | 32% |
| Where is this defined? *(top-3)* | **100%** | 80% |
| Tokens to find a definition | **1.9× fewer** | — |
<sub>n=42 dependents / n=75 definitions across <a href="https://github.com/colinhacks/zod">zod</a>, <a href="https://github.com/pmndrs/zustand">zustand</a>, <a href="https://github.com/honojs/hono">hono</a>. Re-run: <code>npm run eval</code> · method → <a href="./EVAL.md">EVAL.md</a></sub>
---
## ⚡ The five commands
| You want | Run | Saves |
|---|---|:---:|
| "Do we already have this?" | `agentmap --find formatCurrency` | 99.9% |
| "What breaks if I touch this?" | `agentmap --relates lib/auth.ts` | 99.2% |
| "Where is this defined?" | `agentmap --find ChatMessage` | 99% |
| "Give me the repo, cheap" | `agentmap --map --tokens 2000` | 99.3% |
| Don't want to pick? | `agentmap --any <anything>` | — |
`--any` routes it for you: file → symbol → feature → live content search.
Cold build **~1.2s**. Cached query **~0.1s**. No server, no vector DB, no API key.
---
## 🔌 Setup
```bash
npx @raymondchins/agentmap --install-hooks # rebuild on commit + steer the agent to the map
npx @raymondchins/agentmap --install-skill # Claude Code · Cursor · Codex · Gemini · OpenCode · Copilot
```
Most repo-map tools stop at building the map. These two hooks are why it stays useful: the
map **rebuilds itself after every commit**, and the agent gets **nudged to the map the
moment it reaches for a dependency-shaped grep**. Claude Code users can get both from the
[plugin](#4-claude-code-plugin-one-command-bundle).
> **100% local.** Zero network calls, zero telemetry — not one `fetch`/`http` in the source.
> ⚠️ Install the **scoped** name; unscoped `npx agentmap` is someone else's package.
<details>
<summary>Where the cache lives, and how it stays fresh</summary>
<br>
First run caches to `.claude/agentmap/map.json` (`--install-hooks` gitignores it). Later runs
serve that cache **only** on a clean tree at an unchanged `HEAD` — with uncommitted
`.ts/.tsx/.js/…` edits it silently rebuilds, so you never query a stale snapshot.
```
$ npx @raymondchins/agentmap
agentmap: 154 files | 4 features | top hub: lib/utils.ts (deg 52, pr 0.105171)
```
From a checkout, every command also works as `node agentmap.mjs …`.
</details>
---
## 🧠 Why the answers are right
Built on **`ts-morph` — the real TypeScript compiler**, not text matching or tree-sitter
guessing. It resolves `tsconfig` path aliases, `vite`/`webpack` aliases, `#imports` subpaths,
and monorepo workspaces. Where `grep` sees a string, agentmap sees the resolved module.
That's also why barrels don't fool it: `export * from "./x"` looks identical to a real
definition to a text search, so your agent edits the re-export and changes nothing. agentmap
follows the chain and names the file that actually declares it.
<details>
<summary><b>The honest asterisks</b> — read these before quoting a number</summary>
<br>
- **The win scales with the work.** The 63% and 11% rows are the floor. A *trivial
single-file* lookup can cost **more** than `cat` + `grep` — taxonomy's file-import task hit
**−313%**, and it stays in the table.
- **The 98.3% headline is carried by its two biggest rows** — repo dump (150,281 → 1,127) and
blast radius (81,038 → 616). Drop the repo dump and it's **96.9%**; drop both and it's
**89.8%** here, **93.7%** pooled across all three repos, and **73.1%** on the smallest one.
All of those are real — they answer different questions. The headline is the common worst
case: an agent dumping the repo at session start.
- **`--relates` returns the full blast radius**, so it costs *more* than a bare `grep -l` file
list. That's why the same command reads as 99.2% *saved* in the benchmark and *more
expensive* in the eval: the benchmark's baseline is an agent that `cat`s all 65 dependent
files, the eval's is a file list nobody reads. Against the list, agentmap trades tokens for
precision — 100% vs 59.9%, so ~4 in 10 files on the grep list don't belong.
Complete-and-correct over short-and-wrong, but it is a trade → [EVAL.md](./EVAL.md).
- **Numbers are context-token volume**, not answer quality or wall-clock.
- **Token counts are estimates** (`chars / 4`), applied identically to both sides.
- **TypeScript/JavaScript only** (+ Vue SFC) — see [Scope & limitations](#scope--limitations).
</details>
---
## Why it's different
Many "repo context" tools are a photocopy: they dump your repository (or a slice of it) into
the prompt once and walk away — the copy goes stale the moment you edit a file, and nothing
makes the agent actually read it. agentmap is queryable and ranked instead: the agent
interrogates it flag-by-flag rather than swallowing a dump.
It also reports an `edgeCoverage` map-health signal and warns loudly when a repo's imports
mostly *don't* resolve, so a broken map is never quietly framed as success.
The self-refreshing side — a post-commit rebuild plus a `PreToolUse` hook that steers the agent
to the map before it serial-greps — is genuinely useful, but it isn't unique: **CodeGraph**
([colbymchenry/codegraph](https://github.com/colbymchenry/codegraph), ~62k★ (2026-07-26)) ships a native
OS-event file watcher (FSEvents/inotify) with debounced auto-sync and an installer that
auto-configures eight agent CLIs. agentmap's honest edge over the multi-language graph tools is
narrower and sharper: **TS/JS resolution the others approximate, with a published accuracy eval.**
| | **agentmap** | [Aider repo map](https://github.com/Aider-AI/aider) | [RepoMapper](https://github.com/nuptcode/repomapper) | [Repomix](https://github.com/yamadashy/repomix) | [code2prompt](https://github.com/mufeedvh/code2prompt) |
| --- | --- | --- | --- | --- | --- |
| **Ranking algorithm** | Personalized PageRank (file + symbol graphs) | PageRank (graph ranking) | Importance heuristics | None (file order) | None (file order) |
| **Languages** | TS/JS + Vue SFC (via ts-morph) | Many (tree-sitter) | Many (tree-sitter) | Language-agnostic (text) | Language-agnostic (text) |
| **Token-budget output** | Yes — `--map [--tokens N]` ranked digest | Yes (built into Aider's context) | Partial | Yes (size caps) | Yes (templates/caps) |
| **TS/JS resolution depth** | **Compiler-grade — `tsconfig` paths + `vite`/`webpack` alias + `#imports` + workspaces (ts-morph)** | Basename/regex heuristics | Basename/regex heuristics | N/A (text) | N/A (text) |
| **Retrieval-accuracy eval** | **Yes — published [`EVAL.md`](./EVAL.md) vs live ground truth** | No | No | No | No |
| **Agent-loop wiring** | Yes — post-commit auto-refresh + PreToolUse hook | In-process (Aider only) | No | MCP server (no auto-refresh, no nudge) | No |
| **Dependencies** | `ts-morph` only | Python + tree-sitter stack | Python + tree-sitter | Node | Rust binary |
| **Install** | `npx @raymondchins/agentmap` | `pip install aider-chat` | `pip install` | `npx`/global | `cargo`/binary |
<sub>Comparison as of <b>2026-07-27</b>, from each project's own docs. These are moving targets — if a
cell is out of date, that's a bug: <a href="https://github.com/raymondchins/agentmap/issues">open an issue</a>.</sub>
What that table is **not** claiming: agentmap is TS/JS-only (the others are multi-language),
and it's a **file-level import graph**, not a full call-site/reference resolver (see
[Scope & limitations](#scope--limitations)). The differentiators are narrow and honest:
**(1)** compiler-grade TS/JS resolution (aliases, `vite`/`webpack`, `#imports`, workspaces) with a
published accuracy eval, and **(2)** the `--any` router. The agent-loop wiring is real and
convenient but **not** unique — [CodeGraph](https://github.com/colbymchenry/codegraph) and others
auto-sync and auto-configure agent CLIs too; we don't claim it as a moat.
---
## The agent loop (staying current, staying used)
A common failure of repo-map tools: they build a beautiful map, and then the
agent forgets it exists and greps anyway. A map the agent doesn't open is just dead weight.
agentmap closes that loop. Two hooks (in [`./hooks/`](./hooks/)) do the work: the map
**refreshes itself after every commit**, and the agent gets **nudged to query it before it
serial-greps**. You wire it once — then it stays current on its own, and stays used.
> This wiring is table stakes, not the moat — [CodeGraph](https://github.com/colbymchenry/codegraph)
> and other tools also auto-sync (via native OS file watchers) and auto-configure agent CLIs.
> agentmap ships it because it's genuinely useful; the actual point of agentmap is the
> **compiler-grade TS/JS accuracy** the map is built on.
### 1. Auto-refresh on commit
[`hooks/post-commit`](./hooks/post-commit) rebuilds `.claude/agentmap/map.json` after each
commit, detached + silenced so it never slows the commit. It skips during
rebase/merge/cherry-pick and no-ops if Node is missing.
The hooks ship inside the npm package. The simplest setup:
```bash
npx @raymondchins/agentmap --install-hooks
```
This copies `hooks/post-commit` into `.git/hooks/`, sets it executable, ensures
`.claude/agentmap/` is in `.gitignore`, and **auto-wires the `PreToolUse` nudge
hook into `.claude/settings.json`** (merge-safe + idempotent) so map enforcement is
on by default — no manual paste. Manual alternative for just the post-commit hook:
```bash
# from your repo root
cp hooks/post-commit .git/hooks/post-commit
chmod +x .git/hooks/post-commit
```
The hook resolves the builder to the **installed** package — `node_modules/.bin/agentmap`,
a PATH `agentmap` binary verified to be `@raymondchins/agentmap`, then
`npx @raymondchins/agentmap`. It never runs a repo-local `./agentmap.mjs` unless you opt in
with `AGENTMAP_HOOK_ALLOW_LOCAL=1` (for developing agentmap itself), so an
attacker-planted `agentmap.mjs` can't execute on your next commit.
### 2. Force the agent to use it — `PreToolUse` hook
[`hooks/agentmap-nudge.mjs`](./hooks/agentmap-nudge.mjs) is a **non-blocking** hook for
Claude Code that covers **both** the `Grep` tool and raw Bash text-searchers
(`grep`/`rg`/`egrep`/`fgrep`/`ag`/`ack`). When either looks like a dependency /
who-imports / component-usage / reuse / where-is-symbol search, it injects a reminder
steering the agent to `agentmap --any` first. It never denies the call, and stays silent
for raw-string / Tailwind-class / lowercase-HTML-tag sweeps and for pipe-filtered commands
like `ps aux | grep node` — so it's high-signal, not nagging.
**Fires on:** `import`/`require`/`export`/`from '...'` patterns, JSX component tags
(`<Hero`, `<ProviderCard`), explicit intent words (`where is`, `who imports`, `reuse`,
`existing component`), and — in both the `Grep` tool and the Bash branch — bare multi-hump
PascalCase identifiers (`ProviderCard`, `TopProviders`) that almost always mean "where is
this symbol / who uses it". The Bash branch additionally only fires when the searcher is the *primary* command (at the start,
or after `;`/`&&`); piped log-filters stay silent.
All four nudge/gate variants (this one, Codex, Gemini, OpenCode) also **self-gate on
project presence**: since they ship at user/global scope too (plugin bundle, `~/.gemini`,
`~/.codex`, `~/.config/opencode`), they walk up from the tool call's cwd to the
filesystem root looking for `node_modules/@raymondchins/agentmap` or a built
`.claude/agentmap/map.json` before doing anything else, so a repo with no agentmap stays
silent instead of nagging (or, for Codex, denying a grep it has no business denying).
`--install-hooks` writes both matchers into `.claude/settings.json` for you (merge-safe —
preserves existing settings, won't duplicate on re-run). The single hook file dispatches
internally on `tool_name`. For reference, or to wire it by hand:
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Grep",
"hooks": [{ "type": "command", "command": "node ./hooks/agentmap-nudge.mjs" }]
},
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "node ./hooks/agentmap-nudge.mjs" }]
}
]
}
}
```
That's the "forced to use it" in the tagline: the map stays current on its own, and the
agent is steered to it the moment it reaches for a dependency-shaped grep or Bash search.
### 3. Agent skills (Cursor, Claude Code, Codex, OpenCode, Gemini, Antigravity, Copilot)
```bash
npx @raymondchins/agentmap --install-skill
```
…or grab just the skill (no agentmap flags) via the [`skills`](https://github.com/vercel-labs/skills)
CLI — agentmap ships the `skills/agentmap/SKILL.md` layout it expects:
```bash
npx skills add raymondchins/agentmap
```
`--install-skill` copies packaged **SKILL.md** files and a **Cursor rule** (`.cursor/rules/agentmap.mdc`,
`alwaysApply: true`) into the current repo or global agent directories. Paths follow
each platform's official skill-directory conventions. Options:
```bash
agentmap --install-skill --platform cursor # Cursor rule + .cursor/hooks.json shell gate (project)
agentmap --install-skill --platform claude # .claude/skills/agentmap/SKILL.md
agentmap --install-skill --platform codex # .codex/skills/ (project) or ~/.codex/skills/ (global)
agentmap --install-skill --platform opencode # .opencode/skills/ (project) or ~/.config/opencode/skills/ (global)
agentmap --install-skill --platform gemini # .gemini/skills/ (project); global ~/.gemini/skills/ (Windows global: ~/.agents/skills/)
agentmap --install-skill --platform antigravity # .agents/skills/ (project) or ~/.gemini/config/skills/ (global)
agentmap --install-skill --platform copilot # .copilot/skills/ or ~/.copilot/skills/
agentmap --install-skill --global --platform claude # ~/.claude/skills/...
agentmap --install-skill --platform agents # legacy .agents/skills/ (project or global); excluded from default `all`
agentmap --install-skill --dry-run # preview paths, no writes
```
`--platform all` installs: claude, cursor, codex, opencode, gemini, antigravity, copilot (not legacy `agents`).
Some platforms also get **always-on** docs and hooks in the same command:
| `--platform` | Skill | Also installs (project) | Global docs |
|--------------|-------|-------------------------|-------------|
| `gemini` | `.gemini/skills/…/SKILL.md` | `GEMINI.md` + `.gemini/settings.json` BeforeTool nudge | `~/.gemini/GEMINI.md` |
| `codex` | `.codex/skills/…/SKILL.md` | `AGENTS.md` merge-safe `<!-- agentmap:begin/end -->` block | `~/.codex/AGENTS.md` |
| `opencode` | `.opencode/skills/…/SKILL.md` | `AGENTS.md` + `.opencode/plugins/agentmap-nudge.js` | `~/.config/opencode/AGENTS.md` |
| `cursor` | `.cursor/rules/agentmap.mdc` | `.cursor/hooks.json` `beforeShellExecution` gate + `.cursor/hooks/agentmap-cursor-nudge.mjs` | — (project-scope only) |
Codex and OpenCode share one repo-root `AGENTS.md` on project install. Existing content outside the marked block is preserved.
Pair with `--install-hooks` (Claude Code) or `--mcp` (Cursor MCP).
### 4. Claude Code plugin (one-command bundle)
Prefer the plugin over `--install-skill`/`--install-hooks` if you're on Claude Code and
want the skill, the `PreToolUse` grep/Bash nudge, and the stdio MCP server in a single
install that auto-updates:
```bash
# in Claude Code
/plugin marketplace add raymondchins/agentmap
/plugin install agentmap@agentmap
```
The plugin bundles: the packaged **SKILL.md**, the **PreToolUse nudge** (both the `Grep`
tool and Bash text-searchers, via `${CLAUDE_PLUGIN_ROOT}`), and the **stdio MCP server**
(`npx -y @raymondchins/agentmap --mcp`, so `ts-morph` is fetched on demand — the plugin
cache ships no `node_modules`).
> **One thing the plugin can't do: install the git `post-commit` hook.** Claude Code
> plugins can't write into `.git/hooks/`, so the auto-refresh-on-commit still needs a
> one-time `npx @raymondchins/agentmap --install-hooks` in each repo (it also wires the
> nudge into `.claude/settings.json`, harmlessly redundant with the plugin's copy).
> Without it the map still rebuilds on any dirty query — you just lose the commit-time
> refresh.
### Onboarding by platform
Enforcement isn't uniform — some CLIs get a **live hook** that actively steers grep to
agentmap, some get an **MCP server** the agent can call, and some are **docs-only** (a
skill/rule the agent may or may not consult). Honest matrix:
| Platform | Install | Enforcement | Known gaps |
|----------|---------|-------------|------------|
| **Claude Code** | `/plugin install agentmap@agentmap` (or `--install-hooks`) | **live hook** — `PreToolUse` nudge on `Grep` + Bash searchers | non-blocking (never denies grep); bare-symbol `Grep` nudge requires the #3 hook fix |
| **Gemini CLI** | `--install-skill --platform gemini` | **live hook** — `.gemini/settings.json` nudge | fires on `BeforeTool` and emits a top-level `systemMessage`; Gemini parses and then **drops** `hookSpecificOutput.additionalContext` on `BeforeTool`, which is why the nudge used to vanish silently |
| **OpenCode** | `--install-skill --platform opencode` | **log-only** — `.opencode/plugins/agentmap-nudge.js` writes to the log, does not inject context | plugin can't steer the model; relies on the `AGENTS.md` block being read |
| **Cursor** | `--install-skill --platform cursor` + `.cursor/mcp.json` (below) | **live gate** — `.cursor/hooks.json` `beforeShellExecution` hook, plus the `alwaysApply` rule and the MCP server | denies only high-confidence structural greps; allow-fallback for logs/pipes/non-TS-JS; `AGENTMAP_CURSOR_GATE=0` bypasses; project-scope only |
| **Codex CLI** | `--install-skill --platform codex` | **live gate** — `.codex/config.toml` PreToolUse hook | denies only high-confidence structural greps; allow-fallback for logs/pipes/non-TS-JS; `AGENTMAP_CODEX_GATE=0` bypasses; needs a trusted dir + Codex hooks-GA |
| **Copilot CLI** | `--install-skill --platform copilot` | **docs-only** — `.copilot/skills/` | same as Codex — no live hook yet |
**Cursor MCP — copy-paste `.cursor/mcp.json`** (Cursor's `--mcp` wiring is a documented
dead-end otherwise; drop this at your repo root):
```json
{
"mcpServers": {
"agentmap": {
"command": "npx",
"args": ["-y", "@raymondchins/agentmap", "--mcp"]
}
}
}
```
Then Cursor exposes the 11 query tools (`any`, `find`, `relates`, `map`, `hubs`,
`features`, `feature`, `symbols`, `search`, `callers`, `calls`). Run `agentmap --doctor` any time to see what's wired
vs missing.
### Uninstall
agentmap only writes files into your repo/home — remove them to fully uninstall. `agentmap
--doctor` lists every path it wrote, and every docs merge lives inside an
`<!-- agentmap:begin/end -->` (or `# agentmap:begin/end`) fence, so deleting just that block
leaves the rest of your `AGENTS.md` / `GEMINI.md` intact.
| Platform | Remove |
|----------|--------|
| Claude Code | `.claude/skills/agentmap/` + the agentmap `PreToolUse` block in `.claude/settings.json` |
| Cursor | `.cursor/rules/agentmap.mdc`, `.cursor/hooks/agentmap-cursor-nudge.mjs`, the `beforeShellExecution` entry in `.cursor/hooks.json`, + the `agentmap` entry in `.cursor/mcp.json` |
| Codex | `.codex/skills/agentmap/`, the `# agentmap:begin/end` block in `.codex/config.toml`, `.codex/hooks/agentmap-codex-nudge.mjs`, and the fenced block in `AGENTS.md` |
| OpenCode | `.opencode/skills/agentmap/`, `.opencode/plugins/agentmap-nudge.js`, the `AGENTS.md` block |
| Gemini | `.gemini/skills/agentmap/`, `.gemini/hooks/agentmap-nudge.mjs`, the `BeforeTool` hook in `.gemini/settings.json`, the `GEMINI.md` block |
| All | map cache `rm -rf .claude/agentmap/`; npm devDep `npm rm @raymondchins/agentmap`; the agentmap block in `.git/hooks/post-commit` |
### Troubleshooting
| Symptom | Cause / fix |
|---------|-------------|
| `features (0)` | `--features` only detects Next.js `app/` routes; a TanStack `src/routes/` repo legitimately shows 0. Use `--map` / `--symbols` instead. |
| Empty or wrong map | Usually no `tsconfig.json` / resolvable aliases in the target repo, so no edges resolved — run `agentmap --doctor` and check `edgeCoverage` in `--json`. |
| Stale-looking results | By design the map rebuilds from disk on a dirty tree / SHA mismatch. Force a rebuild by just running `agentmap`. |
| Codex/Gemini nudge never fires | Codex's gate is opt-in — set `[features] hooks = true` in `.codex/config.toml` (`AGENTMAP_CODEX_GATE=0` disables it). Gemini needs the `BeforeTool` hook that `--install-skill` writes. |
| Cursor gate blocks a grep you meant | Re-run the same command with `AGENTMAP_CURSOR_GATE=0` prefixed. It only denies high-confidence structural searches; logs, pipes and non-structural sweeps already fall through. Remove the `beforeShellExecution` entry from `.cursor/hooks.json` to turn it off for good. |
| Cursor gate never fires | It is project-scope only, so it must be installed from the repo root (`--install-skill --platform cursor`, not `--global`), and Cursor reads `.cursor/hooks.json` at startup — restart Cursor after installing. |
| Installed the wrong `agentmap` | This is **`@raymondchins/agentmap`** (npm scope) — not the unrelated unscoped `agentmap` packages. |
| Cursor MCP tools missing | `--mcp` doesn't auto-wire Cursor; add the copy-paste `.cursor/mcp.json` from the matrix above and restart Cursor. |
| Hook works in your shell, not in the agent | Almost always **nvm**. Your interactive shell sources `~/.nvm/nvm.sh`; the git hook and the agent's tool runner do not, so `node` isn't on their `PATH`. Point the hook at an absolute node (`which node`) or install a system-wide node. |
| `JavaScript heap out of memory` | Raise the ceiling — the parse peaks and there is no in-process warning that can fire in time (the process dies inside a single call, with heap use still at ~40% one sample earlier). Re-run as `NODE_OPTIONS=--max-old-space-size=8192 npx @raymondchins/agentmap`. Repo **size is not the axis**: measured, a 252-file Next.js app peaks at 683 MB while 4,000 dependency-free files peak at 756 MB, because the dependency `.d.ts` closure (~300 MB, ~1,800 extra program files on a 393-file app) dominates. A small repo with heavy `@types` can need more than a large plain one. |
| Skill file looks out of date | Each installed skill dir carries a `.agentmap_version`. `agentmap --doctor` compares it against the running version and flags the drift; `--install-skill` again overwrites it. |
| `0 files mapped` | agentmap indexes `git ls-files --cached --others --exclude-standard`, so uncommitted files *are* included but **`.gitignore`d ones are not** — a source tree matched by an ignore rule maps to nothing, as does a directory that is not a git repo at all. Confirm with `git ls-files --others --exclude-standard \| head`. |
---
## The `--any` router
Don't want to learn eight flags? You don't have to. Throw anything at `--any` — a filename, a
function, a feature, even a raw string — and it figures out what you meant, returning the first
layer that hits:
```
--any <query>
│
├─ 1. FILE exact path → unique basename → unique substring
├─ 2. SYMBOL exported name contains the query (across all files)
├─ 3. FEATURE app/-router feature name contains the query
└─ 4. CONTENT live `git grep` (tracked + untracked) — never stale
```
Layers 1–3 read the cached structural map (fast, ranked). Layer 4 is a **live disk read**
via `git grep -F`, so raw strings, copy, Tailwind classes, and config values the structural
graph never indexes still resolve instead of coming up empty.
**Symbol hit** (query resolved to a symbol → full block):
```
$ node agentmap.mjs --any cn
[structure] 1 symbol, 0 feature match for "cn"
lib/utils.ts → cn (FunctionDeclaration)
```
**Ambiguous file hit** (query matched multiple files → narrow it):
```
$ node agentmap.mjs --any utils
[structure] "utils" matched 3 files — narrow it:
lib/utils.ts
lib/db/utils.ts
tests/prompts/utils.ts
```
**Content fallback** (no file/symbol/feature match → live git-grep):
```
$ node agentmap.mjs --any streamText
[content] 13 lines:
app/(chat)/api/chat/route.ts:8: streamText,
app/(chat)/api/chat/route.ts:194: const result = streamText({
artifacts/code/server.ts:1:import { streamText } from "ai";
artifacts/code/server.ts:18: const { fullStream } = streamText({
artifacts/code/server.ts:40: const { fullStream } = streamText({
artifacts/sheet/server.ts:1:import { streamText } from "ai";
artifacts/sheet/server.ts:11: const { fullStream } = streamText({
```
---
## Commands
Every snippet below is **representative output** (long lists trimmed) from running agentmap against the public
154-file Next.js repo [vercel/ai-chatbot](https://github.com/vercel/ai-chatbot) (sha 2becdb4).
### `--any <q>` — the router (file → symbol → feature → live content)
See [The `--any` router](#the---any-router) above. Default first move for any
"where/what/who" question.
### `--find <q>` — reuse-before-rebuild symbol search
Find every symbol whose name contains the query — exported symbols **plus** non-exported
top-level declarations. Use it before writing a new util or component to check what already
exists (a private helper counts as reusable too).
```
$ node agentmap.mjs --find Message
find "Message": 55 match
hooks/use-messages.tsx → useMessages (FunctionDeclaration)
lib/errors.ts → getMessageByErrorCode (FunctionDeclaration)
lib/types.ts → messageMetadataSchema (VariableDeclaration)
lib/types.ts → MessageMetadata (TypeAliasDeclaration)
lib/types.ts → ChatMessage (TypeAliasDeclaration)
lib/utils.ts → convertToUIMessages (FunctionDeclaration)
lib/utils.ts → getTextFromMessage (FunctionDeclaration)
tests/helpers.ts → generateTestMessage (FunctionDeclaration)
app/(chat)/actions.ts → generateTitleFromUserMessage (FunctionDeclaration)
…
```
**Barrels don't hide the real file.** When a match is reached through a re-export
(`export * from "./x"`, or a named/renamed re-export, at any depth), the output names the
file that actually declares it. The TypeScript checker resolves the chain, so this works
where a name search can't — `rg` sees the barrel and the origin as two equal hits with no
way to tell which one you can edit. An origin outside the repo reports
`→ defined outside the repo`; a `node_modules` path is never printed.
```
$ node agentmap.mjs --find useComposedRefs # radix-ui/primitives@579c5b84
find "useComposedRefs": 3 match
packages/react/compose-refs/src/index.ts → useComposedRefs (FunctionDeclaration) → defined in packages/react/compose-refs/src/compose-refs.tsx
packages/react/compose-refs/src/compose-refs.tsx → useComposedRefs (FunctionDeclaration)
packages/react/radix-ui/src/internal.ts → useComposedRefs (?)
```
In `--json` this is `definedIn: "<path>"` or `external: true` on the match, present only
when the entry is a pass-through — a real definition carries neither.
### `--search <q>` — BM25 lexical search for vague queries
When you don't know the exact symbol name — the query an agent actually types — `--search`
ranks symbols by **BM25 lexical relevance** over split-identifier tokens (the symbol name,
its file's path segments, feature, and kind), fused with file PageRank so a strong hit in an
important file wins ties. No embeddings, no vector DB; the index is built into `map.json`.
The same ranker is wired into `--any` as a rung that fires **only** when exact file/symbol
matching found nothing, so exact routing is unchanged.
```
$ node agentmap.mjs --search "auth retry logic"
search "auth retry logic": 3 match
src/authRetry.ts → retryWithBackoff (FunctionDeclaration) [6.83]
…
```
Stopwords (`the`, `that`, `of`, …) are dropped, so `--search "the function that dedupes
symbols"` works. Also available as the `search` MCP tool.
### `--relates <path>` — blast radius + transitive relevance
The file's own block (exports / imports / direct dependents) **plus** a random-walk
relevance list (personalized PageRank on the bidirectional import graph) — the files most
related to the target, transitively, not just its direct importers.
```
$ node agentmap.mjs --relates lib/db/schema.ts
relates: lib/db/schema.ts (pr 0.073744)
exports (14): user(VariableDeclaration), User(TypeAliasDeclaration), chat(VariableDeclaration), Chat(TypeAliasDeclaration), message(VariableDeclaration), DBMessage(TypeAliasDeclaration), …
imports (0): —
dependents (21): hooks/use-active-chat.tsx, lib/types.ts, lib/utils.ts, components/chat/artifact.tsx, components/chat/message.tsx, lib/db/queries.ts, app/(chat)/api/chat/route.ts, …
related (random-walk relevance):
lib/utils.ts (0.0476)
lib/types.ts (0.0376)
components/chat/artifact.tsx (0.0372)
components/chat/icons.tsx (0.0264)
components/chat/message.tsx (0.0237)
lib/db/queries.ts (0.0225)
app/(chat)/api/chat/route.ts (0.0218)
…
```
**Type-only dependencies are listed separately, not silently dropped.** `dependents` means
"would break at runtime". A file imported only via `import type` has no runtime dependents
at all — but renaming or deleting its exports still breaks every consumer at compile time.
Those appear under `type-only dependents`, so a types module stops reading like an orphan:
```
$ node agentmap.mjs --relates lib/types.ts # vercel/chatbot@c2f8235e
relates: lib/types.ts (pr 0.002898)
exports (7): messageMetadataSchema(VariableDeclaration), MessageMetadata(TypeAliasDeclaration), …
imports (0): —
dependents (0): —
type-only imports (6): components/chat/artifact.tsx, lib/ai/tools/create-document.ts, …
type-only dependents (23): hooks/use-active-chat.tsx, hooks/use-auto-resume.ts, lib/utils.ts, …
```
22.4% of that repo's import statements are type-only. The fields are
`typeOnlyImports` / `typeOnlyDependents` in `--json`, omitted entirely when empty, and they
never enter PageRank, `--hubs`, symbol ranking or `--export` — the ranking graph stays a
runtime graph.
For a file carrying a React Server Components directive prologue, the output adds one more
line — `boundary: 'use client' (client component)` or `boundary: 'use server' (server module/actions)`
(`rsc: 'client' | 'server'` in `--json`) — right after `dependents`. This is additive and
optional: repos with no `'use client'`/`'use server'` directives never see the line.
### `--callers <sym>` — compiler-accurate call graph (experimental)
Who actually **calls** a symbol, resolved by the TypeScript language service (`ts-morph`
`findReferencesAsNodes`) — not tree-sitter name-matching. This is symbol-level blast radius:
a type-position mention (`typeof foo`), a re-export, a bare value reference (`const x = foo`),
or a same-named private local in another file is a *different* symbol and is never
mis-attributed. `--in <path>` disambiguates a name defined in more than one file (exported
definitions win over same-named private locals); results are ranked by caller-file PageRank
and capped.
```
$ node agentmap.mjs --callers getMessageByErrorCode
callers of getMessageByErrorCode [lib/errors.ts]: 3 call sites
app/(chat)/api/chat/route.ts:88 → POST
lib/db/queries.ts:142 → saveMessage
components/chat/message.tsx:57 → PureMessage
```
**JSX counts as a call site.** `<Foo />` compiles to `React.createElement(Foo, …)` (classic
runtime) or `jsx(Foo, …)` (automatic runtime) — either way it's an invocation, so a
component's callers include everywhere it's rendered, not just plain `foo()` calls.
`<Foo.Bar />` resolves to `Bar`, not the `Foo` namespace; `<Foo>...</Foo>` counts once (the
closing tag isn't a second call site); an intrinsic tag (`<div>`) resolves to nothing
in-project and produces no edge.
```
$ node agentmap.mjs --callers Button
callers of Button [components/ui/button.tsx]: 25 call sites
components/ai-elements/message.tsx:93 → MessageAction
components/ai-elements/message.tsx:263 → MessageBranchPrevious
components/ui/sidebar.tsx:249 → SidebarTrigger
components/ui/alert-dialog.tsx:158 → AlertDialogAction
components/ui/dialog.tsx:72 → DialogContent
…
```
Before 0.17.0, JSX wasn't a recognized call shape at all, so that same query returned
**0 call sites** — a plain `rg '<Button'` beat the tool outright. Captured on
[vercel/ai-chatbot](https://github.com/vercel/ai-chatbot) at `c2f8235`; reproduce it by
running the query against that commit.
A deliberate **deep query**: it lazily spins up the TS type-checker (a few seconds on a large
repo) *only* when invoked — the map build and every other query never pay that cost, and
nothing is persisted. Accurate on statically-resolvable calls; dynamic dispatch, reflection,
and string-keyed access are beyond any static tool. Also available as the `callers` MCP tool.
### `--calls <sym>` — outgoing call graph (experimental)
The companion to `--callers`: which in-project symbols a symbol **invokes**. Each call and
`new X()` site inside its body is resolved by the type checker (`getDefinitionNodes`), which
follows an imported / re-exported binding through to the real declaration — so a same-named
local elsewhere is never confused for the imported one. `node_modules` and TypeScript
built-ins (`console.log`, `Array.map`, …) are excluded; dynamic dispatch, computed member
access, and higher-order callees are honestly skipped.
```
$ node agentmap.mjs --calls extractFacts
extractFacts calls [agentmap.mjs]: 15 in-project targets
agentmap.mjs:756 → makeProject (FunctionDeclaration)
agentmap.mjs:944 → rel (VariableDeclaration)
agentmap.mjs:952 → excluded (VariableDeclaration)
…
```
**JSX counts as an outgoing call too**, for the mirror-image reason: a component whose body
is nothing but `return <Container><Sidebar /></Container>` has no `CallExpression` in it, so
before this fix it reported **zero** outgoing calls even though it clearly depends on both.
Each `<Foo />` / `<Foo>...</Foo>` in the body now resolves to its target declaration the same
way a plain call does — the printed `(kind)` is the target's own declaration kind
(`FunctionDeclaration`, etc.), not "JSX", since resolution is unchanged, only call-site
detection is:
```
$ node agentmap.mjs --calls AppSidebar
AppSidebar calls [components/chat/app-sidebar.tsx]: 35 in-project targets
components/ui/tooltip.tsx:21 → Tooltip (FunctionDeclaration)
components/ui/tooltip.tsx:33 → TooltipContent (FunctionDeclaration)
components/ui/sidebar.tsx:144 → Sidebar (FunctionDeclaration)
components/ui/sidebar.tsx:379 → SidebarContent (FunctionDeclaration)
…
```
Same repo and commit: this returned **6** targets before 0.17.0 — only the plain hook and
helper calls — and 35 after, because the 29 components it renders now count too.
Same lazy, out-of-band model as `--callers` (builds a Project only on the query, nothing
persisted). Also the `calls` MCP tool. JSX closes a real gap here — it doesn't change what's
still out of reach: the `node_modules`/dynamic-dispatch/computed-member/higher-order limits
above still apply.
**Going transitive — `--depth N`.** Both `--callers` and `--calls` accept `--depth N`
(default 1, max 5) for an N-hop closure: `--callers foo --depth 3` is the transitive
blast radius ("everything that reaches `foo`, up to 3 hops"); `--calls foo --depth 3` is
the dependency cone ("everything `foo` pulls in"). It BFS-traverses the same single warm
Project — no extra build — with cycle detection and node caps so a hub can't explode; each
result is tagged with its `depth` and a `via` parent. `--depth 1` is the default single-hop
query.
```
$ node agentmap.mjs --callers leaf --depth 2
callers of leaf [src/chain.ts]: 2 callers within depth 2
src/chain.ts:2 → mid [depth 1]
src/chain.ts:3 → top [depth 2]
```
### `--feature <name>` — files that make up a feature
Resolves a Next.js `app/`-router feature to its file set, plus the external files that
depend on it.
```
$ node agentmap.mjs --feature api
feature "api": 11 files
app/(chat)/api/chat/route.ts
app/(chat)/api/chat/schema.ts
app/(chat)/api/document/route.ts
app/(chat)/api/history/route.ts
app/(chat)/api/messages/route.ts
app/(chat)/api/models/route.ts
app/(chat)/api/suggestions/route.ts
app/(chat)/api/vote/route.ts
app/(auth)/api/auth/guest/route.ts
app/(chat)/api/files/upload/route.ts
app/(chat)/api/chat/[id]/stream/route.ts
external dependents (0): —
```
### `--features` — list features by size
```
$ node agentmap.mjs --features
features (4):
api (11 files)
login (1 files)
register (1 files)
chat (1 files)
```
### `--affected <path>` — which tests cover this file
Walks the reverse-dependency closure and reports the test files that reach the target,
with hop distance. The useful answer is often the empty one: *nothing covers this*, which
is what you want to know **before** a risky edit, not after CI.
Type-only importers count. Changing an exported type breaks every `import type` consumer
at compile time, so the walk follows `dependents` **and** `typeOnlyDependents`.
```
$ node agentmap.mjs --affected agentmap.mjs
affected by agentmap.mjs: 3 test files (of 4 transitive dependents)
test/doctor.test.mjs [1 hop]
test/pkg-imports.test.mjs [1 hop]
test/unit.test.mjs [1 hop]
```
A file with no reachable test says so in words, and `--json` carries `covered: false`.
### `--routes` — the App Router route table
Every URL the repo serves and the file that serves it. **Next.js App Router only** — on any
other repo it exits 1 with an explicit `reason` rather than an empty list, so an agent can
tell "not applicable" from "nothing found".
```
$ node agentmap.mjs --routes
routes: no app/ or src/app/ directory — not a Next.js App Router project
```
### `--route <url>` — resolve a URL to the code that serves it
Goes from a bug report naming a URL straight to the handler, its layout chain, and the
server modules it can reach.
```
$ node agentmap.mjs --route /dashboard/settings
/dashboard/settings (page)
serves: app/dashboard/settings/page.tsx boundary: 'client'
layouts (outer->inner): app/layout.tsx -> app/dashboard/layout.tsx
server modules: lib/server/settings.ts
```
### `--kind <k>` — narrow `--find` / `--search` by declaration kind
A modifier, not a command. Matched loosely and case-insensitively against the ts-morph kind
name, so `--kind function` finds `FunctionDeclaration` and `--kind type` finds
`TypeAliasDeclaration` — you never have to know the enum spelling.
```
$ node agentmap.mjs --find pagerank --kind function
find "pagerank" kind~function: 1 match
agentmap.mjs → pagerank (FunctionDeclaration)
```
Used alone it is a usage error (exit 2) — it has nothing to narrow.
### `--hubs` — most important files (PageRank)
The files that matter most, ranked by PageRank importance (raw dependent degree shown
alongside).
```
$ node agentmap.mjs --hubs
agentmap: 154 files (sha 2becdb4)
hubs (PageRank importance):
lib/utils.ts (deg 52, pr 0.105171)
lib/db/schema.ts (deg 21, pr 0.073744)
lib/types.ts (deg 23, pr 0.067589)
components/chat/artifact.tsx (deg 15, pr 0.036882)
components/chat/icons.tsx (deg 27, pr 0.035378)
lib/errors.ts (deg 9, pr 0.032787)
lib/db/queries.ts (deg 14, pr 0.030085)
…
```
### `--symbols [N]` — top ranked symbols (Aider-style)
The most important individual symbols across the repo, ranked by the identifier graph
(defaults to 30).
```
$ node agentmap.mjs --symbols 10
top 10 ranked symbols (Aider-style):
0.109902 lib/utils.ts → cn (FunctionDeclaration)
0.036013 lib/types.ts → ChatMessage (TypeAliasDeclaration)
0.025686 components/chat/artifact.tsx → ArtifactKind (TypeAliasDeclaration)
0.022461 lib/errors.ts → ChatbotError (ClassDeclaration)
0.021068 lib/types.ts → CustomUIDataTypes (TypeAliasDeclaration)
0.020872 lib/db/schema.ts → Document (TypeAliasDeclaration)
0.020555 components/ai-elements/suggestion.tsx → Suggestion (VariableDeclaration)
0.020555 lib/db/schema.ts → Suggestion (TypeAliasDeclaration)
0.018124 lib/db/schema.ts → DBMessage (TypeAliasDeclaration)
0.015034 lib/errors.ts → ErrorCode (TypeAliasDeclaration)
```
`map.json` persists the top 80. Asking for more re-ranks from the cached map rather than
truncating, so `--symbols 200` really does return 200 where the repo has them. When a repo
has fewer ranked symbols than you asked for, the header says so and `--json` carries
`requested` / `shown` / `truncated`:
```
$ node agentmap.mjs --symbols 200
top 62 ranked symbols (Aider-style) — asked for 200, this repo only ranks 62:
```
### `--map [--tokens N] [--focus <path>]` — token-budgeted ranked digest
The token-budgeted digest (Aider's killer feature): a ranked, files-and-symbols summary
that fits a token budget. Default budget is 8192 (1024 with `--focus`). `--focus <path>`
personalizes the ranking toward a file you're working on.
```
$ node agentmap.mjs --map --tokens 400
# agentmap (154 files, sha 2becdb4) — focus: global, budget ~400 tok
lib/utils.ts:
cn (FunctionDeclaration)
generateUUID (FunctionDeclaration)
lib/types.ts:
ChatMessage (TypeAliasDeclaration)
CustomUIDataTypes (TypeAliasDeclaration)
ChatTools (TypeAliasDeclaration)
Attachment (TypeAliasDeclaration)
components/chat/artifact.tsx:
ArtifactKind (TypeAliasDeclaration)
UIArtifact (TypeAliasDeclaration)
Artifact (VariableDeclaration)
lib/errors.ts:
ChatbotError (ClassDeclaration)
ErrorCode (TypeAliasDeclaration)
lib/db/schema.ts:
Document (TypeAliasDeclaration)
Suggestion (TypeAliasDeclaration)
DBMessage (TypeAliasDeclaration)
# ~387 tokens (14 files shown)
```
Focused on a working file — the ranking re-centers on what `lib/db/queries.ts` actually touches:
```
$ node agentmap.mjs --map --focus lib/db/queries.ts --tokens 350
# agentmap (154 files, sha 2becdb4) — focus: lib/db/queries.ts, budget ~350 tok
lib/utils.ts:
cn (FunctionDeclaration)
generateUUID (FunctionDeclaration)
getDocumentTimestampByIndex (FunctionDeclaration)
fetcher (VariableDeclaration)
getTextFromMessage (FunctionDeclaration)
convertToUIMessages (FunctionDeclaration)
fetchWithErrorHandlers (FunctionDeclaration)
sanitizeText (FunctionDeclaration)
lib/db/schema.ts:
DBMessage (TypeAliasDeclaration)
Suggestion (TypeAliasDeclaration)
Document (TypeAliasDeclaration)
Chat (TypeAliasDeclaration)
User (TypeAliasDeclaration)
chat (VariableDeclaration)
document (VariableDeclaration)
message (VariableDeclaration)
lib/errors.ts:
ChatbotError (ClassDeclaration)
ErrorCode (TypeAliasDeclaration)
# ~324 tokens (8 files shown)
```
### `--print` — full map as JSON
Dumps the cached map (`hubs`, `features`, `rankedSymbols`, `files`) as one JSON object —
for piping into other tools. Also includes a top-level `fileCount`.
```
$ node agentmap.mjs --print | jq '.hubs[0]'
"lib/utils.ts (deg 52, pr 0.105171)"
```
### `--export <mermaid|dot>` — visualize the import graph
Serializes the file import graph (nodes = files, edges = imports, top-N by PageRank, with
three light style tiers) as **Graphviz DOT** or **Mermaid** — paste straight into
[mermaid.live](https://mermaid.live), a GitHub README mermaid block, or `dot -Tsvg`.
`--focus <path>` scopes to a file's 1-hop neighborhood. It reads the cached map only (no
ts-morph Project), and prints graph text to stdout (so it isn't combined with `--json`).
```
$ node agentmap.mjs --export mermaid --focus lib/auth.ts
%% agentmap import graph — 154 files, sha a1b2c3d, focus lib/auth.ts
flowchart TD
classDef hub fill:#d9d9d9,stroke:#333,stroke-width:2px;
n0["lib/auth.ts"]:::hub
…
```
### Global flags
| Flag | Description |
|------|-------------|
| `--help` / `-h` | Print a usage block listing every flag and exit 0. |
| `--version` / `-v` | Print the version from `package.json` and exit 0. |
| `--json` | **Global modifier.** When present, every command prints exactly one JSON object to stdout (no prose). Shapes vary per command: `--json --hubs` → `{command,fileCount,sha,hubs:[string]}`, `--json --find X` → `{command,query,matches:[{file,name,kind}]}`, `--json --relates X` → `{command,file,pagerank,exports,imports,dependents,related}`, `--json --any X` → `{command,query,kind,…payload}`, etc. Bare `--json` (no query flag) → `{command:"build",fileCount,features,topHub}`. |
| `--no-locals` | Hide non-exported top-level declarations from `--find`/`--any` results (shown by default). Never affects `--map`/`--symbols`/`--hubs` ranking. |
| `--include-dts` | Include `.d.ts` declaration files in the symbol/ranking pass (excluded by default so generated types don't flood `--find`/`--symbols`/`--hubs`). |
| `--install-hooks` `[--dry-run]` | Copy `hooks/post-commit` into `.git/hooks/` (chmod 0755), ensure `.claude/agentmap/` is in `.gitignore`, and auto-wire the Claude Code `PreToolUse(Grep)` nudge into `.claude/settings.json` (merge-safe + idempotent). `--dry-run` previews without writing. Exit 0 on success, stderr + exit 3 on failure. |
| `--hook-status` | Report whether the post-commit hook, PreToolUse nudge, and `.gitignore` entry are installed (no writes). |
| `--doctor` | Read-only harness health report: git/Claude hook wiring, installed skills + Cursor rule freshness vs `package.json` version, MCP config entries for OpenCode/Antigravity, and map-cache presence/freshness hints. Always exits 0; suggests fix commands (`agentmap --install-hooks`, `--install-skill`, `--setup-mcp`, `agentmap`) but never runs them. Combine with `--json` for a structured report. |
| `--install-skill` | Install skills + always-on docs/hooks per platform (`--platform claude\|cursor\|codex\|opencode\|gemini\|antigravity\|copilot\|agents\|all`, default `all`; `--project` default, or `--global`; `--dry-run` preview). |
| `--setup-mcp` `[--dry-run]` | Configure agentmap as an MCP server for OpenCode and the Antigravity IDE (merge-safe). `--dry-run` previews without writing. |
| `--mcp` | Start agentmap as a **stdio MCP server** so non-Claude-Code agents (Cursor, Cline, any MCP client) can query the map. Exposes 11 query tools — `any`, `find`, `relates`, `map`, `hubs`, `features`, `feature`, `symbols`, `search`, `callers`, `calls`. |
**Exit-code contract:** `0` = success / match / help / version; `1` = query returned zero results (`--any`, `--find`, `--relates`, `--feature` with no match, or `--map --focus` that resolves to no file — the global digest still prints, with `focusResolved:false` in `--json`); `2` = usage error (missing required arg, unknown flag, two commands at once, or a sub-flag without its parent command); `3` = maintenance command failed (`--install-hooks`, `--install-skill`, `--setup-mcp`, `--hook-status`, `--mcp`). Any token starting with `-` that matches no known flag prints an error to stderr and exits 2.
---
## Scope & limitations
Honesty first — this is deliberately a small, sharp tool, not a universal code-graph.
- **TS/JS (+ Vue SFC), by design.** Built on `ts-morph`. Indexes `.ts/.tsx/.mts/.cts/`
`.js/.jsx/.mjs/.cjs` and the `<script>` blocks of `.vue` single-file components
(best-effort). No Python, Go, Rust, etc. — if your repo isn't TypeScript/JavaScript, use a
tree-sitter-based tool instead. **Want another language?
[Vote in #43](https://github.com/raymondchins/agentmap/issues/43)** — and read
[what a non-TS language would actually get](#what-another-language-would-actually-get)
first, because it would not be the same product.
- **The persisted map is a file-level import graph; the call graph is opt-in.** The
cached map's edges come from static `import` / re-export declarations and the named
symbols crossing them — `--relates` answers the file-level question ("who imports this
module"). Symbol-level, compiler-accurate call-site resolution is available on demand via
`--callers` (who calls a symbol) and `--calls` (what a symbol invokes) — both experimental,
lazy, out-of-band queries that spin up the type-checker only when invoked and are never
folded into the fast map build. The file-level graph additionally records a React Server
Components client/server boundary tag (from `'use client'`/`'use server'` directive
prologues) where present.
- **Alias & workspace resolution.** Resolves `tsconfig`/`jsconfig` `paths`, `vite`/`vitest`/
`webpack` `resolve.alias` (string entries, parsed from the AST — the config is **never
executed**), and pnpm/npm/yarn workspace cross-package imports (`@org/pkg` → its source).
A build reports `edgeCoverage` (the share of repo-local imports that resolved) and prints a
one-line warning when a repo's imports mostly *don't* resolve — so a broken/empty map is
never silently framed as success.
- **Scoping — `.agentmapignore` + `.d.ts`.** Generated `.d.ts` declaration files are excluded
from the symbol ranking **by default** (so a 200-symbol generated types file, or
`next-env.d.ts`, doesn't flood `--find`/`--symbols`/`--hubs`); `--include-dts` restores
them, and they stay live import-resolution targets either way. A repo-root `.agentmapignore`
(gitignore-style subset: anchored `/`, dir `/`, `*` globs, `#` comments) excludes extra paths.
- **PageRank + symbol ranking are real and implemented** (damping 0.85, deterministic
power iteration; personalized variants for `--relates` and `--map --focus`). The symbol
ranking is a faithful port of Aider's identifier-graph approach (credit:
[Aider](https://github.com/Aider-AI/aider), Apache-2.0).
- **Feature detection assumes the Next.js `app/` router.** `--feature` / `--features`
derive features from the first real route segment under `app/` (or `src/app/`), skipping
route groups `(...)`, dynamic `[...]`, and parallel `@...` segments. Repos without an
`app/` directory simply report zero features — every other command still works.
- **Token counts are estimates** (`chars / 4`), not a real BPE tokenizer. Treat
`--map`/`--tokens` budgets as approximate (±10%).
- The PreToolUse hook is **Claude Code-specific** (it speaks Claude Code's hook JSON). The
post-commit hook is generic git.
---
## What another language would actually get
Published *before* any of it is built, because the cheapest way to find out you're
being asked for a different product is to describe the product accurately first.
agentmap's accuracy comes from `ts-morph` — a real TypeScript compiler with a type
checker. Another language would be parsed with tree-sitter: syntax, no types, no
module resolver. That difference decides what each query can honestly return.
| Query | TS / JS today | Another language |
|---|---|---|
| `--relates` (blast radius) | full | **full** — ports best; this is the one to lead with |
| `--search` (BM25) | full | **full** |
| `--hubs` / `--map` / `--symbols` (PageRank) | full | **full** |
| `--print` / `--export` | full | **full** — reads the cached map only |
| `--find` | full | **partial** — no transitive re-export/barrel chains |
| `--any` | full | **partial** |
| `--callers` / `--calls` | full | **none — refuses, explicitly** |
| `--features` / `--feature` | Next.js App Router | **none** |
| incremental rebuild | yes | **no — full rebuild every time** |
`--callers` / `--calls` are ~259 lines of TypeScript language-service calls.
Tree-sitter cannot reproduce them. The options are to refuse loudly or to guess by
name-matching, and **name-matching will not ship**: a silently mis-wired graph is
worse than no graph, and it would falsify every accuracy claim here.
Two non-TS languages is the honest ceiling for one part-time maintainer — each one
is an ongoing tax, not a one-off. Reasoning, including the arguments *against*
doing this at all, is in [ROADMAP.md Part II](ROADMAP.md#part-ii--make-it-useful-for-the-majority-2026-07-26).
**No telemetry.** agentmap makes zero network calls. When an unsupported language
dominates your repo it counts the files locally, prints a one-line pointer to the
vote, and forgets. That count never leaves your machine. Silence it with
`AGENTMAP_NO_CENSUS=1`.
## Forks & ports
Two people ported agentmap to another language rather than open an issue, which is
the strongest evidence that the "nobody asked" reading was wrong:
- [`rifanid98/agentmap-go`](https://github.com/rifanid98/agentmap-go) — Go
- [`dstwn/agentmap-php`](https://github.com/dstwn/agentmap-php) — PHP / Laravel
Neither is affiliated with this repo and neither is endorsed — listed because
pretending they don't exist would be dishonest about demand. If you maintain one:
upstreaming beats competing, please open an issue.
---
## Contributing
Issues and PRs welcome. High-value directions:
- Retrieval-accuracy eval — **done** ([`EVAL.md`](./EVAL.md), `npm run eval`). Next: a
type-aware dependents mode (the eval excludes type-only edges to match the value-import
graph) and an `app/`-router fixture so `--feature` retrieval can be scored too.
- A real tokenizer behind the `--map` budget.
- Hardening feature detection for non-`app/`-router layouts.
Keep the dependency footprint minimal — `ts-morph` is the only runtime dependency (it bundles
the TypeScript compiler, ~10 MB installed), and keeping it that way is a feature.
## License
[MIT](./LICENSE). Symbol-ranking algorithm credit: [Aider](https://github.com/Aider-AI/aider) (Apache-2.0).