A four-axis code-navigation combine for AI coding agents. It binds four persistent tools — qdrant, graphify, beacon, serena — into one doctrine so an agent orients before editing, locates code by meaning, recalls prior decisions, and feeds each finding back so the tools sharpen one another.
This is not a new MCP server. It is a skill (doctrine) plus glue scripts. graphify is a CLI; serena/beacon/qdrant are MCP tools the agent already has. codenav tells the agent how to chain them and ships the shell-runnable connective tissue.
| Tool | Axis | Question it answers | Authority |
|---|---|---|---|
| qdrant | time | what did we decide / break / learn before? | the diary |
| graphify | topology (macro) | how does it all connect, where are hubs & bridges? | the map |
| beacon | semantic retrieval | find the code by meaning — name unknown | the front door |
| serena | precision (micro), live | where EXACTLY, who references it? | the scalpel |
Each owns a different axis. graphify and serena assume you know a name; beacon is the entry when you only have a description; qdrant is the only one that crosses sessions; serena is the only one that reads live source. Used in order they collapse "I don't know this codebase" into a precise edit with minimal token burn.
0. doctor.sh --fix self-heal: install deps, build/refresh graph (run once per repo)
1. qdrant-find "<keywords>" recall prior context (skip on trivial lookups)
2. graphify query "<question>" orient by topology — which abstracts are involved
3. beacon semantic-search fuzzy-locate files when the symbol name is unknown
4. serena find_symbol / pinpoint exact symbol + callers (the authority)
find_referencing_symbols (editing a hub / prepping a PR? blast_radius.py FIRST)
5. grep / Glob last resort
6. after change: graphify update . + graphify_to_qdrant.py + qdrant-store the decision
You don't always run all five — see skills/codenav/SKILL.md for the decision rule.
flowchart TD
Q["Code question / task"] --> R{"Edited here<br/>before?"}
R -->|yes| MEM["qdrant-find<br/><i>recall prior decisions/gotchas</i>"]
R -->|no| MEM
MEM --> ORI["graphify query<br/><i>orient: which abstracts?</i>"]
ORI --> K{"Know the<br/>symbol name?"}
K -->|"no — only a description"| SEM["beacon semantic-search<br/><i>fuzzy-locate candidate files</i>"]
K -->|yes| PIN
SEM --> PIN["serena find_symbol /<br/>find_referencing_symbols<br/><i>pinpoint + callers — the authority</i>"]
PIN --> V{"serena<br/>confirms?"}
V -->|no| STALE["index stale →<br/>graphify update . / beacon reindex"]
V -->|yes| EDIT["make the edit"]
STALE --> PIN
EDIT --> POST["graphify update .<br/>+ qdrant-store the decision"]
classDef mem fill:#2d2d44,stroke:#8888cc,color:#fff
classDef topo fill:#1f3a2f,stroke:#66bb88,color:#fff
classDef sem fill:#3a3320,stroke:#ccaa55,color:#fff
classDef prec fill:#3a2030,stroke:#cc6699,color:#fff
class MEM,POST mem
class ORI topo
class SEM sem
class PIN prec
- graphify → qdrant:
scripts/graphify_to_qdrant.pyemits god-nodes, cross-abstract bridges and the abstract map as qdrant-store-ready facts (kind: architectural-fact). A future session recalls the topology without rebuilding. - beacon → graphify:
scripts/beacon_enrich.pytakes a beacon hit's file path and reports which abstract it lives in and what it bridges to — a flat hit becomes a situated one. - serena ↔ graphify:
serena find_symbolgives the exact location;graphify explaingives the neighbourhood. Micro and macro views of the same node. - fan-out:
scripts/locate.sh "<concept>"runs graphify now and prints the exact beacon + serena calls for the agent to merge.
Two surfaces derived from the same graph close the gap with dedicated graph-review and visual-onboarding tools:
- change-impact:
scripts/blast_radius.py --base origin/mainwalks the dependents of changed files N hops → impacted files/abstracts + per-seed test-coverage gaps + a low/medium/high risk hint. Run it before editing a hub or prepping a PR to size the blast radius first. Every hit is a graph-derived hypothesis — confirm hot edges with serena. - onboarding: graphify already emits
graphify-out/GRAPH_REPORT.md(god-nodes, abstracts, suggested questions) and an interactive*.htmlmap on every build. Read/open those to orient a human on an unfamiliar repo;graphify explain/graphify pathgive walkthroughs.
Each tool's output sharpens another's input — the combine is a cycle, not a one-way pipe:
flowchart LR
subgraph axes["the four axes"]
QD["qdrant<br/><b>time</b>"]
GF["graphify<br/><b>topology</b>"]
BC["beacon<br/><b>semantic</b>"]
SR["serena<br/><b>precision</b>"]
end
GF -->|"god-nodes + bridges<br/>(graphify_to_qdrant.py)"| QD
QD -->|"recalled symbol/file<br/>→ skip rediscovery"| SR
QD -->|"recalled abstract<br/>→ scoped query"| GF
BC -->|"hit file → which abstract?<br/>(beacon_enrich.py)"| GF
GF -->|"candidate symbol names"| SR
SR -->|"exact source_location"| GF
GF -->|"explain neighbourhood"| SR
SR -.->|"authority: overrides<br/>stale index hits"| BC
SR -.->|"authority"| GF
classDef t fill:#2d2d44,stroke:#8888cc,color:#fff
classDef g fill:#1f3a2f,stroke:#66bb88,color:#fff
classDef b fill:#3a3320,stroke:#ccaa55,color:#fff
classDef s fill:#3a2030,stroke:#cc6699,color:#fff
class QD t
class GF g
class BC b
class SR s
This repo is a self-hosting plugin marketplace. From Claude Code:
/plugin marketplace add DKeken/codenav
/plugin install codenav@codenav
Then invoke the doctrine with /codenav:codenav. The skill and glue scripts come with it.
Drop the skill where your agent loads skills (Claude Code: ~/.claude/skills/codenav/):
cp -r skills/codenav ~/.claude/skills/codenavMake scripts runnable and call them from your repo root (where graphify-out/graph.json lives):
chmod +x scripts/*.sh scripts/*.py
bash scripts/doctor.sh --fix # one-command self-heal: deps + graph
python3 scripts/graphify_to_qdrant.py --project <name> --skip-barrels # emit architectural facts
python3 scripts/beacon_enrich.py --file <path> # situate a beacon hit
python3 scripts/blast_radius.py --base origin/main # change-impact before a PR
bash scripts/locate.sh "<fuzzy concept>" # fan-out locatedoctor.sh is the bootstrap a fresh install runs first: it installs graphify + python deps if
missing, builds or refreshes graphify-out/graph.json, applies a taxonomy.py re-cluster when
present, checks every bundled script, and prints the agent-side MCP checks (qdrant/beacon/serena)
a shell cannot run. Bare doctor.sh diagnoses without changing anything.
--skip-barrels drops index.ts / __init__.py re-export files from the god-node list so
the signal is real abstractions, not structural plumbing. Barrels that remain (in bridges) are
disambiguated by parent dir (index.ts (deps)) instead of collapsing into one fake node.
recluster.py self-bootstraps: if graphify/networkx aren't importable under the launching
python, it re-execs under the interpreter graphify recorded at build time
(graphify-out/.graphify_python). So a bare python3 recluster.py works regardless of where
graphify is installed.
Stdlib only, no graphify install needed (the functions under test are pure):
python3 tests/test_glue.pyCovers barrel disambiguation, --skip-barrels, the bridge metric (own + reached communities),
beacon_enrich's three outcomes (full-path resolve, ambiguous bare filename, unknown file), and
blast_radius's reverse/forward dependent walk + per-seed test-coverage-gap detection.
-
graphify —
pip install graphifyy(oruv tool install graphifyy). Build the graph once:graphify .thengraphify update .after changes. Optionalgraphify --mcpexposes it over MCP. -
serena — the serena MCP server (symbol search over your repo). No model needed; it runs a language server over your code.
-
beacon — the hybrid (semantic + keyword + BM25) code-search MCP. Semantic search needs a running embedding backend. Out of the box beacon points at a local Ollama:
# 1. install + run Ollama, then pull the default embedding model ollama pull nomic-embed-text # 768-dim, what beacon expects by default ollama serve # serves http://localhost:11434 # 2. index your repo, then sanity-check /beacon:reindex # first full index (one-time, then incremental) /beacon:index-status # file/chunk count, last sync
Beacon's defaults: provider
ollama, modelnomic-embed-text, endpointhttp://localhost:11434/v1, 768 dims, hybrid chunking (512 tok, 50 overlap). Config lives in.claude/beacon.json; change a setting with/beacon:config set <key> <value>.Don't want to run Ollama? Switch the provider — each has different dims, so a reindex is forced when you switch:
provider model dims needs ollama(default)nomic-embed-text768 local Ollama, no key openaitext-embedding-3-small1536 OPENAI_API_KEYvoyagevoyage-code-31024 Voyage key litellmvoyage-code-31024 a LiteLLM proxy (Vertex/Bedrock/…) /beacon:config provider openai # then set OPENAI_API_KEY, confirm the reindex promptIf you skip beacon entirely, codenav still works — you lose the "find by meaning when the symbol name is unknown" step and fall back to graphify-orient → serena-pinpoint.
-
qdrant — the qdrant MCP with a persistent collection for cross-session memory. Point it at a local or hosted Qdrant; codenav tags every fact with
metadata.projectso memories stay per-repo retrievable.
codenav reads no API keys of its own — it orchestrates whatever the session already has. The only model dependency is beacon's embedding backend (Ollama by default); graphify (AST-only) and serena (language server) need no model or key.
graphify's Louvain clustering on a sparse AST-only graph over-fragments — a well-layered monorepo can split into hundreds of tiny communities. When the project already HAS a canonical architecture, re-cluster by that taxonomy instead of by blind modularity:
python3 scripts/recluster.py --map taxonomy.example.pyrecluster.py remaps every node into a project-defined abstract by its source path, then
regenerates graph.json, the report, and the HTML. On a 14k-node / 689-community graph this
collapsed to 39 clean abstracts that mirror the real architecture (contracts, core, db:,
api:, web:, …). Copy taxonomy.example.py, edit the classify() function for
your repo's layout, and pass it with --map.
codenav was built and battle-tested on AGONTS — a multi-tenant
AI-teammate SaaS (hexagonal monorepo: contracts → core → adapters → apps, seven bounded
contexts). The 14k-node / 689→39 re-cluster numbers above are from its real codebase, and
taxonomy.example.py encodes its actual layer/context layout — copy it as a starting point if
your repo is shaped similarly. The doctrine exists because navigating a repo that size with grep
alone was the bottleneck; the four-tool combine is what replaced it.
MIT