Skip to main content

Measure Net Complexity Score for codebases — cognitive complexity, trend tracking, CI integration

Project description

Complexity Accounting Tool

"Complexity is the core problem of software." — Liping

Measures Net Complexity Score (NCS) — whether your codebase is an asset or liability to future development. CI-ready, git-aware, multi-language, built for real engineering teams.

Quick Start

pip install complexity-accounting

# Scan a directory
python -m complexity_accounting scan /path/to/code

# JSON output (for CI)
python -m complexity_accounting scan /path/to/code --json

# Compare branches
python -m complexity_accounting compare --base main --head HEAD --repo .

# Trend over commits
python -m complexity_accounting trend --repo . --commits 20

# CI gate: fail if NCS > 8
python -m complexity_accounting scan . --fail-above 8

What It Measures

Metric What Why
Cognitive Complexity How hard is the code to understand Primary signal — measures human burden
Cyclomatic Complexity Number of decision paths Classic metric, good for test coverage estimation
Net Complexity Score Weighted aggregate with hotspot, churn, coupling, and MI penalties Single number for CI gates
Hotspots Functions above threshold (default 10) Identifies refactoring targets
Class Complexity Total cognitive + WMC (Weighted Methods per Class) Identifies god classes and bloated abstractions
Churn Factor How frequently files change Penalizes volatile, complex code
Coupling Factor Import fan-out (efferent coupling) Penalizes tightly coupled modules
Duplication Factor Token-based clone detection (Type-1 & Type-2) Penalizes copy-paste code that inflates maintenance cost
Maintainability Index Composite metric (0-100) Industry-standard maintainability score

Net Complexity Score (NCS)

NCS aggregates all factors into a single score. Two scoring models are available via --ncs-model:

Multiplicative (default)

NCS = (w_cog * avg_cognitive + w_cyc * avg_cyclomatic)
      * (1 + hotspot_ratio)
      * churn_factor
      * coupling_factor
      * duplication_factor
      * mi_factor

Factors compound — code that is complex AND churns frequently AND is tightly coupled scores disproportionately worse, reflecting real-world maintenance risk. Best for CI gating and risk assessment.

Additive

NCS = base
      + w_hotspot * (hotspot_ratio * 10)
      + w_churn * ((churn_factor - 1) * 10)
      + w_coupling * ((coupling_factor - 1) * 10)
      + w_duplication * ((duplication_factor - 1) * 10)
      + w_mi * ((100 - avg_mi) / 10)

Each factor contributes an independent, bounded penalty. Score changes are predictable and proportional. Best for incremental debt paydown and progress tracking.

Factors

  • Weights — cognitive: 0.7, cyclomatic: 0.3 (configurable)
  • hotspot_ratio = sum of excess complexity above threshold / (total functions * threshold) — severity-weighted, not a binary count
  • churn_factor = 1.0 + log(avg_file_churn) / 10
  • coupling_factor = 1.0 + avg_efferent_coupling / max_efferent_coupling
  • duplication_factor = 1.0 + avg_duplication_ratio — token-based clone detection across all files (range 1.0–2.0)
  • mi_factor = 1.0 + max(0, (50 - avg_mi) / 50) — penalizes low maintainability (range 1.0–2.0)
  • Additive weights — hotspot: 0.2, churn: 0.1, coupling: 0.1, duplication: 0.15, mi: 0.1 (configurable)
  • Rating: low <=3 | moderate <=6 | concerning <=10 | critical >10

Which model should I use?

Use case Recommended model
CI gating / quality gates multiplicative — compounding prevents gaming any single metric
Risk assessment & prioritization multiplicative — co-occurring problems are flagged proportionally
Gradual tech debt reduction additive — predictable score movement per fix
Team dashboards & trend tracking additive — bounded, stable increments are easier to chart
Fine-grained weight tuning additive — 7 independent weights vs 2

Supported Languages

Language Backend Install
Python libcst included
Go tree-sitter-go pip install complexity-accounting[go]
Java tree-sitter-java pip install complexity-accounting[java]
TypeScript tree-sitter-typescript pip install complexity-accounting[ts]
JavaScript tree-sitter-javascript pip install complexity-accounting[js]
Rust tree-sitter-rust pip install complexity-accounting[rust]
C/C++ tree-sitter-cpp pip install complexity-accounting[cpp]

CLI Reference

scan — Analyze files and directories

python -m complexity_accounting scan <path> [options]
Option Description Default
--json JSON output (shorthand for --format json) off
--format FORMAT Output format: text, json, html, sarif text
--threshold N Hotspot cognitive complexity threshold 10
--top N Show top N complex functions 20
--fail-above FLOAT Exit 1 if NCS exceeds value none
--config PATH Path to .complexity.toml auto-detect
--weights KEY=VAL Override NCS weights (e.g. cognitive=0.7,cyclomatic=0.3) config
--churn-days N Days of git history for churn analysis 90
--churn-commits N Max commits for churn analysis 100
--no-churn Skip churn factor calculation off
--no-coupling Skip coupling factor calculation off
--no-duplication Skip duplication factor calculation off
--duplication-min-tokens N Minimum token sequence length for clone detection 50
--no-cache Disable content-hash caching off
--ncs-model MODEL NCS formula: multiplicative (compounding, risk-focused) or additive (linear, progress-focused) multiplicative
--brief Hide NCS factor breakdown (shown by default) off
--include-tests Include test files in complexity scoring off
--workers N Number of parallel workers for scanning auto
--output FILE / -o Write output to FILE instead of stdout stdout

Output Formats:

  • text — Human-readable report with NCS breakdown, top functions, and top classes
  • json — Machine-readable JSON with full metrics
  • html — Styled HTML report with interactive elements
  • sarif — SARIF 2.1 format for IDE integration (VS Code, GitHub Code Scanning)

compare — Diff complexity between git refs

python -m complexity_accounting compare --base REF --head REF [options]
Option Description Default
--base REF Base reference (branch, tag, SHA) required
--head REF Head reference HEAD
--repo PATH Git repository path .
--json JSON output off
--markdown Markdown output (for PR comments) off
--full Scan all files, not just changed ones off
--include-tests Include test files in complexity scoring off
--output FILE / -o Write output to FILE instead of stdout stdout

trend — Complexity over recent commits

python -m complexity_accounting trend --repo . [options]
Option Description Default
--repo PATH Git repository path .
--commits N Number of commits to analyze 10
--ref REF Starting reference HEAD
--json JSON output off
--include-tests Include test files in complexity scoring off
--output FILE / -o Write output to FILE instead of stdout stdout

list-plugins — List discovered language plugins

python -m complexity_accounting list-plugins

Shows all discovered language plugins (built-in + extension points). Useful for debugging custom language support.

Configuration

Configuration precedence: CLI args > .complexity.toml > pyproject.toml [tool.complexity-accounting] > defaults

.complexity.toml

hotspot-threshold = 8
weight-cognitive = 0.8
weight-cyclomatic = 0.2
risk-low = 5
risk-moderate = 10
risk-high = 20
churn-days = 90
churn-commits = 100

Or in pyproject.toml:

[tool.complexity-accounting]
hotspot-threshold = 8
weight-cognitive = 0.8
weight-cyclomatic = 0.2

GitHub Action

Use this action in any repository to automatically report complexity scores on pushes and pull requests.

Quick Start

# .github/workflows/complexity.yml
name: Complexity Check
on: [push, pull_request]
permissions:
  contents: read
  pull-requests: write

jobs:
  complexity:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: zhanglpg/code-complexity-measure@main
        with:
          path: '.'
          fail-above: '8'
          post-comment: 'true'

Inputs

Input Description Default
path Path to scan .
threshold Cognitive complexity hotspot threshold 10
fail-above Fail the check if NCS exceeds this value (none)
output-format Output format: json or markdown markdown
python-version Python version to use 3.11
post-comment Post results as a PR comment (true/false) false
update-comment Update existing PR comment instead of creating duplicates true
extras Comma-separated language extras to install (e.g. go,java,cpp) (none)

Outputs

Output Description
ncs Net Complexity Score (float)
hotspot-count Number of hotspot functions (int)
pass Whether the scan passed the threshold (true/false)
delta NCS delta from comparison, if available (float)

Features

  • Job Summary — Every run writes a complexity report to the GitHub Actions Job Summary, visible on the workflow run page.
  • PR Comments — When post-comment: 'true', posts a detailed comparison report as a PR comment. Subsequent pushes update the same comment instead of creating duplicates.
  • Push & PR Support — On pull_request events, compares the PR base vs head. On push events, compares HEAD~1 vs HEAD.
  • Threshold Gating — Set fail-above to fail the check if NCS exceeds the value. The Job Summary and PR comment are always posted, even when the check fails.
  • Multi-Language — Set extras: 'go,java' to install Go and Java support via tree-sitter.
  • Pip Caching — Dependencies are cached across runs for faster execution.

Full Example

name: Complexity Check
on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read
  pull-requests: write  # Required for PR comments

jobs:
  complexity:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history needed for comparison

      - name: Complexity Check
        id: complexity
        uses: zhanglpg/code-complexity-measure@main
        with:
          path: 'src'
          threshold: '10'
          fail-above: '8'
          post-comment: 'true'
          extras: 'go'

      - name: Use outputs
        if: always()
        run: |
          echo "NCS: ${{ steps.complexity.outputs.ncs }}"
          echo "Hotspots: ${{ steps.complexity.outputs.hotspot-count }}"
          echo "Passed: ${{ steps.complexity.outputs.pass }}"
          echo "Delta: ${{ steps.complexity.outputs.delta }}"

Note: fetch-depth: 0 is required for comparison reports. permissions: pull-requests: write is required for PR comments.

See examples/complexity-check.yml for a copy-pasteable workflow.

Manual Workflow Step

You can also use the CLI directly without the composite action:

- name: Complexity Gate
  run: |
    pip install complexity-accounting
    python -m complexity_accounting scan . --fail-above 8

- name: PR Complexity Delta
  run: |
    python -m complexity_accounting compare \
      --base origin/main --head HEAD --repo . --markdown \
      > complexity-report.md

Pre-commit Hook

Add complexity checking to your pre-commit config:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/zhanglpg/code-complexity-measure
    rev: main
    hooks:
      - id: complexity-check
        args: ['--fail-above', '8']

Architecture

complexity_accounting/
├── scanner.py          # Core: cognitive + cyclomatic complexity via libcst
├── models.py           # Data models (Function, Class, File, ScanResult)
├── git_tracker.py      # Git-aware: compare refs, track trends, PR deltas
├── churn.py            # Git churn analysis (modification frequency)
├── coupling.py         # Import coupling analysis (efferent coupling)
├── duplication.py      # Token-based clone detection (Type-1 & Type-2)
├── cache.py            # Content-hash caching for incremental scans
├── halstead.py         # Halstead complexity metrics
├── html_report.py      # HTML report generation
├── sarif.py            # SARIF 2.1 format export
├── plugin.py           # Language plugin discovery system
├── base_parser.py      # Base parser protocol for language plugins
├── go_parser.py        # Go support via tree-sitter
├── java_parser.py      # Java support via tree-sitter
├── ts_parser.py        # TypeScript support via tree-sitter
├── js_parser.py        # JavaScript support via tree-sitter
├── rust_parser.py      # Rust support via tree-sitter
├── cpp_parser.py       # C/C++ support via tree-sitter
├── config.py           # Configuration loading (.complexity.toml, pyproject.toml)
├── __main__.py         # CLI entry point
└── __init__.py
  • libcst for Python AST parsing (preserves comments, whitespace, position info)
  • tree-sitter for Go, Java, TypeScript, JavaScript, Rust, and C/C++ parsing
  • Pure Python, no external services
  • Graceful degradation — churn/coupling are optional, tool works without git
  • Plugin system — Extensible language support via entry points

Installation

# Core (Python analysis only — uses libcst, no extra dependencies)
pip install complexity-accounting

Install for Specific Languages

Each non-Python language uses tree-sitter for parsing and is installed as an optional extra:

# Go
pip install complexity-accounting[go]

# Java
pip install complexity-accounting[java]

# TypeScript
pip install complexity-accounting[ts]

# JavaScript
pip install complexity-accounting[js]

# Rust
pip install complexity-accounting[rust]

# C / C++
pip install complexity-accounting[cpp]

You can combine multiple extras in a single install:

# Go + Java + TypeScript
pip install complexity-accounting[go,java,ts]

# All supported languages
pip install complexity-accounting[go,java,ts,js,rust,cpp]

Development

pip install complexity-accounting[dev]

Requirements

  • Python >= 3.8
  • libcst >= 1.0.0
  • tomli >= 1.0.0 (Python < 3.11 only)

Testing

# All tests
pytest

# With coverage
pytest --cov --cov-report=term-missing

# Skip optional language tests
pytest -m "not go and not java"

# End-to-end only
pytest -m e2e

License

MIT

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

complexity_accounting-1.6.1.tar.gz (102.3 kB view details)

Uploaded Source

Built Distribution

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

complexity_accounting-1.6.1-py3-none-any.whl (62.4 kB view details)

Uploaded Python 3

File details

Details for the file complexity_accounting-1.6.1.tar.gz.

File metadata

  • Download URL: complexity_accounting-1.6.1.tar.gz
  • Upload date:
  • Size: 102.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for complexity_accounting-1.6.1.tar.gz
Algorithm Hash digest
SHA256 0bf4a33fff56428019c818d937ba01ba485d0f7ad5b27e32cae2bbd5502ba9fa
MD5 d11ef337f1569264570ad4ba0587f484
BLAKE2b-256 54de7b7445d0b8dadb21c3d7e4eb4e75cdbc973c37171608c976fdd220514367

See more details on using hashes here.

Provenance

The following attestation bundles were made for complexity_accounting-1.6.1.tar.gz:

Publisher: publish.yml on zhanglpg/code-complexity-measure

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

File details

Details for the file complexity_accounting-1.6.1-py3-none-any.whl.

File metadata

File hashes

Hashes for complexity_accounting-1.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8639a4c4084359b4ed5cea55b018863e53659ae23d5238a30edf8bbc0ac1923a
MD5 bcf68cbdc45d822603e633abbef1f2e5
BLAKE2b-256 81e38192848199327ecf4db6cb5c328766d99186692913bdf5fdc06b7b2b6ede

See more details on using hashes here.

Provenance

The following attestation bundles were made for complexity_accounting-1.6.1-py3-none-any.whl:

Publisher: publish.yml on zhanglpg/code-complexity-measure

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