flutter_pruner 1.0.0 copy "flutter_pruner: ^1.0.0" to clipboard
flutter_pruner: ^1.0.0 copied to clipboard

Semantic auditor for Flutter/Dart projects. Finds unused assets, duplicate files and unreachable declarations using the Dart analyzer, with confidence tiers and a reversible apply workflow.

Flutter Pruner #

Find dead Dart code and unused Flutter assets with semantic reachability, then remove only what the configured safety policy allows.

CI License: MIT

Flutter Pruner is a cleanup CLI for Flutter and Dart projects. It connects Dart declarations, libraries and assets in one graph, classifies every finding by confidence, and provides an apply workflow with byte snapshots and verified rollback.

Version 1.0.0 establishes the public API compatibility baseline. Start from a clean Git worktree and review the dry-run before using it on production code.

Why Flutter Pruner #

  • Semantic, not grep-based. Dart references are resolved through the analyzer's element model instead of source-text matching.
  • Cross-domain reachability. Dead code does not keep an otherwise unused asset alive merely because its source contains the asset path.
  • Fail-closed confidence. Dynamic references, incomplete targets, dangling graph facts and unsupported edits downgrade findings instead of being ignored.
  • Declared-scope analysis. Every configured entrypoint is analyzed; conditional branches the graph cannot model keep coverage incomplete.
  • Verified whole-run recovery. Every touched path is quarantined before mutation. If an apply run cannot finish, its mutations are restored to the run baseline and accepted only after byte and verification checks pass.
  • Audit-ready output. Completed scans and handled apply outcomes save a structured report; HTML reports are self-contained and work offline.

Flutter Pruner currently analyzes:

Area What it finds Apply support
Dart Unreachable top-level declarations and empty libraries Policy-controlled
Assets Exact asset references and assets unreachable from live Dart code Policy-controlled
Duplicates Byte-identical files using SHA-256 Review only

Installation #

Flutter Pruner requires Dart SDK 3.9 or newer.

macOS #

dart pub global activate flutter_pruner

Add Pub's executable directory to ~/.zshrc:

export PATH="$HOME/.pub-cache/bin:$PATH"

Then reload the shell with source ~/.zshrc.

Ubuntu #

dart pub global activate flutter_pruner

Add Pub's executable directory to ~/.bashrc:

export PATH="$HOME/.pub-cache/bin:$PATH"

Then reload the shell with source ~/.bashrc.

Windows #

Run in PowerShell:

dart pub global activate flutter_pruner

Open Environment Variables, edit the user Path, and add:

%LOCALAPPDATA%\Pub\Cache\bin

Upgrade and uninstall #

Upgrade:

dart pub global activate flutter_pruner

Uninstall:

dart pub global deactivate flutter_pruner

Quick start #

Run the workflow from your Flutter or Dart project root:

# 1. Detect the project and create .flutter_pruner/config.yaml
flutter_pruner init

# 2. Analyze without changing source files or assets
flutter_pruner scan

# 3. For actionable application/package-internal configs, preview the plan
flutter_pruner apply --dry-run

# 4. Apply findings authorized by the configured mode
flutter_pruner apply

The init wizard asks about project type, build targets, public entrypoints and verification commands. It never marks target coverage complete by inference; that is an explicit project-owner assertion.

To work from another directory, add --project path/to/project to any command.

Choose the right analysis mode #

The mode defines the analysis boundary and apply policy. It is not a shortcut for increasing confidence.

Mode Use when Result and actionability
application The project owns the complete application boundary Complete hard gates can produce SAFE; apply selects only SAFE
package Unknown applications or packages may consume the public API All non-protected findings are capped at REVIEW; apply and dry-run are disabled
package-internal You intentionally want to clean only the local package boundary Private local findings can be SAFE; external-consumer candidates may be HIGH only when that is their sole risk

Reusable and hybrid projects are conservatively detected as package. Choose package-internal explicitly when external consumers are intentionally outside the scan:

flutter_pruner init --type package-internal

Both package modes require at least one public_entrypoints entry. Public exports and their dependency closure remain reachable, so an exported API is not reported unused merely because the package does not call it internally.

A generated package configuration contains the explicit boundary:

analysis:
  mode: package-internal
  public_entrypoints: [lib/my_package.dart]

init writes the full target-matrix section and verification policy around it.

Every package-internal run warns that external consumers were not scanned. A mutation containing eligible HIGH findings asks for [y/N]; CI can accept that one specific risk with flutter_pruner apply --yes. The flag does not bypass coverage, planning, verification or quarantine.

Understand the confidence tiers #

Tier Meaning
SAFE All hard gates passed and no manual risk remains
HIGH All hard gates passed and exactly one known manual risk remains
REVIEW Evidence is incomplete, ambiguous, unsupported or has multiple risks
PROTECTED Framework naming rules or dependency ownership forbid removal

PROTECTED always wins. Conditional imports/exports, unresolved or dynamic analysis, generated-code uncertainty, duplicate groups and unsupported editor actions stay non-actionable rather than being silently promoted.

See the confidence model for the complete policy.

Preview, apply and rollback #

Apply takes a project-local operation lock shared with rollback and quarantine maintenance. Before mutation it rejects incomplete or non-terminal historical quarantines, captures the verifier baseline, and builds a dependency-closed plan. The plan may use internal atomic units, but the command is all-or-nothing: if any unit, rescan, convergence check, canonical report, or recovery check fails, it restores every mutation from this run to the original baseline and verifies that recovery.

Use repeatable --finding-id values when the reviewed scope is smaller than the full eligible set:

flutter_pruner apply --dry-run \
  --finding-id 'dart:my_app/lib/example.dart#_unusedHelper'
flutter_pruner apply \
  --finding-id 'dart:my_app/lib/example.dart#_unusedHelper'

IDs are exact and case-sensitive. The complete requested batch must exist, be apply-eligible, and already be dependency-closed; the planner never adds an unrequested logical finding. An unknown, non-actionable, or blocked requested ID stops the whole batch before verification or quarantine creation (exit 2). Empty or duplicate CLI values are usage errors (exit 64). Every rescan and the final convergence check remain bound to the same allowlist, while unselected findings stay visible in the report.

recoveryRequired means that restoration could not be proven (for example, a timed-out mutation process could not be confirmed stopped). Stop using the project with this tool, inspect the quarantine and report, and recover the project before another mutating command. A successfully recovered failed run does not retain earlier transactions.

To restore an applied run:

flutter_pruner quarantine list
flutter_pruner rollback <run-id>

Quarantine lives under .flutter_pruner/quarantine/<run-id>. Its manifest is a revisioned, checksummed journal with staged recovery for interrupted replacement; ambiguous or corrupt journal state blocks a new apply. Add --clean to the rollback command only when the restored quarantine should also be removed.

For a regular-file edit, the original source path is renamed into quarantine before a separately staged candidate is published with no-replace semantics. Restore uses the same preserve-then-no-replace pattern: a concurrent recreation of the project path is retained and the run becomes recoveryRequired instead of overwriting it. This protects against observed path races; it is not a claim of crash-durable filesystem transactions (see the boundaries below).

The rollback contract for a regular file is its captured bytes and, where the platform exposes them, POSIX permission bits. It does not preserve extended attributes, ACLs, ownership (uid/gid), or hard-link topology. Use version control or a filesystem backup when those metadata matter.

Reports #

Every completed scan and handled apply outcome receives a unique report below .flutter_pruner/reports/:

scan-<run-id>.html
apply-<run-id>.html

No format or output option is required for the default searchable offline HTML report. The terminal always shows the human summary and saved path. Export JSON for CI or other machine consumers when needed:

flutter_pruner scan --format json
flutter_pruner apply --dry-run --report-format json

Use --output for scan or --report-output for apply only when CI needs a stable artifact path. Mutating apply always keeps canonical JSON alongside the quarantine, even when HTML is selected.

JSON schema v3 records coverage, blockers, findings, manual risks, verification and transaction outcomes. See structured run reports for the schema, HTML features and CI selectors.

Important boundaries #

  • scan never changes project sources or assets, but it does write its report under tool-owned .flutter_pruner/ state.
  • target_matrix.complete: true means the owner has declared every supported platform, flavor, entrypoint and Dart-define combination.
  • package-internal proves only the local boundary; external apps may still use public symbols, deep imports or package assets.
  • Built-in callback-handle boundaries cover PluginUtilities.getCallbackHandle, Isolate.spawn, and Workmanager.initialize; custom runtime registries must be modeled by a future adapter or protected by project policy.
  • Asset paths assembled only by custom runtime code remain a trust boundary. Known dynamic or unresolved asset usage blocks affected findings, but the tool cannot prove that an unmodeled runtime API has no consumers.
  • Configured verification and import cleanup run as argv-only subprocesses with deadlines and bounded captured output. An unconfirmed detached process tree produces recoveryRequired; Windows termination behavior is covered by the hosted release CI job.
  • A subprocess that deliberately detaches before it can be observed remains a trust boundary; do not configure mutation-capable cleanup through such a tool.
  • The local filesystem contract does not currently include directory fsync, renameat2 durability semantics, or a guarantee against a microscopic open-file-descriptor append concurrent with path-level displacement. Treat a power loss or an adversarial writer during that window as requiring manual recovery, not as a proven atomic rollback.
  • Rollback is not a blanket metadata restore: for regular files it restores captured bytes and POSIX permission bits where available, not xattrs, ACLs, uid/gid, or hard-link topology.
  • Source-byte measurements are not release binary-size claims.

BuildTarget and TargetMatrix are public v1 Dart APIs. Their defensive immutable snapshot behavior is intentional; source-breaking changes require a future major release.

The public reporting and adapter-presentation DTOs likewise defensively snapshot collection arguments, so collection-bearing constructors are intentionally non-const. VerificationPolicy is exported for API callers. Treat AdapterRegistry.builtIn as read-only at runtime; a custom adapter set is passed explicitly to AdapterRegistry.resolve(adapters: [...]), while changes to the built-ins belong in the registry initializer. These public API contracts follow Semantic Versioning; source-breaking changes require a future major release.

When upgrading older configs, remove analysis.root_coverage. The only accepted mode values are application, package and package-internal; the old workspace, --safe and --high interfaces are intentionally rejected.

Documentation #

Document Start here for
Architecture Scan flow, layers and apply transactions
Graph model Nodes, roots, reachability and blockers
Confidence model Safety gates and confidence tiers
Run reports JSON v3, HTML and CI selectors
Verified Flutter facts Framework assumptions and primary sources
Contributor guide Development setup and pull requests
Adapter guide Adding a new analyzer

See ROADMAP.md for planned work. Adapters are the main extension point; the adapter guide includes a copyable template.

License #

MIT — see LICENSE.

7
likes
0
points
424
downloads

Publisher

verified publishernhanlee.dev

Weekly Downloads

Semantic auditor for Flutter/Dart projects. Finds unused assets, duplicate files and unreachable declarations using the Dart analyzer, with confidence tiers and a reversible apply workflow.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

analyzer, args, collection, crypto, glob, logging, path, pubspec_parse, yaml

More

Packages that depend on flutter_pruner