Lopper is a local-first CLI/TUI for measuring dependency surface area in source repos. It compares imported dependencies to actual usage and reports waste, risk cues, and recommendations.
macOS/Linux (Homebrew tap, stable):
brew tap ben-ranford/tap
brew install loppermacOS/Linux (Homebrew tap, rolling):
brew install ben-ranford/tap/lopper-rollinglopper-rolling tracks main and is not a stable semver release.
Windows (GitHub Releases):
# Open latest release page and download the Windows asset for your platform.
gh release view --repo ben-ranford/lopper --webGenerate a local manpage:
make manpageThe generated manpage is available as docs/man/lopper.1 and is installed automatically by the Homebrew formulas.
Run without local install:
docker run --rm ghcr.io/ben-ranford/lopper:latest --helpUse the first-party Scan with Lopper action in CI:
- uses: ben-ranford/lopper@v1
with:
version: action
repo: .
language: all
top: '20'For reproducible CI, pin both the action ref and version to a concrete release such as v1.7.0. See docs/ci-usage.md for PR comment and SARIF workflows.
Analyze one dependency:
lopper analyse lodash --repo . --language js-tsRank top dependencies by waste:
lopper analyse --top 20 --repo . --language all --format tableEmit JSON:
lopper analyse --top 20 --repo . --language all --format jsonEmit CSV for spreadsheet or pipeline ingestion:
lopper analyse --top 20 --repo . --language all --format csv > lopper.csvEmit SARIF for code scanning:
lopper analyse --top 20 --repo . --language all --format sarif > lopper.sarifEmit a preview CycloneDX JSON SBOM for direct dependency rows:
lopper analyse --top 20 --repo . --language all \
--format cyclonedx-json \
--enable-feature sbom-attestation-exports-preview > lopper.cdx.jsonThe CycloneDX export preserves Lopper-specific usage, reachability, runtime,
license, provenance, waste, removal-candidate, and baseline context as
component properties. It does not infer missing versions or package URLs, and it
is not a full transitive dependency inventory. SPDX 2.3 JSON is available behind
spdx-sbom-export-preview, and dashboard-wide CycloneDX portfolio exports are
available behind dashboard-cyclonedx-portfolio-preview.
Launch the interactive TUI:
lopper tui --repo . --language allRun the local MCP server for agent workflows:
lopper mcpSee docs/mcp.md for tool names, input schemas, client configuration, and safety behavior.
Tune thresholds and score weights:
lopper analyse --top 20 \
--repo . \
--language all \
--threshold-fail-on-increase 2 \
--threshold-low-confidence-warning 35 \
--threshold-min-usage-percent 45 \
--enable-feature reachability-vulnerability-prioritization-preview \
--advisory-source security/lopper-advisories.yml \
--threshold-reachable-vuln-priority high \
--score-weight-usage 0.50 \
--score-weight-impact 0.30 \
--score-weight-confidence 0.20Save an immutable baseline snapshot keyed by commit:
lopper analyse --top 20 \
--repo . \
--language all \
--format json \
--baseline-store .artifacts/lopper-baselines \
--save-baselineSave using a human label key:
lopper analyse --top 20 \
--repo . \
--language all \
--format json \
--baseline-store .artifacts/lopper-baselines \
--save-baseline \
--baseline-label release-candidateCompare against a stored baseline key and gate CI:
lopper analyse --top 20 \
--repo . \
--language all \
--format json \
--baseline-store .artifacts/lopper-baselines \
--baseline-key commit:abc123 \
--threshold-fail-on-increase 2List the newest saved snapshots, or inspect one without printing its full report payload. Baseline discovery is stable in v1.8.1:
lopper baseline list \
--store .artifacts/lopper-baselines \
--limit 20
lopper baseline show label:release-candidate \
--store .artifacts/lopper-baselines \
--format jsonAttach local vulnerability advisories for reachability-weighted security triage:
lopper analyse --top 20 \
--repo . \
--language all \
--enable-feature reachability-vulnerability-prioritization-preview \
--advisory-source security/lopper-advisories.yml \
--threshold-reachable-vuln-priority highAdvisory ingestion is preview-gated and local-only. Lopper does not fetch a proprietary or network
vulnerability database. After package name and ecosystem match, Lopper evaluates supported OSV
affected-version metadata and legacy fixedVersion values. A finding is affected when the
installed version is confirmed vulnerable; it is unevaluable when blank, malformed, or unsupported
installed or advisory version metadata prevents a reliable comparison. Confirmed unaffected matches
are omitted, while affected and unevaluable findings remain actionable. Reachable unevaluable
findings fail closed at every non-off reachable-vulnerability threshold regardless of computed
priority; off disables that gate. The priority score ranks triage using advisory severity plus
reachability, runtime, and static import evidence; it is not an exploitability claim.
Generate an org-level dashboard across multiple repos:
lopper dashboard \
--repos "./api,./frontend,./worker" \
--format html \
--output org-report.htmlUse a dashboard config file:
lopper dashboard --config lopper-org.yml --format jsonRemote dashboard config entries can use repoUrl with the dashboard-remote-repos feature and may pin exactly one of branch, tag, or full commit SHA. Unpinned remote entries continue to track remote HEAD; dashboard JSON, CSV, HTML, and saved dashboard baselines include the resolved commit SHA for materialized remote repos.
Preview pull request dependency-surface review:
lopper pr-review \
--base 0123456789abcdef0123456789abcdef01234567 \
--head fedcba9876543210fedcba9876543210fedcba98 \
--format markdown \
--enable-feature dependency-surface-pr-review-previewpr-review requires explicit immutable SHAs, analyzes detached worktrees without running package-manager commands, and separates added, removed, upgraded/downgraded, policy-changed, newly reachable, and materially worsened rows.
- Supported adapters:
js-ts,python,cpp,jvm,kotlin-android,go,php,ruby,rust,dotnet,elixir,swift,dart,powershell js-tsmerges workspace-level declarations frompnpm-workspace.yaml,package.json#workspaces, and Yarn.yarnrc.ymlcatalogs.- Source of truth for adapter IDs:
lopper --help - Language modes:
auto: choose highest-confidence adapterall: run all matching adapters and merge results<id>: force one adapter
Repo-level config example (.lopper.yml):
policy:
packs:
- ./policies/org-defaults.yml
- https://example.com/lopper/policy.yml#sha256=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
thresholds:
fail_on_increase_percent: 2
low_confidence_warning_percent: 35
min_usage_percent_for_recommendations: 45
reachable_vulnerability_priority: high
removal_candidate_weight_usage: 0.50
removal_candidate_weight_impact: 0.30
removal_candidate_weight_confidence: 0.20
advisories:
source: security/lopper-advisories.ymlThreshold defaults:
fail_on_increase_percent: -1(disabled)low_confidence_warning_percent: 40min_usage_percent_for_recommendations: 40reachable_vulnerability_priority: offremoval_candidate_weight_usage: 0.50removal_candidate_weight_impact: 0.30removal_candidate_weight_confidence: 0.20
Threshold ranges:
fail_on_increase_percentmust be-1or>= 0low_confidence_warning_percentmust be between0and100min_usage_percent_for_recommendationsmust be between0and100reachable_vulnerability_prioritymust beoff,low,medium,high, orcritical- removal candidate weights must be
>= 0and at least one must be greater than0
Precedence is CLI > repo config > imported policy packs > defaults.
Additional guides:
docs/threshold-tuning.mddocs/notifications.mddocs/feature-flags.md
Launch TUI:
lopper tui --repo . --language allCompare the TUI summary against a stored baseline:
lopper tui \
--repo . \
--language all \
--baseline-store .artifacts/lopper-baselines \
--baseline-key commit:abc123Inside the TUI, open a dependency to inspect codemod suggestions and apply safe suggestions without leaving the session:
open js-ts:lodash
apply-codemod --confirm
apply-codemod refuses dirty git worktrees by default; add --allow-dirty only when you intentionally want to apply into a dirty tree. The action prints applied, skipped, and failed files plus the rollback backup path. Baselines can also be saved and compared in-session:
Python safe unused-import suggestions are stable and enabled by default. They are syntactically conservative rather than proof that an import has no module side effects. Mutation still requires --apply-codemod --apply-codemod-confirm, refuses a dirty worktree by default, and writes rollback evidence. Use --disable-feature python-codemod-suggestions for an explicit rollback.
save-baseline
save-baseline release-candidate
compare-baseline label:release-candidate
compare-baseline --file baseline.json
In-session baseline commands default to .artifacts/lopper-baselines and commit:<HEAD> when no store or label/key is provided.
Capture a runtime trace:
export LOPPER_RUNTIME_TRACE=.artifacts/lopper-runtime.ndjson
export LOPPER_ROOT=/path/to/lopper
export NODE_OPTIONS="--require ${LOPPER_ROOT}/scripts/runtime/require-hook.cjs --loader ${LOPPER_ROOT}/scripts/runtime/loader.mjs"
npm testUse the hook files from the lopper checkout or install tree. Relative hook paths resolve from the repo running npm test, not from lopper itself.
Or let Lopper run the test command and capture the trace automatically:
lopper analyse --top 20 --repo . --language js-ts --runtime-test-command "npm test"Use trace in analysis:
lopper analyse --top 20 --repo . --language js-ts --runtime-trace .artifacts/lopper-runtime.ndjsonWith runtime traces enabled:
runtimeUsage.correlationmarks each supported dependency asstatic-only,runtime-only, oroverlap.runtimeUsage.modulesincludes runtime-loaded module paths.runtimeUsage.parentModulesincludes parent module paths that triggered the load.runtimeUsage.entrypointsincludes entrypoint modules seen in the runtime trace.runtimeUsage.topSymbolsincludes best-effort runtime symbol hits.
If --runtime-trace points to a missing file, analysis continues with static results and adds a warning.
First-party Python runtime capture is stable for these conservative pytest-family command forms (runner arguments may follow):
pytest
python -m pytest
python3 -m pytest
lopper analyse --top 20 --repo . --language python \
--runtime-test-command "pytest"The stable python-runner-profiles feature adds these direct-exec forms:
python -m unittest
python3 -m unittest
uv run pytest
uv run -- pytest
uv run python -m pytest
uv run python3 -m pytest
uv run -- python -m pytest
uv run -- python3 -m pytest
uv run python -m unittest
uv run python3 -m unittest
uv run -- python -m unittest
uv run -- python3 -m unittest
For example:
lopper analyse --top 20 --repo . --language python \
--runtime-test-command "uv run -- python -m unittest discover -s tests"The optional -- immediately after uv run is the only accepted uv wrapper delimiter. Arguments after the selected pytest command or python[3] -m pytest|unittest module are forwarded verbatim, including a later --; uv wrapper flags, arbitrary uv tools, Python interpreter flags, inline environment assignments, and shell operators are rejected.
Lopper resolves supported executables from trusted absolute PATH entries in order, then from its fixed system fallback directories. It rejects writable search directories, writable/non-executable tools, and executables outside the allowlist. Lopper injects its import hook only into the runtime command environment by prepending the shipped scripts/runtime/sitecustomize.py directory to PYTHONPATH; an existing project sitecustomize.py is chained exactly once. It does not install project dependencies or invoke a shell. Use --disable-feature python-runtime-capture or --disable-feature python-runner-profiles for rollback.
Explicit Python trace consumption remains compatible through --runtime-trace.
Each trace line should identify the Python adapter and the imported module:
{"language":"python","module":"requests.sessions","parent":"/repo/main.py","entrypoint":"/repo/main.py"}When the import root differs from the package name, include dependency:
{"language":"python","dependency":"beautifulsoup4","module":"bs4"}Use the trace in analysis:
lopper analyse --top 20 --repo . --language python --runtime-trace .artifacts/python-runtime.ndjsonPython caveats:
- Runtime module names map to the top-level import package, with common aliases such as
bs4->beautifulsoup4. - The first-party hook emits third-party imports resolved from
site-packagesordist-packagesand filters local modules and stdlib imports. - Subprocesses and pytest workers inherit the trace environment when they inherit the parent process environment; concurrent writers append NDJSON lines to the same trace file.
- Project
sitecustomize.pyruns once before Lopper installs its import wrapper; imports performed only during that startup hook are not included in the trace.
See CONTRIBUTING.md for development setup and commands.
For watched hotspot packages, run make mem-profiles to capture alloc-space summaries before or after performance-sensitive changes.
- Report schema:
docs/report-schema.json,docs/report-schema.md - Multi-repo dashboard:
docs/dashboard.md - Pull request dependency-surface review:
docs/pr-review.md - Memory profiling workflow:
docs/memory-profiling.md - SARIF code scanning:
docs/sarif-code-scanning.md - Threshold tuning:
docs/threshold-tuning.md - MCP server integration:
docs/mcp.md - Runtime trace annotations:
scripts/runtime/ - Adapter and architecture extensibility:
docs/extensibility.md - CI and release workflow:
docs/ci-usage.md - Contribution guide:
CONTRIBUTING.md


