diff --git a/.claude/skills/cut-release/SKILL.md b/.claude/skills/cut-release/SKILL.md index f7e9e12..ce313ae 100644 --- a/.claude/skills/cut-release/SKILL.md +++ b/.claude/skills/cut-release/SKILL.md @@ -4,10 +4,11 @@ description: >- Runs the OpenLoop Play release, which is two dispatched GitHub Actions workflows rather than a local sequence: `release.yml` bumps the version, merges its own bump PR, then builds and signs the AAB from the resulting merge sha and publishes it as a run artifact; `tag.yml` cuts the tag - afterwards. Use when the user says "/cut-release", "cut a release", "cut release", "ship a - release", "start the release process", "bump the version and release", or wants to move a merged - `main` toward a tagged Play release. The only manual step is uploading the `.aab` to Play - Console; the tag still means "this shipped", so it is cut after that upload, never before. + afterwards. Also drafts both sets of release notes, including the Play Console "What's new" + text. Use when the user says "/cut-release", "cut a release", "cut release", "ship a release", + "start the release process", "bump the version and release", or wants to move a merged `main` + toward a tagged Play release. The only manual step is uploading the `.aab` to Play Console; the + tag still means "this shipped", so it is cut after that upload, never before. --- # cut-release — OpenLoop Play release @@ -17,15 +18,16 @@ Follow [shared operating instructions](../../../docs/OPERATING_INSTRUCTIONS.md) **Releases run in GitHub Actions as of 2026-09-07.** Do not bump the version by hand, do not build the bundle locally, and do not cut the tag with a local script — the pipeline does all of it, with guards that a manual run does not have. Your job is to dispatch the right workflow, -read what it reports, and stop at the one gate a workflow cannot clear. +retrieve and check what it produced, draft the notes, and stop at the one gate a workflow +cannot clear. ## How a release runs **Run 1 — `Release · build signed AAB` (`.github/workflows/release.yml`)** Reads the current `versionCode`, bumps it, opens and merges its own bump PR, resolves the merge -sha, then builds and signs from that exact sha and publishes the `.aab` plus drafted notes as a -run artifact. Guards it enforces, so you do not have to: +sha, then builds and signs from that exact sha and publishes the `.aab` plus a raw commit log as +a run artifact. Guards it enforces, so you do not have to: - `versionName` must equal `1.0.` before it will bump; it stops rather than guess a new scheme if that convention ever breaks @@ -51,17 +53,75 @@ script unmodified to cut the tag and GitHub release. than falling back to doing the release by hand. 2. When it finishes, read the run summary: it prints the version, the build sha, the bundle path and the exact `tag.yml` inputs to use next. -3. Hand the owner the artifact link and the drafted notes. **Stop there** — see below. -4. After the owner confirms the Play upload, dispatch run 2 with the version, sha and run id from +3. Retrieve the bundle (below) and draft both notes files (below). +4. Hand the owner the bundle, the `jarsigner` result, and both drafts. **Stop there.** +5. After the owner confirms the Play upload, dispatch run 2 with the version, sha and run id from that summary. +## Getting the signed bundle + +The runner is a throwaway VM. Nothing it builds reaches the owner's disk, and +`app/build/outputs/bundle/release/app-release.aab` on his machine is whatever he last built by +hand — stale, an older `versionCode`, and a live hazard at upload time. **Never point the owner +at a locally built `.aab`.** The only bundle that shipped through the pipeline is the artifact. + +```bash +gh run download -n openloop- -D +``` + +`gh` extracts into the current directory when `-D` is omitted, preserving the artifact's internal +structure — convenient from the repo root, since the `.aab` lands in the gitignored `releases/`, +but it also drops an untracked `notes/` there. Naming a destination avoids the surprise. From the +browser: open the release run, scroll past the job list to **Artifacts** at the bottom, and +download `openloop-`. Either way the contents are: + +```text +releases/openloop--.aab +notes/commits-.md +``` + +Two properties worth stating to the owner rather than assuming he knows: + +- **Artifacts expire after 30 days.** Long enough to upload, not an archive. Tell him to save the + `.aab` into his local `releases/` folder — it is gitignored, it is what the old process kept, + and it is what `scripts/tag-release.ps1` looks for in the manual fallback. +- **His local `app/build/` is no longer where releases come from** and will keep drifting from + what actually ships. Suggest deleting the stale bundle so there is nothing to grab by mistake. + +## Release notes — you draft both + +Run 1 writes `notes/commits-.md`: the raw commit log since the previous tag. That is +material, not copy. Writing the actual notes is this skill's job (owner call 2026-08-28), and it +is not something a shell script can do — which is a reason to draft them yourself, never a reason +to skip them. + +Draft two files into `docs/local/` (gitignored — owner-only, never commit): + +1. **`github-release-notes-.md`** (technical) — the commit log grouped by area, mirroring + the structure of the 1.0.49–1.0.51 drafts. Only needed if the owner wants a curated summary; + `tag-release.ps1` defaults to `gh --generate-notes` and needs no file at all. Offer it, do not + assume it. +2. **`play-notes-.md`** (user-facing) — the Play Console "What's new". Short, plain + bullets, feature-first, no version numbers, no jargon. Mirror the voice of the 1.0.49 and + 1.0.51 drafts: three tight bullets, not padded to fill a character limit. + +**When nothing user-facing changed**, say so plainly instead of inventing copy. Check with +`git diff --quiet .. -- app/src`. If it is empty, the honest draft is a +one-line maintenance note, and the right advice is that a production rollout may not be worth +spending on a build users cannot tell apart from the last one — an internal testing track proves +the pipeline without publishing a no-op update. Never write "bug fixes and performance +improvements" for a release that contains neither; that is a false claim to real users. + ## Stop — wait for the Play upload (hard stop) Nothing in this session can upload to Play Console or confirm that someone did. **Do not dispatch `tag.yml` until the owner explicitly confirms the `.aab` is uploaded.** Refuse even if asked to "just do it all" — the tag means "this shipped", and only the owner can establish that. -This is now the *only* hard stop. The old Stop A (a required approving review on the bump PR) is +"Go ahead", "sounds good" and "uploading now" are instructions or progress reports, not +statements that the upload finished. Ask, rather than treating enthusiasm as confirmation. + +This is the *only* hard stop. The old Stop A (a required approving review on the bump PR) is gone: the workflow merges its own bump PR, which is a deliberate owner decision, not an oversight. ## Detect where a release currently stands @@ -112,17 +172,6 @@ applies: build and install the release APK from the build sha on an emulator, dr that dependency serves, and confirm no R8-only crash before the owner uploads. If nothing native landed, say so explicitly and skip — do not run a device check with nothing to verify. -## Release notes - -Run 1 drafts both files into the artifact: - -- **GitHub release notes** (technical) — grouped commit log since the previous tag. Only needed - if the owner wants a curated summary; `tag-release.ps1` defaults to `gh --generate-notes`. -- **Play Console "What's new"** (user-facing) — a placeholder, deliberately not auto-written. - Short, plain, feature-first bullets, no version numbers, no jargon. Three tight bullets is the - house style. If no `app/src` changed since the last tag, the draft says so — surface that to - the owner, because a release with nothing user-facing may not be worth a production rollout. - ## Owner calls — binding, read before doing anything 1. **2026-08-28 (issue #158)** — this skill does **not** run or gate on the Play @@ -130,7 +179,11 @@ Run 1 drafts both files into the artifact: `DEFINITION_OF_DONE.md` and `release-signing-and-aab.md` §3; it is deliberately not performed here. If a checklist asks for vitals numbers, write `N/A — vitals check out of scope for /cut-release, owner call 2026-08-28 (issue #158)`. -2. **2026-09-07** — the release is automated end to end except the Play upload. The workflow +2. **2026-08-28 (issue #158)** — this skill **does** draft the Play Console "What's new" text. + Moving the build into a workflow does not retire this: the workflow produces the commit log, + you write the copy. An agent does not get to drop an owner call because the mechanics around + it changed. +3. **2026-09-07** — the release is automated end to end except the Play upload. The workflow merges its own bump PR; no approving review is required on it. The sweep does not run on a release bump. The tag is still cut only after the upload is confirmed. @@ -165,11 +218,15 @@ pipeline, and fix the pipeline afterwards rather than leaving two paths in use. 2. Never type, or ask the owner to type, a `versionCode` or `versionName`. Run 1 reads them. 3. Never derive a build sha from a branch name or a moving `origin/main` — use the sha the release run reports, which is the merge commit it built. -4. Never gather or gate on Play vitals numbers — out of scope per the 2026-08-28 owner call. -5. Never attach the `.aab`, an unsigned APK, or any binary to the GitHub release. +4. Never hand the owner a locally built `.aab`, or let him upload one. The shipping bundle is the + release run's artifact; a local `app/build/` copy is a different build with a different sha. +5. Never write user-facing release notes for changes that did not happen. If nothing in `app/src` + moved, say so. +6. Never gather or gate on Play vitals numbers — out of scope per the 2026-08-28 owner call. +7. Never attach the `.aab`, an unsigned APK, or any binary to the GitHub release. `release-signing-and-aab.md`'s "Never attach" section explains why: a signing-key mismatch between the upload key and Play's app-signing key forks the install base permanently. The bundle lives in the workflow run's artifacts, which is not the same thing. -6. Never do the release by hand because dispatching is inconvenient. If you cannot dispatch the +8. Never do the release by hand because dispatching is inconvenient. If you cannot dispatch the workflow from this session, say so and hand it to the owner. -7. If a stage's precondition does not hold, say so and refuse rather than improvising around it. +9. If a stage's precondition does not hold, say so and refuse rather than improvising around it. diff --git a/.codex/skills/cut-release/SKILL.md b/.codex/skills/cut-release/SKILL.md index f7e9e12..ce313ae 100644 --- a/.codex/skills/cut-release/SKILL.md +++ b/.codex/skills/cut-release/SKILL.md @@ -4,10 +4,11 @@ description: >- Runs the OpenLoop Play release, which is two dispatched GitHub Actions workflows rather than a local sequence: `release.yml` bumps the version, merges its own bump PR, then builds and signs the AAB from the resulting merge sha and publishes it as a run artifact; `tag.yml` cuts the tag - afterwards. Use when the user says "/cut-release", "cut a release", "cut release", "ship a - release", "start the release process", "bump the version and release", or wants to move a merged - `main` toward a tagged Play release. The only manual step is uploading the `.aab` to Play - Console; the tag still means "this shipped", so it is cut after that upload, never before. + afterwards. Also drafts both sets of release notes, including the Play Console "What's new" + text. Use when the user says "/cut-release", "cut a release", "cut release", "ship a release", + "start the release process", "bump the version and release", or wants to move a merged `main` + toward a tagged Play release. The only manual step is uploading the `.aab` to Play Console; the + tag still means "this shipped", so it is cut after that upload, never before. --- # cut-release — OpenLoop Play release @@ -17,15 +18,16 @@ Follow [shared operating instructions](../../../docs/OPERATING_INSTRUCTIONS.md) **Releases run in GitHub Actions as of 2026-09-07.** Do not bump the version by hand, do not build the bundle locally, and do not cut the tag with a local script — the pipeline does all of it, with guards that a manual run does not have. Your job is to dispatch the right workflow, -read what it reports, and stop at the one gate a workflow cannot clear. +retrieve and check what it produced, draft the notes, and stop at the one gate a workflow +cannot clear. ## How a release runs **Run 1 — `Release · build signed AAB` (`.github/workflows/release.yml`)** Reads the current `versionCode`, bumps it, opens and merges its own bump PR, resolves the merge -sha, then builds and signs from that exact sha and publishes the `.aab` plus drafted notes as a -run artifact. Guards it enforces, so you do not have to: +sha, then builds and signs from that exact sha and publishes the `.aab` plus a raw commit log as +a run artifact. Guards it enforces, so you do not have to: - `versionName` must equal `1.0.` before it will bump; it stops rather than guess a new scheme if that convention ever breaks @@ -51,17 +53,75 @@ script unmodified to cut the tag and GitHub release. than falling back to doing the release by hand. 2. When it finishes, read the run summary: it prints the version, the build sha, the bundle path and the exact `tag.yml` inputs to use next. -3. Hand the owner the artifact link and the drafted notes. **Stop there** — see below. -4. After the owner confirms the Play upload, dispatch run 2 with the version, sha and run id from +3. Retrieve the bundle (below) and draft both notes files (below). +4. Hand the owner the bundle, the `jarsigner` result, and both drafts. **Stop there.** +5. After the owner confirms the Play upload, dispatch run 2 with the version, sha and run id from that summary. +## Getting the signed bundle + +The runner is a throwaway VM. Nothing it builds reaches the owner's disk, and +`app/build/outputs/bundle/release/app-release.aab` on his machine is whatever he last built by +hand — stale, an older `versionCode`, and a live hazard at upload time. **Never point the owner +at a locally built `.aab`.** The only bundle that shipped through the pipeline is the artifact. + +```bash +gh run download -n openloop- -D +``` + +`gh` extracts into the current directory when `-D` is omitted, preserving the artifact's internal +structure — convenient from the repo root, since the `.aab` lands in the gitignored `releases/`, +but it also drops an untracked `notes/` there. Naming a destination avoids the surprise. From the +browser: open the release run, scroll past the job list to **Artifacts** at the bottom, and +download `openloop-`. Either way the contents are: + +```text +releases/openloop--.aab +notes/commits-.md +``` + +Two properties worth stating to the owner rather than assuming he knows: + +- **Artifacts expire after 30 days.** Long enough to upload, not an archive. Tell him to save the + `.aab` into his local `releases/` folder — it is gitignored, it is what the old process kept, + and it is what `scripts/tag-release.ps1` looks for in the manual fallback. +- **His local `app/build/` is no longer where releases come from** and will keep drifting from + what actually ships. Suggest deleting the stale bundle so there is nothing to grab by mistake. + +## Release notes — you draft both + +Run 1 writes `notes/commits-.md`: the raw commit log since the previous tag. That is +material, not copy. Writing the actual notes is this skill's job (owner call 2026-08-28), and it +is not something a shell script can do — which is a reason to draft them yourself, never a reason +to skip them. + +Draft two files into `docs/local/` (gitignored — owner-only, never commit): + +1. **`github-release-notes-.md`** (technical) — the commit log grouped by area, mirroring + the structure of the 1.0.49–1.0.51 drafts. Only needed if the owner wants a curated summary; + `tag-release.ps1` defaults to `gh --generate-notes` and needs no file at all. Offer it, do not + assume it. +2. **`play-notes-.md`** (user-facing) — the Play Console "What's new". Short, plain + bullets, feature-first, no version numbers, no jargon. Mirror the voice of the 1.0.49 and + 1.0.51 drafts: three tight bullets, not padded to fill a character limit. + +**When nothing user-facing changed**, say so plainly instead of inventing copy. Check with +`git diff --quiet .. -- app/src`. If it is empty, the honest draft is a +one-line maintenance note, and the right advice is that a production rollout may not be worth +spending on a build users cannot tell apart from the last one — an internal testing track proves +the pipeline without publishing a no-op update. Never write "bug fixes and performance +improvements" for a release that contains neither; that is a false claim to real users. + ## Stop — wait for the Play upload (hard stop) Nothing in this session can upload to Play Console or confirm that someone did. **Do not dispatch `tag.yml` until the owner explicitly confirms the `.aab` is uploaded.** Refuse even if asked to "just do it all" — the tag means "this shipped", and only the owner can establish that. -This is now the *only* hard stop. The old Stop A (a required approving review on the bump PR) is +"Go ahead", "sounds good" and "uploading now" are instructions or progress reports, not +statements that the upload finished. Ask, rather than treating enthusiasm as confirmation. + +This is the *only* hard stop. The old Stop A (a required approving review on the bump PR) is gone: the workflow merges its own bump PR, which is a deliberate owner decision, not an oversight. ## Detect where a release currently stands @@ -112,17 +172,6 @@ applies: build and install the release APK from the build sha on an emulator, dr that dependency serves, and confirm no R8-only crash before the owner uploads. If nothing native landed, say so explicitly and skip — do not run a device check with nothing to verify. -## Release notes - -Run 1 drafts both files into the artifact: - -- **GitHub release notes** (technical) — grouped commit log since the previous tag. Only needed - if the owner wants a curated summary; `tag-release.ps1` defaults to `gh --generate-notes`. -- **Play Console "What's new"** (user-facing) — a placeholder, deliberately not auto-written. - Short, plain, feature-first bullets, no version numbers, no jargon. Three tight bullets is the - house style. If no `app/src` changed since the last tag, the draft says so — surface that to - the owner, because a release with nothing user-facing may not be worth a production rollout. - ## Owner calls — binding, read before doing anything 1. **2026-08-28 (issue #158)** — this skill does **not** run or gate on the Play @@ -130,7 +179,11 @@ Run 1 drafts both files into the artifact: `DEFINITION_OF_DONE.md` and `release-signing-and-aab.md` §3; it is deliberately not performed here. If a checklist asks for vitals numbers, write `N/A — vitals check out of scope for /cut-release, owner call 2026-08-28 (issue #158)`. -2. **2026-09-07** — the release is automated end to end except the Play upload. The workflow +2. **2026-08-28 (issue #158)** — this skill **does** draft the Play Console "What's new" text. + Moving the build into a workflow does not retire this: the workflow produces the commit log, + you write the copy. An agent does not get to drop an owner call because the mechanics around + it changed. +3. **2026-09-07** — the release is automated end to end except the Play upload. The workflow merges its own bump PR; no approving review is required on it. The sweep does not run on a release bump. The tag is still cut only after the upload is confirmed. @@ -165,11 +218,15 @@ pipeline, and fix the pipeline afterwards rather than leaving two paths in use. 2. Never type, or ask the owner to type, a `versionCode` or `versionName`. Run 1 reads them. 3. Never derive a build sha from a branch name or a moving `origin/main` — use the sha the release run reports, which is the merge commit it built. -4. Never gather or gate on Play vitals numbers — out of scope per the 2026-08-28 owner call. -5. Never attach the `.aab`, an unsigned APK, or any binary to the GitHub release. +4. Never hand the owner a locally built `.aab`, or let him upload one. The shipping bundle is the + release run's artifact; a local `app/build/` copy is a different build with a different sha. +5. Never write user-facing release notes for changes that did not happen. If nothing in `app/src` + moved, say so. +6. Never gather or gate on Play vitals numbers — out of scope per the 2026-08-28 owner call. +7. Never attach the `.aab`, an unsigned APK, or any binary to the GitHub release. `release-signing-and-aab.md`'s "Never attach" section explains why: a signing-key mismatch between the upload key and Play's app-signing key forks the install base permanently. The bundle lives in the workflow run's artifacts, which is not the same thing. -6. Never do the release by hand because dispatching is inconvenient. If you cannot dispatch the +8. Never do the release by hand because dispatching is inconvenient. If you cannot dispatch the workflow from this session, say so and hand it to the owner. -7. If a stage's precondition does not hold, say so and refuse rather than improvising around it. +9. If a stage's precondition does not hold, say so and refuse rather than improvising around it. diff --git a/.cursor/skills/cut-release/SKILL.md b/.cursor/skills/cut-release/SKILL.md index f7e9e12..ce313ae 100644 --- a/.cursor/skills/cut-release/SKILL.md +++ b/.cursor/skills/cut-release/SKILL.md @@ -4,10 +4,11 @@ description: >- Runs the OpenLoop Play release, which is two dispatched GitHub Actions workflows rather than a local sequence: `release.yml` bumps the version, merges its own bump PR, then builds and signs the AAB from the resulting merge sha and publishes it as a run artifact; `tag.yml` cuts the tag - afterwards. Use when the user says "/cut-release", "cut a release", "cut release", "ship a - release", "start the release process", "bump the version and release", or wants to move a merged - `main` toward a tagged Play release. The only manual step is uploading the `.aab` to Play - Console; the tag still means "this shipped", so it is cut after that upload, never before. + afterwards. Also drafts both sets of release notes, including the Play Console "What's new" + text. Use when the user says "/cut-release", "cut a release", "cut release", "ship a release", + "start the release process", "bump the version and release", or wants to move a merged `main` + toward a tagged Play release. The only manual step is uploading the `.aab` to Play Console; the + tag still means "this shipped", so it is cut after that upload, never before. --- # cut-release — OpenLoop Play release @@ -17,15 +18,16 @@ Follow [shared operating instructions](../../../docs/OPERATING_INSTRUCTIONS.md) **Releases run in GitHub Actions as of 2026-09-07.** Do not bump the version by hand, do not build the bundle locally, and do not cut the tag with a local script — the pipeline does all of it, with guards that a manual run does not have. Your job is to dispatch the right workflow, -read what it reports, and stop at the one gate a workflow cannot clear. +retrieve and check what it produced, draft the notes, and stop at the one gate a workflow +cannot clear. ## How a release runs **Run 1 — `Release · build signed AAB` (`.github/workflows/release.yml`)** Reads the current `versionCode`, bumps it, opens and merges its own bump PR, resolves the merge -sha, then builds and signs from that exact sha and publishes the `.aab` plus drafted notes as a -run artifact. Guards it enforces, so you do not have to: +sha, then builds and signs from that exact sha and publishes the `.aab` plus a raw commit log as +a run artifact. Guards it enforces, so you do not have to: - `versionName` must equal `1.0.` before it will bump; it stops rather than guess a new scheme if that convention ever breaks @@ -51,17 +53,75 @@ script unmodified to cut the tag and GitHub release. than falling back to doing the release by hand. 2. When it finishes, read the run summary: it prints the version, the build sha, the bundle path and the exact `tag.yml` inputs to use next. -3. Hand the owner the artifact link and the drafted notes. **Stop there** — see below. -4. After the owner confirms the Play upload, dispatch run 2 with the version, sha and run id from +3. Retrieve the bundle (below) and draft both notes files (below). +4. Hand the owner the bundle, the `jarsigner` result, and both drafts. **Stop there.** +5. After the owner confirms the Play upload, dispatch run 2 with the version, sha and run id from that summary. +## Getting the signed bundle + +The runner is a throwaway VM. Nothing it builds reaches the owner's disk, and +`app/build/outputs/bundle/release/app-release.aab` on his machine is whatever he last built by +hand — stale, an older `versionCode`, and a live hazard at upload time. **Never point the owner +at a locally built `.aab`.** The only bundle that shipped through the pipeline is the artifact. + +```bash +gh run download -n openloop- -D +``` + +`gh` extracts into the current directory when `-D` is omitted, preserving the artifact's internal +structure — convenient from the repo root, since the `.aab` lands in the gitignored `releases/`, +but it also drops an untracked `notes/` there. Naming a destination avoids the surprise. From the +browser: open the release run, scroll past the job list to **Artifacts** at the bottom, and +download `openloop-`. Either way the contents are: + +```text +releases/openloop--.aab +notes/commits-.md +``` + +Two properties worth stating to the owner rather than assuming he knows: + +- **Artifacts expire after 30 days.** Long enough to upload, not an archive. Tell him to save the + `.aab` into his local `releases/` folder — it is gitignored, it is what the old process kept, + and it is what `scripts/tag-release.ps1` looks for in the manual fallback. +- **His local `app/build/` is no longer where releases come from** and will keep drifting from + what actually ships. Suggest deleting the stale bundle so there is nothing to grab by mistake. + +## Release notes — you draft both + +Run 1 writes `notes/commits-.md`: the raw commit log since the previous tag. That is +material, not copy. Writing the actual notes is this skill's job (owner call 2026-08-28), and it +is not something a shell script can do — which is a reason to draft them yourself, never a reason +to skip them. + +Draft two files into `docs/local/` (gitignored — owner-only, never commit): + +1. **`github-release-notes-.md`** (technical) — the commit log grouped by area, mirroring + the structure of the 1.0.49–1.0.51 drafts. Only needed if the owner wants a curated summary; + `tag-release.ps1` defaults to `gh --generate-notes` and needs no file at all. Offer it, do not + assume it. +2. **`play-notes-.md`** (user-facing) — the Play Console "What's new". Short, plain + bullets, feature-first, no version numbers, no jargon. Mirror the voice of the 1.0.49 and + 1.0.51 drafts: three tight bullets, not padded to fill a character limit. + +**When nothing user-facing changed**, say so plainly instead of inventing copy. Check with +`git diff --quiet .. -- app/src`. If it is empty, the honest draft is a +one-line maintenance note, and the right advice is that a production rollout may not be worth +spending on a build users cannot tell apart from the last one — an internal testing track proves +the pipeline without publishing a no-op update. Never write "bug fixes and performance +improvements" for a release that contains neither; that is a false claim to real users. + ## Stop — wait for the Play upload (hard stop) Nothing in this session can upload to Play Console or confirm that someone did. **Do not dispatch `tag.yml` until the owner explicitly confirms the `.aab` is uploaded.** Refuse even if asked to "just do it all" — the tag means "this shipped", and only the owner can establish that. -This is now the *only* hard stop. The old Stop A (a required approving review on the bump PR) is +"Go ahead", "sounds good" and "uploading now" are instructions or progress reports, not +statements that the upload finished. Ask, rather than treating enthusiasm as confirmation. + +This is the *only* hard stop. The old Stop A (a required approving review on the bump PR) is gone: the workflow merges its own bump PR, which is a deliberate owner decision, not an oversight. ## Detect where a release currently stands @@ -112,17 +172,6 @@ applies: build and install the release APK from the build sha on an emulator, dr that dependency serves, and confirm no R8-only crash before the owner uploads. If nothing native landed, say so explicitly and skip — do not run a device check with nothing to verify. -## Release notes - -Run 1 drafts both files into the artifact: - -- **GitHub release notes** (technical) — grouped commit log since the previous tag. Only needed - if the owner wants a curated summary; `tag-release.ps1` defaults to `gh --generate-notes`. -- **Play Console "What's new"** (user-facing) — a placeholder, deliberately not auto-written. - Short, plain, feature-first bullets, no version numbers, no jargon. Three tight bullets is the - house style. If no `app/src` changed since the last tag, the draft says so — surface that to - the owner, because a release with nothing user-facing may not be worth a production rollout. - ## Owner calls — binding, read before doing anything 1. **2026-08-28 (issue #158)** — this skill does **not** run or gate on the Play @@ -130,7 +179,11 @@ Run 1 drafts both files into the artifact: `DEFINITION_OF_DONE.md` and `release-signing-and-aab.md` §3; it is deliberately not performed here. If a checklist asks for vitals numbers, write `N/A — vitals check out of scope for /cut-release, owner call 2026-08-28 (issue #158)`. -2. **2026-09-07** — the release is automated end to end except the Play upload. The workflow +2. **2026-08-28 (issue #158)** — this skill **does** draft the Play Console "What's new" text. + Moving the build into a workflow does not retire this: the workflow produces the commit log, + you write the copy. An agent does not get to drop an owner call because the mechanics around + it changed. +3. **2026-09-07** — the release is automated end to end except the Play upload. The workflow merges its own bump PR; no approving review is required on it. The sweep does not run on a release bump. The tag is still cut only after the upload is confirmed. @@ -165,11 +218,15 @@ pipeline, and fix the pipeline afterwards rather than leaving two paths in use. 2. Never type, or ask the owner to type, a `versionCode` or `versionName`. Run 1 reads them. 3. Never derive a build sha from a branch name or a moving `origin/main` — use the sha the release run reports, which is the merge commit it built. -4. Never gather or gate on Play vitals numbers — out of scope per the 2026-08-28 owner call. -5. Never attach the `.aab`, an unsigned APK, or any binary to the GitHub release. +4. Never hand the owner a locally built `.aab`, or let him upload one. The shipping bundle is the + release run's artifact; a local `app/build/` copy is a different build with a different sha. +5. Never write user-facing release notes for changes that did not happen. If nothing in `app/src` + moved, say so. +6. Never gather or gate on Play vitals numbers — out of scope per the 2026-08-28 owner call. +7. Never attach the `.aab`, an unsigned APK, or any binary to the GitHub release. `release-signing-and-aab.md`'s "Never attach" section explains why: a signing-key mismatch between the upload key and Play's app-signing key forks the install base permanently. The bundle lives in the workflow run's artifacts, which is not the same thing. -6. Never do the release by hand because dispatching is inconvenient. If you cannot dispatch the +8. Never do the release by hand because dispatching is inconvenient. If you cannot dispatch the workflow from this session, say so and hand it to the owner. -7. If a stage's precondition does not hold, say so and refuse rather than improvising around it. +9. If a stage's precondition does not hold, say so and refuse rather than improvising around it. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3cf14e..9ae5ac5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,10 @@ name: Release · build signed AAB # resulting merge sha, and publishes it as a run artifact. The owner uploads that .aab to Play, # then dispatches tag.yml (run 2) to cut the tag — so the tag keeps meaning "this shipped". # +# The artifact also carries the commit log since the previous tag. That is material for the +# release notes, not the notes themselves: the cut-release skill writes those (owner call +# 2026-08-28), because store copy is a claim to real users and no shell script should be making it. +# # There is deliberately NO pre-PR sweep here. The bump diff is two lines in app/build.gradle.kts; # nothing in the sweep can be affected by changing an integer, and every commit this release # carries already passed the sweep on its own feature PR. What feature PRs never cover is the @@ -236,7 +240,10 @@ jobs: git diff "$prev".."$SHA" -- gradle/libs.versions.toml fi - - name: Draft release notes + # Material for the release notes, not the notes. Writing them is the cut-release skill's + # job (owner call 2026-08-28) — store copy is a claim to real users, and a shell script has + # no business making it. Named commits-*.md so nobody mistakes it for finished copy. + - name: Collect the commit log env: NAME: ${{ steps.build.outputs.name }} SHA: ${{ steps.build.outputs.sha }} @@ -245,23 +252,26 @@ jobs: mkdir -p notes prev=$(git tag --sort=-v:refname | head -1) { - echo "# ${NAME}" + echo "# ${NAME} — raw material for the release notes" + echo + echo "This is NOT release notes. The cut-release skill writes those, in two files:" + echo "docs/local/github-release-notes-${NAME}.md and docs/local/play-notes-${NAME}.md." echo - echo "## What's new since ${prev:-the beginning}" + echo "## Commits since ${prev:-the beginning}" echo if [[ -n "$prev" ]]; then git log "$prev".."$SHA" --oneline --no-merges; fi echo - echo "" - } > "notes/github-release-notes-${NAME}.md" - { - echo "" if [[ -n "$prev" ]] && git diff --quiet "$prev".."$SHA" -- app/src; then - echo "NOTE: no app/src changed since ${prev}. This release has nothing user-facing to announce." + echo "## Nothing user-facing changed" + echo + echo "No file under app/src changed since ${prev}. Say that plainly in the Play notes" + echo "instead of inventing feature copy, and raise with the owner whether a production" + echo "rollout is worth spending on a build users cannot tell apart from the last one." fi - } > "notes/play-notes-${NAME}.md" + } > "notes/commits-${NAME}.md" + cat "notes/commits-${NAME}.md" - - name: Publish the bundle and notes + - name: Publish the bundle and commit log uses: actions/upload-artifact@v4 with: name: openloop-${{ steps.build.outputs.name }} @@ -284,6 +294,7 @@ jobs: echo echo "### Next" echo - echo "1. Download the artifact above and upload the \`.aab\` to Play Console." - echo "2. Dispatch **Tag release** with version \`${{ steps.build.outputs.name }}\`, sha \`${{ steps.build.outputs.sha }}\`, run id \`${GITHUB_RUN_ID}\`." + echo "1. \`gh run download ${GITHUB_RUN_ID} -n openloop-${{ steps.build.outputs.name }}\` — or the Artifacts section below." + echo "2. Write the release notes from \`notes/commits-${{ steps.build.outputs.name }}.md\`, then upload the \`.aab\` to Play Console." + echo "3. Dispatch **Tag release** with version \`${{ steps.build.outputs.name }}\`, sha \`${{ steps.build.outputs.sha }}\`, run id \`${GITHUB_RUN_ID}\`." } >> "$GITHUB_STEP_SUMMARY"