dart-decimate
v0.0.14
Published
Rust-native codebase intelligence for Dart and Flutter module graphs.
Readme
Dart Decimate

Find dead Dart code, circular dependencies, duplicated code, complex functions, dependency problems, risky Flutter wiring, and PR risk fast.
Dart Decimate is a Rust-native codebase intelligence tool for Dart and Flutter. It looks at your repo as a graph:
- Dart files are nodes.
import,export,part,part of, andlibrary augmentare edges.- The report tells you what is unused, risky, duplicated, tangled, or hard to maintain.
It is not a formatter. It is not a replacement for dart analyze. It is not a
Flutter style guide. It does not enforce opinions like "all providers must use
Riverpod code generation."
It answers practical questions:
- What code can probably be deleted?
- What files depend on each other in a circle?
- What functions are too complex?
- What code was copied around?
- What dependency is unused or missing from
pubspec.yaml? - What changed code is risky before a PR lands?
- What should an AI coding agent inspect before making a fix?
Start Here
Inside any Dart or Flutter project, run this:
npx --yes dart-decimate human .That is the easiest command. It checks the whole repo for dead code, circular dependencies, duplicated code, complex functions, dependency hygiene, architecture drift, Flutter graph issues, security candidates, and PR-risk signals.
To open the same report in your browser:
npx --yes dart-decimate html .To review only files changed from another branch or ref:
npx --yes dart-decimate html . --compare origin/main--compare REF aliases --changed-since REF. If the ref is not found,
Dart Decimate suggests similar local or remote branches.
For JSON output that agents and CI can parse:
npx --yes dart-decimate json .If the human report says FAIL, or JSON says "verdict": "fail", Dart Decimate
worked. It means it found error-level issues. It does not mean the tool crashed.
Exit codes:
0: no error-level findings1: Dart Decimate found issues2: command, config, or runtime error8: security gate found new review-required candidates
Install
You do not need to install anything permanently. Use npx:
npx --yes dart-decimate human .Add this to package.json if you want a short project command:
{
"scripts": {
"dart-decimate": "dart-decimate json ."
},
"devDependencies": {
"dart-decimate": "^0.0.14"
}
}Then run:
npm run dart-decimateIf you prefer Cargo:
cargo install --git https://github.com/sgaabdu4/dart-decimateCargo installs dart-decimate and dart-decimate-mcp into ~/.cargo/bin.
Fish:
fish_add_path ~/.cargo/binBash or Zsh:
export PATH="$HOME/.cargo/bin:$PATH"From a local checkout:
git clone https://github.com/sgaabdu4/dart-decimate.git
cd dart-decimate
cargo install --path . --forceThen run it in your app:
cd /path/to/flutter_or_dart_repo
dart-decimate check . --format jsonnpx
The npm package name is dart-decimate. These commands do not require a global
install.
Check everything:
npx --yes dart-decimate human .Machine-readable JSON:
npx --yes dart-decimate json .Open the HTML report in your browser:
npx --yes dart-decimate html .The html shortcut opens the report by default. On report commands, use
--format html to print static HTML or --open to write a private temporary
file and open it in the default browser.
Generated finding reports group issues by type in numbered, collapsible sections. Search narrows visible findings, and type filters open the selected group.
Human terminal reports strip control characters from user-derived paths and messages; HTML reports escape user-derived content.
Print the HTML report instead:
npx --yes dart-decimate html . --stdout > dart-decimate-report.htmlChanged files only: npx --yes dart-decimate html . --compare origin/main.
--compare REF aliases --changed-since REF and suggests similar branches when
the ref is not found.
To run the GitHub version directly:
npx --yes --package github:sgaabdu4/dart-decimate dart-decimate check . --format jsonWhat Dart Decimate Looks For
1. Dead Code
Dead code is code that is not reachable from your entry points.
Dart Decimate finds:
- dead Dart files
- unused public exports
- unused type aliases
- unused enum values
- unused private class members
- unrendered Flutter widgets
- missing entry points
- stale
dart-decimate-ignorecomments
Default entry points are evaluated across the root and discovered local package
roots. They include public lib/ Dart files outside lib/src/, lib/main.dart,
direct bin/ scripts, and, outside production mode, direct test/,
integration_test/, test_driver/, tool/, scripts/, and pigeon/
scripts. Nested test/, integration_test/, and test_driver/ files with
main() are also roots, including files matching Patrol's configured
test_file_suffix. Git-ignored Dart files are excluded from source analysis.
Generated Dart companions, Flutter l10n outputs, and FlutterFire options are
protected from dead-file cleanup; generated-only cycles are also suppressed.
Unused-symbol checks count references from non-raw Dart string interpolation; raw strings and escaped dollars stay literal.
Useful commands:
dart-decimate dead-code . --entry lib/main.dart --format json
dart-decimate check . --unused-files --unused-exports --unused-deps --format json2. Complex Code
Cyclomatic complexity means "how many paths can this function take?"
Cognitive complexity means "how hard is this function to understand?"
CRAP score combines complexity with test coverage. A complex function with poor coverage gets a worse score.
Dart Decimate finds:
- high cyclomatic complexity
- high cognitive complexity
- high combined complexity
- high CRAP score
- coverage gaps
- low health score files
- hotspots
- refactoring targets
Useful commands:
dart-decimate health . --format json
dart-decimate health . --complexity-breakdown --top 10 --format json
dart-decimate health . --file-scores --hotspots --targets --format json3. Duplicated Code
Duplication means the same Dart code appears in more than one place.
Dart Decimate finds exact and semantic clone groups. Each clone group gets a stable
fingerprint like dup:abc12345, so agents can trace it before touching code.
Clone windows must meet both line and token thresholds; sparse duplicated blocks
can span more than --min-lines, and line_count reports the actual match.
Declaration-only abstract contracts are filtered, and copied local Pub package
mirrors are canonicalized before same-mirror matches are ignored.
Useful commands:
dart-decimate dupes . --format json
dart-decimate dupes . --mode semantic --min-lines 5 --format json
dart-decimate dupes . --threshold 5 --format json
dart-decimate trace-clone . --fingerprint dup:abc12345 --format json4. Circular Dependencies
A circular dependency means file A depends on file B, and B eventually depends back on A. Cycles make code harder to move, test, and delete.
Dart Decimate finds:
- circular dependencies
- re-export cycles
- import/export/part/augment targets that do not resolve
- invalid
part/part ofrelationships
Generated typed GoRouter route registry back-edges are warning-level only when the importing file uses real typed-route navigation helpers. Mixed cycles still emit error-level residual findings for unrelated edges, registry-to-registry imports, exports, or non-route registry API usage.
Useful commands:
dart-decimate cycles . --format json
dart-decimate check . --circular-deps --re-export-cycles --format json5. Architecture Drift
Architecture drift means code crosses boundaries it should not cross.
Example: lib/domain/ depending on lib/ui/.
Dart Decimate finds:
- boundary violations
- files outside configured boundary zones
- forbidden direct calls across boundaries
- policy-pack violations for banned imports, exports, and calls
Useful command:
dart-decimate check . \
--boundary lib/domain:lib/ui \
--boundary-coverage \
--format json6. Dependency Hygiene
Dependency hygiene means your imports and pubspec.yaml agree.
Dart Decimate finds:
- unused runtime dependencies
- unused dev dependencies
- runtime dependencies used only by tests
- imports missing from
pubspec.yaml - unused dependency overrides
- invalid dependency overrides
- imports into another package's private
lib/src - duplicate public API exports
Dart Decimate follows Pub package ownership when resolving package: imports,
using .dart_tool/package_config.json when present while keeping same-package
imports and owner-local path dependencies local. Workspace members and copied
nested packages with the same package name are resolved by owner. Non-Dart
tooling references in Flutter config files, including launcher and splash
config, workflow files, Makefiles, and tool/ scripts can count as dependency
usage. Envied annotations count envied_generator/build_runner, FlutterGen
configuration counts flutter_gen_runner, and Dart tests count the test
runner. Known generator-internal imports from generated Dart, such as
package:slang/generated.dart, are not reported as unlisted dependencies.
Useful commands:
dart-decimate trace-dependency . --dependency collection --format json
dart-decimate check . --unused-deps --unlisted-deps --private-src-imports --format json7. Flutter-Specific Graph Issues
Dart Decimate does not care which state-management style you use. It uses Flutter and Dart patterns only to avoid false positives and to find graph problems.
Dart Decimate finds:
- GoRouter route path/name collisions
- private Flutter widget classes
- top-level widget helper functions
- unused widget constructor parameters
- widget classes that are never constructed
- missing
context.mountedguards after awaited widget work
unused-widget-param counts normal field reads, transformed constructor
initializer reads, inherited field reads, forwarding helpers, and Dart object
patterns that destructure the widget class itself. unrendered-widget counts
widgets constructed through builder callbacks and subclass construction, while
type-only references do not count as rendering.
8. Security Candidates
These are review prompts, not proof of an exploit.
Dart Decimate finds candidates for:
- hardcoded secrets
- insecure HTTP transport
- TLS validation bypasses
- risky WebView settings
- process execution
- raw SQL
- Firebase client API keys in
FirebaseOptions - plain local storage of secret-like material
Firebase client API keys are warning-level by default because FlutterFire
generates client config. To make them fail a gate, set
"dart-decimate/security-firebase-api-key" = "error" in [rules]. Common
authentication copy such as password reset and password requirement text is
filtered before reporting hardcoded-secret candidates unless it is bound to a
secret-like name or contains a concrete token-like segment. OAuth authorization
and token endpoint URLs are excluded from secret candidates as public metadata,
while cleartext endpoint transport remains an insecure-transport candidate. Stripe secret-key names
and sk_test_/sk_live_ values remain review candidates even when they look
like placeholders. A fixed Process.start of Platform.resolvedExecutable
with fixed list arguments is not classified as shell command injection.
Useful commands:
dart-decimate security . --surface --format json
dart-decimate security . --ci --sarif-file dart-decimate-security.sarif
git diff --cached --unified=0 | dart-decimate security . --gate new --diff-stdin --format json
dart-decimate security . --gate newly-reachable --compare origin/main --format json--gate new keeps candidates on added lines. --gate newly-reachable keeps
reachable candidates affected by changed files. Both accept --compare REF,
--changed-since REF, --diff-file PATCH, or --diff-stdin.
9. PR Risk
Use this before merging changed code.
Dart Decimate reports:
- risk score
- pass / warn / fail risk level
- findings introduced by the PR
- findings that already existed
- risky changed files
Compile-time environment feature flags are warning-level only when every
occurrence is in a non-lib/ development, tooling, example, or test path, or in
lib/main_dev.dart, lib/main_debug.dart, lib/main_e2e.dart,
lib/main_test.dart, or lib/main_driver.dart. Other lib/ files, including
lib/dev_flags.dart, services, and screens, remain error-level. SDK/config flag
calls stay error-level by default.
Useful commands:
dart-decimate audit . --base origin/main --format json
dart-decimate audit . --base origin/main --gate new-only --format json10. Runtime Intelligence
Static analysis says what is connected. Runtime coverage says what actually ran.
Dart Decimate can read LCOV, V8, and Istanbul coverage data.
Useful commands:
dart-decimate health . --coverage coverage/lcov.info --coverage-gaps --max-crap 30 --format json
dart-decimate coverage analyze . --runtime-coverage coverage-final.json --format jsonHow To Read The Summary
Example:
{
"files": 466,
"edges": 1231,
"quality_score": 93,
"cycles": 2,
"code_duplications": 26,
"complex_functions": 13,
"dead_files": 11,
"findings": 125
}Plain English:
files: Dart files Dart Decimate parsededges: imports, exports, parts, and augments it resolvedquality_score: project health from0to100cycles: emitted circular dependency findings after policy classificationcode_duplications: duplicated code groupscomplex_functions: functions over the complexity limitsdead_files: files Dart Decimate thinks are unreachablefindings: total issues in the report
JSON For Agents
Use JSON when another tool or AI agent will read the result:
dart-decimate check . --format jsonEvery finding includes:
rule_idkindseveritypathlinecolumnsafe_to_delete- related
files - related dependency
edge, when available - suggested
actions
Example shape:
{
"schema_version": "dart-decimate.report.v1",
"kind": "combined",
"tool": "dart-decimate",
"command": "check",
"verdict": "fail",
"summary": {
"files": 466,
"edges": 1231,
"quality_score": 93,
"findings": 125
},
"findings": [],
"next_steps": []
}When grouped security findings hide additional occurrences, next_steps can
include review-security-surface, which reruns
dart-decimate security . --format json --surface.
If a JSON command fails before a report can be built, stdout still stays machine-readable:
{ "error": true, "message": "coverage analyze requires --runtime-coverage PATH", "exit_code": 2 }Fixes
Preview safe fixes:
dart-decimate fix . --format jsonApply confirmed safe fixes:
dart-decimate fix . --apply --confirm --format jsonSafe fixes are intentionally conservative. Dart Decimate can currently apply:
- simple dead-file deletion
- stale suppression removal
- one-line unused Pub dependency removal
- one-line unused top-level Dart declaration removal
Everything else stays review-only.
Watch Mode
Rerun checks while you work:
dart-decimate watch . --no-clearRun once and exit, useful for scripts:
dart-decimate watch . --once --format jsonMCP
Start the MCP server:
dart-decimate-mcpAgents can use it to inspect a project, trace files, trace symbols, inspect duplicates, review PR risk, read runtime coverage slices, preview fixes, and ask what is safest to do next.
fix_apply is the only mutating MCP tool and requires explicit yes: true.
Config
Dart Decimate reads config from:
.dart-decimaterc.dart-decimaterc.json.dart-decimaterc.jsoncdart-decimate.toml.dart-decimate.toml
Example:
[cli]
format = "json"
entry = ["lib/main.dart"]
production = true
[health]
max_cyclomatic = 20
max_cognitive = 15
coverage_gaps = true
fileScores = true
hotspots = true
targets = true
[dupes]
mode = "semantic"
min_tokens = 80
threshold = 5
[boundaries]
presets = ["layered"]
rules = ["lib/domain:lib/ui"]
[security]
surface = true
categories = ["hardcoded-secret", "firebase-api-key", "insecure-transport", "tls-bypass"]
[rules]
unused-files = "error"
unused-exports = "warn"
security-candidate = "warn"
"dart-decimate/security-firebase-api-key" = "error"Full Issue List
Run this for the live list:
dart-decimate schema --format json | jq .issue_typesCurrent issue types:
See docs/issue-types.md. Prefer the schema command when you need the installed binary's exact list.
CI
See docs/ci.md for CI checks, PR gates, hook setup, CI templates, and review-thread reconciliation.
Scope
Dart Decimate implements local Fallow-style codebase intelligence for Dart and Flutter.
Fallow features that are JS-specific or require hosted backends return clear unsupported JSON instead of pretending to work:
dart-decimate migrate --dry-run --format json
dart-decimate telemetry status --format json
dart-decimate license status --format jsonDevelopment
See docs/ci.md for the local verification commands and CI gates.
This repository forbids unsafe_code.
Release Flow
Current version: 0.0.14.
After the first public release, changes should go through pull requests. Every
PR to main must bump both Cargo.toml and package.json above the base
branch and to an unpublished npm version.
To release a new version:
- Update both
Cargo.tomlandpackage.jsonto the same unpublished version. - Open a PR.
- Let CI pass.
- Merge to
main. - GitHub Actions publishes
dart-decimateto npm, creates tagvX.Y.Z, and creates the GitHub release.
Release reruns for the same commit may update GitHub release assets. If the npm package already exists for that commit, the publish step is skipped; a reused tag or npm version from another commit fails.
Local hooks block direct pushes to main, fetch the configured base ref when
needed, and run the same version, release, lint, Fallow, package, and test gates
as the local no-mistakes configuration.
License
Licensed under the MIT License. See LICENSE.
