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_requestevents, compares the PR base vs head. Onpushevents, comparesHEAD~1vsHEAD. - Threshold Gating — Set
fail-aboveto 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: 0is required for comparison reports.permissions: pull-requests: writeis 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
Release history Release notifications | RSS feed
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0bf4a33fff56428019c818d937ba01ba485d0f7ad5b27e32cae2bbd5502ba9fa
|
|
| MD5 |
d11ef337f1569264570ad4ba0587f484
|
|
| BLAKE2b-256 |
54de7b7445d0b8dadb21c3d7e4eb4e75cdbc973c37171608c976fdd220514367
|
Provenance
The following attestation bundles were made for complexity_accounting-1.6.1.tar.gz:
Publisher:
publish.yml on zhanglpg/code-complexity-measure
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
complexity_accounting-1.6.1.tar.gz -
Subject digest:
0bf4a33fff56428019c818d937ba01ba485d0f7ad5b27e32cae2bbd5502ba9fa - Sigstore transparency entry: 1167896700
- Sigstore integration time:
-
Permalink:
zhanglpg/code-complexity-measure@c484e2615856c709c9db118a085be8cdc2a1c264 -
Branch / Tag:
refs/tags/v1.6.1 - Owner: https://github.com/zhanglpg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c484e2615856c709c9db118a085be8cdc2a1c264 -
Trigger Event:
release
-
Statement type:
File details
Details for the file complexity_accounting-1.6.1-py3-none-any.whl.
File metadata
- Download URL: complexity_accounting-1.6.1-py3-none-any.whl
- Upload date:
- Size: 62.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8639a4c4084359b4ed5cea55b018863e53659ae23d5238a30edf8bbc0ac1923a
|
|
| MD5 |
bcf68cbdc45d822603e633abbef1f2e5
|
|
| BLAKE2b-256 |
81e38192848199327ecf4db6cb5c328766d99186692913bdf5fdc06b7b2b6ede
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
complexity_accounting-1.6.1-py3-none-any.whl -
Subject digest:
8639a4c4084359b4ed5cea55b018863e53659ae23d5238a30edf8bbc0ac1923a - Sigstore transparency entry: 1167896770
- Sigstore integration time:
-
Permalink:
zhanglpg/code-complexity-measure@c484e2615856c709c9db118a085be8cdc2a1c264 -
Branch / Tag:
refs/tags/v1.6.1 - Owner: https://github.com/zhanglpg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c484e2615856c709c9db118a085be8cdc2a1c264 -
Trigger Event:
release
-
Statement type: