From 5f29dfebf7a4cfcff5645ee8a7b48af0c492325b Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 28 Aug 2026 14:46:05 +0200 Subject: [PATCH] Latest stable dependencies, and the gate that keeps them there Three git-tag pins move (crdt_sync_dart v0.10.0 -> v0.11.1, design_system v0.1.0 -> v0.2.1, github_device_auth v0.3.0 -> v0.3.1) and every caret in the pubspec becomes an exact pin, flutter_secure_storage 10.3.1 -> 11.0.0 being the one that matters. That version demands compileSdk 37, which needs AGP > 9.1.0, which needs Gradle >= 9.5.0, so those three move in this commit too. **mcp 1.x -> 2.1.1 in the MCP subproject is a real breaking change.** FastMCP was renamed MCPServer and moved to `mcp.server.mcpserver`; the decorator and `run()` API are unchanged, so the migration is the import and the constructor. Verified by driving the server over stdio: `initialize` and `tools/list` both answer, all three tools still enumerate. 24 tests at 100% coverage. `very_good_analysis` 10.3 adds a `cascade_invocations` finding on the port-in-use message in bin/todo_desktop.dart. Fixed, not suppressed. 300 Dart tests green, `flutter analyze` clean, release APK builds. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK --- .fvmrc | 3 + .github/workflows/dependency-freshness.yml | 57 +++++++++++++++++++ .gitignore | 3 + .pre-commit-config.yaml | 21 +++++++ android/app/build.gradle.kts | 7 ++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle.kts | 2 +- bin/todo_desktop.dart | 11 ++-- mcp/pyproject.toml | 2 +- mcp/todo_mcp/__init__.py | 2 +- mcp/todo_mcp/_mcp.py | 6 +- pubspec.lock | 48 ++++++++-------- pubspec.yaml | 50 ++++++++-------- scripts/check_dependency_freshness.sh | 34 +++++++++++ 14 files changed, 187 insertions(+), 61 deletions(-) create mode 100644 .fvmrc create mode 100644 .github/workflows/dependency-freshness.yml create mode 100755 scripts/check_dependency_freshness.sh diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 0000000..16a96ef --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.47.2" +} diff --git a/.github/workflows/dependency-freshness.yml b/.github/workflows/dependency-freshness.yml new file mode 100644 index 0000000..e3f46ac --- /dev/null +++ b/.github/workflows/dependency-freshness.yml @@ -0,0 +1,57 @@ +name: dependency freshness + +# Every dependency on its ecosystem's newest STABLE release, enforced on push +# as well as at commit time. The local pre-commit hook only sees the manifests +# in a commit and can be skipped entirely by a fresh clone that never ran +# `pre-commit install`; this runs `--all` over the whole repo. +# +# `--strict` is CI-only. Offline the gate degrades to its cached answer and +# passes, because `--no-verify` is banned locally and a hook that hard-failed +# on a flaky connection would leave no way to commit. A runner is always +# online, so here an undeterminable answer is a real failure. +# +# Expect this to go red for reasons this repo's commit did not cause: an +# upstream release overnight is enough. That is the chosen behaviour -- the +# alternative is a gate that only notices staleness when someone remembers to +# look. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + dependency-freshness: + name: dependencies on newest stable + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # The gate lives in the shared utils repo so every repo resolves the + # same "latest stable" the same way. Checking it out beats vendoring a + # copy, which is exactly how a gate and its survey start disagreeing. + - name: Check out the shared gate + uses: actions/checkout@v4 + with: + repository: kuhyx/utils + path: .utils + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install the gate's two runtime dependencies + run: python -m pip install --upgrade pyyaml packaging + + - name: Cache registry lookups + uses: actions/cache@v4 + with: + path: ~/.cache/dep-freshness + key: dep-freshness-${{ hashFiles('**/pubspec.yaml', '**/pyproject.toml', + '**/requirements*.txt', '**/package.json', '**/Cargo.toml', '**/go.mod') }} + restore-keys: dep-freshness- + + - name: Every dependency is on newest stable + run: bash .utils/scripts/check_dependency_freshness.sh --all --strict diff --git a/.gitignore b/.gitignore index db5b834..4a718a2 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ todo-backlog.md # Android Gradle build output /android/build/ + +# pytest-cov scratch file from the mcp/ subproject. +.coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 036d138..f105ada 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,3 +60,24 @@ repos: entry: scripts/check_md_naming.sh language: system files: \.(md|markdown)$ + + # Every dependency on its ecosystem's newest STABLE release, exact-pinned + # so the question is mechanically answerable at all. + # + # `files:` is what keeps this cheap: a commit touching only source makes + # ZERO network requests, and manifest commits mostly hit a 6h disk cache. + # + # Runs WITHOUT --strict on purpose. Offline it degrades to the cached + # answer and passes with a loud banner; `--no-verify` is banned here, so + # a hook that hard-failed on a flaky connection would leave no way to + # commit at all. CI, always online, is the strict authority. + - id: dependency-freshness + name: dependencies on newest stable + entry: scripts/check_dependency_freshness.sh + language: system + pass_filenames: true + files: >- + (pubspec\.(yaml|lock)|pyproject\.toml|requirements.*\.txt|uv\.lock + |package\.json|package-lock\.json|pnpm-lock\.yaml + |Cargo\.(toml|lock)|go\.(mod|sum) + |\.fvmrc|\.nvmrc|\.python-version)$ diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 13a941b..8803db7 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -18,7 +18,12 @@ val keystoreProperties = Properties().apply { android { namespace = "dev.kuhy.todo" - compileSdk = flutter.compileSdkVersion + // Pinned above `flutter.compileSdkVersion` (36 on Flutter 3.47.2): + // flutter_secure_storage 11.0.0, which arrives with crdt_sync_flutter + // v0.3.1, publishes AAR metadata demanding API 37 or later. That in + // turn needs AGP > 9.1.0, which needs Gradle >= 9.5.0 -- see + // settings.gradle.kts and gradle-wrapper.properties. + compileSdk = 37 ndkVersion = flutter.ndkVersion compileOptions { diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 2d428bf..ffcd530 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index c21f0c5..c00b1b8 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,7 +19,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "9.0.1" apply false + id("com.android.application") version "9.3.2" apply false id("org.jetbrains.kotlin.android") version "2.3.20" apply false } diff --git a/bin/todo_desktop.dart b/bin/todo_desktop.dart index 693e3a3..bf01118 100644 --- a/bin/todo_desktop.dart +++ b/bin/todo_desktop.dart @@ -116,11 +116,12 @@ Future _recoverPort( final guard = PortGuard(profileDir: profileDir(home)); switch (await guard.resolve(_port)) { case AbortWithOwner(:final description): - stderr.writeln('port $_port is already in use by $description.'); - stderr.writeln( - 'todo needs this exact port — its notes and GitHub token are stored ' - 'per origin. Free the port, then run todo again.', - ); + stderr + ..writeln('port $_port is already in use by $description.') + ..writeln( + 'todo needs this exact port — its notes and GitHub token are stored ' + 'per origin. Free the port, then run todo again.', + ); exit(1); case AttachToExisting(:final pid): // Re-running the browser command opens a second window rather than diff --git a/mcp/pyproject.toml b/mcp/pyproject.toml index 75c12c5..fb67ea2 100644 --- a/mcp/pyproject.toml +++ b/mcp/pyproject.toml @@ -7,7 +7,7 @@ dependencies = [ # The MCP SDK is the only runtime dependency: FastMCP powers the stdio # server. It lives in a dedicated venv (see scripts/setup_mcp.sh / # ../.mcp.json), kept entirely separate from the Dart/Flutter app. - "mcp>=1.9", + "mcp==2.1.1", ] # Explicit package discovery so ``pip install -e .`` picks up the package and diff --git a/mcp/todo_mcp/__init__.py b/mcp/todo_mcp/__init__.py index 45d451b..c498695 100644 --- a/mcp/todo_mcp/__init__.py +++ b/mcp/todo_mcp/__init__.py @@ -7,7 +7,7 @@ canonical, human-exportable "NotesMarkdown" snapshot — and exposes it to MCP clients (Claude Code and its subagents) as a handful of read-only tools. See :mod:`todo_mcp._backlog` for the parser and :mod:`todo_mcp._mcp` for the -FastMCP stdio server. +MCPServer stdio server. """ from __future__ import annotations diff --git a/mcp/todo_mcp/_mcp.py b/mcp/todo_mcp/_mcp.py index fda98b4..44d492a 100644 --- a/mcp/todo_mcp/_mcp.py +++ b/mcp/todo_mcp/_mcp.py @@ -30,7 +30,7 @@ from pathlib import Path import sys from typing import Any -from mcp.server.fastmcp import FastMCP +from mcp.server.mcpserver import MCPServer from todo_mcp._backlog import Note, parse_backlog @@ -43,7 +43,9 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) -mcp = FastMCP("todo") +# mcp 2.x renamed FastMCP to MCPServer; the decorator and run() API are +# unchanged, so only the import and the constructor moved. +mcp = MCPServer("todo") _DEFAULT_LIST_LIMIT = 100 diff --git a/pubspec.lock b/pubspec.lock index 4d861a8..007f4ca 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -101,11 +101,11 @@ packages: dependency: "direct main" description: path: crdt_sync_dart - ref: "crdt_sync_dart-v0.11.0" - resolved-ref: feaaf691037da1580fabc992819180d85a97c90a + ref: "crdt_sync_dart-v0.11.1" + resolved-ref: "978d32af3af66f0d6500c4c6c0c6df5370ebe9a2" url: "https://github.com/kuhyx/utils" source: git - version: "0.11.0" + version: "0.11.1" cross_file: dependency: transitive description: @@ -134,11 +134,11 @@ packages: dependency: "direct main" description: path: design_system - ref: "design_system-v0.1.0" - resolved-ref: e6c9af81e9ac3000d87d9168d3ef87ba2979258b + ref: "design_system-v0.2.1" + resolved-ref: "978d32af3af66f0d6500c4c6c0c6df5370ebe9a2" url: "https://github.com/kuhyx/utils" source: git - version: "0.1.0" + version: "0.2.1" dev_build: dependency: transitive description: @@ -268,18 +268,18 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "7686b1d6a29985dcbb808c59518226e603e3bfa7c0ddfd1a0d00e4cda77c868e" + sha256: "15e8c8fe269fdf7d469b23008ab3df521c8b826ed345820532364c31bdebace6" url: "https://pub.dev" source: hosted - version: "10.3.1" + version: "11.0.0" flutter_secure_storage_darwin: dependency: transitive description: name: flutter_secure_storage_darwin - sha256: "82329fa5cdf343773b1b6897dea959105a29f092454259edff92f9f6637e8149" + sha256: ac6d76a752de0cd738334eb4b21743fc4943f449f5b6e308f18838b048c02ac0 url: "https://pub.dev" source: hosted - version: "0.3.2" + version: "0.4.0" flutter_secure_storage_linux: dependency: transitive description: @@ -326,11 +326,11 @@ packages: dependency: "direct main" description: path: github_device_auth - ref: "github_device_auth-v0.3.0" - resolved-ref: "254e3b00c7f41a54c588ec32564017f96f328252" + ref: "github_device_auth-v0.3.1" + resolved-ref: "11c752fe6508117d4d2db1c0744090221d5f6cb8" url: "https://github.com/kuhyx/utils" source: git - version: "0.3.0" + version: "0.3.1" glob: dependency: transitive description: @@ -415,10 +415,10 @@ packages: dependency: "direct main" description: name: idb_shim - sha256: "3448298f244bc76a14aca71461eeab6add2b8a877930521c42c2e8b71b644590" + sha256: e36a748165663a165e1f292e9d85af6e3aa087f6e2291d16f86b2035103b2950 url: "https://pub.dev" source: hosted - version: "2.9.6+2" + version: "2.9.8" image: dependency: transitive description: @@ -671,18 +671,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "02180b01c1237b9706b663d9402b2cf2402b3407f48cce99cc19e3200f095b8a" + sha256: "34f00f9becd2743c1fb05363d624f9f70d37f7ccdcdda47450bc0b8c9d327b8c" url: "https://pub.dev" source: hosted - version: "13.2.1" + version: "13.3.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "7f7ae28cf400d13f811e297ff37742dba83b79e0a6f5dce14eec0248274e6ce9" + sha256: "365ef7379fc22507256adda3385152942ffce08935452bc972c2e52a0bebae41" url: "https://pub.dev" source: hosted - version: "7.1.0" + version: "7.2.0" shared_preferences: dependency: "direct main" description: @@ -764,10 +764,10 @@ packages: dependency: "direct main" description: name: sqflite_common_ffi - sha256: "5ccd38136edb9beb3213f6927775d52db70dfdadcdb28dad1f625ca9f2b9824f" + sha256: d5564f1308cafbf064be498f2beb1e993e742985a7cca9e2e228d6cbccd84a05 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.2+1" sqflite_common_ffi_web: dependency: transitive description: @@ -836,11 +836,11 @@ packages: dependency: "direct main" description: path: sync_settings_ui - ref: "sync_settings_ui-v0.4.0" - resolved-ref: "26d7507fff52cd005bbd713e0b8608dc03a017b2" + ref: "sync_settings_ui-v0.4.1" + resolved-ref: "51700aa89eaf1789ae98491aaa854c54d35b4170" url: "https://github.com/kuhyx/utils" source: git - version: "0.4.0" + version: "0.4.1" synchronized: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 291f1f4..850484b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.27+1 +version: 1.0.28+1 environment: sdk: ^3.12.2 @@ -33,47 +33,47 @@ dependencies: crdt_sync: git: url: https://github.com/kuhyx/utils - ref: crdt_sync_dart-v0.11.0 + ref: crdt_sync_dart-v0.11.1 path: crdt_sync_dart # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 + cupertino_icons: 1.0.9 # The shared palette, scales and components. Owns the tokens: there is # deliberately no local theme.dart to drift from the frozen table. design_system: git: url: https://github.com/kuhyx/utils - ref: design_system-v0.1.0 + ref: design_system-v0.2.1 path: design_system - file_selector: ^1.1.0 + file_selector: 1.1.0 flutter: sdk: flutter # Stores the GitHub sync token in the OS keystore (Android Keystore / # libsecret) instead of plaintext SharedPreferences. - flutter_secure_storage: ^10.3.1 + flutter_secure_storage: 11.0.0 github_device_auth: git: url: https://github.com/kuhyx/utils - ref: github_device_auth-v0.3.0 + ref: github_device_auth-v0.3.1 path: github_device_auth # One-tap sign-in for the sync account. Lives only in the app: `crdt_sync` # stays pure Dart so it keeps working on Linux desktop and headless, where # this plugin does not exist. See lib/sync/google_sign_in_backend.dart. - google_sign_in: ^7.2.0 - http: ^1.6.0 - idb_shim: ^2.9.6+2 - path: ^1.9.1 - path_provider: ^2.1.5 - share_plus: ^13.1.0 - shared_preferences: ^2.5.5 - sqflite_common_ffi: ^2.4.2 + google_sign_in: 7.2.0 + http: 1.6.0 + idb_shim: 2.9.8 + path: 1.9.1 + path_provider: 2.1.6 + share_plus: 13.3.0 + shared_preferences: 2.5.5 + sqflite_common_ffi: 2.4.2+1 # Retained only to read the legacy on-device DB during the one-time # migration in NoteRepository.open; remove once all devices are migrated. - sqlite_crdt: ^3.0.4 + sqlite_crdt: 3.0.4 # Shared "Sync settings" screen (Firebase + local backup), reused across # every kuhy app that syncs. GitHub mirror stays app-local (see @@ -82,30 +82,30 @@ dependencies: sync_settings_ui: git: url: https://github.com/kuhyx/utils - ref: sync_settings_ui-v0.4.0 + ref: sync_settings_ui-v0.4.1 path: sync_settings_ui - url_launcher: ^6.3.2 - uuid: ^4.5.3 + url_launcher: 6.3.2 + uuid: 4.6.0 dev_dependencies: # Virtual clock for unit-testing debounced timers (e.g. LocalBackup). - fake_async: ^1.3.3 + fake_async: 1.3.3 # Plugin platform interfaces, depended on directly so tests can swap in # fakes for the file picker and URL launcher (see settings_screen_test.dart). - file_selector_platform_interface: ^2.7.0 + file_selector_platform_interface: 2.7.0 # Generates the Android launcher icons from assets/icon/ (see # python_pkg/app_icons in testsAndMisc for the shared style). - flutter_launcher_icons: ^0.14.4 + flutter_launcher_icons: 0.14.4 flutter_test: sdk: flutter - plugin_platform_interface: ^2.1.8 - url_launcher_platform_interface: ^2.3.2 + plugin_platform_interface: 2.1.8 + url_launcher_platform_interface: 2.3.2 # Stricter than flutter_lints -- standardized across this ecosystem's # Flutter apps (see project memory: all Flutter apps use # very_good_analysis, not flutter_lints). - very_good_analysis: ^10.2.0 + very_good_analysis: ^10.3.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/scripts/check_dependency_freshness.sh b/scripts/check_dependency_freshness.sh new file mode 100755 index 0000000..5cb0d78 --- /dev/null +++ b/scripts/check_dependency_freshness.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# ============================================================================ +# Fail if any dependency in the commit is behind its ecosystem's newest stable. +# +# Thin delegate to the shared gate in ~/utils, which owns the registry +# adapters, the pre-release rules and the allowlist semantics. Copying that +# logic here is what lets one repo's idea of "current" drift from every other +# repo's -- so this script only locates the shared checker and forwards its +# arguments. +# +# Usage: +# scripts/check_dependency_freshness.sh [ ...] # pre-commit +# scripts/check_dependency_freshness.sh --all --strict # CI +# +# Exit: 0 current | 1 behind | 2 allowlist wrong | 3 undeterminable (offline) +# ============================================================================ + +set -euo pipefail + +readonly SHARED_GATE="${UTILS_ROOT:-$HOME/utils}/scripts/check_dependency_freshness.sh" + +main() { + if [[ ! -x "$SHARED_GATE" ]]; then + echo "Error: shared dependency-freshness gate not found at $SHARED_GATE" >&2 + echo " Clone github.com/kuhyx/utils to ~/utils, or set" >&2 + echo " UTILS_ROOT to where it lives." >&2 + exit 1 + fi + + exec bash "$SHARED_GATE" "$@" +} + +main "$@"