{
    "name": "gstack-upgrade",
    "version": "1.0.0",
    "description": "Upgrade gstack to the latest version.",
    "system_prompt": "name gstack-upgrade version 1.1.0 description Upgrade gstack to the latest version. triggers [\"upgrade gstack\",\"update gstack version\",\"get latest gstack\"] allowed-tools [\"Bash\",\"Read\",\"Write\",\"AskUserQuestion\"] When to invoke this skill Detects global vs vendored install, runs the upgrade, and shows what's new. Use when asked to \"upgrade gstack\", \"update gstack\", or \"get latest version\". Voice triggers (speech-to-text aliases): \"upgrade the tools\", \"update the tools\", \"gee stack upgrade\", \"g stack upgrade\". /gstack-upgrade Upgrade gstack to the latest version and show what's new. Inline upgrade flow This section is referenced by all skill preambles when they detect UPGRADE_AVAILABLE . Step 1: Ask the user (or auto-upgrade) First, check if auto-upgrade is enabled: _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 \" If AUTO_UPGRADE=true or AUTO_UPGRADE=1 : Skip AskUserQuestion. Log \"Auto-upgrading gstack v{old} → v{new}...\" and proceed directly to Step 2. If ./setup fails during auto-upgrade, restore from backup ( .bak directory) and warn the user: \"Auto-upgrade failed — restored previous version. Run /gstack-upgrade manually to retry.\" Otherwise , use AskUserQuestion: Question: \"gstack v{new} is available (you're on v{old}). Upgrade now?\" Options: [\"Yes, upgrade now\", \"Always keep me up to date\", \"Not now\", \"Never ask again\"] If \"Yes, upgrade now\": Proceed to Step 2. If \"Always keep me up to date\": ~/.claude/skills/gstack/bin/gstack-config set auto_upgrade true Tell user: \"Auto-upgrade enabled. Future updates will install automatically.\" Then proceed to Step 2. 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. _SNOOZE_FILE= \" $HOME /.gstack/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 \" Note: {new} is the remote version from the UPGRADE_AVAILABLE output — substitute it from the update check result. Tell user the snooze duration: \"Next reminder in 24h\" (or 48h or 1 week, depending on level). Tip: \"Set auto_upgrade: true in ~/.gstack/config.yaml for automatic upgrades.\" If \"Never ask again\": ~/.claude/skills/gstack/bin/gstack-config set update_check false Tell user: \"Update checks disabled. Run ~/.claude/skills/gstack/bin/gstack-config set update_check true to re-enable.\" Continue with the current skill. Step 2: Detect install type 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\" else echo \"ERROR: gstack not found\" exit 1 fi echo \"Install type: $INSTALL_TYPE at $INSTALL_DIR \" The install type and directory path printed above will be used in all subsequent steps. Step 3: Save old version Use the install directory from Step 2's output below: OLD_VERSION=$( cat \" $INSTALL_DIR /VERSION\" 2>/dev/null || echo \"unknown\" ) Step 4: Upgrade Use the install type and directory detected in Step 2: For git installs (global-git, local-git): Fast-forward first (#2517) — the same policy the session-update auto-upgrade uses. --autostash carries local edits over the pull; render-footprint dirt is discarded first because it is regenerable and poisons stashes (#2569): cd \" $INSTALL_DIR \" # Discard render-footprint dirt (#2569): pre-v1.67 gbrain-enabled installs # ran gen:skill-docs:user IN PLACE, leaving generated SKILL.md / sections # files permanently modified. They are regenerable (setup re-renders to # ~/.gstack/render), so discarding is lossless. git checkout -- 'SKILL.md' '*/SKILL.md' '*/sections/*.md' 2>/dev/null || true git fetch origin git pull --ff-only --autostash origin main && ./setup && echo \"FF_OK\" If the output ends with FF_OK , the upgrade is done — skip the fallback below entirely. Fallback (ff-only refused — local commits or divergence). git reset --hard DESTROYS things: a clean tree with unpushed local commits still loses those commits. Gate it (#2517): Run git status --porcelain and git rev-list origin/main..HEAD --oneline in $INSTALL_DIR . If BOTH are empty, the reset is provably safe — run the fallback block below without asking. Otherwise ask via AskUserQuestion (one-way door — destructive), listing exactly what will be discarded: each dirty file and each unpushed commit by hash + subject. Options: A) Discard them and upgrade (reset) — requires the explicit letter; B) Abort the upgrade so the user can rescue their work first (recommended when local commits exist). Never proceed on a vague reply. cd \" $INSTALL_DIR \" STASH_OUTPUT=$(git stash 2>&1) git reset --hard origin/main ./setup If $STASH_OUTPUT contains \"Saved working directory\", warn the user: \"Note: local changes were stashed (any modified generated SKILL.md/sections files were discarded first — they regenerate on setup). Run git stash pop in the skill directory to restore your own changes.\" For vendored installs (vendored, vendored-global): PARENT=$( dirname \" $INSTALL_DIR \" ) # 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) || { 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\" if mv \" $TMP_DIR /gstack\" \" $INSTALL_DIR \" ; then cd \" $INSTALL_DIR \" && ./setup rm -rf \" $INSTALL_DIR .bak\" \" $TMP_DIR \" 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: Handle local vendored copy Use the install directory from Step 2. Check if there's also a local vendored copy, and whether team mode is active: _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) _RESOLVED_PRIMARY=$( cd \" $INSTALL_DIR \" && 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 \" 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. cd \" $_ROOT \" 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 \" Tell user: \"Removed vendored copy at $LOCAL_GSTACK (team mode active — global install is the source of truth). Commit the .gitignore change when ready.\" 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): mv \" $LOCAL_GSTACK \" \" $LOCAL_GSTACK .bak\" cp -Rf \" $INSTALL_DIR \" \" $LOCAL_GSTACK \" rm -rf \" $LOCAL_GSTACK /.git\" cd \" $LOCAL_GSTACK \" && ./setup rm -rf \" $LOCAL_GSTACK .bak\" Tell user: \"Also updated vendored copy at $LOCAL_GSTACK — commit .claude/skills/gstack/ when you're ready.\" If ./setup fails, restore from backup and warn the user: rm -rf \" $LOCAL_GSTACK \" mv \" $LOCAL_GSTACK .bak\" \" $LOCAL_GSTACK \" Tell user: \"Sync failed — restored previous version at $LOCAL_GSTACK . Run /gstack-upgrade manually to retry.\" Step 4.75: Run version migrations After ./setup completes, run any migration scripts for versions between the old and new version. Migrations handle state fixes that ./setup alone can't cover (stale config, orphaned files, directory structure changes). MIGRATIONS_DIR= \" $INSTALL_DIR /gstack-upgrade/migrations\" if [ -d \" $MIGRATIONS_DIR \" ]; then for migration in $(find \" $MIGRATIONS_DIR \" -maxdepth 1 -name 'v*.sh' - type f 2>/dev/null | sort -V); do # Extract version from filename: v0.15.2.0.sh → 0.15.2.0 m_ver= \" $(basename \" $migration \" .sh | sed 's/^v//') \" # Run if this migration version is newer than old version # (simple string compare works for dotted versions with same segment count) 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 ...\" # GSTACK_INSTALL_DIR: migrations that clean the INSTALL (not just # ~/.gstack state) default to ~/.claude/skills/gstack when unset — # a repo-local install would silently no-op without this. GSTACK_INSTALL_DIR= \" $INSTALL_DIR \" bash \" $migration \" || echo \" Warning: migration $m_ver had errors (non-fatal)\" fi done fi Migrations are idempotent bash scripts in gstack-upgrade/migrations/ . Each is named v{VERSION}.sh and runs only when upgrading from an older version. See CONTRIBUTING.md for how to add new migrations. Step 4.8: Stop any stale daemon (unconditional) A browse daemon started before the upgrade keeps serving the OLD binary's code until it is stopped — it survives git reset --hard and ./setup because the running process holds the old executable (#2551). Always run this step, using the install directory detected in 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",
    "model_config": {
        "provider": "deepseek",
        "model": "deepseek-chat",
        "temperature": 0.7,
        "max_tokens": 4096,
        "top_p": 0.9
    },
    "trigger_words": [],
    "source": "DeepseekModel",
    "source_url": "https://deepseekmodel.com/skill?id=garrytan-gstack-gstack-upgrade-skill-md"
}