`.fvmrc` is a declaration nothing in this fleet reads. `subosito/flutter-action`
takes its own `flutter-version:`, and that pin is what the runner installs, so
the two drift in silence and the failure mode is the worst kind: green
locally, red in CI, with no diff to explain it.
kuhylog is the live case. Its runner is on Flutter **3.44.9**, which predates
the `@awaitNotRequired` annotation, so `unawaited(Navigator.push(...))` is
`unnecessary_unawaited` on 3.47.2 here and `discarded_futures` there -- the two
SDKs demand opposite code and CI went red on a commit that is clean locally.
The fleet is spread across 3.44.6, 3.44.8, 3.47.0 and 3.47.1 with no one
tracking it.
So a workflow's `flutter-version:` / `node-version:` is now a dependency like
any other, judged against the same latest stable. That needs no separate
"these must agree" rule: two values both required to equal latest already
agree. A channel name and a `${{ }}` expression are skipped -- one is not a
version, the other resolves at run time and guessing is worse than silence.
Found real drift on its first run: both TypeScript workflows here pinned
`node-version: 24`, a floating major that lets two runs of the same commit
install different Node. Now 24.20.0.
321 tests, 100% branch coverage on every production module.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK
The gate is three files per repo -- a delegate, a pre-commit hook entry and a
CI workflow -- and Phase 3 needs them in sixteen more repos. Hand-replication
is precisely how the 250-line cap ended up shipping under four different hook
ids, two of which were independent reimplementations rather than delegates, so
the copy is now a script and the templates have exactly one home.
scripts/install_dependency_freshness_gate.sh <repo> [--check] [--fvm]
Idempotent, and it rewrites a copy that has drifted from the template rather
than leaving it. `--check` reports without writing. `--fvm` pins the Flutter
SDK, and only in a repo that actually holds a pubspec.yaml -- an .fvmrc in a
Python repo declares a toolchain nothing there builds with. An unresolvable
SDK version leaves the pin alone and exits 1: guessing would pin every repo to
a number nobody chose.
The pre-commit block is APPENDED rather than parsed and re-emitted. Every one
of these configs carries comments explaining why its hooks exist, and a YAML
round-trip through the standard library drops all of them.
306 tests, 100% branch coverage on every production module.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK