diff --git a/AGENTS.md b/AGENTS.md index 3112663..f4b847b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,11 @@ The app should be treated as a trusted local control agent. Be conservative with - Keep pull requests tightly scoped to one issue wherever possible. - Do not mix implementation work with broad refactors unless the issue explicitly calls for it. - Prefer existing project patterns once the app scaffold exists. +- Before creating a milestone, verify whether the intended milestone already exists. +- Reuse an existing open milestone instead of creating a duplicate. +- Create a release milestone only when it is missing. +- Do not create future release milestones casually. The active release milestone should normally be the next version after `package.json`. +- Before release work starts, verify the target milestone name exactly matches the version being released, for example `Release 0.1.8` for `package.json` version `0.1.8`. ## Development standards @@ -82,6 +87,8 @@ Only the maintainer should publish releases. Release builds are published from tags named `vX.Y.Z`, where `X.Y.Z` must match `package.json`. +Before creating a release milestone, verify whether the intended milestone already exists. Reuse an existing open milestone. Create the milestone only if it is missing. If the milestone exists but is closed, reopen it only when the work truly belongs in that release. + The release workflow is `.github/workflows/release.yml`. It runs on the self-hosted Windows signing runner with the `switchify-signing` label. The runner is expected to have: - Windows. @@ -101,6 +108,71 @@ $env:SWITCHIFY_CERTUM_CERT_THUMBPRINT = "" $env:SWITCHIFY_CERTUM_TIMESTAMP_URL = "http://time.certum.pl" ``` +### Release preflight + +Before creating or pushing a release tag, the maintainer or agent must verify all of the following and stop if any check fails: + +- Local `main` is clean and up to date with `origin/main`. +- `package.json` version is the exact version being released. +- The release tag is exactly `vX.Y.Z`, where `X.Y.Z` equals `package.json`. +- The release issue and release PR are assigned to milestone `Release X.Y.Z`. +- The milestone `Release X.Y.Z` exists and has no unrelated open issues. +- The self-hosted runner with label `switchify-signing` is online and not busy. +- The Certum signing certificate thumbprint is available out-of-band or through the configured repository variable. +- The matching certificate exists in `Cert:\CurrentUser\My`, has a private key, and SimplySign is logged in. + +Use commands like these for preflight checks. Do not print, document, or commit the real certificate thumbprint. + +```powershell +$packageVersion = node -p "require('./package.json').version" +$expectedTag = "v$packageVersion" +$expectedMilestone = "Release $packageVersion" + +git status --short --branch +git pull --ff-only + +$milestones = gh api repos/switchifyapp/switchify-pc/milestones?state=all | ConvertFrom-Json +$milestone = $milestones | Where-Object { $_.title -eq $expectedMilestone } | Select-Object -First 1 +if (-not $milestone) { + throw "Expected milestone was not found: $expectedMilestone" +} + +$runner = (gh api repos/switchifyapp/switchify-pc/actions/runners | ConvertFrom-Json).runners | + Where-Object { $_.labels.name -contains 'switchify-signing' } | + Select-Object -First 1 + +if (-not $runner -or $runner.status -ne 'online' -or $runner.busy) { + throw 'The switchify-signing runner is not ready.' +} + +$thumbprint = $env:SWITCHIFY_CERTUM_CERT_THUMBPRINT -replace '[^a-fA-F0-9]', '' +if (-not $thumbprint) { + throw 'SWITCHIFY_CERTUM_CERT_THUMBPRINT is not set in this shell. Do not hard-code it.' +} + +$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | + Where-Object { $_.Thumbprint -eq $thumbprint } | + Select-Object -First 1 + +if (-not $cert -or -not $cert.HasPrivateKey) { + throw 'The Certum signing certificate is missing or has no private key.' +} +``` + +When preparing the next milestone, verify first and create only if missing: + +```powershell +$nextMilestone = "Release X.Y.Z" +$milestones = gh api repos/switchifyapp/switchify-pc/milestones?state=all | ConvertFrom-Json +$existing = $milestones | Where-Object { $_.title -eq $nextMilestone } | Select-Object -First 1 + +if (-not $existing) { + gh api repos/switchifyapp/switchify-pc/milestones -f title=$nextMilestone +} +``` + +If the thumbprint is available only as a GitHub repository variable, the maintainer may verify that the variable exists without printing its value, then verify the local certificate with the thumbprint supplied privately. + The release workflow: - installs dependencies with `npm ci` @@ -136,6 +208,47 @@ git push origin vX.Y.Z The workflow can also be dispatched manually by the maintainer with a `tag` input. +Do not push a release tag until the release PR with the matching version bump has merged to `main` and local `main` has been pulled. + +Do not tag if: + +- `package.json` does not match the intended tag. +- The release issue or PR milestone does not match `Release X.Y.Z`. +- The signing runner is offline or busy. +- The Certum certificate cannot be verified locally. + +After the release workflow succeeds: + +1. Verify the GitHub Release exists for `vX.Y.Z`. +2. Verify the release assets are present: + - `Switchify-PC-Setup-X.Y.Z-x64.exe` + - `latest.yml` + - `builder-debug.yml` +3. Verify there are no open issues in milestone `Release X.Y.Z`. +4. Close milestone `Release X.Y.Z`. +5. If issues remain open, do not close the milestone; report the blockers. + +Use commands like these for post-release verification and milestone closure: + +```powershell +gh release view $expectedTag --repo switchifyapp/switchify-pc + +$openIssues = gh issue list ` + --repo switchifyapp/switchify-pc ` + --milestone $expectedMilestone ` + --state open ` + --json number,title,state | ConvertFrom-Json + +if ($openIssues.Count -gt 0) { + throw "Milestone $expectedMilestone still has open issues." +} + +gh api "repos/switchifyapp/switchify-pc/milestones/$($milestone.number)" ` + -X PATCH ` + -f state=closed ` + --silent +``` + Do not: - publish releases from contributor machines