mcpField
G
gstack-upgrade

gstack-upgrade

github.com/garrytanAgent 工作流138.1k351 次安装今天更新
代码仓库

关于 gstack-upgrade

gstack-upgrade 是用于更新 gstack 并说明变更内容的技能。它处理 Git 和 vendored 安装,会检查本地更改,并为更新失败提供恢复指引。

主要功能

  1. 01检查并应用 gstack 更新
  2. 02支持 Git 和 vendored 安装
  3. 03保留或报告冲突的本地更改
  4. 04刷新已注册的安装

适用场景

  1. 01有新版本时更新 gstack
  2. 02更新中止或失败时查看恢复步骤

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/garrytan/gstack --skill gstack-upgrade

安装后在 Claude Code 中输入/gstack-upgrade确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

SKILL.md.tmpl
migrations/
v0.15.2.0.sh
v0.16.2.0.sh
v1.0.0.0.sh
v1.1.3.0.sh
v1.17.0.0.sh
v1.27.0.0.sh
v1.37.0.0.sh
v1.38.1.0.sh
v1.40.0.0.sh
v1.58.0.0.sh
v1.65.0.0.sh
v1.67.0.0.sh
v1.78.0.0.sh
v1.86.0.0.sh
v1.91.19.0.sh
v1.91.65.0.sh

详细介绍

/gstack-upgrade

将 gstack 升级到最新版本,并查看新增内容。

内联升级流程

所有技能前置内容在检测到 UPGRADE_AVAILABLE 时都会引用本节。

步骤 1:询问用户(或自动升级)

首先,检查是否启用了自动升级:

_AUTO=""
[ "${GSTACK_AUTO_UPGRADE:-}" = "1" ] && _AUTO="true"
[ -z "$_AUTO" ] && _AUTO=$(~/.claude/skills/gstack/bin/gstack-config get auto_upgrade 2>/dev/null || true)
echo "AUTO_UPGRADE=$_AUTO"

如果 AUTO_UPGRADE=true 或 AUTO_UPGRADE=1: 跳过 AskUserQuestion。记录“正在将 gstack 从 v{old} 升级到 v{new}……”,然后直接继续步骤 2。如果设置失败,请按照步骤 4 中针对安装类型的恢复流程操作:vendored 安装恢复其备份;git 安装停止,并记录升级前的提交,不执行破坏性重置。除非确实恢复成功,否则绝不要声称已恢复。

否则,使用 AskUserQuestion:

  • 问题:“gstack v{new} 已推出(你正在使用 v{old})。现在升级吗?” 四字段形式(UPGRADE_AVAILABLE <old> <old>+<n> <sha>,main 有更新):询问“gstack main 比 v{old} 领先 {n} 个提交(当前为 {sha})。现在升级吗?”
  • 选项:["是,现在升级", "始终让我保持最新", "暂不", "不再询问"]

如果选择“是,现在升级”: 继续步骤 2。

如果选择“始终让我保持最新”:

~/.claude/skills/gstack/bin/gstack-config set auto_upgrade true

告知用户:“已启用自动升级。今后将自动安装更新。”然后继续步骤 2。

如果选择“暂不”: 写入延后状态,并采用逐步延长的间隔(第一次延后 = 24 小时,第二次 = 48 小时,第三次及之后 = 1 周),然后继续使用当前技能。不要再提及此次升级。

GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
_SNOOZE_FILE="$GSTACK_STATE_ROOT/update-snoozed"
_REMOTE_VER="{new}"
_CUR_LEVEL=0
if [ -f "$_SNOOZE_FILE" ]; then
  _SNOOZED_VER=$(awk '{print $(1)}' "$_SNOOZE_FILE")
  if [ "$_SNOOZED_VER" = "$_REMOTE_VER" ]; then
    _CUR_LEVEL=$(awk '{print $(2)}' "$_SNOOZE_FILE")
    case "$_CUR_LEVEL" in *[!0-9]*) _CUR_LEVEL=0 ;; esac
  fi
fi
_NEW_LEVEL=$((_CUR_LEVEL + 1))
[ "$_NEW_LEVEL" -gt 3 ] && _NEW_LEVEL=3
echo "$_REMOTE_VER $_NEW_LEVEL $(date +%s)" > "$_SNOOZE_FILE"

注意:{new} 是 UPGRADE_AVAILABLE 输出中的远程版本——请从更新检查结果中替换该值。对于四字段形式的行,使用 SHA(第四个字段)作为 {new},这样 main 更新后延后状态就会结束。

告知用户延后时长:“下次提醒时间:24 小时后”(或根据级别显示 48 小时后或 1 周后)。提示:“在 ~/.gstack/config.yaml 中设置 auto_upgrade: true 可启用自动升级。”

如果选择“不再询问”:

~/.claude/skills/gstack/bin/gstack-config set update_check false

告知用户:“已禁用更新检查。运行 ~/.claude/skills/gstack/bin/gstack-config set update_check true 可重新启用。” 继续使用当前技能。

步骤 2:检测安装类型
if [ -d "$HOME/.claude/skills/gstack/.git" ]; then
  INSTALL_TYPE="global-git"
  INSTALL_DIR="$HOME/.claude/skills/gstack"
elif [ -d "$HOME/.gstack/repos/gstack/.git" ]; then
  INSTALL_TYPE="global-git"
  INSTALL_DIR="$HOME/.gstack/repos/gstack"
elif [ -d ".claude/skills/gstack/.git" ]; then
  INSTALL_TYPE="local-git"
  INSTALL_DIR=".claude/skills/gstack"
elif [ -d ".agents/skills/gstack/.git" ]; then
  INSTALL_TYPE="local-git"
  INSTALL_DIR=".agents/skills/gstack"
elif [ -d ".claude/skills/gstack" ]; then
  INSTALL_TYPE="vendored"
  INSTALL_DIR=".claude/skills/gstack"
elif [ -d "$HOME/.claude/skills/gstack" ]; then
  INSTALL_TYPE="vendored-global"
  INSTALL_DIR="$HOME/.claude/skills/gstack"
elif _SRC=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT 2>/dev/null) && _SRC=$(awk -F '\t' '$(1) == "claude" && $(6) != "-" { print $(6); exit }' "$_SRC/installs.tsv" 2>/dev/null) && [ -d "$_SRC/.git" ]; then
  # The install registry names the checkout setup activated for this host (e.g. a ~/gstack clone).
  INSTALL_TYPE="global-git"
  INSTALL_DIR="$_SRC"
else
  echo "ERROR: gstack not found"
  exit 1
fi
INSTALL_DIR=$(cd -- "$INSTALL_DIR" && pwd -P) || { echo "ERROR: cannot enter the gstack install directory" >&2; exit 1; }
echo "Install type: $INSTALL_TYPE at $INSTALL_DIR"

上方输出的安装类型和绝对目录将在后续所有步骤中使用。 明确传递 INSTALL_TYPE、INSTALL_DIR、OLD_VERSION,以及之后的 UPSTREAM_REF 和 NEW_VERSION:工具调用可能会在新的 shell 中运行,因此后续每个代码块开始时都要从已捕获的输出中为这些变量赋值(例如 INSTALL_DIR=/abs/path/printed/above)。不要依赖先前调用的工作目录或 shell 变量。每个更改文件的代码块都必须先检查 INSTALL_DIR;如果其未设置、为空、目录不存在、不可读,或不是 gstack 自身的检出目录,则停止且不要执行任何 git 命令。

步骤 3:保存旧版本

使用步骤 2 输出中的安装目录:

OLD_VERSION=$(cat "${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}/VERSION" 2>/dev/null || echo "unknown")
echo "OLD_VERSION=$OLD_VERSION"
步骤 4:升级

使用步骤 2 中检测到的安装类型和目录:

对于 git 安装(global-git、local-git):

先通过 bin/gstack-ff-update 快进更新;它与 session-update 自动升级使用相同的代码:可以干净合并的本地编辑会随更新保留,而发生冲突的编辑(或暂存的更改、未合并的文件)会保存到救援引用中,而不是留下冲突标记:

cd -- "${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}" || exit 1
{ [ -f VERSION ] && [ -f setup ] && [ -f bin/gstack-config ] && _p=$(git rev-parse --show-prefix 2>/dev/null) && [ -z "$_p" ] && [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ]; } || { echo "ERROR: $INSTALL_DIR is not a gstack checkout; nothing was changed. Re-run Step 2." >&2; exit 1; }
git fetch origin
# Channel ref (docs/releases.md): origin/stable for a team install, else origin/main
_CH=$(bash bin/gstack-update-channel.sh --fetch-ref 2>&1) || _CH=origin/main
echo "$_CH"; UPSTREAM_REF=$(printf '%s\n' "$_CH" | tail -1)
echo "UPSTREAM_REF=${UPSTREAM_REF:=origin/main}"
# Same pre-advance check as the auto-updater: the incoming release's Bun floor,
# judged by bash whatever this tool shell is (zsh does not word-split).
HOLD=$(UPSTREAM_REF="$UPSTREAM_REF" bash -c '. bin/gstack-bun-version.sh && gstack_bun_incoming_hold . "$UPSTREAM_REF"' 2>/dev/null) && { case "$HOLD" in bun-version-unreadable*) echo "BUN_VERSION_UNREADABLE: $HOLD; nothing was changed" >&2 ;; *) echo "BUN_TOO_OLD: $HOLD; nothing was changed" >&2 ;; esac; exit 1; }
PRE_UPGRADE_COMMIT=$(git rev-parse HEAD)
echo "PRE_UPGRADE_COMMIT=$PRE_UPGRADE_COMMIT"
# Past the release (by ancestry, or by VERSION in a depth-1 clone): wait for stable; nothing moves back.
if [ "$UPSTREAM_REF" = origin/stable ] && [ "$(git rev-parse origin/stable)" != "$PRE_UPGRADE_COMMIT" ] && ! git merge-base --is-ancestor HEAD origin/stable 2>/dev/null \
  && { git merge-base --is-ancestor origin/stable HEAD 2>/dev/null || [ "$(printf '%s\n%s\n' "$(git show origin/stable:VERSION)" "$(cat VERSION)" | sort -V | tail -1)" = "$(cat VERSION)" ]; }; then echo "AHEAD_OF_STABLE"; exit 0; fi
FF=$(bash bin/gstack-ff-update . "$UPSTREAM_REF"); FF_RC=$?
echo "$FF"
case "$FF_RC" in
  0) if ./setup --refresh-registered; then echo "FF_OK"; else echo "SETUP_FAILED: git update succeeded; stop and inspect setup output (previous commit: $PRE_UPGRADE_COMMIT)" >&2; exit 1; fi ;;
  4) echo "FF_REFUSED" ;;
  *) echo "FF_STOPPED: $FF" >&2; exit 1 ;;
esac

Setup 的 --refresh-registered 会刷新此检出注册的每个安装(每个主机、全局安装和此项目的安装),先打印来源,最后输出每个安装各占一行的 Upgrade summary。按原样转达这些行。failed 行表示该主机保留了之前的安装;请提供其重试命令,切勿声称所有主机都已刷新。skipped 行属于其他检出或项目;转达其命令,但不要运行。

如果输出以 FF_OK 结尾,则升级已完成——完全跳过下面的回退流程。如果该行之前显示 rescued <old> <new> refs/gstack/rescue/<oid> unmerged=<0|1>,请告知用户,其有冲突的本地编辑已保存到该位置,并转达脚本打印的 Inspect、Export 和 Delete 命令(导出到临时检出,并使用 git apply -3;切勿在安装目录中使用 stash apply;unmerged=1 表示救援内容需要手动解决)。 遇到 FF_STOPPED 时,停止并转达原因:没有任何更改。 遇到 AHEAD_OF_STABLE 时,停止:此检出已领先于每周发布版本,因此在下次发布之前不会有任何更改(每次合并后都会执行 gstack-config set update_channel main)。如果上方出现 fetching origin/stable failed 行,停止并转达该行;没有任何更改。 ff-busy 表示另一个 gstack 更新正在运行(请在一分钟后重试);autostash-unrecoverable 会指出阻碍更新的内容(发布版本新增的未跟踪文件,或另一个 git 命令);index-unreadable 表示检出的索引已损坏。 遇到 BUN_TOO_OLD 时,停止:告知用户运行 bun upgrade,然后再次运行 /gstack-upgrade。 遇到 BUN_VERSION_UNREADABLE 时,停止:bun --version 未输出 gstack 能识别的版本,因此 bun upgrade 也可能无法运行;告知用户从 https://bun.sh 重新安装 Bun(在 Git Bash 或 macOS/Linux 终端中运行 curl -fsSL https://bun.sh/install | bash),确认 bun --version 输出类似 1.4.2 的版本,然后再次运行 /gstack-upgrade(docs/troubleshooting.md#bun-version-unreadable)。 遇到 SETUP_FAILED 时,停止;保留用户更改并报告恢复提交。git 路径上没有 .bak。切勿仅因 setup 失败就进入分歧回退流程。只有遇到 FF_REFUSED(本地提交或历史分歧)时才进入该流程;网络/身份验证失败应停止并修复,不要重置。

回退(仅快进被拒绝——本地提交或分歧)。 git reset --hard 会销毁内容:即使工作区干净,未推送的本地提交仍会丢失。请先确认:

  1. 在 $INSTALL_DIR 中运行 git status --porcelain 和 git rev-list "${UPSTREAM_REF:?}"..HEAD --oneline。
  2. 如果两者都为空,则可以确定重置是安全的——无需询问,直接运行下面的回退代码块。
  3. 否则,通过 AskUserQuestion 询问(不可逆操作——有破坏性),准确列出将被丢弃的内容:每个有更改的文件,以及每个未推送提交的哈希值和主题。选项:A) 丢弃这些内容并升级(重置)——必须明确回复字母;B) 取消升级,让用户先自行挽救工作(存在本地提交时推荐)。回复含糊时绝不可继续。
cd -- "${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}" || exit 1
{ [ -f VERSION ] && [ -f setup ] && [ -f bin/gstack-config ] && _p=$(git rev-parse --show-prefix 2>/dev/null) && [ -z "$_p" ] && [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ]; } || { echo "ERROR: $INSTALL_DIR is not a gstack checkout; nothing was changed. Re-run Step 2." >&2; exit 1; }
# Regenerable render-footprint dirt (setup re-renders it) never goes into the stash.
git checkout -- 'SKILL.md' '*/SKILL.md' '*/sections/*.md' 2>/dev/null || true
STASH_OUTPUT=$(git stash 2>&1)
git reset --hard "${UPSTREAM_REF:?UPSTREAM_REF is not set: substitute the value Step 4 printed}"
./setup --refresh-registered

如果 $STASH_OUTPUT 包含 "Saved working directory",请提醒用户:"注意:本地更改已暂存(任何已修改的生成文件 SKILL.md/sections 文件都已先行丢弃——它们会在 setup 时重新生成)。运行 git stash pop,在技能目录中恢复你自己的更改。"

对于 vendored 安装(vendored、vendored-global):

(cd -- "${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}" && [ -f VERSION ] && [ -f setup ] && [ -f bin/gstack-config ]) || { echo "ERROR: INSTALL_DIR=${INSTALL_DIR:-} is not a gstack install; nothing was changed. Re-run Step 2." >&2; exit 1; }
# A stale .bak from a previously crashed upgrade would make the mv below NEST
# the live install inside it and the failure-restore arm would "restore" the
# stale backup. It may also be the only good copy from that crashed run —
# abort and let the human inspect, never delete it silently.
[ -e "$INSTALL_DIR.bak" ] && { echo "ERROR: stale backup exists at $INSTALL_DIR.bak (from a previous failed upgrade?) — inspect it, salvage/remove it, then re-run." >&2; exit 1; }
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/gstack-upgrade.XXXXXX") || { echo "ERROR: mktemp failed — aborting upgrade (install untouched)." >&2; exit 1; }
git clone --depth 1 https://github.com/garrytan/gstack.git "$TMP_DIR/gstack" || { echo "ERROR: clone failed — aborting upgrade (install untouched)." >&2; rm -rf "$TMP_DIR"; exit 1; }
mv "$INSTALL_DIR" "$INSTALL_DIR.bak" || { rm -rf "$TMP_DIR"; exit 1; }
if mv "$TMP_DIR/gstack" "$INSTALL_DIR"; then
  if (cd "$INSTALL_DIR" && ./setup --refresh-registered); then
    rm -rf "$INSTALL_DIR.bak" "$TMP_DIR"
  else
    rm -rf "$INSTALL_DIR"
    mv "$INSTALL_DIR.bak" "$INSTALL_DIR" || { echo "ERROR: restore failed; backup retained." >&2; exit 1; }
    rm -rf "$TMP_DIR"
    echo "ERROR: setup failed; previous install restored." >&2
    exit 1
  fi
else
  mv "$INSTALL_DIR.bak" "$INSTALL_DIR"
  echo "ERROR: swap failed — previous install restored; upgrade aborted." >&2
  rm -rf "$TMP_DIR"
  exit 1
fi
第 4.5 步:处理本地 vendored 副本

使用第 2 步中的安装目录。检查是否还有本地 vendored 副本,以及团队模式是否已启用:

_RESOLVED_PRIMARY=$(cd -- "${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}" && pwd -P) || exit 1
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
LOCAL_GSTACK=""
if [ -n "$_ROOT" ] && [ -d "$_ROOT/.claude/skills/gstack" ]; then
  _RESOLVED_LOCAL=$(cd "$_ROOT/.claude/skills/gstack" && pwd -P)
  if [ "$_RESOLVED_LOCAL" != "$_RESOLVED_PRIMARY" ]; then
    LOCAL_GSTACK="$_ROOT/.claude/skills/gstack"
  fi
fi
_TEAM_MODE=$(~/.claude/skills/gstack/bin/gstack-config get team_mode 2>/dev/null || echo "false")
echo "LOCAL_GSTACK=$LOCAL_GSTACK"
echo "TEAM_MODE=$_TEAM_MODE"

如果 LOCAL_GSTACK 非空且 TEAM_MODE 为 true: 删除 vendored 副本。团队模式使用全局安装作为唯一事实来源。

(cd -- "${LOCAL_GSTACK:?LOCAL_GSTACK is not set: re-run the detection block above and substitute the printed path}" && [ -f VERSION ] && [ -f setup ] && [ -f bin/gstack-config ]) || { echo "ERROR: LOCAL_GSTACK=${LOCAL_GSTACK:-} is not a gstack copy; nothing was changed." >&2; exit 1; }
_ROOT=${LOCAL_GSTACK%/.claude/skills/gstack}
{ [ "$_ROOT" != "$LOCAL_GSTACK" ] && cd -- "${_ROOT:?}" && _p=$(git rev-parse --show-prefix 2>/dev/null) && [ -z "$_p" ] && [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ]; } || { echo "ERROR: $LOCAL_GSTACK is not the vendored copy at the root of a git repository; nothing was changed." >&2; exit 1; }
git rm -r --cached .claude/skills/gstack/ 2>/dev/null || true
if ! grep -qF '.claude/skills/gstack/' .gitignore 2>/dev/null; then
  echo '.claude/skills/gstack/' >> .gitignore
fi
rm -rf "$LOCAL_GSTACK"

告知用户:“已删除 $LOCAL_GSTACK 处的 vendored 副本(团队模式已启用——全局安装是唯一事实来源)。准备好后,请提交 .gitignore 更改。”

如果 LOCAL_GSTACK 非空且 TEAM_MODE 不为 true: 从刚升级的主安装复制内容来更新它(采用与 README vendored 安装相同的方法):

(cd -- "${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}" && [ -f VERSION ] && [ -f setup ] && [ -f bin/gstack-config ]) || { echo "ERROR: INSTALL_DIR=${INSTALL_DIR:-} is not a gstack install; nothing was changed." >&2; exit 1; }
(cd -- "${LOCAL_GSTACK:?LOCAL_GSTACK is not set: re-run the detection block above and substitute the printed path}" && [ -f VERSION ] && [ -f setup ] && [ -f bin/gstack-config ]) || { echo "ERROR: LOCAL_GSTACK=${LOCAL_GSTACK:-} is not a gstack copy; nothing was changed." >&2; exit 1; }
[ -e "$LOCAL_GSTACK.bak" ] && { echo "ERROR: stale vendored backup; inspect it before retrying." >&2; exit 1; }
mv "$LOCAL_GSTACK" "$LOCAL_GSTACK.bak" || exit 1
if cp -Rf "$INSTALL_DIR" "$LOCAL_GSTACK" && rm -rf "$LOCAL_GSTACK/.git" && (cd "$LOCAL_GSTACK" && ./setup --refresh-registered); then
  rm -rf "$LOCAL_GSTACK.bak"
  echo "LOCAL_SYNC_OK"
else
  rm -rf "$LOCAL_GSTACK"
  mv "$LOCAL_GSTACK.bak" "$LOCAL_GSTACK" || { echo "ERROR: restore failed; backup retained." >&2; exit 1; }
  echo "ERROR: sync failed; previous vendored copy restored." >&2
  exit 1
fi

仅当 LOCAL_SYNC_OK 时,告知用户:“也已更新 $LOCAL_GSTACK 处的 vendored 副本——准备好后请提交 .claude/skills/gstack/。”否则停止并报告恢复结果;不要继续迁移或宣布成功。

第 4.75 步:运行版本迁移

运行 ./setup 后,执行 gstack-upgrade/migrations/ 中比旧版本更新的迁移(幂等的 v{VERSION}.sh 脚本;CONTRIBUTING.md 说明了如何添加迁移脚本)。这些迁移会修复仅靠 ./setup 无法处理的状态问题(过时的配置、孤立文件)。

MIGRATIONS_DIR="${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}/gstack-upgrade/migrations"
: "${OLD_VERSION:?OLD_VERSION is not set: substitute the value Step 3 printed}"
if [ -d "$MIGRATIONS_DIR" ]; then
  for migration in $(find "$MIGRATIONS_DIR" -maxdepth 1 -name 'v*.sh' -type f 2>/dev/null | sort -V); do
    m_ver="$(basename "$migration" .sh | sed 's/^v//')"
    if [ "$OLD_VERSION" != "unknown" ] && [ "$(printf '%s\n%s' "$OLD_VERSION" "$m_ver" | sort -V | head -1)" = "$OLD_VERSION" ] && [ "$OLD_VERSION" != "$m_ver" ]; then
      echo "Running migration $m_ver..."
      # Without GSTACK_INSTALL_DIR a migration that cleans the install assumes ~/.claude/skills/gstack.
      GSTACK_INSTALL_DIR="$INSTALL_DIR" bash "$migration" || echo "  Warning: migration $m_ver had errors (non-fatal)"
    fi
  done
fi
第 4.8 步:停止任何过时的守护进程(无条件)

升级前启动的浏览守护进程仍在运行旧二进制文件(重置和 ./setup 都不会停止它),因此始终使用第 2 步中的安装目录执行此步骤。

INSTALL_DIR_PLACEHOLDER="<install dir from Step 2>"
NEW_HASH=$(cat "$INSTALL_DIR_PLACEHOLDER/browse/dist/.version" 2>/dev/null || echo "")
_STATE_FILE="${BROWSE_STATE_FILE:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/browse.json}"
if [ -z "$NEW_HASH" ] || [ ! -f "$_STATE_FILE" ]; then
  echo "DAEMON_CHECK=none (no state file or no fresh build hash)"
else
  DAEMON_PID=$(sed -n 's/.*"pid"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$_STATE_FILE" | head -1)
  DAEMON_PORT=$(sed -n 's/.*"port"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$_STATE_FILE" | head -1)
  OLD_HASH=$(sed -n 's/.*"binaryVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$_STATE_FILE" | head -1)
  if [ -z "$DAEMON_PID" ] || ! kill -0 "$DAEMON_PID" 2>/dev/null; then
    echo "DAEMON_CHECK=dead (no live daemon to stop)"
  elif [ "$OLD_HASH" = "$NEW_HASH" ]; then
    echo "DAEMON_CHECK=current (daemon already runs the new binary)"
  elif curl -fsS --max-time 2 "http://127.0.0.1:$DAEMON_PORT/health" 2>/dev/null | grep -q '"status":"healthy"'; then
    echo "DAEMON_CHECK=stale-responsive pid=$DAEMON_PID hash=${OLD_HASH:-unknown} -> $NEW_HASH"
    "$INSTALL_DIR_PLACEHOLDER/browse/dist/browse" stop && echo "DAEMON_STOPPED=yes"
  else
    echo "DAEMON_CHECK=stale-busy pid=$DAEMON_PID hash=${OLD_HASH:-unknown} -> $NEW_HASH"
  fi
fi

替换为第 2 步中的安装目录,然后根据 DAEMON_CHECK 采取相应操作:

  1. stale-responsive + DAEMON_STOPPED=yes: 告知用户:“已停止旧版浏览守护进程(二进制文件 {OLD_HASH} → {NEW_HASH})。下一条浏览命令会使用新二进制文件启动一个全新的守护进程。”
  2. stale-busy: 守护进程正在运行旧二进制文件,但正忙于处理任务——交由它继续运行,升级期间绝不要杀掉忙碌的守护进程。告知用户:“浏览守护进程仍在运行升级前的二进制文件({OLD_HASH} → {NEW_HASH}),但当前正忙。任务完成后,使用 browse stop 停止它——或者立即使用 browse --force-restart stop 强制停止(会丢失该会话的标签页、Cookie 和登录状态)。”
  3. none / dead / current: 不作说明。
第 5 步:写入标记并清除缓存
GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
mkdir -p "$GSTACK_STATE_ROOT"
echo "$OLD_VERSION" > "$GSTACK_STATE_ROOT"/just-upgraded-from
rm -f "$GSTACK_STATE_ROOT"/last-update-check
rm -f "$GSTACK_STATE_ROOT"/update-snoozed
第 6 步:展示更新内容

读取 $INSTALL_DIR/CHANGELOG.md。找出旧版本和新版本之间的所有版本条目。按主题分组总结为 5–7 条要点,并优先介绍面向用户的更改。

四个字段:将 git -C "$INSTALL_DIR" log --oneline <PRE_UPGRADE_COMMIT>..HEAD 的结果(≤7 条要点)总结为“gstack v{old},main 位于 {sha}”。

格式:

gstack v{new} — upgraded from v{old}!

What's new:
- [bullet 1]
- [bullet 2]
- ...

Happy shipping!
第 7 步:继续

继续执行用户最初调用的技能;升级已完成。


单独使用

如果直接调用 /gstack-upgrade(而非从前置提示中调用):

  1. 强制执行全新更新检查(绕过缓存):
~/.claude/skills/gstack/bin/gstack-update-check --force 2>/dev/null || \
.claude/skills/gstack/bin/gstack-update-check --force 2>/dev/null || true
  1. 如果为 UPGRADE_AVAILABLE(三个或四个字段):执行上文的步骤 2-6。

    如果为 CHECK_FAILED ...:无法读取远程版本,因此更新状态未知。显示该行(其中列出了检查失败的 URL),并告知用户检查失败;绝不要报告“已是最新版本”。停止。

  2. 如果没有输出(主安装已是最新版本):检查是否存在过时的本地 vendored 副本。

运行上文的步骤 2 bash 代码块,以检测主安装类型和目录(INSTALL_TYPE 和 INSTALL_DIR)。然后运行上文的步骤 4.5 检测 bash 代码块,以检查是否存在本地 vendored 副本(LOCAL_GSTACK)以及团队模式状态(TEAM_MODE)。

如果 LOCAL_GSTACK 为空(没有本地 vendored 副本):告知用户“您已是最新版本(v{version})。”

**如果 LOCAL_GSTACK 非空且 TEAM_MODE 为 true:**使用上文的步骤 4.5 团队模式移除 bash 代码块删除 vendored 副本。告知用户:“全局 v{version} 已是最新版本。已移除过时的 vendored 副本(团队模式已启用)。准备好后提交 .gitignore 更改。”

如果 LOCAL_GSTACK 非空且 TEAM_MODE 不为 true,比较版本:

PRIMARY_VER=$(cat "${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}/VERSION" 2>/dev/null || echo "unknown")
LOCAL_VER=$(cat "${LOCAL_GSTACK:?LOCAL_GSTACK is not set: re-run the Step 4.5 detection block}/VERSION" 2>/dev/null || echo "unknown")
echo "PRIMARY=$PRIMARY_VER LOCAL=$LOCAL_VER"

**如果版本不同:**按照上文的步骤 4.5 同步 bash 代码块,将本地副本更新为主安装的版本。告知用户:“全局 v{PRIMARY_VER} 已是最新版本。已将本地 vendored 副本从 v{LOCAL_VER} 更新为 v{PRIMARY_VER}。准备好后提交 .claude/skills/gstack/。”

**如果版本相同:**告知用户“您已是最新版本(v{PRIMARY_VER})。全局和本地 vendored 副本均已是最新版本。”

评论

支持 Markdown