How to Use C++ Semantic Mode for Complex Parsing in Code‑Graph‑RAG

Enable C++ semantic mode by installing the [cpp] extra, setting CG_RAG_CPP_FRONTEND=libclang, and running the indexer—this activates LibClang‑based symbol resolution for fully‑qualified names and template‑aware parsing.

The code‑graph‑rag open‑source project provides two C++ parsing strategies. For complex codebases—those with heavy template use, nested namespaces, or cross‑module dependencies—the semantic mode delivers accurate qualified names and type resolution that Tree‑Sitter alone cannot provide. This guide shows how to configure and verify C++ semantic parsing.


Understanding the Two C++ Parsing Modes

Mode Backend Capabilities Best For
Tree‑Sitter Syntactic only Fast concrete syntax tree, no type resolution Quick indexing of simple codebases
Semantic (LibClang/Hybrid) LibClang + Clang AST Fully‑qualified names, template instantiation, overload resolution, namespace tracking Complex C++ with templates, cross‑language edges, precise call graphs

The Hybrid frontend (default) attempts Tree‑Sitter first and falls back to LibClang when available. For guaranteed semantic parsing, force LibClang explicitly.


Installing the C++ Semantic Frontend

The semantic parser requires the optional cpp extra, which pulls in libclang bindings.

pip install "code-graph-rag[cpp]"

On Linux systems, you may additionally need the development headers:

sudo apt-get install libclang-dev

Verify the installation by checking that clang.cindex imports successfully:

python -c "import clang.cindex; print(clang.cindex.Config().lib)"

Selecting the C++ Frontend via Environment Variable

The CppFrontend enum in codebase_rag/constants/languages.py defines three valid values1:

  • treesitter — Pure syntactic parsing
  • libclang — Full semantic resolution
  • hybrid — Tree‑Sitter with LibClang fallback (default)

Set the environment variable before any indexing operation:

export CG_RAG_CPP_FRONTEND=libclang

Or within Python:

import os
os.environ["CG_RAG_CPP_FRONTEND"] = "libclang"

The parser_loader.py module reads this variable and instantiates CppHandler with the corresponding backend2.


Configuring and Running the Semantic Parser

Step‑by‑Step Indexing Workflow

1. Import and instantiate the handler directly (programmatic use):

from codebase_rag.parsers.handlers.cpp import CppHandler

handler = CppHandler()  # Respects CG_RAG_CPP_FRONTEND automatically

2. Run the main indexer:

python -m codebase_rag.main /path/to/cpp/repo --language cpp

The indexer emits a log message confirming the active frontend (see logs.py for exact formatting). Expect output like:


Using LibClang C++ frontend

3. Verify semantic resolution in output:

After indexing, load the generated graph and inspect qualified_name attributes:

from codebase_rag.graph_loader import load_graph
from pathlib import Path

graph = load_graph(Path("/tmp/cgr_graph"))

# Semantic mode produces fully-qualified names

func = graph.get_node_by_qn("engine::render::Pipeline::bind")
print(func["qualified_name"])  # → engine::render::Pipeline::bind

Without semantic mode, the same function would appear as merely bind.


How Semantic Resolution Constructs Qualified Names

The semantic resolution pipeline in codebase_rag/parsers/handlers/cpp.py operates as follows3:

  1. AST node identification — CppHandler.extract_function_name attempts basic extraction via cpp_utils helpers. For lambdas, it synthesizes names like lambda_42_15.

  2. Qualified‑name construction — CppHandler.build_function_qualified_name delegates to resolve_fqn_from_ast when a semantic frontend is active. This utility traverses the Clang AST to locate enclosing namespaces, classes, and template parameters.

  3. Name concatenation — The resolver concatenates scope components: namespace::Class<T>::function.

  4. Export detection — is_function_exported checks extern "C" and visibility attributes using cpp_utils.is_exported, respecting semantic parser information.

  5. Base‑class handling — extract_base_class_name processes both simple identifiers and TS_TEMPLATE_TYPE nodes for complex inheritance chains.

The core resolution logic resides in codebase_rag/utils/fqn_resolver.py, which queries LibClang's cursor.semantic_parent chain to build complete identifiers.


Complete Example: End‑to‑End Semantic Indexing

import os
from pathlib import Path
from codebase_rag.main import run_indexer
from codebase_rag.graph_loader import load_graph

# Force semantic mode

os.environ["CG_RAG_CPP_FRONTEND"] = "libclang"

# Run indexing

repo = Path("/home/dev/graphics-engine")
run_indexer([str(repo)], languages=["cpp"])

# Inspect results

graph = load_graph(Path("/tmp/cgr_graph"))

# Find all methods in a specific namespace

methods = [
    n for n in graph.nodes()
    if n.get("qualified_name", "").startswith("gfx::vulkan::")
]

print(f"Found {len(methods)} Vulkan-gfx methods with qualified names")
for m in methods[:5]:
    print(f"  - {m['qualified_name']}")

Using Semantic Search with C++ Results

The semantic search tool leverages embeddings generated by the semantic frontend. Once indexed, query the codebase:

from codebase_rag.tools.semantic_search import semantic_code_search

results = semantic_code_search(
    ingestor=your_ingestor_instance,
    query="memory pool allocator initialization",
    top_k=10,
    project="graphics-engine"
)

for r in results:
    print(f"{r['function_qn']}: score {r['score']:.3f}")

Accurate function_qn values from semantic parsing ensure search results map to precise code locations.


Summary

  • Install: pip install "code-graph-rag[cpp]" to obtain LibClang bindings
  • Configure: Set CG_RAG_CPP_FRONTEND=libclang for guaranteed semantic parsing (or use hybrid/treesitter alternatives)
  • Verify: Check qualified_name attributes contain full namespace and class scoping
  • Key files: languages.py (enum definition), cpp.py (handler), fqn_resolver.py (resolution logic)

Frequently Asked Questions

What is the difference between Hybrid and LibClang modes?

Hybrid mode attempts Tree‑Sitter parsing first and falls back to LibClang only when the syntactic parser fails or when semantic information is explicitly required. LibClang mode forces the semantic frontend for all C++ files, ensuring consistent fully‑qualified naming at the cost of slightly slower indexing. Use LibClang when you need guaranteed accuracy for template‑heavy code.

Why does my qualified name still lack namespace information?

This occurs when: (1) the cpp extra is not installed, (2) CG_RAG_CPP_FRONTEND is unset or set to treesitter, or (3) LibClang cannot locate system headers. Verify with python -c "import clang.cindex" and check that libclang-dev is installed on Linux systems.

Can I mix semantic and non‑semantic parsing in the same project?

No— the frontend selection applies globally per indexing run. However, you can run separate indexing passes with different frontends and merge graphs if needed. The CppHandler initializes its strategy once at import time based on the environment variable.

Does semantic mode support C++20 modules?

Yes. The LibClang frontend recognizes export declarations and module boundaries. The extract_function_name and is_function_exported methods in cpp.py handle visibility attributes and module linkage as reported by the Clang AST, though module‑specific naming conventions may require additional verification in your codebase.

Footnotes

  1. CppFrontend enum definition — codebase_rag/constants/languages.py lines 101–106 ↩

  2. Frontend loading logic — codebase_rag/parsers/parser_loader.py ↩

  3. Qualified name construction — codebase_rag/parsers/handlers/cpp.py lines 41–49 ↩

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →