with one click
cut-stable
// Promote a rolling -rN candidate to stable vX.Y.Z (no suffix). Bumps AppVersion, creates fresh tag without suffix, full rebuild + Mac/Linux CI, restores Latest, deletes -rN.
// Promote a rolling -rN candidate to stable vX.Y.Z (no suffix). Bumps AppVersion, creates fresh tag without suffix, full rebuild + Mac/Linux CI, restores Latest, deletes -rN.
| name | cut-stable |
| description | Promote a rolling -rN candidate to stable vX.Y.Z (no suffix). Bumps AppVersion, creates fresh tag without suffix, full rebuild + Mac/Linux CI, restores Latest, deletes -rN. |
| when | Latest -rN passed full verification gate (build + tests + Mac/Linux CI green + 12 assets + MCP-verify + **mandatory pre-cut live update gate**) AND user gave explicit "cut" / "ok" / "promote" command (rule 6 в `CLAUDE.md`, lessons v2.31.2 / v2.31.7). Cut НЕ autonomous. |
Promotes vX.Y.Z-rN to stable vX.Y.Z (без суффикса). Per CLAUDE.local.md
"Build / push steps" — обязательно НОВЫЙ тэг без -rN, а не просто переброс
prerelease flag.
Все 6 чек-боксов должны быть зелёные:
dotnet build -c Release → 0 errorsVlessServersResolverTests, ConfigGeneratorEmptyServersGuardTests, FreeConfigAggregatorPreserveTests)successsuccessgh release view vX.Y.Z-rN --json assets --jq '.assets|length' → 12Если все зелёные — ждём explicit user команду "cut" / "ok" / "promote" (rule 6
в CLAUDE.md). Cut НЕ autonomous. User паузит явной командой "hold stable".
Why this exists: green CI + green tests + MCP-verify of the change itself do NOT cover the auto-update path. v2.31.2 cut'нулся на all-green и оказался partial-fix (UI regression поймали только хотфиксом v2.31.3). v2.31.7 cut'нулся на all-green и его helper.cmd parser bug сломал 100% upgrades — поймали через ~7 дней по user-reports. Conclusion: тот же binary который user скачает со stable должен в чистой среде успешно auto-update'нуться к ТЕКУЩЕМУ кандидату ПЕРЕД cut'ом. Иначе stable shipping = выпуск broken update path в production.
When to run: после verification gate (5 первых чекбоксов) PASS, перед тем как просить user'а "cut" / "ok" / "promote". Если этот gate упал — НЕ просим cut, а ship'аем -r(N+1) с фиксом и крутим cycle заново.
Steps (выполнять в том же VM/host где работаем над release; не трогать prod-инсталляцию VPNRouter):
a) Identify previous stable release tag:
gh release list --repo PavelLizunov/VPNRouter --exclude-pre-releases --limit 1
Запомни tag (e.g. v2.31.7). Это baseline для теста.
b) Download install ZIP в чистый temp dir:
rm -rf /c/Temp/stable-test && mkdir -p /c/Temp/stable-test
gh release download <previous-stable-tag> --repo PavelLizunov/VPNRouter \
--pattern 'VPNRouter-*-windows-x64.zip' --dir /c/Temp/stable-test
c) Extract + launch + initial settle:
cd /c/Temp/stable-test
powershell -Command "Expand-Archive -Path 'VPNRouter-*-windows-x64.zip' -DestinationPath ./extracted -Force"
powershell -Command "Start-Process -FilePath './extracted/VPNRouter.App.exe'"
Wait 30s для App init (settings load, update check spin-up).
d) Trigger update к ТЕКУЩЕМУ кандидату -rN. Два пути:
Experimental channel is on, иначе -rN
неvidible) → нажать "Установить" / "Install".e) Wait for update flow to complete. Helper .cmd должен:
tail -f "$LOCALAPPDATA/VPNRouter/Logs/update.log" # или %ProgramData%/VPNRouter/logs/update.log
f) Verify new version installed cleanly:
powershell -Command "(Get-Item /c/Temp/stable-test/extracted/VPNRouter.App.exe).VersionInfo.ProductVersion"
Должна быть <candidate-rN-version> (e.g. 2.31.8-r10). Также:
powershell -Command "(Get-Item /c/Temp/stable-test/extracted/VPNRouter.Core.dll).VersionInfo.FileVersion"
AppVersion должна совпадать с candidate (правило #5 — string Version ВКЛЮЧАЯ -rN суффикс).
g) 30-second smoke: убедись App работает после update.
h) Cleanup:
powershell -Command "Get-Process VPNRouter.App,VPNRouter.Service,sing-box -ErrorAction SilentlyContinue | Stop-Process -Force"
rm -rf /c/Temp/stable-test
i) IF ANY STEP FAILS (download error, install hang, helper crash, version mismatch, App не запускается после update, smoke fails):
-r(N+1) через ship-rolling-candidate skill.Detailed report для user'а (часть "request cut" сообщения):
vX.Y.(Z-1) (or same Z, prior -rN cut)Только после PASS — просим user'а "cut" / "ok" / "promote".
VPNRouter.Core/AppVersion.cs:
public const string Version = "X.Y.Z"; // no suffix
git add VPNRouter.Core/AppVersion.cs
git commit -m "release: cut vX.Y.Z stable (drop -rN suffix)
User confirmed <r-version> fixes work. Promoting per CLAUDE.local.md
§Release Process step 6.
No code changes since <last-rN-commit-hash>.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
git push github HEAD:main
git push origin HEAD:main # retry если VPN down
powershell -ExecutionPolicy Bypass -File build.ps1 -Version "X.Y.Z" -Upload
build.ps1 сделает:
gh release create vX.Y.Z --latest (без --prerelease)Tag vX.Y.Z создаётся build.ps1 на текущем commit. Stable tag — finalный, force-update НЕЛЬЗЯ.
git fetch github --tags # fetches vX.Y.Z
git push origin vX.Y.Z # mirror в Forgejo
Wait for both runs. Verify 12 assets:
gh release view vX.Y.Z --repo PavelLizunov/VPNRouter --json assets --jq '.assets | length'
Должно быть 12.
plans/release-notes-vX.Y.Z.md — собирает фиксы со всех -r1..-rN:
gh release edit vX.Y.Z --repo PavelLizunov/VPNRouter \
--title "VPNRouter vX.Y.Z — <one-line headline>" \
--notes-file "plans/release-notes-vX.Y.Z.md"
gh release delete vX.Y.Z-r1 --yes --repo PavelLizunov/VPNRouter
gh release delete vX.Y.Z-r2 --yes --repo PavelLizunov/VPNRouter
# ... etc для всех -rN
Тэги НЕ удаляем — vX.Y.Z-r1, vX.Y.Z-r2 остаются в git history.
После tag push на stable, build-mac.yml Trigger Homebrew Cask step должен
дисптачить repository_dispatch к PavelLizunov/homebrew-vpnrouter. Tap'овский
update-cask.yml должен обновить Casks/vpnrouter.rb к новой версии:
gh api "repos/PavelLizunov/homebrew-vpnrouter/contents/Casks/vpnrouter.rb" \
--jq '.content' | base64 -d | head -5
Должна быть version "X.Y.Z" + новый sha256.
Если не обновился — проверить gh run list --repo PavelLizunov/homebrew-vpnrouter
для последнего dispatch'а.
curl -sI "https://vpn.ninitux.com/apt/dists/stable/main/binary-amd64/Packages"
HTTP/1.1 200 OK ожидается. publish-apt.yml workflow должен был добавить новую
.deb в reprepro index.
В ~/.claude/projects/.../memory/MEMORY.md:
gh release create --latest, GitHub сам забирает --latest у предыдущего release. Не нужно руками снимать.v2.28.3 + AppVersion 2.28.3-r6 → SemVer считает stable новее prerelease same-core, но на коде r6 это враньё. Всегда совпадать.git tag -f vX.Y.Z) после публикации release.vX.Y.Z-rN из git — мы только release удаляем.[HINT] Download the complete skill directory including SKILL.md and all related files