Featured

Deploy OpenClaw in 60 seconds — 20% off logoDeploy OpenClaw in 60 seconds — 20% off

Launch OpenClaw on Hostinger in about 60 seconds and keep your agent live 24/7. Our referral link gives you 20% off, no coupon code needed.

Launch on Hostinger
Run your Hermes agent on Hostinger, fully managed logoRun your Hermes agent on Hostinger, fully managed

Launch Hermes on Hostinger in one click, fully managed, no VPS knowledge needed. Use code ZACAARON10 for 10% off.

Launch on Hostinger
Crawl and scrape any site into clean data, 10% off logoCrawl and scrape any site into clean data, 10% off

Firecrawl crawls and scrapes any site into clean markdown for your agent. Get 1,000 free credits, and new users get 10% off their first purchase.

Try Firecrawl free
6,000+ web scrapers for your AI agent, start free logo6,000+ web scrapers for your AI agent, start free

Apify gives your agent live web data: 6,000+ prebuilt scrapers and actors, MCP-ready. Sign up free with $5 in usage credits.

Try Apify free
One API to scrape, enrich, and extract the internet. logoOne API to scrape, enrich, and extract the internet.

Context.dev gives your agents a single API to scrape, enrich, and extract live web data — no proxies, no parsers, no maintenance.

Start building free
SetupClaw: done-for-you OpenClaw for founders & exec teams logoSetupClaw: done-for-you OpenClaw for founders & exec teams

White-glove OpenClaw for founders and exec teams (4–50+ employees): we install, harden, integrate your tools, and maintain it — secured from day one.

Get it set up for you
SEO data APIs for your agent, $1 free credit logoSEO data APIs for your agent, $1 free credit

DataForSEO gives your agent live access to SERP results, keyword data, backlinks, and on-page SEO data through one API. New accounts get a $1 credit, good for up to 20,000 keyword or backlink lookups.

Try DataForSEO free
Reach 48,000+ AI builders

A flat monthly placement in front of developers actively installing AI tools. No lock-in, cancel anytime.

Advertise here

Works with

Claude CodeClaude DesktopCursorVS CodeClineCodex CLIOpenClaw+ any MCP client

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

Builds a semantic knowledge graph of C++ code and exposes 9 MCP tools for AI assistants to search classes, functions, inheritance, callers, callees, overrides, and more.

README.md

cpp-semantic-graph

A C++ semantic knowledge graph — let AI understand your C++ codebase precisely

中文文档

Builds a semantic knowledge graph of a C++ codebase for AI coding assistants (Claude Code, Cursor, Windsurf, etc.) and exposes it through 9 query tools over the MCP protocol. The AI can then search class definitions, look up inheritance, trace call chains, and analyze the blast radius of a change — no file hopping, no grep.

Why this project exists: clang, not tree-sitter

Most general-purpose code-graph tooling parses source with tree-sitter — a fast, incremental syntax parser. That choice is fine for many languages, but it is the wrong tool for a C++ semantic graph.

The distinction is syntax vs. semantics:

  • A syntax parser answers *"do these tokens form a valid C++ program, and

what does the tree look like?"* — it does not resolve what a name refers to.

  • Semantic analysis (what a compiler front-end does) answers *"which entity

does this symbol denote, what is its type, and how does it relate to others?"* — this requires type resolution and name lookup.

C++ is a language where a great deal of information lives in the type system, not on the page. Consider:

namespace nv {
struct Base { virtual void PerformUpgrade() = 0; };
template<typename T> struct Middle : virtual Base {     // virtual inheritance + template
    void PerformUpgrade() override {}
};
struct SoCUpdate : Middle<SoC>, NonCopyable, public Logger {};   // multiple inheritance
}

A syntax parser (tree-sitter) can see that SoCUpdate is a class followed by three base-specifier text fragments. It cannot determine:

  • That Middle<SoC> is the specialization nv::Middle<SoC> — requires

template instantiation.

  • Which namespace Logger lives in — requires name lookup.
  • That the root of the chain is virtual Base — requires **expanding the

template and following the referenced declaration**.

  • Which base-class method PerformUpgrade() override actually overrides —

requires cross-class virtual-function matching.

These all require type resolution, which is exactly what a compiler front-end (clang) provides and what a syntax parser structurally cannot. This is not tree-sitter being "bad" — it is intentionally type-free, single-file, and fast, designed for syntax highlighting and code folding. Using it to build a C++ semantic graph is simply the wrong tool for the job.

This project uses clang/libclang's semantic AST instead. The difference is visible in the parser:

  • base_spec.referenced — resolves a base class to its **real declaration

cursor** across files and namespaces (parser/ast_visitor.py).

  • clang_isVirtualBase — detects virtual inheritance, the C++-specific

diamond-inheritance semantics that syntax trees have no notion of.

  • cursor.is_virtual_method() / is_pure_virtual_method() — virtual-function

semantics.

  • access_specifier — distinguishes public/protected/private inheritance.
  • Cross-TU override matching — base and derived classes often live in different

files; clang's referenced cursor links them, which a per-file syntax parse cannot.

Why do you need it?

Common pain points when an AI assistant tries to understand C++ code:

| Pain point | cpp-semantic-graph's answer | |------------|------------------------------| | "Where is this class defined?" | cpp_search_class("SocUpdate") → namespace + file location | | "Who calls this function?" | cpp_get_callers("GetSocBootChain") → all callers | | "What does changing this header affect?" | Incremental update recursively walks include deps, re-parses only affected TUs | | "Which overrides exist for this virtual?" | cpp_get_overrides("PerformUpgrade", "BasePeriUpdate") → all implementations | | "What does this module's architecture look like?" | cpp_traverse_graph("SocUpdate") → multi-hop traversal |

✨ Core features

  • 9 MCP tools: class search, function search, inheritance, call chains (caller/callee), overrides, file symbols, multi-hop traversal, doc search
  • Incremental updates: based on the include-dependency graph, a single .cpp change refreshes in seconds (16× faster than a full rebuild)
  • Doc fusion: bidirectional linking between project docs and code; searching docs auto-locates related code
  • Plug-and-play: one YAML config + compile_commands.json to start; the MCP server auto-registers with AI tools
  • Project-agnostic: no project-specific hardcoding in the schema or tool definitions — portable to any C++ project

🚀 Quick start

Prerequisites

  • Python 3.10+
  • libclang (matching your LLVM version)
  • compile_commands.json (generated by CMake/Bear)

1. Install

git clone https://github.com/FS-Yuao/cpp-semantic-graph.git
cd cpp-semantic-graph

# 一键创建 venv 并安装核心依赖 (推荐)
./setup_env.sh
#   默认在项目内创建 .venv;可选:
#   ./setup_env.sh /path/to/venv   指定 venv 路径
#   ./setup_env.sh --with-docs     同时装 doc embedding 依赖 (含 torch,体积大)

# 或手动创建:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

前置: 系统 libclang (匹配 LLVM 版本;Ubuntu: apt install libclang-18-dev)。 clang bindings 版本须与系统 libclang 一致 (本机 libclang-18 对应 clang>=18,<19,换 LLVM 版本时同步改 requirements.txt)。

2. Prepare compile_commands.json

If your project builds with CMake:

cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ...

Without CMake, use Bear to intercept build commands:

bear -- make

3. Write the config file

Create cpp_semantic_graph.yaml (the only file you need to edit):

project:
  name: "your_project"
  compile_commands: "/path/to/your/project/compile_commands.json"

# Source scope — decides what counts as "project code"
source_paths:
  - "src"
  - "include"

# Generated-code paths (e.g. ARA COM src-gen, protobuf)
generated_paths:
  - "src-gen"

# Paths to ignore entirely
exclude_paths:
  - "thirdparty"
  - "build"
  - "test/mock"

# libclang path (adjust for your system)
libclang_path: "/usr/lib/llvm-18/lib/libclang.so.1"

parse_options:
  skip_function_bodies: false  # must be false to extract call relations
  max_workers: 4               # parallel parse processes

4. Full parse

⚠️ Important: stop the MCP server before a full parse, or the DB connection it holds will cause write failures (disk I/O error). Stop with pkill -f "run_server.py", or disable the MCP server in Claude Code settings and restart.

# 1. Stop the MCP server (required!)
pkill -f "run_server.py"

# 2. Full parse
python3 -m cpp_semantic_graph full-parse \
  --config cpp_semantic_graph.yaml \
  --db semantic_graph_full.db

# 3. Restart the MCP server (after parsing finishes)

After parsing, the database contains:

  • Nodes: classes, structs, functions (with signature, namespace, file location)
  • Edges: inheritance, calls, overrides, belongs_to, type aliases (type_alias), using-declarations (using_decl), friends (friend_of), etc. (the instantiates template-instantiation edge is not yet enabled due to libclang AST shape — see Complex scenarios)
  • Include dependencies: the include graph between translation units
  • Doc associations: doc sections ↔ code entities (optional)

5. Start the MCP server

# Set the database path
export CPP_GRAPH_DB=/path/to/semantic_graph_full.db

# Start the MCP server (stdio transport)
python3 -m cpp_semantic_graph.mcp_server.server

6. Register with AI tools

Claude Code

Edit ~/.claude.json, add under mcpServers:

{
  "mcpServers": {
    "cpp-semantic-graph": {
      "type": "stdio",
      "command": "python3",
      "args": ["/absolute/path/to/cpp-semantic-graph/mcp_server/run_server.py"],
      "env": {
        "CPP_GRAPH_DB": "/absolute/path/to/semantic_graph_full.db"
      }
    }
  }
}

Cursor

Edit .cursor/mcp.json:

{
  "mcpServers": {
    "cpp-semantic-graph": {
      "command": "python3",
      "args": ["/absolute/path/to/cpp-semantic-graph/mcp_server/run_server.py"],
      "env": {
        "CPP_GRAPH_DB": "/absolute/path/to/semantic_graph_full.db"
      }
    }
  }
}

Windsurf / other MCP clients

Configuration is similar; refer to each tool's MCP docs. The core parameters:

  • command: python3
  • args: ["/path/to/mcp_server/run_server.py"]
  • env.CPP_GRAPH_DB: absolute path to the database

💡 Tip: the CPP_GRAPH_PROJECT env var sets the project name (used in MCP instructions); if unset it is inferred from the DB path.

---

🛠️ 11 MCP tools

| # | Tool | Purpose | Typical scenario | |---|------|---------|------------------| | 1 | cpp_search_class | Search class definitions by name | "Where is SocUpdate defined?" | | 2 | cpp_search_function | Search function definitions by name | "What's the signature of PerformUpgrade?" | | 3 | cpp_get_inheritance | Query a class's inheritance | "Which classes derive from BasePeriUpdate?" | | 4 | cpp_get_callers | Who calls a given function | "Who calls GetSocBootChain?" | | 5 | cpp_get_callees | What a given function calls | "What does PerformUpgrade call internally?" | | 6 | cpp_get_overrides | All overrides of a virtual function | "Which overrides exist for PerformUpgrade?" | | 7 | cpp_get_file_symbols | All symbols in a file | "What's in soc_update.cpp?" | | 8 | cpp_traverse_graph | Multi-hop graph traversal | "What does changing SocUpdate affect?" | | 9 | cpp_search_docs | Search project docs (-> related code) | "The OTA upgrade-flow design doc" | | 10 | cpp_blast_radius | Blast radius of a change (recursive callers + override expansion + file aggregation) | "I'm changing PerformUpgrade — which files must I review?" | | 11 | cpp_get_code_docs | Reverse: code symbol -> docs describing it | "What design docs describe PerformUpgrade?" |

Tool details

cpp_search_class(name, exact=False)

Search C++ class definitions by name. Supports fuzzy matching.

cpp_search_class("SocUpdate")
-> ## Search results: class "SocUpdate" (1)
  ### update::SocUpdate
  - File: soc_update.h:15-120

cpp_search_function(name, class_name="")

Search function definitions by name. Optionally restrict by owning class.

cpp_search_function("PerformUpgrade", class_name="SocUpdate")
-> ## Search results: function "PerformUpgrade" (1)
  ### SocUpdate::PerformUpgrade [virtual, override]
  - Signature: void PerformUpgrade() override
  - File: soc_update.cpp:45

cpp_get_inheritance(class_name, direction="down", depth=1)

Query inheritance. direction="down" for subclasses, "up" for base classes. depth=-1 recurses fully.

cpp_get_inheritance("BasePeriUpdate", direction="down", depth=-1)
-> ## Subclasses of BasePeriUpdate (4)
  - update::SocUpdate --public--> BasePeriUpdate
  - update::McuUpdate --public--> BasePeriUpdate
  - ...

cpp_get_callers(function_name, class_name="")

Who calls a given function (impact analysis).

cpp_get_callers("GetSocBootChain")
-> ## Code that calls "GetSocBootChain" (3)
  ### OtaManager::CheckBootChain
  - File: ota_manager.cpp:128
  - Call type: calls_direct

cpp_get_callees(function_name, class_name="")

What a given function calls (call-chain analysis).

cpp_get_callees("PerformUpgrade", class_name="SocUpdate")
-> ## Code called by "PerformUpgrade" (8)
  ...

cpp_get_overrides(function_name, class_name)

All override implementations of a virtual function. class_name is the base class that declares the virtual (required).

cpp_get_overrides("PerformUpgrade", class_name="BasePeriUpdate")
-> ## Overrides of "PerformUpgrade" (4)
  ### update::SocUpdate::PerformUpgrade
  - Signature: void PerformUpgrade() override
  - File: soc_update.cpp:45
  - Overrides base: BasePeriUpdate

cpp_get_file_symbols(file_path)

All class and function symbols in a file. file_path supports partial matching.

cpp_get_file_symbols("soc_update.h")
-> ## File symbols: soc_update.h (12 total)
  ### Classes/structs (2)
    1. ### [class] update::SocUpdate
  ### Functions (10)
    ...

cpp_traverse_graph(start, relation_types=None, direction="outgoing", depth=3, max_results=50)

Multi-hop graph traversal — the most flexible query. Walks related nodes along specified relation types.

Common relation types: inherits_public, inherits_protected, overrides, belongs_to, calls_direct, calls_virtual, calls_callback, doc_describes_code, code_refers_to_doc

cpp_traverse_graph("SocUpdate", depth=2, max_results=30)
-> ## Traversal results: from "SocUpdate" (18 nodes)
  Depth: 2, edges traversed: 22
  ### Related nodes
    - [class] update::SocUpdate (soc_update.h)
    - [class] update::BasePeriUpdate (base_peri_update.h)
    - [function] update::SocUpdate::PerformUpgrade (soc_update.cpp)
    ...

cpp_search_docs(keyword, tag="", max_results=10, min_confidence=0.7)

Search project docs, returns doc sections + associated code.

cpp_search_docs("升级", tag="架构设计")
-> ## Doc search: "升级" (3 results)
  ### OTA complete upgrade flow
  - File: docs/OTA_flow/OTA_COMPLETE_FLOW.md
  - Word count: 2450
  - Tags: 架构设计, OTA

  Associated code:
    - [class] BasePeriUpdate confidence=0.92
    - [class] SocUpdate confidence=0.88

Note: default min_confidence=0.7 filters low-quality co-occurrence associations (confidence=0.6 makes up 63%, mostly noise like "刷写" matching Data/Response). Pass 0.0 to see all associations.

cpp_get_code_docs(symbol, min_confidence=0.0, max_results=10)

Reverse lookup: given a code symbol, returns doc sections describing it (design docs / HLD / architecture docs). Complement of cpp_search_docs — pass a code symbol directly without thinking of keywords.

cpp_get_code_docs("PerformUpgrade")
-> ## Docs describing "PerformUpgrade" (6 sections)

  ### 3. Current flow
  - Doc: SOC A/B partition switch design
  - File: AB_Switch/AB_PARTITION_SWITCH_DESIGN.md:62-81
  - Tags: 架构设计, A/B分区

  ### 4.3 Components
  - File: ADC4.0_System_architecture/ADC4.0_OTA_SW_HLD.md:342-578
  - Tags: 系统架构

Note: reverse associations are mostly content_scan (confidence=0.6). Unlike forward, code symbols appearing in docs are usually genuinely about them, so 0.6 is typically valid. Default returns all, rely on max_results to bound.

---

📖 CLI usage

Besides MCP tools, a CLI is provided for direct queries:

# Search a class
python3 -m cpp_semantic_graph search-class "SocUpdate"

# Query inheritance
python3 -m cpp_semantic_graph inheritance "BasePeriUpdate" --direction down --depth -1

# Search a function
python3 -m cpp_semantic_graph search-func "PerformUpgrade"

# File symbols
python3 -m cpp_semantic_graph file-symbols "soc_update.cpp"

# Include dependencies
python3 -m cpp_semantic_graph include "base_peri_update.h" --mode all

# DB stats
python3 -m cpp_semantic_graph stats

Incremental update

After a code change, the incremental update re-parses only affected translation units:

# Based on git diff (default HEAD~1)
python3 -m cpp_semantic_graph incremental --base HEAD~1

# Specify files manually
python3 -m cpp_semantic_graph incremental --files soc_update.cpp,base_peri_update.h

# Detect only, don't execute
python3 -m cpp_semantic_graph incremental --files soc_update.cpp --dry-run

# Skip doc-association rebuild (faster)
python3 -m cpp_semantic_graph incremental --files soc_update.cpp --skip-associations

---

📐 Architecture

┌─────────────────────────────────────────────────┐
│                  AI tool layer                   │
│  Claude Code / Cursor / Windsurf / other MCP     │
└────────────────────┬────────────────────────────┘
                     │ MCP protocol (stdio)
┌────────────────────▼────────────────────────────┐
│              MCP Server (9 tools)                │
│  FastMCP + Lazy-init DB connection + Markdown    │
└────────────────────┬────────────────────────────┘
                     │ Python API
┌────────────────────▼────────────────────────────┐
│                Query layer (query/)              │
│  GraphQuery │ CallQuery │ PolymorphismQuery      │
│  TraverseQuery │ DocQuery │ IncludeQuery         │
└────────────────────┬────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────┐
│                Data layer (db/)                  │
│  SQLite + 9 indexes + CASCADE constraints        │
│  node │ edge │ include_dep │ parse_status        │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│              Parser layer (parser/)              │
│  AST Visitor (libclang) │ CompileDB │ Config     │
│  ChangeDetector │ ImpactAnalyzer                 │
└─────────────────────────────────────────────────┘

Core data model

Nodes (node table): classes, structs, functions — with namespace, file location, signature, etc.

Edges (edge table): relations between nodes; direction convention:

  • inherits: from=subclass → to=base class
  • calls: from=caller → to=callee
  • belongs_to: from=function → to=owning class
  • overrides: from=derived function → to=base function

include_dep table: include dependencies between translation units — the core basis for incremental updates.

---

🧩 Complex scenarios (template/alias/friend)

| Feature | Relation edge | Status | Notes | |---------|---------------|--------|-------| | Type alias using Alias = T | type_alias | ✅ Enabled | Alias node stored + edge links to target; when the target comes from an external library the edge may be dropped as dangling, but the alias node keeps target_type metadata | | using-declaration using B::func | using_decl | ✅ Enabled | Subclass function → base function; this project's source has no ordinary using-declarations, only 1 literal operator not extracted | | Friend friend class F | friend_of | ✅ Enabled | friend → host class; this project's source has no friend declarations, 0 edges as expected | | Template instantiation | instantiates | ⏸️ Not yet enabled | libclang does not produce a standalone CLASS_DECL node for template specializations (the specialization name only appears in the spelling of CONSTRUCTOR/TYPE_REF), so walk_preorder finds no class declaration containing < and extraction yields nothing. The extractor code is retained, to be enabled once we switch to LibTooling or rebuild from TYPE_REF |

This area previously had "extractors written but not wired into the pipeline" (dead code). AliasExtractor/FriendExtractor are now integrated into SemanticExtractor.parse(), and output was verified against clangd.

---

🔄 Incremental-update mechanism

1. ChangeDetector   ──-> detect file changes (git diff / manual)
2. ImpactAnalyzer    ──-> analyze blast radius (.h -> recursive includers)
3. Delete stale data ──-> delete out-edges (not shared nodes) + include_dep + parse_status
4. Re-parse          ──-> SemanticExtractor.parse() × affected TUs
5. Upsert new data   ──-> node update / edge dedup
6. Cleanup remnants  ──-> delete functions/classes no longer in the file
7. Rebuild doc links ──-> content_scan (optional embedding)

Core deletion strategy: only out-edges (edges whose from_id is in that file) are deleted; in-edges are kept; nodes are updated via upsert. This ensures classes/functions shared in headers are never mistakenly deleted.

| Scenario | Affected TUs | Update time | |----------|-------------|-------------| | Single .cpp change | 1 | ~11s | | Single .h change | recursive includers | depends on TU count (7 TUs ~76s) | | Idempotency (second run) | unchanged | edge count stable ✅ |

Note: the git-diff auto-detect mode (--base HEAD~1) may not work under Android repo-tool-managed repos (_ensure_repo_root finds the repo top-level .git rather than the sub-repo's). Using --files to specify changed files manually is more reliable.

---

📚 Doc fusion

Bidirectionally links project docs (Markdown) with code entities, so searching docs auto-locates related code.

This project has doc fusion configured: 58 docs → 546 sections → 1,756 association edges (doc_describes_code + code_refers_to_doc). cpp_search_docs works out of the box.

Configuration

Create config/doc_config.yaml:

doc_dirs:
  - "docs/"

exclude_patterns:
  - "*.html"
  - "*/build/*"

# Auto-tag by directory
tag_rules:
  - path_pattern: "architecture/**"
    tags: ["架构设计"]
  - path_pattern: "api/**"
    tags: ["接口规约"]

section_split:
  min_level: 2          # split on ##
  min_word_count: 20    # sections under 20 words are merged

# Manual precise links (no intrusion into doc text)
manual_links:
  - doc: "architecture/OTA_FLOW.md"
    heading: "升级流程"
    code:
      - "BasePeriUpdate"
      - "SocUpdate"
      - "PerformUpgrade"

Embedding association (optional, experimental)

⚠️ Experimental — not validated. The embedding pipeline has not been tested end-to-end. It is retained from the design phase but excluded from the default association strategy. Results and accuracy are unknown.

After installing sentence-transformers, docs and code can be auto-associated by semantic similarity. This is not enabled by default — pass --rebuild-embeddings to incremental or call ingest_embedding_associations() explicitly:

pip install sentence-transformers

Defaults to all-MiniLM-L6-v2. For Chinese projects, bge-small-zh-v1.5 or multilingual-e5-small is recommended.

---

🧪 Validation

After a full parse, you can run correctness validation (cross-checked against clangd):

python3 -m cpp_semantic_graph full-parse \
  --config cpp_semantic_graph.yaml \
  --validate \
  --baseline validation/clangd_baseline.json

---

📋 Dependencies

Core (required)

| Package | Version | Purpose | |---------|---------|---------| | clang | ≥18 | libclang Python bindings, AST parsing | | PyYAML | ≥6.0 | Config file parsing | | mcp | ≥1.0 | MCP protocol implementation (FastMCP) |

Doc fusion (optional)

| Package | Version | Purpose | |---------|---------|---------| | sentence-transformers | ≥2.0 | doc-code semantic association | | torch | ≥2.0 | sentence-transformers dependency |

Install

# Core
pip install clang PyYAML mcp

# Doc fusion (optional)
pip install sentence-transformers

Or with requirements files:

pip install -r requirements.txt        # core
pip install -r requirements-docs.txt   # doc fusion (optional)

---

🗂️ Project structure

cpp_semantic_graph/
├── __init__.py                  # package declaration
├── __main__.py                  # python -m entry point
├── cli.py                       # CLI tools (search/inheritance/incremental/...)
├── pipeline.py                  # full-parse pipeline
├── incremental_updater.py       # incremental-update orchestrator
│
├── parser/                      # parser layer
│   ├── ast_visitor.py           #   AST extractor (libclang)
│   ├── config.py                #   project config loading
│   ├── compile_db.py            #   compile_commands.json parsing
│   ├── change_detector.py       #   file-change detection (git diff)
│   ├── impact_analyzer.py       #   blast-radius analysis
│   ├── doc_parser.py            #   doc parser
│   ├── doc_association.py       #   doc-code association
│   ├── association_ingester.py  #   association-edge ingestion
│   └── models.py                #   data models
│
├── query/                       # query layer
│   ├── graph_query.py           #   class/function/file-symbol queries
│   ├── call_query.py            #   call-relation queries
│   ├── polymorphism_query.py    #   polymorphism queries
│   ├── traverse.py              #   multi-hop traversal
│   ├── doc_query.py             #   doc-fusion queries
│   ├── include_query.py         #   include-dependency queries
│   ├── architecture_query.py    #   architecture-overview queries
│   └── fusion_query.py          #   fusion queries
│
├── db/                          # data layer
│   ├── graph_db.py              #   SQLite operations
│   ├── importer.py              #   JSON->SQLite import
│   ├── schema.sql               #   schema + indexes
│   └── relation_types.py        #   relation-type enum
│
├── mcp_server/                  # MCP server
│   ├── server.py                #   FastMCP server + 9 tools
│   └── run_server.py            #   launch script
│
├── validation/                  # correctness validation
│   ├── accuracy_validator.py    #   accuracy validator
│   └── clangd_baseline.py       #   clangd baseline
│
├── config/                      # config templates
│   ├── doc_config.yaml          #   doc-config example
│   └── template_whitelist.yaml  #   template whitelist
│
└── cpp_semantic_graph.yaml      # project config (user-authored)

---

❓ FAQ

Q: Which LLVM/libclang version do I need?

One matching the LLVM used to compile your project. How to check:

# clang version
clang --version

# corresponding libclang path
ls /usr/lib/llvm-*/lib/libclang.so*

Specify it via libclang_path in the config.

Q: How do I generate compile_commands.json?

  • CMake project: cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ...
  • Make project: bear -- make
  • Ninja project: bear -- ninja
  • Bazel project: use bazel-compile-commands

Q: Are incremental updates safe?

Yes. The deletion strategy only removes out-edges (edges whose from_id is in that file), not nodes. Classes/functions shared in headers are updated via upsert and never mistakenly deleted. For extreme cases (e.g. renaming a class), a full rebuild is recommended.

Q: Which AI tools are supported?

Any AI tool that supports the MCP protocol: Claude Code, Cursor, Windsurf, Continue, etc. The MCP server uses stdio transport, the most broadly supported transport.

Q: How big is the database?

A typical C++ project (~100 translation units): about 1500 nodes / 5000 edges / 20000 include relations, SQLite file ~5-10 MB. With doc fusion, node and edge counts roughly double, DB ~5-15 MB.

Q: How does this differ from clangd?

| | cpp-semantic-graph | clangd | |---|---|---| | Data storage | offline SQLite graph | real-time AST | | Query scope | cross-file, cross-module | single file + index | | Call chains | full call graph + multi-hop traversal | single-hop references | | Doc association | supported | not supported | | Incremental update | based on include-dependency graph | real-time | | Best for | architecture understanding, blast-radius analysis | real-time editing, signature lookup |

They complement each other: use clangd for day-to-day editing, cpp-semantic-graph for architecture understanding and impact analysis.

Q: Why are some call edges missing? (conditional compilation)

cpp-semantic-graph builds its AST from the preprocessed translation unit of a single compile configuration — the one recorded in compile_commands.json. The C preprocessor drops every #if / #ifdef / #else branch not selected by the current -D flags before libclang sees the code, so any function call inside a non-selected branch is invisible to the graph. get_callers / get_callees return empty for such a call even though it clearly exists in the source.

This is an inherent limitation of the libclang single-config method, not a bug — clangd has the same blind spot, since it also parses one configuration.

Typical shape. A function whose signature sits outside the #if still gets a node, but calls inside the non-selected #else body are missing. The node therefore exists yet appears to have no callers / no callees for those calls:

bool Foo::CompareVersion(const char* path) {   // signature -> node exists
#if SKIP_CHECK              // current config: SKIP_CHECK == 1, this branch kept
  LogWarning("skipped");    // -> call edge extracted (Logger::Warning)
#else                        // dead branch, removed by preprocessor
  ExtractVersion(path);     // -> NO call edge (invisible to libclang)
  CompareVersions(...);     // -> NO call edge
#endif
}

How to tell a blind spot from a real omission. If get_callers is empty for a function you can see called in source, first check whether the call site is inside a #if/#else block, and whether the controlling macro's current value selects that branch. The graph only reflects the configuration that was compiled; it is correct for that configuration.

Mitigation. To analyze another configuration, regenerate compile_commands.json with the corresponding -D flags and rebuild the graph. A single preprocessed AST cannot show all configurations at once; there is no way to union them from one parse.

---

📄 License

MIT License

---

📋 Test reports

| Report | Description | |--------|-------------| | TEST_REPORT.md | Comprehensive test report (functionality/accuracy/efficiency/bug-fix) | | TEST_CASES.md | Test-case table (62 cases, 98.4% pass) | | TEST_THREE_LAYERS.md | Three-layer tests (question→tool call→code verification, 25 real questions) | | TEST_DOC_FUSION.md | Doc-fusion tests (27 cases, 89% pass) | | PROJECT_EVALUATION.md | Overall project evaluation (six-dimension scoring + comparison + conclusion) |

See related servers & alternatives →

Related MCP servers

Browse all →

Related guides

Hand-picked reading to help you choose and use Vector & Memory servers.