Переглянути джерело

ci(release): 增加发布门禁与版本发布 Skill

正式发布构建等待同 SHA 的 main CI 成功,避免缓存预热与 tag 构建并发重复编译。
darknessomi 1 місяць тому
батько
коміт
35afbfb978
2 змінених файлів з 123 додано та 0 видалено
  1. 63 0
      .agents/skills/qmai-release/SKILL.md
  2. 60 0
      .github/workflows/build.yml

+ 63 - 0
.agents/skills/qmai-release/SKILL.md

@@ -0,0 +1,63 @@
+---
+name: qmai-release
+description: Release a stable QMAI version by updating version metadata and changelog, pushing main, waiting for the same-commit main CI release-cache warmup, then creating and pushing an annotated version tag. Use when asked to bump, publish, or tag a QMAI stable release; do not use for prerelease builds or ordinary PR delivery.
+---
+
+# QMAI Stable Release
+
+Use this workflow only in the `Mochocyang/QMAI` repository. Preserve the user's authorization boundary: preparing a release does not authorize pushing, tagging, or publishing unless the user requested those external mutations.
+
+## Release invariants
+
+- Push the release commit to `main` by itself. Never push `main` and the version tag in one command or atomic push.
+- Wait for the `CI` workflow on the exact release commit to finish successfully before creating the tag. This allows the Linux, Windows, and macOS release-cache warmup to complete.
+- Never create the tag while required validation or the matching main CI run is failing, cancelled, missing, or still running.
+- Never overwrite, move, or force-push `main` or a release tag.
+- Stop if `origin/main` moves away from the release commit before tagging. Reconcile the new remote state instead of tagging an older commit.
+- Use an annotated tag named `v<package-version>` and verify that the remote tag resolves to the release commit.
+
+## Workflow
+
+1. Confirm the repository and release target.
+   - Verify the remote is `Mochocyang/QMAI`.
+   - Require a clean worktree before switching or pulling.
+   - Fetch `origin/main` and tags, switch to `main`, and update with `--ff-only`.
+   - Confirm the requested version is a stable semantic version and the tag does not already exist locally or remotely.
+
+2. Update the complete QMAI release surface.
+   - `package.json`
+   - root package version and root package entry in `package-lock.json`
+   - `src-tauri/Cargo.toml`
+   - the `qmai` package entry in `src-tauri/Cargo.lock`
+   - `src-tauri/tauri.conf.json`
+   - `src/lib/changelog.ts`
+   - `src/lib/changelog.spec.ts`
+   - Derive release notes from commits since the previous version tag. Do not invent features or copy stale notes.
+
+3. Validate before committing.
+   - Confirm every version source has the exact requested version.
+   - Run `node scripts/release-notes.mjs <version>` and inspect that it returns the intended Chinese notes rather than the generic fallback.
+   - Run `npx vitest run src/lib/changelog.spec.ts scripts/release-notes.spec.mjs`.
+   - Run `npm run test:mocks`, `npm run build`, and `git diff --check` unless the user explicitly narrows validation.
+   - If a required check fails, do not tag. Separate a verified pre-existing baseline failure from a release regression and obtain explicit authorization before proceeding despite it.
+
+4. Commit and push only `main`.
+   - Use a Chinese Conventional Commit such as `chore(release): 升级版本至 <version>`.
+   - Re-fetch `origin/main` immediately before pushing and confirm it is the release commit's parent.
+   - Push only `main`. Record the exact release commit SHA.
+
+5. Wait for cache warmup on the exact release SHA.
+   - Find the `CI` workflow run whose event is `push`, branch is `main`, and head SHA equals the release SHA.
+   - Wait with `gh run watch <run-id> --repo Mochocyang/QMAI --exit-status`.
+   - Stop if no matching run appears within 5 minutes, the run does not complete within 90 minutes, or its conclusion is not `success`.
+
+6. Tag only after CI succeeds.
+   - Fetch `origin/main` and tags again.
+   - Require local `HEAD`, `origin/main`, and the recorded release SHA to match.
+   - Reconfirm that `v<version>` does not exist.
+   - Create an annotated tag on the recorded SHA, then push only that tag in a separate command.
+
+7. Verify delivery.
+   - Verify remote `main` and the dereferenced remote tag both resolve to the recorded release SHA.
+   - Confirm `QMAI Multi-Platform Release` started for that tag and SHA.
+   - Report the release workflow URL and current status. Do not claim release completion while jobs or required assets are pending.

+ 60 - 0
.github/workflows/build.yml

@@ -6,11 +6,71 @@ on:
       - "v*"
 
 permissions:
+  actions: read
   contents: write
 
 jobs:
+  wait-for-main-ci:
+    name: Wait for main release cache
+    runs-on: ubuntu-22.04
+    timeout-minutes: 90
+    steps:
+      - name: Require successful CI for this commit
+        env:
+          GH_TOKEN: ${{ github.token }}
+          RELEASE_SHA: ${{ github.sha }}
+          REPOSITORY: ${{ github.repository }}
+        shell: bash
+        run: |
+          set -euo pipefail
+
+          missing_deadline=$((SECONDS + 300))
+          completion_deadline=$((SECONDS + 5400))
+
+          while (( SECONDS < completion_deadline )); do
+            run_row="$(
+              gh api -X GET \
+                -H "Accept: application/vnd.github+json" \
+                -H "X-GitHub-Api-Version: 2026-03-10" \
+                "repos/${REPOSITORY}/actions/workflows/ci.yml/runs" \
+                -f event=push \
+                -f branch=main \
+                -f head_sha="${RELEASE_SHA}" \
+                -f per_page=20 \
+                --jq '((.workflow_runs | sort_by(.created_at) | last) // empty) | [.id, .status, (.conclusion // ""), .html_url] | @tsv'
+            )"
+
+            if [[ -z "${run_row}" ]]; then
+              if (( SECONDS >= missing_deadline )); then
+                echo "::error::No main CI push run found for ${RELEASE_SHA}; push main and wait for CI before tagging"
+                exit 1
+              fi
+              echo "Waiting for the main CI run for ${RELEASE_SHA} to appear..."
+              sleep 15
+              continue
+            fi
+
+            IFS=$'\t' read -r run_id run_status run_conclusion run_url <<< "${run_row}"
+            echo "Main CI ${run_id}: ${run_status}${run_conclusion:+/${run_conclusion}} (${run_url})"
+
+            if [[ "${run_status}" == "completed" ]]; then
+              if [[ "${run_conclusion}" == "success" ]]; then
+                echo "Main CI completed successfully; release cache is ready"
+                exit 0
+              fi
+              echo "::error::Main CI ${run_id} concluded ${run_conclusion}; refusing to build the release"
+              exit 1
+            fi
+
+            sleep 15
+          done
+
+          echo "::error::Timed out waiting for main CI for ${RELEASE_SHA}"
+          exit 1
+
   build-release:
     name: Build ${{ matrix.display_label }}
+    needs: wait-for-main-ci
     if: github.event_name == 'push'
     runs-on: ${{ matrix.runner }}
     env: