diff --git a/.fvmrc b/.fvmrc index 827b556..9bc5a0b 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.47.5" + "flutter": "3.47.6" } diff --git a/.github/workflows/release-apk.yml b/.github/workflows/release-apk.yml index 6fd992e..b5a4b41 100644 --- a/.github/workflows/release-apk.yml +++ b/.github/workflows/release-apk.yml @@ -47,7 +47,7 @@ jobs: - uses: subosito/flutter-action@v2 with: - flutter-version: 3.47.5 + flutter-version: 3.47.6 channel: stable cache: true diff --git a/.github/workflows/repo-contract.yml b/.github/workflows/repo-contract.yml new file mode 100644 index 0000000..89d5597 --- /dev/null +++ b/.github/workflows/repo-contract.yml @@ -0,0 +1,25 @@ +name: repo-contract + +# The local pre-commit hook can be skipped (a fresh clone has no hooks), so the +# contract is enforced on push too. Mirrors md-naming.yml. +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + repo-contract: + name: repo contract + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check out the shared gate + uses: actions/checkout@v4 + with: + repository: kuhyx/utils + path: .utils + + - name: Enforce the repo contract + run: bash .utils/scripts/check_repo_contract.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 14c7bb7..f09c9a5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -93,3 +93,9 @@ repos: |package\.json|package-lock\.json|pnpm-lock\.yaml |Cargo\.(toml|lock)|go\.(mod|sum) |\.fvmrc|\.nvmrc|\.python-version)$ + - id: repo-contract + name: repo contract (CLAUDE.md Commands + test_changed) + entry: scripts/check_repo_contract.sh + language: system + pass_filenames: false + files: ^(CLAUDE\.md|AGENTS\.md|scripts/test_changed\.sh)$ diff --git a/AGENTS.md b/AGENTS.md index 129f4c0..63258a3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,6 +43,12 @@ and sync peer-to-peer through a GitHub repo used as dumb storage. - Release APK: `flutter build apk --release` (signs with the debug key; debug builds are janky — always measure smoothness on a release build). - Sync smoke test (hits real GitHub, needs a token): `dart run tool/sync_smoke.dart`. +- run: `./run.sh` (desktop; mobile: `flutter run`) +- test: `flutter test --reporter=compact` +- test-changed: `scripts/test_changed.sh` +- lint: `flutter analyze` +- coverage: `flutter test --coverage --reporter=compact` +- coverage-gaps: `coverage-gaps coverage/lcov.info` ## Architecture diff --git a/mcp/pyproject.toml b/mcp/pyproject.toml index e64b53f..ed72277 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==2.2.0", + "mcp==2.3.0", ] # Explicit package discovery so ``pip install -e .`` picks up the package and diff --git a/pubspec.lock b/pubspec.lock index 85547c2..1a33b26 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -390,11 +390,11 @@ packages: dependency: "direct main" description: path: github_device_auth - ref: "github_device_auth-v0.3.1" - resolved-ref: "11c752fe6508117d4d2db1c0744090221d5f6cb8" + ref: "github_device_auth-v0.3.2" + resolved-ref: "4ccaba1bb3e495ef72d70c6a7e7d7ec98d755792" url: "https://github.com/kuhyx/utils" source: git - version: "0.3.1" + version: "0.3.2" glob: dependency: transitive description: @@ -479,10 +479,10 @@ packages: dependency: "direct main" description: name: idb_shim - sha256: "6a478bf0928fa954512f2a1e7e56f09e2fb2e3e7d183eb051a59b5d6b39ec16f" + sha256: "1495b1bca25088ab05be682ba32532e62f1559b7cd781141c96130b7654ac9f7" url: "https://pub.dev" source: hosted - version: "2.9.9" + version: "2.9.10" image: dependency: "direct main" description: @@ -863,10 +863,10 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "34f00f9becd2743c1fb05363d624f9f70d37f7ccdcdda47450bc0b8c9d327b8c" + sha256: db40767f130189d53f3ae8a6393c217ff4a492da1baff718a9873679c0c00224 url: "https://pub.dev" source: hosted - version: "13.3.0" + version: "13.3.1" share_plus_platform_interface: dependency: transitive description: @@ -1069,10 +1069,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + sha256: e8cc955c97e06df9879140a1fe301f3d493f01b7c62dec7f46834be31d7e9fda url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" url_launcher_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7e4c549..aef2ebf 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.41+1 +version: 1.0.42+1 environment: sdk: ^3.12.2 @@ -59,14 +59,14 @@ dependencies: github_device_auth: git: url: https://github.com/kuhyx/utils - ref: github_device_auth-v0.3.1 + ref: github_device_auth-v0.3.2 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.9 + idb_shim: 2.9.10 # Re-encodes attached images (resize, EXIF strip) in pure Dart, so the # phone and the desktop wrapper share one code path. image: 4.10.1 @@ -77,7 +77,7 @@ dependencies: open_file: 4.0.0 path: 1.9.1 path_provider: 2.1.6 - share_plus: 13.3.0 + share_plus: 13.3.1 shared_preferences: 2.5.5 sqflite_common_ffi: 2.4.3 # Retained only to read the legacy on-device DB during the one-time @@ -93,7 +93,7 @@ dependencies: url: https://github.com/kuhyx/utils ref: sync_settings_ui-v0.4.1 path: sync_settings_ui - url_launcher: 6.3.2 + url_launcher: 6.3.3 uuid: 4.6.0 # Paste and drag-and-drop listeners on the desktop (web) page. web: 1.1.1 diff --git a/scripts/check_repo_contract.sh b/scripts/check_repo_contract.sh new file mode 100755 index 0000000..8488858 --- /dev/null +++ b/scripts/check_repo_contract.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# ============================================================================ +# Fail if this repo breaks the shared repo contract. +# +# Thin delegate to the shared gate in ~/src/utils, which owns the contract +# text. Copy this file to scripts/check_repo_contract.sh in a repo; never copy +# the shared gate itself, or the shim would exec itself forever. +# ============================================================================ + +set -euo pipefail + +readonly SHARED_GATE="${UTILS_ROOT:-$HOME/src/utils}/scripts/check_repo_contract.sh" + +main() { + if [[ ! -x "$SHARED_GATE" ]]; then + echo "Error: shared repo-contract gate not found at $SHARED_GATE" >&2 + echo " Clone github.com/kuhyx/utils to ~/src/utils, or set" >&2 + echo " UTILS_ROOT to where it lives." >&2 + exit 1 + fi + + exec bash "$SHARED_GATE" "$@" +} + +main "$@" diff --git a/scripts/test_changed.sh b/scripts/test_changed.sh new file mode 100755 index 0000000..025e200 --- /dev/null +++ b/scripts/test_changed.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# ============================================================================ +# Run only the tests related to files changed vs HEAD (staged, unstaged and +# untracked). Quiet: failures plus a one-line summary. Falls back to the full +# suite when a change cannot be mapped to tests. Generated by repo_contract. +# ============================================================================ + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +CHANGED=() +while IFS= read -r f; do + [[ -n "$f" && -e "$f" ]] && CHANGED+=("$f") +done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u) + +full=0 +tests=() +for f in "${CHANGED[@]}"; do + case "$f" in + pubspec.yaml | pubspec.lock | test/flutter_test_config.dart) full=1 ;; + test/*_test.dart) tests+=("$f") ;; + lib/*.dart) + rel="${f#lib/}" + if [[ -f "test/${rel%.dart}_test.dart" ]]; then + tests+=("test/${rel%.dart}_test.dart") + else + while IFS= read -r t; do tests+=("$t"); done \ + < <(git ls-files "test/*$(basename "${f%.dart}")_test.dart") + fi + ;; + esac +done + +if [[ $full -eq 0 && ${#CHANGED[@]} -eq 0 ]]; then echo "no changes vs HEAD: nothing to test"; exit 0; fi +if [[ $full -eq 0 && ${#tests[@]} -gt 0 ]]; then flutter test --reporter=compact "${tests[@]}"; exit $?; fi +if [[ $full -eq 0 ]] && ! printf '%s\n' "${CHANGED[@]}" | grep -q '\.dart$'; then + echo "no dart changes: nothing to test"; exit 0 +fi +echo "no mapped tests: running full suite" +flutter test --reporter=compact