repomatic 7.8.0__tar.gz → 7.9.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 (173) hide show
  1. {repomatic-7.8.0 → repomatic-7.9.0}/PKG-INFO +2 -2
  2. {repomatic-7.8.0 → repomatic-7.9.0}/pyproject.toml +5 -5
  3. {repomatic-7.8.0 → repomatic-7.9.0}/pyproject.toml.orig +5 -5
  4. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/__init__.py +2 -2
  5. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/cli.py +48 -3
  6. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/config.py +15 -0
  7. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/_release-engine.yaml +147 -21
  8. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/agent-sphinx-docs.md +10 -7
  9. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/autofix.yaml +46 -45
  10. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/autolock.yaml +4 -3
  11. repomatic-7.9.0/repomatic/data/awesome_template/.github/code-of-conduct.md +43 -0
  12. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/cancel-runs.yaml +5 -4
  13. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/changelog.yaml +18 -17
  14. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/debug.yaml +6 -5
  15. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/docs.yaml +23 -7
  16. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/labels.yaml +10 -9
  17. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/lint.yaml +13 -12
  18. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/release.yaml +23 -5
  19. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/babysit-ci/SKILL.md +3 -3
  20. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/repomatic-deps/SKILL.md +1 -1
  21. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/repomatic-ship/SKILL.md +6 -2
  22. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/tests.yaml +8 -7
  23. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/unsubscribe.yaml +5 -4
  24. repomatic-7.9.0/repomatic/data/yamllint.yaml +12 -0
  25. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/gh.py +40 -21
  26. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/token.py +16 -0
  27. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/workflow_sync.py +26 -7
  28. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/gitignore.py +38 -0
  29. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/init_project.py +65 -0
  30. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/lint_repo.py +48 -13
  31. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/metadata.py +112 -27
  32. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/prepare_release.py +13 -2
  33. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/setup_guide.py +41 -18
  34. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide-token.md +2 -1
  35. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/tool_registry.py +4 -1
  36. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/tool_runner.py +115 -6
  37. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/version_sync.py +8 -1
  38. repomatic-7.8.0/repomatic/data/awesome_template/.github/code-of-conduct.md +0 -70
  39. repomatic-7.8.0/repomatic/data/yamllint.yaml +0 -6
  40. {repomatic-7.8.0 → repomatic-7.9.0}/license +0 -0
  41. {repomatic-7.8.0 → repomatic-7.9.0}/readme.md +0 -0
  42. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/__main__.py +0 -0
  43. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/attestation.py +0 -0
  44. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/awesome_toc.py +0 -0
  45. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/binaries_page.py +0 -0
  46. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/binary.py +0 -0
  47. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/broken_links.py +0 -0
  48. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/bundle.py +0 -0
  49. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/cache.py +0 -0
  50. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/changelog.py +0 -0
  51. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/checksums.py +0 -0
  52. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/compat.py +0 -0
  53. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/__init__.py +0 -0
  54. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/action-publish-pypi.yaml +0 -0
  55. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/agent-grunt-qa.md +0 -0
  56. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/agent-qa-engineer.md +0 -0
  57. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/config.yml +0 -0
  58. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/new-link.yaml +0 -0
  59. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/awesome_template/.github/contributing.md +0 -0
  60. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/awesome_template/.github/contributing.zh.md +0 -0
  61. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/awesome_template/.github/funding.yml +0 -0
  62. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/awesome_template/.github/pull_request_template.md +0 -0
  63. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/awesome_template/__init__.py +0 -0
  64. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/awesome_template/license +0 -0
  65. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/bumpversion.toml +0 -0
  66. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/coverage.toml +0 -0
  67. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/labeller-content-based.yaml +0 -0
  68. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/labeller-file-based.yaml +0 -0
  69. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/labels.toml +0 -0
  70. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/lychee.toml +0 -0
  71. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/mdformat.toml +0 -0
  72. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/mypy.toml +0 -0
  73. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/pytest.toml +0 -0
  74. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/ruff.toml +0 -0
  75. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/av-false-positive/SKILL.md +0 -0
  76. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/awesome-triage/SKILL.md +0 -0
  77. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/benchmark-update/SKILL.md +0 -0
  78. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/brand-assets/SKILL.md +0 -0
  79. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/file-bug-report/SKILL.md +0 -0
  80. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/github-housekeeping/SKILL.md +0 -0
  81. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/repomatic-audit/SKILL.md +0 -0
  82. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/repomatic-changelog/SKILL.md +0 -0
  83. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/repomatic-init/SKILL.md +0 -0
  84. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/repomatic-topics/SKILL.md +0 -0
  85. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/sphinx-docs-sync/SKILL.md +0 -0
  86. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/translation-sync/SKILL.md +0 -0
  87. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/skills/upstream-audit/SKILL.md +0 -0
  88. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/typos.toml +0 -0
  89. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/uv.toml +0 -0
  90. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/vt-trend-chart.js +0 -0
  91. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/data/zizmor.yaml +0 -0
  92. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/dep_graph.py +0 -0
  93. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/dep_report.py +0 -0
  94. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/dep_sources.py +0 -0
  95. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/docs.py +0 -0
  96. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/frontmatter.py +0 -0
  97. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/git_ops.py +0 -0
  98. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/__init__.py +0 -0
  99. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/actions.py +0 -0
  100. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/dev_release.py +0 -0
  101. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/issue.py +0 -0
  102. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/matrix.py +0 -0
  103. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/pr_body.py +0 -0
  104. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/release_sync.py +0 -0
  105. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/releases.py +0 -0
  106. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/sponsor.py +0 -0
  107. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/status.py +0 -0
  108. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/github/unsubscribe.py +0 -0
  109. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/http.py +0 -0
  110. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/humanize.py +0 -0
  111. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/images.py +0 -0
  112. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/labels.py +0 -0
  113. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/mailmap.py +0 -0
  114. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/matrix_axes.py +0 -0
  115. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/npm.py +0 -0
  116. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/plugin.py +0 -0
  117. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/py.typed +0 -0
  118. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/pypi.py +0 -0
  119. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/pyproject.py +0 -0
  120. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/registry.py +0 -0
  121. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/sync_ops.py +0 -0
  122. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/__init__.py +0 -0
  123. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/available-admonition.md +0 -0
  124. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/broken-links-issue.md +0 -0
  125. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/bump-version.md +0 -0
  126. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/detect-squash-merge.md +0 -0
  127. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/development-warning.md +0 -0
  128. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/fix-changelog.md +0 -0
  129. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/fix-typos.md +0 -0
  130. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/fix-vulnerable-deps.md +0 -0
  131. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/format-images.md +0 -0
  132. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/format-json.md +0 -0
  133. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/format-markdown.md +0 -0
  134. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/format-pyproject.md +0 -0
  135. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/format-python.md +0 -0
  136. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/format-shell.md +0 -0
  137. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/generated-footer.md +0 -0
  138. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/github-releases.md +0 -0
  139. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/immutable-releases.md +0 -0
  140. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/pr-metadata.md.noformat +0 -0
  141. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/prepare-release.md.noformat +0 -0
  142. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/refresh-tip.md +0 -0
  143. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/release-notes.md +0 -0
  144. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/release-sync-report.md +0 -0
  145. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide-branch-ruleset.md +0 -0
  146. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide-dependabot.md +0 -0
  147. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide-fork-pr-approval.md +0 -0
  148. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide-notifications-pat.md +0 -0
  149. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide-pages-source.md +0 -0
  150. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide-pypi-trusted-publisher.md +0 -0
  151. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide-sha-pinning-required.md +0 -0
  152. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide-verify.md +0 -0
  153. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide-virustotal.md +0 -0
  154. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/setup-guide.md +0 -0
  155. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/sync-action-pins.md +0 -0
  156. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/sync-bumpversion.md +0 -0
  157. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/sync-dep-sources.md +0 -0
  158. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/sync-gitignore.md +0 -0
  159. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/sync-mailmap.md +0 -0
  160. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/sync-repomatic.md +0 -0
  161. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/sync-tool-versions.md +0 -0
  162. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/sync-uv-lock.md +0 -0
  163. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/sync-workflow-pins.md +0 -0
  164. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/unavailable-admonition.md +0 -0
  165. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/unsubscribe-phase1.md +0 -0
  166. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/unsubscribe-phase2.md +0 -0
  167. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/update-dep-graph.md +0 -0
  168. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/update-docs.md +0 -0
  169. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/templates/yanked-admonition.md +0 -0
  170. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/tool_runner_page.py +0 -0
  171. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/uv.py +0 -0
  172. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/virustotal.py +0 -0
  173. {repomatic-7.8.0 → repomatic-7.9.0}/repomatic/vulnerable_deps.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: repomatic
3
- Version: 7.8.0
3
+ Version: 7.9.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
@@ -59,7 +59,7 @@ Requires-Dist: wcmatch>=10
59
59
  Requires-Python: >=3.10
60
60
  Project-URL: Changelog, https://github.com/kdeldycke/repomatic/blob/main/changelog.md
61
61
  Project-URL: Documentation, https://kdeldycke.github.io/repomatic
62
- Project-URL: Download, https://github.com/kdeldycke/repomatic/releases/tag/v7.8.0
62
+ Project-URL: Download, https://github.com/kdeldycke/repomatic/releases/tag/v7.9.0
63
63
  Project-URL: Funding, https://github.com/sponsors/kdeldycke
64
64
  Project-URL: Homepage, https://github.com/kdeldycke/repomatic
65
65
  Project-URL: Issues, https://github.com/kdeldycke/repomatic/issues
@@ -4,7 +4,7 @@ requires = ["uv-build>=0.8"]
4
4
 
5
5
  [project]
6
6
  name = "repomatic"
7
- version = "7.8.0"
7
+ version = "7.9.0"
8
8
  description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
9
9
  readme = "readme.md"
10
10
  keywords = [
@@ -92,7 +92,7 @@ email = "kevin@deldycke.com"
92
92
  [project.urls]
93
93
  Changelog = "https://github.com/kdeldycke/repomatic/blob/main/changelog.md"
94
94
  Documentation = "https://kdeldycke.github.io/repomatic"
95
- Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.8.0"
95
+ Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.9.0"
96
96
  Funding = "https://github.com/sponsors/kdeldycke"
97
97
  Homepage = "https://github.com/kdeldycke/repomatic"
98
98
  Issues = "https://github.com/kdeldycke/repomatic/issues"
@@ -146,7 +146,7 @@ module-root = ""
146
146
  [tool.nuitka]
147
147
  copyright = "Kevin Deldycke <kevin@deldycke.com> and contributors. Distributed under GPL-2.0-or-later license."
148
148
  file-description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
149
- file-version = "7.8.0"
149
+ file-version = "7.9.0"
150
150
  include-data-dir = [
151
151
  "repomatic/data/awesome_template=repomatic/data/awesome_template",
152
152
  "repomatic/data/skills=repomatic/data/skills",
@@ -156,7 +156,7 @@ include-package-data = ["click_extra"]
156
156
  linux-icon = "docs/assets/icon.png"
157
157
  macos-app-icon = "docs/assets/icon.icns"
158
158
  product-name = "Repomatic"
159
- product-version = "7.8.0"
159
+ product-version = "7.9.0"
160
160
  windows-icon-from-ico = "docs/assets/icon.ico"
161
161
 
162
162
  [tool.ruff]
@@ -250,7 +250,7 @@ fail_under = 80
250
250
  precision = 2
251
251
 
252
252
  [tool.bumpversion]
253
- current_version = "7.8.0"
253
+ current_version = "7.9.0"
254
254
  parse = '(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.dev(?P<dev>\d+))?'
255
255
  serialize = [
256
256
  "{major}.{minor}.{patch}.dev{dev}",
@@ -12,7 +12,7 @@ requires = [ "uv-build>=0.8" ]
12
12
  [project]
13
13
  # Docs: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
14
14
  name = "repomatic"
15
- version = "7.8.0"
15
+ version = "7.9.0"
16
16
  description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
17
17
  readme = "readme.md"
18
18
  keywords = [
@@ -145,7 +145,7 @@ dependencies = [
145
145
  ]
146
146
  urls.Changelog = "https://github.com/kdeldycke/repomatic/blob/main/changelog.md"
147
147
  urls.Documentation = "https://kdeldycke.github.io/repomatic"
148
- urls.Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.8.0"
148
+ urls.Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.9.0"
149
149
  urls.Funding = "https://github.com/sponsors/kdeldycke"
150
150
  urls.Homepage = "https://github.com/kdeldycke/repomatic"
151
151
  urls.Issues = "https://github.com/kdeldycke/repomatic/issues"
@@ -250,7 +250,7 @@ copyright = "Kevin Deldycke <kevin@deldycke.com> and contributors. Distributed u
250
250
  file-description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
251
251
  # Numeric only: Nuitka rejects PEP 440 .devN suffixes. Kept in sync with the
252
252
  # package version by the dedicated [tool.bumpversion] rule that strips the suffix.
253
- file-version = "7.8.0"
253
+ file-version = "7.9.0"
254
254
  include-data-dir = [
255
255
  "repomatic/data/awesome_template=repomatic/data/awesome_template",
256
256
  "repomatic/data/skills=repomatic/data/skills",
@@ -269,7 +269,7 @@ include-package-data = [ "click_extra" ]
269
269
  linux-icon = "docs/assets/icon.png"
270
270
  macos-app-icon = "docs/assets/icon.icns"
271
271
  product-name = "Repomatic"
272
- product-version = "7.8.0"
272
+ product-version = "7.9.0"
273
273
  windows-icon-from-ico = "docs/assets/icon.ico"
274
274
 
275
275
  [tool.ruff]
@@ -407,7 +407,7 @@ report.fail_under = 80
407
407
  report.precision = 2
408
408
 
409
409
  [tool.bumpversion]
410
- current_version = "7.8.0"
410
+ current_version = "7.9.0"
411
411
  # Parse versions with an optional .devN suffix (PEP 440).
412
412
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\.dev(?P<dev>\\d+))?"
413
413
  serialize = [
@@ -17,5 +17,5 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = "7.8.0"
21
- __git_tag_sha__ = "ab43527b67361553b1c8539e8f3d9e6e2013b98c"
20
+ __version__ = "7.9.0"
21
+ __git_tag_sha__ = "292c4e8438249adeb269808d485409c9b56b49bd"
@@ -104,7 +104,12 @@ from .docs import update_docs as _update_docs
104
104
  from .frontmatter import split_frontmatter
105
105
  from .git_ops import commit_and_push_files, create_and_push_tag
106
106
  from .github import token as _token_mod, unsubscribe as _unsub_mod
107
- from .github.actions import cancel_superseded_runs, format_multiline_output
107
+ from .github.actions import (
108
+ AnnotationLevel,
109
+ cancel_superseded_runs,
110
+ emit_annotation,
111
+ format_multiline_output,
112
+ )
108
113
  from .github.dev_release import (
109
114
  cleanup_dev_releases as _cleanup_dev_releases,
110
115
  sync_dev_release as _sync_dev_release,
@@ -144,7 +149,7 @@ from .github.unsubscribe import (
144
149
  unsubscribe_threads as _unsubscribe_threads,
145
150
  )
146
151
  from .github.workflow_sync import run_workflow_lint
147
- from .gitignore import build_gitignore
152
+ from .gitignore import build_gitignore, orphaned_rules
148
153
  from .humanize import format_age, format_file_size
149
154
  from .images import (
150
155
  DEFAULT_MIN_SAVINGS_BYTES,
@@ -1244,8 +1249,16 @@ def close_stale_bump_pr(part: str) -> None:
1244
1249
  default=None,
1245
1250
  help=("Output path. Defaults to gitignore-location from [tool.repomatic] config."),
1246
1251
  )
1252
+ @option(
1253
+ "--drop-orphans/--no-drop-orphans",
1254
+ default=False,
1255
+ help=(
1256
+ "Overwrite rules found on disk but absent from the generated file, "
1257
+ "instead of refusing to drop them."
1258
+ ),
1259
+ )
1247
1260
  @pass_context
1248
- def sync_gitignore(ctx: Context, output_path: Path | None) -> None:
1261
+ def sync_gitignore(ctx: Context, output_path: Path | None, drop_orphans: bool) -> None:
1249
1262
  """Sync a .gitignore file from gitignore.io templates.
1250
1263
 
1251
1264
  Fetches templates for a base set of categories plus any extras from
@@ -1253,6 +1266,11 @@ def sync_gitignore(ctx: Context, output_path: Path | None) -> None:
1253
1266
  Writes to the path specified by gitignore-location (default
1254
1267
  ./.gitignore).
1255
1268
 
1269
+ The generated file is the whole file: nothing is read back from the copy on
1270
+ disk. A rule added there by hand is therefore dropped on the next sync, so
1271
+ the write aborts when it would lose one. Move the rule into
1272
+ gitignore-extra-content to keep it, or pass --drop-orphans to let it go.
1273
+
1256
1274
  \b
1257
1275
  Examples:
1258
1276
  # Generate .gitignore using config from pyproject.toml
@@ -1275,6 +1293,32 @@ def sync_gitignore(ctx: Context, output_path: Path | None) -> None:
1275
1293
  if output_path is None:
1276
1294
  output_path = Path(config.gitignore.location)
1277
1295
 
1296
+ # Compare against what is already there before overwriting it. Skipped for
1297
+ # stdout, which destroys nothing, and for a first run, which has nothing to
1298
+ # destroy.
1299
+ if not drop_orphans and str(output_path) != "-" and output_path.is_file():
1300
+ orphans = orphaned_rules(output_path.read_text(encoding="UTF-8"), content)
1301
+ if orphans:
1302
+ emit_annotation(
1303
+ AnnotationLevel.ERROR,
1304
+ f"{output_path}: sync would drop {len(orphans)} hand-added "
1305
+ f"rule(s): {', '.join(orphans)}",
1306
+ )
1307
+ echo(
1308
+ f"Refusing to overwrite {output_path}: "
1309
+ f"{len(orphans)} rule(s) on disk are absent from the generated "
1310
+ "file and would be lost.",
1311
+ err=True,
1312
+ )
1313
+ for rule in orphans:
1314
+ echo(f" {rule}", err=True)
1315
+ echo(
1316
+ "\nMove them into [tool.repomatic.gitignore] extra-content to "
1317
+ "keep them, or re-run with --drop-orphans to discard them.",
1318
+ err=True,
1319
+ )
1320
+ ctx.exit(1)
1321
+
1278
1322
  log_output_target(".gitignore", output_path)
1279
1323
 
1280
1324
  echo(content.rstrip(), file=prep_path(output_path))
@@ -3166,6 +3210,7 @@ def lint_repo(
3166
3210
  exit_code = run_repo_lint(
3167
3211
  package_name=package_name,
3168
3212
  repo_name=repo_name,
3213
+ is_package=metadata.is_python_package,
3169
3214
  is_sphinx=is_sphinx,
3170
3215
  project_description=project_description,
3171
3216
  keywords=keywords,
@@ -869,6 +869,21 @@ class Config:
869
869
  PAT with `notifications` scope stored as `REPOMATIC_NOTIFICATIONS_PAT`.
870
870
  """
871
871
 
872
+ nuitka_dev_targets: list[str] = field(
873
+ default_factory=lambda: ["linux-arm64"],
874
+ metadata={CONFIG_PATH_METADATA_KEY: "nuitka.dev-targets"},
875
+ )
876
+ """Nuitka build targets compiled on ordinary pushes, as a canary.
877
+
878
+ An ordinary push to the default branch rebuilds binaries only for these
879
+ targets: enough to catch a compilation break early, while freeing runner
880
+ slots the full fleet would occupy on every code push just to refresh the
881
+ rolling dev pre-release (a draft). The full target roster still builds on
882
+ release commits, on the weekly `schedule` trigger, and on
883
+ `workflow_dispatch`. Defaults to `["linux-arm64"]`, the fastest and
884
+ cheapest builder. Set to `[]` to skip dev builds entirely.
885
+ """
886
+
872
887
  nuitka_enabled: bool = field(
873
888
  default=True,
874
889
  metadata={CONFIG_PATH_METADATA_KEY: "nuitka.enabled"},
@@ -30,9 +30,10 @@ permissions: {}
30
30
  # Supply-chain cooldown: no package published within the window can be resolved by
31
31
  # any command in this workflow. Set here, not per command, so it also covers the
32
32
  # `metadata` bootstrap and any step added later; a workflow-level `env:` cannot
33
- # reference `needs`, so the window is a literal that tests/test_workflows.py holds
34
- # equal to `[tool.repomatic] minimum-release-age`. Deliberate bypasses are
35
- # per-package CLI flags (`--exclude-newer-package`, `--min-release-age-exclude`).
33
+ # reference `needs`, so the window is a literal kept equal to `[tool.repomatic]
34
+ # minimum-release-age`. repomatic's own test suite enforces that upstream; a
35
+ # synced copy is kept in step by hand. Deliberate bypasses are per-package CLI
36
+ # flags (`--exclude-newer-package`, `--min-release-age-exclude`).
36
37
  # See claude.md for the rationale.
37
38
  env:
38
39
  NPM_CONFIG_MIN_RELEASE_AGE: 7
@@ -64,7 +65,7 @@ jobs:
64
65
  - name: Run repomatic metadata
65
66
  id: metadata
66
67
  run: >
67
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' metadata
68
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' metadata
68
69
  --format github-json --output "$GITHUB_OUTPUT"
69
70
  new_commits_matrix release_commits_matrix build_targets nuitka_matrix
70
71
  nuitka_extras is_python_project package_name current_version
@@ -102,10 +103,28 @@ jobs:
102
103
  container: ${{ matrix.container }}
103
104
  # We keep going when a job flagged as not stable fails.
104
105
  continue-on-error: ${{ matrix.state == 'unstable' }}
106
+ # Headroom over the slowest measured cold-cache compile job (17.4 min on
107
+ # windows-x64); see docs/nuitka.md § Compile caching for the full table.
108
+ # Without it a hung compile squats a runner-cap slot for the 360-min default.
109
+ timeout-minutes: 45
105
110
  permissions:
106
111
  id-token: write
107
112
  attestations: write
108
113
  contents: read
114
+ env:
115
+ # Relocate every Nuitka cache (ccache/clcache objects, downloads,
116
+ # bytecode) under the workspace, so the cache step below and Nuitka
117
+ # agree on one path both on VM jobs and inside the manylinux containers.
118
+ NUITKA_CACHE_DIR: ${{ github.workspace }}/.nuitka-cache
119
+ # Two ccache settings cross-VM hits depend on: hash the compiler
120
+ # binary's content (the default mtime+size check trips on runner VMs,
121
+ # which do not keep stable file timestamps) and tolerate
122
+ # __DATE__/__TIME__ macros. The cache-prep step below also writes both
123
+ # into $CCACHE_DIR/ccache.conf, so whichever channel reaches the
124
+ # wrapper carries the same values. Nuitka's internal clcache on MSVC
125
+ # ignores both settings.
126
+ CCACHE_COMPILERCHECK: content
127
+ CCACHE_SLOPPINESS: time_macros
109
128
  steps:
110
129
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
111
130
  with:
@@ -113,6 +132,70 @@ jobs:
113
132
  - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
114
133
  with:
115
134
  version: "0.12.1"
135
+ # Persist the Nuitka compile caches across runs: ccache objects on gcc
136
+ # targets, Nuitka's internal clcache objects on MSVC, plus downloads
137
+ # and demoted-module bytecode. The per-commit key never hits exactly,
138
+ # so every run saves a fresh snapshot and restores the latest previous
139
+ # one via the prefix; ccache/clcache hash compiler, flags and sources
140
+ # themselves, so a stale snapshot degrades to cache misses, never to
141
+ # wrong objects. Two exclusions:
142
+ # - Release commits build cold, on purpose: a cache entry is writable
143
+ # by any code running in any main-ref workflow (the cache API is not
144
+ # gated by `permissions:`), and its restore is a tar extraction into
145
+ # the workspace, so a poisoned entry could reach a signed, published,
146
+ # immutable binary. Skipping restore AND save on release commits
147
+ # keeps every published artifact compiled from source on a fresh VM;
148
+ # canary and weekly runs keep the warm-cache speed.
149
+ # - macOS is excluded entirely: its ccache scored 0/470 cross-VM hits
150
+ # through every configuration channel tried (CCACHE_* env vars, then
151
+ # ccache.conf inside this very cache dir), so persisting it only paid
152
+ # restore/save overhead for entries that never hit. Capture Nuitka's
153
+ # CCACHE_LOGFILE from a scheduled run to diagnose before re-enabling.
154
+ - name: Restore Nuitka compile cache
155
+ if: >-
156
+ matrix.platform_id != 'macos'
157
+ && !fromJSON(needs.metadata.outputs.metadata).release_commits_matrix
158
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
159
+ with:
160
+ path: .nuitka-cache
161
+ key: nuitka-${{ matrix.target }}-${{ matrix.commit }}
162
+ restore-keys: nuitka-${{ matrix.target }}-
163
+ # Two duties. Trim: Nuitka's clcache has no size cap or pruning (ccache
164
+ # self-prunes at 5 GiB), so left alone a restore-then-save cycle grows
165
+ # the cache without bound; reset past 2 GiB and let the next save start
166
+ # a fresh lineage. Configure: write ccache's settings inside the cache
167
+ # dir itself, the one channel that reliably reaches the wrapper (ccache
168
+ # always reads $CCACHE_DIR/ccache.conf, while the job-level CCACHE_*
169
+ # env vars above die with scons's scrubbed compiler environment).
170
+ - name: Prepare Nuitka cache
171
+ if: >-
172
+ matrix.platform_id != 'macos'
173
+ && !fromJSON(needs.metadata.outputs.metadata).release_commits_matrix
174
+ shell: bash
175
+ run: |
176
+ if [[ -d .nuitka-cache ]]; then
177
+ size_mb=$(du -sm .nuitka-cache | cut -f1)
178
+ echo "Nuitka cache restored at ${size_mb} MiB."
179
+ if (( size_mb > 2048 )); then
180
+ echo "Cache exceeds 2 GiB, resetting."
181
+ rm -rf .nuitka-cache
182
+ fi
183
+ fi
184
+ mkdir -p .nuitka-cache/ccache
185
+ printf 'compiler_check = content\nsloppiness = time_macros\n' \
186
+ > .nuitka-cache/ccache/ccache.conf
187
+ # The manylinux containers ship no ccache (Nuitka warns and compiles
188
+ # cold), so the canary target pays full C compilation on every push.
189
+ # EPEL is a distro archive, exempt from the install cooldown (see
190
+ # claude.md § Distro archives are out of scope). Non-fatal on mirror
191
+ # hiccups: a release build must not die for a missing cache helper.
192
+ - name: Install ccache in container
193
+ if: matrix.container
194
+ shell: bash
195
+ run: >
196
+ (dnf install --assumeyes --quiet epel-release
197
+ && dnf install --assumeyes --quiet ccache)
198
+ || echo "ccache unavailable, building uncached."
116
199
  - name: Setup venv
117
200
  # --managed-python forces uv's own python-build-standalone interpreter,
118
201
  # whose deployment floors are the ones we declare and verify (macOS
@@ -150,7 +233,7 @@ jobs:
150
233
  uv --no-progress sync --frozen ${flags}
151
234
  - name: Pre-bake build metadata
152
235
  if: contains(matrix.current_version, '.dev')
153
- run: uvx --no-progress 'click-extra==8.8.0' prebake all
236
+ run: uvx --no-progress 'click-extra==8.8.1' prebake all
154
237
  - name: Build binary
155
238
  id: build-binary
156
239
  continue-on-error: true
@@ -162,6 +245,15 @@ jobs:
162
245
  # passes as a proper argv, so a plain word-split is enough here.
163
246
  env:
164
247
  NUITKA_PYTHON_FLAGS: ${{ matrix.nuitka_python_flags }}
248
+ # On macOS Nuitka auto-downloads a ccache binary from nuitka.net,
249
+ # with no checksum at the call site (SconsCaching.py's
250
+ # getCachedDownload passes none). Since the macOS cache is not
251
+ # persisted (0/470 cross-VM hits, see the cache step above), ccache
252
+ # brings zero benefit there and the unchecksummed download is pure
253
+ # supply-chain surface: disable it. Empty (and inert) elsewhere;
254
+ # Linux ccache and MSVC clcache are unaffected.
255
+ NUITKA_CCACHE_FLAG: >-
256
+ ${{ matrix.platform_id == 'macos' && '--disable-ccache' || '' }}
165
257
  BIN_NAME: ${{ matrix.bin_name }}
166
258
  MODULE_PATH: ${{ matrix.module_path }}
167
259
  # Pin the macOS floor: without it, compiled objects and processed
@@ -171,11 +263,11 @@ jobs:
171
263
  ${{ matrix.platform_id == 'macos' && matrix.min_os || '' }}
172
264
  shell: bash
173
265
  run: |
174
- # NUITKA_PYTHON_FLAGS holds the entry point's --python-flag (if any),
266
+ # NUITKA_PYTHON_FLAGS and NUITKA_CCACHE_FLAG hold optional flags,
175
267
  # meant to word-split.
176
268
  # shellcheck disable=SC2086
177
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' run nuitka -- \
178
- ${NUITKA_PYTHON_FLAGS} \
269
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' run nuitka -- \
270
+ ${NUITKA_PYTHON_FLAGS} ${NUITKA_CCACHE_FLAG} \
179
271
  --output-filename="${BIN_NAME}" "${MODULE_PATH}"
180
272
  - name: Upload Nuitka crash report
181
273
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@@ -183,6 +275,11 @@ jobs:
183
275
  name: nuitka-crash-report-${{ matrix.os }}-${{ matrix.short_sha }}.xml
184
276
  if-no-files-found: ignore
185
277
  path: nuitka-crash-report.xml
278
+ # Sizing signal for the cache strategy: hit/miss counts are in Nuitka's
279
+ # own "Cached C files" log lines above, this step adds what they omit.
280
+ - name: Nuitka cache stats
281
+ shell: bash
282
+ run: du -sm .nuitka-cache/* 2>/dev/null || echo "No Nuitka cache dir."
186
283
  - if: steps.build-binary.outcome == 'failure'
187
284
  run: |
188
285
  echo "Nuitka build failed, skipping the rest of the steps."
@@ -199,8 +296,27 @@ jobs:
199
296
  BIN_NAME: ${{ matrix.bin_name }}
200
297
  shell: bash
201
298
  run: >
202
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0'
299
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0'
203
300
  verify-binary --target "${TARGET}" --binary "${BIN_NAME}"
301
+ # On a non-release run, exercise the freshly-built binary in place: the
302
+ # standalone test-binaries job only runs for release commits, so canary
303
+ # and weekly-schedule builds still prove the binary works without paying
304
+ # a second runner-queue slot. Env mirrors the test-binaries job; see its
305
+ # comments for the UTF-8 and safe.directory rationale.
306
+ - name: Self-test binary
307
+ if: ${{ !fromJSON(needs.metadata.outputs.metadata).release_commits_matrix }}
308
+ env:
309
+ BIN_NAME: ${{ matrix.bin_name }}
310
+ PYTHONIOENCODING: utf8
311
+ GIT_CONFIG_COUNT: "1"
312
+ GIT_CONFIG_KEY_0: safe.directory
313
+ GIT_CONFIG_VALUE_0: "*"
314
+ shell: bash
315
+ run: >
316
+ uv --no-progress run --frozen --
317
+ click-extra test-suite
318
+ --binary "./${BIN_NAME}"
319
+ --jobs max
204
320
  - name: Upload binaries
205
321
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
206
322
  with:
@@ -232,7 +348,7 @@ jobs:
232
348
  BIN_NAME: ${{ matrix.bin_name }}
233
349
  shell: bash
234
350
  run: >
235
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' run gh --
351
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' run gh --
236
352
  attestation verify "${BIN_NAME}"
237
353
  --repo "${{ github.repository }}"
238
354
  --signer-repo kdeldycke/repomatic
@@ -248,7 +364,7 @@ jobs:
248
364
  BUNDLE_PATH: ${{ steps.attest-binary.outputs.bundle-path }}
249
365
  shell: bash
250
366
  run: >
251
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0'
367
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0'
252
368
  pack-attestation --bundle "${BUNDLE_PATH}"
253
369
  - name: Upload attestation bundle
254
370
  # Globbed rather than named, so the filename stays the CLI's to decide.
@@ -266,10 +382,15 @@ jobs:
266
382
  needs:
267
383
  - metadata
268
384
  - compile-binaries
269
- # Skip binary tests for branches that don't affect code (e.g., update-mailmap, format-markdown).
385
+ # Release commits only: non-release builds self-test inside compile-binaries
386
+ # instead of paying a second runner-queue slot per target. This standalone
387
+ # job re-validates the artifact on a pristine VM, through the same
388
+ # upload/download round-trip a user's binary takes. Also skipped for
389
+ # branches that don't affect code (e.g., update-mailmap, format-markdown).
270
390
  if: >
271
391
  fromJSON(needs.metadata.outputs.metadata).nuitka_matrix
272
392
  && !(fromJSON(needs.metadata.outputs.metadata).skip_binary_build || false)
393
+ && fromJSON(needs.metadata.outputs.metadata).release_commits_matrix
273
394
  strategy:
274
395
  # Advisory matrix (default fail-fast): see docs/workflows.md "Matrix fail-fast strategy".
275
396
  # Fall back to an empty `{"include":[]}` matrix when nuitka_matrix is null
@@ -285,6 +406,8 @@ jobs:
285
406
  container: ${{ matrix.container }}
286
407
  # We keep going when a job flagged as not stable fails.
287
408
  continue-on-error: ${{ matrix.state == 'unstable' }}
409
+ # The suite runs in ~1-2 min; a hung run must not squat a runner-cap slot.
410
+ timeout-minutes: 10
288
411
  steps:
289
412
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
290
413
  with:
@@ -356,7 +479,7 @@ jobs:
356
479
  CURRENT_VERSION: ${{ matrix.current_version }}
357
480
  COMMIT: ${{ matrix.commit }}
358
481
  run: >
359
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' git-tag
482
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' git-tag
360
483
  --tag "v${CURRENT_VERSION}"
361
484
  --commit "${COMMIT}"
362
485
  --skip-existing
@@ -398,7 +521,7 @@ jobs:
398
521
  continue-on-error: true
399
522
  env:
400
523
  GH_TOKEN: ${{ secrets.REPOMATIC_PAT || github.token }}
401
- run: uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' sync-dev-release --live --delete
524
+ run: uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' sync-dev-release --live --delete
402
525
  - name: Create GitHub release draft
403
526
  # Idempotent: skips if release already exists (e.g. workflow re-run).
404
527
  # Notes carry the PyPI availability admonition from the start: baking it
@@ -510,7 +633,7 @@ jobs:
510
633
  BUNDLE_PATH: ${{ steps.attest-manpages.outputs.bundle-path }}
511
634
  GH_TOKEN: ${{ secrets.REPOMATIC_PAT || github.token }}
512
635
  run: |
513
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' \
636
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' \
514
637
  pack-attestation --bundle "${BUNDLE_PATH}" > upload-list.txt
515
638
  xargs gh release upload "v${CURRENT_VERSION}" \
516
639
  --repo "${{ github.repository }}" \
@@ -595,7 +718,7 @@ jobs:
595
718
  PACKAGE_NAME: ${{ fromJSON(needs.metadata.outputs.metadata).package_name }}
596
719
  shell: bash
597
720
  run: |
598
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' pack-attestation \
721
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' pack-attestation \
599
722
  --bundle "${BUNDLE_PATH}" \
600
723
  --dir release-assets \
601
724
  --name "${PACKAGE_NAME}-extra-assets" > upload-list.txt
@@ -673,7 +796,7 @@ jobs:
673
796
  # Python distributions live in the CLI; the shell only feeds the
674
797
  # printed list to `gh release upload`.
675
798
  run: |
676
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' pack-binaries \
799
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' pack-binaries \
677
800
  --version "${CURRENT_VERSION}" --dir ./compile-assets > upload-list.txt
678
801
  if [[ -s upload-list.txt ]]; then
679
802
  xargs gh release upload "v${CURRENT_VERSION}" \
@@ -706,6 +829,9 @@ jobs:
706
829
  # (rare) multi-release runs keeps them from racing on the same files.
707
830
  max-parallel: 1
708
831
  runs-on: ubuntu-slim
832
+ # Upload plus the 600 s --poll-timeout, with margin; a stuck poll must not
833
+ # squat a runner-cap slot for the 360-min default.
834
+ timeout-minutes: 15
709
835
  permissions:
710
836
  contents: write
711
837
  steps:
@@ -738,7 +864,7 @@ jobs:
738
864
  VIRUSTOTAL_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }}
739
865
  CURRENT_VERSION: ${{ matrix.current_version }}
740
866
  run: >
741
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' scan-virustotal
867
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' scan-virustotal
742
868
  --tag "v${CURRENT_VERSION}"
743
869
  --binaries-dir ./binaries
744
870
  --poll
@@ -751,7 +877,7 @@ jobs:
751
877
  env:
752
878
  GH_TOKEN: ${{ github.token }}
753
879
  run: >
754
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' sync-binaries
880
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' sync-binaries
755
881
  --repo "${{ github.repository }}"
756
882
  --page docs/binaries.md
757
883
  --records docs/assets/virustotal-scans.json
@@ -766,7 +892,7 @@ jobs:
766
892
  CURRENT_VERSION: ${{ matrix.current_version }}
767
893
  DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
768
894
  run: >
769
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' git-commit-push
895
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' git-commit-push
770
896
  --message "Record v${CURRENT_VERSION} binaries"
771
897
  --branch "${DEFAULT_BRANCH}"
772
898
  docs/binaries.md
@@ -814,5 +940,5 @@ jobs:
814
940
  env:
815
941
  GH_TOKEN: ${{ github.token }}
816
942
  run: >
817
- uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.8.0' sync-dev-release --live
943
+ uvx --no-progress --exclude-newer-package repomatic=P0D 'repomatic==7.9.0' sync-dev-release --live
818
944
  --upload-assets release_assets
@@ -541,7 +541,7 @@ Hand-written, but with a strict structure that downstream projects should mirror
541
541
 
542
542
  5. **Python compatibility matrix** — a `<!-- matrix python -->` / `<!-- matrix-end -->` comment-marker region whose embedded table is regenerated by `click-extra refresh-directives` (the fourth `repomatic update-docs` phase runs it automatically), so it refreshes on every release without manual intervention. click-extra's matrix machinery owns the generation logic: per-tag Python support from classifiers with `requires-python`/Poetry/`setup.py` fallbacks, release-date capping, and range grouping. Keep the marker form (not the live `{matrix}` fence) so the table also renders when browsing the file on GitHub. Rendering conventions the reader relies on: `✅` / `❌` glyph cells, consecutive same-support tags grouped into one row labelled `` `4.25.x` → `6.15.x` ``, newest ranges on top. Tags with no Python declaration at all are out of scope; note the cutoff in a sentence beneath the table rather than padding with blank rows.
543
543
 
544
- 6. **Executables** table linking to GitHub Release binaries for each platform/architecture. Always points at `releases/latest/download/...` — never bake in version numbers that need bumping every release. The release pipeline uploads unversioned alias assets (`<package>-linux-arm64.bin`) alongside the version-stamped ones precisely so these evergreen URLs resolve.
544
+ 6. **Executables** table linking to GitHub Release binaries for each platform/architecture. Its URLs belong to the release freeze (`PrepareRelease.freeze_install_download_urls`), which ratchets them forward to `/releases/download/vX.Y.Z/<package>-X.Y.Z-linux-arm64.bin` on every release: a repo that has shipped once carries versioned URLs permanently, and there is no unfreeze step. A never-released repo starts from the evergreen `releases/latest/download/<package>-linux-arm64.bin` form, which resolves because the release pipeline uploads versionless alias assets alongside the version-stamped ones. Both spellings resolve, so neither is drift: never hand-edit one into the other.
545
545
 
546
546
  7. **Release verification** section showing `gh attestation verify` against the package's own repo, with the `--signer-repo` flag if the release workflow runs as a reusable workflow from another repo. Mirror exactly: a stale flag here breaks reader trust.
547
547
 
@@ -549,7 +549,7 @@ Sync rules:
549
549
 
550
550
  - Re-check the Repology page on every release. New distros get a new tab; dropped distros get the tab removed.
551
551
  - The Python compatibility matrix is auto-generated; never hand-edit it.
552
- - Version-number references in download URLs must use `releases/latest/download/...`, not pinned tags. Hand-pinned versions in install.md were a long-running source of doc drift.
552
+ - Download URLs in `install.md` are generated, not hand-maintained, so a version number in one is expected rather than drift. The single manual intervention is the documented repair: when a release's binary lane failed, re-point the table at the last release that actually carries binaries, since the freeze pins the version optimistically before the binaries exist (`lint-repo` reports the gap but never repairs it; see `claude.md` § A published release freezes what is missing from it).
553
553
  - The Try it tab-set's `Specific version` tab does carry a pinned version as an example — that's intentional (it teaches the syntax). From repomatic `v7.4.0` on, the prepare-release freeze step bumps it automatically (`freeze_install_cli_version` rewrites `{package}@X.Y.Z` and `{package}==X.Y.Z` pins in `docs/install.md`); hand-bumping is only needed on repos pinned to older release engines.
554
554
 
555
555
  ### `packaging.md`: distribution and dependency reference
@@ -575,9 +575,10 @@ Primary toctree (user-facing), in this order:
575
575
  05. `tool-runner` — Only when the project ships a `repomatic run`-style tool runner.
576
576
  06. `workflows` — Only when the project publishes reusable workflows.
577
577
  07. `test-matrix` — Only when the project documents its CI test-matrix composition.
578
- 08. `security` — Optional, and absent by default: a security page with nothing project-specific to say (no real threat model, no attack surface worth describing, no dedicated reporting channel) is boilerplate that dilutes the docs. Add it only when the project has a genuine security consideration. When present, single-source it as `docs/security.md`: GitHub's security tab detects the file in `docs/` as well as `.github/`, so no duplicate copy is needed.
579
- 09. `skills`, `agents` — Only when the project ships Claude Code skills or agents (see § Recipes › skills/agents pages below).
580
- 10. `benchmark` — Optional comparison page; only useful for projects positioning against alternatives. MyST docstring authoring needs no local page: it is canonically documented on the [click-extra MyST docstrings page](https://kdeldycke.github.io/click-extra/myst-docstrings.html), which also covers the `click-extra convert-to-myst` migration command.
578
+ 08. `nuitka` — Only for projects compiling standalone executables through the release engine: the canonical home for build targets, fleet cadence, compile caching and measured build times (the `binaries` page below stays the per-release catalog).
579
+ 09. `security` — Optional, and absent by default: a security page with nothing project-specific to say (no real threat model, no attack surface worth describing, no dedicated reporting channel) is boilerplate that dilutes the docs. Add it only when the project has a genuine security consideration. When present, single-source it as `docs/security.md`: GitHub's security tab detects the file in `docs/` as well as `.github/`, so no duplicate copy is needed.
580
+ 10. `skills`, `agents` — Only when the project ships Claude Code skills or agents (see § Recipes › skills/agents pages below).
581
+ 11. `benchmark` — Optional comparison page; only useful for projects positioning against alternatives. MyST docstring authoring needs no local page: it is canonically documented on the [click-extra MyST docstrings page](https://kdeldycke.github.io/click-extra/myst-docstrings.html), which also covers the `click-extra convert-to-myst` migration command.
581
582
 
582
583
  Development toctree, in this order:
583
584
 
@@ -641,6 +642,7 @@ Page-shape rules that apply across the roster:
641
642
  | `managers.md` | `file-submodule` |
642
643
  | `mkdocs.md` | `markdown` |
643
644
  | `myst-docstrings.md` | `pencil` |
645
+ | `nuitka.md` | `file-binary` |
644
646
  | `operation-contracts.md` | `tasklist` |
645
647
  | `overrides.md` | `pin` |
646
648
  | `packaging.md` | `package-dependents` |
@@ -769,8 +771,9 @@ Strictness flags:
769
771
 
770
772
  Theme assets and OpenGraph:
771
773
 
772
- - `html_logo = "assets/logo-square.svg"` and `html_favicon = "assets/favicon.svg"` are the canonical paths. Both files live under `docs/assets/` and ship as SVG so the Furo theme's dark-mode swap works without a separate asset bundle. The `/brand-assets` skill is the canonical producer of these files: it manages the SVG sources (favicon, square logo, banner, social banner), exports light/dark PNG variants, and wires the new files into `docs/conf.py`. When `html_logo`/`html_favicon` are missing or out of date, run `/brand-assets` instead of editing the assets by hand.
773
- - `ogp_image` must be an **absolute** URL to the committed PNG on the GitHub raw host — `f"https://raw.githubusercontent.com/{github_user}/{project_id}/main/docs/assets/banner-social-light.png"` — never a site-relative `assets/...` path. `docs/assets/` is not copied into the built site (it is neither in `html_static_path` nor referenced by any doc body, so Sphinx's image collector never picks it up), so a relative `ogp_image` resolves against `ogp_site_url` to a URL that 404s for the social crawlers that only ever see the raw HTML. Set it when the project has a banner asset; without `ogp_image`, `sphinxext.opengraph` falls back to the favicon, which scales poorly. Still define `ogp_site_url = f"https://{github_user}.github.io/{project_id}/"` for the page-level `og:url` tag. The PNG is exported from `assets/banner-social.svg` by the `/brand-assets` skill (which also produces the dark variant for dark-mode social cards on platforms that support them).
774
+ - The sidebar logo is Furo's `light_logo`/`dark_logo` **pair** of theme options, set to `"logo-square-light.png"` and `"logo-square-dark.png"`. Never `html_logo`: Furo performs no dark-mode swap on a single logo, so the light-only `assets/logo-square.svg` keeps its near-black wordmark on the dark theme, where it lands around 1.2:1 against Furo's background. Both options must be set for the pair to render at all, and `html_logo` must be unset, since Furo's `sidebar/brand.html` prefers it and skips the pair when it is present. Furo resolves both names against `_static/`, so each PNG needs its own `html_static_path` entry (that setting accepts individual files, which land at the root of `_static/`; listing `docs/assets/` wholesale would copy every screenshot and report along with them). The two PNGs are same-aspect exports of the same SVG, so the swap holds geometry.
775
+ - `html_favicon = "assets/favicon.svg"` is the canonical path, an SVG since browsers handle SVG favicons natively. The `/brand-assets` skill is the canonical producer of every one of these files: it manages the SVG sources (favicon, square logo, banner, social banner), exports light/dark PNG variants, and wires the new files into `docs/conf.py`. When they are missing or out of date, run `/brand-assets` instead of editing the assets by hand.
776
+ - `ogp_image` must be an **absolute** URL to the committed PNG on the GitHub raw host — `f"https://raw.githubusercontent.com/{github_user}/{project_id}/main/docs/assets/banner-social-light.png"` — never a site-relative `assets/...` path. `docs/assets/` is not copied into the built site (only the two sidebar logos are named in `html_static_path`, and no doc body references the banner, so Sphinx's image collector never picks it up), so a relative `ogp_image` resolves against `ogp_site_url` to a URL that 404s for the social crawlers that only ever see the raw HTML. Set it when the project has a banner asset; without `ogp_image`, `sphinxext.opengraph` falls back to the favicon, which scales poorly. Still define `ogp_site_url = f"https://{github_user}.github.io/{project_id}/"` for the page-level `og:url` tag. The PNG is exported from `assets/banner-social.svg` by the `/brand-assets` skill (which also produces the dark variant for dark-mode social cards on platforms that support them).
774
777
  - The Furo `announcement` banner template across this lineage references the same `github_user` constant defined at the top of `conf.py`. Rebuilding the announcement string locally inside `html_theme_options` avoids a per-theme override layer.
775
778
 
776
779
  Pruning checklist before merging any `conf.py` change: