How GraphML, Neo4j, and Obsidian Export Formats Handle Metadata in Code Review Graphs

All three export formats—GraphML, Neo4j Cypher, and Obsidian—preserve the same core metadata fields including qualified_name, kind, file_path, language, community_id, and edge relationship types, each encoded according to format-specific conventions.

The code_review_graph package provides multiple export backends for analyzing code relationships as graph structures. Each format transforms the unified payload from export_graph_data(store) differently while maintaining complete semantic fidelity. Understanding these metadata mappings helps you select the right export format for your visualization or database needs.

Core Metadata Fields Exported Across All Formats

Every export format preserves six essential metadata fields that describe nodes and edges in the code review graph.

Metadata Field Description
qualified_name Fully-qualified identifier (e.g., module::Class::method)
kind Entity type (File, Class, Function, Method, etc.)
file_path Absolute or project-relative source file location
language Programming language of the symbol
community_id Optional community clustering identifier
Edge kind Semantic relationship type (CALLS, IMPORTS, INHERITS, etc.)

These fields originate in code_review_graph/visualization.py through the export_graph_data function, which supplies the unified payload consumed by all exporters.

GraphML Export: XML-Based Metadata Encoding

The GraphML export encodes metadata as typed XML data attributes. In code_review_graph/exports.py (lines 72–108), the export_graphml function constructs an XML document with explicit <key> declarations for each metadata attribute.

Node metadata is written as <data> children:

  • kind → <data key="kind">
  • file_path → <data key="file">
  • language → <data key="language">
  • community_id → <data key="community"> (optional)

Edge metadata uses <data key="edge_kind"> to preserve relationship types. Values are escaped with html.escape() to ensure XML safety.


# GraphML Lines 72-73: Writing node kind

xml_data += f'    <data key="kind">{html.escape(str(data.get("kind", "Unknown")))}</data>\n'
xml_data += f'    <data key="file">{html.escape(str(data.get("file_path", "")))}</data>\n'

The resulting .graphml file imports directly into Gephi, yEd, Cytoscape, and other graph visualization tools with full metadata preservation.

Neo4j Cypher Export: Property Maps and Relationship Labels

The Neo4j Cypher export translates metadata into Cypher-native structures. In code_review_graph/exports.py (lines 41–65), export_neo4j_cypher generates CREATE statements:

  • qualified_name becomes a node property and matching key for edge creation
  • kind becomes a node label (:Class, :Function) and relationship type
  • file_path, language, and community_id populate the property map

# Neo4j Cypher Lines 41-42, 58-60: Label and relationship creation

node_lines.append(f'CREATE (n:{kind} {{qualified_name: "{qualified_name}", ...}})')
rel_lines.append(f'MATCH (a), (b) WHERE a.qualified_name = "{source}" AND b.qualified_name = "{target}" CREATE (a)-[:{edge_kind}]->(b)')

Empty language values default to empty strings. Edge kinds default to RELATES_TO when unspecified. The output is a plain-text .cypher script ready for cypher-shell or Neo4j Browser execution.

The Obsidian export creates a navigable knowledge base with metadata embedded in markdown front-matter. In code_review_graph/exports.py (lines 39–82), export_obsidian_vault generates one file per node:

---
kind: Function
file: src/core/parser.py
language: python
community: 3
---

The qualified_name drives Wikilink resolution—[[module::Class::method|Display Name]]—enabling bidirectional navigation. The "Connections" section lists adjacent nodes with their relationship types:


## Connections

- **CALLS**: [[target_module::TargetFunc]]
- **IMPORTS**: [[some_module::SomeClass]]

Community overview pages (_COMMUNITY_<id>.md) aggregate members, and _INDEX.md provides a complete directory. This format preserves all metadata while creating a human-readable, traversable documentation system.

Quick Export Examples

from code_review_graph.exports import export_graphml, export_neo4j_cypher, export_obsidian_vault
from pathlib import Path

# GraphML for network visualization tools

export_graphml(store, Path("output/graph.graphml"))

# Cypher script for Neo4j graph database

export_neo4j_cypher(store, Path("output/graph.cypher"))

# Complete Obsidian vault with metadata-rich markdown

export_obsidian_vault(store, Path("output/obsidian_vault"))

Metadata Handling Comparison

Aspect GraphML Neo4j Cypher Obsidian
Storage model XML attributes Property maps + labels YAML front-matter
Qualified name usage Node/edge ID Matching key + property Filename + Wikilinks
Kind representation <data key="kind"> Node/relationship labels kind: front-matter field
Edge semantics edge_kind data attribute Relationship type label Markdown list with bold kind
Community support Optional <data> element Optional property Front-matter + overview pages
Best for Visualization tools (Gephi, yEd) Graph databases, complex queries Documentation, manual exploration

Key Source Files

Summary

  • GraphML uses XML <data> elements with typed keys, suitable for interchange with visualization platforms
  • Neo4j Cypher maps metadata to property maps and labels, optimized for graph database queries
  • Obsidian embeds metadata in YAML front-matter with Wikilink navigation, designed for human-readable knowledge bases
  • All three formats originate from the same export_graph_data payload, ensuring consistent metadata availability regardless of output format

Frequently Asked Questions

Does GraphML preserve community clustering information?

Yes. When community_id exists, GraphML emits <data key="community"> on nodes (lines 78–99 in exports.py). The key is optional and only included when the community assignment exists in the source graph.

How does Neo4j handle missing relationship types?

The Neo4j exporter defaults edge labels to RELATES_TO when the kind field is empty or unspecified (lines 58–60). Explicit kinds like CALLS or IMPORTS become the actual relationship type in the generated Cypher.

Can Obsidian vaults be regenerated incrementally?

The export_obsidian_vault function creates a complete vault from scratch each run. Existing files are overwritten. For incremental updates, you would need to implement custom merge logic outside the core exporter.

Which format best preserves metadata for programmatic analysis?

Neo4j Cypher offers the strongest programmatic access—property maps support indexed lookups, and Cypher's query language enables complex traversals. GraphML works well for tool interoperability, while Obsidian prioritizes human readability over programmatic access.

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 →