Detect coupling violations and circular dependencies in your codebase.
Most linters check code style. noupling checks architecture.
It scans your project, builds a dependency graph from actual import statements, and quantifies how coupled your modules are using risk-weighted scoring (RRI/TRI). It finds:
- Coupling violations - sibling modules that depend on each other, breaking architectural boundaries
- Circular dependencies - dependency chains that form loops (A -> B -> C -> A), preventing independent development and testing
Every violation gets a risk score (RRI) based on dependency direction and density: upward and circular dependencies carry higher risk weights than downward ones. The result is a single health score (0-100) you can track over time and gate in CI.
- 16 languages: C#, Dart, Elixir, Go, Haskell, Java, JavaScript, Kotlin, PHP, Python, Ruby, Rust, Scala, Swift, TypeScript, Zig
- Tree-sitter parsing: Fast, accurate AST-based import extraction (no regex)
- Parallel scanning: Rayon-powered file discovery and parsing
- 12 report formats: JSON, XML, Markdown, HTML, SonarCloud, Mermaid, DOT, Sunburst, Dashboard, PR, Briefing, Strategy (or
allto generate every format) - Interactive HTML report: Kover-style drill-down with color-coded scores
- Sunburst visualization: Zoomable D3.js dependency graph with animated drill-down
- Technical Leader Dashboard: Single-page executive view with all metrics, sortable scorecard, risk matrix
- Monorepo support: Independent analysis per module with cross-module dependency validation
- Architectural layers: Define dependency direction, suppress legitimate downward coupling
- XS metric: Quantify refactoring cost per violation, find the weakest link in cycles
- Inline suppression:
// noupling:ignoreto suppress known acceptable coupling - Advanced metrics: Module independence, blast radius, instability (Martin's I), abstractness (Martin's A), distance from main sequence (Martin's D), dependency depth, violation age
- Per-module trends:
--by-moduleflag to track score evolution per directory across snapshots - PR/CI mode:
--diff-base mainto only flag new violations - Risk-weighted scoring (RRI/TRI): Quantify coupling risk by dependency direction and density
- Gravity Well detection: Identify modules that attract excessive inbound dependencies
- Red Flags (Fused Sibling, Trapped Child): Detect structural anti-patterns in module relationships
- External dependency tracking: Count third-party (unresolved) imports per module
- Configurable risk weights per dependency direction: Tune scoring to match your architecture's priorities
- Configurable: Thresholds, layers, dependency rules, glob ignore patterns
# Install
cargo install --path .
# Scan your project
noupling scan /path/to/project
# See the health score
noupling audit /path/to/project
# Generate an interactive HTML report
noupling report /path/to/project --format htmlbrew tap pererikbergman/noupling
brew install nouplinggit clone https://github.com/pererikbergman/noupling.git
cd noupling
cargo install --path .Download from GitHub Releases. Available for Linux (x86_64, aarch64), macOS (Apple Silicon, Intel), and Windows.
noupling scan /path/to/projectDiscovers source files, parses imports via Tree-sitter, and stores the dependency graph in .noupling/history.db.
noupling audit /path/to/projectDisplays a health score (0-100), coupling violations sorted by severity, and circular dependencies grouped by cycle order.
Use --fail-below to fail in CI when the score drops:
noupling audit /path/to/project --fail-below 80 # Exit code 1 if score < 80noupling report /path/to/project --format json # Comprehensive JSON
noupling report /path/to/project --format xml # Comprehensive XML
noupling report /path/to/project --format md # Multi-file navigable Markdown
noupling report /path/to/project --format html # Interactive HTML with drill-down
noupling report /path/to/project --format sonar # SonarCloud generic issue import
noupling report /path/to/project --format mermaid # Mermaid flowchart diagram
noupling report /path/to/project --format dot # GraphViz DOT graph
noupling report /path/to/project --format bundle # Zoomable sunburst with dependency edges
noupling report /path/to/project --format dashboard # Interactive Technical Leader Dashboard
noupling report /path/to/project --format all # Generate every format above in one commandOnly report violations from files changed compared to a base branch:
noupling scan /path/to/project --diff-base main
noupling audit /path/to/projectScans the full project for import resolution but filters results to changed files only. Use this in CI to fail PRs only on new issues.
- name: Install noupling
run: cargo install --path .
- name: Scan (diff mode)
run: noupling scan . --diff-base origin/main
- name: Audit (fail if score drops below 80)
run: noupling audit . --fail-below 80
- name: Generate Sonar report
run: noupling report . --format sonarCopy .github/workflows/noupling-pr.yml from this repo to your project. It automatically comments on PRs with:
- Health score
- Total and new violations
- Violation details
The workflow installs noupling from the latest release, runs a diff scan, and posts a comment. Updates existing comments on force-pushes.
Generate the generic issue import file and reference it in your Sonar config:
noupling report . --format sonarAdd to sonar-project.properties:
sonar.externalIssuesReportPaths=.noupling/noupling-sonar.jsonSettings are stored in .noupling/settings.json (auto-created on first run):
{
"thresholds": {
"score_green": 90.0,
"score_yellow": 70.0,
"critical_severity": 0.5,
"minimum_severity": 0.2
},
"ignore_patterns": [
"**/.git/**",
"**/build/**",
"**/generated/**",
"**/node_modules/**"
],
"source_extensions": [
"rs", "kt", "java", "ts", "py", "swift", "cs",
"go", "hs", "js", "jsx", "kts", "tsx", "zig",
"dart", "php", "rb"
],
"allow_inline_suppression": true,
"risk_weights": {
"downward": 2,
"sibling": 4,
"upward": 6,
"external": 8,
"transitive": 9,
"circular": 10
},
"coupling_mode": "strict"
}| Setting | Description | Default |
|---|---|---|
score_green |
Score threshold for "healthy" (green) | 90.0 |
score_yellow |
Score threshold for "warning" (yellow) | 70.0 |
critical_severity |
Violations above this are flagged critical | 0.5 |
minimum_severity |
Hide violations below this (reduce noise) | 0.2 |
ignore_patterns |
Glob patterns for dirs/files to skip | 15 defaults |
source_extensions |
File types to scan | 17 extensions |
allow_inline_suppression |
Enable noupling:ignore comments |
true |
risk_weights |
Risk weights per dependency direction (downward, sibling, upward, external, transitive, circular) | 2, 4, 6, 8, 9, 10 |
coupling_mode |
Coupling analysis mode (strict or relaxed) |
strict |
Define layers to suppress coupling violations that follow the intended dependency direction:
{
"layers": [
{ "name": "presentation", "pattern": "**/ui/**", "allow_sibling": false, "max_sibling_density": 3, "reduced_sibling_weight": 2 },
{ "name": "domain", "pattern": "**/domain/**" },
{ "name": "data", "pattern": "**/data/**" }
]
}Dependencies may only flow downward (presentation -> domain -> data). Downward dependencies are not flagged as coupling violations. Upward dependencies (data -> presentation) are flagged as both coupling and layer violations.
Forbid specific dependency patterns:
{
"dependency_rules": [
{ "from": "**/ui/**", "to": "**/data/**", "allow": false,
"message": "UI must not depend on data layer directly" }
]
}Define independent modules within a monorepo, each analyzed separately:
{
"modules": [
{ "name": "app", "path": "app/src", "depends_on": ["lib-core"] },
{ "name": "lib-core", "path": "lib/core/src", "depends_on": [] }
]
}Cross-module imports not listed in depends_on are flagged as violations. Use --module app to audit or report a single module.
Suppress known acceptable coupling with a comment on the import line:
import com.example.legacy.Helper // noupling:ignoreWorks with //, #, and -- comment styles. Disable with "allow_inline_suppression": false.
-
Scan: Discover source files, parse imports with Tree-sitter, resolve to project paths
-
Store: Persist modules and dependencies in SQLite (
.noupling/history.db) -
Analyze:
- D_acc: For each directory, compute the union of all external dependencies from its subtree
- BFS: Walk the tree top-down, checking sibling pairs for coupling
- Cycles: Find circular dependencies among siblings at each level
-
Score: Each dependency is classified by direction and assigned a risk weight:
- Downward (weight 2) - following the intended layer direction
- Sibling (weight 4) - coupling between peer modules
- Upward (weight 6) - violating the layer direction
- External (weight 8) - dependency on a third-party (unresolved) import
- Transitive (weight 9) - indirect dependency through intermediate modules
- Circular (weight 10) - part of a dependency cycle
RRI (Relationship Risk Index) =
direction_weight × density(number of imports)TRI (Total Risk Index) = sum of all RRIs
Health Score =
100 × (1 - TRI / (total_modules × max_weight))
See docs/architecture.md for the full technical details.
| Language | Extensions | Import Pattern |
|---|---|---|
| C# | .cs |
using directives |
| Dart | .dart |
import directives |
| Elixir | .ex, .exs |
alias / import / use / require |
| Go | .go |
import declarations |
| Haskell | .hs |
import declarations |
| Java | .java |
import declarations |
| JavaScript | .js, .jsx |
ES import statements |
| Kotlin | .kt, .kts |
import declarations |
| PHP | .php |
use / require / include |
| Python | .py |
import / from...import |
| Ruby | .rb |
require / require_relative |
| Rust | .rs |
use declarations |
| Scala | .scala, .sc |
import declarations |
| Swift | .swift |
import declarations |
| TypeScript | .ts, .tsx |
ES import statements |
| Zig | .zig |
@import() builtins |
See CONTRIBUTING.md for build instructions, coding standards, branching strategy, and how to add a new language parser.
If you discover a security vulnerability, please report it privately via GitHub Security Advisories.
