Skip to main content
Glama
FS-Yuao

cpp-semantic-graph

by FS-Yuao

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 PerformUpdate() = 0; };
template<typename T> struct Middle : virtual Base {     // virtual inheritance + template
    void PerformUpdate() override {}
};
struct ChipUpdate : Middle<SoC>, NonCopyable, public Logger {};   // multiple inheritance
}

A syntax parser (tree-sitter) can see that ChipUpdate 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 PerformUpdate() 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.

Related MCP server: codegraph

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("FirmwareUpdate") → namespace + file location

"Who calls this function?"

cpp_get_callers("QueryBootChain") → 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("PerformUpdate", "DeviceAdapter") → all implementations

"What does this module's architecture look like?"

cpp_traverse_graph("FirmwareUpdate") → 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 FirmwareUpdate defined?"

2

cpp_search_function

Search function definitions by name

"What's the signature of PerformUpdate?"

3

cpp_get_inheritance

Query a class's inheritance

"Which classes derive from DeviceAdapter?"

4

cpp_get_callers

Who calls a given function

"Who calls QueryBootChain?"

5

cpp_get_callees

What a given function calls

"What does PerformUpdate call internally?"

6

cpp_get_overrides

All overrides of a virtual function

"Which overrides exist for PerformUpdate?"

7

cpp_get_file_symbols

All symbols in a file

"What's in chip_update.cpp?"

8

cpp_traverse_graph

Multi-hop graph traversal

"What does changing FirmwareUpdate 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 PerformUpdate — which files must I review?"

11

cpp_get_code_docs

Reverse: code symbol -> docs describing it

"What design docs describe PerformUpdate?"

Tool details

cpp_search_class(name, exact=False)

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

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

cpp_search_function(name, class_name="")

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

cpp_search_function("PerformUpdate", class_name="FirmwareUpdate")
-> ## Search results: function "PerformUpdate" (1)
  ### FirmwareUpdate::PerformUpdate [virtual, override]
  - Signature: void PerformUpdate() override
  - File: chip_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("DeviceAdapter", direction="down", depth=-1)
-> ## Subclasses of DeviceAdapter (4)
  - update::FirmwareUpdate --public--> DeviceAdapter
  - update::McuUpdate --public--> DeviceAdapter
  - ...

cpp_get_callers(function_name, class_name="")

Who calls a given function (impact analysis).

cpp_get_callers("QueryBootChain")
-> ## Code that calls "QueryBootChain" (3)
  ### UpdateManager::CheckBootChain
  - File: update_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("PerformUpdate", class_name="FirmwareUpdate")
-> ## Code called by "PerformUpdate" (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("PerformUpdate", class_name="DeviceAdapter")
-> ## Overrides of "PerformUpdate" (4)
  ### update::FirmwareUpdate::PerformUpdate
  - Signature: void PerformUpdate() override
  - File: chip_update.cpp:45
  - Overrides base: DeviceAdapter

cpp_get_file_symbols(file_path)

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

cpp_get_file_symbols("chip_update.h")
-> ## File symbols: chip_update.h (12 total)
  ### Classes/structs (2)
    1. ### [class] update::FirmwareUpdate
  ### 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("FirmwareUpdate", depth=2, max_results=30)
-> ## Traversal results: from "FirmwareUpdate" (18 nodes)
  Depth: 2, edges traversed: 22
  ### Related nodes
    - [class] update::FirmwareUpdate (chip_update.h)
    - [class] update::DeviceAdapter (base_device_update.h)
    - [function] update::FirmwareUpdate::PerformUpdate (chip_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] DeviceAdapter confidence=0.92
    - [class] FirmwareUpdate 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("PerformUpdate")
-> ## Docs describing "PerformUpdate" (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 "FirmwareUpdate"

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

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

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

# Include dependencies
python3 -m cpp_semantic_graph include "base_device_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 chip_update.cpp,base_device_update.h

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

# Skip doc-association rebuild (faster)
python3 -m cpp_semantic_graph incremental --files chip_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/UPDATE_FLOW.md"
    heading: "Update flow"
    code:
      - "DeviceAdapter"
      - "FirmwareUpdate"
      - "PerformUpdate"

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.


🧠 Memory layer (findings) — stop re-deriving the same conclusions

The doc_graph/ sub-project extends doc fusion into a team memory hub: durable conclusions from AI sessions (constraints, lessons, decisions, facts, risks) are stored as first-class records, anchored to code symbols, and automatically surfaced whenever those symbols are queried.

Session derives a conclusion
  → record_finding_tool (idempotent, symbol-anchored)
  → next session queries the symbol → finding surfaces automatically
  → code evolves → check_finding_freshness marks it stale

What it adds on top of doc fusion:

Capability

How

Finding store

Independent SQLite tables (finding / finding_symbol / finding_fts), survives full graph rebuilds

Symbol anchoring

Findings link to code symbols; any doc-graph query touching that symbol brings them back

3-way RRF retrieval

FTS5 + LIKE + symbol backref run in parallel, fused with Reciprocal Rank Fusion

CJK & camelCase tokenization

QueryBootChain is found by "boot chain"; Chinese matched per-character

Synonym expansion

Built-in zh↔en tech table (崩溃↔crash↔挂↔abort…) — colloquial queries hit formal conclusions

Vector rerank (optional)

Local bge-small via fastembed, CPU-only, reranks lexical hits only

Staleness detection

Anchored symbols checked against the code graph; vanished symbols → stale

Honest engineering notes (all measured, see doc_graph/ docs):

  • Bi-encoders (bge-small 33M, e5-large 560M) cannot separate relevant from irrelevant pairs for "short zh query vs short zh conclusion" — everything sits in a dense 0.8+ similarity band. Vectors are therefore used only for reranking lexical hits, never as an independent recall source.

  • A cross-encoder (bge-reranker-base) gives reliable strong negatives but unreliable weak positives — kept as an optional gate, not shipped as default.

  • The winning combo is boring: caller-side rewriting (the MCP caller is an LLM)

    • a deterministic synonym table. Zero API, zero GPU, fully offline.

Runs as a streamable-HTTP singleton (systemd), so all MCP clients share one process — see doc_graph/README.md.


🧪 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)

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides code intelligence for AI coding agents by indexing repositories into a hybrid knowledge graph, enabling agents to query dependencies, impact, and context through 28 MCP tools.
    3
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides AI agents with a function-level dependency graph of the codebase through 30 MCP tools, enabling structural queries about code dependencies, callers, and impact analysis.
    1,605 npm
    94
    Apache 2.0
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI coding assistants to understand codebase architecture in real time by parsing source code into a relationship graph and exposing call chains, dependencies, class hierarchies, and conventions via MCP tools.
    14
    4
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides structural code intelligence via 26 MCP tools, enabling AI assistants to query code symbols, dependencies, and call graphs accurately without file-pasting.
    -