Skip to main content

Structural call-graph diff for codebases, built for coding agents

Project description

codiff

A structural call-graph diff tool for multi-language codebases, built to work with coding agents. Instead of a line diff, it shows what changed at the function level, which functions were added and where they hook into existing code, which were modified, which were removed, with call relationships mapped across files.

Here's the output of codiff diff --base main --format mermaid run on codiff's own codebase:

%%{init: {'layout': 'elk', 'elk': {'direction': 'RIGHT'}, 'maxTextSize': 999999, 'theme': 'base', 'themeVariables': {'background': '#ffffff', 'clusterBkg': '#f8fafc', 'clusterBorder': '#94a3b8', 'primaryColor': '#f8fafc', 'primaryBorderColor': '#94a3b8', 'primaryTextColor': '#1e293b', 'lineColor': '#64748b', 'fontSize': '13px', 'fontFamily': 'ui-monospace, SFMono-Regular, Menlo, monospace'}}}%%
classDiagram
    direction LR

    class n5["codiff/cli.py"] {
        ~ _run_diff()  sig
        ~ main()
    }

    class n7["codiff/diff/indexer.py"] {
        + _incremental_update_db()
        ~ _full_index()  sig
        ~ ensure_indexed()  sig
    }

    class n3["PythonParser"] {
        <<codiff/languages/python/parser.py>>
        ~ build_package_exports()  calls +1−1
    }

    class n8["codiff/diff/snapshot.py"] {
        + _chunk_to_node_info()
        + _git_changed_files()
        + _parse_and_expand_stale()
        + build_snapshot_incremental()
        ~ build_from_path()  sig
        ~ build_from_ref()  sig
        ~ load_from_db()  calls +1−1
    }
    class n0["_ClassStub"] {
        <<codiff/diff/snapshot.py>>
        + __init__()
    }
    class n1["_NodeStub"] {
        <<codiff/diff/snapshot.py>>
        + __init__()
    }

    class n2["LanguageParser"] {
        <<codiff/languages/parser.py>>
        ~ build_modules_dict()  calls +1−1
    }

    class n6["codiff/db/operations.py"] {
        + _insert_call_edges()
        + _insert_classes()
        + _insert_functions()
        + _session()
        + get_indexed_sha()
        + load_snapshot()
        + update_sha()
        + write_full_snapshot()
        + write_incremental()
    }

    style n5 fill:#fefce8,color:#854d0e,stroke:#f59e0b,stroke-width:3px
    style n7 fill:#fefce8,color:#854d0e,stroke:#f59e0b,stroke-width:3px
    style n3 fill:#fefce8,color:#854d0e,stroke:#f59e0b,stroke-width:3px
    style n8 fill:#fefce8,color:#854d0e,stroke:#f59e0b,stroke-width:3px
    style n0 fill:#f0fdf4,color:#166534,stroke:#22c55e,stroke-width:3px
    style n1 fill:#f0fdf4,color:#166534,stroke:#22c55e,stroke-width:3px
    style n2 fill:#fefce8,color:#854d0e,stroke:#f59e0b,stroke-width:3px
    style n6 fill:#f0fdf4,color:#166534,stroke:#22c55e,stroke-width:3px

    %% Relationships
    n3 --|> n2
    n5 --> n8 : calls
    n7 --> n0 : calls
    n7 --> n1 : calls
    n7 --> n6 : calls
    n7 --> n8 : calls
    n8 --> n0 : calls
    n8 --> n1 : calls
    n8 --> n6 : calls

Each box is a file or class. Green = only additions, yellow = at least one modification. Inside each box: + added function, ~ modified. Annotations: sig = signature changed, calls +N−N = now calls N more / N fewer functions. Arrows show which files call into which.

Supported languages

Language Extensions
Python .py
TypeScript .ts, .tsx

How it works

codiff maintains a SQLite call-graph index (.codiff.db) at the repo root. On first run it does a full parse; on subsequent runs it re-parses only the files changed since the last indexed commit, then detects stale callers (functions whose callees were renamed or deleted) and re-parses those too. Both the base and head snapshots are built incrementally from this index, so diffs stay fast even on large codebases.

The graph delta — added, modified, removed functions — is computed deterministically from the resolved call graph. No LLM, no embeddings, fully offline.

Requirements

  • Python 3.11+
  • Git

Installation

pip install codiff

Usage

Agent integration (MCP + project instructions)

Run once per project to configure your coding agent:

codiff init --agent claude      # Claude Code
codiff init --agent codex       # OpenAI Codex CLI
codiff init --agent gemini      # Gemini CLI
codiff init --agent vibe        # Mistral Vibe

Each command writes the MCP server config and a project instructions file telling the agent to use the codiff_diff MCP tool when creating pull requests:

Agent MCP config Instructions file
claude .mcp.json CLAUDE.md
codex .codex/config.toml AGENTS.md
gemini ~/.gemini/settings.json ¹ GEMINI.md
vibe ~/.vibe/config.toml ²

¹ Gemini CLI MCP config is global (not project-scoped). Restart Gemini CLI after running. ² Mistral Vibe MCP config is global (not project-scoped). Restart Vibe after running.

When creating a pull request, the agent calls codiff_diff(base_ref="main", head_ref="HEAD", format="mermaid") and embeds the returned diagram in the PR description. GitHub renders Mermaid natively — no plugin needed.

CLI

# Index a repository (writes .codiff.db — runs automatically on first diff)
codiff index <path>

# Diff
codiff diff                          # diff HEAD vs working tree (terminal output)
codiff diff --format mermaid         # output a Mermaid class diagram
codiff diff --format json            # output structured JSON (for editor integrations)
codiff diff --base main              # diff a specific base ref
codiff diff --head <ref>             # diff two git refs directly
codiff diff --repo /path/to/repo     # diff a different repo
codiff diff --include-tests          # include test functions (hidden by default)
codiff diff --include-deleted        # include deleted functions (hidden by default)
codiff diff --workers 8              # set parallel worker count (default: cpu_count // 2)
codiff diff --debug                  # print timing breakdown for each processing step

# Configure a coding agent (see Agent integration below)
codiff init --agent <agent>

Output formats

Format Description
terminal Colored terminal output with UML-style boxes (default)
mermaid Two Mermaid classDiagram blocks — paste into any Markdown file or PR description
json Structured JSON — consumed by editor integrations (e.g. the VS Code extension)

Reading the terminal output

The output shows one box per changed file. Boxes are laid out side by side when they fit the terminal width, with labeled arrows between adjacent connected boxes.

Inside each file box

Methods belonging to the same class are grouped into a dashed sub-box (╭╌╌╌ ClassName ╌╌╌╮). Standalone functions appear directly in the file box. Deleted functions (only shown with --include-deleted) are collected into a red ╭╌╌╌ deleted ╌╌╌╮ sub-box.

Functions are listed with an indicator and an annotation:

Indicator Meaning
+ green Function was added
~ yellow Function was modified
- red Function was removed
Annotation Meaning
entry point Nothing calls this new function — new public surface
sig changed Parameters or return type changed
calls changed The function now calls different things
body changed Pure implementation change

For added functions, arrows show intra-file call relationships — a function indented under another calls it.

Intra-file class relationships

When two changed classes in the same file are related, each class box shows a dim annotation before its methods:

╭╌╌╌ PageAwarePreChunker ╌╌╌╮
│ calls  PageAwareBuilder   │
│ ──────────────            │
│ + __init__                │
╰╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╯

Relationship types: calls (method calls to another class) and inherits (superclass relationship detected from class definitions).

Colors

Functions that form a connected call chain share a color across the entire output — across boxes, across files. All magenta names belong to one chain, all cyan to another.

  • Chain color on the function name — part of a call chain
  • White on the function name — added/modified but not connected to any chain
  • ~ yellow — always marks a modified function regardless of chain membership

Arrows between file boxes

Labeled arrows appear between adjacent file boxes when there is a cross-file relationship:

Label Meaning
calls ────▶ A function in the left file calls a function in the right file
inherits ────▶ A class in the left file inherits from a class in the right file

Reading the Mermaid output

The Mermaid format produces two diagrams:

  1. Connected modules — files that have call or inheritance relationships with other changed files. Rendered with ELK layout left-to-right, with calls and inherits arrows between class boxes.

  2. Isolated modules — files with no cross-file relationships (e.g. migration files, config). Grouped by their top two folder levels into namespace clusters, rendered with Dagre left-to-right.

Both diagrams use color-coded class boxes:

  • Green — only additions in this file/class
  • Yellow — at least one modification
  • Red — only deletions (requires --include-deleted)

Inside each box, functions are listed with a prefix and optional annotation:

Prefix Meaning
+ Function was added
~ Function was modified
- Function was removed
Annotation Meaning
sig Parameters or return type changed
calls +N−N Call list changed — N new callees, N dropped

When a class appears inside a file box, its file path is shown as a «stereotype» subtitle below the class name.

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

codiff-0.1.1.tar.gz (124.7 kB view details)

Uploaded Source

Built Distribution

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

codiff-0.1.1-py3-none-any.whl (82.1 kB view details)

Uploaded Python 3

File details

Details for the file codiff-0.1.1.tar.gz.

File metadata

  • Download URL: codiff-0.1.1.tar.gz
  • Upload date:
  • Size: 124.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for codiff-0.1.1.tar.gz
Algorithm Hash digest
SHA256 7a23717a0d26c846cf32526fa76112c47eeba6bb015dedc0beb5532cd4ab2516
MD5 1fbf29b1657c9bb44d0a5a259c818386
BLAKE2b-256 099fa2e5a02758dc5c1d70933499ba51e233f0baced50bcda60dcf8a25125bb7

See more details on using hashes here.

Provenance

The following attestation bundles were made for codiff-0.1.1.tar.gz:

Publisher: publish.yml on issahammoud/codiff

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

File details

Details for the file codiff-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: codiff-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 82.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for codiff-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 578088d018b7ef5ed6a63a58f844850b7879edb0cc1a956e18ad691a6436a47a
MD5 f17fd950809d458354a7518a9057d0b7
BLAKE2b-256 9dcd044df49b417e903e3f8172a47dc2f5bc2f2ebfac864dd33a3c48acd229de

See more details on using hashes here.

Provenance

The following attestation bundles were made for codiff-0.1.1-py3-none-any.whl:

Publisher: publish.yml on issahammoud/codiff

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