Skip to main content

Fast dependency graph analysis and visualization for Python, JavaScript/TypeScript, Rust, and Terraform projects

Project description

Serpentine

Serpentine

See every reference between every named entity in your codebase — functions, classes, modules, variables — resolved down to the definition.

Serpentine UI

Serpentine builds a code reference graph for Python, JavaScript/TypeScript, Rust, and Terraform projects. A code reference is when one named entity mentions another by name — a function call, a type annotation, an assignment, an inheritance declaration. Serpentine resolves each reference through the language's scoping rules down to the exact definition it points to, then visualizes the result as an interactive graph that updates in real time as you edit.


Features

  • Multi-language: Python, JavaScript, TypeScript, Rust, and Terraform
  • Full reference resolution: Traces calls, type annotations, assignments, and inheritance — not just import edges
  • Interactive graph: Expandable nodes, search, pan/zoom, collapsible modules
  • Real-time updates: File watcher pushes changes via WebSocket
  • VCS integration: Compare any two git refs and see added, modified, and deleted nodes highlighted on the graph
  • Agent-ready CLI: Structured JSON output with selectors for use in AI agent workflows

What counts as a code reference?

Serpentine tracks every place one named entity statically mentions another by name, resolved through the language's scoping rules to the specific definition it refers to.

Reference type Example Edge
Function call result = parse(data) result --calls--> parse
Constructor loader = CSVLoader(path) loader --has-a--> CSVLoader
Name reference default = MISSING default --references--> MISSING
Inheritance class Stats(Base) Stats --is-a--> Base
Expression total = a + b total --references--> a, total --references--> b

All four edge types (calls, has-a, references, is-a) resolve through imports — so a reference to an imported name traces all the way back to its definition in the source module.


Installation

PyPI (recommended)

pip install serpentine-parser

Or with uv:

uv tool install serpentine-parser

From source

Prerequisites: Python 3.12+, Rust toolchain, Node.js 18+

git clone https://github.com/serpentine-parser/serpentine.git
cd serpentine

# 1. Build the frontend
cd frontend && npm install && npm run build && cd ..

# 2. Build the Rust extension and install the CLI
make install

This installs the serpentine CLI globally via uv tool, which you will need to install separately.


Quick Start

Navigate to any Python, JavaScript/TypeScript, Rust, or Terraform project and run:

serpentine serve

Serpentine will analyze the project, start a local server at http://127.0.0.1:8765, and open the visualization in your browser. File changes are detected automatically and the graph updates in real time.


CLI Reference

serpentine serve

Start the visualization server with live file watching.

serpentine serve [PATH] [OPTIONS]

Arguments:
  PATH    Directory to analyze (default: current directory)

Options:
  -p, --port INT        Port to run the server on (default: 8765)
  -h, --host TEXT       Host to bind to (default: 127.0.0.1)
  --no-browser          Don't open browser automatically
  --no-watch            Disable file watching (static analysis only)

serpentine analyze

Analyze a project and output the code reference graph. Default format is text (one edge per line as caller --type--> callee); use --format json for structured output.

serpentine analyze [PATH] [OPTIONS]

Options:
  -o, --output PATH           Write output to file instead of stdout
  --format TEXT               Output format: text (default) or json
  --pretty                    Pretty-print JSON output
  --select TEXT               Selector expression to filter nodes (see below)
  --exclude TEXT              Exclusion pattern (same syntax as --select)
  --no-cfg                    Strip control-flow graph data from nodes
  --edges-only                Output only the edges array (compact, json only)
  --source                    Include source code blocks in text output
  --include-assignments       Include variable/assignment nodes in text output
  --include-standard          Include stdlib nodes (default: off)
  --include-third-party       Include third-party nodes (default: off)
  --state TEXT                Filter by change state: modified,added,deleted
  --edge-type TEXT            Comma-separated edge types to include: calls,is-a,has-a,references,imports

serpentine catalog

Flat list of all nodes. Default format is text (file-grouped hierarchy); use --format json to see node IDs for selector expressions.

serpentine catalog [PATH] [OPTIONS]

Options:
  --filter TEXT               Glob pattern matched against node id and name
                              (repeatable; multiple patterns = union)
  --format TEXT               Output format: text (default) or json
  --include-assignments       Include variable/assignment nodes (default: excluded)
  -o, --output PATH           Write output to file instead of stdout
  --pretty                    Pretty-print JSON output
  --include-standard          Include stdlib nodes
  --include-third-party       Include third-party nodes

serpentine stats

Quick summary of project scale: node/edge counts by type and origin.

serpentine stats [PATH] [OPTIONS]

Options:
  --pretty                    Pretty-print JSON
  --include-standard          Include stdlib nodes
  --include-third-party       Include third-party nodes

serpentine init

Set up Serpentine in an existing project.

serpentine init [PATH] [OPTIONS]

Arguments:
  PATH    Project directory (default: current directory)

Options:
  --harness TEXT    Target a specific harness: claude, cursor, copilot,
                    codex, opencode (repeatable; auto-detects if omitted)

Creates .serpentine.yml with default configuration and adds .serpentine to .gitignore. Then installs harness-specific configuration for whichever AI coding tools are detected in the project:

Harness What gets installed
claude .claude/skills/code-analysis/SKILL.md + appends to CLAUDE.md
cursor .cursor/rules/serpentine.mdc
copilot appends to .github/copilot-instructions.md
codex appends to AGENTS.md
opencode appends to AGENTS.md

Already-existing files are skipped rather than overwritten. Use --harness to target a specific harness instead of auto-detecting.


Querying the Graph

serpentine analyze and serpentine catalog accept --select and --exclude to slice the graph down to just the nodes you care about. This is useful for large codebases where the full graph is too noisy, or for piping results into other tools.

Step 1 — Find your node IDs

Every node has a dotted ID that reflects its location in the codebase. Use catalog with --format json to discover them:

serpentine catalog . --format json --pretty

This outputs a flat list of every module, class, and function with its ID:

{
  "nodes": [
    { "id": "src.auth.models.User", "name": "User", "type": "class", ... },
    { "id": "src.auth.views.login", "name": "login", "type": "function", ... },
    { "id": "src.payments.stripe.charge", "name": "charge", "type": "function", ... }
  ]
}

Narrow it down with --filter (glob matched against both ID and name):

# Find everything related to auth
serpentine catalog . --filter "*auth*" --format json --pretty

# Find by name across modules
serpentine catalog . --filter "*User*" --format json --pretty

Step 2 — Select nodes and their dependencies

Once you have IDs, use --select with serpentine analyze. The selector controls which nodes (and their relationships) appear in the output.

Plain pattern — just the matching nodes:

serpentine analyze --select "src.auth.*" --no-cfg --pretty

+pattern — the matching nodes plus everything they depend on (upstream):

# What does the login view need to work?
serpentine analyze --select "+src.auth.views.login" --no-cfg --pretty

pattern+ — the matching nodes plus everything that depends on them (downstream):

# What breaks if I change the User model?
serpentine analyze --select "src.auth.models.User+" --no-cfg --pretty

+pattern+ — both directions (full blast radius):

serpentine analyze --select "+src.payments.*+" --no-cfg --pretty

@pattern — the full connected component (everything reachable in any direction):

serpentine analyze --select "@src.auth.*" --no-cfg --pretty

Bounded hops — limit how many levels to traverse:

# 2 levels of dependencies upstream, 1 level downstream
serpentine analyze --select "2+src.auth.views.login+1" --no-cfg --pretty

Multiple selectors — comma-separated, combined as a union:

serpentine analyze --select "+src.auth.*,+src.payments.*" --no-cfg --pretty

Step 3 — Exclude noise

--exclude removes nodes from the result (including their descendants):

# Show auth and its deps, but skip test files
serpentine analyze --select "+src.auth.*" --exclude "*test*" --no-cfg --pretty

Glob pattern rules

* in a selector matches any characters including dots, so it crosses module boundaries:

You want Use
All nodes whose ID contains "auth" *auth*
All children of a specific module src.auth.*
A class anywhere in the project *.User
All test files *test*

Tip: ** is equivalent to * — both match across dots.

Compact output for large graphs

Use --edges-only to get just the edge list (much smaller than the full node tree):

serpentine analyze --select "+src.auth.*+" --edges-only --pretty

Each edge has a from and to field with node IDs, and a type field:

Type Meaning
calls One entity calls another as a function
has-a A variable holds an instance of a class (constructor call)
references One entity names another without calling it (assignment, annotation, expression)
is-a Inheritance — one class extends another

VCS Integration

When Serpentine is run inside a git repository, it automatically detects available refs (branches, tags, and recent commits) and enables the compare toolbar in the UI.

Comparing refs

Click the compare toolbar to select a baseline ref and a target ref. Serpentine analyzes the graph at each ref and overlays change status on every node:

Status Meaning
modified Node exists in both refs but its dependencies changed
added Node exists in the target ref but not the baseline
deleted Node existed in the baseline ref but was removed

Unchanged nodes remain visible for context.

Filtering by state

Use the state: selector method in the search bar to filter the graph to only changed nodes:

state:modified
state:added
state:deleted

Selectors compose with all graph operators:

# All nodes modified on the current branch, plus their upstream dependencies
+state:modified

# Full connected component of every added node
@state:added

Checkpoints

The checkpoint button marks the current live graph state as the comparison baseline. This is useful when you want to track changes made during a session rather than comparing against a git ref.

Configuration

Files excluded via .serpentine.yml (exclude_dirs, exclude_patterns) are excluded from VCS snapshot analysis as well — ensuring consistent results between live analysis and historical comparison.


Configuration

Serpentine looks for .serpentine.yml or serpentine.yml in the project root. All settings are optional.

analysis:
  # File extensions to analyze (default: all supported)
  extensions: [".py", ".js", ".jsx", ".ts", ".tsx", ".rs", ".tf"]

  # Directories to skip
  exclude_dirs:
    - node_modules
    - .venv
    - dist
    - build

  # Glob patterns for files to skip
  exclude_patterns:
    - "**/*.test.ts"
    - "**/generated/**"

Using Serpentine with AI Agents

Serpentine's CLI is designed to be consumed by AI coding agents (Claude, Cursor, Copilot, etc.). The structured output, selector syntax, and --edges-only flag exist specifically to give agents precise, low-noise subgraphs without requiring file reads.

The problem it solves

When an agent starts a non-trivial task, it faces a cold-start problem: it doesn't know which files are relevant, how components relate, or what the blast radius of a change might be. The usual approach — grep, glob, read files one at a time — is expensive in tokens and often incomplete.

Serpentine collapses that exploration into 2-3 CLI calls. The agent gets a structural picture of the relevant code before it starts reading files or writing a plan. In practice this means:

  • Plans are scoped to the right files and boundaries from the start
  • Less back-and-forth between reading files to trace call chains
  • Fewer surprises mid-implementation when a dependency was missed

Recommended workflow for agents

# 1. Get the lay of the land — module names, rough scale
serpentine stats .

# 2. Find relevant node IDs by name
serpentine catalog . --filter "*auth*" --format json --pretty

# 3. Get the subgraph for the relevant area
serpentine analyze . --select "+src.auth.*+" --no-cfg --edges-only --pretty

Read the edges to understand what connects to what, then read only the files that are actually relevant.

Scenarios where this pays off

Before refactoring a module Use --select "module+" to map everything that depends on the module before writing a plan. This ensures the plan accounts for every callsite and doesn't discover new dependents halfway through.

Before adding a feature that spans modules Use --select "+moduleA.*,+moduleB.*" to get the combined subgraph of two areas and understand exactly where they intersect. The boundaries of the work become clear before any code is written.

Orienting to an unfamiliar codebase serpentine stats gives you the top-level module list and scale. serpentine catalog . gives a flat index of every class and function. Together these give structural orientation without reading a single file.

Before deleting code Use --select "*.TargetFunction+" to get all downstream dependents. An empty result confirms the code is truly unused.

Tracing a call chain Use --select "+*.entrypoint+3" to get 3 hops downstream from an entry point and understand the execution path before adding instrumentation or fixing a bug.

Harness integration

Run serpentine init in any project to wire Serpentine into your AI coding tools. For Claude Code, this installs the code-analysis skill to .claude/skills/code-analysis/ and appends navigation instructions to CLAUDE.md — Claude then uses stats, catalog, and analyze instead of reading files or grepping. For Cursor, Copilot, Codex, and OpenCode, it installs the equivalent rules or instructions file for each tool.


Development

Project Structure

serpentine/
├── rust/                       # Rust analyzer (tree-sitter + PyO3)
│   └── src/
│       ├── lib.rs              # PyO3 module entry point
│       ├── python/             # Python parser
│       ├── javascript/         # JS/TS parser
│       ├── rust_lang/          # Rust parser
│       ├── terraform/          # Terraform/HCL parser
│       ├── subscribers/        # Event processors (imports, calls, defs)
│       └── graph/              # Graph builder and resolvers
├── src/serpentine/             # Python package
│   ├── cli.py                  # CLI commands
│   ├── state.py                # Graph state management
│   ├── watcher.py              # File watcher
│   ├── selector.py             # Graph selector engine
│   ├── config.py               # Config loading
│   └── server/                 # Starlette web server + WebSocket
├── frontend/                   # React frontend (Vite + TypeScript)
│   └── src/
├── Makefile                    # Build targets
└── pyproject.toml

Building for Development

Tilt is recommended for development — it rebuilds the Rust extension and frontend in parallel when files change:

tilt up

Or manually:

# Build Rust extension (rerun after changes to rust/src/)
uv run maturin develop

# Build frontend (rerun after changes to frontend/src/)
cd frontend && npm run build

# Run server
uv run serpentine serve --no-browser

Adding a Language

  1. Create a parser module in rust/src/<language>/
  2. Walk the AST and emit three event types via the message bus:
    • ImportStatement — records what names are imported from which modules
    • UseName — fires for every identifier in expression position (not definition position)
    • CallExpression — fires for every function or constructor invocation
  3. Register the parser in rust/src/lib.rs

These three events are the minimum required for the graph builder to resolve all code references in your language. See rust/src/python/ for a complete reference implementation and CONTRIBUTING.md for full details.


License

Apache 2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

serpentine_parser-0.4.0.tar.gz (1.6 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

serpentine_parser-0.4.0-cp310-abi3-win_amd64.whl (2.8 MB view details)

Uploaded CPython 3.10+Windows x86-64

serpentine_parser-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.1 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

serpentine_parser-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (3.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

serpentine_parser-0.4.0-cp310-abi3-macosx_11_0_arm64.whl (2.9 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

serpentine_parser-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl (3.0 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file serpentine_parser-0.4.0.tar.gz.

File metadata

  • Download URL: serpentine_parser-0.4.0.tar.gz
  • Upload date:
  • Size: 1.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for serpentine_parser-0.4.0.tar.gz
Algorithm Hash digest
SHA256 3dd06cb0baabcc8443152a642917388886acdd9d8a877481b0989c3485cac39c
MD5 f7800ad3111be89070d5dae05d8d4c06
BLAKE2b-256 8a3e299135f470d94bd2933c7464e34688a94547e26b4101c1e2fcce2dc641f6

See more details on using hashes here.

Provenance

The following attestation bundles were made for serpentine_parser-0.4.0.tar.gz:

Publisher: release.yml on serpentine-parser/serpentine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file serpentine_parser-0.4.0-cp310-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for serpentine_parser-0.4.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 eeb83e2bec04ddbc7ca64c0f24b10b2f9d4438ef3990f1bc15489d883a3e7290
MD5 882525085370fe05c553286ec5f611d2
BLAKE2b-256 6a13479871475c00f7e24a7b7913ecc9371dc413275cc41158412104f37c44db

See more details on using hashes here.

Provenance

The following attestation bundles were made for serpentine_parser-0.4.0-cp310-abi3-win_amd64.whl:

Publisher: release.yml on serpentine-parser/serpentine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file serpentine_parser-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for serpentine_parser-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9f98e14cc5c5bd6dfa1794b7c069b69963ee196368c668d7e23b5981a0d06cba
MD5 821b3b4736dfca1d5778d607d0be741b
BLAKE2b-256 c34221a2c646df35e8047c86da7193f63b4dd804c9921e6d1f90a1e9b37ebd32

See more details on using hashes here.

Provenance

The following attestation bundles were made for serpentine_parser-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on serpentine-parser/serpentine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file serpentine_parser-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for serpentine_parser-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 210d2234f216e225ca977133141775e9aff1fb595ca36b6539dadd1e22a20f5f
MD5 8a7265b24730fa202cd711eb8523ced3
BLAKE2b-256 b041015a99f669ef521083294ee8cd88c2650ce679cbfe5d1888f3a978e64813

See more details on using hashes here.

Provenance

The following attestation bundles were made for serpentine_parser-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on serpentine-parser/serpentine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file serpentine_parser-0.4.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for serpentine_parser-0.4.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 a0a01a30c73a94d3ce2eef2780189ed116618a37e09c1b6e462f80877b0d673b
MD5 d38c1ab149939c0d2fad8a6b33e49c8b
BLAKE2b-256 5058dead09f206a5237da409b48f957d51e98603345ec523a6d811f26486ce8a

See more details on using hashes here.

Provenance

The following attestation bundles were made for serpentine_parser-0.4.0-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on serpentine-parser/serpentine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file serpentine_parser-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for serpentine_parser-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 0a3b351e2e8c0272d95e4633f88d9e04773b9bc730973d0a9c5fe4e3935fc19c
MD5 6fc8087cdb41cbc60c8cea9820a62279
BLAKE2b-256 1b90261aabe707d2afec7cea9012228ada947cb45d4f7e884daf3df896733a86

See more details on using hashes here.

Provenance

The following attestation bundles were made for serpentine_parser-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on serpentine-parser/serpentine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page