بنقرة واحدة
gettokens-release-governance
GetTokens macOS 发版流程:版本同步、提交 tag、Release workflow、Sparkle appcast、官方 DMG 下载验收、memory 写回。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
GetTokens macOS 发版流程:版本同步、提交 tag、Release workflow、Sparkle appcast、官方 DMG 下载验收、memory 写回。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
GetTokens 领域工程:账号池、quota、UI 体系、调试与 CLIProxyAPI fork。
GetTokens 流程治理:Wails 开发回路、spaces、文档记忆写回与 AGENTS 同步。
GetTokens Codex 账号列表:Codex Channel Routing、账号请求顺序、两模式路由、路由探测、模型映射、OAuth 透传语义、openai-compatible 映射保存与浏览器预览。
GetTokens CLIProxyAPI 上游版本 intake / reference-port 工作流:看新 tag、规划合并、拆子 space、在 gettokens/sidecar 内重实现、验证、重建 sidecar、dev App 验收与双仓提交。
Use when dxyer iOS verification, smoke testing, or debugging needs real API traffic evidence through Proxyman CLI: clear sessions, discover proxy host, export HAR/proxymansession logs, inspect request headers, verify DXY-TOD-SESSION user switching, or distinguish API data mismatch from UI/function failure.
GetTokens 性能治理:用于排查或预防内存占用、WebView/Wails 卡顿、React 渲染放大、大账号池请求放大、sidecar Go 热路径、日志/SQLite/本地文件膨胀、性能回归、perf budget 或 profiling 验收;当用户说性能、内存、卡顿、占用、峰值、泄漏、慢、回归、治理、budget、pprof、profiling,或改动触及账号池、quota、live sessions、session-management、usage attribution、sidecar management API、Wails bridge、日志热路径时使用。
| name | gettokens-release-governance |
| description | GetTokens macOS 发版流程:版本同步、提交 tag、Release workflow、Sparkle appcast、官方 DMG 下载验收、memory 写回。 |
当用户要求“提交 / 推送 / 发版 / release / 发布 / 走发版流程”,或需要确认某个 GetTokens macOS 版本是否已经可分发时,使用本 skill。它是 GetTokens 专用发布入口,执行时同时遵守 check 的 Ship / Release Follow-through 门禁和 gettokens-ops-governance 的文档、memory、AGENTS 边界。
vX.Y.Z,由 GitHub Actions Release workflow 触发。master,但不得移动、删除或重建已发布 tag。/Applications/GetTokens.app 正式版,不 kill 正式版进程,不替换正式版 sidecar 或配置。先读状态,不要假设干净:
git status --short --branch -uall
git fetch --tags origin
git tag --sort=-version:refname | head -20
git ls-remote --tags origin "refs/tags/vX.Y.Z"
判断并记录:
origin/<branch> 是否同步。frontend/wailsjs 若只有尾随空白漂移,优先运行 ./scripts/normalize-wailsjs.sh 收口,再重新读 diff。只同步 release 版本三件套:
npm --prefix frontend version X.Y.Z --no-git-tag-version --allow-same-version
md5 -q frontend/package.json
必须更新并核对:
frontend/package.jsonfrontend/package-lock.jsonfrontend/package.json.md5frontend/package.json.md5 必须等于 md5 -q frontend/package.json,不要手写猜值。
标准发布前至少运行:
go test ./...
npm --prefix frontend run test:unit
npm --prefix frontend run typecheck
npm --prefix frontend run build
docs-linhay/scripts/check-docs.sh
说明:
只 stage 发版相关文件:
git add frontend/package.json frontend/package-lock.json frontend/package.json.md5
git commit -m "chore: bump version to X.Y.Z"
git push origin <branch>
git tag vX.Y.Z
git push origin vX.Y.Z
推 tag 前再次确认:
git rev-parse HEAD 等于 git rev-parse origin/<branch>。git ls-remote --tags origin "refs/tags/vX.Y.Z" 为空。docs-linhay/references/CLIProxyAPI gitlink 构建 sidecar,先确认该 gitlink commit 已经在 AxApp/CLIProxyAPI#gettokens/sidecar 远端可达;不要只看本地 reference repo。推荐门禁:EXPECTED_CLI_PROXY_COMMIT="$(git ls-tree HEAD docs-linhay/references/CLIProxyAPI | awk '$2 == "commit" {print $3}')"
test -n "$EXPECTED_CLI_PROXY_COMMIT"
git ls-remote https://github.com/AxApp/CLIProxyAPI.git refs/heads/gettokens/sidecar | rg "$EXPECTED_CLI_PROXY_COMMIT"
若远端没有该 commit,先在 docs-linhay/references/CLIProxyAPI 内推送 gettokens/sidecar,再重新执行本地门禁和版本提交;不要推一个必然会在 CI 中 upload-pack: not our ref 的 tag。
tag 推送后监控 Release workflow:
gh run list --workflow Release --limit 5 --json databaseId,headBranch,headSha,status,conclusion,createdAt,url,displayTitle
gh run watch <run-id> --exit-status
成功标准:
Build H5 成功。Build macOS arm64 成功。Build macOS amd64 成功。Publish Sparkle appcast 成功。Publish Release 成功。失败处理:
gh run view <run-id> --log-failed。Release workflow 成功后读取官方 Release:
gh release view vX.Y.Z --json url,assets,publishedAt,tagName
必须存在七个资产:
GetTokens_macOS_AppleSilicon.dmgGetTokens_macOS_AppleSilicon.tar.gzGetTokens_darwin_arm64.tar.gzGetTokens_macOS_Intel.dmgGetTokens_macOS_Intel.tar.gzGetTokens_darwin_amd64.tar.gzchecksums.txt其中 GetTokens_macOS_* 是用户可读资产名,GetTokens_darwin_* 是 go-selfupdate 检测兼容资产名。若某个已安装旧版本因本地策略没有进入 Sparkle 原生 UI,它仍会依赖 go-selfupdate DetectLatest 判断是否有新版;缺少 darwin_<arch> 兼容资产时,旧客户端会把已发布版本误判为“当前已是最新版本”。
注意:Sparkle appcast 默认不是 GitHub Release asset,它发布到 sparkle-appcast 分支。
先从 CI job 确认发布动作:
gh run view <run-id> --json jobs,status,conclusion,url
git ls-remote origin refs/heads/sparkle-appcast
再验证远端 appcast 内容。推荐先 fetch,避免本地 origin/sparkle-appcast 缓存陈旧:
git fetch origin sparkle-appcast
git show origin/sparkle-appcast:appcast-arm64.xml | rg "vX.Y.Z|GetTokens_macOS_AppleSilicon\\.dmg|sparkle:version|shortVersionString"
git show origin/sparkle-appcast:appcast-amd64.xml | rg "vX.Y.Z|GetTokens_macOS_Intel\\.dmg|sparkle:version|shortVersionString"
可用网络直读补充验证:
curl --max-time 15 -fsSL https://raw.githubusercontent.com/AxApp/GetTokens/sparkle-appcast/appcast-arm64.xml
curl --max-time 15 -fsSL https://raw.githubusercontent.com/AxApp/GetTokens/sparkle-appcast/appcast-amd64.xml
判断口径:
Publish Sparkle appcast job 成功,并且 sparkle-appcast 分支两个 XML 顶部包含目标版本和对应 DMG URL,即为 Sparkle 已发布。appcast-*.xml,这是当前流程的正常状态,不代表 Sparkle 没发。raw.githubusercontent.com 超时,改用 git fetch + git show origin/sparkle-appcast:<file> 作为权威确认。如果线上已经出现“Sparkle 在线更新后 app 签名、公证静态校验都通过,但 GUI 启动被 AppleSystemPolicy / provenance runtime policy 拦截”的真实用户故障,必须额外执行一轮 server-side 止血判断:
sparkle-appcast 继续停留在上一安全版本;必要时把新版本视为“仅 GitHub Release 手动安装可用”。结论口径也要区分:
GitHub Release 已发布:官方 DMG 可手动下载安装。Sparkle rollout 已 hold:appcast 故意不暴露该版本,避免旧客户端继续触发已知坏更新链路。如果用户明确要求“检查更新能检测到新版”、或最终交付口径包含“在线更新 / 检查更新可用”,不得静默执行 Sparkle hold。此时必须让远端 appcast 顶部保留目标版本,或在 hold 前先明确说明“检查更新不会看到这个版本”并获得用户确认。
不要因为单次 macOS provenance/runtime policy 故障就在客户端永久禁用 Sparkle。默认策略是:macOS bundle 内 Sparkle 可用时,检查更新走 Sparkle 原生 UI;疑似系统策略拦截按独立故障保留证据、临时 hold appcast 或做端到端升级回归。
必须下载 GitHub Release 上的正式资产,不使用本地 dist/ 替代:
VERIFY_DIR="$(mktemp -d /tmp/gettokens-vX.Y.Z-verify.XXXXXX)"
EXPECTED_CLI_PROXY_COMMIT="$(git ls-tree HEAD docs-linhay/references/CLIProxyAPI | awk '$2 == "commit" {print $3}')"
test -n "$EXPECTED_CLI_PROXY_COMMIT"
gh release download vX.Y.Z --dir "$VERIFY_DIR"
cd "$VERIFY_DIR"
shasum -a 256 -c checksums.txt
DMG 级验收:
spctl -a -t open --context context:primary-signature -v GetTokens_macOS_AppleSilicon.dmg
xcrun stapler validate GetTokens_macOS_AppleSilicon.dmg
spctl -a -t open --context context:primary-signature -v GetTokens_macOS_Intel.dmg
xcrun stapler validate GetTokens_macOS_Intel.dmg
挂载并验包内 app:
mkdir -p /tmp/gettokens-vX.Y.Z-arm64 /tmp/gettokens-vX.Y.Z-amd64
hdiutil attach -nobrowse -readonly -mountpoint /tmp/gettokens-vX.Y.Z-arm64 GetTokens_macOS_AppleSilicon.dmg
hdiutil attach -nobrowse -readonly -mountpoint /tmp/gettokens-vX.Y.Z-amd64 GetTokens_macOS_Intel.dmg
codesign -dv --verbose=4 /tmp/gettokens-vX.Y.Z-arm64/GetTokens.app 2>&1 | rg "Authority=|Timestamp=|Notarization|TeamIdentifier="
codesign -dv --verbose=4 /tmp/gettokens-vX.Y.Z-amd64/GetTokens.app 2>&1 | rg "Authority=|Timestamp=|Notarization|TeamIdentifier="
file /tmp/gettokens-vX.Y.Z-arm64/GetTokens.app/Contents/MacOS/GetTokens
file /tmp/gettokens-vX.Y.Z-amd64/GetTokens.app/Contents/MacOS/GetTokens
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" -c "Print :CFBundleVersion" -c "Print :SUFeedURL" /tmp/gettokens-vX.Y.Z-arm64/GetTokens.app/Contents/Info.plist
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" -c "Print :CFBundleVersion" -c "Print :SUFeedURL" /tmp/gettokens-vX.Y.Z-amd64/GetTokens.app/Contents/Info.plist
cat /tmp/gettokens-vX.Y.Z-arm64/GetTokens.app/Contents/MacOS/cli-proxy-api.meta.json
cat /tmp/gettokens-vX.Y.Z-amd64/GetTokens.app/Contents/MacOS/cli-proxy-api.meta.json
EXPECTED_CLI_PROXY_COMMIT="$EXPECTED_CLI_PROXY_COMMIT" \
python3 - <<'PY'
import json
import os
from pathlib import Path
expected = os.environ["EXPECTED_CLI_PROXY_COMMIT"].strip()
for label, meta_path in {
"arm64": Path("/tmp/gettokens-vX.Y.Z-arm64/GetTokens.app/Contents/MacOS/cli-proxy-api.meta.json"),
"amd64": Path("/tmp/gettokens-vX.Y.Z-amd64/GetTokens.app/Contents/MacOS/cli-proxy-api.meta.json"),
}.items():
meta = json.loads(meta_path.read_text())
actual = str(meta.get("commit", "")).strip()
if actual != expected:
raise SystemExit(f"{label} sidecar metadata mismatch: {actual!r} != {expected!r}")
PY
hdiutil detach /tmp/gettokens-vX.Y.Z-arm64
hdiutil detach /tmp/gettokens-vX.Y.Z-amd64
验收标准:
accepted,来源包含 Notarized Developer ID。stapler validate 成功。.app 签名链为 Developer ID Application: HAN LIN (3L8RM3MDLS),有 timestamp 和 Notarization Ticket=stapled。arm64,Intel 可执行文件为 x86_64。CFBundleShortVersionString 与 CFBundleVersion 等于目标版本。SUFeedURL 分别指向 appcast-arm64.xml 和 appcast-amd64.xml。.app 内置 cli-proxy-api.meta.json 的 commit 必须等于 docs-linhay/references/CLIProxyAPI 当前提交;如果 release 修复依赖某个 sidecar 热路径字段、endpoint 或 JSON 字段,还必须用 strings 或只读 management API 验证该特征确实存在。只验证主程序版本、签名和 DMG 公证不足以声明 sidecar 修复已进入正式包。发布结论必须写入 docs-linhay/memory/YYYY-MM-DD.md,至少包含:
CI 发布完成、可分发 DMG 验收完成 或具体阻塞。写回后运行:
docs-linhay/scripts/check-docs.sh
qmd update && qmd embed
如果只产生 post-release memory/docs 变更,可以在 tag 之后单独提交并推送 master,但不要移动 release tag。
区分三种状态:
已推 tag,CI 进行中:还不能说 Release 已发布。CI 发布完成:workflow 成功,GitHub Release 和七个资产存在。可分发 DMG 验收完成:官方资产下载后,checksum、DMG Gatekeeper/stapler、app 签名/架构/版本/Sparkle 全部通过。最终回复应给出: