mcpField
G
gstack-upgrade

gstack-upgrade

github.com/garrytanエージェントワークフロー138.1k351 回インストール今日更新
リポジトリ

gstack-upgrade について

gstack-upgrade は、gstack を更新し変更点を示すスキルです。Git と vendored のインストールに対応し、ローカル変更を確認して、更新失敗時の復旧手順を扱います。

主な機能

  1. 01gstack の更新確認と適用
  2. 02Git と 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 をスキップします。「Auto-upgrading gstack v{old} → v{new}...」と記録して、ステップ 2 に進みます。セットアップに失敗した場合は、ステップ 4 のインストール方式別の復旧手順に従います。ベンダー経由のインストールではバックアップを復元します。git インストールでは、アップグレード前のコミットを記録した状態で停止し、破壊的なリセットは行いません。実際に復元に成功していない限り、復元したと主張してはいけません。

それ以外の場合は AskUserQuestion を使用します。

  • 質問:「gstack v{new} が利用可能です(現在は v{old} です)。今すぐアップグレードしますか?」 4 つのフィールド(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 時間、2 回目は 48 時間、3 回目以降は 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 の出力に含まれるリモートバージョンです。更新チェックの結果から値を置き換えてください。4 フィールドの行では、main が進んだときにスヌーズが終了するよう、SHA(4 番目のフィールド)を {new} として使用します。

スヌーズ期間をユーザーに伝えます:「次回の通知は 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 を明示的に引き継いでください。ツール呼び出しは新しいシェルで実行される場合があるため、後続の各ブロックの先頭で、取得した出力から値を割り当ててください(例:INSTALL_DIR=/abs/path/printed/above)。前の呼び出しの作業ディレクトリやシェル変数に依存しないでください。ファイルを変更する各ブロックでは、まず 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 を通じてファストフォワードします。これは、セッション更新による自動アップグレードと同じコードです。競合なくマージできるローカル編集は更新に引き継がれ、競合する編集(またはステージ済みの変更、未マージファイル)は、競合マーカーを残したままにせず、レスキュー用の参照に保存されます。

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 は、このチェックアウトで登録したすべてのインストール(各ホスト、グローバル、このプロジェクト)を更新し、最初にソースを表示した後、インストールごとに 1 行の Upgrade summary で終了します。表示された行をそのまま伝えてください。failed の行は、そのホストのインストールが以前の状態のままであることを意味します。再試行コマンドを伝え、すべてのホストが更新されたとは決して言わないでください。skipped の行は、別のチェックアウトまたはプロジェクトに属することを意味します。記載されたコマンドを伝え、実行しないでください。

出力が FF_OK で終わる場合、アップグレードは完了です。以下のフォールバックは完全にスキップしてください。その直前の行が rescued <old> <new> refs/gstack/rescue/<oid> unmerged=<0|1> の場合は、競合するローカル編集がそこに保存されたことをユーザーに伝え、スクリプトが出力した Inspect、Export、Delete コマンドを伝えてください(エクスポート先はスクラッチチェックアウトとし、インストール先で stash apply を実行せず、git apply -3 を使用してください。unmerged=1 は、レスキューに手動での解決が必要であることを意味します)。 FF_STOPPED の場合は停止し、その理由を伝えてください。変更はありません。 AHEAD_OF_STABLE の場合は停止してください。チェックアウトはすでに週次リリースより先に進んでいるため、次のリリースが切られるまでは変更されません(すべてのマージ後に gstack-config set update_channel main が実行されます)。 上記に fetching origin/stable failed の行がある場合は、停止してその内容を伝えてください。変更はありません。 ff-busy は、別の gstack 更新が実行中であることを意味します(1 分後に再試行してください)。 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 はありません。セットアップに失敗したというだけで、分岐履歴のフォールバックに進まないでください。フォールバックに進むのは FF_REFUSED(ローカルコミットまたは分岐した履歴)の場合のみです。ネットワークまたは認証の失敗は、リセットではなく修復のために停止してください。

フォールバック(ff-only が拒否された場合 — ローカルコミットまたは分岐)。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" が含まれている場合は、ユーザーに次の注意を伝えてください。「注意:ローカル変更は stash に保存されました(変更された生成済みの SKILL.md/sections ファイルは、セットアップ時に再生成されるため、先に破棄されています)。独自の変更を復元するには、スキルディレクトリで git stash pop を実行してください。」

ベンダー同梱インストールの場合(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
Step 4.5: ローカルのベンダーコピーを処理する

Step 2 のインストールディレクトリを使用します。ローカルのベンダーコピーもあるか、またチームモードが有効かどうかを確認します。

_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 の場合: ベンダーコピーを削除します。チームモードでは、グローバルインストールを唯一の信頼できる情報源として使用します。

(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 のベンダーコピーを削除しました。グローバルインストールが唯一の信頼できる情報源です。準備ができたら .gitignore の変更をコミットしてください。"

LOCAL_GSTACK が空でなく、かつ TEAM_MODE が true ではない場合: 新しくアップグレードしたプライマリーインストールからコピーして更新します(README のベンダーインストールと同じ方法です)。

(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 のベンダーコピーも更新しました。準備ができたら .claude/skills/gstack/ をコミットしてください。" それ以外の場合は停止して復旧結果を報告し、マイグレーションを続行したり、成功を伝えたりしないでください。

Step 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
Step 4.8: 古いデーモンを停止する(無条件)

アップグレード前に起動したブラウズデーモンは、古いバイナリのまま実行され続けます(リセットと ./setup では停止されません)。そのため、必ずこのステップを Step 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

Step 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: 何も伝えません。
Step 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
Step 6: 新機能を表示する

$INSTALL_DIR/CHANGELOG.md を読みます。古いバージョンと新しいバージョンの間にあるすべてのバージョン項目を探します。テーマごとにまとめた 5~7 個の箇条書きで要約し、ユーザー向けの変更を先に記載します。

4 つのフィールド:git -C "$INSTALL_DIR" log --oneline <PRE_UPGRADE_COMMIT>..HEAD の結果(最大 7 個の箇条書き)を「gstack v{old}, main at {sha}」として要約します。

形式:

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

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

Happy shipping!
Step 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(3つまたは4つのフィールド)の場合:上記のステップ2~6に従います。

    CHECK_FAILED ... の場合:リモートバージョンを読み取れなかったため、更新状況は不明です。この行(失敗した URL が記載されています)を表示し、確認に失敗したことをユーザーに伝えます。「すでに最新バージョンです」と報告してはいけません。ここで終了します。

  2. 出力がない場合(プライマリは最新):古いローカルのベンダーコピーがないか確認します。

上記のステップ2の bash ブロックを実行して、プライマリのインストールタイプとディレクトリ(INSTALL_TYPE と INSTALL_DIR)を検出します。次に、上記のステップ4.5の検出用 bash ブロックを実行して、ローカルのベンダーコピー(LOCAL_GSTACK)とチームモードの状態(TEAM_MODE)を確認します。

LOCAL_GSTACK が空の場合(ローカルのベンダーコピーがない場合):ユーザーに「すでに最新バージョン(v{version})です。」と伝えます。

**LOCAL_GSTACK が空でなく、かつ TEAM_MODE が true の場合:**上記のステップ4.5のチームモード用削除 bash ブロックを使用して、ベンダーコピーを削除します。ユーザーに「グローバルの v{version} は最新です。古いベンダーコピーを削除しました(チームモードが有効です)。準備ができたら、.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} は最新です。ローカルのベンダーコピーを v{LOCAL_VER} から v{PRIMARY_VER} に更新しました。準備ができたら、.claude/skills/gstack/ をコミットしてください。」と伝えます。

**バージョンが一致する場合:**ユーザーに「最新バージョン(v{PRIMARY_VER})です。グローバルとローカルのベンダーコピーはどちらも最新です。」と伝えます。

コメント