Skip to content

raymondchins/agentmap

Repository files navigation

agentmap — ~98% token savings to understand a codebase (up to 99.9% per task)

agentmap

The TS/JS-accurate repo map for your coding agent — a compiler-grade ts-morph import/symbol graph that answers "where is it / what breaks / does this already exist" in ~98% fewer context tokens.

Your AI coding agent re-learns your codebase every session — opening files and grepping to find what connects to what, burning tokens before it writes a line. agentmap gives it a queryable, ranked code-relationship map for TypeScript/JavaScript repos instead — a ts-morph import/symbol graph (the real TypeScript compiler, so aliases, vite/webpack resolve.alias, package.json #imports subpaths, and workspace cross-package imports all resolve) ranked by personalized PageRank. Ask it to "add a field" or "fix the login bug" and it finds the right files, their imports, and what already exists in ~98% fewer context tokens on average (up to ~99.9% per task; figures are chars/4 estimates applied equally to both sides) — kept current by a post-commit auto-refresh and actually used via a PreToolUse(Grep) hook.

agentmap's wedge in one line: it's the TS/JS-accurate repo map — a real TypeScript-compiler graph, not a tree-sitter approximation — with a published, honest accuracy eval to back it. That precision is the point; the auto-refresh/nudge wiring below is convenience, not the moat.

npm CI License: MIT node

One file, one runtime dependency (ts-morph, which bundles the TypeScript compiler — ~10 MB installed). No vector DB, no embedding API, no server. npx @raymondchins/agentmap --any <query> and you have a ranked answer.

Fully local — no network calls, no telemetry, no data leaves your machine. agentmap reads your code, writes a cache under .claude/agentmap/, and never phones home (there is not a single fetch/http call in the source). Your code is never sent anywhere.

⚠️ Always install the scoped name: @raymondchins/agentmap. npx agentmap (unscoped) runs an unrelated package by a different author — this project is @raymondchins/agentmap, and the scoped name is required in every install and command. npmjs.com/package/@raymondchins/agentmap


Benchmark

Every task you hand a coding agent starts with the same hidden step — find the relevant code. Here's the token cost of that step, reading raw files vs querying agentmap, on a real 154-file Next.js app (vercel/ai-chatbot). Every figure is captured tool output (node benchmark/bench.mjs <repo> at the pinned sha):

The question the agent has to answer first Reading files With agentmap Saved
Where is this symbol defined?1,9502099%
Does a helper for this already exist? (reuse)14,7401999.9%
What breaks if I change this file? (blast radius)81,03861699.2%
What files make up this feature?6,1211,02583.3%
Give me a repo overview3,0651,12763.2%
Load the whole repo into context150,2811,12799.3%
What does this one file import?58351711.3%
All 7 tasks combined257,7784,45198.3%

Context tokens the agent burns to answer each question — token est = chars/4, applied to both sides.

That's the agent reaching the same answer on 58× fewer tokens overall — and the pattern holds across zod (367 files, 99.2%) and taxonomy (125 files, 96.0%), peaking at 646× fewer on a single whole-repo map. Reproducible at pinned shas; full per-scenario tables in ./benchmark/RESULTS.md.

Methodology note: the 58× overall figure is dominated by the whole-repo-load scenario (Scenario F — 150 K vs 1 K tokens), which skews the combined ratio sharply upward. Excluding it, the per-task overall ratio on the same sample repo is approximately 32×. Both numbers are real; the headline captures the most common agent worst-case (repo-dump on session start), while the per-task average better represents typical individual queries. RESULTS.md has the full breakdown.

Fewer tokens, but are they the right tokens? Token efficiency is only half the story — a separate EVAL.md (npm run eval) scores retrieval accuracy against ground truth derived live from real repos (zod, zustand, hono). Headline: agentmap returns the symbol definition in the top 3 ~95% of the time (naive grep ~79%) at ~2.6× fewer tokens, and identifies a module's dependents at ~100% precision (grep ~58%). Honest tradeoffs and method in EVAL.md.

Speed: a cold build (parse + PageRank + symbol graph) takes ~1.2s; a warm cached query returns in ~0.1s (the lazy-loaded path added in 0.2.2) — the agent has a ranked answer back before it would have finished opening the first handful of files.

Honest notes: the win scales with the work — the small rows above (63%, 11%) are the floor, and a trivial single-file lookup can even cost more than cat+grep (taxonomy's file-import task hit −313%; we leave it in). Numbers measure context-token volume, not answer quality or wall-clock.


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.

But the real reason to reach for agentmap is accuracy. It's built on ts-morph — the actual TypeScript compiler — so its import graph resolves the things a text/tree-sitter scanner guesses at: tsconfig/jsconfig paths, vite/vitest/webpack resolve.alias, package.json Node #imports subpaths, and pnpm/npm/yarn workspace cross-package imports. It reports an edgeCoverage map-health signal and warns loudly when a repo's imports mostly don't resolve, so a broken map is never framed as success — and a separate EVAL.md scores retrieval accuracy against live ground truth. That compiler-grade precision on TS/JS is the wedge.

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, ~57k★) 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 RepoMapper Repomix 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 vs live ground truth No No No No
Agent-loop wiring Yes — post-commit auto-refresh + PreToolUse hook In-process (Aider only) No No 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

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). 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 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/) 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 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 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:

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:

# 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 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.

--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:

{
  "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)

npx @raymondchins/agentmap --install-skill

…or grab just the skill (no agentmap flags) via the skills CLI — agentmap ships the skills/agentmap/SKILL.md layout it expects:

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:

agentmap --install-skill --platform cursor           # Cursor rule only (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

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:

# 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 hookPreToolUse 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 the AfterTool/systemMessage path (the earlier BeforeTool + additionalContext combo was silently dropped — fixed in #4)
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) MCP + docsalwaysApply rule + the MCP server Cursor's own hooks aren't wired; the rule is advisory
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):

{
  "mcpServers": {
    "agentmap": {
      "command": "npx",
      "args": ["-y", "@raymondchins/agentmap", "--mcp"]
    }
  }
}

Then Cursor exposes the 8 query tools (any, find, relates, map, hubs, features, feature, symbols). Run agentmap --doctor any time to see what's wired vs missing.


Quickstart

No install needed:

npx @raymondchins/agentmap --any <query>

…or run it directly from a checkout:

node agentmap.mjs --any <query>

The first run builds and caches the map to .claude/agentmap/map.json (add .claude/agentmap/ to .gitignore). Subsequent runs serve the cache when the tree is clean and HEAD is unchanged, and silently rebuild from disk when there are uncommitted .ts/.tsx/.js/... edits — so queries always reflect your in-flight work.

Run with no flag to build + print a one-line summary:

$ node agentmap.mjs
agentmap: 154 files | 4 features | top hub: lib/utils.ts (deg 52, pr 0.105171)

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 (sha 2becdb4).

--any <q> — the router (file → symbol → feature → live content)

See 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)
  …

--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)
  …

--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

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)
  …

Same lazy, out-of-band model as --callers (builds a Project only on the query, nothing persisted). Also the calls MCP tool.

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)

--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 [--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)"

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}.
--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 8 query tools — any, find, relates, map, hubs, features, feature, symbols.

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. Support for other languages is a possible future direction.
  • 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.
  • 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, 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.

Contributing

Issues and PRs welcome. High-value directions:

  • Retrieval-accuracy eval — done (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. Symbol-ranking algorithm credit: Aider (Apache-2.0).

About

The repo map your coding agent is forced to use — ~98% fewer tokens (up to 99.9% per task) to understand a TS/JS codebase. PageRank hubs, Aider-style symbol ranking, token-budgeted digest, --any router, wired into the agent loop.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors