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, codiff 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.
No LLM, no embeddings, fully offline.
Supported languages
| Language | Extensions |
|---|---|
| Python | .py |
| TypeScript | .ts, .tsx |
Installation
pip install codiff
Quickstart
# Run from inside any git repository
codiff diff
This compares your working tree against HEAD. If everything is already committed, use --base and --head to compare two refs explicitly:
# Diff between two branches
codiff diff --base main --head my-feature
# Diff introduced by the last commit only
codiff diff --base HEAD~1 --head HEAD
# Diff between two arbitrary commits
codiff diff --base a1b2c3d --head e4f5g6h
To get a Mermaid diagram (useful for PR descriptions):
codiff diff --base main --format mermaid
To save the diagram to a file and preview it:
codiff diff --base main --format mermaid > mermaid.md
Open mermaid.md in any Markdown previewer (VS Code, GitHub, etc.) to visualise the diagram.
GitHub Action
codiff is available as a GitHub Action that posts a structural diff comment on every pull request — no AI coding agent required. The comment is created when the PR opens and updated on every subsequent push.
Run once in your repository to set it up:
codiff init --agent github-actions
This creates .github/workflows/codiff.yml:
name: codiff
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
codiff:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: issahammoud/codiff-action@v1
Commit and push the file to activate the action.
Note: Use either the GitHub Action or an AI coding agent integration, not both. If both are active, codiff detects the redundancy automatically and the action stays silent when the agent has already embedded a diagram in the PR description.
Agent integration (MCP)
Run once per project to wire codiff into 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
This writes the MCP server config and a project instructions file that tells the agent to call codiff_diff when creating pull requests. The agent then automatically embeds a structural diagram in every PR description.
| 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 |
— |
Here's the output of codiff diff --base b8dde67 --head d25c385 --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.
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.
Requirements
- Python 3.11+
- Git
CLI reference
# 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
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:
-
Connected modules — files that have call or inheritance relationships with other changed files. Rendered with ELK layout left-to-right, with
callsandinheritsarrows between class boxes. -
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file codiff-0.1.3.tar.gz.
File metadata
- Download URL: codiff-0.1.3.tar.gz
- Upload date:
- Size: 126.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e7532a5039024ef8faed0d691dc67d164d68550a7a944bf7720040e80140ff1
|
|
| MD5 |
3f536ba4dc66ecadc41db5d6cad7c8f7
|
|
| BLAKE2b-256 |
55f194f9281c8e3be6cdc5334bb3720e76e535a134f25c3fd742861f2295bec6
|
Provenance
The following attestation bundles were made for codiff-0.1.3.tar.gz:
Publisher:
publish.yml on issahammoud/codiff
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codiff-0.1.3.tar.gz -
Subject digest:
8e7532a5039024ef8faed0d691dc67d164d68550a7a944bf7720040e80140ff1 - Sigstore transparency entry: 2071242364
- Sigstore integration time:
-
Permalink:
issahammoud/codiff@aae30da5cbd8be26bab5aee7391e1445b9d16206 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/issahammoud
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@aae30da5cbd8be26bab5aee7391e1445b9d16206 -
Trigger Event:
push
-
Statement type:
File details
Details for the file codiff-0.1.3-py3-none-any.whl.
File metadata
- Download URL: codiff-0.1.3-py3-none-any.whl
- Upload date:
- Size: 83.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a311d229a4764a3a6d7523ee8427c4640f557b9a946d59f1ae6ed2b241191184
|
|
| MD5 |
b709564c424c6bc28b3d7836c7c8c270
|
|
| BLAKE2b-256 |
0e35d55bd0fcdd266c8b0753fe9f82f4d65da1aadd7eeb015a053272909cd874
|
Provenance
The following attestation bundles were made for codiff-0.1.3-py3-none-any.whl:
Publisher:
publish.yml on issahammoud/codiff
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
codiff-0.1.3-py3-none-any.whl -
Subject digest:
a311d229a4764a3a6d7523ee8427c4640f557b9a946d59f1ae6ed2b241191184 - Sigstore transparency entry: 2071242393
- Sigstore integration time:
-
Permalink:
issahammoud/codiff@aae30da5cbd8be26bab5aee7391e1445b9d16206 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/issahammoud
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@aae30da5cbd8be26bab5aee7391e1445b9d16206 -
Trigger Event:
push
-
Statement type: