cd /news/developer-tools/graft ยท home โ€บ topics โ€บ developer-tools โ€บ article
[ARTICLE ยท art-79845] src=github.com โ†— pub= topic=developer-tools verified=true sentiment=โ†‘ positive

Graft

Graft, a tool from Nanonets, reduces coding agent tool calls by 46%, tokens by 42%, and time by 60% in a 162-run controlled benchmark by building a persistent graph of codebase context as linked markdown files, eliminating repeated exploration overhead. The tool integrates with Claude Code, Cursor, Codex, and Gemini, and can be installed via npm and initialized with a single command.

read21 min views1 publishedJul 30, 2026
Graft
Image: source

Turbocharge Claude Code, Cursor, Codex, Gemini & every coding agent: faster, cheaper, with contextual understanding specific to your codebase.

vs. a standard session, no Graft With Graft
Tool calls 46% fewer
Tokens 42% fewer
Time 60% less
Correctness equal

The table is a 162-run controlled benchmark (same agent, same file tools, only the context differs). The "up to 4ร— cheaper / 3ร— faster" figures are the biggest single-task wins from a separate real-repo sweep (PocketBase, ollama, Excalidraw). Full method & per-repo numbers โ†“

Quick startThe problemWhat Graft doesHow the graph gets builtWhat's in a nodeWhat runs whereAgent integrationโ€”MCP serverยทClaude Code (deep integration)CLISearch & orient(graft grep

/graft map

)Monorepos & multi-repo foldersVisualize it(graft viz

)BenchmarkTested on your popular reposDevelopmentLicense

npm install -g @nanonets/graft   # install the CLI, once
graft init                       # build the graph + wire it into Claude Code

That is the whole setup. graft init

asks which of your coding agents to wire up, builds graft/

from your code, and drops a statusline and hooks into .claude/

, so from the next session on Graft rides along in Claude Code: it pulls the matching nodes into each prompt and rebuilds the graph in the background after every turn. No daemon, no re-indexing to remember, nothing to run or maintain by default โ€” the graph is just files.

Nothing is written until you pick. Run graft init --dry-run

to see every file it would touch first, or graft init --agents claude

to skip the prompt and wire Claude Code alone.

graft build

adds graft/

to your .gitignore

automatically โ€” the graph is a local, regenerable cache (like node_modules

), not something you commit. What you share is the wiring init

dropped into .claude/

; each teammate runs graft build

to generate their own graph:

git add .claude && git commit -m "wire in graft"

Prefer not to install globally? npx @nanonets/graft init

works the same way.

Every task, your coding agent starts blind. Before it changes anything, it re-explores the repo: grep a term, open a file, follow an import, back out, try again. It is rebuilding a picture of a codebase it mapped an hour ago and threw away. That rediscovery burns most of a run's tool calls, tokens, and latency, and it is pure overhead:

Repeated. Every task pays the exploration cost again, from zero.Discarded. Whatever the agent figured out dies with the session.Unshared. The next teammate, and their agent, start from scratch too.

Humans onboard to a codebase once. Agents onboard every single time.

Graft builds that understanding once and writes it into your repo as a folder of linked markdown files, one node per system, API, or concept.

Real explanations, not a list of symbols. Each node says, in plain English, what a part of the system does and how it connects to the rest, the way a senior engineer would explain it. That is the part an agent actually needs so it can skip the exploration. It is not a dump of function names.A real graph you can read. No embeddings, no similarity search, no index to keep warm. The graph is a set of linked files your agent opens, greps, and follows, exactly the way it reads any other file in the repo.Grafted into git. The graph is just files ingraft/

. Commit it, and anyone who clones the repo has it. No database, no server, no setup. Git does the syncing, and a stale graph shows up as a diff in review instead of rotting in some external store.The diff lives with the code. When a change moves things around, you see it in the graph diff in the same pull request, right next to the code that caused it.Your provider, your key, your model. Summaries are written by any provider you choose โ€” OpenAI, Anthropic (native), OpenRouter, Fireworks, Groq, a LiteLLM proxy, or a local model โ€” under your own key. The structural code graph (graft build

,graft check

) is deterministic tree-sitter and never calls a model at all.

Graft builds the graph in two passes, both powered by a language model:

Read each file. Every source file is summarized once into a short description of what it does.Group into nodes. Those summaries are grouped into a curated set of nodes (subsystems, key files, and concepts) with typed links between them. Graft chooses the right level of detail for you instead of making one node per file, so a big repo becomes a few dozen readable nodes.

flowchart LR
    S[Source files] --> T["Tier 1 โ€” tree-sitter<br/>no model, no key"]
    S --> P1["Pass 1 โ€” LLM summarizes<br/>each file (--deep)"]
    T --> W["graft/.graph/wiring.json<br/>per-symbol code graph"]
    P1 --> P2["Pass 2 โ€” group into nodes<br/>+ typed links"]
    P2 --> N["graft/*.md<br/>markdown node graph"]

Every pass is cached by content hash โ€” the LLM ones and the tree-sitter parse alike. Re-running only touches the files that changed, so the second build is fast and cheap (on this repo, 124 files: 0.74s cold, 0.18s after one edited file, 0.18s with nothing changed). graft build --no-reuse

forces a cold re-parse.

That cheapness is what lets every query refresh the graph before it answers. A retrieval call stats the tree against the last build's fingerprint (~3ms), and rebuilds only if something moved โ€” so ask

/grep

/callers

/skeleton

/map

describe the code as it is right now, including edits that are unsaved to git: uncommitted, unstaged, or staged all look the same to graft, which never reads git at all. The refresh is structural and $0

; it never calls the LLM. Turn it off per-command with --no-refresh

, or everywhere with GRAFT_NO_REFRESH=1

.

Alongside the markdown graph, graft build

builds graft/.graph/wiring.json

โ€” a per-symbol code graph โ€” plus a per-file wiring card mirroring your source tree. Tier 1 is pure tree-sitter (every function, class, and call edge; deterministic, no model, no network), which is why plain graft build

needs no key. The --deep

pass adds a one-line summary and a crux excerpt per symbol, cached by body hash.

A node is a single markdown file. Most code maps stop at an address: this thing lives in that file, on that line. That tells an agent where to look, not what it will find, so it still has to open the source and read. A Graft node holds the meaning inline, so the agent learns what it needs up front and opens the file only when it wants more.

Each node holds:

Part What it holds
Summary
A plain-English explanation of what the code does, written by the model and cached. It is there whether or not the code was ever documented, and it is regenerated when the source changes.
Crux
The handful of lines that actually carry the logic: the guard, the skip condition, the state change. Lifted straight from the source and stored inline, so the agent sees how it works, not just what.
Sources
The exact files the node is built from, each tracked by a content hash, so Graft can tell precisely when a node has gone stale.
Links
Typed connections to other nodes (depends_on , part_of , uses , implements , produces ), written as [[wikilinks]] your agent can follow.
Notes
Anything you write below the generated block. It is preserved across regenerations, so your own context is never overwritten.

That is three depths in one file: the summary says what the code does, the crux shows how, and the sources point to the rest if the agent needs it. A plain index makes it read a whole file to learn one thing. A Graft node hands it the answer inline, and the follow-up read often never happens.

The crux is stored as the code itself, not as a line range, on purpose. Line numbers drift whenever unrelated code above them shifts, but the lines that matter do not. Keeping the text, not the numbers, means the crux stays correct even as the file around it moves.

Summary, sources, links, and notes ship today in markdown nodes. The crux ships per-symbol in the code graph ( graft build --deep); inlining it into markdown nodes is next.

On your machine, no key, no network: the structural code graph.graft build

(wiring graph + per-file cards),graft check

, andgraft ask

are deterministic tree-sitter โ€” they never call a model.Through your provider key: the LLM-written parts โ€”graft build --deep

adds the concept nodes (file summaries + node synthesis) and the per-symbol summaries and cruxes. graft is vendor-neutral: setGRAFT_PROVIDER

(openai

for any OpenAI-compatible endpoint, oranthropic

for the native API), yourGRAFT_API_KEY

,GRAFT_MODEL

, and โ€” for theopenai

wire format โ€”GRAFT_BASE_URL

to point at OpenRouter, Fireworks, Groq, a LiteLLM proxy, a local server, or OpenAI itself. Or pass--provider/--model/--api-key/--base-url

on the command line. (OPENROUTER_API_KEY

still works as a deprecated fallback.)No telemetry and no analytics โ€” the only network calls are the LLM requests you configured.

See .env.example for the full list of settings (model, base URL, graph directory).

One command wires Graft into the coding agents you use:

npx @nanonets/graft init

On a terminal, init

shows you every agent it knows about โ€” flagging the ones it detected (via their config directories) and listing the exact files each would write โ€” and wires only the ones you select. Claude Code is pre-selected; nothing else is. Selected agents get a marker-fenced Graft section in their shared instruction file โ€” AGENTS.md

(Codex, OpenCode and other CLIs that read it), GEMINI.md

, .github/copilot-instructions.md

โ€” or a wholly-owned rule/skill file for the agents that use one โ€” .cursor/rules/graft.mdc

, .kiro/steering/graft.md

, .windsurf/rules/graft.md

, .adal/skills/graft/SKILL.md

for AdaL (progressive-disclosure skill, same shape as the Claude Code skill below). Re-running only updates Graft's own section (or replaces the owned file) and never touches the rest of your content.

With no TTY to prompt on โ€” CI, a Dockerfile, a piped shell โ€” init

writes nothing and prints the command to run instead. Pass --agents <ids>

or --yes

to make a scripted run explicit.

Flag Effect
--agents <ids...>
wire only these, no prompt โ€” ids: agents , cursor , gemini , copilot , kiro , windsurf , adal , claude
--yes , -y
skip the prompt and wire every detected agent
--dry-run
print every file init would touch, then exit without writing
--all-agents
write instruction files for every known agent, detected or not
--no-agents
Claude Code wiring only; skip other agents
--list-agents
print the known agent ids and exit
--no-mcp
skip MCP server registration
--no-hooks
skip hook installation
--no-global
skip writes outside this repo (the ~/.codex/ entries below)

Selecting the agents

host also touches your user-level Codex config, when ~/.codex/

exists:

Path What changes
~/.codex/config.toml
registers the Graft MCP server ([mcp_servers.graft] )
~/.codex/hooks/graft/graft-hooks.cjs
the post-edit hook shim
~/.codex/hooks.json
a PostToolUse entry matching `Write Edit

Both configs are user-level, so they apply to every repo you open with Codex, not just this one. The picker labels these machine-wide

, --dry-run

lists them in their own section, and --no-global

skips them while still wiring AGENTS.md

.

graft init

also registers Graft's MCP server with agents that support it, so these six tools appear natively, no shell required. Claude Code gets this too: graft init

writes the server into the project's .mcp.json

(restart Claude Code to load it). Skip with --no-mcp

; run it manually with graft mcp [dir]

.

Tool Takes What it's for
graft_ask
a question Ranked nodes with file:line, source inlined โ€” usually the full answer, no follow-up read needed.
graft_skeleton
a file path Every signature in that file, no bodies โ€” the API surface for a tenth of the tokens.
graft_callers
a symbol Who depends on it, or what it depends on with direction: out , N levels deep for blast radius.
graft_grep
a regex Every hit, grouped by enclosing symbol, ranked by how coupled that symbol is.
graft_map
nothing A first look at an unfamiliar repo: directory clusters, hubs, hotspots.
graft_check
nothing Whether the local graph has drifted from the code.

Register it by hand if your agent needs it explicit:

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

Where a CLI agent supports user-level hooks.json

, init

also installs Graft's post-edit hook โ€” blast-radius warnings and automatic $0

graph re-sync after edits (skip with --no-hooks

).

graft init

always wires up Claude Code, and Claude Code gets more than an instruction file. From then on, any Claude Code session opened in the repo gets:

a live statuslineโ€” graph size, % enriched, and aโš  N stale

warning when the code has moved ahead of the graphauto-syncโ€” every graft query brings the graph up to date first, so an answer always describes the code as it is right now, uncommitted edits included. A query refreshes only what it reads; the markdown undergraft/

is refreshed by the background rebuild at the end of a turn that touched code. Both are structural and$0

โ€” auto-sync never calls the LLM on its owncontext on tapโ€” each prompt pulls the matching nodes into the session; editing a file surfaces what depends on it ("blast radius"); new sessions start with the repo map

edit a file โ†’ blast radius appears inline โ†’ graph auto-resyncs โ†’ confirmed in graft viz

graft init

is idempotent and never clobbers your existing .claude/settings.json

โ€” it merges its blocks and leaves the rest alone. Want the LLM summaries too? Run graft build --deep

(with a key) whenever you like; auto-sync will never do it for you.

graft build [dir]                    # build graft/ from the code at [dir]: wiring graph + per-file cards (no LLM, no key)
graft build --deep                   # add the LLM layer: concept nodes + per-symbol summary/crux (cached)
graft build --extensions .ts .py     # only include these code extensions
graft build --no-reuse               # re-parse every file instead of replaying unchanged ones from cache

graft ask "<task>" [dir]             # query the graph โ€” ranked nodes + exact file:line (no LLM, no key)
graft ask "<task>" --json            # machine-readable result
graft ask "<task>" --in <scope>      # narrow to one sub-project of a monorepo/multi-repo folder (see below)

graft skeleton <file> [dir]          # every signature in one file, no bodies โ€” the API surface for ~1/10th the tokens (no LLM, no key)

graft callers <symbol> [dir]         # who calls/references/imports/implements/extends a symbol (no LLM, no key)
graft callers <symbol> --direction out  # the reverse: what the symbol itself calls/references (was `graft callees`)
graft callers <symbol> -d N          # walk transitively out to depth N โ€” full blast radius (was `graft impact`)

graft grep "<regex>" [dir]           # exhaustive regex search over indexed files, grouped by enclosing symbol (no LLM, no key)
graft grep "<regex>" --in <path>     # narrow to files whose path contains this substring
graft grep "<regex>" -i --fixed      # case-insensitive; treat the pattern as a literal string, not a regex

graft map [dir]                      # token-budgeted repo orientation โ€” dir clusters, hubs, hotspots (no LLM, no key)
graft map --max-dirs N               # raise/lower the number of directories shown

graft check [dir]                    # fail (exit 1) if graft/ has drifted from the code (never auto-refreshes โ€” it's the drift report)
graft check --json                   # print the drift report as JSON


graft viz [dir]                      # see the graph: serves an interactive viewer on localhost
graft viz --port 5000 --no-open      # pick a port; don't auto-open the browser

graft init [dir]                     # pick which agents to wire (prompts on a terminal; writes nothing until you choose)
graft init --dry-run                 # list every file it would touch, then exit
graft init --agents cursor kiro      # wire only these agents, no prompt (ids: agents, cursor, gemini, copilot, kiro, windsurf, adal, claude)
graft init --yes                     # no prompt; wire every detected agent
graft init --no-global               # skip writes outside this repo (~/.codex/ config + hooks)
graft init --no-build                # wire the files only; don't build the graph
graft init --all-agents              # wire every known agent, detected or not
graft init --list-agents             # list known agent ids and exit

graft version                        # print the installed + latest published npm version
graft upgrade                        # npm install -g the latest published version

graft --dir <path>                   # use a context dir other than <repo>/graft
graft --version, -v                  # print the installed version and exit

Method calls resolve through the receiver's type โ€” constructor assignments (self.router = APIRouter()

) and type annotations, not just the call-site name โ€” so callers

/grep --in

return calls bound to the right type on method-heavy code, not every method anywhere with that name.

graft grep "<regex>"

is exhaustive over every indexed file and groups hits by enclosing symbol, ranked by the same in-edge coupling graft map

uses โ€” built for "every occurrence of this pattern" tasks where graft ask

's ranked top-N isn't enough:

"NEEDLE" โ€” 2 hits in 2 symbols across 1 files (searched 1 indexed files)

heavilyCalled ยท function ยท src/a.ts:L1-L3 ยท 3 in-edges
  L2: console.log("NEEDLE hit in heavilyCalled");

rarelyCalled ยท function ยท src/a.ts:L4-L6 ยท 0 in-edges
  L5: console.log("NEEDLE hit in rarelyCalled");

graft map

is a token-budgeted first look at a repo โ€” directory clusters with file/symbol counts, each dir's local hubs, and the global hotspots โ€” all ranked by in-degree, no LLM, no key:

repo map โ€” 113 files ยท 687 symbols ยท 2186 edges ยท typescript

src/                63 files ยท 527 symbols   hubs: contextDirFor (node-file.ts, 21โ†), wiringPath (write.ts, 14โ†), buildGraph (build.ts, 11โ†)
test/               43 files ยท 102 symbols   hubs: edge (graph-traverse.test.ts, 4โ†), graphOf (graph-traverse.test.ts, 4โ†), fileNode (graph-map.test.ts, 3โ†)
viewer/             5 files ยท 58 symbols   hubs: $ (main.ts, 9โ†), activeGraph (main.ts, 5โ†), cvar (data.ts, 5โ†)
scripts/            2 files ยท 0 symbols

hotspots: contextDirFor ยท function ยท src/context/node-file.ts:L100-L103 ยท 21โ†  wiringPath ยท function ยท src/graph/write.ts:L20-L22 ยท 14โ†  buildGraph ยท function ยท src/graph/build.ts:L104-L218 ยท 11โ†  ...

Graft handles two shapes without any config:

A monorepo with one(a.git

pnpm-workspace.yaml

/package.json

workspaces

, or per-packagego.mod

/pyproject.toml

/Cargo.toml

) โ€”graft build

discovers each sub-project as a ranking scope.ask

/map

rank every scope on its own terms and fuse the results, so the biggest sub-project can't drown a small one; hits carry[scope/]

labels, andgraft map

groups its directory clusters by scope first.A folder of separate git repos(no.git

at the top) โ€”graft build

auto-splits: each child gets its own (git-ignored)graft/

, and the parent gets agraft/workspace.json

index. Queries from the parent federate across every child, always labeled<child>/

. Rungraft build

inside a child to work on just that repo.

Either way, narrow to one sub-project with graft ask "<task>" --in <scope>/

once you know where you're working.

graft viz

opens a local, interactive view of both graphs โ€” no install, no dev server; the viewer ships prebuilt inside the package.

search โ†’ jump to a node โ†’ dependency graph lights up

Context tab โ€” the architecture graph fromgraft/*.md

. Nodes colored by type, sized by connectedness.Code tab โ€” the per-symbol graph fromgraft/.graph/wiring.json

(rungraft build

first).Outline tab โ€” the file โ†’ class โ†’ method hierarchy as a collapsible tree.

Edges speak the code's language. Every link is one of a closed set of verbs, each answering a question someone building or reviewing code actually asks:

Verb The question it answers
part_of / contains
where does this live?
uses / calls / imports / depends_on
what breaks if I change this?
produces
where does this output come from?
configures
what changes its behavior without a code change?
validates
what checks or judges this? (tests, drift checks, scoring)
extends / implements
what contract must this honor?

Select a node and its edges take on direction: amber = what it depends on, teal = what depends on it, with the verb written on each highlighted edge. Chips above the canvas filter by verb; tree-sitter-extracted edges draw solid while LLM-inferred ones draw dashed. The viewer live-reloads when graft/

changes on disk. Older graphs with vague verbs (influences

, supports

) are normalized on load โ€” no regeneration needed.

An agent that reads the graph should be cheaper and faster without getting more answers wrong. That's the whole claim, so we measured it instead of asserting it.

The harness ran three variants of the same Claude Sonnet 5 agent with the same file tools: cold (explores from zero), Graft (a graft ask --source

bundle pushed up front), and pull (graft_ask/graft_skeleton tools, nothing injected โ€” context paid for only when asked). An Opus 4.8 judge scored correctness with a required-keyword floor, so a fast-but-wrong answer couldn't win by being fast. Cost is cache-aware: reads โ‰ˆ0.1ร—, writes 1.25ร—, the billing model agents actually run under.

162 runs, two repos (graft itself and a real Node/Express auth service), 3 trials each, tasks split between single-file and multi-file questions.

Metric (mean/task) Cold Graft
Cost ($) 0.0429 0.0292 (โˆ’32%)
Uncached input tokens 8,070 4,650 (โˆ’42%)
Tool calls 4.2 2.3 (โˆ’46%)
Latency (s) 39.8 15.8 (โˆ’60%)
Correctness 93% 93% (equal)

Graft never answered worse than cold, on any corpus. The pull variant gave up most of that speed for something bigger: correctness jumped to 98%, +5 points over cold, the strongest single result in the sweep. Push when speed is what you need; pull when being right matters more.

The sweep above measures the mechanism. The real test is whether graft helps an agent ship real changes on code people actually run, not just answer questions. So we benchmark it on popular open-source repos: 15 tasks each, 10 real developer questions plus 5 actual implementation tasks (real merged pull requests, each re-implemented from its base commit and scored against the files the maintainers actually changed). Same agent (Claude Opus), same file tools; the only difference is whether graft is wired in.

Across these repos graft runs up to 4ร— cheaper and 3ร— faster, with better or no loss of correctness: it reproduces the real merged PRs by touching the same files the maintainers did. Per-repo detail below.

Aggregate over 15 tasks Standard Claude Code With graft
Cost $13.91 $11.02 (โˆ’21%)
Wall-clock 2,044s 1,762s (โˆ’14%)
PRs reproduced 5 / 5 5 / 5 (same files as the maintainers)

Cheaper and faster with no loss of correctness: graft reproduced all five merged PRs, touching the same files the maintainers did. The gap is widest on cross-file understanding โ€” "how does auth work across OAuth2 providers" dropped from $2.19 to $0.84.

The 10 questions we asked

Orientationโ€” Give me a map of PocketBase's architecture: the main subsystems and how an HTTP request flows through to the database.** Entry-point trace**โ€” Trace end-to-end what happens when a client creates a record via the REST API, from route handler to database write.** Feature location**โ€” I want to add a brand-new collection field type. Where do I hook it in, and which pieces must change?** Bug localization**โ€” Realtime subscriptions silently stop delivering events after a while. Where would you start looking, and why?** Blast radius**โ€” If I change the signature of the record-validation logic, what depends on it and what could break?** Cross-file synthesis**โ€” How does auth work across OAuth2 providers: where are tokens issued, validated, stored, and refreshed?** Extensibility**โ€” How do I use PocketBase as a Go framework to register a custom route plus an on-record-create hook?** Security discovery**โ€” Where is user input validated, and where are collection API access rules enforced before a query runs?** Public API**โ€” As an external app, how do I authenticate and then list and filter records over the REST API?** Test verification**โ€” Where are the tests for the record CRUD API, and what do they assert about access rules?

The 5 merged PRs we re-implemented

Each PR was reset to its base commit; graft's diff was scored against the files the merged PR changed.

PR Type What it does Files the maintainers touched

apis/file.go

, tools/filesystem/filesystem.go

#6947tools/security/random_by_regex.go

#6690x/oauth2/endpoints

tools/auth/patreon.go

#2726apis/middlewares.go

#3192plugins/migratecmd/templates.go

Method

Two clones of PocketBase at the same commit: one wired with graft init

, one untouched and verified graft-free. Each task run headless (claude -p

, Claude Opus) with an empty MCP config. Understanding questions were graded by whether the answer pointed to the right files and functions; PR tasks were scored on whether the agent's diff touched the same files as the merged PR. Every transcript was audited to confirm graft was actually used in the graft arm and absent from the standard arm.

git clone https://github.com/NanoNets/context-graph-engine.git && cd context-graph-engine
npm install
npm run build
npm test

npm run cli -- build --deep .      # run the CLI from source

MIT. See LICENSE.

โ”€โ”€ more in #developer-tools 4 stories ยท sorted by recency
โ”€โ”€ more on @nanonets 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain โ€” perfect for shipping the agent you just read about.

$git push zahid main
โ†’ Live at https://your-agent.zahid.host โœ“
Get free account โ†’ Pricing
from โ‚ฌ0/mo ยท no card required
LIVE [news/graft] indexed:0 read:21min 2026-07-30 ยท โ€”