From e3571b2e1bf2ee97d70e9ee59f9ce9189a6c54d8 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Thu, 4 Jun 2026 14:57:18 -0400 Subject: [PATCH] fix(#676): update tests/docs referencing deleted hotfix.yml (#680) hotfix.yml was deleted (folded into release.yml). Remove the now-broken release-coverage-scope and policy-release-no-npm-self-upgrade assertions that readFileSync'd hotfix.yml (release.yml equivalents retained), drop the dead install-smoke.yml path trigger, and update VERSIONING.md / docs/branching.md prose to describe hotfixes via the Release workflow with a patch version. Co-authored-by: Claude Opus 4.8 --- .github/workflows/install-smoke.yml | 1 - VERSIONING.md | 21 +++++------------- docs/branching.md | 22 +++++++++---------- ...olicy-release-no-npm-self-upgrade.test.cjs | 16 -------------- tests/release-coverage-scope.test.cjs | 13 ++--------- 5 files changed, 18 insertions(+), 55 deletions(-) diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml index 279923a8a..0b62bd48c 100644 --- a/.github/workflows/install-smoke.yml +++ b/.github/workflows/install-smoke.yml @@ -30,7 +30,6 @@ on: - 'tests/release-tarball-smoke.install.test.cjs' - '.github/workflows/install-smoke.yml' - '.github/workflows/release.yml' - - '.github/workflows/hotfix.yml' push: branches: - main diff --git a/VERSIONING.md b/VERSIONING.md index cb9de4e96..c568db28a 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -71,35 +71,24 @@ For fixes that need to ship without waiting for the next minor. A hotfix `vX.YY.Z` cumulatively includes everything in `vX.YY.{Z-1}` plus every `fix:`/`chore:` commit landed on `main` since that base. The base tag is the anchor — `git cherry $BASE_TAG main` reveals exactly which commits are still unshipped, and the new `vX.YY.Z` tag becomes the next hotfix's base, so the cycle is self-documenting. -#### Two paths +#### How to dispatch a hotfix -**Path A — `hotfix.yml` (canonical, two-step):** +Hotfixes are dispatched via the **Release workflow (`release.yml`)** with a patch version (X.Y.Z). There is no separate hotfix workflow. -1. Trigger `hotfix.yml` with `action=create`, `version=1.27.1`, `auto_cherry_pick=true` (default). +1. Trigger `release.yml` with `action=create`, `version=1.27.1`, `auto_cherry_pick=true` (default). - Workflow detects `BASE_TAG` = highest `v1.27.*` < `v1.27.1` (so `1.27.1` branches from `v1.27.0`; `1.27.2` would branch from `v1.27.1`). - Branches `hotfix/1.27.1` from `BASE_TAG`. - Auto-cherry-picks every `fix:`/`chore:` commit on `origin/main` not already in the base, oldest-first. Patch-equivalents are skipped via `git cherry`. `feat:`/`refactor:` are **never** auto-included. - On conflict the workflow halts with the offending SHA. Resolve manually on the branch, then re-run finalize with `auto_cherry_pick=false`. - Bumps `package.json` (and `sdk/package.json`), pushes the branch, and lists every included SHA in the run summary. 2. (Optional) push additional manual commits to `hotfix/1.27.1`. -3. Trigger `hotfix.yml` with `action=finalize`. The workflow: +3. Trigger `release.yml` with `action=finalize`. The workflow: - Runs `install-smoke` cross-platform gate. - Runs full test suite + coverage. - - Builds SDK, bundles `sdk-bundle/gsd-sdk.tgz` inside the CC tarball (parity with `release-sdk.yml`). + - Builds SDK, bundles `sdk-bundle/gsd-sdk.tgz` inside the CC tarball. - Tags `v1.27.1`, publishes to `@latest`, re-points `@next → v1.27.1`. - Opens merge-back PR against `main`. -**Path B — `release-sdk.yml` (stopgap, one-shot):** - -Active while the `@opengsd/gsd-sdk` npm token is unavailable; bundles the SDK inside the CC tarball. - -1. Trigger `release-sdk.yml` with `action=hotfix`, `version=1.27.1`, `auto_cherry_pick=true`. - - The `prepare` job creates the branch and cherry-picks (same logic as Path A). - - `install-smoke` runs against the new branch. - - The `release` job tags, publishes to `@latest`, re-points `@next`, opens merge-back PR. - - Idempotent: if `hotfix/1.27.1` already exists (e.g. you ran `hotfix.yml create` first), the prepare job checks it out and re-runs cherry-pick as a no-op. -2. `dry_run=true` exercises the full pipeline without pushing the branch or publishing. - ### Minor Release (Standard Cycle) For accumulated fixes and enhancements. diff --git a/docs/branching.md b/docs/branching.md index 6dd40958a..2f1edd960 100644 --- a/docs/branching.md +++ b/docs/branching.md @@ -74,7 +74,7 @@ These are work branches. Open one, push commits, PR it, merge it, let it auto-de | `perf/` | Performance work, no behavior change | `next` | `perf/3300-skill-index` | | `ci/` | CI/workflow changes only | `next` | `ci/3801-add-node-26-matrix` | | `revert/` | Reverting a previously-merged change | `next` (or `main` if urgent) | `revert/3919-bad-merge` | -| `hotfix/X.Y.Z` | Patch release branch (created by `hotfix.yml`) | `main` | `hotfix/1.27.1` | +| `hotfix/X.Y.Z` | Patch release branch (created by `release.yml` with a patch version X.Y.Z) | `main` | `hotfix/1.27.1` | | `release/X.Y.0` | Minor/major release branch (created by `release.yml`) | `main` | `release/1.28.0` | > **The branch name rule is enforced** by `.github/workflows/branch-naming.yml`. @@ -130,10 +130,10 @@ git pull --ff-only git checkout -b fix/3919-critical-crash # ... commit, push, PR to next, merge. -# 2. Trigger the hotfix workflow from the Actions tab: -# workflow: Hotfix Release +# 2. Trigger the Release workflow (release.yml) from the Actions tab: +# workflow: Release # action: create -# version: 1.27.1 (next patch number) +# version: 1.27.1 (next patch number, X.Y.Z) # auto_cherry_pick: true (default) ``` @@ -148,7 +148,7 @@ The workflow: 6. `finalize` publishes to npm `@latest`, tags `v1.27.1`, opens merge-back PRs to **both** `main` and `next`. -> See `.github/workflows/hotfix.yml` and `VERSIONING.md` for the deep dive. +> See `.github/workflows/release.yml` and `VERSIONING.md` for the deep dive. ### Flow 3 — Minor or major release @@ -212,7 +212,7 @@ resolve anyway. The treadmill is gone. ## Cheat sheet: "where does my PR go?" ``` -Is it a hotfix release branch? → main (cut by hotfix.yml) +Is it a hotfix release branch? → main (cut by release.yml with a patch version X.Y.Z) Is it a stable release branch? → main (cut by release.yml) Is it an RC-blocker fix? → release/X.Y.0 (and also next, or rely on back-merge) Is it everything else? → next @@ -248,10 +248,10 @@ the base branch — no need to recreate the PR. ``` - **Phasing in:** see the migration notes in `docs/adr/230-introduce-next-integration-branch.md`. -- **Updating release.yml / hotfix.yml:** these workflows currently branch - from `main` and cherry-pick from `main`. After phase-2 of the migration - they should branch from `next` (release) and cherry-pick from `next` - (hotfix). The patches are inlined in the ADR. +- **Updating release.yml:** this workflow currently branches from `main` and + cherry-picks from `main`. After phase-2 of the migration it should branch + from `next` (release) and cherry-pick from `next` (hotfix). The patches + are inlined in the ADR. --- @@ -262,7 +262,7 @@ A few exceptions where the rules above bend: - **True production-down emergency.** Push directly to a `fix/critical-*` branch, PR to `main`. The auto-back-merge workflow will replay it onto `next` within minutes. Use sparingly — most "urgent" things are fine to go - through `next` and ship same day via the hotfix workflow. + through `next` and ship same day via the Release workflow (patch version). - **Documentation-only typo on a published page.** If the only change is a doc fix that's visible right now and shouldn't wait for the next release, PR it to `main`. The auto-back-merge will sync `next`. Most doc changes diff --git a/tests/policy-release-no-npm-self-upgrade.test.cjs b/tests/policy-release-no-npm-self-upgrade.test.cjs index 753593cfa..02794b0b8 100644 --- a/tests/policy-release-no-npm-self-upgrade.test.cjs +++ b/tests/policy-release-no-npm-self-upgrade.test.cjs @@ -18,7 +18,6 @@ const NPM_SELF_UPGRADE_RE = /\bnpm\s+(install|i)\s+(-g|--global)\b[^\n]*\bnpm(@| describe('policy: no runtime npm self-upgrade in release lanes (#318)', () => { const releaseFile = path.join(WORKFLOWS_DIR, 'release.yml'); - const hotfixFile = path.join(WORKFLOWS_DIR, 'hotfix.yml'); test('release.yml must not contain a runtime global npm self-upgrade step', () => { const content = fs.readFileSync(releaseFile, 'utf8'); @@ -36,21 +35,6 @@ describe('policy: no runtime npm self-upgrade in release lanes (#318)', () => { ); }); - test('hotfix.yml must not contain a runtime global npm self-upgrade step', () => { - const content = fs.readFileSync(hotfixFile, 'utf8'); - const lines = content.split('\n'); - const violations = lines - .map((line, idx) => ({ line, lineNo: idx + 1 })) - .filter(({ line }) => NPM_SELF_UPGRADE_RE.test(line)); - - assert.strictEqual( - violations.length, - 0, - `hotfix.yml contains ${violations.length} runtime npm self-upgrade line(s) — ` + - `Violations: ${violations.map(({ lineNo, line }) => `line ${lineNo}: ${line.trim()}`).join('; ')}` - ); - }); - test('NPM_SELF_UPGRADE_RE correctly matches the antipattern', () => { // Positive cases — must match assert.ok(NPM_SELF_UPGRADE_RE.test('npm install -g npm@latest'), 'should match npm install -g npm@latest'); diff --git a/tests/release-coverage-scope.test.cjs b/tests/release-coverage-scope.test.cjs index e336627a4..f795a855a 100644 --- a/tests/release-coverage-scope.test.cjs +++ b/tests/release-coverage-scope.test.cjs @@ -1,5 +1,5 @@ // allow-test-rule: source-text-is-the-product -// .github/workflows/{release,hotfix}.yml are the deployed CI contract; asserting +// .github/workflows/release.yml is the deployed CI contract; asserting // the release-gate test command is only expressible against the workflow text. 'use strict'; @@ -10,7 +10,6 @@ const fs = require('node:fs'); const path = require('node:path'); const RELEASE_WORKFLOW = path.join(__dirname, '..', '.github', 'workflows', 'release.yml'); -const HOTFIX_WORKFLOW = path.join(__dirname, '..', '.github', 'workflows', 'hotfix.yml'); describe('release-coverage-scope', () => { test('release.yml uses test:coverage:unit (not full suite) in both rc and finalize gates', () => { @@ -23,13 +22,5 @@ describe('release-coverage-scope', () => { `release.yml has ${unitCount} 'npm run test:coverage:unit' line(s); expected 2`); }); - test('hotfix.yml uses test:coverage:unit (not full suite) in the finalize gate', () => { - const lines = fs.readFileSync(HOTFIX_WORKFLOW, 'utf8').split('\n').map(l => l.trim()); - const bareCount = lines.filter(l => l === 'npm run test:coverage').length; - const unitCount = lines.filter(l => l === 'npm run test:coverage:unit').length; - assert.strictEqual(bareCount, 0, - `hotfix.yml still has ${bareCount} bare 'npm run test:coverage' line(s); expected 0`); - assert.strictEqual(unitCount, 1, - `hotfix.yml has ${unitCount} 'npm run test:coverage:unit' line(s); expected 1`); - }); + });