Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ shows the documentation of that version.
(and once per organisation, [One-time organisation setup](docs/organisation-setup.md)).
3. Start a release: **Actions → Release → Run workflow** on `main` ([Releasing](docs/releasing.md)).

Or let a coding agent walk you through it: [the adopt-release-tools skill](docs/adopting.md#let-an-agent-do-it).

## Pages

- [Home](docs/index.md)
Expand Down
19 changes: 18 additions & 1 deletion docs/adopting.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,28 @@ rather than through `vars` inheritance or `secrets: inherit`: the reusable workf
declare them as a required input and a required secret, so a stub that forgets one fails
before anything runs, and no other secret of the repository reaches release-tools.

## Let an agent do it

Repository admins can hand the adoption to a coding agent with the
`adopt-release-tools` [agent skill](https://agentskills.io), which ships in
[`skills/adopt-release-tools`](https://github.com/Algorithmiq/release-tools/tree/main/skills/adopt-release-tools):

```bash
npx skills add Algorithmiq/release-tools --skill adopt-release-tools
```

or copy that folder into your agent's skills directory. Then ask the agent to adopt
release-tools in your repository. It reads the repository and these pages at the latest
release, writes the files and opens the adoption PR; you run every command that changes
GitHub, from the commands it prepares. It follows this page and
[Per-repository setup](repository-setup.md), which stay the reference.

## `.github/workflows/release.yml`

Set `version-files` to the files that hold the version number, such as `VERSION` or
`pyproject.toml` (whose `[project]` version is updated). Leave it empty if the version
comes from the git tag (setuptools-scm, hatch-vcs). `stage` chooses where a release
comes only from the git tag (setuptools-scm, hatch-vcs). A `VERSION` file kept in step
with the tags next to setuptools-scm goes in too: the release bot then writes it. `stage` chooses where a release
starts (alpha, beta or rc) and promotes a release in beta to release candidates; see
[Releasing](releasing.md).

Expand Down
6 changes: 6 additions & 0 deletions docs/developing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ just test # the release package, cliff.toml, the actions, and that the examp
reference uses the same release-tools tag and git-cliff version.
- `tests/test_leaks.py` checks that no tracked file holds an AWS account ID, ARN or
endpoint, or names an `Algorithmiq/` repository other than the public ones.
- `tests/test_repository_setup.py` runs the jq of docs/repository-setup.md, step 8, on
sample rulesets: it adds only what is missing, so a second run changes nothing.
- `tests/test_skill.py` checks the agent skill in `skills/adopt-release-tools/`: its
frontmatter, the major version it is written for, that every release-tools path
and heading it reads exists, and that it stops for the admin and hands over only
literal commands.

On Nix, `nix develop` (or [direnv](https://direnv.net) with the tracked `.envrc`) gives a
shell with Python 3.12, uv, just and git; run the same commands inside it. The tools
Expand Down
3 changes: 3 additions & 0 deletions docs/organisation-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ An organisation owner does this once. The commands need `gh` with the `admin:org
--repos release-tools,AEGISS < algorithmiq-release-bot.*.private-key.pem
```

A repository that adopts the process later is added to both lists on its own
([Per-repository setup](repository-setup.md), step 1).

3. **Allow the actions.** If the organisation restricts which actions and reusable
workflows may run, allow `Algorithmiq/release-tools/*` and the actions it uses
(`actions/checkout`, `actions/create-github-app-token`,
Expand Down
78 changes: 63 additions & 15 deletions docs/repository-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ which retargets the open PRs; everyone then updates their clones.

1. **Install the app and share the credentials** with the repository: add it to the app's
installation (Organisation settings → GitHub Apps → algorithmiq-release-bot →
Configure), and to the `--repos` lists of the variable and the secret.
Configure), and to the repositories of the variable and the secret, one at a time.
Not with `--repos`: it replaces each list, and drops every repository left out.

```bash
REPO_ID=$(gh api repos/$REPO --jq .id)
gh api -X PUT orgs/Algorithmiq/actions/variables/RELEASE_BOT_CLIENT_ID/repositories/$REPO_ID
gh api -X PUT orgs/Algorithmiq/actions/secrets/RELEASE_BOT_PRIVATE_KEY/repositories/$REPO_ID
```

2. **The baseline: where versions count from and the changelog starts.** The release
workflow counts versions from the newest final tag `vX.Y.Z`, and the changelog starts
there. Settle it before the tag ruleset (step 5) exists.
Expand Down Expand Up @@ -112,25 +120,65 @@ which retargets the open PRs; everyone then updates their clones.
([One-time organisation setup](organisation-setup.md), step 5) and the roles' trust
policies cover every repository. The first pre-release is published straight away.
8. **After the stubs are on `main`: require the PR checks on `main` and let the bot
bypass the `main` ruleset.** `pull_request_target` runs the workflow file of the base
branch, so the checks never report until the stub is on `main`; requiring them earlier
blocks every PR, including the one adding them. The bypass lets the bot merge its
changelog PR at once, without a review or the PR checks (see [Caveats](caveats.md));
without it, each changelog PR waits for a maintainer's approval.
bypass the rulesets of `main`.** `pull_request_target` runs the workflow file of the
base branch, so the checks never report until the stub is on `main`; requiring them
earlier blocks every PR, including the one adding them. The bypass lets the bot merge
its changelog PR at once, without a review or the PR checks (see
[Caveats](caveats.md)); without it, each changelog PR waits for a maintainer's
approval.

The rulesets that apply to `main`, whatever their name:

```bash
gh api repos/$REPO/rules/branches/main --jq '[.[] | {ruleset_id, ruleset_source_type}] | unique'
```

Run the following once for each `Repository` one with a `pull_request` or
`required_status_checks` rule, with its id. It adds the two checks, and a
`required_status_checks` rule if the ruleset has none, and the bot to the bypass
list, each only if it is missing: running it again changes nothing.

```bash
id=$(gh api repos/$REPO/rulesets --jq '.[] | select(.name == "main") | .id')
gh api repos/$REPO/rulesets/$id | jq --argjson app "$APP_ID" '{name, target, enforcement, conditions,
rules: (.rules | map(if .type == "required_status_checks"
then .parameters.required_status_checks += [{"context": "PR checks / PR title", "integration_id": 15368},
{"context": "PR checks / Migration notes", "integration_id": 15368}]
else . end)),
bypass_actors: (.bypass_actors + [{"actor_id": $app, "actor_type": "Integration", "bypass_mode": "always"}])}' \
id=<the ruleset id>
gh api repos/$REPO/rulesets/$id | jq --argjson app "$APP_ID" --argjson checks '[
{"context": "PR checks / PR title", "integration_id": 15368},
{"context": "PR checks / Migration notes", "integration_id": 15368}]' '
{name, target, enforcement, conditions,
rules: (if ($checks | length) == 0 or any(.rules[]; .type == "required_status_checks")
then .rules
else .rules + [{"type": "required_status_checks", "parameters":
{"strict_required_status_checks_policy": false, "required_status_checks": []}}] end
| map(if .type == "required_status_checks"
then .parameters.required_status_checks |= (. as $have
| . + [$checks[] | select(.context as $c | all($have[]; .context != $c))])
else . end)),
bypass_actors: (.bypass_actors // []
| if any(.[]; .actor_id == $app and .actor_type == "Integration") then .
else . + [{"actor_id": $app, "actor_type": "Integration", "bypass_mode": "always"}] end)}' \
| gh api -X PUT repos/$REPO/rulesets/$id --input -
```

This assumes a `main` ruleset with a `required_status_checks` rule; create one first
if there is none.
An `Organization` ruleset with such a rule gets the bypass only: the same command
with `--argjson checks '[]'`, at `orgs/Algorithmiq/rulesets/$id` in both calls, the
read too, run by an organisation owner. Never the checks: an organisation ruleset would require
them in every repository it targets, and they never report where the stubs are not,
so every PR there would wait.

If no `Repository` ruleset applies to `main`, create one that requires the checks:

```bash
gh api -X POST repos/$REPO/rulesets --input - <<EOF
{"name": "PR checks", "target": "branch", "enforcement": "active",
"conditions": {"ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}},
"rules": [{"type": "required_status_checks", "parameters": {"strict_required_status_checks_policy": false,
"required_status_checks": [{"context": "PR checks / PR title", "integration_id": 15368},
{"context": "PR checks / Migration notes", "integration_id": 15368}]}}],
"bypass_actors": [{"actor_id": $APP_ID, "actor_type": "Integration", "bypass_mode": "always"}]}
EOF
```

A repository that had its own checks for PR titles or migration notes drops them from
its ruleset now (see [Caveats](caveats.md)).

The first release: check that no deprecation is due for removal, then
**Actions → Release → Run workflow** on `main` (or
Expand Down
Loading
Loading