Skip to content

zhijiewong/livegraph

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

175 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

livegraph

A runtime-augmented code knowledge graph for Python codebases.

livegraph builds a graph of a Python project in Neo4j, fusing static tree-sitter analysis with runtime observation of the project's pytest suite. Every CALLS edge is tagged with provenance (static / runtime) so the graph reflects what the code does, not just what it looks like.

Quick start

docker compose up -d
cp .env.example .env
pip install -e ".[dev]"
livegraph build /path/to/python/project

See docs/superpowers/specs/ for the design.

Using livegraph from a coding agent (MCP)

After livegraph build /path/to/project, expose the graph to a coding agent over MCP:

LIVEGRAPH_PROJECT=myproject livegraph mcp

The server runs over stdio. Configure your MCP host (Claude Code, Cursor) to launch it. Example .mcp.json for Claude Code:

{
  "mcpServers": {
    "livegraph-myproject": {
      "command": "livegraph",
      "args": ["mcp", "--project", "myproject"],
      "env": {
        "NEO4J_URI": "bolt://localhost:7687",
        "NEO4J_USER": "neo4j",
        "NEO4J_PASSWORD": "livegraph-local"
      }
    }
  }
}

The server exposes 10 read-only tools, including the two that no purely static code-graph tool can run:

Tool What it answers
find_symbol(query) Symbols matching a name
get_source(qualified_name) Source + coverage for a symbol
find_callers(qualified_name, provenance) Who calls this — static/runtime/any
find_callees(qualified_name, provenance) What this calls — same filter
runtime_only_calls(file?) Calls runtime caught that static missed
dead_static_calls(file?) Predicted calls that never executed
tests_for(qualified_name) Tests that cover a symbol
untested_symbols(file?, kind?) Functions/methods no test exercised
imports(file, direction) File-level import edges
graph_status() Aggregate counts; call this first
change_impact(diff, max_depth, provenance, limit) Given a git diff: changed symbols, transitive callers with per-edge provenance, and the tests to run

Acceptance test: with the server registered, ask your agent "show me the dynamic-dispatch calls in this project". A working integration finds and calls runtime_only_calls.

Keeping the graph in sync (livegraph update)

After the first livegraph build, subsequent edits don't require a full rebuild. Run:

LIVEGRAPH_PROJECT=myproject livegraph update

The command walks the project, computes SHA-256 hashes of every .py file, compares against the hashes stored on File nodes, and re-ingests only the files whose content actually changed. Deletions are removed from the graph; new files are added; unchanged files are skipped.

Runtime data (from livegraph trace) is preserved on changed-file symbols but flagged runtime_stale=true. CALLS edges that were already verified at runtime survive incremental re-ingest. A subsequent livegraph trace clears the stale flag on every symbol that appears in the new observations.

Use --dry-run to preview the classification without writing to the graph:

livegraph update --dry-run

Known limitation: a function renamed in file A while file B still calls it by the old name leaves an orphaned CALLS edge until file B is also touched. Run a full livegraph build to fully recover.

Free-form queries (describe_schema + run_cypher)

For questions the 11 structured tools don't cover, livegraph exposes two "open" MCP tools so the agent can compose Cypher itself:

  1. describe_schema() — returns the graph's labels, edges, properties, safety rules, the configured project name, and six example queries. Call it once per session.

  2. run_cypher(query, params?, row_limit?, timeout_seconds?) — runs a read-only Cypher query. Belt-and-suspenders safety:

    • Lexical pre-scan rejects writes (CREATE, MERGE, DELETE, SET, REMOVE, DROP, LOAD CSV, USING PERIODIC COMMIT, CALL) with a friendly error.
    • Neo4j READ transaction enforces read mode at the engine.
    • Per-transaction timeout (default 30s).
    • Server-side row truncation (default 1000 rows) with a truncated flag on the response.
    • $project parameter is auto-injected so queries can use the configured project name symbolically.

Example agent prompt: "Show me functions in this project that are called by something runtime-only and have no tests." The agent reads describe_schema, composes the Cypher, calls run_cypher, and returns the answer — all without livegraph needing an LLM dependency of its own.

The agent's LLM (Claude Sonnet, Opus, GPT-5, whichever) writes the Cypher. livegraph just provides the safe execution endpoint.

Semantic search (livegraph embed + semantic_search)

For questions where the concept is clear but the right names aren't, livegraph can compute vector embeddings of every Function and Method and serve them via a 14th MCP tool.

This stack is opt-in:

pip install 'livegraph[semantic]'    # adds sentence-transformers + torch (~2 GB)

After ingesting a project, compute its embeddings:

LIVEGRAPH_PROJECT=myproject livegraph embed

The default model is all-MiniLM-L6-v2 (384 dimensions, ~80 MB). Override via LIVEGRAPH_EMBED_MODEL (any HuggingFace sentence-transformers model id).

Re-running livegraph embed is idempotent — only symbols whose source has changed since the last run get re-embedded (tracked via embedding_source_hash, mirroring Phase 5's content-hash pattern). To swap to a model with different dimensions, pass --rebuild.

The MCP server exposes semantic_search:

semantic_search(query: str, limit: int = 10, kind: str = "any")

Example agent prompt: "Where do we handle JWT verification?". The agent calls semantic_search("JWT verification token validation") and the top results are ranked by cosine similarity to the query — even if no symbol in the project literally contains those words.

If the [semantic] extra is not installed, the tool returns an empty result list with a warning containing the install hint, and the rest of livegraph keeps working.

Watch mode (livegraph watch)

Mirror source-file edits into the graph live — useful while an MCP client is connected and you're editing code.

livegraph watch --project myproj /path/to/repo

Flags:

  • --embed — after each update, re-run embedding for symbols whose source changed (requires pip install 'livegraph[semantic]').
  • --debounce-ms 300 — coalesce file events within this window (default 300; tunable via LIVEGRAPH_WATCH_DEBOUNCE_MS).
  • --ignore PATTERN — glob patterns to ignore (repeatable). Layered on top of .gitignore and builtin ignores (.git/, __pycache__/, .venv/, venv/, node_modules/).

The loop logs each update; Ctrl-C stops it cleanly. Backend errors trigger exponential backoff (1s → 30s cap) so the watcher stays alive if Neo4j blips. Runtime CALLS edges from prior pytest runs are preserved on unchanged files.

Semantic neighborhood (semantic_neighborhood)

For when an agent wants more than "what matches my query" — it wants "where do I look and what do I run." The 15th MCP tool:

semantic_neighborhood(
    query: str,
    limit: int = 10,             # number of semantic seeds
    per_seed_limit: int = 10,    # cap per expansion list
    kind: str = "any",           # "any" | "function" | "method"
    include: list[str] | None = None,  # subset of {"callers","callees","tests"}
    min_score: float = 0.0,
)

For each top-K semantic match, returns the direct callers (with static/runtime/both provenance), callees (same), and tests that cover the symbol (Phase 2 coverage edges). One vector query plus up to three batched expansion queries — same latency budget as semantic_search plus 3 Cypher round-trips.

Example agent prompt: "Where does this codebase do JWT verification, and what tests cover it?" The agent calls semantic_neighborhood once and gets matching functions, their call sites, and the tests it should run to validate a change.

Git history (livegraph ingest-history)

Phase 10 attaches a time/authorship axis to the graph. Walk the project's git history into (:Commit) / (:Author) nodes with CHANGED_IN edges (file-level always; symbol-level when commit hunks overlap the symbol's current source lines).

livegraph ingest-history --project myproj /path/to/repo
livegraph ingest-history --project myproj --since-last   # incremental

Three new MCP tools (bringing the count to 18):

Tool What it answers
symbol_history(qualified_name, limit) Recent commits + authors that touched a symbol.
recent_changes(since, limit, kind) Symbols changed in commits since a timestamp.
top_churn(window_days, limit, kind) Hotspot symbols ranked by commit count.

Caveats:

  • Attribution uses the current parse's line ranges, so symbols whose lines moved a lot through history may be over- or under-credited for old commits. Recent commits are accurate.
  • Author identity keys on email; respects .mailmap if present.
  • livegraph update and livegraph watch leave history edges alone. Run ingest-history --since-last after a long editing session to catch up.

Architecture analysis

Three read-only MCP tools (bringing the count to 21) for "is our architecture healthy?" questions, all over edges that already exist in the graph:

Tool What it answers
find_cycles(scope, provenance, min_size, limit) Strongly-connected components in the call graph (scope="call", filterable by static/runtime/any) or import graph (scope="module").
layering_violations(layers, edge_kind, limit) Edges that go "up" the supplied layering. layers is an ordered list of {name, patterns} — top layers may depend on lower layers; the reverse is a violation. Files matching no pattern are unlayered and silently skipped; files matching multiple take the first.
hubs(kind, min_fanin, limit) Symbols with high inbound CALLS — the "everything depends on this helper" detector.

Example agent prompt: "Are any of our domain modules importing infrastructure code by mistake?" The agent calls layering_violations with the project's layering and gets back the specific edges to fix.

CI mode (livegraph check)

Phase 12 turns the existing analysis surface into something CI can enforce. Drop a .livegraph.toml at the project root:

[project]
name = "myproj"

[checks.cycles]
enabled = true
scope = "module"
max_cycles = 0

[checks.layering]
enabled = true
max_violations = 0
layers = [
    { name = "web",    patterns = ["web/**", "api/**"] },
    { name = "domain", patterns = ["domain/**"] },
    { name = "infra",  patterns = ["infra/**", "db/**"] },
]

[checks.churn]
enabled = true
window_days = 30
hot_files_threshold = 10
ignore = ["tests/**"]

[checks.hubs]
enabled = false
min_fanin = 25
max_hubs = 0

Then in CI:

livegraph update          # refresh the graph
livegraph check           # run all enabled checks

Exit codes: 0 = pass, 1 = at least one check failed, 2 = config or environment error (or, with --strict, the graph is stale vs disk). Output is human-readable by default; pass --format json for a machine-readable payload, --fail-fast to stop at the first failing check.

Each check wraps an existing MCP tool — find_cycles, layering_violations, top_churn, hubs — so the agent's "show me" question and CI's "fail the build" question read the same graph data.

TypeScript / JavaScript support

Phase 13 makes livegraph multi-language. livegraph build /path/to/repo auto-detects what's there:

  • *.py → Python pipeline (Phases 1-2).
  • *.ts, *.tsx, *.js, *.jsx, *.mjs, *.cjs → TypeScript pipeline.

Both populate the same Neo4j schema, so an agent can ask find_symbol, find_callers, find_cycles, semantic_search over a mixed Python + TS monorepo and get unified results.

What gets captured for TS:

  • Definitions: function, const x = () => {}, class C { method() {} }, export function, export default function (recorded as default).
  • Imports: ES modules — relative paths (with extension inference and index.{ts,tsx,…} resolution), tsconfig paths aliases (read from tsconfig.json if present), bare specifiers (recorded as thirdparty).
  • Calls: direct calls (foo()), method calls (obj.m()), new C(). Name-matched against project-defined symbols, per-language.

Qualified names follow the existing <file_path>::<dotted> scheme, e.g. src/calc.ts::Calculator.add.

Out of scope for v1: CommonJS require(), dynamic import(), JSX components as call edges, decorators, type-aware method resolution, Node runtime tracing (TS CALLS edges are always static=true, runtime=false).

Override auto-detection with --lang python or --lang typescript.

About

A runtime-augmented code knowledge graph for Python codebases (Tree-sitter + sys.monitoring + Neo4j).

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors