SSD Nodes Learn 🎉 VPS from $5.50/mo
Guides Matt ConnorBy Matt Connor

Graft: a codebase map for coding agents

Graft parses your repo with tree-sitter into a codebase map your coding agent queries over MCP, so it stops rediscovering the same structure every session.

What a codebase map for coding agents is

A codebase map for coding agents is a persistent index of your repository that the agent looks things up in, instead of grepping its way around from scratch in every new session. Graft is one implementation of that idea. It parses your code with tree-sitter, writes a folder of linked markdown nodes plus a per-symbol wiring graph, and serves retrieval tools over MCP (model context protocol, the standard interface coding agents use to call outside tools).

Graft is not a proxy and it is not a gateway. Nothing sits between your agent and the model API. The map is a folder on disk that the agent reads. That distinction decides which problem you are solving: a self-hosted token gateway meters and routes the requests you already send, while a map changes how many requests you need to send at all.

The technique is older than this tool and will outlive it. Learn the technique first, then the mechanics.

Why coding agents burn context rediscovering structure

Watch an agent start work on a repository it has already seen fifty times. It lists directories. It greps for a symbol. It opens three files to find which one defines the function, then a fourth to find out who calls it. None of that is the task. It is orientation, and it is paid for in input tokens on every single session.

The cause is simple. A model has no memory between sessions. Everything the agent learned about your layout lived in a context window that was discarded when the session ended. So the same discovery runs again from zero, at full price. On a large repository the orientation phase costs more than the edit does: ten tool calls to locate the code, one to change it.

A map breaks that loop by moving discovery off the model and onto disk. A parser walks the repository once, records which symbol is defined where and which symbol calls which, then keeps that record current as the code changes. The agent asks one question and gets an answer with file and line attached. Repeated exploration becomes a cheap lookup.

You already use a weaker version of this. An AGENTS.md that states your conventions stops the agent re-deriving your conventions every time. A generated map stops it re-deriving your structure. The difference is who writes it. You hand-write the instruction file, so it stays small. A parser generates the map, so it can cover ten thousand files. For where the budget actually goes inside a session, how Claude Code spends its context window covers the accounting.

What Graft actually builds

Two artefacts, both under one graft/ folder at the repository root.

The first is a node graph written as linked markdown, one file per node. Each node holds a plain-English summary, a "crux" of the important logic lines lifted out of the source, the exact source files with a content hash, typed wikilinks to other nodes (depends_on, part_of, uses, implements), and a notes section that survives regeneration so you can record context a parser cannot infer.

The second is graft/.graph/wiring.json, the per-symbol structural graph that tree-sitter extracts: definitions, references and the call edges between them.

The split matters because only one half needs a model. graft build is pure tree-sitter and never calls an LLM (large language model), so it is deterministic and costs nothing. graft build --deep adds the written summaries and the per-symbol cruxes, and those are model calls you pay for.

Language support is tiered, and the tier tells you how far to trust a call graph. TypeScript, JavaScript, Python, Go and Java get scope-aware cross-file resolution. Rust, C, C++, C#, Ruby, PHP, Kotlin, Scala, Swift, Elixir, Solidity, OCaml, Zig and Dart get symbols plus generic call edges, which means an edge can be a name match rather than a resolved reference. Compiler-grade edges are opt-in with --lsp and a language server such as rust-analyzer or gopls.

Install Graft and pin the version

Graft needs Node.js 20 or newer and is MIT licensed. As of August 2026 the current release is 0.10.1, and the first published version, 0.1.0, is dated July 2026. Treat it as young software.

npm install -g @nanonets/graft@0.10.1
npm ls -g @nanonets/graft

npm ls -g should print @nanonets/graft@0.10.1. Pin that version on purpose. A bare npm install -g @nanonets/graft resolves the latest tag at the moment you run it, and on a project shipping several minor releases a month that gives you a different tool on Tuesday than your colleague installed on Monday. A pinned version keeps the CLI flags and the graph format the same for everyone, so you upgrade when you decide to.

Then wire it into a repository you own:

cd /path/to/your/repo
graft init --dry-run
graft init

graft init asks which of your coding agents to wire up, then builds the graph. Run --dry-run first and read the list of files it plans to touch, because some of them sit outside the repository. graft init is idempotent and does not overwrite existing configs, so running it a second time is safe.

As of August 2026 the wiring covers Claude Code, Cursor, Codex, GitHub Copilot, Google Gemini, Kiro, Windsurf and AdaL. Claude Code gets the deepest integration: an MCP server entry, a statusline showing graph size and staleness, post-edit hooks that rebuild the graph, and a skill file under .claude/. The rest get an instruction or rule file that tells the agent the tools exist. "Supported" therefore means Graft writes the wiring, so an agent that skips its own rules file will skip the map too. That is the ordinary reason agents ignore the instructions you write for them, and it applies here as much as anywhere.

What lands in your repository, and what stays out of git

After graft init, expect these:

  • graft/: the markdown node graph and graft/.graph/wiring.json. Added to .gitignore for you.
  • .mcp.json: registers the graft MCP server so Claude Code starts it.
  • .claude/settings.json: merged in place, adding the statusline and the post-edit hooks.
  • AGENTS.md, GEMINI.md, .github/copilot-instructions.md, .cursor/rules/graft.mdc, .kiro/steering/graft.md, .windsurf/rules/graft.md and .adal/skills/graft/SKILL.md: marker-fenced sections appended to whichever files match the agents you picked.
  • ~/.codex/config.toml, ~/.codex/hooks.json and ~/.codex/hooks/graft/graft-hooks.cjs: machine-wide, written only when you select Codex. graft init --no-global skips them, and graft init --no-hooks skips the hook shim on its own.

The graph is a cache, like node_modules. Do not commit it. It regenerates from the code in seconds, it changes on nearly every edit, and committing it turns a one-line fix into a several-hundred-file diff that no reviewer will read. Commit the wiring instead, AGENTS.md and .mcp.json among them. A teammate clones the repository, runs graft build, and gets their own local graph.

Check that the ignore rule landed before your first commit:

grep -n graft .gitignore
git status --short

grep should print a line containing graft/, and git status --short should list nothing under graft/. Files under graft/ appearing in that output means the ignore entry is missing or overridden elsewhere. Fix it before you commit, because git keeps tracking a file once it has been added, and a later .gitignore edit will not untrack it.

If you would rather register the MCP server by hand, or pin it to the same version you installed, the entry is small:

{
  "mcpServers": {
    "graft": {
      "command": "npx",
      "args": ["-y", "@nanonets/graft@0.10.1", "mcp"]
    }
  }
}

The retrieval tools your agent calls instead of grep

Graft exposes six tools over MCP. graft_find_code returns ranked nodes for a task description, with file and line. graft_file_api returns every signature in a file with no bodies. graft_trace_calls walks callers or callees several levels deep. graft_find_all returns regex hits grouped by symbol. graft_repo_map gives a first look at an unfamiliar repository. graft_check_freshness reports whether the graph still matches the code.

Every one has a CLI twin, which is how you check what your agent is actually being handed:

graft map .
graft ask "where do we validate the refresh token"
graft skeleton src/auth/session.ts
graft callers validateRefreshToken
graft callers validateRefreshToken --direction out
graft grep "refresh_token" --json

graft ask should print ranked nodes with file:line references rather than file contents. That is the whole mechanism: the agent receives a pointer and opens one file, instead of reading ten to find the right one. graft viz opens an interactive viewer on localhost if you want to look at the graph yourself. If graft ask returns nothing useful for a question you could answer in thirty seconds, the graph is stale or your language sits in the broad tier, and the map will not help your agent either.

One cost is easy to miss. Six tool definitions are injected into the system prompt of every request for the whole session. You pay that whether the agent uses the map or not. On a repository small enough to fit in context, the fixed charge can be larger than the exploration it saves.

What happens to the graph when the code changes

Structural refresh is cheap and automatic. Graft reads your working tree rather than git, so an edit you have not committed and an edit you have staged are equally visible to it. A query re-parses only the files whose stat changed, which the project documents as roughly 3 ms of overhead, and a turn-end rebuild touches only files where code moved. Set GRAFT_NO_REFRESH=1 or pass --no-refresh to answer from the graph on disk without re-parsing. Pass --no-reuse to force a cold re-parse of everything, which is what you want after upgrading Graft itself.

The model-written half behaves differently, and it is the part that goes quietly wrong. Summaries and cruxes are cached. Each node records a content hash of its sources, so when a source file changes the node is marked stale rather than presented as current. That flag only helps if something acts on it. Refresh with graft build --deep, which spends model tokens again.

Make staleness visible:

graft check .
echo $?

Exit status 0 means the graph matches the code. Exit status 1 means drift. Run it from a pre-push hook, or on the branch in CI, so a six-month-old map cannot answer confidently about code that was rewritten in March.

Read the published benchmark numbers carefully

Graft's headline claim is "up to 4x cheaper and 3x faster, with better or no loss of correctness". Those come from the project's own benchmarks, published in its README. Here are the two runs it reports in full.

ChartGraft's own published benchmark results, versus a no-map baseline, as of August 2026
The data behind this chart
[
  {
    "label": "Controlled sweep",
    "run_count": 162,
    "token_saving_pct": 42,
    "tool_call_saving_pct": 46,
    "correctness_pct": 93,
    "baseline_correctness_pct": 93
  },
  {
    "label": "SWE-bench Verified",
    "run_count": 50,
    "token_saving_pct": 23,
    "tool_call_saving_pct": 25,
    "correctness_pct": 66,
    "baseline_correctness_pct": 54
  }
]

The controlled sweep is 162 runs over two repositories, one of them Graft itself, with three trials per task. It reports 42% fewer tokens and 46% fewer tool calls. The SWE-bench Verified run is 50 instances with the same model on both arms, and it reports a smaller saving: 23% of tokens and 25% of tool calls. A third run reproduced five merged PocketBase pull requests at a cost of 11.02 US dollars against 13.91 for the baseline.

Treat all of it as a vendor benchmark. Two things limit what it can tell you. The controlled sweep includes Graft's own repository, which is the codebase its authors tuned against. SWE-bench Verified is a public dataset of issues from well-known open-source Python projects, and public datasets are the ones tools get optimised for, whether or not anyone means to. Neither is a statement about your private monorepo, which has its own naming habits and its own dead code.

Correctness deserves a second read. On the controlled sweep it did not move: 93% with the map against 93% without. The jump to 66% from 54% appears on SWE-bench Verified only. A tool that cuts your token bill and leaves quality flat is still a good trade. Just do not carry the SWE-bench correctness result across to the sweep's token result and quote both as one claim.

Measure your own token delta before you believe any of it

The only number that matters is the one from your repository. This method takes an afternoon.

Pick a task you can repeat exactly. A question beats an edit, because an edit changes the repository and the second run is no longer the same experiment. "Which module enforces the rate limit on the login route" is the right shape.

Turn on telemetry and send it to your own terminal:

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=console
claude

The console exporter prints metric records as they are collected. The one you want is claude_code.token.usage, which carries a type attribute of input, output, cacheRead or cacheCreation. Orientation shows up in input and cacheRead, because that is where file contents land. Add those two together.

Run the task three times, each in a fresh session, with the map wired. Then remove the graft entry from .mcp.json and run it three more times. Compare medians rather than single runs, because agent runs vary widely and one unlucky run will tell you the opposite of the truth. Record the tool-call count as well: tool calls are the mechanism, and tokens are the effect, so a token saving with no drop in tool calls means something else moved.

Then subtract the costs the benchmark does not show. graft build --deep spends model tokens on every full refresh. The six tool schemas ride along in every request. If your agents run on a server you rent, putting a hard ceiling on agent spend turns this from a surprise into a budget, and what a coding agent's telemetry actually reports covers what leaves the machine once you enable the exporter.

Where does a codebase map stop helping?

  • The repository already fits in context. A single small service does not need a map, and you still pay for six tool schemas on every request. If your agent finds any file today in one or two tool calls, skip it.
  • Your language is in the broad tier. Generic call edges mean graft callers can miss a caller, or produce one from a name collision. Confirm with graft grep before you trust a blast radius.
  • The graph went stale and nobody noticed. graft check exits 1 on drift, which is only useful if something runs it. A hook or a CI step, not a habit.
  • The monorepo needs scoping. A single-git monorepo is auto-split by workspace file, go.mod, pyproject.toml or Cargo.toml, and graft ask "..." --in services/billing/ narrows a query to one sub-project. The same instinct that leads to nested AGENTS.md files per package applies to the map.
  • The agent ignores the wiring. Watch the tool calls in a real session before you conclude the map is being used. An agent still running grep is telling you it never read the rules file.

FAQ

Should I commit the graft/ folder to git?

No. graft build adds graft/ to your .gitignore automatically, because the graph is a regenerable cache like node_modules. It changes on nearly every edit, so committing it buries real diffs under hundreds of generated files. Commit the wiring that tells agents the map exists, AGENTS.md and .mcp.json among them, and let each teammate run graft build locally. Verify with grep -n graft .gitignore and git status --short before your first commit, because git keeps tracking a file once it has been added, and editing .gitignore afterwards does not untrack it.

Does Graft cost money to run?

The structural half does not. graft build, graft ask, graft check and the six MCP retrieval tools are tree-sitter operations that never call a model. graft build --deep is the paid half: it writes the plain-English summaries and per-symbol cruxes through an LLM, configured with GRAFT_PROVIDER, GRAFT_API_KEY and GRAFT_MODEL, plus GRAFT_BASE_URL for any OpenAI-compatible endpoint. You can run Graft with structure only and never spend a token on the graph itself.

How much will a codebase map actually save on my repository?

Nobody can tell you without measuring. The project reports 42% fewer tokens on its own 162-run sweep and 23% on SWE-bench Verified, both against a baseline with no map. Both are vendor benchmarks, one of them run partly on Graft's own repository, and neither describes your private code. Run one repeatable question three times with the map and three times without, with CLAUDE_CODE_ENABLE_TELEMETRY=1 and OTEL_METRICS_EXPORTER=console set, then compare the median of claude_code.token.usage for the input and cacheRead types.

What happens to the graph when I refactor?

Structure re-parses itself. Graft stats the working tree and re-parses only the files that changed, so a rename is picked up on the next query at roughly 3 ms of overhead, and it sees uncommitted work because it reads files rather than git history. The model-written summaries are what goes stale: each node stores a content hash of its sources, and a changed source marks the node stale instead of rewriting it. Run graft check . to see the drift, then graft build --deep to refresh the written half.

Which coding agents can use Graft today?

As of August 2026 graft init wires Claude Code, Cursor, Codex, GitHub Copilot, Google Gemini, Kiro, Windsurf and AdaL. Claude Code gets the most: an MCP server entry in .mcp.json, a statusline, post-edit hooks and a skill file under .claude/. Codex gets an AGENTS.md section plus machine-wide entries under ~/.codex/, which graft init --no-global skips. The others receive a rules or steering file. Any other MCP client can use the server directly by registering the command npx -y @nanonets/graft@0.10.1 mcp.