repomatic 7.1.0__tar.gz → 7.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. {repomatic-7.1.0 → repomatic-7.2.0}/PKG-INFO +2 -2
  2. {repomatic-7.1.0 → repomatic-7.2.0}/pyproject.toml +21 -10
  3. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/__init__.py +2 -2
  4. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/broken_links.py +1 -1
  5. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/cache.py +96 -90
  6. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/changelog.py +47 -17
  7. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/cli.py +292 -614
  8. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/config.py +67 -41
  9. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/_release-engine.yaml +26 -19
  10. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/action-publish-pypi.yaml +1 -1
  11. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/agent-sphinx-docs.md +1 -1
  12. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/autofix.yaml +98 -56
  13. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/bumpversion.toml +6 -2
  14. repomatic-7.2.0/repomatic/data/cancel-runs.yaml +50 -0
  15. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/changelog.yaml +18 -18
  16. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/debug.yaml +4 -4
  17. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/docs.yaml +6 -6
  18. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/labels.yaml +11 -11
  19. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/lint.yaml +17 -17
  20. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-babysit-ci.md +6 -3
  21. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-ship.md +12 -10
  22. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/tests.yaml +5 -5
  23. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/unsubscribe.yaml +4 -2
  24. repomatic-7.2.0/repomatic/dep_sources.py +377 -0
  25. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/deps_graph.py +287 -344
  26. repomatic-7.2.0/repomatic/docs.py +172 -0
  27. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/git_ops.py +14 -35
  28. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/__init__.py +3 -6
  29. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/actions.py +47 -0
  30. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/dev_release.py +1 -2
  31. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/gh.py +100 -12
  32. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/issue.py +2 -17
  33. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/matrix.py +3 -0
  34. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/pr_body.py +132 -47
  35. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/release_sync.py +5 -50
  36. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/releases.py +100 -100
  37. {repomatic-7.1.0/repomatic → repomatic-7.2.0/repomatic/github}/sponsor.py +11 -49
  38. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/token.py +85 -174
  39. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/unsubscribe.py +25 -36
  40. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/workflow_sync.py +137 -124
  41. repomatic-7.2.0/repomatic/gitignore.py +88 -0
  42. repomatic-7.2.0/repomatic/http.py +80 -0
  43. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/images.py +2 -0
  44. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/init_project.py +10 -11
  45. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/lint_repo.py +1 -6
  46. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/metadata.py +51 -47
  47. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/npm.py +4 -7
  48. repomatic-7.1.0/repomatic/release_prep.py → repomatic-7.2.0/repomatic/prepare_release.py +18 -5
  49. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/pypi.py +22 -16
  50. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/pyproject.py +22 -28
  51. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/registry.py +46 -20
  52. repomatic-7.2.0/repomatic/setup_guide.py +383 -0
  53. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/sync_ops.py +362 -163
  54. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/bump-version.md +2 -3
  55. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/detect-squash-merge.md +4 -3
  56. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/fix-changelog.md +2 -5
  57. repomatic-7.2.0/repomatic/templates/fix-typos.md +8 -0
  58. repomatic-7.2.0/repomatic/templates/fix-vulnerable-deps.md +15 -0
  59. repomatic-7.2.0/repomatic/templates/format-images.md +5 -0
  60. repomatic-7.2.0/repomatic/templates/format-json.md +8 -0
  61. repomatic-7.2.0/repomatic/templates/format-markdown.md +8 -0
  62. repomatic-7.2.0/repomatic/templates/format-pyproject.md +8 -0
  63. repomatic-7.2.0/repomatic/templates/format-python.md +8 -0
  64. repomatic-7.2.0/repomatic/templates/format-shell.md +8 -0
  65. repomatic-7.2.0/repomatic/templates/pr-metadata.md.noformat +15 -0
  66. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/prepare-release.md +2 -3
  67. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-virustotal.md +3 -0
  68. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/sync-action-pins.md +2 -5
  69. repomatic-7.2.0/repomatic/templates/sync-bumpversion.md +11 -0
  70. repomatic-7.2.0/repomatic/templates/sync-dep-sources.md +17 -0
  71. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/sync-gitignore.md +2 -5
  72. repomatic-7.2.0/repomatic/templates/sync-mailmap.md +11 -0
  73. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/sync-repomatic.md +2 -5
  74. repomatic-7.2.0/repomatic/templates/sync-tool-versions.md +15 -0
  75. repomatic-7.2.0/repomatic/templates/sync-uv-lock.md +14 -0
  76. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/sync-workflow-pins.md +2 -5
  77. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/update-deps-graph.md +2 -5
  78. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/update-docs.md +2 -5
  79. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/test_matrix.py +3 -1
  80. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/tool_runner.py +57 -35
  81. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/uv.py +223 -583
  82. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/version_sync.py +50 -39
  83. repomatic-7.2.0/repomatic/vulnerable_deps.py +592 -0
  84. repomatic-7.1.0/repomatic/data/cancel-runs.yaml +0 -55
  85. repomatic-7.1.0/repomatic/github/advisories.py +0 -131
  86. repomatic-7.1.0/repomatic/templates/fix-typos.md +0 -11
  87. repomatic-7.1.0/repomatic/templates/fix-vulnerable-deps.md +0 -18
  88. repomatic-7.1.0/repomatic/templates/format-images.md +0 -8
  89. repomatic-7.1.0/repomatic/templates/format-json.md +0 -11
  90. repomatic-7.1.0/repomatic/templates/format-markdown.md +0 -11
  91. repomatic-7.1.0/repomatic/templates/format-pyproject.md +0 -11
  92. repomatic-7.1.0/repomatic/templates/format-python.md +0 -11
  93. repomatic-7.1.0/repomatic/templates/format-shell.md +0 -11
  94. repomatic-7.1.0/repomatic/templates/pr-metadata.md.noformat +0 -17
  95. repomatic-7.1.0/repomatic/templates/sync-bumpversion.md +0 -14
  96. repomatic-7.1.0/repomatic/templates/sync-mailmap.md +0 -14
  97. repomatic-7.1.0/repomatic/templates/sync-tool-versions.md +0 -18
  98. repomatic-7.1.0/repomatic/templates/sync-uv-lock.md +0 -17
  99. {repomatic-7.1.0 → repomatic-7.2.0}/license +0 -0
  100. {repomatic-7.1.0 → repomatic-7.2.0}/readme.md +0 -0
  101. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/__main__.py +0 -0
  102. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/binaries_page.py +0 -0
  103. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/binary.py +0 -0
  104. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/checksums.py +0 -0
  105. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/__init__.py +0 -0
  106. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/agent-grunt-qa.md +0 -0
  107. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/agent-qa-engineer.md +0 -0
  108. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/autolock.yaml +0 -0
  109. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/config.yml +0 -0
  110. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/new-link.yaml +0 -0
  111. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/code-of-conduct.md +0 -0
  112. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/contributing.md +0 -0
  113. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/contributing.zh.md +0 -0
  114. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/funding.yml +0 -0
  115. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/pull_request_template.md +0 -0
  116. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/awesome_template/__init__.py +0 -0
  117. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/awesome_template/license +0 -0
  118. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/codecov.yaml +0 -0
  119. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/labeller-content-based.yaml +0 -0
  120. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/labeller-file-based.yaml +0 -0
  121. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/labels.toml +0 -0
  122. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/lychee.toml +0 -0
  123. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/mdformat.toml +0 -0
  124. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/mypy.toml +0 -0
  125. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/pytest.toml +0 -0
  126. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/release.yaml +0 -0
  127. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/ruff.toml +0 -0
  128. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-av-false-positive.md +0 -0
  129. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-awesome-triage.md +0 -0
  130. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-benchmark-update.md +0 -0
  131. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-brand-assets.md +0 -0
  132. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-file-bug-report.md +0 -0
  133. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-audit.md +0 -0
  134. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-changelog.md +0 -0
  135. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-deps.md +0 -0
  136. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-init.md +0 -0
  137. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-topics.md +0 -0
  138. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-sphinx-docs-sync.md +0 -0
  139. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-translation-sync.md +0 -0
  140. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/skill-upstream-audit.md +0 -0
  141. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/typos.toml +0 -0
  142. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/uv.toml +0 -0
  143. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/yamllint.yaml +0 -0
  144. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/data/zizmor.yaml +0 -0
  145. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/pr.py +0 -0
  146. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/github/status.py +0 -0
  147. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/mailmap.py +0 -0
  148. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/myst_converter.py +0 -0
  149. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/myst_docstrings.py +0 -0
  150. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/py.typed +0 -0
  151. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/rst_to_myst.py +0 -0
  152. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/__init__.py +0 -0
  153. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/available-admonition.md +0 -0
  154. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/broken-links-issue.md +0 -0
  155. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/development-warning.md +0 -0
  156. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/generated-footer.md +0 -0
  157. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/github-releases.md +0 -0
  158. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/immutable-releases.md +0 -0
  159. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/refresh-tip.md +0 -0
  160. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/release-notes.md +0 -0
  161. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/release-sync-report.md +0 -0
  162. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-branch-ruleset.md +0 -0
  163. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-dependabot.md +0 -0
  164. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-fork-pr-approval.md +0 -0
  165. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-notifications-pat.md +0 -0
  166. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-pages-source.md +0 -0
  167. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-pypi-trusted-publisher.md +0 -0
  168. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-token.md +0 -0
  169. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-verify.md +0 -0
  170. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/setup-guide.md +0 -0
  171. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/unavailable-admonition.md +0 -0
  172. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/unsubscribe-phase1.md +0 -0
  173. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/unsubscribe-phase2.md +0 -0
  174. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/templates/yanked-admonition.md +0 -0
  175. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/tool_runner_page.py +0 -0
  176. {repomatic-7.1.0 → repomatic-7.2.0}/repomatic/virustotal.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: repomatic
3
- Version: 7.1.0
3
+ Version: 7.2.0
4
4
  Summary: 🏭 Automate repository maintenance, releases, and CI/CD workflows
5
5
  Keywords: changelog-formatter,dependabot-alternative,github-actions,github-labels,github-workflow,gitignore,mailmap,mypy,nuitka,python-automation,release-automation,repository-automation,repository-management,reusable-workflows,software-factory,sphinx-doc,typo,uv,workflow-reusable
6
6
  Author: Kevin Deldycke
@@ -58,7 +58,7 @@ Requires-Dist: wcmatch>=10
58
58
  Requires-Python: >=3.10
59
59
  Project-URL: Changelog, https://github.com/kdeldycke/repomatic/blob/main/changelog.md
60
60
  Project-URL: Documentation, https://kdeldycke.github.io/repomatic
61
- Project-URL: Download, https://github.com/kdeldycke/repomatic/releases/tag/v7.1.0
61
+ Project-URL: Download, https://github.com/kdeldycke/repomatic/releases/tag/v7.2.0
62
62
  Project-URL: Funding, https://github.com/sponsors/kdeldycke
63
63
  Project-URL: Homepage, https://github.com/kdeldycke/repomatic
64
64
  Project-URL: Issues, https://github.com/kdeldycke/repomatic/issues
@@ -1,11 +1,11 @@
1
1
  [build-system]
2
2
  build-backend = "uv_build"
3
- requires = [ "uv-build>=0.9" ]
3
+ requires = [ "uv-build>=0.9,<0.12" ]
4
4
 
5
5
  [project]
6
6
  # Docs: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
7
7
  name = "repomatic"
8
- version = "7.1.0"
8
+ version = "7.2.0"
9
9
  description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
10
10
  readme = "readme.md"
11
11
  keywords = [
@@ -122,7 +122,7 @@ dependencies = [
122
122
  ]
123
123
  urls.Changelog = "https://github.com/kdeldycke/repomatic/blob/main/changelog.md"
124
124
  urls.Documentation = "https://kdeldycke.github.io/repomatic"
125
- urls.Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.1.0"
125
+ urls.Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.2.0"
126
126
  urls.Funding = "https://github.com/sponsors/kdeldycke"
127
127
  urls.Homepage = "https://github.com/kdeldycke/repomatic"
128
128
  urls.Issues = "https://github.com/kdeldycke/repomatic/issues"
@@ -206,7 +206,7 @@ exclude-newer = "1 week"
206
206
  # `exclude-newer`, returning the package to the normal cooldown. repomatic is
207
207
  # pinned to git main (no PyPI release to freeze), so it keeps a permanent
208
208
  # "0 day" span.
209
- exclude-newer-package = { click-extra = "2026-07-10T00:00:00Z", repomatic = "0 day" }
209
+ exclude-newer-package = { repomatic = "0 day" }
210
210
  # Package is at root level, not in "./src/".
211
211
  build-backend.module-root = ""
212
212
 
@@ -219,7 +219,7 @@ copyright = "Kevin Deldycke <kevin@deldycke.com> and contributors. Distributed u
219
219
  file-description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
220
220
  # Numeric only: Nuitka rejects PEP 440 .devN suffixes. Kept in sync with the
221
221
  # package version by the dedicated [tool.bumpversion] rule that strips the suffix.
222
- file-version = "7.1.0"
222
+ file-version = "7.2.0"
223
223
  include-data-dir = [
224
224
  "repomatic/data/awesome_template=repomatic/data/awesome_template",
225
225
  ]
@@ -237,7 +237,7 @@ include-package-data = [ "click_extra" ]
237
237
  linux-icon = "docs/assets/icon.png"
238
238
  macos-app-icon = "docs/assets/icon.icns"
239
239
  product-name = "Repomatic"
240
- product-version = "7.1.0"
240
+ product-version = "7.2.0"
241
241
  windows-icon-from-ico = "docs/assets/icon.ico"
242
242
 
243
243
  [tool.ruff]
@@ -276,7 +276,14 @@ default.extend-identifiers = { Github = "GitHub", IOS = "iOS", Javascript = "Jav
276
276
  # False positives.
277
277
  default.extend-words = { astroid = "astroid", oject = "oject", PNGs = "PNGs" }
278
278
  # Skip content between <!-- typos:off --> and <!-- typos:on --> markers.
279
- default.extend-ignore-re = [ "(?s)<!-- typos:off -->.*?<!-- typos:on -->" ]
279
+ default.extend-ignore-re = [
280
+ "(?s)<!-- typos:off -->.*?<!-- typos:on -->",
281
+ # `{qeury}` is the intentional-typo example in the typos tool's own docs_notes
282
+ # guidance (tool_runner.py), which recommends downstream projects guard such
283
+ # examples and encoded hashes from the unattended fix-typos pull request.
284
+ "\\{qeury\\}",
285
+ 'base32 "[0-9a-z]{52}"',
286
+ ]
280
287
  default.extend-ignore-identifiers-re = [
281
288
  # error: `certifi` should be `certify`, `certifies`, `certified`
282
289
  # ╭▸ ./docs/assets/dependencies.mmd:184:11
@@ -344,7 +351,7 @@ run.source = [ "repomatic" ]
344
351
  report.precision = 2
345
352
 
346
353
  [tool.bumpversion]
347
- current_version = "7.1.0"
354
+ current_version = "7.2.0"
348
355
  # Parse versions with an optional .devN suffix (PEP 440).
349
356
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\.dev(?P<dev>\\d+))?"
350
357
  serialize = [
@@ -399,9 +406,13 @@ search = "## [`{current_version}` (unreleased)]("
399
406
  replace = "## [`{new_version}` (unreleased)]("
400
407
 
401
408
  [[tool.bumpversion.files]]
402
- # Update the version in the citation file.
409
+ # Update the version in the citation file. Anchored to a line start (regex) so
410
+ # it does not also match the `cff-version:` schema field, which would otherwise
411
+ # be overwritten with the package version on every bump as soon as the two
412
+ # values coincide once.
403
413
  filename = "./citation.cff"
404
- search = "version: {current_version}"
414
+ regex = true
415
+ search = "(?m)^version: {current_version}"
405
416
  replace = "version: {new_version}"
406
417
 
407
418
  [[tool.bumpversion.files]]
@@ -17,5 +17,5 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = "7.1.0"
21
- __git_tag_sha__ = "8d3bdd26cb0a91c422f1b7210cb978ba75865822"
20
+ __version__ = "7.2.0"
21
+ __git_tag_sha__ = "7336863d9f3f5faea26f887211f3f745dc963fb5"
@@ -21,7 +21,7 @@ GitHub issue. Sphinx linkcheck parsing detects broken auto-generated links
21
21
  (intersphinx, autodoc, type annotations) that Lychee cannot see because they
22
22
  only exist in the rendered HTML output.
23
23
 
24
- Issue lifecycle management is delegated to {mod}`~repomatic.issue`.
24
+ Issue lifecycle management is delegated to {mod}`~repomatic.github.issue`.
25
25
  """
26
26
 
27
27
  from __future__ import annotations
@@ -55,6 +55,31 @@ from extra_platforms import is_macos, is_windows
55
55
 
56
56
  from .config import load_repomatic_config
57
57
 
58
+ TYPE_CHECKING = False
59
+ if TYPE_CHECKING:
60
+ from collections.abc import Callable
61
+ from typing import Any
62
+
63
+
64
+ def _atomic_write(dest: Path, prefix: str, write: Callable[[Path], object]) -> None:
65
+ """Write *dest* atomically: temp file in the target directory, then rename.
66
+
67
+ The rename is atomic on POSIX (same-filesystem rename) and safe on Windows
68
+ (`Path.replace` overwrites atomically). *write* receives the temp path and
69
+ fills it (its return value is ignored, so `write_text`/`write_bytes` pass
70
+ straight through); partial writes are cleaned up on any failure.
71
+ """
72
+ dest.parent.mkdir(parents=True, exist_ok=True)
73
+ fd, tmp = tempfile.mkstemp(dir=dest.parent, prefix=prefix, suffix=".tmp")
74
+ try:
75
+ os.close(fd)
76
+ write(Path(tmp))
77
+ Path(tmp).replace(dest)
78
+ except BaseException:
79
+ # Clean up partial writes on any failure.
80
+ Path(tmp).unlink(missing_ok=True)
81
+ raise
82
+
58
83
 
59
84
  @dataclass(frozen=True)
60
85
  class CacheEntry:
@@ -235,17 +260,8 @@ def store_binary(
235
260
  :return: Path to the cached binary.
236
261
  """
237
262
  dest = cached_binary_path(name, version, platform_key, source.name)
238
- dest.parent.mkdir(parents=True, exist_ok=True)
239
263
 
240
- # Atomic write: temp file in same directory, then rename.
241
- fd, tmp = tempfile.mkstemp(
242
- dir=dest.parent,
243
- prefix=f".{source.name}.",
244
- suffix=".tmp",
245
- )
246
- try:
247
- os.close(fd)
248
- tmp_path = Path(tmp)
264
+ def fill(tmp_path: Path) -> None:
249
265
  shutil.copy2(source, tmp_path)
250
266
  tmp_path.chmod(0o755)
251
267
  # copy2 preserves the source mtime, which for a binary extracted from
@@ -254,12 +270,8 @@ def store_binary(
254
270
  # auto_purge() call below, before this store even returns. Stamp the
255
271
  # store time instead.
256
272
  os.utime(tmp_path)
257
- tmp_path.replace(dest)
258
- except BaseException:
259
- # Clean up partial writes on any failure.
260
- Path(tmp).unlink(missing_ok=True)
261
- raise
262
273
 
274
+ _atomic_write(dest, f".{source.name}.", fill)
263
275
  logging.debug("Cached %s %s for %s at %s.", name, version, platform_key, dest)
264
276
  auto_purge()
265
277
  return dest
@@ -322,30 +334,14 @@ def clear_cache(
322
334
  if not bin_root.is_dir():
323
335
  return 0, 0
324
336
 
325
- cutoff = time.time() - (max_age_days * 86400) if max_age_days is not None else None
326
- files_deleted = 0
327
- bytes_freed = 0
328
-
329
- for entry in cache_info():
330
- if tool is not None and entry.tool != tool:
331
- continue
332
- if cutoff is not None and entry.mtime >= cutoff:
333
- continue
334
- logging.debug("Purging cached binary: %s", entry.path)
335
- try:
336
- bytes_freed += entry.size
337
- entry.path.unlink()
338
- # Remove the .sha256 sidecar if present.
339
- sidecar = entry.path.with_suffix(entry.path.suffix + ".sha256")
340
- if sidecar.is_file():
341
- sidecar.unlink()
342
- files_deleted += 1
343
- except OSError:
344
- logging.debug("Failed to remove %s.", entry.path)
345
-
346
- # Prune empty parent directories up to bin_root.
347
- _prune_empty_dirs(bin_root)
348
- return files_deleted, bytes_freed
337
+ return _purge(
338
+ cache_info(),
339
+ bin_root,
340
+ keep=lambda entry: (
341
+ (tool is not None and entry.tool != tool) or _is_fresh(entry, max_age_days)
342
+ ),
343
+ sidecars=True,
344
+ )
349
345
 
350
346
 
351
347
  # ---------------------------------------------------------------------------
@@ -405,20 +401,7 @@ def store_response(
405
401
  """
406
402
  dest = _http_dir() / namespace / f"{key}.json"
407
403
  try:
408
- dest.parent.mkdir(parents=True, exist_ok=True)
409
- fd, tmp = tempfile.mkstemp(
410
- dir=dest.parent,
411
- prefix=".response.",
412
- suffix=".tmp",
413
- )
414
- try:
415
- os.close(fd)
416
- tmp_path = Path(tmp)
417
- tmp_path.write_bytes(data)
418
- tmp_path.replace(dest)
419
- except BaseException:
420
- Path(tmp).unlink(missing_ok=True)
421
- raise
404
+ _atomic_write(dest, ".response.", lambda tmp_path: tmp_path.write_bytes(data))
422
405
  except OSError:
423
406
  logging.debug("Failed to cache HTTP response: %s/%s.", namespace, key)
424
407
  return None
@@ -478,25 +461,14 @@ def clear_http_cache(
478
461
  if not http_root.is_dir():
479
462
  return 0, 0
480
463
 
481
- cutoff = time.time() - (max_age_days * 86400) if max_age_days is not None else None
482
- files_deleted = 0
483
- bytes_freed = 0
484
-
485
- for entry in http_cache_info():
486
- if namespace is not None and entry.namespace != namespace:
487
- continue
488
- if cutoff is not None and entry.mtime >= cutoff:
489
- continue
490
- logging.debug("Purging cached response: %s", entry.path)
491
- try:
492
- bytes_freed += entry.size
493
- entry.path.unlink()
494
- files_deleted += 1
495
- except OSError:
496
- logging.debug("Failed to remove %s.", entry.path)
497
-
498
- _prune_empty_dirs(http_root)
499
- return files_deleted, bytes_freed
464
+ return _purge(
465
+ http_cache_info(),
466
+ http_root,
467
+ keep=lambda entry: (
468
+ (namespace is not None and entry.namespace != namespace)
469
+ or _is_fresh(entry, max_age_days)
470
+ ),
471
+ )
500
472
 
501
473
 
502
474
  # ---------------------------------------------------------------------------
@@ -528,20 +500,11 @@ def store_config(
528
500
  """
529
501
  dest = _config_dir() / tool_name / filename
530
502
  try:
531
- dest.parent.mkdir(parents=True, exist_ok=True)
532
- fd, tmp = tempfile.mkstemp(
533
- dir=dest.parent,
534
- prefix=f".{filename}.",
535
- suffix=".tmp",
503
+ _atomic_write(
504
+ dest,
505
+ f".{filename}.",
506
+ lambda tmp_path: tmp_path.write_text(content, encoding="UTF-8"),
536
507
  )
537
- try:
538
- os.close(fd)
539
- tmp_path = Path(tmp)
540
- tmp_path.write_text(content, encoding="UTF-8")
541
- tmp_path.replace(dest)
542
- except BaseException:
543
- Path(tmp).unlink(missing_ok=True)
544
- raise
545
508
  except OSError:
546
509
  logging.debug("Failed to cache config for %s.", tool_name)
547
510
  return None
@@ -592,21 +555,64 @@ def clear_config_cache(
592
555
  if not config_root.is_dir():
593
556
  return 0, 0
594
557
 
558
+ return _purge(
559
+ config_cache_info(),
560
+ config_root,
561
+ keep=lambda entry: tool is not None and entry.tool != tool,
562
+ )
563
+
564
+
565
+ def _is_fresh(
566
+ entry: CacheEntry | HttpCacheEntry | ConfigCacheEntry,
567
+ max_age_days: int | None,
568
+ ) -> bool:
569
+ """Whether *entry* is younger than the age cutoff.
570
+
571
+ A `None` cutoff keeps nothing: age-unfiltered clears delete every entry
572
+ the caller's other filters matched.
573
+ """
574
+ if max_age_days is None:
575
+ return False
576
+ return entry.mtime >= time.time() - max_age_days * 86400
577
+
578
+
579
+ def _purge(
580
+ entries: list[CacheEntry] | list[HttpCacheEntry] | list[ConfigCacheEntry],
581
+ root: Path,
582
+ *,
583
+ keep: Callable[[Any], bool],
584
+ sidecars: bool = False,
585
+ ) -> tuple[int, int]:
586
+ """Delete the cache *entries* not spared by the *keep* predicate.
587
+
588
+ Shared delete loop behind {func}`clear_cache`, {func}`clear_http_cache`,
589
+ and {func}`clear_config_cache`. Empty parent directories under *root* are
590
+ pruned afterwards.
591
+
592
+ :param entries: Candidate entries from one of the `*_cache_info()` listers.
593
+ :param root: The cache subtree the entries live in.
594
+ :param keep: Entries for which this returns `True` are left untouched.
595
+ :param sidecars: Also remove each entry's `.sha256` sidecar (binary cache).
596
+ :return: Tuple of (files_deleted, bytes_freed).
597
+ """
595
598
  files_deleted = 0
596
599
  bytes_freed = 0
597
-
598
- for entry in config_cache_info():
599
- if tool is not None and entry.tool != tool:
600
+ for entry in entries:
601
+ if keep(entry):
600
602
  continue
601
- logging.debug("Purging cached config: %s", entry.path)
603
+ logging.debug("Purging cache entry: %s", entry.path)
602
604
  try:
603
605
  bytes_freed += entry.size
604
606
  entry.path.unlink()
607
+ if sidecars:
608
+ sidecar = entry.path.with_suffix(entry.path.suffix + ".sha256")
609
+ if sidecar.is_file():
610
+ sidecar.unlink()
605
611
  files_deleted += 1
606
612
  except OSError:
607
613
  logging.debug("Failed to remove %s.", entry.path)
608
614
 
609
- _prune_empty_dirs(config_root)
615
+ _prune_empty_dirs(root)
610
616
  return files_deleted, bytes_freed
611
617
 
612
618
 
@@ -102,7 +102,7 @@ of this code *and* adding a new dependency — more complexity, not less.
102
102
  Related modules
103
103
  ^^^^^^^^^^^^^^^
104
104
 
105
- - `release_prep.py` orchestrates the full release preparation
105
+ - `prepare_release.py` orchestrates the full release preparation
106
106
  across changelog, citation, and workflow files, delegating
107
107
  changelog operations to this module.
108
108
  - `metadata.py` handles version bump eligibility checks and
@@ -124,6 +124,7 @@ from packaging.version import Version
124
124
 
125
125
  from .git_ops import get_all_version_tags, get_tag_date
126
126
  from .github.actions import AnnotationLevel, emit_annotation
127
+ from .github.pr_body import render_template
127
128
  from .github.releases import (
128
129
  GitHubRelease,
129
130
  GitHubReleasesUnavailable,
@@ -292,9 +293,6 @@ class Changelog:
292
293
  if not self.current_version:
293
294
  return self.content.rstrip()
294
295
 
295
- # Lazy import to avoid circular dependency: pr_body → metadata → changelog.
296
- from .github.pr_body import render_template
297
-
298
296
  elements = self.decompose_version(self.current_version)
299
297
 
300
298
  # Idempotent: skip if an unreleased section already exists.
@@ -350,9 +348,6 @@ class Changelog:
350
348
  if not self.current_version:
351
349
  return False
352
350
 
353
- # Lazy import to avoid circular dependency: pr_body → metadata → changelog.
354
- from .github.pr_body import render_template
355
-
356
351
  elements = self.decompose_version(self.current_version)
357
352
  if not elements.version:
358
353
  return False
@@ -488,9 +483,6 @@ class Changelog:
488
483
  lower_version = v
489
484
  break
490
485
 
491
- # Lazy import to avoid circular dependency: pr_body → metadata → changelog.
492
- from .github.pr_body import render_template
493
-
494
486
  compare_base = f"v{lower_version}" if lower_version else "v0.0.0"
495
487
  elements = VersionElements(
496
488
  compare_url=f"{repo_url}/compare/{compare_base}...v{version}",
@@ -687,8 +679,6 @@ def build_release_admonition(
687
679
  links.append(f"[{GITHUB_LABEL}]({github_url})")
688
680
  if not links:
689
681
  return ""
690
- # Lazy import to avoid circular dependency: pr_body → metadata → changelog.
691
- from .github.pr_body import render_template
692
682
 
693
683
  platforms = " and ".join(links)
694
684
  verb = FIRST_AVAILABLE_VERB if first_on_all else AVAILABLE_VERB
@@ -718,8 +708,6 @@ def build_unavailable_admonition(
718
708
  names.append(GITHUB_LABEL)
719
709
  if not names:
720
710
  return ""
721
- # Lazy import to avoid circular dependency: pr_body → metadata → changelog.
722
- from .github.pr_body import render_template
723
711
 
724
712
  platforms = " and ".join(names)
725
713
  return render_template(
@@ -1133,9 +1121,6 @@ def lint_changelog_dates(
1133
1121
  # decomposing and re-rendering an already-correct section is a
1134
1122
  # no-op.
1135
1123
  if fix:
1136
- # Lazy import to avoid circular dependency: pr_body → metadata → changelog.
1137
- from .github.pr_body import render_template
1138
-
1139
1124
  for version, _date in releases:
1140
1125
  elements = changelog.decompose_version(version)
1141
1126
 
@@ -1235,3 +1220,48 @@ def lint_changelog_dates(
1235
1220
  if fix and modified:
1236
1221
  return 0
1237
1222
  return 1 if has_mismatch else 0
1223
+
1224
+
1225
+ def build_expected_body(
1226
+ changelog: Changelog,
1227
+ version: str,
1228
+ *,
1229
+ admonition_override: str | None = None,
1230
+ ) -> str:
1231
+ """Build the expected release body from the changelog.
1232
+
1233
+ Decomposes the changelog section into discrete elements and renders
1234
+ them through the `github-releases` template. This allows the
1235
+ GitHub release body to include a different subset of elements than
1236
+ the `release-notes` template used for `changelog.md` entries.
1237
+
1238
+ :param changelog: Parsed changelog instance.
1239
+ :param version: Version string (e.g. `1.2.3`).
1240
+ :param admonition_override: If provided, replaces the
1241
+ `availability_admonition` from the changelog. Used by
1242
+ `release_notes_with_admonition` to inject a pre-computed
1243
+ admonition at release time.
1244
+ :return: The rendered release body, or empty string if the
1245
+ version has no changelog section.
1246
+ """
1247
+ elements = changelog.decompose_version(version)
1248
+ if (
1249
+ not elements.changes
1250
+ and not elements.availability_admonition
1251
+ and not elements.development_warning
1252
+ and not elements.editorial_admonition
1253
+ and not elements.yanked_admonition
1254
+ ):
1255
+ return ""
1256
+
1257
+ if admonition_override is not None:
1258
+ elements.availability_admonition = admonition_override
1259
+ # Extract tag range from compare URL (e.g. "v1.1.0...v2.0.0").
1260
+ tag_range = (
1261
+ elements.compare_url.rsplit("/compare/", 1)[-1] if elements.compare_url else ""
1262
+ )
1263
+ return render_template(
1264
+ "github-releases",
1265
+ **asdict(elements),
1266
+ tag_range=tag_range,
1267
+ )