Dependency hygiene
Enforces pubspec/lock discipline — caret ranges in pubspec.yaml with a committed pubspec.lock as the only pin, a separately-recorded SDK version string, a version-pinned very_good_analysis include (whose missing file fails default analyze, or silently drops the ruleset where warnings are non-fatal), transitive-tree auditing before adding a package, a dependency gate that refuses network/telemetry/crash/ads/heavy-transitive deps by policy, and vendoring any bus-factor-1 native plugin behind an interface into third_party/. Use when running dart pub add/get/upgrade/outdated/deps, editing pubspec.yaml or pubspec.lock, bumping the Flutter/Dart SDK, choosing or rejecting a new dependency, removing a package, or auditing what a dependency drags in.From its SKILL.md
npx -y skills add zakariaf/Flutter-Skills --skill dependency-hygieneAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 22 days oldThe repository was created 22 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
11.9 KB, ~2.7k tokens by cl100k_base, as published. Nobody here has run it
Dependency hygiene
Every dependency is a permanent liability someone else may have to service. Optimise for resolves and builds years from now, not for latest. This skill governs pubspec/lock mechanics, the gate a new package must pass, and the escape hatch when a critical package rots.
Read the reference for the task at hand:
references/dependency-gate-and-audit.md— the refuse/accept gate, transitive auditing, licence recording, upgrade discipline.references/sdk-pin-and-lint-include.md— recording the SDK version and the silent-lint-disable trap on any SDK bump.references/vendoring-behind-an-interface.md— vendoring a bus-factor-1 native plugin intothird_party/without touchinglib/.
Run scripts/audit-deps.sh before a PR that changes pubspec.yaml.
Non-negotiable rules
- Caret ranges in
pubspec.yaml, exact pins only inpubspec.lock.drift: ^2.31.0, neverdrift: 2.31.0. The lock pins; ranges only keep resolution solvable. Exact pins in a pubspec manufacture unsolvable conflicts on the next SDK bump and buy nothing the lock is not already delivering. - Commit
pubspec.lock. This is an application, not a package — the asymmetry that flips the rule. The default Dart.gitignoretemplate listspubspec.lock; delete that line. The committed lock is the only thing that makes a stranger'sgit cloneresolve the exact versions that were tested on a real device. - A
pubspec.yamldiff without itspubspec.lockdelta is incomplete. Runflutter pub getand stage the lock in the same commit — add, upgrade, and remove alike. - Record the SDK version separately from the tool. Keep
environment: sdk:a real range sopubcan solve; pin the exact tested Flutter version in a committed record CI reads (see reference). The record is what a stranger and CI read; do not confuse a version-manager tool with the file. - The lint include filename is coupled to the resolved SDK. A version-pinned
include:that names a file absent from the resolved linter package emitsinclude_file_not_found— fatal to defaultdart analyze/flutter analyze, so standard CI catches it. It only goes silent-and-green where warnings are made non-fatal, and even then it is your ruleset's added/promoted rules that stop applying, not the analyzer's built-ins. Verify it after any SDK bump (see reference). - Audit the transitive tree before adding, not the pubspec. The second hop is exactly where a banned SDK arrives. Run
scripts/audit-deps.sh(ordart pub deps --json+ the audit script) before committing a new dependency. - Refuse by policy anything that opens a network path, reports crashes/usage, drags in a telemetry core, or collects device identifiers for an unshipped feature — directly or transitively. Green CI is not evidence a native capability works. See the gate reference.
- Grep
lib/after removing a dependency. A package deleted frompubspec.yamlbut still transitively resolvable keeps compiling today and breaks on the clone that matters. The analyzer catches an unresolved import, not one that resolves by accident. - Wrap a bus-factor-1 native plugin behind an interface now, vendor later. The interface is cheap insurance; pre-emptive vendoring is a maintenance burden against a break that has not happened. See the vendoring reference.
Ranges in pubspec, pins in the lock
| Do | Never |
|---|---|
Caret range: drift: ^2.31.0 | Exact pin: drift: 2.31.0 |
environment: sdk: ^3.6.0 — a real range | An exact sdk: version |
Commit pubspec.lock | Gitignore pubspec.lock |
The lock is authoritative. Ranges keep the solver able to move on an SDK upgrade; the lock keeps everyone on the versions that were actually verified.
The gate every new dependency must pass
Refuse a package if it — or anything in its transitive tree — does any of:
- Opens a network path you did not choose (
http,dio, sockets, gRPC) when the product promise is no-network or offline-first. - Reports crashes or usage (Crashlytics, Sentry, any analytics or attribution SDK) against a no-telemetry policy.
- Drags in a telemetry/analytics core transitively — a crash SDK's core can worsen the store privacy label even when you never call it.
- Collects device identifiers for a feature you do not ship.
- Requires an
--enable-experimentflag — an abandoned repo that needs an experiment to build stops building.
Also weigh, and usually refuse: any bus-factor-1 package not behind an interface; anything whose function is a few hundred lines of first-party Dart; anything that only exists to save typing. Record the licence — a permissive one (MIT, BSD, Apache-2.0) is a precondition for the vendoring escape hatch; a copyleft dependency removes that option. Full checklist in references/dependency-gate-and-audit.md.
Auditing the transitive tree
dart pub add --dry-run <package> # see what would resolve, resolve nothing
dart pub deps --json > /tmp/deps.json
scripts/audit-deps.sh # walks the resolved set, flags banned patterns
The audit walks the full resolved graph, matches each name against a policy pattern list, marks every hit direct or TRANSITIVE, and separates APK-shipping deps from build/test-only ones (a banned package reachable only from dev_dependencies never reaches the binary and is not a shipping defect). Exit 1 means refuse the dependency or find one that does not pull those in. To learn who introduced a package: dart pub deps | grep -B4 <name>.
Edit the BANNED/ALLOW lists at the top of scripts/audit_deps.py to match your policy; every ALLOW entry needs a written justification beside it.
Upgrading
Run dart pub outdated on your own schedule; there is no bot. Dependabot has never supported pub; Renovate raises PRs a solo repo has no workflow for. Keep their one useful idea by hand: never blind-upgrade a native plugin your app's core capability depends on.
Green CI is not evidence of a native capability. No CI job on a headless emulator can prove the app plays audio, reads a sensor, or talks to a platform channel end-to-end — integration_test can assert a channel call was issued, nothing more. After any bump to a capability-critical native plugin or the SDK itself, run a manual pass on a real device. After any upgrade touching a codegen package (drift, freezed, json_serializable), regenerate and commit — stale generated code is caught by CI, not the analyzer (see run-codegen).
Vendoring a bus-factor-1 plugin
A single-maintainer native plugin whose failure means the app cannot build is the one dependency worth pre-planning for. The mitigation is a thin interface, authored on day one, so vendoring never touches lib/:
// A port the app depends on; the plugin lives behind it. The `Gateway` suffix
// marks a thin wrapper over a SPECIFIC plugin (here flutter_secure_storage) —
// `naming-conventions` owns that Service-vs-Gateway distinction.
abstract interface class SecureStorageGateway {
Future<void> write(String key, String value);
Future<String?> read(String key);
}
Only when the plugin actually breaks — stops building against a Flutter release, or ships a regression upstream will not fix — clone it at the last-good tag into third_party/<plugin>/, point the pubspec at path:, record the SHA and every changed line in a VENDORED.md, confirm the licence permits redistribution, and patch, don't refactor. Full procedure and triggers in references/vendoring-behind-an-interface.md. See examples/vendored_plugin_behind_interface.dart.
Removing a dependency
Delete the pubspec entry, run flutter pub get, commit the pubspec.lock delta in the same commit — then grep -r "package:<name>" lib/. A transitively-resolvable import compiles today and breaks the clone that matters.
When multi-package (workspace)
In a Dart pub workspace / monorepo, each package keeps its own pubspec.yaml with ranges, but the workspace resolves one shared lock at the root — audit and commit that root lock. Run the audit against the whole workspace's resolved tree, not one package's. A single package's pubspec cannot show what a sibling drags into the shared resolution. See codegen-and-toolchain for workspace toolchain mechanics.
Anti-patterns
- Exact pin in
pubspec.yaml. Turns the next SDK upgrade into an unsolvable-conflict debugging session; the lock already pins. - Gitignoring
pubspec.lockin an app. A fresh clone resolves whateverpubfeels like today, not what was tested. - Editing pubspec without committing the lock delta. The next machine resolves a different graph.
- Reading
pubspec.yamlto decide if a dep is safe. The banned SDK is on the second hop, invisible there. - Trusting green CI for a native capability. The emulator has no voice engine / real sensor; green proves the Dart compiled, not that the feature works.
- Pre-emptively vendoring a healthy plugin. You take on the maintenance burden before any break; the interface is the cheap insurance, not the fork.
- Vendoring by refactoring. Every line you touch in
third_party/is a line you own forever — patch the break and stop. - Adding a bus-factor-1 package with no interface. When it rots you have no seam to vendor behind.
Definition of done
- New/changed deps use caret ranges; no exact pins in any
pubspec.yaml. -
pubspec.lockdelta is staged in the same commit as thepubspec.yamlchange. -
scripts/audit-deps.shexits 0 (or every hit has a justifiedALLOWentry). - On an SDK bump: the version record is updated and the pinned lint
include:file is confirmed present in the resolved package (references/sdk-pin-and-lint-include.md). - Licence recorded for any new dependency; permissive if it is a vendoring candidate.
- Capability-critical native plugin sits behind an interface; a real-device pass was run after its bump.
- After a removal:
grep -r "package:<name>" lib/is empty.
Related skills
codegen-and-toolchain— workspace linking, SDK pinning at the toolchain level, generated-code commit-vs-gitignore.run-codegen— the deterministicbuild_runnerpass to run after upgrading a codegen package.lint-and-style-config— the strictanalysis_options.yamlthe version-pinnedinclude:feeds.service-boundary-and-native— the injectable-interface-per-side-effect pattern the vendoring seam relies on.naming-conventions— owns theService-vs-Gatewayboundary-suffix rule; a plugin wrapper likeSecureStorageGatewaytakes theGatewaysuffix.ci-pipeline-and-gates— wiring the audit and lock-freshness checks into CI.
References
- Dart — Package dependencies & version constraints: https://dart.dev/tools/pub/dependencies
- Dart —
pubspec.lockand glossary: https://dart.dev/tools/pub/glossary#lockfile - Dart —
dart pub deps: https://dart.dev/tools/pub/cmd/pub-deps - Dart —
dart pub outdated: https://dart.dev/tools/pub/cmd/pub-outdated - Dart — Pub workspaces: https://dart.dev/tools/pub/workspaces
- Flutter — Adding plugin packages / path dependencies: https://docs.flutter.dev/packages-and-plugins/using-packages
What ships with it: 6 files
25.2 KB alongside SKILL.md, 2 of them executable
examples/
references/
scripts/
- audit_deps.pyruns5.1 KB
- audit-deps.shruns3.9 KB