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 start
- The problem
- What Graft does
- How the graph gets built
- What's in a node
- What runs where
- Agent integration — MCP server · Claude Code (deep integration)
- CLI
- Search & orient (
graft grep/graft map) - Monorepos & multi-repo folders
- Visualize it (
graft viz) - Benchmark
- Tested on your popular repos
- Development
- License
npm install -g @nanonets/graft # install the CLI, once
graft init # build the graph + wire it into Claude CodeThat is the whole setup. graft init 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.
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 in
graft/. 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"]
Both LLM passes are cached by content hash. Re-running only touches the files that changed, so the second build is fast and cheap.
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 askare deterministic tree-sitter — they never call a model. - Through your provider key: the LLM-written parts —
graft build --deepadds the concept nodes (file summaries + node synthesis) and the per-symbol summaries and cruxes. graft is vendor-neutral: setGRAFT_PROVIDER(openaifor any OpenAI-compatible endpoint, oranthropicfor the native API), yourGRAFT_API_KEY,GRAFT_MODEL, and — for theopenaiwire format —GRAFT_BASE_URLto point at OpenRouter, Fireworks, Groq, a LiteLLM proxy, a local server, or OpenAI itself. Or pass--provider/--model/--api-key/--base-urlon the command line. (OPENROUTER_API_KEYstill 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
# detects your agents and writes each one's native instruction file;
# Claude Code additionally gets the live statusline + hooks belowinit auto-detects which agents are present (via their config directories) and writes a marker-fenced Graft section into each one's shared instruction file — AGENTS.md, GEMINI.md, .github/copilot-instructions.md — or a wholly-owned rule file for the agents that use one — .cursor/rules/graft.mdc, .kiro/steering/graft.md, .windsurf/rules/graft.md. Re-running only updates Graft's own section (or replaces the owned file) and never touches the rest of your content.
| Flag | Effect |
|---|---|
--agents <ids...> |
wire only these — ids: agents, cursor, gemini, copilot, kiro, windsurf, claude |
--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 |
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 stalewarning when the code has moved ahead of the graph - auto-sync — after you edit code, Graft rebuilds the graph in the background at the end of the turn (structural,
$0— it never calls the LLM on its own) - context 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 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
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] # wire Graft into the coding agents detected in this repo (Claude Code always gets full hooks + statusline + MCP)
graft init --no-build # wire the files only; don't build the graph
graft init --agents cursor kiro # wire only these agents (ids: agents, cursor, gemini, copilot, kiro, windsurf, claude)
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
# global
graft --dir <path> # use a context dir other than <repo>/graft
graft --version, -v # print the installed version and exitMethod 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
.git(apnpm-workspace.yaml/package.jsonworkspaces, or per-packagego.mod/pyproject.toml/Cargo.toml) —graft builddiscovers each sub-project as a ranking scope.ask/maprank 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 mapgroups its directory clusters by scope first. - A folder of separate git repos (no
.gitat the top) —graft buildauto-splits: each child gets its own (git-ignored)graft/, and the parent gets agraft/workspace.jsonindex. Queries from the parent federate across every child, always labeled<child>/. Rungraft buildinside 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 from
graft/*.md. Nodes colored by type, sized by connectedness. - Code tab — the per-symbol graph from
graft/.graph/wiring.json(rungraft buildfirst). - 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 |
|---|---|---|---|
| #6744 | feat | Generate & serve WebP thumbnails | apis/file.go, tools/filesystem/filesystem.go |
| #6947 | fix | Uniform char distribution in regex random strings | tools/security/random_by_regex.go |
| #6690 | refactor | Patreon OAuth2 to use x/oauth2/endpoints |
tools/auth/patreon.go |
| #2726 | perf | Drop a redundant admin-count query on a hot middleware path | apis/middlewares.go |
| #3192 | fix | Restore prior API rules on automigration rollback | plugins/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 sourceMIT. See LICENSE.

