metabook-py 1.0.1__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 (55) hide show
  1. metabook_py-1.0.1/.github/WORKFLOW.md +214 -0
  2. metabook_py-1.0.1/.github/actions/build-dist/action.yml +23 -0
  3. metabook_py-1.0.1/.github/actions/publish-pypi/action.yml +26 -0
  4. metabook_py-1.0.1/.github/actions/setup/action.yml +17 -0
  5. metabook_py-1.0.1/.github/dependabot.yml +12 -0
  6. metabook_py-1.0.1/.github/rulesets/dev.json +54 -0
  7. metabook_py-1.0.1/.github/rulesets/main.json +57 -0
  8. metabook_py-1.0.1/.github/rulesets/next.json +54 -0
  9. metabook_py-1.0.1/.github/rulesets/release.json +51 -0
  10. metabook_py-1.0.1/.github/rulesets/tags.json +37 -0
  11. metabook_py-1.0.1/.github/workflows/automerge.yml +21 -0
  12. metabook_py-1.0.1/.github/workflows/matrix.yml +58 -0
  13. metabook_py-1.0.1/.github/workflows/next.yml +48 -0
  14. metabook_py-1.0.1/.github/workflows/pr-merged.yml +66 -0
  15. metabook_py-1.0.1/.github/workflows/pr.yml +121 -0
  16. metabook_py-1.0.1/.github/workflows/promote.yml +80 -0
  17. metabook_py-1.0.1/.github/workflows/publish.yml +68 -0
  18. metabook_py-1.0.1/.github/workflows/release.yml +99 -0
  19. metabook_py-1.0.1/.gitignore +24 -0
  20. metabook_py-1.0.1/.python-version +1 -0
  21. metabook_py-1.0.1/Dockerfile +33 -0
  22. metabook_py-1.0.1/LICENSE +21 -0
  23. metabook_py-1.0.1/PKG-INFO +67 -0
  24. metabook_py-1.0.1/README.md +47 -0
  25. metabook_py-1.0.1/SETUP_INSTRUCTIONS.md +59 -0
  26. metabook_py-1.0.1/bin/clean.sh +38 -0
  27. metabook_py-1.0.1/bin/publish.sh +245 -0
  28. metabook_py-1.0.1/bin/setup.sh +44 -0
  29. metabook_py-1.0.1/bin/update_deps.sh +16 -0
  30. metabook_py-1.0.1/docker-compose.yml +18 -0
  31. metabook_py-1.0.1/makefile +511 -0
  32. metabook_py-1.0.1/pyproject.toml +63 -0
  33. metabook_py-1.0.1/requirements.txt +9 -0
  34. metabook_py-1.0.1/src/metabook_py/__init__.py +0 -0
  35. metabook_py-1.0.1/src/metabook_py/core/__init__.py +0 -0
  36. metabook_py-1.0.1/src/metabook_py/core/cache.py +58 -0
  37. metabook_py-1.0.1/src/metabook_py/core/config.py +15 -0
  38. metabook_py-1.0.1/src/metabook_py/core/exceptions.py +33 -0
  39. metabook_py-1.0.1/src/metabook_py/main.py +91 -0
  40. metabook_py-1.0.1/src/metabook_py/mcp_server.py +127 -0
  41. metabook_py-1.0.1/src/metabook_py/models/__init__.py +0 -0
  42. metabook_py-1.0.1/src/metabook_py/models/book.py +36 -0
  43. metabook_py-1.0.1/src/metabook_py/models/structure.py +94 -0
  44. metabook_py-1.0.1/src/metabook_py/routers/__init__.py +0 -0
  45. metabook_py-1.0.1/src/metabook_py/routers/books.py +140 -0
  46. metabook_py-1.0.1/src/metabook_py/services/__init__.py +0 -0
  47. metabook_py-1.0.1/src/metabook_py/services/counter.py +398 -0
  48. metabook_py-1.0.1/src/metabook_py/services/detector.py +198 -0
  49. metabook_py-1.0.1/src/metabook_py/services/discovery.py +145 -0
  50. metabook_py-1.0.1/src/metabook_py/services/fetcher.py +137 -0
  51. metabook_py-1.0.1/tests/conftest.py +158 -0
  52. metabook_py-1.0.1/tests/test_counter.py +179 -0
  53. metabook_py-1.0.1/tests/test_detector.py +91 -0
  54. metabook_py-1.0.1/tests/test_discovery.py +168 -0
  55. metabook_py-1.0.1/uv.lock +1815 -0
@@ -0,0 +1,214 @@
1
+ # Branching and release
2
+
3
+ Two long-lived integration lanes (`dev`, `next`) plus `main` and the tags.
4
+ Release branches are temporary and versioned.
5
+
6
+ ```
7
+ <type>/<slug> ──PR──> dev ──(daily/manual)──> next ──cut──> release/vX.Y.Z ──draft PR──> main
8
+ (deleted on merge) (preview) (deleted on release)
9
+ ```
10
+
11
+ Adapted from [exegia/corpora-py](https://github.com/exegia/corpora-py). This
12
+ repo is currently a scaffold only: merging a release tags `vX.Y.Z`, which
13
+ triggers `publish.yml` to ship the wheel to PyPI (once a `pypi` trusted
14
+ publisher is configured for this repo). There is no deploy job.
15
+
16
+ ## Feature branches
17
+
18
+ Named `<type>/<slug>` — `feat`, `fix`, `chore`, `docs`, `ci`, `refactor`,
19
+ `test`, `perf`, `build`, `style`, `revert`. (Git forbids `:` in a ref name, so
20
+ the conventional-commit form lives in the **PR title**: `feat: add parser`.)
21
+
22
+ Branch off `dev` and open a PR back into it. While the PR is a draft only the
23
+ guard runs; marking it **ready for review** starts the tests and the AI review
24
+ (if `CLAUDE_CODE_OAUTH_TOKEN` is configured), which then re-run on every push.
25
+ `guard` and `check` are required, so a red one cannot land.
26
+
27
+ **Stacked PRs** (a feature branch based on another feature branch, not `dev`
28
+ directly) are supported: the guard validates a `<type>/<slug>` base the same
29
+ way it validates the head, so `feat/b → feat/a → dev` passes as long as both
30
+ branches and the title follow the convention. Merge bottom-up; each merge
31
+ rebases the remaining stack onto `dev`.
32
+
33
+ When it merges the branch deletes itself (repository setting) and `dev` moves
34
+ forward. Nothing is versioned yet.
35
+
36
+ Dependabot is the one exception: it opens `dependabot/<ecosystem>/<dep>-<ver>`
37
+ with a `Bump X from A to B` title, and neither is renameable. `make pr-guard`
38
+ waves those through whichever branch they target.
39
+
40
+ ## `dev` and `next`
41
+
42
+ `dev` is the working branch. Features land here all day.
43
+
44
+ `next` is staging. A scheduled workflow (22:00 UTC daily) and a manual
45
+ **Promote to next** action open a PR from `dev` into `next` when `dev` is
46
+ ahead. That PR auto-merges once `guard` and `check` pass. Every push to
47
+ `next` also runs the os × Python matrix.
48
+
49
+ ## Versioning
50
+
51
+ The promote job classifies the bump from line-count churn
52
+ (`git diff --shortstat origin/next...origin/dev`, insertions + deletions):
53
+
54
+ | Churn | Makefile bump | Semver | Your label |
55
+ |-------|---------------|--------|------------|
56
+ | `< 100` | `patch` | `0.0.+1` | minor |
57
+ | `100–999` | `minor` | `0.+1.0` | major |
58
+ | `≥ 1000` | `major` | `+1.0.0` | breaking |
59
+
60
+ `workflow_dispatch` can override with `major` / `minor` / `patch`. The chosen
61
+ version is stored on the promote PR as `<!-- release: vX.Y.Z -->` so the cut
62
+ still knows it after `dev` and `next` are equal.
63
+
64
+ The version is written into `pyproject.toml` plus `uv.lock`. Version lives
65
+ only on `release/v*` until `main` is merged back.
66
+
67
+ ## Release branches
68
+
69
+ Named `release/vX.Y.Z`, and always carry that version in `pyproject.toml`.
70
+ The guard rejects a PR into `main` where the root and the branch name
71
+ disagree.
72
+
73
+ A push to `next` cuts (or refreshes) the branch from `next` plus a
74
+ `chore(release): open vX.Y.Z` commit. Exactly one is in flight at a time: if a
75
+ draft PR into `main` is already open, later promotions fast-forward that same
76
+ branch and **keep its version**.
77
+
78
+ Last-minute fixes can still PR `<type>/<slug>` directly into that in-flight
79
+ `release/v*` (same guard/check as `dev`). A push refreshes the draft into
80
+ `main`.
81
+
82
+ Its ruleset deliberately omits `creation` and `deletion` rules — `make
83
+ cut-release` has to create it and `make delete-branch` has to remove it after
84
+ the release. The automation App is on the bypass list so it can push a
85
+ refresh without opening a PR (once one is configured — see Secrets below).
86
+
87
+ ## `main`
88
+
89
+ No direct pushes; PRs only from `release/vX.Y.Z`. Merging one creates the
90
+ `vX.Y.Z` tag and GitHub Release, deletes the release branch, then opens PRs
91
+ that merge `main` back into `next` and `dev` and deletes leftover remote
92
+ feature / `release/v*` heads.
93
+
94
+ It does **not** cut the next release branch. That waits for the next promote.
95
+
96
+ Everything downstream hangs off that tag: `publish.yml` sends the wheel to
97
+ PyPI.
98
+
99
+ ## Workflows
100
+
101
+ | File | Trigger | Does |
102
+ | ------------------- | -------------------------------- | --------------------------------------------------- |
103
+ | `pr.yml` | PR opened / ready / pushed | `guard`, `check`, `package` (into main), `review` (into `dev`) |
104
+ | `promote.yml` | 22:00 UTC daily / manual | bootstrap lanes, open `dev` → `next` PR, auto-merge |
105
+ | `next.yml` | push to `next` | cut/refresh `release/v*` |
106
+ | `pr-merged.yml` | push to `release/v*` | upsert the draft release PR into `main` |
107
+ | `release.yml` | PR merged into `main` | tag, sync lanes, cleanup |
108
+ | `matrix.yml` | push to `next` / `release/v*`, weekly | os × Python coverage |
109
+ | `publish.yml` | push of a `vX.Y.Z` tag | builds and publishes the wheel to PyPI |
110
+ | `automerge.yml` | Dependabot PR | enables auto-merge |
111
+
112
+ Every step in the first five is a `make` target, so anything CI does can be
113
+ reproduced locally.
114
+
115
+ ### Merge methods differ by level
116
+
117
+ Feature PRs into `dev` (or an in-flight `release/v*`) are **squashed** — that
118
+ is this repo's convention and it keeps each feature one commit. PRs into
119
+ `next` and the release PR into `main` are a **merge**, and the rulesets
120
+ enforce that. Squashing the release PR would collapse the whole release into
121
+ a single commit, and `gh release --generate-notes` would have nothing to list.
122
+
123
+ ### The tag must not be created by `GITHUB_TOKEN`
124
+
125
+ `make tag-release` should run with the automation App's token, and that is
126
+ load-bearing rather than incidental. Events raised by `GITHUB_TOKEN` do not
127
+ start new workflow runs, so a tag pushed with it would leave `publish.yml`
128
+ sitting there, never firing, with nothing to indicate the release had not
129
+ shipped.
130
+
131
+ The same rule is why `pr-merged.yml`, `promote.yml` and `next.yml` run as the
132
+ App: a PR opened by `GITHUB_TOKEN` cannot trigger further workflows, and
133
+ `guard` and `check` are *required* checks — those PRs would never be
134
+ mergeable.
135
+
136
+ ### Apply the rulesets after the first cut lands on `main`
137
+
138
+ `.github/rulesets/*.json` is inert until `make rulesets-apply`. Each file's
139
+ `bypass_actors` currently only lists the repository-admin role (`actor_id:
140
+ 5`, `RepositoryRole`) — add your automation App's Integration id to each
141
+ file once one is set up, the same way `corpora-py` does, or the App won't be
142
+ able to push refreshes without opening a PR for itself.
143
+
144
+ ```bash
145
+ make rulesets-diff # what GitHub has now
146
+ make rulesets-apply # push all five files
147
+ ```
148
+
149
+ `make rulesets-apply` matches by `.name`, so a file must keep the name of the
150
+ ruleset already on GitHub (`Protect main branch`, `Protect dev branch`,
151
+ `Protect next branch`, `Protect release branches`, `Publishing`) or a second
152
+ one is created alongside it.
153
+
154
+ Enable **Allow auto-merge** on the repository. Promote and post-release sync
155
+ PRs use `gh pr merge --auto`.
156
+
157
+ ## Bootstrap and manual operations
158
+
159
+ There are no `dev` / `next` branches until the first wrapup. Run the
160
+ **Release** workflow manually (`Actions → Release → Run workflow`) — the
161
+ release job skips and wrapup creates the lanes. Locally:
162
+
163
+ ```bash
164
+ make bootstrap-lanes
165
+ ```
166
+
167
+ `dev` is created from the newest `release/v*` if one exists, otherwise `main`.
168
+ `next` is created from `main`. Then run **Promote to next** (or wait until
169
+ 22:00 UTC) once there is work on `dev`.
170
+
171
+ Other useful targets:
172
+
173
+ ```bash
174
+ make ci # what CI runs on a PR
175
+ make pack # build the publishable wheel
176
+ make pkg-version # the version in pyproject.toml
177
+ make churn-info FROM=origin/next TO=origin/dev
178
+ make next-version BUMP=patch # what the next tag would be called
179
+ make release-notes RANGE=origin/main..HEAD
180
+ make cleanup-local # prune local feature / release branches
181
+ make rulesets-diff # rulesets GitHub actually has
182
+ make rulesets-apply # push .github/rulesets/*.json
183
+ ```
184
+
185
+ `make tag-release` is idempotent — a tag already released is skipped, not an
186
+ error.
187
+
188
+ Everything is parameterised on `TRUNK`, so a one-off against a different trunk
189
+ is `make pr-guard TRUNK=some-branch ...`.
190
+
191
+ Scheduled workflows are read from the **default branch**. `promote.yml` will
192
+ not fire on a cron until this file has shipped to `main`.
193
+
194
+ ## Secrets
195
+
196
+ | Name | Where | Used by |
197
+ | --------------------------------------------------- | ---------------- | --------------------------- |
198
+ | `AUTOMATION_APP_ID` / `AUTOMATION_APP_PRIVATE_KEY` | repo or org | opening PRs, branches, tags |
199
+ | `CLAUDE_CODE_OAUTH_TOKEN` | repo or org | the AI review (optional) |
200
+
201
+ Neither is set up yet on this scaffold. Without the automation App secrets,
202
+ `promote.yml`, `next.yml`, `pr-merged.yml` and the `wrapup` job in
203
+ `release.yml` will fail at their first step — a PR opened with `GITHUB_TOKEN`
204
+ cannot trigger further workflows, so the release PR's own checks would never
205
+ start. Set these up (a small GitHub App with `contents: write` +
206
+ `pull_requests: write`, installed on this repo) before relying on the
207
+ automated promote/release flow.
208
+
209
+ PyPI needs no secret once set up — `publish.yml` uses OIDC trusted publishing
210
+ against the `pypi` environment. **That binding names the workflow file**, so
211
+ renaming `publish.yml` breaks it until the publisher is re-pointed on PyPI.
212
+
213
+ Without `CLAUDE_CODE_OAUTH_TOKEN` the review job skips with a note in the job
214
+ summary rather than failing.
@@ -0,0 +1,23 @@
1
+ name: 'Build Distribution'
2
+ description: 'Installs uv, builds the sdist and wheel, and uploads them as an artifact'
3
+ runs:
4
+ using: "composite"
5
+ steps:
6
+ - name: Install uv
7
+ uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5
8
+ with:
9
+ enable-cache: true
10
+
11
+ - name: Build the metabook-py sdist and wheel
12
+ shell: bash
13
+ run: |
14
+ uv build --out-dir dist/
15
+ echo "Built distributions:"
16
+ ls -lh dist/
17
+
18
+ - name: Upload artifact
19
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
20
+ with:
21
+ name: dist
22
+ path: dist/
23
+ if-no-files-found: error
@@ -0,0 +1,26 @@
1
+ name: "Publish to PyPI"
2
+ description: "Download the metabook-py wheel artifact and publish it to PyPI"
3
+ runs:
4
+ using: "composite"
5
+ steps:
6
+ - name: Install uv
7
+ uses: astral-sh/setup-uv@v5 # v5
8
+ with:
9
+ enable-cache: true
10
+
11
+ - name: Download artifact
12
+ uses: actions/download-artifact@v8 # v4
13
+ with:
14
+ name: dist
15
+ path: dist/
16
+
17
+ - name: List wheels to publish
18
+ shell: bash
19
+ run: ls -lh dist/*.whl dist/*.tar.gz 2>/dev/null || true
20
+
21
+ - name: Publish all distributions to PyPI
22
+ shell: bash
23
+ # `uv publish dist/` passes a directory, which matches no files ("No
24
+ # files found to publish"); uv expects file globs. Bare `uv publish`
25
+ # defaults to the `dist/*` glob and uploads every built wheel.
26
+ run: uv publish
@@ -0,0 +1,17 @@
1
+ name: Setup
2
+ description: uv and the project's Python toolchain.
3
+
4
+ runs:
5
+ using: composite
6
+ steps:
7
+ - uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
8
+ with:
9
+ enable-cache: true
10
+ cache-dependency-glob: uv.lock
11
+
12
+ # uv provisions the interpreter itself, so there is no actions/setup-python
13
+ # here. With no argument, it reads .python-version, which is the same file
14
+ # every local checkout resolves against.
15
+ - name: Install Python
16
+ shell: bash
17
+ run: uv python install
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip" # See documentation for possible values.
4
+ directory: "/" # Location of package manifests.
5
+ schedule:
6
+ interval: "weekly"
7
+
8
+ # Maintain dependencies for GitHub Actions.
9
+ - package-ecosystem: "github-actions"
10
+ directory: "/"
11
+ schedule:
12
+ interval: "weekly"
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "Protect dev branch",
3
+ "target": "branch",
4
+ "enforcement": "active",
5
+ "bypass_actors": [
6
+ {
7
+ "actor_id": 5,
8
+ "actor_type": "RepositoryRole",
9
+ "bypass_mode": "always"
10
+ }
11
+ ],
12
+ "conditions": {
13
+ "ref_name": {
14
+ "include": [
15
+ "refs/heads/dev"
16
+ ],
17
+ "exclude": []
18
+ }
19
+ },
20
+ "rules": [
21
+ {
22
+ "type": "deletion"
23
+ },
24
+ {
25
+ "type": "non_fast_forward"
26
+ },
27
+ {
28
+ "type": "pull_request",
29
+ "parameters": {
30
+ "dismiss_stale_reviews_on_push": true,
31
+ "require_code_owner_review": false,
32
+ "require_last_push_approval": false,
33
+ "required_approving_review_count": 0,
34
+ "required_review_thread_resolution": true,
35
+ "allowed_merge_methods": ["merge"]
36
+ }
37
+ },
38
+ {
39
+ "type": "required_status_checks",
40
+ "parameters": {
41
+ "do_not_enforce_on_create": false,
42
+ "required_status_checks": [
43
+ {
44
+ "context": "guard"
45
+ },
46
+ {
47
+ "context": "check"
48
+ }
49
+ ],
50
+ "strict_required_status_checks_policy": true
51
+ }
52
+ }
53
+ ]
54
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "Protect main branch",
3
+ "target": "branch",
4
+ "enforcement": "active",
5
+ "bypass_actors": [
6
+ {
7
+ "actor_id": 5,
8
+ "actor_type": "RepositoryRole",
9
+ "bypass_mode": "always"
10
+ }
11
+ ],
12
+ "conditions": {
13
+ "ref_name": {
14
+ "include": [
15
+ "refs/heads/main"
16
+ ],
17
+ "exclude": []
18
+ }
19
+ },
20
+ "rules": [
21
+ {
22
+ "type": "deletion"
23
+ },
24
+ {
25
+ "type": "non_fast_forward"
26
+ },
27
+ {
28
+ "type": "pull_request",
29
+ "parameters": {
30
+ "dismiss_stale_reviews_on_push": true,
31
+ "require_code_owner_review": false,
32
+ "require_last_push_approval": false,
33
+ "required_approving_review_count": 0,
34
+ "required_review_thread_resolution": true,
35
+ "allowed_merge_methods": ["merge"]
36
+ }
37
+ },
38
+ {
39
+ "type": "required_status_checks",
40
+ "parameters": {
41
+ "do_not_enforce_on_create": false,
42
+ "required_status_checks": [
43
+ {
44
+ "context": "guard"
45
+ },
46
+ {
47
+ "context": "check"
48
+ },
49
+ {
50
+ "context": "package"
51
+ }
52
+ ],
53
+ "strict_required_status_checks_policy": true
54
+ }
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "Protect next branch",
3
+ "target": "branch",
4
+ "enforcement": "active",
5
+ "bypass_actors": [
6
+ {
7
+ "actor_id": 5,
8
+ "actor_type": "RepositoryRole",
9
+ "bypass_mode": "always"
10
+ }
11
+ ],
12
+ "conditions": {
13
+ "ref_name": {
14
+ "include": [
15
+ "refs/heads/next"
16
+ ],
17
+ "exclude": []
18
+ }
19
+ },
20
+ "rules": [
21
+ {
22
+ "type": "deletion"
23
+ },
24
+ {
25
+ "type": "non_fast_forward"
26
+ },
27
+ {
28
+ "type": "pull_request",
29
+ "parameters": {
30
+ "dismiss_stale_reviews_on_push": true,
31
+ "require_code_owner_review": false,
32
+ "require_last_push_approval": false,
33
+ "required_approving_review_count": 0,
34
+ "required_review_thread_resolution": true,
35
+ "allowed_merge_methods": ["merge"]
36
+ }
37
+ },
38
+ {
39
+ "type": "required_status_checks",
40
+ "parameters": {
41
+ "do_not_enforce_on_create": false,
42
+ "required_status_checks": [
43
+ {
44
+ "context": "guard"
45
+ },
46
+ {
47
+ "context": "check"
48
+ }
49
+ ],
50
+ "strict_required_status_checks_policy": true
51
+ }
52
+ }
53
+ ]
54
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "Protect release branches",
3
+ "target": "branch",
4
+ "enforcement": "active",
5
+ "bypass_actors": [
6
+ {
7
+ "actor_id": 5,
8
+ "actor_type": "RepositoryRole",
9
+ "bypass_mode": "always"
10
+ }
11
+ ],
12
+ "conditions": {
13
+ "ref_name": {
14
+ "include": [
15
+ "refs/heads/release/v*"
16
+ ],
17
+ "exclude": []
18
+ }
19
+ },
20
+ "rules": [
21
+ {
22
+ "type": "non_fast_forward"
23
+ },
24
+ {
25
+ "type": "pull_request",
26
+ "parameters": {
27
+ "dismiss_stale_reviews_on_push": false,
28
+ "require_code_owner_review": false,
29
+ "require_last_push_approval": false,
30
+ "required_approving_review_count": 0,
31
+ "required_review_thread_resolution": false,
32
+ "allowed_merge_methods": ["squash", "merge"]
33
+ }
34
+ },
35
+ {
36
+ "type": "required_status_checks",
37
+ "parameters": {
38
+ "do_not_enforce_on_create": false,
39
+ "required_status_checks": [
40
+ {
41
+ "context": "guard"
42
+ },
43
+ {
44
+ "context": "check"
45
+ }
46
+ ],
47
+ "strict_required_status_checks_policy": false
48
+ }
49
+ }
50
+ ]
51
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "Publishing",
3
+ "target": "tag",
4
+ "enforcement": "active",
5
+ "bypass_actors": [
6
+ {
7
+ "actor_id": 5,
8
+ "actor_type": "RepositoryRole",
9
+ "bypass_mode": "always"
10
+ }
11
+ ],
12
+ "conditions": {
13
+ "ref_name": {
14
+ "exclude": [],
15
+ "include": [
16
+ "refs/tags/v*"
17
+ ]
18
+ }
19
+ },
20
+ "rules": [
21
+ {
22
+ "type": "deletion"
23
+ },
24
+ {
25
+ "type": "non_fast_forward"
26
+ },
27
+ {
28
+ "type": "creation"
29
+ },
30
+ {
31
+ "type": "update"
32
+ },
33
+ {
34
+ "type": "required_signatures"
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,21 @@
1
+ name: Dependabot auto-merge
2
+
3
+ on: pull_request
4
+
5
+ # NOTE: "workflows" is not a valid permission scope — its presence made this
6
+ # file unparseable, so GitHub reported a failed run on EVERY push. Dependabot
7
+ # PRs that modify workflow files can't be auto-merged by GITHUB_TOKEN anyway
8
+ # (that needs the `workflow` OAuth scope, which isn't grantable here).
9
+ permissions:
10
+ contents: write
11
+ pull-requests: write
12
+ jobs:
13
+ dependabot:
14
+ runs-on: ubuntu-latest
15
+ if: ${{ github.actor == 'dependabot[bot]' }}
16
+ steps:
17
+ - name: Enable auto-merge for Dependabot PRs
18
+ run: gh pr merge --auto --merge "$PR_URL"
19
+ env:
20
+ PR_URL: ${{github.event.pull_request.html_url}}
21
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,58 @@
1
+ # Cross-platform, cross-version coverage.
2
+ #
3
+ # `check` in pr.yml is deliberately a single job on one runner: it is a
4
+ # required status check on main, and a matrixed job reports contexts like
5
+ # "check (macos-latest, 3.14)" rather than the plain "check" the ruleset names,
6
+ # which would leave every PR unmergeable. This workflow keeps the breadth an
7
+ # os x Python matrix needs — ubuntu + macOS across the supported Python
8
+ # versions — where it does not collide with a required context:
9
+ #
10
+ # * every push to `next` or a release branch, so a macOS- or 3.14-only break
11
+ # surfaces on staging / the in-flight RC rather than on the release PR;
12
+ # * weekly, to catch upstream dependency drift on an untouched tree;
13
+ # * on demand.
14
+ name: Matrix
15
+
16
+ on:
17
+ push:
18
+ branches:
19
+ - next
20
+ - "release/v*"
21
+ schedule:
22
+ # Every Sunday at 8:05 UTC.
23
+ - cron: "5 8 * * 0"
24
+ workflow_dispatch:
25
+
26
+ concurrency:
27
+ group: matrix-${{ github.ref }}
28
+ cancel-in-progress: true
29
+
30
+ permissions:
31
+ contents: read
32
+
33
+ jobs:
34
+ test:
35
+ name: test (${{ matrix.os }}, ${{ matrix.python-version }})
36
+ runs-on: ${{ matrix.os }}
37
+ strategy:
38
+ fail-fast: false
39
+ matrix:
40
+ os: [ ubuntu-latest, macos-latest ]
41
+ python-version: [ "3.13", "3.14" ]
42
+
43
+ # uv provisions the matrix Python itself, overriding .python-version.
44
+ env:
45
+ UV_PYTHON: ${{ matrix.python-version }}
46
+
47
+ steps:
48
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
49
+
50
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
51
+ with:
52
+ enable-cache: true
53
+ cache-dependency-glob: uv.lock
54
+
55
+ - name: Install Python ${{ matrix.python-version }}
56
+ run: uv python install ${{ matrix.python-version }}
57
+
58
+ - run: make ci
@@ -0,0 +1,48 @@
1
+ # `next` moved: cut/refresh `release/vX.Y.Z`.
2
+ #
3
+ # `pr.yml` already ran the full suite on the promote PR, so this only versions.
4
+ # Pushing the release branch is what starts pr-merged.yml, which opens or
5
+ # refreshes the draft PR into main.
6
+ name: Next
7
+
8
+ on:
9
+ push:
10
+ branches:
11
+ - next
12
+ workflow_dispatch:
13
+
14
+ concurrency:
15
+ group: next
16
+ cancel-in-progress: false
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ jobs:
22
+ cut-release:
23
+ name: cut-release
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
27
+ id: app-token
28
+ with:
29
+ app-id: ${{ secrets.AUTOMATION_APP_ID }}
30
+ private-key: ${{ secrets.AUTOMATION_APP_PRIVATE_KEY }}
31
+
32
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
33
+ with:
34
+ fetch-depth: 0
35
+ fetch-tags: true
36
+ token: ${{ steps.app-token.outputs.token }}
37
+
38
+ # `make cut-release` re-locks: uv.lock pins the workspace versions.
39
+ - uses: ./.github/actions/setup
40
+
41
+ - name: Cut or refresh the release branch
42
+ env:
43
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
44
+ GH_REPO: ${{ github.repository }}
45
+ run: |
46
+ git config user.name "metabook-automation[bot]"
47
+ git config user.email "metabook-automation[bot]@users.noreply.github.com"
48
+ make cut-release