Skip to content

jonochang/untangle

Repository files navigation

untangle

A fast, multi-language dependency graph analyzer that catches structural regressions in CI.

Untangle builds module-level dependency graphs from your source code, computes structural complexity metrics, and diffs them between git revisions. Add it to your CI pipeline to fail PRs that introduce circular dependencies or increase coupling.

Version 0.4.0 adds a layered architecture view and consolidates the CLI around analyze submodes for report, graph, and architecture projections.

Supported Languages

Language Granularity Parser
Python Module (file-level) tree-sitter
Ruby File-level tree-sitter
Go Package-level tree-sitter
Rust Module (file-level) tree-sitter

Quick Start

# Analyze current state
untangle analyze report ./src --lang python

# Export the raw dependency graph
untangle analyze graph ./src --lang go --format dot | dot -Tsvg -o deps.svg

# Project a layered architecture view
untangle analyze architecture ./src --lang python --format dot | dot -Tsvg -o architecture.svg

# Check projected architecture against policy
untangle analyze architecture-check ./src --lang python --format text

# Generate a starter architecture policy
untangle analyze architecture-init ./src --lang python --level 1

# Diff against main (the CI use case)
untangle diff --base origin/main --head HEAD --fail-on fanout-increase,new-scc

# Compute CRAP (complexity + coverage) for functions
untangle quality functions ./src --metric crap --coverage lcov.info --lang rust --format text

# Generate the unified engineer-facing quality report
untangle quality report ./src --coverage lcov.info --lang rust --format text

Commands

  • untangle analyze report [path] prints the standard structural report in text, json, or sarif.
  • untangle analyze graph [path] exports the raw dependency graph in dot or json.
  • untangle analyze architecture [path] projects a layered architecture view in dot or json.
  • untangle analyze architecture-check [path] validates projected component boundaries in text or json.
  • untangle analyze architecture-init [path] generates a starter architecture policy in .untangle.toml.
  • untangle diff [path] compares two git revisions and supports CI policy gating.
  • untangle quality functions [path] reports function-level quality metrics.
  • untangle quality report [path] combines structural metrics, hotspots, function quality, architecture analysis, and priority actions.
  • untangle quality project [path] keeps the legacy aggregate quality summary.
  • untangle service-graph [path] derives service dependencies from [services] config plus API metadata.
  • untangle config show [path] and untangle config explain [path] inspect merged configuration.

What It Measures

  • Fan-out / Fan-in — how many modules does each module depend on (and how many depend on it)?
  • Fan-out entropy — Shannon entropy of outgoing edges. Distinguishes "depends on 10 things equally" from "depends on 10 things but 90% on one."
  • Strongly connected components — circular dependency clusters. The hardest structural debt to unwind.
  • SCC-adjusted entropy — penalises complexity inside circular clusters.
  • Edge provenance — every edge carries the source module path and import line/column. (For Go, the path is the package directory.)

CI Integration

- name: Dependency structure check
  run: |
    untangle diff --base origin/main --head ${{ github.sha }} \
      --fail-on fanout-increase,new-scc \
      --format json > untangle-diff.json
Exit Code Meaning
0 No policy violations
1 One or more --fail-on conditions triggered, or an error occurred

Fail-on Conditions

Condition Triggers when
fanout-increase Any module's fan-out increased
fanout-threshold Any module exceeds --threshold-fanout
new-scc A new circular dependency cluster appeared
scc-growth An existing circular cluster gained members
entropy-increase Graph-level mean entropy increased
new-edge Any new dependency edge was added (strict mode)
new-architecture-violation A projected component boundary violation appears in head but not base
new-architecture-cycle A new projected component cycle appears in head
architecture-cycle-growth A projected component cycle in head grew relative to base

Configuration

Create a .untangle.toml in your project root:

[defaults]
lang = "python"
quiet = false
include_tests = false

[targeting]
exclude = ["vendor/**", "**/test/**", "**/*_test.go"]

[analyze.report]
format = "json"
top = 20
insights = "auto"

[analyze.graph]
format = "dot"

[analyze.architecture]
format = "dot"
level = 1
check_format = "text"
fail_on_violations = true
fail_on_cycles = true

[analyze.architecture.allowed_dependencies]
api = ["db", "utils"]
db = []
utils = []

[diff]
format = "json"
fail_on = ["fanout-increase", "new-scc", "scc-growth", "new-architecture-violation"]

[quality]
format = "text"

Example Output

$ untangle analyze report ./src --lang python --format text

  Modules: 342    Edges: 1,208    Density: 0.010
  Fan-out: mean 3.5 · p90 8 · max 23
  SCCs: 4 (29 modules in circular clusters, largest: 12)

  Top fan-out:
    src/core/engine.py         23  (entropy: 4.12)
    src/api/middleware.py       18  (entropy: 3.89)  ⚠ SCC #0 (12 members)
    src/handlers/dispatch.py   15  (entropy: 3.74)

  2 files skipped (syntax errors), 14 unresolved imports
  Completed in 0.85s (403 modules/sec)

Installation

# From crates.io
cargo install untangle

# From source
git clone https://github.com/jonochang/untangle && cd untangle
cargo build --release

Pre-built binaries for Linux, macOS, and Windows are available on the releases page.

Nix users can also build from package.nix.

How It Works

Untangle uses tree-sitter to parse source files into concrete syntax trees, then extracts import statements via language-specific S-expression queries. This means it handles files with syntax errors gracefully (partial parse) and runs at >10,000 files/sec.

The extracted imports are resolved to project-internal modules and assembled into a directed graph using petgraph. Metrics are computed using Tarjan's algorithm for SCCs and standard graph traversal for fan-out/fan-in.

For diff, untangle reads files at arbitrary git refs via libgit2 without checking out branches, keeping the operation fast and non-destructive.

Design Documents

License

MIT OR Apache-2.0

About

A fast, multi-language dependency graph analyzer that catches structural regressions in CI.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages