ADR-0002: NetworkX Multi-Layer Code Graph¶
Date: 2025-02-01
Status: accepted
Context¶
The agent needs to represent code relationships across multiple signal types to perform structural analysis. Required relationship types include:
CALLS(function/method invocations)IMPORTS(module imports)REFERENCES(symbol references from LSP)CONTAINS(file-to-symbol containment)INHERITS/IMPLEMENTS(type hierarchy)TESTS(test-to-production coverage)COCHANGES(files that frequently change together from git history)SIMILAR_TO(cloned/duplicated code blocks)
The graph must support multiple edges between the same node pair (a function can both call and reference another), typed filtering for algorithm-specific views, and standard graph algorithms (PageRank, betweenness centrality, community detection).
Alternatives considered:
- Neo4j / purpose-built graph DB: Richer query language (Cypher) but requires external service, complicates deployment for a CLI tool
- igraph: Faster for large graphs but less Pythonic API and weaker multi-edge support
- Custom adjacency lists: Lightweight but no algorithm library
Decision¶
Use nx.MultiDiGraph from NetworkX, wrapped in a CodeGraph class at src/code_context_agent/tools/graph/model.py.
Key design elements:
- Typed nodes (
CodeNodewithNodeTypeenum: FILE, CLASS, FUNCTION, METHOD, VARIABLE, MODULE, PATTERN_MATCH) stored as PydanticFrozenModelinstances - Typed edges (
CodeEdgewithEdgeTypeenum) using the edge type as the MultiDiGraph key, allowing multiple relationship types per node pair - Filtered views via
CodeGraph.get_view(edge_types)that produce a simplenx.DiGraphsuitable for algorithms that do not support multigraphs - Analysis algorithms in
CodeAnalyzer(src/code_context_agent/tools/graph/analysis.py): PageRank, betweenness centrality, TrustRank (personalized PageRank from entry points), Leiden/Louvain community detection, triangle detection, coupling measurement, dependency chain traversal - Progressive exploration via
ProgressiveExplorer(src/code_context_agent/tools/graph/disclosure.py) for interactive drill-down - Serialization via
nx.node_link_data()/nx.node_link_graph()to JSON, persisted as.code-context/code_graph.json
Graph ingestion happens through 7 dedicated ingest tools (code_graph_ingest_lsp, _astgrep, _rg, _inheritance, _tests, _git, _clones) that each add typed edges from their respective signal sources.
Consequences¶
Positive:
- Rich algorithm library out of the box: PageRank, betweenness, Louvain/Leiden, shortest paths, clique detection all work directly on the graph
MultiDiGraphnaturally models the multi-layer nature of code relationships (same node pair can haveCALLS,REFERENCES, andCOCHANGESedges simultaneously)- JSON serialization enables persistence and MCP server consumption without graph DB infrastructure
networkx[extra]dependency bringsscipyand community detection backends
Negative:
- In-memory only; large monorepos (100K+ nodes) may hit memory limits
get_view()creates a copy of the graph for each algorithm call (not a zero-copy view)- JSON serialization format changed between NetworkX 3.5 (
linkskey) and 3.6+ (edgeskey), requiring compatibility handling infrom_node_link_data()
Neutral:
- Graph state is module-level (
_graphs: dict[str, CodeGraph]intools/graph/tools.py), not per-agent; this is acceptable for single-agent CLI usage - The
CodeGraphwrapper deliberately does not expose the full NetworkX API, providing a controlled surface area