diff --git a/.mise/tasks/refresh b/.mise/tasks/refresh index 71b7a01..1b1fbde 100755 --- a/.mise/tasks/refresh +++ b/.mise/tasks/refresh @@ -13,4 +13,5 @@ mise upgrade --local git pull project_uv lock --upgrade project_uv sync --dev --group docs -pre-commit autoupdate +# 7-day cooldown on hook updates (policies/project_standards.md in rhizomatics/hq) +prek autoupdate --cooldown-days 7 diff --git a/mise.toml b/mise.toml index cd72cc0..0e45a65 100644 --- a/mise.toml +++ b/mise.toml @@ -1,4 +1,4 @@ [tools] -pre-commit = "latest" +prek = "latest" python = "3.14" uv = "latest" diff --git a/pyproject.toml b/pyproject.toml index b4e7ecd..88b2c98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,3 +11,5 @@ docs = ["zensical>=0.0.72"] [tool.uv] package = false default-groups = ["docs"] +# 7-day release cooldown (policies/project_standards.md in rhizomatics/hq) +exclude-newer = "7 days" diff --git a/scripts/sync-admin.sh b/scripts/sync-admin.sh index f79f5c3..5986ea4 100755 --- a/scripts/sync-admin.sh +++ b/scripts/sync-admin.sh @@ -56,6 +56,11 @@ while IFS= read -r gitdir; do $check || { mkdir -p "$(dirname "$dest")"; cp "$tpl" "$dest"; } done fi + # npm repos share one .npmrc, which holds the release cooldown for npm install/update. + if [[ $eco == node ]] && ! cmp -s "$here/templates/node/npmrc" "$repo/.npmrc"; then + changes+=("update .npmrc") + $check || cp "$here/templates/node/npmrc" "$repo/.npmrc" + fi # The .github repo holds the shared labels.yml at its root, so .github/labels.yml is still legacy there. for f in "${legacy[@]}"; do [[ -e $repo/$f ]] || continue diff --git a/templates/README.md b/templates/README.md index facc6bd..f59d458 100644 --- a/templates/README.md +++ b/templates/README.md @@ -31,9 +31,10 @@ repo's `.mise/tasks/`, replacing the old `refresh-deps.sh` (which it removes): | Repos | Task | What it does | |---|---|---| -| Python (`uv.lock`) | `mise run refresh` | Upgrades mise tools, pulls bot PRs, re-locks and syncs with `uv`, runs `pre-commit autoupdate` | +| Python (`uv.lock`) | `mise run refresh` | Upgrades mise tools, pulls bot PRs, re-locks and syncs with `uv`, runs `prek autoupdate --cooldown-days 7` | | Python (`uv.lock`) | `mise run clean` | Removes build, test and lint caches and recreates an empty `.venv` | -| Node (`package-lock.json`) | `mise run refresh` | Pulls bot PRs, runs `pre-commit autoupdate`, upgrades mise tools, runs `npm update` | +| Node (`package-lock.json`) | `mise run refresh` | Pulls bot PRs, runs `prek autoupdate --cooldown-days 7`, upgrades mise tools, runs `npm update` | +| Node (`package-lock.json`) | `.npmrc` (not a task) | `min-release-age=7`, so `npm install`/`npm update` skip versions under 7 days old; `@rhizomatics/*` is exempt | The Python `refresh` hides the user-level `~/.config/uv/uv.toml` from `uv` (`XDG_CONFIG_HOME=/dev/null`) so personal settings never reach a committed `uv.lock`, while the project's own `[tool.uv]` still applies. @@ -42,6 +43,22 @@ A file task shadows a `mise.toml` task of the same name, so a repo that needs it (supernotify's `clean` also tears down its Docker preview) defines it in `mise.toml`, and `sync-admin.sh` then leaves the shared one out. +## Cooldowns + +Every automatic update waits 7 days after a release (`policies/project_standards.md` in +`rhizomatics/hq`), so a compromised or broken release has time to be pulled first: + +| Updater | Where the cooldown lives | +|---|---| +| Dependabot | `cooldown: default-days: 7` on each ecosystem | +| uv | `exclude-newer = "7 days"` in `[tool.uv]`; per-package exceptions in `exclude-newer-package` | +| npm | shared `.npmrc` (`min-release-age=7`) | +| prek hooks | `prek autoupdate --cooldown-days 7` in the shared `refresh` task | + +Own packages may go to 0 days, and trusted, widely used ones (Home Assistant, astral) to 2. +Repos use `prek` (from `mise.toml`) rather than `pre-commit`; it reads the same +`.pre-commit-config.yaml`. + ## Dependabot `dependabot.yml` can't be shared, so each repo has its own with a common shape per ecosystem: diff --git a/templates/node/npmrc b/templates/node/npmrc new file mode 100644 index 0000000..e018b5d --- /dev/null +++ b/templates/node/npmrc @@ -0,0 +1,5 @@ +# Shared copy, kept in step by rhizomatics/.github scripts/sync-admin.sh: change it there, not here. +# 7-day cooldown for npm install/update (policies/project_standards.md in rhizomatics/hq). +# Rhizomatics' own packages are exempt. +min-release-age=7 +min-release-age-exclude[]=@rhizomatics/* diff --git a/templates/node/tasks/refresh b/templates/node/tasks/refresh index ec620fb..7222c1d 100755 --- a/templates/node/tasks/refresh +++ b/templates/node/tasks/refresh @@ -4,6 +4,7 @@ set -euo pipefail git pull -pre-commit autoupdate +# 7-day cooldown on hook updates (policies/project_standards.md in rhizomatics/hq) +prek autoupdate --cooldown-days 7 mise upgrade --local npm update diff --git a/templates/python/tasks/refresh b/templates/python/tasks/refresh index 71b7a01..1b1fbde 100755 --- a/templates/python/tasks/refresh +++ b/templates/python/tasks/refresh @@ -13,4 +13,5 @@ mise upgrade --local git pull project_uv lock --upgrade project_uv sync --dev --group docs -pre-commit autoupdate +# 7-day cooldown on hook updates (policies/project_standards.md in rhizomatics/hq) +prek autoupdate --cooldown-days 7 diff --git a/uv.lock b/uv.lock index b246a73..3026205 100644 --- a/uv.lock +++ b/uv.lock @@ -2,13 +2,26 @@ version = 1 revision = 5 requires-python = ">=3.14" +[options] +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer-span = "P7D" + [[package]] name = "click" -version = "8.5.1" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[package]] +name = "deepmerge" +version = "4.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/37/edbf1b0707709013c0cf2e8317e03d97ed29bf4cac709fe328904ae92a38/click-8.5.1.tar.gz", hash = "sha256:9bbfada76703fd1d784b83eea012e3e8c6a001a876543ef104fa56aaaeb072b4", size = 399151, upload-time = "2026-11-06T17:37:01.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/e9/5e99e56e03f9461858e686fdaf82e2516671bc1bd46f8b7ad7b9a8375f6f/deepmerge-4.0.0.tar.gz", hash = "sha256:948565f2bc9cdb41259a4a23d26638e5840c9e084a343ab279ffd3edcfdf74d3", size = 22792, upload-time = "2026-10-25T19:25:28.967Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/7d/164b7ffc8624fe9efb4221bf9af485d5c1e41b9ae118045d2b6dc5457d8b/click-8.5.1-py3-none-any.whl", hash = "sha256:8bb5376d2006950386f8c6427613334a2acadc5f559cb80e638d00b4df957e37", size = 126163, upload-time = "2026-11-06T17:36:59.287Z" }, + { url = "https://files.pythonhosted.org/packages/c2/89/362146b2ec7d0d503f824ac8496c88f0408ee0d39203b02cc10feac08146/deepmerge-4.0.0-py3-none-any.whl", hash = "sha256:a36dd5810fc3b90bd3e0b681158221361d6a31a6109411b27b211b371fed6d5b", size = 15429, upload-time = "2026-10-25T19:25:28.012Z" }, ] [[package]] @@ -25,11 +38,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.11.3" +version = "3.11.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/eb/dc0fd6eff604bfb870b7fb68b90985a32caaf3c76f67736bbf7f438acd59/markdown-3.11.3.tar.gz", hash = "sha256:81eb91631dc97ad1efbc76019ef0f80fe90ca49ddf81546752d79989c3bfff3e", size = 498810, upload-time = "2026-11-05T20:30:52.926Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/32/0b4d0297064244f04af4331b65de16237cc3cce07474171ee14222a28981/markdown-3.11.2.tar.gz", hash = "sha256:87bf5d90f2d2882e2c02fcda182c2800456b985bff38913867c49706481514b0", size = 491445, upload-time = "2026-10-19T19:41:42.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/6f/1fa8970e960e5c832874399e6efa564870ade8206d57fef5d5e296b834f5/markdown-3.11.3-py3-none-any.whl", hash = "sha256:e1a31a112232504d753e5c01bb8c2de90d3f0ba986b063e10770cf7695b09e73", size = 116758, upload-time = "2026-11-05T20:30:51.775Z" }, + { url = "https://files.pythonhosted.org/packages/fe/66/bcab3b677b71ad286b4e607e3427499537c76e13ad2e22584ba7a7d9882c/markdown-3.11.2-py3-none-any.whl", hash = "sha256:f80390747a2a0ff2ba54da074ce4f862f72a6685f608cd2637b646a6d83993f5", size = 114266, upload-time = "2026-10-19T19:41:41.244Z" }, ] [[package]] @@ -230,10 +243,11 @@ wheels = [ [[package]] name = "zensical" -version = "0.1.0" +version = "0.0.74" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, + { name = "deepmerge" }, { name = "jinja2" }, { name = "markdown" }, { name = "pathspec" }, @@ -243,16 +257,16 @@ dependencies = [ { name = "tomli" }, { name = "zensical-ui" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/35/173b9b5557ec92e151f1098dcbc482b37e52c43732a71606c1eb19b91e25/zensical-0.1.0.tar.gz", hash = "sha256:0715bae72a1acb0164eb06f489d6bb93bb3e0fbcf786fa8259daae5955611526", size = 517416, upload-time = "2026-11-05T15:32:53.217Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/41/147eed1f072917de5d91e2aa0c2204edf87e5e9c7cd34c314177969d5281/zensical-0.0.74.tar.gz", hash = "sha256:f2ddd17daead93fdbcaa42b5bdc367f9a4808bad282fe706763cb52dba29d96b", size = 514731, upload-time = "2026-10-30T22:09:31.956Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/92/f875d742b7e3a2383e7c2d1c69189f907bc05cedfaba18034f62cdba392e/zensical-0.1.0-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4cec7739b505a64b38167fbf92a7cccfdda9faf2deb96cb9d2d099ff50f59942", size = 8652118, upload-time = "2026-11-05T15:32:36.667Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b9/f57bfffb0243c484c0d76c3b4e100ba7dcaa0ffae5c18adb96533aabe5c0/zensical-0.1.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:bea2aa1312b4c927798ebe32bb5c399d40c10a0341ad9f1b7cbfdc6beaaa547f", size = 8242303, upload-time = "2026-11-05T15:32:39.068Z" }, - { url = "https://files.pythonhosted.org/packages/71/5a/ee5c8e3e2072da5ce8b151d244d82fdfb9afc4e15042271b3e9a5747b8a0/zensical-0.1.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f2892e08d7ea4baf714d0d033246044ea0f1b4acedd5a593988fcf61bdf26e30", size = 8688680, upload-time = "2026-11-05T15:32:40.955Z" }, - { url = "https://files.pythonhosted.org/packages/d6/50/8fccb0185b43b65bb69e253e50aba1cf33dde38f9f7558be1a6d4b5043c8/zensical-0.1.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d184a9e718ea7857558128a0708c950b3ab47366e96c724b33890aab70c64f5a", size = 9184145, upload-time = "2026-11-05T15:32:43.419Z" }, - { url = "https://files.pythonhosted.org/packages/52/12/4a83b4bc5600fd4be4c3deea8c3714c3475ff4e2a1b287ceb68bf62caa14/zensical-0.1.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:60151ebc13022bea4b68222d694d48121e2d11558e28d18dc61d4cfdd83fe51e", size = 8859597, upload-time = "2026-11-05T15:32:45.518Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a8/4d484bfabf9a02ba61edefd4ef8ac867f992dc57df8675e61ffc8d387100/zensical-0.1.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7363783df33340cd4c9e52dc0f53dcbd217480f7ee0e2b4959a1c78def88cb9c", size = 9455176, upload-time = "2026-11-05T15:32:47.444Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d7/0845176ceac35091d0cfaef6d37b515dfa2bbd4714e8aee6d4c446c3e26e/zensical-0.1.0-cp311-abi3-win_amd64.whl", hash = "sha256:a0614fb694fc41ddd84b6743e64c4797db4a83cc0bc84accd252036033fb1fa2", size = 9387745, upload-time = "2026-11-05T15:32:49.4Z" }, - { url = "https://files.pythonhosted.org/packages/4d/6e/fc3c2e90d850a6bc4ac04c0ad4603f041e2d1241e3fab78ea6be0f26bac5/zensical-0.1.0-cp311-abi3-win_arm64.whl", hash = "sha256:ff89088b554f7a88358eaa470c6738b6d1511f9fec835f0bacc29f95dd6139f9", size = 8784201, upload-time = "2026-11-05T15:32:51.349Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a3/4031992e04a323f09b3fb89531fdce6c13f83650533a39809ddab9b4a4ef/zensical-0.0.74-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:44f5afe36d1fda5a80a2e9b24cecd25e2d6e6f1f05845299284108d7be40a48b", size = 8621183, upload-time = "2026-10-30T22:09:09.263Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0a/6d56d6886637927204180ddf8480483f9ba4f838cf23f871c02845e8b1ef/zensical-0.0.74-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:912a6a5eb834b283e31ca89341026ccb0bba260351f9123139dbb476a3daf505", size = 8217741, upload-time = "2026-10-30T22:09:12.401Z" }, + { url = "https://files.pythonhosted.org/packages/e3/88/deedbf55d985629af31cdbd126a83d60c89440d32d94fd86c9ab7b13a215/zensical-0.0.74-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7722e1c2505925a477719b79fdc25defe53ce032bab1d769bd4861b130cf149c", size = 8661389, upload-time = "2026-10-30T22:09:14.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/48/77a87b588801a6945a274415bffd0ffe7d7781424bec449d54422780784f/zensical-0.0.74-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9da81ad4bb42bdd92a52d9e42623eaa25c936fa0df0d26e507dcd282e8bf85b2", size = 9151632, upload-time = "2026-10-30T22:09:17.611Z" }, + { url = "https://files.pythonhosted.org/packages/20/80/c8ffff0de58e6f8810b1d7609442efa2919058c8d1035d266db4b2aad7d9/zensical-0.0.74-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4df8506f0c13226436c6955203b3332e5334b579a1daeeb3c9e7749c726e5327", size = 8830888, upload-time = "2026-10-30T22:09:20.736Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c6/3eff7d34d94fe1978637d60094db4c407e9f3779a41f37fa678a6d8e7356/zensical-0.0.74-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6a6fad6241a6fb04012ca87bf31e11f7cc3b3248a84fe4bba3e3b86a3506cb09", size = 9422666, upload-time = "2026-10-30T22:09:23.882Z" }, + { url = "https://files.pythonhosted.org/packages/cf/91/dc6a6783b62d82ad872beebb14339d0e9b8372dd8b74bd5c4b388d8dab1d/zensical-0.0.74-cp311-abi3-win_amd64.whl", hash = "sha256:491ee04190f8fc0b731a40d99062f979a508b522265c5042cbd79eb7a59e250d", size = 9356286, upload-time = "2026-10-30T22:09:26.574Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f2/fa31d226655699c974137499a98dd5a188882f0c9460ba584107e072ad2d/zensical-0.0.74-cp311-abi3-win_arm64.whl", hash = "sha256:a43b3801fb6e90f774d32b4e485a81cdf028a5765d0d5398d28cf406be888731", size = 8750302, upload-time = "2026-10-30T22:09:29.718Z" }, ] [[package]]