[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DJPG9Q1HKCYYDFD5MZNEC2":3,"skill-same:01M6DJPG9Q1HKCYYDFD5MZNEC2":137},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":29,"linkOnly":30,"markdownContent":31,"markdownBody":32,"frontMatter":33,"refName":43,"commitSha":44,"skillDirectory":5,"forksCount":45,"repositoryHtmlUrl":46,"repositoryHomepageUrl":6,"ownerHtmlUrl":47,"ownerBlogUrl":48,"ownerDescription":49,"updatedAt":50,"install":51,"resources":64},"01M6DJPG9Q1HKCYYDFD5MZNEC2","gstack-upgrade",null,"Upgrade gstack to the latest version.","garrytan\u002Fgstack","garrytan","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F19957?v=4","01M6DJCCDDG53MTYYRTXAXFG17","Garry Tan","01M6DJCCKGZB08AY82GDQXVJ8G",141234,"MIT","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28],"gstack upgrade","gstack","upgrade","update","Bash","read","write","version","get","latest","2026-11-10 10:12:30",false,"---\nname: gstack-upgrade\nversion: 1.1.0\ndescription: Upgrade gstack to the latest version.\ntriggers:\n  - upgrade gstack\n  - update gstack version\n  - get latest gstack\nallowed-tools:\n  - Bash\n  - Read\n  - Write\n  - AskUserQuestion\n---\n\u003C!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n\u003C!-- Regenerate: bun run gen:skill-docs -->\n\n# \u002Fgstack-upgrade\n\nUpgrade gstack to the latest version and show what's new.\n\n## Inline upgrade flow\n\nThis section is referenced by all skill preambles when they detect `UPGRADE_AVAILABLE`.\n\n### Step 1: Ask the user (or auto-upgrade)\n\nFirst, check if auto-upgrade is enabled:\n```bash\n_AUTO=\"\"\n[ \"${GSTACK_AUTO_UPGRADE:-}\" = \"1\" ] && _AUTO=\"true\"\n[ -z \"$_AUTO\" ] && _AUTO=$(~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-config get auto_upgrade 2>\u002Fdev\u002Fnull || true)\necho \"AUTO_UPGRADE=$_AUTO\"\n```\n\n**If `AUTO_UPGRADE=true` or `AUTO_UPGRADE=1`:** Skip AskUserQuestion. Log \"Auto-upgrading gstack v{old} → v{new}...\" and proceed directly to Step 2. On setup failure, follow Step 4's install-specific recovery: vendored installs restore their backup; git installs stop with the pre-upgrade commit recorded, without a destructive reset. Never claim restoration unless it actually succeeded.\n\n**Otherwise**, use AskUserQuestion:\n- Question: \"gstack **v{new}** is available (you're on v{old}). Upgrade now?\"\n  Four fields (`UPGRADE_AVAILABLE \u003Cold> \u003Cold>+\u003Cn> \u003Csha>`, main moved): ask \"gstack main is **{n} commit(s)** past v{old} (at {sha}). Upgrade now?\"\n- Options: [\"Yes, upgrade now\", \"Always keep me up to date\", \"Not now\", \"Never ask again\"]\n\n**If \"Yes, upgrade now\":** Proceed to Step 2.\n\n**If \"Always keep me up to date\":**\n```bash\n~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-config set auto_upgrade true\n```\nTell user: \"Auto-upgrade enabled. Future updates will install automatically.\" Then proceed to Step 2.\n\n**If \"Not now\":** Write snooze state with escalating backoff (first snooze = 24h, second = 48h, third+ = 1 week), then continue with the current skill. Do not mention the upgrade again.\n```bash\nGSTACK_STATE_ROOT=$(~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-paths --get GSTACK_STATE_ROOT); : \"${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with .\u002Fsetup or \u002Fgstack-upgrade}\"\n_SNOOZE_FILE=\"$GSTACK_STATE_ROOT\u002Fupdate-snoozed\"\n_REMOTE_VER=\"{new}\"\n_CUR_LEVEL=0\nif [ -f \"$_SNOOZE_FILE\" ]; then\n  _SNOOZED_VER=$(awk '{print $(1)}' \"$_SNOOZE_FILE\")\n  if [ \"$_SNOOZED_VER\" = \"$_REMOTE_VER\" ]; then\n    _CUR_LEVEL=$(awk '{print $(2)}' \"$_SNOOZE_FILE\")\n    case \"$_CUR_LEVEL\" in *[!0-9]*) _CUR_LEVEL=0 ;; esac\n  fi\nfi\n_NEW_LEVEL=$((_CUR_LEVEL + 1))\n[ \"$_NEW_LEVEL\" -gt 3 ] && _NEW_LEVEL=3\necho \"$_REMOTE_VER $_NEW_LEVEL $(date +%s)\" > \"$_SNOOZE_FILE\"\n```\nNote: `{new}` is the remote version from the `UPGRADE_AVAILABLE` output — substitute it from the update check result. On a four-field line use the SHA (fourth field) as `{new}`, so the snooze ends when main moves.\n\nTell user the snooze duration: \"Next reminder in 24h\" (or 48h or 1 week, depending on level). Tip: \"Set `auto_upgrade: true` in `~\u002F.gstack\u002Fconfig.yaml` for automatic upgrades.\"\n\n**If \"Never ask again\":**\n```bash\n~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-config set update_check false\n```\nTell user: \"Update checks disabled. Run `~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-config set update_check true` to re-enable.\"\nContinue with the current skill.\n\n### Step 2: Detect install type\n\n```bash\nif [ -d \"$HOME\u002F.claude\u002Fskills\u002Fgstack\u002F.git\" ]; then\n  INSTALL_TYPE=\"global-git\"\n  INSTALL_DIR=\"$HOME\u002F.claude\u002Fskills\u002Fgstack\"\nelif [ -d \"$HOME\u002F.gstack\u002Frepos\u002Fgstack\u002F.git\" ]; then\n  INSTALL_TYPE=\"global-git\"\n  INSTALL_DIR=\"$HOME\u002F.gstack\u002Frepos\u002Fgstack\"\nelif [ -d \".claude\u002Fskills\u002Fgstack\u002F.git\" ]; then\n  INSTALL_TYPE=\"local-git\"\n  INSTALL_DIR=\".claude\u002Fskills\u002Fgstack\"\nelif [ -d \".agents\u002Fskills\u002Fgstack\u002F.git\" ]; then\n  INSTALL_TYPE=\"local-git\"\n  INSTALL_DIR=\".agents\u002Fskills\u002Fgstack\"\nelif [ -d \".claude\u002Fskills\u002Fgstack\" ]; then\n  INSTALL_TYPE=\"vendored\"\n  INSTALL_DIR=\".claude\u002Fskills\u002Fgstack\"\nelif [ -d \"$HOME\u002F.claude\u002Fskills\u002Fgstack\" ]; then\n  INSTALL_TYPE=\"vendored-global\"\n  INSTALL_DIR=\"$HOME\u002F.claude\u002Fskills\u002Fgstack\"\nelif _SRC=$(~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-paths --get GSTACK_STATE_ROOT 2>\u002Fdev\u002Fnull) && _SRC=$(awk -F '\\t' '$(1) == \"claude\" && $(6) != \"-\" { print $(6); exit }' \"$_SRC\u002Finstalls.tsv\" 2>\u002Fdev\u002Fnull) && [ -d \"$_SRC\u002F.git\" ]; then\n  # The install registry names the checkout setup activated for this host (e.g. a ~\u002Fgstack clone).\n  INSTALL_TYPE=\"global-git\"\n  INSTALL_DIR=\"$_SRC\"\nelse\n  echo \"ERROR: gstack not found\"\n  exit 1\nfi\nINSTALL_DIR=$(cd -- \"$INSTALL_DIR\" && pwd -P) || { echo \"ERROR: cannot enter the gstack install directory\" >&2; exit 1; }\necho \"Install type: $INSTALL_TYPE at $INSTALL_DIR\"\n```\n\nThe install type and the absolute directory printed above are used in all subsequent steps.\nCarry `INSTALL_TYPE`, `INSTALL_DIR`, `OLD_VERSION`, and later `UPSTREAM_REF` and `NEW_VERSION` forward explicitly: tool calls may run in fresh shells, so start each later block by assigning them from the captured output (for example `INSTALL_DIR=\u002Fabs\u002Fpath\u002Fprinted\u002Fabove`). Do not rely on a prior call's working directory or shell variables. Every block that changes files first checks `INSTALL_DIR` and stops before any git command when it is unset, empty, missing, unreadable, or not gstack's own checkout.\n\n### Step 3: Save old version\n\nUse the install directory from Step 2's output below:\n\n```bash\nOLD_VERSION=$(cat \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\u002FVERSION\" 2>\u002Fdev\u002Fnull || echo \"unknown\")\necho \"OLD_VERSION=$OLD_VERSION\"\n```\n\n### Step 4: Upgrade\n\nUse the install type and directory detected in Step 2:\n\n**For git installs** (global-git, local-git):\n\nFast-forward first, through `bin\u002Fgstack-ff-update`, the same code the\nsession-update auto-upgrade uses: local edits that merge cleanly ride over the\nupdate, and an edit that conflicts (or a staged change, or an unmerged file)\nis saved in a rescue ref instead of being left as conflict markers:\n```bash\ncd -- \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\" || exit 1\n{ [ -f VERSION ] && [ -f setup ] && [ -f bin\u002Fgstack-config ] && _p=$(git rev-parse --show-prefix 2>\u002Fdev\u002Fnull) && [ -z \"$_p\" ] && [ \"$(git rev-parse --is-inside-work-tree 2>\u002Fdev\u002Fnull)\" = true ]; } || { echo \"ERROR: $INSTALL_DIR is not a gstack checkout; nothing was changed. Re-run Step 2.\" >&2; exit 1; }\ngit fetch origin\n# Channel ref (docs\u002Freleases.md): origin\u002Fstable for a team install, else origin\u002Fmain\n_CH=$(bash bin\u002Fgstack-update-channel.sh --fetch-ref 2>&1) || _CH=origin\u002Fmain\necho \"$_CH\"; UPSTREAM_REF=$(printf '%s\\n' \"$_CH\" | tail -1)\necho \"UPSTREAM_REF=${UPSTREAM_REF:=origin\u002Fmain}\"\n# Same pre-advance check as the auto-updater: the incoming release's Bun floor,\n# judged by bash whatever this tool shell is (zsh does not word-split).\nHOLD=$(UPSTREAM_REF=\"$UPSTREAM_REF\" bash -c '. bin\u002Fgstack-bun-version.sh && gstack_bun_incoming_hold . \"$UPSTREAM_REF\"' 2>\u002Fdev\u002Fnull) && { 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; }\nPRE_UPGRADE_COMMIT=$(git rev-parse HEAD)\necho \"PRE_UPGRADE_COMMIT=$PRE_UPGRADE_COMMIT\"\n# Past the release (by ancestry, or by VERSION in a depth-1 clone): wait for stable; nothing moves back.\nif [ \"$UPSTREAM_REF\" = origin\u002Fstable ] && [ \"$(git rev-parse origin\u002Fstable)\" != \"$PRE_UPGRADE_COMMIT\" ] && ! git merge-base --is-ancestor HEAD origin\u002Fstable 2>\u002Fdev\u002Fnull \\\n  && { git merge-base --is-ancestor origin\u002Fstable HEAD 2>\u002Fdev\u002Fnull || [ \"$(printf '%s\\n%s\\n' \"$(git show origin\u002Fstable:VERSION)\" \"$(cat VERSION)\" | sort -V | tail -1)\" = \"$(cat VERSION)\" ]; }; then echo \"AHEAD_OF_STABLE\"; exit 0; fi\nFF=$(bash bin\u002Fgstack-ff-update . \"$UPSTREAM_REF\"); FF_RC=$?\necho \"$FF\"\ncase \"$FF_RC\" in\n  0) if .\u002Fsetup --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 ;;\n  4) echo \"FF_REFUSED\" ;;\n  *) echo \"FF_STOPPED: $FF\" >&2; exit 1 ;;\nesac\n```\n\nSetup's `--refresh-registered` refreshes every install this checkout\nregistered (each host, global and this project's), prints the source first,\nand ends with an `Upgrade summary` of one row per install. Relay those rows\nas printed. A `failed` row means that host kept its previous install; give\nits retry command and never say every host was refreshed. A `skipped` row\nbelongs to another checkout or project; pass on its command, do not run it.\n\nIf the output ends with `FF_OK`, the upgrade is done — skip the fallback\nbelow entirely. When the line before it reads\n`rescued \u003Cold> \u003Cnew> refs\u002Fgstack\u002Frescue\u002F\u003Coid> unmerged=\u003C0|1>`, tell the user\ntheir conflicting local edits were saved there and relay the `Inspect`,\n`Export` and `Delete` commands the script printed (export into a scratch\ncheckout with `git apply -3`, never `stash apply` in the install;\n`unmerged=1` means the rescue needs manual resolution).\nOn `FF_STOPPED`, STOP and relay the reason: nothing was changed.\nOn `AHEAD_OF_STABLE`, STOP: the checkout is already past the latest stable release, so nothing changes until the next cut (`gstack-config set update_channel main` follows every merge). On a `fetching origin\u002Fstable failed` line above, STOP and relay it; nothing was changed.\n`ff-busy` means another gstack update is running (retry in a minute);\n`autostash-unrecoverable` names what is in the way (an untracked file the\nrelease adds, or another git command); `index-unreadable` means the checkout's\nindex is damaged.\nOn `BUN_TOO_OLD`, STOP: tell the user to run `bun upgrade`, then \u002Fgstack-upgrade again.\nOn `BUN_VERSION_UNREADABLE`, STOP: `bun --version` did not print a version gstack can read, so `bun upgrade` may not run either; tell the user to reinstall Bun from https:\u002F\u002Fbun.sh (`curl -fsSL https:\u002F\u002Fbun.sh\u002Finstall | bash` in Git Bash or a macOS\u002FLinux terminal), check that `bun --version` prints a version such as 1.4.2, then run \u002Fgstack-upgrade again (docs\u002Ftroubleshooting.md#bun-version-unreadable).\nOn `SETUP_FAILED`, STOP; keep user changes and report the recovery commit. There is no `.bak` on the git path. Do not enter the divergence fallback merely because setup failed. Enter it only on `FF_REFUSED` (local commits or a diverged history); network\u002Fauth failures stop for repair, not reset.\n\n**Fallback (ff-only refused — local commits or divergence).** `git reset\n--hard` DESTROYS things: a clean tree with unpushed local commits still loses\nthose commits. Gate it:\n\n1. Run `git status --porcelain` and `git rev-list \"${UPSTREAM_REF:?}\"..HEAD --oneline`\n   in `$INSTALL_DIR`.\n2. If BOTH are empty, the reset is provably safe — run the fallback block\n   below without asking.\n3. Otherwise ask via AskUserQuestion (one-way door — destructive), listing\n   exactly what will be discarded: each dirty file and each unpushed commit\n   by hash + subject. Options: **A)** Discard them and upgrade (reset) —\n   requires the explicit letter; **B)** Abort the upgrade so the user can\n   rescue their work first (recommended when local commits exist). Never\n   proceed on a vague reply.\n\n```bash\ncd -- \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\" || exit 1\n{ [ -f VERSION ] && [ -f setup ] && [ -f bin\u002Fgstack-config ] && _p=$(git rev-parse --show-prefix 2>\u002Fdev\u002Fnull) && [ -z \"$_p\" ] && [ \"$(git rev-parse --is-inside-work-tree 2>\u002Fdev\u002Fnull)\" = true ]; } || { echo \"ERROR: $INSTALL_DIR is not a gstack checkout; nothing was changed. Re-run Step 2.\" >&2; exit 1; }\n# Regenerable render-footprint dirt (setup re-renders it) never goes into the stash.\ngit checkout -- 'SKILL.md' '*\u002FSKILL.md' '*\u002Fsections\u002F*.md' 2>\u002Fdev\u002Fnull || true\nSTASH_OUTPUT=$(git stash 2>&1)\ngit reset --hard \"${UPSTREAM_REF:?UPSTREAM_REF is not set: substitute the value Step 4 printed}\"\n.\u002Fsetup --refresh-registered\n```\nIf `$STASH_OUTPUT` contains \"Saved working directory\", warn the user: \"Note: local changes were stashed (any modified generated SKILL.md\u002Fsections files were discarded first — they regenerate on setup). Run `git stash pop` in the skill directory to restore your own changes.\"\n\n**For vendored installs** (vendored, vendored-global):\n```bash\n(cd -- \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\" && [ -f VERSION ] && [ -f setup ] && [ -f bin\u002Fgstack-config ]) || { echo \"ERROR: INSTALL_DIR=${INSTALL_DIR:-} is not a gstack install; nothing was changed. Re-run Step 2.\" >&2; exit 1; }\n# A stale .bak from a previously crashed upgrade would make the mv below NEST\n# the live install inside it and the failure-restore arm would \"restore\" the\n# stale backup. It may also be the only good copy from that crashed run —\n# abort and let the human inspect, never delete it silently.\n[ -e \"$INSTALL_DIR.bak\" ] && { echo \"ERROR: stale backup exists at $INSTALL_DIR.bak (from a previous failed upgrade?) — inspect it, salvage\u002Fremove it, then re-run.\" >&2; exit 1; }\nTMP_DIR=$(mktemp -d \"${TMPDIR:-\u002Ftmp}\u002Fgstack-upgrade.XXXXXX\") || { echo \"ERROR: mktemp failed — aborting upgrade (install untouched).\" >&2; exit 1; }\ngit clone --depth 1 https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack.git \"$TMP_DIR\u002Fgstack\" || { echo \"ERROR: clone failed — aborting upgrade (install untouched).\" >&2; rm -rf \"$TMP_DIR\"; exit 1; }\nmv \"$INSTALL_DIR\" \"$INSTALL_DIR.bak\" || { rm -rf \"$TMP_DIR\"; exit 1; }\nif mv \"$TMP_DIR\u002Fgstack\" \"$INSTALL_DIR\"; then\n  if (cd \"$INSTALL_DIR\" && .\u002Fsetup --refresh-registered); then\n    rm -rf \"$INSTALL_DIR.bak\" \"$TMP_DIR\"\n  else\n    rm -rf \"$INSTALL_DIR\"\n    mv \"$INSTALL_DIR.bak\" \"$INSTALL_DIR\" || { echo \"ERROR: restore failed; backup retained.\" >&2; exit 1; }\n    rm -rf \"$TMP_DIR\"\n    echo \"ERROR: setup failed; previous install restored.\" >&2\n    exit 1\n  fi\nelse\n  mv \"$INSTALL_DIR.bak\" \"$INSTALL_DIR\"\n  echo \"ERROR: swap failed — previous install restored; upgrade aborted.\" >&2\n  rm -rf \"$TMP_DIR\"\n  exit 1\nfi\n```\n\n### Step 4.5: Handle local vendored copy\n\nUse the install directory from Step 2. Check if there's also a local vendored copy, and whether team mode is active:\n\n```bash\n_RESOLVED_PRIMARY=$(cd -- \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\" && pwd -P) || exit 1\n_ROOT=$(git rev-parse --show-toplevel 2>\u002Fdev\u002Fnull)\nLOCAL_GSTACK=\"\"\nif [ -n \"$_ROOT\" ] && [ -d \"$_ROOT\u002F.claude\u002Fskills\u002Fgstack\" ]; then\n  _RESOLVED_LOCAL=$(cd \"$_ROOT\u002F.claude\u002Fskills\u002Fgstack\" && pwd -P)\n  if [ \"$_RESOLVED_LOCAL\" != \"$_RESOLVED_PRIMARY\" ]; then\n    LOCAL_GSTACK=\"$_ROOT\u002F.claude\u002Fskills\u002Fgstack\"\n  fi\nfi\n_TEAM_MODE=$(~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-config get team_mode 2>\u002Fdev\u002Fnull || echo \"false\")\necho \"LOCAL_GSTACK=$LOCAL_GSTACK\"\necho \"TEAM_MODE=$_TEAM_MODE\"\n```\n\n**If `LOCAL_GSTACK` is non-empty AND `TEAM_MODE` is `true`:** Remove the vendored copy. Team mode uses the global install as the single source of truth.\n\n```bash\n(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\u002Fgstack-config ]) || { echo \"ERROR: LOCAL_GSTACK=${LOCAL_GSTACK:-} is not a gstack copy; nothing was changed.\" >&2; exit 1; }\n_ROOT=${LOCAL_GSTACK%\u002F.claude\u002Fskills\u002Fgstack}\n{ [ \"$_ROOT\" != \"$LOCAL_GSTACK\" ] && cd -- \"${_ROOT:?}\" && _p=$(git rev-parse --show-prefix 2>\u002Fdev\u002Fnull) && [ -z \"$_p\" ] && [ \"$(git rev-parse --is-inside-work-tree 2>\u002Fdev\u002Fnull)\" = true ]; } || { echo \"ERROR: $LOCAL_GSTACK is not the vendored copy at the root of a git repository; nothing was changed.\" >&2; exit 1; }\ngit rm -r --cached .claude\u002Fskills\u002Fgstack\u002F 2>\u002Fdev\u002Fnull || true\nif ! grep -qF '.claude\u002Fskills\u002Fgstack\u002F' .gitignore 2>\u002Fdev\u002Fnull; then\n  echo '.claude\u002Fskills\u002Fgstack\u002F' >> .gitignore\nfi\nrm -rf \"$LOCAL_GSTACK\"\n```\nTell user: \"Removed vendored copy at `$LOCAL_GSTACK` (team mode active — global install is the source of truth). Commit the `.gitignore` change when ready.\"\n\n**If `LOCAL_GSTACK` is non-empty AND `TEAM_MODE` is NOT `true`:** Update it by copying from the freshly-upgraded primary install (same approach as README vendored install):\n```bash\n(cd -- \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\" && [ -f VERSION ] && [ -f setup ] && [ -f bin\u002Fgstack-config ]) || { echo \"ERROR: INSTALL_DIR=${INSTALL_DIR:-} is not a gstack install; nothing was changed.\" >&2; exit 1; }\n(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\u002Fgstack-config ]) || { echo \"ERROR: LOCAL_GSTACK=${LOCAL_GSTACK:-} is not a gstack copy; nothing was changed.\" >&2; exit 1; }\n[ -e \"$LOCAL_GSTACK.bak\" ] && { echo \"ERROR: stale vendored backup; inspect it before retrying.\" >&2; exit 1; }\nmv \"$LOCAL_GSTACK\" \"$LOCAL_GSTACK.bak\" || exit 1\nif cp -Rf \"$INSTALL_DIR\" \"$LOCAL_GSTACK\" && rm -rf \"$LOCAL_GSTACK\u002F.git\" && (cd \"$LOCAL_GSTACK\" && .\u002Fsetup --refresh-registered); then\n  rm -rf \"$LOCAL_GSTACK.bak\"\n  echo \"LOCAL_SYNC_OK\"\nelse\n  rm -rf \"$LOCAL_GSTACK\"\n  mv \"$LOCAL_GSTACK.bak\" \"$LOCAL_GSTACK\" || { echo \"ERROR: restore failed; backup retained.\" >&2; exit 1; }\n  echo \"ERROR: sync failed; previous vendored copy restored.\" >&2\n  exit 1\nfi\n```\nOnly on `LOCAL_SYNC_OK`, tell user: \"Also updated vendored copy at `$LOCAL_GSTACK` — commit `.claude\u002Fskills\u002Fgstack\u002F` when you're ready.\" Otherwise stop and report the recovery outcome; do not continue migrations or announce success.\n\n### Step 4.75: Run version migrations\n\nAfter `.\u002Fsetup`, run the migrations in `gstack-upgrade\u002Fmigrations\u002F` (idempotent\n`v{VERSION}.sh` scripts; CONTRIBUTING.md says how to add one) newer than the old\nversion: they fix state `.\u002Fsetup` alone cannot (stale config, orphaned files).\n\n```bash\nMIGRATIONS_DIR=\"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\u002Fgstack-upgrade\u002Fmigrations\"\n: \"${OLD_VERSION:?OLD_VERSION is not set: substitute the value Step 3 printed}\"\nif [ -d \"$MIGRATIONS_DIR\" ]; then\n  for migration in $(find \"$MIGRATIONS_DIR\" -maxdepth 1 -name 'v*.sh' -type f 2>\u002Fdev\u002Fnull | sort -V); do\n    m_ver=\"$(basename \"$migration\" .sh | sed 's\u002F^v\u002F\u002F')\"\n    if [ \"$OLD_VERSION\" != \"unknown\" ] && [ \"$(printf '%s\\n%s' \"$OLD_VERSION\" \"$m_ver\" | sort -V | head -1)\" = \"$OLD_VERSION\" ] && [ \"$OLD_VERSION\" != \"$m_ver\" ]; then\n      echo \"Running migration $m_ver...\"\n      # Without GSTACK_INSTALL_DIR a migration that cleans the install assumes ~\u002F.claude\u002Fskills\u002Fgstack.\n      GSTACK_INSTALL_DIR=\"$INSTALL_DIR\" bash \"$migration\" || echo \"  Warning: migration $m_ver had errors (non-fatal)\"\n    fi\n  done\nfi\n```\n\n### Step 4.8: Stop any stale daemon (unconditional)\n\nA browse daemon started before the upgrade keeps running the old binary (the\nreset and `.\u002Fsetup` do not stop it), so always run this step with the install\ndirectory from Step 2.\n\n```bash\nINSTALL_DIR_PLACEHOLDER=\"\u003Cinstall dir from Step 2>\"\nNEW_HASH=$(cat \"$INSTALL_DIR_PLACEHOLDER\u002Fbrowse\u002Fdist\u002F.version\" 2>\u002Fdev\u002Fnull || echo \"\")\n_STATE_FILE=\"${BROWSE_STATE_FILE:-$(git rev-parse --show-toplevel 2>\u002Fdev\u002Fnull || pwd)\u002F.gstack\u002Fbrowse.json}\"\nif [ -z \"$NEW_HASH\" ] || [ ! -f \"$_STATE_FILE\" ]; then\n  echo \"DAEMON_CHECK=none (no state file or no fresh build hash)\"\nelse\n  DAEMON_PID=$(sed -n 's\u002F.*\"pid\"[[:space:]]*:[[:space:]]*\\([0-9][0-9]*\\).*\u002F\\1\u002Fp' \"$_STATE_FILE\" | head -1)\n  DAEMON_PORT=$(sed -n 's\u002F.*\"port\"[[:space:]]*:[[:space:]]*\\([0-9][0-9]*\\).*\u002F\\1\u002Fp' \"$_STATE_FILE\" | head -1)\n  OLD_HASH=$(sed -n 's\u002F.*\"binaryVersion\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*\u002F\\1\u002Fp' \"$_STATE_FILE\" | head -1)\n  if [ -z \"$DAEMON_PID\" ] || ! kill -0 \"$DAEMON_PID\" 2>\u002Fdev\u002Fnull; then\n    echo \"DAEMON_CHECK=dead (no live daemon to stop)\"\n  elif [ \"$OLD_HASH\" = \"$NEW_HASH\" ]; then\n    echo \"DAEMON_CHECK=current (daemon already runs the new binary)\"\n  elif curl -fsS --max-time 2 \"http:\u002F\u002F127.0.0.1:$DAEMON_PORT\u002Fhealth\" 2>\u002Fdev\u002Fnull | grep -q '\"status\":\"healthy\"'; then\n    echo \"DAEMON_CHECK=stale-responsive pid=$DAEMON_PID hash=${OLD_HASH:-unknown} -> $NEW_HASH\"\n    \"$INSTALL_DIR_PLACEHOLDER\u002Fbrowse\u002Fdist\u002Fbrowse\" stop && echo \"DAEMON_STOPPED=yes\"\n  else\n    echo \"DAEMON_CHECK=stale-busy pid=$DAEMON_PID hash=${OLD_HASH:-unknown} -> $NEW_HASH\"\n  fi\nfi\n```\n\nSubstitute the install directory from Step 2, then act on `DAEMON_CHECK`:\n\n1. **`stale-responsive` + `DAEMON_STOPPED=yes`:** Tell the user \"Stopped the\n   old browse daemon (binary {OLD_HASH} → {NEW_HASH}). The next browse command\n   starts a fresh daemon on the new binary.\"\n2. **`stale-busy`:** The daemon runs the old binary but is mid-work — DEFER to\n   it, never kill a busy daemon during upgrade. Tell the user: \"A browse daemon\n   is still running the pre-upgrade binary ({OLD_HASH} → {NEW_HASH}) but is\n   busy right now. When it finishes, stop it with `browse stop` — or force it\n   immediately with `browse --force-restart stop` (loses that session's\n   tabs\u002Fcookies\u002Flogins).\"\n3. **`none` \u002F `dead` \u002F `current`:** say nothing.\n\n### Step 5: Write marker + clear cache\n\n```bash\nGSTACK_STATE_ROOT=$(~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-paths --get GSTACK_STATE_ROOT); : \"${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with .\u002Fsetup or \u002Fgstack-upgrade}\"\nmkdir -p \"$GSTACK_STATE_ROOT\"\necho \"$OLD_VERSION\" > \"$GSTACK_STATE_ROOT\"\u002Fjust-upgraded-from\nrm -f \"$GSTACK_STATE_ROOT\"\u002Flast-update-check\nrm -f \"$GSTACK_STATE_ROOT\"\u002Fupdate-snoozed\n```\n\n### Step 6: Show What's New\n\nRead `$INSTALL_DIR\u002FCHANGELOG.md`. Find all version entries between the old version and the new version. Summarize as 5-7 bullets grouped by theme, user-facing changes first.\n\nFour fields: summarize `git -C \"$INSTALL_DIR\" log --oneline \u003CPRE_UPGRADE_COMMIT>..HEAD` (≤7 bullets) as \"gstack v{old}, main at {sha}\".\n\nFormat:\n```\ngstack v{new} — upgraded from v{old}!\n\nWhat's new:\n- [bullet 1]\n- [bullet 2]\n- ...\n\nHappy shipping!\n```\n\n### Step 7: Continue\n\nContinue with the skill the user originally invoked; the upgrade is done.\n\n---\n\n## Standalone usage\n\nWhen invoked directly as `\u002Fgstack-upgrade` (not from a preamble):\n\n1. Force a fresh update check (bypass cache):\n```bash\n~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-update-check --force 2>\u002Fdev\u002Fnull || \\\n.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-update-check --force 2>\u002Fdev\u002Fnull || true\n```\n\n2. If `UPGRADE_AVAILABLE` (three or four fields): follow Steps 2-6 above.\n\n   If `CHECK_FAILED ...`: the remote version could not be read, so the update\n   status is unknown. Show the line (it names the URL that failed) and tell the\n   user the check failed; never report \"already on the latest version\". Stop.\n\n3. If no output (primary is up to date): check for a stale local vendored copy.\n\nRun the Step 2 bash block above to detect the primary install type and directory (`INSTALL_TYPE` and `INSTALL_DIR`). Then run the Step 4.5 detection bash block above to check for a local vendored copy (`LOCAL_GSTACK`) and team mode status (`TEAM_MODE`).\n\n**If `LOCAL_GSTACK` is empty** (no local vendored copy): tell the user \"You're already on the latest version (v{version}).\"\n\n**If `LOCAL_GSTACK` is non-empty AND `TEAM_MODE` is `true`:** Remove the vendored copy using the Step 4.5 team-mode removal bash block above. Tell user: \"Global v{version} is up to date. Removed stale vendored copy (team mode active). Commit the `.gitignore` change when ready.\"\n\n**If `LOCAL_GSTACK` is non-empty AND `TEAM_MODE` is NOT `true`**, compare versions:\n```bash\nPRIMARY_VER=$(cat \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\u002FVERSION\" 2>\u002Fdev\u002Fnull || echo \"unknown\")\nLOCAL_VER=$(cat \"${LOCAL_GSTACK:?LOCAL_GSTACK is not set: re-run the Step 4.5 detection block}\u002FVERSION\" 2>\u002Fdev\u002Fnull || echo \"unknown\")\necho \"PRIMARY=$PRIMARY_VER LOCAL=$LOCAL_VER\"\n```\n\n**If versions differ:** follow the Step 4.5 sync bash block above to update the local copy from the primary. Tell user: \"Global v{PRIMARY_VER} is up to date. Updated local vendored copy from v{LOCAL_VER} → v{PRIMARY_VER}. Commit `.claude\u002Fskills\u002Fgstack\u002F` when you're ready.\"\n\n**If versions match:** tell the user \"You're on the latest version (v{PRIMARY_VER}). Global and local vendored copy are both up to date.\"\n","\u003C!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n\u003C!-- Regenerate: bun run gen:skill-docs -->\n\n# \u002Fgstack-upgrade\n\nUpgrade gstack to the latest version and show what's new.\n\n## Inline upgrade flow\n\nThis section is referenced by all skill preambles when they detect `UPGRADE_AVAILABLE`.\n\n### Step 1: Ask the user (or auto-upgrade)\n\nFirst, check if auto-upgrade is enabled:\n```bash\n_AUTO=\"\"\n[ \"${GSTACK_AUTO_UPGRADE:-}\" = \"1\" ] && _AUTO=\"true\"\n[ -z \"$_AUTO\" ] && _AUTO=$(~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-config get auto_upgrade 2>\u002Fdev\u002Fnull || true)\necho \"AUTO_UPGRADE=$_AUTO\"\n```\n\n**If `AUTO_UPGRADE=true` or `AUTO_UPGRADE=1`:** Skip AskUserQuestion. Log \"Auto-upgrading gstack v{old} → v{new}...\" and proceed directly to Step 2. On setup failure, follow Step 4's install-specific recovery: vendored installs restore their backup; git installs stop with the pre-upgrade commit recorded, without a destructive reset. Never claim restoration unless it actually succeeded.\n\n**Otherwise**, use AskUserQuestion:\n- Question: \"gstack **v{new}** is available (you're on v{old}). Upgrade now?\"\n  Four fields (`UPGRADE_AVAILABLE \u003Cold> \u003Cold>+\u003Cn> \u003Csha>`, main moved): ask \"gstack main is **{n} commit(s)** past v{old} (at {sha}). Upgrade now?\"\n- Options: [\"Yes, upgrade now\", \"Always keep me up to date\", \"Not now\", \"Never ask again\"]\n\n**If \"Yes, upgrade now\":** Proceed to Step 2.\n\n**If \"Always keep me up to date\":**\n```bash\n~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-config set auto_upgrade true\n```\nTell user: \"Auto-upgrade enabled. Future updates will install automatically.\" Then proceed to Step 2.\n\n**If \"Not now\":** Write snooze state with escalating backoff (first snooze = 24h, second = 48h, third+ = 1 week), then continue with the current skill. Do not mention the upgrade again.\n```bash\nGSTACK_STATE_ROOT=$(~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-paths --get GSTACK_STATE_ROOT); : \"${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with .\u002Fsetup or \u002Fgstack-upgrade}\"\n_SNOOZE_FILE=\"$GSTACK_STATE_ROOT\u002Fupdate-snoozed\"\n_REMOTE_VER=\"{new}\"\n_CUR_LEVEL=0\nif [ -f \"$_SNOOZE_FILE\" ]; then\n  _SNOOZED_VER=$(awk '{print $(1)}' \"$_SNOOZE_FILE\")\n  if [ \"$_SNOOZED_VER\" = \"$_REMOTE_VER\" ]; then\n    _CUR_LEVEL=$(awk '{print $(2)}' \"$_SNOOZE_FILE\")\n    case \"$_CUR_LEVEL\" in *[!0-9]*) _CUR_LEVEL=0 ;; esac\n  fi\nfi\n_NEW_LEVEL=$((_CUR_LEVEL + 1))\n[ \"$_NEW_LEVEL\" -gt 3 ] && _NEW_LEVEL=3\necho \"$_REMOTE_VER $_NEW_LEVEL $(date +%s)\" > \"$_SNOOZE_FILE\"\n```\nNote: `{new}` is the remote version from the `UPGRADE_AVAILABLE` output — substitute it from the update check result. On a four-field line use the SHA (fourth field) as `{new}`, so the snooze ends when main moves.\n\nTell user the snooze duration: \"Next reminder in 24h\" (or 48h or 1 week, depending on level). Tip: \"Set `auto_upgrade: true` in `~\u002F.gstack\u002Fconfig.yaml` for automatic upgrades.\"\n\n**If \"Never ask again\":**\n```bash\n~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-config set update_check false\n```\nTell user: \"Update checks disabled. Run `~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-config set update_check true` to re-enable.\"\nContinue with the current skill.\n\n### Step 2: Detect install type\n\n```bash\nif [ -d \"$HOME\u002F.claude\u002Fskills\u002Fgstack\u002F.git\" ]; then\n  INSTALL_TYPE=\"global-git\"\n  INSTALL_DIR=\"$HOME\u002F.claude\u002Fskills\u002Fgstack\"\nelif [ -d \"$HOME\u002F.gstack\u002Frepos\u002Fgstack\u002F.git\" ]; then\n  INSTALL_TYPE=\"global-git\"\n  INSTALL_DIR=\"$HOME\u002F.gstack\u002Frepos\u002Fgstack\"\nelif [ -d \".claude\u002Fskills\u002Fgstack\u002F.git\" ]; then\n  INSTALL_TYPE=\"local-git\"\n  INSTALL_DIR=\".claude\u002Fskills\u002Fgstack\"\nelif [ -d \".agents\u002Fskills\u002Fgstack\u002F.git\" ]; then\n  INSTALL_TYPE=\"local-git\"\n  INSTALL_DIR=\".agents\u002Fskills\u002Fgstack\"\nelif [ -d \".claude\u002Fskills\u002Fgstack\" ]; then\n  INSTALL_TYPE=\"vendored\"\n  INSTALL_DIR=\".claude\u002Fskills\u002Fgstack\"\nelif [ -d \"$HOME\u002F.claude\u002Fskills\u002Fgstack\" ]; then\n  INSTALL_TYPE=\"vendored-global\"\n  INSTALL_DIR=\"$HOME\u002F.claude\u002Fskills\u002Fgstack\"\nelif _SRC=$(~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-paths --get GSTACK_STATE_ROOT 2>\u002Fdev\u002Fnull) && _SRC=$(awk -F '\\t' '$(1) == \"claude\" && $(6) != \"-\" { print $(6); exit }' \"$_SRC\u002Finstalls.tsv\" 2>\u002Fdev\u002Fnull) && [ -d \"$_SRC\u002F.git\" ]; then\n  # The install registry names the checkout setup activated for this host (e.g. a ~\u002Fgstack clone).\n  INSTALL_TYPE=\"global-git\"\n  INSTALL_DIR=\"$_SRC\"\nelse\n  echo \"ERROR: gstack not found\"\n  exit 1\nfi\nINSTALL_DIR=$(cd -- \"$INSTALL_DIR\" && pwd -P) || { echo \"ERROR: cannot enter the gstack install directory\" >&2; exit 1; }\necho \"Install type: $INSTALL_TYPE at $INSTALL_DIR\"\n```\n\nThe install type and the absolute directory printed above are used in all subsequent steps.\nCarry `INSTALL_TYPE`, `INSTALL_DIR`, `OLD_VERSION`, and later `UPSTREAM_REF` and `NEW_VERSION` forward explicitly: tool calls may run in fresh shells, so start each later block by assigning them from the captured output (for example `INSTALL_DIR=\u002Fabs\u002Fpath\u002Fprinted\u002Fabove`). Do not rely on a prior call's working directory or shell variables. Every block that changes files first checks `INSTALL_DIR` and stops before any git command when it is unset, empty, missing, unreadable, or not gstack's own checkout.\n\n### Step 3: Save old version\n\nUse the install directory from Step 2's output below:\n\n```bash\nOLD_VERSION=$(cat \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\u002FVERSION\" 2>\u002Fdev\u002Fnull || echo \"unknown\")\necho \"OLD_VERSION=$OLD_VERSION\"\n```\n\n### Step 4: Upgrade\n\nUse the install type and directory detected in Step 2:\n\n**For git installs** (global-git, local-git):\n\nFast-forward first, through `bin\u002Fgstack-ff-update`, the same code the\nsession-update auto-upgrade uses: local edits that merge cleanly ride over the\nupdate, and an edit that conflicts (or a staged change, or an unmerged file)\nis saved in a rescue ref instead of being left as conflict markers:\n```bash\ncd -- \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\" || exit 1\n{ [ -f VERSION ] && [ -f setup ] && [ -f bin\u002Fgstack-config ] && _p=$(git rev-parse --show-prefix 2>\u002Fdev\u002Fnull) && [ -z \"$_p\" ] && [ \"$(git rev-parse --is-inside-work-tree 2>\u002Fdev\u002Fnull)\" = true ]; } || { echo \"ERROR: $INSTALL_DIR is not a gstack checkout; nothing was changed. Re-run Step 2.\" >&2; exit 1; }\ngit fetch origin\n# Channel ref (docs\u002Freleases.md): origin\u002Fstable for a team install, else origin\u002Fmain\n_CH=$(bash bin\u002Fgstack-update-channel.sh --fetch-ref 2>&1) || _CH=origin\u002Fmain\necho \"$_CH\"; UPSTREAM_REF=$(printf '%s\\n' \"$_CH\" | tail -1)\necho \"UPSTREAM_REF=${UPSTREAM_REF:=origin\u002Fmain}\"\n# Same pre-advance check as the auto-updater: the incoming release's Bun floor,\n# judged by bash whatever this tool shell is (zsh does not word-split).\nHOLD=$(UPSTREAM_REF=\"$UPSTREAM_REF\" bash -c '. bin\u002Fgstack-bun-version.sh && gstack_bun_incoming_hold . \"$UPSTREAM_REF\"' 2>\u002Fdev\u002Fnull) && { 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; }\nPRE_UPGRADE_COMMIT=$(git rev-parse HEAD)\necho \"PRE_UPGRADE_COMMIT=$PRE_UPGRADE_COMMIT\"\n# Past the release (by ancestry, or by VERSION in a depth-1 clone): wait for stable; nothing moves back.\nif [ \"$UPSTREAM_REF\" = origin\u002Fstable ] && [ \"$(git rev-parse origin\u002Fstable)\" != \"$PRE_UPGRADE_COMMIT\" ] && ! git merge-base --is-ancestor HEAD origin\u002Fstable 2>\u002Fdev\u002Fnull \\\n  && { git merge-base --is-ancestor origin\u002Fstable HEAD 2>\u002Fdev\u002Fnull || [ \"$(printf '%s\\n%s\\n' \"$(git show origin\u002Fstable:VERSION)\" \"$(cat VERSION)\" | sort -V | tail -1)\" = \"$(cat VERSION)\" ]; }; then echo \"AHEAD_OF_STABLE\"; exit 0; fi\nFF=$(bash bin\u002Fgstack-ff-update . \"$UPSTREAM_REF\"); FF_RC=$?\necho \"$FF\"\ncase \"$FF_RC\" in\n  0) if .\u002Fsetup --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 ;;\n  4) echo \"FF_REFUSED\" ;;\n  *) echo \"FF_STOPPED: $FF\" >&2; exit 1 ;;\nesac\n```\n\nSetup's `--refresh-registered` refreshes every install this checkout\nregistered (each host, global and this project's), prints the source first,\nand ends with an `Upgrade summary` of one row per install. Relay those rows\nas printed. A `failed` row means that host kept its previous install; give\nits retry command and never say every host was refreshed. A `skipped` row\nbelongs to another checkout or project; pass on its command, do not run it.\n\nIf the output ends with `FF_OK`, the upgrade is done — skip the fallback\nbelow entirely. When the line before it reads\n`rescued \u003Cold> \u003Cnew> refs\u002Fgstack\u002Frescue\u002F\u003Coid> unmerged=\u003C0|1>`, tell the user\ntheir conflicting local edits were saved there and relay the `Inspect`,\n`Export` and `Delete` commands the script printed (export into a scratch\ncheckout with `git apply -3`, never `stash apply` in the install;\n`unmerged=1` means the rescue needs manual resolution).\nOn `FF_STOPPED`, STOP and relay the reason: nothing was changed.\nOn `AHEAD_OF_STABLE`, STOP: the checkout is already past the latest stable release, so nothing changes until the next cut (`gstack-config set update_channel main` follows every merge). On a `fetching origin\u002Fstable failed` line above, STOP and relay it; nothing was changed.\n`ff-busy` means another gstack update is running (retry in a minute);\n`autostash-unrecoverable` names what is in the way (an untracked file the\nrelease adds, or another git command); `index-unreadable` means the checkout's\nindex is damaged.\nOn `BUN_TOO_OLD`, STOP: tell the user to run `bun upgrade`, then \u002Fgstack-upgrade again.\nOn `BUN_VERSION_UNREADABLE`, STOP: `bun --version` did not print a version gstack can read, so `bun upgrade` may not run either; tell the user to reinstall Bun from https:\u002F\u002Fbun.sh (`curl -fsSL https:\u002F\u002Fbun.sh\u002Finstall | bash` in Git Bash or a macOS\u002FLinux terminal), check that `bun --version` prints a version such as 1.4.2, then run \u002Fgstack-upgrade again (docs\u002Ftroubleshooting.md#bun-version-unreadable).\nOn `SETUP_FAILED`, STOP; keep user changes and report the recovery commit. There is no `.bak` on the git path. Do not enter the divergence fallback merely because setup failed. Enter it only on `FF_REFUSED` (local commits or a diverged history); network\u002Fauth failures stop for repair, not reset.\n\n**Fallback (ff-only refused — local commits or divergence).** `git reset\n--hard` DESTROYS things: a clean tree with unpushed local commits still loses\nthose commits. Gate it:\n\n1. Run `git status --porcelain` and `git rev-list \"${UPSTREAM_REF:?}\"..HEAD --oneline`\n   in `$INSTALL_DIR`.\n2. If BOTH are empty, the reset is provably safe — run the fallback block\n   below without asking.\n3. Otherwise ask via AskUserQuestion (one-way door — destructive), listing\n   exactly what will be discarded: each dirty file and each unpushed commit\n   by hash + subject. Options: **A)** Discard them and upgrade (reset) —\n   requires the explicit letter; **B)** Abort the upgrade so the user can\n   rescue their work first (recommended when local commits exist). Never\n   proceed on a vague reply.\n\n```bash\ncd -- \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\" || exit 1\n{ [ -f VERSION ] && [ -f setup ] && [ -f bin\u002Fgstack-config ] && _p=$(git rev-parse --show-prefix 2>\u002Fdev\u002Fnull) && [ -z \"$_p\" ] && [ \"$(git rev-parse --is-inside-work-tree 2>\u002Fdev\u002Fnull)\" = true ]; } || { echo \"ERROR: $INSTALL_DIR is not a gstack checkout; nothing was changed. Re-run Step 2.\" >&2; exit 1; }\n# Regenerable render-footprint dirt (setup re-renders it) never goes into the stash.\ngit checkout -- 'SKILL.md' '*\u002FSKILL.md' '*\u002Fsections\u002F*.md' 2>\u002Fdev\u002Fnull || true\nSTASH_OUTPUT=$(git stash 2>&1)\ngit reset --hard \"${UPSTREAM_REF:?UPSTREAM_REF is not set: substitute the value Step 4 printed}\"\n.\u002Fsetup --refresh-registered\n```\nIf `$STASH_OUTPUT` contains \"Saved working directory\", warn the user: \"Note: local changes were stashed (any modified generated SKILL.md\u002Fsections files were discarded first — they regenerate on setup). Run `git stash pop` in the skill directory to restore your own changes.\"\n\n**For vendored installs** (vendored, vendored-global):\n```bash\n(cd -- \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\" && [ -f VERSION ] && [ -f setup ] && [ -f bin\u002Fgstack-config ]) || { echo \"ERROR: INSTALL_DIR=${INSTALL_DIR:-} is not a gstack install; nothing was changed. Re-run Step 2.\" >&2; exit 1; }\n# A stale .bak from a previously crashed upgrade would make the mv below NEST\n# the live install inside it and the failure-restore arm would \"restore\" the\n# stale backup. It may also be the only good copy from that crashed run —\n# abort and let the human inspect, never delete it silently.\n[ -e \"$INSTALL_DIR.bak\" ] && { echo \"ERROR: stale backup exists at $INSTALL_DIR.bak (from a previous failed upgrade?) — inspect it, salvage\u002Fremove it, then re-run.\" >&2; exit 1; }\nTMP_DIR=$(mktemp -d \"${TMPDIR:-\u002Ftmp}\u002Fgstack-upgrade.XXXXXX\") || { echo \"ERROR: mktemp failed — aborting upgrade (install untouched).\" >&2; exit 1; }\ngit clone --depth 1 https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack.git \"$TMP_DIR\u002Fgstack\" || { echo \"ERROR: clone failed — aborting upgrade (install untouched).\" >&2; rm -rf \"$TMP_DIR\"; exit 1; }\nmv \"$INSTALL_DIR\" \"$INSTALL_DIR.bak\" || { rm -rf \"$TMP_DIR\"; exit 1; }\nif mv \"$TMP_DIR\u002Fgstack\" \"$INSTALL_DIR\"; then\n  if (cd \"$INSTALL_DIR\" && .\u002Fsetup --refresh-registered); then\n    rm -rf \"$INSTALL_DIR.bak\" \"$TMP_DIR\"\n  else\n    rm -rf \"$INSTALL_DIR\"\n    mv \"$INSTALL_DIR.bak\" \"$INSTALL_DIR\" || { echo \"ERROR: restore failed; backup retained.\" >&2; exit 1; }\n    rm -rf \"$TMP_DIR\"\n    echo \"ERROR: setup failed; previous install restored.\" >&2\n    exit 1\n  fi\nelse\n  mv \"$INSTALL_DIR.bak\" \"$INSTALL_DIR\"\n  echo \"ERROR: swap failed — previous install restored; upgrade aborted.\" >&2\n  rm -rf \"$TMP_DIR\"\n  exit 1\nfi\n```\n\n### Step 4.5: Handle local vendored copy\n\nUse the install directory from Step 2. Check if there's also a local vendored copy, and whether team mode is active:\n\n```bash\n_RESOLVED_PRIMARY=$(cd -- \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\" && pwd -P) || exit 1\n_ROOT=$(git rev-parse --show-toplevel 2>\u002Fdev\u002Fnull)\nLOCAL_GSTACK=\"\"\nif [ -n \"$_ROOT\" ] && [ -d \"$_ROOT\u002F.claude\u002Fskills\u002Fgstack\" ]; then\n  _RESOLVED_LOCAL=$(cd \"$_ROOT\u002F.claude\u002Fskills\u002Fgstack\" && pwd -P)\n  if [ \"$_RESOLVED_LOCAL\" != \"$_RESOLVED_PRIMARY\" ]; then\n    LOCAL_GSTACK=\"$_ROOT\u002F.claude\u002Fskills\u002Fgstack\"\n  fi\nfi\n_TEAM_MODE=$(~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-config get team_mode 2>\u002Fdev\u002Fnull || echo \"false\")\necho \"LOCAL_GSTACK=$LOCAL_GSTACK\"\necho \"TEAM_MODE=$_TEAM_MODE\"\n```\n\n**If `LOCAL_GSTACK` is non-empty AND `TEAM_MODE` is `true`:** Remove the vendored copy. Team mode uses the global install as the single source of truth.\n\n```bash\n(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\u002Fgstack-config ]) || { echo \"ERROR: LOCAL_GSTACK=${LOCAL_GSTACK:-} is not a gstack copy; nothing was changed.\" >&2; exit 1; }\n_ROOT=${LOCAL_GSTACK%\u002F.claude\u002Fskills\u002Fgstack}\n{ [ \"$_ROOT\" != \"$LOCAL_GSTACK\" ] && cd -- \"${_ROOT:?}\" && _p=$(git rev-parse --show-prefix 2>\u002Fdev\u002Fnull) && [ -z \"$_p\" ] && [ \"$(git rev-parse --is-inside-work-tree 2>\u002Fdev\u002Fnull)\" = true ]; } || { echo \"ERROR: $LOCAL_GSTACK is not the vendored copy at the root of a git repository; nothing was changed.\" >&2; exit 1; }\ngit rm -r --cached .claude\u002Fskills\u002Fgstack\u002F 2>\u002Fdev\u002Fnull || true\nif ! grep -qF '.claude\u002Fskills\u002Fgstack\u002F' .gitignore 2>\u002Fdev\u002Fnull; then\n  echo '.claude\u002Fskills\u002Fgstack\u002F' >> .gitignore\nfi\nrm -rf \"$LOCAL_GSTACK\"\n```\nTell user: \"Removed vendored copy at `$LOCAL_GSTACK` (team mode active — global install is the source of truth). Commit the `.gitignore` change when ready.\"\n\n**If `LOCAL_GSTACK` is non-empty AND `TEAM_MODE` is NOT `true`:** Update it by copying from the freshly-upgraded primary install (same approach as README vendored install):\n```bash\n(cd -- \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\" && [ -f VERSION ] && [ -f setup ] && [ -f bin\u002Fgstack-config ]) || { echo \"ERROR: INSTALL_DIR=${INSTALL_DIR:-} is not a gstack install; nothing was changed.\" >&2; exit 1; }\n(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\u002Fgstack-config ]) || { echo \"ERROR: LOCAL_GSTACK=${LOCAL_GSTACK:-} is not a gstack copy; nothing was changed.\" >&2; exit 1; }\n[ -e \"$LOCAL_GSTACK.bak\" ] && { echo \"ERROR: stale vendored backup; inspect it before retrying.\" >&2; exit 1; }\nmv \"$LOCAL_GSTACK\" \"$LOCAL_GSTACK.bak\" || exit 1\nif cp -Rf \"$INSTALL_DIR\" \"$LOCAL_GSTACK\" && rm -rf \"$LOCAL_GSTACK\u002F.git\" && (cd \"$LOCAL_GSTACK\" && .\u002Fsetup --refresh-registered); then\n  rm -rf \"$LOCAL_GSTACK.bak\"\n  echo \"LOCAL_SYNC_OK\"\nelse\n  rm -rf \"$LOCAL_GSTACK\"\n  mv \"$LOCAL_GSTACK.bak\" \"$LOCAL_GSTACK\" || { echo \"ERROR: restore failed; backup retained.\" >&2; exit 1; }\n  echo \"ERROR: sync failed; previous vendored copy restored.\" >&2\n  exit 1\nfi\n```\nOnly on `LOCAL_SYNC_OK`, tell user: \"Also updated vendored copy at `$LOCAL_GSTACK` — commit `.claude\u002Fskills\u002Fgstack\u002F` when you're ready.\" Otherwise stop and report the recovery outcome; do not continue migrations or announce success.\n\n### Step 4.75: Run version migrations\n\nAfter `.\u002Fsetup`, run the migrations in `gstack-upgrade\u002Fmigrations\u002F` (idempotent\n`v{VERSION}.sh` scripts; CONTRIBUTING.md says how to add one) newer than the old\nversion: they fix state `.\u002Fsetup` alone cannot (stale config, orphaned files).\n\n```bash\nMIGRATIONS_DIR=\"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\u002Fgstack-upgrade\u002Fmigrations\"\n: \"${OLD_VERSION:?OLD_VERSION is not set: substitute the value Step 3 printed}\"\nif [ -d \"$MIGRATIONS_DIR\" ]; then\n  for migration in $(find \"$MIGRATIONS_DIR\" -maxdepth 1 -name 'v*.sh' -type f 2>\u002Fdev\u002Fnull | sort -V); do\n    m_ver=\"$(basename \"$migration\" .sh | sed 's\u002F^v\u002F\u002F')\"\n    if [ \"$OLD_VERSION\" != \"unknown\" ] && [ \"$(printf '%s\\n%s' \"$OLD_VERSION\" \"$m_ver\" | sort -V | head -1)\" = \"$OLD_VERSION\" ] && [ \"$OLD_VERSION\" != \"$m_ver\" ]; then\n      echo \"Running migration $m_ver...\"\n      # Without GSTACK_INSTALL_DIR a migration that cleans the install assumes ~\u002F.claude\u002Fskills\u002Fgstack.\n      GSTACK_INSTALL_DIR=\"$INSTALL_DIR\" bash \"$migration\" || echo \"  Warning: migration $m_ver had errors (non-fatal)\"\n    fi\n  done\nfi\n```\n\n### Step 4.8: Stop any stale daemon (unconditional)\n\nA browse daemon started before the upgrade keeps running the old binary (the\nreset and `.\u002Fsetup` do not stop it), so always run this step with the install\ndirectory from Step 2.\n\n```bash\nINSTALL_DIR_PLACEHOLDER=\"\u003Cinstall dir from Step 2>\"\nNEW_HASH=$(cat \"$INSTALL_DIR_PLACEHOLDER\u002Fbrowse\u002Fdist\u002F.version\" 2>\u002Fdev\u002Fnull || echo \"\")\n_STATE_FILE=\"${BROWSE_STATE_FILE:-$(git rev-parse --show-toplevel 2>\u002Fdev\u002Fnull || pwd)\u002F.gstack\u002Fbrowse.json}\"\nif [ -z \"$NEW_HASH\" ] || [ ! -f \"$_STATE_FILE\" ]; then\n  echo \"DAEMON_CHECK=none (no state file or no fresh build hash)\"\nelse\n  DAEMON_PID=$(sed -n 's\u002F.*\"pid\"[[:space:]]*:[[:space:]]*\\([0-9][0-9]*\\).*\u002F\\1\u002Fp' \"$_STATE_FILE\" | head -1)\n  DAEMON_PORT=$(sed -n 's\u002F.*\"port\"[[:space:]]*:[[:space:]]*\\([0-9][0-9]*\\).*\u002F\\1\u002Fp' \"$_STATE_FILE\" | head -1)\n  OLD_HASH=$(sed -n 's\u002F.*\"binaryVersion\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*\u002F\\1\u002Fp' \"$_STATE_FILE\" | head -1)\n  if [ -z \"$DAEMON_PID\" ] || ! kill -0 \"$DAEMON_PID\" 2>\u002Fdev\u002Fnull; then\n    echo \"DAEMON_CHECK=dead (no live daemon to stop)\"\n  elif [ \"$OLD_HASH\" = \"$NEW_HASH\" ]; then\n    echo \"DAEMON_CHECK=current (daemon already runs the new binary)\"\n  elif curl -fsS --max-time 2 \"http:\u002F\u002F127.0.0.1:$DAEMON_PORT\u002Fhealth\" 2>\u002Fdev\u002Fnull | grep -q '\"status\":\"healthy\"'; then\n    echo \"DAEMON_CHECK=stale-responsive pid=$DAEMON_PID hash=${OLD_HASH:-unknown} -> $NEW_HASH\"\n    \"$INSTALL_DIR_PLACEHOLDER\u002Fbrowse\u002Fdist\u002Fbrowse\" stop && echo \"DAEMON_STOPPED=yes\"\n  else\n    echo \"DAEMON_CHECK=stale-busy pid=$DAEMON_PID hash=${OLD_HASH:-unknown} -> $NEW_HASH\"\n  fi\nfi\n```\n\nSubstitute the install directory from Step 2, then act on `DAEMON_CHECK`:\n\n1. **`stale-responsive` + `DAEMON_STOPPED=yes`:** Tell the user \"Stopped the\n   old browse daemon (binary {OLD_HASH} → {NEW_HASH}). The next browse command\n   starts a fresh daemon on the new binary.\"\n2. **`stale-busy`:** The daemon runs the old binary but is mid-work — DEFER to\n   it, never kill a busy daemon during upgrade. Tell the user: \"A browse daemon\n   is still running the pre-upgrade binary ({OLD_HASH} → {NEW_HASH}) but is\n   busy right now. When it finishes, stop it with `browse stop` — or force it\n   immediately with `browse --force-restart stop` (loses that session's\n   tabs\u002Fcookies\u002Flogins).\"\n3. **`none` \u002F `dead` \u002F `current`:** say nothing.\n\n### Step 5: Write marker + clear cache\n\n```bash\nGSTACK_STATE_ROOT=$(~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-paths --get GSTACK_STATE_ROOT); : \"${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with .\u002Fsetup or \u002Fgstack-upgrade}\"\nmkdir -p \"$GSTACK_STATE_ROOT\"\necho \"$OLD_VERSION\" > \"$GSTACK_STATE_ROOT\"\u002Fjust-upgraded-from\nrm -f \"$GSTACK_STATE_ROOT\"\u002Flast-update-check\nrm -f \"$GSTACK_STATE_ROOT\"\u002Fupdate-snoozed\n```\n\n### Step 6: Show What's New\n\nRead `$INSTALL_DIR\u002FCHANGELOG.md`. Find all version entries between the old version and the new version. Summarize as 5-7 bullets grouped by theme, user-facing changes first.\n\nFour fields: summarize `git -C \"$INSTALL_DIR\" log --oneline \u003CPRE_UPGRADE_COMMIT>..HEAD` (≤7 bullets) as \"gstack v{old}, main at {sha}\".\n\nFormat:\n```\ngstack v{new} — upgraded from v{old}!\n\nWhat's new:\n- [bullet 1]\n- [bullet 2]\n- ...\n\nHappy shipping!\n```\n\n### Step 7: Continue\n\nContinue with the skill the user originally invoked; the upgrade is done.\n\n---\n\n## Standalone usage\n\nWhen invoked directly as `\u002Fgstack-upgrade` (not from a preamble):\n\n1. Force a fresh update check (bypass cache):\n```bash\n~\u002F.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-update-check --force 2>\u002Fdev\u002Fnull || \\\n.claude\u002Fskills\u002Fgstack\u002Fbin\u002Fgstack-update-check --force 2>\u002Fdev\u002Fnull || true\n```\n\n2. If `UPGRADE_AVAILABLE` (three or four fields): follow Steps 2-6 above.\n\n   If `CHECK_FAILED ...`: the remote version could not be read, so the update\n   status is unknown. Show the line (it names the URL that failed) and tell the\n   user the check failed; never report \"already on the latest version\". Stop.\n\n3. If no output (primary is up to date): check for a stale local vendored copy.\n\nRun the Step 2 bash block above to detect the primary install type and directory (`INSTALL_TYPE` and `INSTALL_DIR`). Then run the Step 4.5 detection bash block above to check for a local vendored copy (`LOCAL_GSTACK`) and team mode status (`TEAM_MODE`).\n\n**If `LOCAL_GSTACK` is empty** (no local vendored copy): tell the user \"You're already on the latest version (v{version}).\"\n\n**If `LOCAL_GSTACK` is non-empty AND `TEAM_MODE` is `true`:** Remove the vendored copy using the Step 4.5 team-mode removal bash block above. Tell user: \"Global v{version} is up to date. Removed stale vendored copy (team mode active). Commit the `.gitignore` change when ready.\"\n\n**If `LOCAL_GSTACK` is non-empty AND `TEAM_MODE` is NOT `true`**, compare versions:\n```bash\nPRIMARY_VER=$(cat \"${INSTALL_DIR:?INSTALL_DIR is not set: re-run Step 2 and substitute the printed path}\u002FVERSION\" 2>\u002Fdev\u002Fnull || echo \"unknown\")\nLOCAL_VER=$(cat \"${LOCAL_GSTACK:?LOCAL_GSTACK is not set: re-run the Step 4.5 detection block}\u002FVERSION\" 2>\u002Fdev\u002Fnull || echo \"unknown\")\necho \"PRIMARY=$PRIMARY_VER LOCAL=$LOCAL_VER\"\n```\n\n**If versions differ:** follow the Step 4.5 sync bash block above to update the local copy from the primary. Tell user: \"Global v{PRIMARY_VER} is up to date. Updated local vendored copy from v{LOCAL_VER} → v{PRIMARY_VER}. Commit `.claude\u002Fskills\u002Fgstack\u002F` when you're ready.\"\n\n**If versions match:** tell the user \"You're on the latest version (v{PRIMARY_VER}). Global and local vendored copy are both up to date.\"\n",{"name":5,"version":34,"triggers":35,"description":7,"allowed-tools":39},"1.1.0",[36,37,38],"upgrade gstack","update gstack version","get latest gstack",[23,40,41,42],"Read","Write","AskUserQuestion","main","dd9834d44d2650818cb6d8e17c57f795cb56bf98",20846,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack","https:\u002F\u002Fgithub.com\u002Fgarrytan","https:\u002F\u002Fblog.garrytan.com","Writes software, dreams. ","2026-11-12 03:19:55",{"source":8,"skillName":5,"commands":52,"prompt":62,"zipAvailable":63},[53,56,59],{"tool":54,"command":55},"npx","npx skills add garrytan\u002Fgstack --skill gstack-upgrade",{"tool":57,"command":58},"bunx","bunx skills add garrytan\u002Fgstack --skill gstack-upgrade",{"tool":60,"command":61},"pnpm","pnpm dlx skills add garrytan\u002Fgstack --skill gstack-upgrade","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002FSKILL.md",true,[65,71,75,79,83,87,91,95,99,103,107,111,115,119,123,127,131],{"path":66,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":69,"sourceHtmlUrl":70},"migrations\u002Fv0.15.2.0.sh","SCRIPT","脚本",854,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv0.15.2.0.sh",{"path":72,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":73,"sourceHtmlUrl":74},"migrations\u002Fv0.16.2.0.sh",2773,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv0.16.2.0.sh",{"path":76,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":77,"sourceHtmlUrl":78},"migrations\u002Fv1.0.0.0.sh",2016,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.0.0.0.sh",{"path":80,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":81,"sourceHtmlUrl":82},"migrations\u002Fv1.1.3.0.sh",6020,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.1.3.0.sh",{"path":84,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":85,"sourceHtmlUrl":86},"migrations\u002Fv1.17.0.0.sh",2544,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.17.0.0.sh",{"path":88,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":89,"sourceHtmlUrl":90},"migrations\u002Fv1.27.0.0.sh",19199,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.27.0.0.sh",{"path":92,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":93,"sourceHtmlUrl":94},"migrations\u002Fv1.37.0.0.sh",4703,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.37.0.0.sh",{"path":96,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":97,"sourceHtmlUrl":98},"migrations\u002Fv1.38.1.0.sh",4082,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.38.1.0.sh",{"path":100,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":101,"sourceHtmlUrl":102},"migrations\u002Fv1.40.0.0.sh",5900,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.40.0.0.sh",{"path":104,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":105,"sourceHtmlUrl":106},"migrations\u002Fv1.58.0.0.sh",2857,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.58.0.0.sh",{"path":108,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":109,"sourceHtmlUrl":110},"migrations\u002Fv1.65.0.0.sh",7822,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.65.0.0.sh",{"path":112,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":113,"sourceHtmlUrl":114},"migrations\u002Fv1.67.0.0.sh",2865,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.67.0.0.sh",{"path":116,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":117,"sourceHtmlUrl":118},"migrations\u002Fv1.78.0.0.sh",1531,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.78.0.0.sh",{"path":120,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":121,"sourceHtmlUrl":122},"migrations\u002Fv1.86.0.0.sh",886,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.86.0.0.sh",{"path":124,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":125,"sourceHtmlUrl":126},"migrations\u002Fv1.91.19.0.sh",3395,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.91.19.0.sh",{"path":128,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":129,"sourceHtmlUrl":130},"migrations\u002Fv1.91.65.0.sh",1897,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002Fmigrations\u002Fv1.91.65.0.sh",{"path":132,"resourceType":133,"resourceTypeDesc":134,"sizeBytes":135,"sourceHtmlUrl":136},"SKILL.md.tmpl","OTHER","其他",24294,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fgstack-upgrade\u002FSKILL.md.tmpl",[138,166,186,199,218,232],{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":149,"publishedAt":29},"01M6DK0K9VCQMFF4BKGT2S57V9","write-skill","Write a new agent skill or improve one without bloat: the edit ladder, levels, drift and size checks, full diets. (gstack)","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fwrite-skill\u002FSKILL.md",[144],{"categoryNo":145,"categoryName":146,"taxonomyType":147,"taxonomyTypeDesc":148},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[25,150,151,152,153,154,155,156,157,158,159,160,24,161,20,162,23,163,164,165],"write skill","edit","new","bloat","ladder","add","size","diet","full","agent skill","grep","write a skill","drift","improve","new agent","lesson",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":172,"publishedAt":29},"01M6DK0DMV3K47JV0KXTYGZ0F7","verify","Use when coding: run the checks and tests \u002Fship picks until green. (gstack)","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fverify\u002FSKILL.md",[],[168,173,174,175,176,177,178,160,151,24,179,180,181,20,23,182,183,184,185],"change","it","my","use","coding","ship","run","work","check","tests","green","make","when",{"skillNo":187,"name":188,"nameZh":6,"description":189,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":190,"categories":191,"keywords":192,"publishedAt":29},"01M6DK0A63TY611T7G30913VT6","unfreeze","Clear the freeze boundary set by \u002Ffreeze, allowing edits to all directories again. (gstack)","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Funfreeze\u002FSKILL.md",[],[188,193,24,20,194,195,23,196,197,198,151],"directories","remove","freeze","boundary","clear","SET",{"skillNo":200,"name":201,"nameZh":6,"description":202,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":203,"categories":204,"keywords":205,"publishedAt":29},"01M6DK00ACSHDP8X0JE90FNQQJ","ubicloud","Run a heavy suite, build or eval on an ephemeral 16-vCPU Ubicloud VM: TTL-named VMs, a reaper, quota preflight and a hang watchdog. (gstack)","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Fubicloud\u002FSKILL.md",[],[201,23,206,207,208,20,209,210,211,212,213,158,214,215,24,179,216,217],"vm","quota","eval","ttl","suite","vms","remote","preflight","watchdog","test run","build","test",{"skillNo":219,"name":220,"nameZh":6,"description":221,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":222,"categories":223,"keywords":224,"publishedAt":29},"01M6DJZXTBG6SAZKY1RX8BJG55","beta","Clean fixture tool skill with no forced reads and no mode table.","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Fcontext-bill\u002Ftree-a\u002Fbeta\u002FSKILL.md",[],[220,225,226,227,228,229,230,231],"clean","mode","fixture tool","with","table","tool","fixture",{"skillNo":233,"name":234,"nameZh":6,"description":235,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":236,"categories":237,"keywords":238,"publishedAt":29},"01M6DJZTJ0A0D31G2GH9FD3SE1","alpha","Fixture dispatcher with a mode table and forced-read references.","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgstack\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Fcontext-bill\u002Ftree-a\u002Falpha\u002FSKILL.md",[],[234,226,228,229,239,24,240,241,231,242],"upstream","dispatcher","references","router"]