From 1cf567977f97ad45aa9a1c5f3ab5b989fb7055e1 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 28 Aug 2026 17:38:25 +0200 Subject: [PATCH] Latest stable dependencies, gatelock v0.7.2, and the gate Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/dependency-freshness.yml | 62 +++++++ .pre-commit-config.yaml | 22 +++ .../dependency-freshness-gate-2026-08-28.json | 33 ++++ meta/requirements-test.txt | 34 ++-- meta/requirements.txt | 158 +++++++++--------- python_pkg/random_jpg/requirements.txt | 2 +- scripts/check_dependency_freshness.sh | 34 ++++ 7 files changed, 248 insertions(+), 97 deletions(-) create mode 100644 .github/workflows/dependency-freshness.yml create mode 100644 docs/superpowers/evidence/dependency-freshness-gate-2026-08-28.json create mode 100755 scripts/check_dependency_freshness.sh diff --git a/.github/workflows/dependency-freshness.yml b/.github/workflows/dependency-freshness.yml new file mode 100644 index 00000000..23a0a737 --- /dev/null +++ b/.github/workflows/dependency-freshness.yml @@ -0,0 +1,62 @@ +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 + + # "3.x", never a pinned minor: the Python target IS the interpreter the + # gate runs under (registries/toolchain.python_installed), so a runner + # held a release behind this machine reports a repo's `requires-python` + # as excluding "latest" while the same check passes locally. flax-docs-mcp + # hit exactly that -- `>=3.14` green here, red on a 3.13 runner. + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - 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/.pre-commit-config.yaml b/.pre-commit-config.yaml index 560fcb0e..1b782144 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -592,3 +592,25 @@ 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 + |\.github/workflows/.*\.ya?ml)$ diff --git a/docs/superpowers/evidence/dependency-freshness-gate-2026-08-28.json b/docs/superpowers/evidence/dependency-freshness-gate-2026-08-28.json new file mode 100644 index 00000000..748bf331 --- /dev/null +++ b/docs/superpowers/evidence/dependency-freshness-gate-2026-08-28.json @@ -0,0 +1,33 @@ +{ + "intent": "Put every dependency this repo declares on its newest stable release, and install the shared dependency-freshness gate so it stays there.", + "scope": [ + "meta/requirements.txt, meta/requirements-test.txt, python_pkg/random_jpg/requirements.txt", + ".pre-commit-config.yaml, .github/workflows/dependency-freshness.yml, scripts/check_dependency_freshness.sh", + "Non-goal: no source changes; this is manifests and gate wiring only" + ], + "changes": [ + "Exact-pinned 95 previously range-specified dependencies to their newest stable release, via ~/.claude/scripts/apply_dep_pins.py driven by the gate's own --json findings.", + "Moved the gatelock git-tag dependency from gatelock-v0.1.0 to gatelock-v0.7.2 -- six minor versions of drift that a PEP 508 direct reference hid from every previous check.", + "Installed the shared gate's three pieces (delegate, pre-commit hook, CI workflow) from ~/utils/dep_freshness/templates." + ], + "verification": [ + { + "command": "REQUIREMENTS_FILE=meta/requirements.txt ./meta/scripts/ci_mirror.sh", + "result": "pass", + "evidence": "exit 0; clean venv rebuilt from the repinned meta/requirements.txt, pre-commit --all-files and the full suite green inside it" + }, + { + "command": "~/utils/scripts/check_dependency_freshness.sh --all", + "result": "pass", + "evidence": "exit 0 -- 97 findings before, 0 after" + } + ], + "risks": [ + "gatelock v0.1.0 -> v0.7.2 is a six-version jump in a shared library; the clean-venv mirror is what exercises it here.", + "Exact pins mean CI goes red on any upstream release until the pin is bumped. That is the chosen behaviour, not a regression." + ], + "rollback": [ + "git revert the commit; the pins and the three gate files are the whole change.", + "After reverting, re-run ci_mirror.sh to confirm the older pins still resolve." + ] +} diff --git a/meta/requirements-test.txt b/meta/requirements-test.txt index 8bff454a..dca04e64 100644 --- a/meta/requirements-test.txt +++ b/meta/requirements-test.txt @@ -21,20 +21,20 @@ # suite itself fails loudly here on a missing import. # ============================================================================== -anthropic>=0.25.0 -beautifulsoup4>=4.0 -coverage>=7.4.0 -defusedxml>=0.7.1 -markdownify>=1.2.0 -numpy>=1.20 -pillow>=10.0 -pytest>=8.0.0 -pytest-cov>=4.1.0 -pytest-randomly>=3.15.0 -pytest-sugar>=1.0.0 -pytest-timeout>=2.2.0 -pytest-xdist>=3.5.0 -requests>=2.0 -rich>=13.0 -typer>=0.12.0 -typing_extensions +anthropic==1.2.0 +beautifulsoup4==4.15.0 +coverage==7.15.4 +defusedxml==0.7.1 +markdownify==1.2.3 +numpy==2.5.2 +pillow==12.3.0 +pytest==9.1.1 +pytest-cov==7.1.0 +pytest-randomly==4.1.0 +pytest-sugar==1.1.1 +pytest-timeout==2.4.0 +pytest-xdist==3.8.0 +requests==2.34.2 +rich==15.0.0 +typer==0.27.2 +typing_extensions==4.16.0 diff --git a/meta/requirements.txt b/meta/requirements.txt index 20e174c8..8290d28c 100644 --- a/meta/requirements.txt +++ b/meta/requirements.txt @@ -4,82 +4,82 @@ # Sorted alphabetically (enforced by pre-commit `requirements-txt-fixer`). # ============================================================================== -add-trailing-comma>=3.1.0 -aiohttp>=3.9 -anthropic>=0.25.0 -autoflake>=2.2.0 -autopep8>=2.0.0 -bandit>=1.7.0 -beautifulsoup4>=4.0 -berserk>=0.13 -black>=24.0.0 -bottle>=0.12 -codespell>=2.2.0 -coverage>=7.4.0 -darglint>=1.8.0 -dead>=1.5.0 -defusedxml>=0.7.1 -docformatter>=1.7.0 -fixit>=2.1.0 -flake8>=7.0.0 -flake8-annotations>=3.0.0 -flake8-bandit>=4.1.0 -flake8-bugbear>=24.0.0 -flake8-comprehensions>=3.14.0 -flake8-docstrings>=1.7.0 -flake8-eradicate>=1.5.0 -flake8-pie>=0.16.0 -flake8-print>=5.0.0 -flake8-pyi>=24.0.0 -flake8-pytest-style>=2.0.0 -flake8-return>=1.2.0 -flake8-simplify>=0.21.0 -gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.1.0#subdirectory=gatelock -genanki>=0.13 -geopandas>=1.0 -howlongtobeatpy>=1.0 -hypothesis>=6.98.0 -importlib-metadata>=7.0.0 -interrogate>=1.5.0 -isort>=5.13.0 -lxml>=5.0 -markdownify>=1.2.0 -matplotlib>=3.0 -mccabe>=0.7.0 -mypy>=1.8.0 -numpy>=1.20 -opencv-python>=4.0 -pillow>=10.0 -pip-audit>=2.6.0 -pipdeptree>=2.14.0 -pre-commit>=3.6.0 -prospector>=1.10.0 -pycodestyle>=2.11.0 -pydocstyle>=6.3.0 -pyflakes>=3.2.0 -pygame>=2.0 -pylint>=3.0.0 -pyright>=1.1.350 -pytest>=8.0.0 -pytest-cov>=4.1.0 -pytest-randomly>=3.15.0 -pytest-sugar>=1.0.0 -pytest-timeout>=2.2.0 -pytest-xdist>=3.5.0 -python-chess>=1.999 -python-kasa>=0.7 -pyupgrade>=3.15.0 -radon>=6.0.0 -reorder-python-imports>=3.12.0 -requests>=2.0 -rich>=13.0 -ruff>=0.8.0 -safety>=2.3.0 -selenium>=4.0 -typer>=0.12.0 -types-python-dateutil>=2.8.0 -types-PyYAML>=6.0.0 -types-requests>=2.31.0 -types-setuptools>=69.0.0 -typing_extensions # Self annotation on py310 target (session_autopsy store lock) -websockets>=13.0 +add-trailing-comma==4.0.0 +aiohttp==3.14.3 +anthropic==1.2.0 +autoflake==2.4.0 +autopep8==2.3.2 +bandit==1.9.4 +beautifulsoup4==4.15.0 +berserk==0.14.0 +black==26.5.1 +bottle==0.13.4 +codespell==2.4.3 +coverage==7.15.4 +darglint==1.8.1 +dead==2.1.0 +defusedxml==0.7.1 +docformatter==1.7.8 +fixit==2.2.1 +flake8==7.3.0 +flake8-annotations==3.2.0 +flake8-bandit==4.1.1 +flake8-bugbear==25.11.29 +flake8-comprehensions==3.17.0 +flake8-docstrings==1.7.0 +flake8-eradicate==1.5.0 +flake8-pie==0.16.0 +flake8-print==5.0.0 +flake8-pyi==26.5.0 +flake8-pytest-style==2.2.0 +flake8-return==1.2.0 +flake8-simplify==0.30.0 +gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.2#subdirectory=gatelock +genanki==0.13.1 +geopandas==1.1.4 +howlongtobeatpy==1.0.23 +hypothesis==6.165.10 +importlib-metadata==9.0.1 +interrogate==1.7.0 +isort==9.0.1 +lxml==6.1.2 +markdownify==1.2.3 +matplotlib==3.11.1 +mccabe==0.7.0 +mypy==2.3.1 +numpy==2.5.2 +opencv-python==5.0.0.93 +pillow==12.3.0 +pip-audit==2.10.1 +pipdeptree==4.2.2 +pre-commit==4.6.2 +prospector==1.19.1 +pycodestyle==2.14.0 +pydocstyle==6.3.0 +pyflakes==3.4.0 +pygame==2.6.1 +pylint==4.0.7 +pyright==1.1.411 +pytest==9.1.1 +pytest-cov==7.1.0 +pytest-randomly==4.1.0 +pytest-sugar==1.1.1 +pytest-timeout==2.4.0 +pytest-xdist==3.8.0 +python-chess==1.999 +python-kasa==0.10.2 +pyupgrade==3.21.2 +radon==6.0.1 +reorder-python-imports==3.17.0 +requests==2.34.2 +rich==15.0.0 +ruff==0.16.5 +safety==3.8.1 +selenium==4.48.0 +typer==0.27.2 +types-python-dateutil==2.9.0.20260807 +types-PyYAML==6.0.12.20260815 +types-requests==2.33.0.20260712 +types-setuptools==84.0.0.20260812 +typing_extensions==4.16.0# Self annotation on py310 target (session_autopsy store lock) +websockets==17.1 diff --git a/python_pkg/random_jpg/requirements.txt b/python_pkg/random_jpg/requirements.txt index 3868fb16..61b76ad1 100644 --- a/python_pkg/random_jpg/requirements.txt +++ b/python_pkg/random_jpg/requirements.txt @@ -1 +1 @@ -pillow +pillow==12.3.0 diff --git a/scripts/check_dependency_freshness.sh b/scripts/check_dependency_freshness.sh new file mode 100755 index 00000000..6b096ccc --- /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 "$@"