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, and coupling penalties | Single number for CI gates |
| Hotspots | Functions above threshold (default 10) | Identifies refactoring targets |
| Churn Factor | How frequently files change | Penalizes volatile, complex code |
| Coupling Factor | Import fan-out (efferent coupling) | Penalizes tightly coupled modules |
Net Complexity Score (NCS)
NCS = (w_cog * avg_cognitive + w_cyc * avg_cyclomatic) * (1 + hotspot_ratio) * churn_factor * coupling_factor
- Weights — cognitive: 0.7, cyclomatic: 0.3 (configurable)
- hotspot_ratio = functions above threshold / total functions
- churn_factor = 1.0 + log(avg_file_churn) / 10
- coupling_factor = 1.0 + avg_efferent_coupling / max_efferent_coupling
- Rating: low <=3 | moderate <=6 | concerning <=10 | critical >10
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 | off |
--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 |
--output FILE / -o |
Write output to file instead of stdout | stdout |
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 |
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 |
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
├── 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)
├── 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
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.0.tar.gz.
File metadata
- Download URL: complexity_accounting-1.6.0.tar.gz
- Upload date:
- Size: 92.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
404d1532e8f95bfd2d1dde58731481fe27909b664704a64615434944fea12ab6
|
|
| MD5 |
69e5e227ffc4f135d79ccd69ceebd963
|
|
| BLAKE2b-256 |
294565341dd864df0457386724837df8b2ed0ba8ddba1083eb6bf9b019f6801d
|
Provenance
The following attestation bundles were made for complexity_accounting-1.6.0.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.0.tar.gz -
Subject digest:
404d1532e8f95bfd2d1dde58731481fe27909b664704a64615434944fea12ab6 - Sigstore transparency entry: 1154945627
- Sigstore integration time:
-
Permalink:
zhanglpg/code-complexity-measure@3d965812f847ea1f709be493c44e64c05334851c -
Branch / Tag:
refs/tags/v1.6.0 - Owner: https://github.com/zhanglpg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3d965812f847ea1f709be493c44e64c05334851c -
Trigger Event:
release
-
Statement type:
File details
Details for the file complexity_accounting-1.6.0-py3-none-any.whl.
File metadata
- Download URL: complexity_accounting-1.6.0-py3-none-any.whl
- Upload date:
- Size: 55.8 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 |
687776875fa1bea9ec67a90d9459129ddc7e9206f7548ca7a7edf581f4593277
|
|
| MD5 |
657f7ec380604a77f5da3df8ef72db70
|
|
| BLAKE2b-256 |
86423a1bcbc207ce1cdce5920ac35aab7c9cab0566051437de8ba668f4ab206f
|
Provenance
The following attestation bundles were made for complexity_accounting-1.6.0-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.0-py3-none-any.whl -
Subject digest:
687776875fa1bea9ec67a90d9459129ddc7e9206f7548ca7a7edf581f4593277 - Sigstore transparency entry: 1154945630
- Sigstore integration time:
-
Permalink:
zhanglpg/code-complexity-measure@3d965812f847ea1f709be493c44e64c05334851c -
Branch / Tag:
refs/tags/v1.6.0 - Owner: https://github.com/zhanglpg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3d965812f847ea1f709be493c44e64c05334851c -
Trigger Event:
release
-
Statement type: