CLI tool that generates orbital SVG visualizations of codebase health
Project description
Canopy
Orbital SVG visualizations of codebase health.
Canopy analyses your Python project — complexity, dead code, churn, and module structure — then renders a single SVG diagram you can embed in your README or CI artifacts. Click the diagram below for an interactive view with tooltips, zoom and pan.
What it shows
| Visual element | Meaning |
|---|---|
| Node colour | Health — green (healthy MI), amber (moderate), red (unhealthy) |
| Node size | Lines of code |
| Pulse ring | High git churn (recent changes) |
| Spots | Dead code detected by Vulture |
| Rings | Architectural layers defined in canopy.yml |
| Edges | Import dependencies between modules |
Installation
pip install canopy-code
Canopy shells out to radon and vulture, so install them too:
pip install "canopy-code[tools]"
For development:
git clone https://github.com/bruno-portfolio/canopy-code.git
cd canopy-code
pip install -e ".[dev,tools]"
pre-commit install
Quick Start
# Analyse the current directory
canopy run .
# Specify a project path and output file
canopy run ./my-project --output docs/canopy.svg
# Generate SVG + interactive HTML viewer
canopy run . --output docs/canopy.svg --html docs/canopy.html
# Use a custom config
canopy run . --config path/to/canopy.yml
Configuration
Create a canopy.yml (or canopy.yaml) at the project root. All fields
are optional — sensible defaults apply.
project: myproject # display name (default: directory name)
source: src/myproject # source root relative to project (default: ".")
module_depth: 2 # how many levels to group (default: 2)
ignore: # glob patterns to exclude (future)
- "tests/**"
layers: # architectural ring grouping
core:
modules: ["_core", "domain"]
infra:
modules: ["_cache", "_db"]
label: Infrastructure
vulture:
min_confidence: 60 # Vulture confidence threshold (default: 60)
exclude_types: # Vulture result types to ignore
- attribute
git:
churn_days: 30 # lookback window for churn (default: 30)
thresholds:
mi_healthy: 40 # MI score above this is green (default: 40)
mi_moderate: 20 # MI score above this is amber (default: 20)
churn_high: 20 # commits above this triggers pulse (default: 20)
min_loc: 50 # modules below this LOC get collapsed (default: 50)
output:
path: docs/canopy.svg # output file path (default: canopy.svg)
width: 1000 # SVG width in pixels (default: 1000)
height: 800 # SVG height in pixels (default: 800)
GitHub Action
Add this workflow to .github/workflows/canopy.yml to regenerate the
diagram on every push to main:
name: Canopy
on:
push:
branches: [main]
jobs:
canopy:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for churn data
- uses: bruno-portfolio/canopy-code@main
By default, the action creates a pull request with the updated diagram. This works with branch protection rules and required status checks.
If your repo has required status checks on PRs, pass a PAT so the PR
triggers your test workflows (the default GITHUB_TOKEN won't):
- uses: bruno-portfolio/canopy-code@main
with:
token: ${{ secrets.CANOPY_PAT }}
Create a fine-grained PAT
with Contents: Read and write + Pull requests: Read and write scoped to
your repo, then add it as a repository secret named CANOPY_PAT.
For repos without branch protection, you can push directly:
- uses: bruno-portfolio/canopy-code@main
with:
strategy: push
Note:
fetch-depth: 0is required for accurate churn data. Without it the clone is shallow and churn will be unavailable (Canopy warns and continues with churn = 0).
Embedding in README
After the SVG is generated, reference it in your README:

GitHub renders inline SVGs natively — no external hosting needed.
To link the static SVG to an interactive HTML viewer on GitHub Pages:
<p align="center">
<a href="https://your-user.github.io/your-repo/canopy.html">
<img src="docs/canopy.svg" width="100%" />
</a>
</p>
The HTML viewer is self-contained (zero external dependencies) and provides hover tooltips, click-to-pin, zoom (scroll) and pan (drag).
Limitations
- Dynamic imports (
importlib.import_module,__import__) are not detected by the static AST parser. TYPE_CHECKINGimports are treated as real imports (no special handling yet).- Shallow clones produce no churn data — use
fetch-depth: 0in CI. exclude_typesin Vulture config is a v1 allowlist; per-module exclusions are not yet supported.ignorepatterns are declared in config but not yet wired through collectors (planned for a future release).
License
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 canopy_code-0.1.0.tar.gz.
File metadata
- Download URL: canopy_code-0.1.0.tar.gz
- Upload date:
- Size: 59.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c680cac91164a3534b171fe630615564e9866043adc40b99176fcbf550694af
|
|
| MD5 |
c347a358026f03beaef9159d2f6d616f
|
|
| BLAKE2b-256 |
7c1cc88a28091a644a17d1daf397d83e100e647a159bf94c2881e0828df208d5
|
Provenance
The following attestation bundles were made for canopy_code-0.1.0.tar.gz:
Publisher:
publish.yml on bruno-portfolio/canopy-code
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
canopy_code-0.1.0.tar.gz -
Subject digest:
1c680cac91164a3534b171fe630615564e9866043adc40b99176fcbf550694af - Sigstore transparency entry: 1031939876
- Sigstore integration time:
-
Permalink:
bruno-portfolio/canopy-code@023855ec31d073e866a5d2393fff1352d0b371aa -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bruno-portfolio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@023855ec31d073e866a5d2393fff1352d0b371aa -
Trigger Event:
push
-
Statement type:
File details
Details for the file canopy_code-0.1.0-py3-none-any.whl.
File metadata
- Download URL: canopy_code-0.1.0-py3-none-any.whl
- Upload date:
- Size: 33.5 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 |
7c50a9b272f57964fd1a5d58cecdc3160abce54836eb72d944402911d91a7f21
|
|
| MD5 |
c12e32f7a49926c71dd88b292ee54984
|
|
| BLAKE2b-256 |
3d16f68a8f196eeb98f145300c15f7b0158a6e1763bca3dc8ef1f2fc87ae9794
|
Provenance
The following attestation bundles were made for canopy_code-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on bruno-portfolio/canopy-code
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
canopy_code-0.1.0-py3-none-any.whl -
Subject digest:
7c50a9b272f57964fd1a5d58cecdc3160abce54836eb72d944402911d91a7f21 - Sigstore transparency entry: 1031939945
- Sigstore integration time:
-
Permalink:
bruno-portfolio/canopy-code@023855ec31d073e866a5d2393fff1352d0b371aa -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bruno-portfolio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@023855ec31d073e866a5d2393fff1352d0b371aa -
Trigger Event:
push
-
Statement type: