Files
gstack/bin/gstack-brain-uninstall
T
4e4f2959cd v1.91.126.0 fix(brain-sync): hold case-colliding artifact paths instead of reporting ok (#3220) (#3254)
* fix(brain-sync): verify requested index entries after add

Refuse missing or unrelated staged paths and stale indexed content without consuming the queue or resetting the index.

* fix(brain-sync): refuse new casefold path collisions

Check component prefixes in requested/index and outgoing trees, including retained commits and fetch/merge retries. Preserve historical aliases, local commits, index, and queue on refusal.

* fix(brain-sync): hold case-colliding paths instead of halting the sync (#3220)

Builds on Kkkkkuro's staged-index and casefold checks (#3245, from #3223).
A queued path whose spelling differs only by letter case from another path
in the index or the batch is held before staging: it stays queued, every
other file keeps syncing, and the status is "collision" rather than "ok".
An established spelling keeps syncing; only the new alias waits.

Skill start prints one ARTIFACTS_SYNC attention line naming the colliding
names and the repair anchor (docs/troubleshooting.md#artifact-case-collision);
gstack-doctor warns the same way. The outgoing check is cached by HEAD so
the empty-queue drain at every skill boundary stays near main's cost.
gstack-slug and skill start's slug path are unchanged.

Co-authored-by: Kkkkkuro <180382840+Kkkkkuro@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(brain-sync): close review gaps in the case-collision hold

- Windows: write the held-path list with LF and strip CR from helper output,
  so CRLF cannot let a held path through or blank case_names.
- --publish-removals runs the outgoing case check before it pushes.
- The cached outgoing verdict is keyed by every ref, not HEAD alone, so a
  moved remote baseline is re-checked.
- The repair snippet folds only JSONL logs and lists snapshots and other
  files for a manual decision.

Co-authored-by: Kkkkkuro <180382840+Kkkkkuro@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(brain-sync): second review pass on the case-collision hold

- The repair loop also folds the dropped spelling's untracked (held) files
  and says to check for leftovers before removing the folder on Linux.
- The cached outgoing verdict also keys on HEAD's symbolic target.
- The outgoing check no longer needs a temp file, so a failed mktemp can
  no longer skip it.

Co-authored-by: Kkkkkuro <180382840+Kkkkkuro@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(brain-sync): give the retry-merge fixture a git identity

The retry merge commits with the ambient identity. CI and Ubicloud runners
have none under the test HOME, so the merge failed before the outgoing case
check ran and the drain reported push_failed instead of error.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): unreleased entry for the #3220 case-collision hold

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.126.0 stamp

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: KuroChen <Kkkkkuro@users.noreply.github.com>
Co-authored-by: Kkkkkuro <180382840+Kkkkkuro@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-17 21:17:39 +00:00

170 lines
6.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# gstack-brain-uninstall — clean off-ramp for gstack-brain sync.
#
# Usage:
# gstack-brain-uninstall [--yes] [--delete-remote]
#
# Removes the git layer from ~/.gstack/ and clears sync config. Your local
# gstack memory (learnings, timelines, etc.) is NOT touched — this is an
# uninstall-sync command, not a delete-data command.
#
# Flags:
# --yes Skip the confirmation prompt.
# --delete-remote Also delete the GitHub repo via `gh repo delete`
# (interactive unless --yes is also passed).
#
# What it removes (in ~/.gstack/):
# .git/ — the sync repo's git data
# .gitignore — canonical ignore-all marker
# .gitattributes — merge driver declarations
# .brain-allowlist — sync path list
# .brain-privacy-map.json — sync privacy classifier
# .brain-queue.d/ — pending spool (one file per record)
# .brain-queue.jsonl — legacy pending queue (pre-spool)
# .brain-discover-cursor — discover-new cursor
# .brain-last-push — timestamp marker
# .brain-worktree-last-advance — daily worktree-advance stamp (#2516)
# .brain-skip.txt — user-maintained skip list
# .brain-sync.lock.d/ — lock dir (if present)
# .brain-sync-status.json — health status
# .brain-case-check — cached case-collision verdict for HEAD
# consumers.json — consumer/reader registry
#
# What it clears (via gstack-config):
# artifacts_sync_mode → off
# artifacts_sync_mode_prompted → false (so user re-prompts on re-init)
#
# What it does NOT touch:
# Project data (projects/*, retros/*, developer-profile.json, etc.)
# Consumer tokens in gstack-config (<name>_token keys)
# ~/.gstack-brain-remote.txt in your home directory
# The actual remote git repo (unless --delete-remote)
set -euo pipefail
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
gstack_state_root_select
GSTACK_HOME="$_gstack_sr_root"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONFIG_BIN="$SCRIPT_DIR/gstack-config"
# v1.27.0.0+ canonical name; brain-remote is the legacy fallback during migration.
if [ -f "$HOME/.gstack-artifacts-remote.txt" ]; then
REMOTE_FILE="$HOME/.gstack-artifacts-remote.txt"
else
REMOTE_FILE="$HOME/.gstack-brain-remote.txt"
fi
ASSUME_YES=0
DELETE_REMOTE=0
while [ $# -gt 0 ]; do
case "$1" in
--yes|-y) ASSUME_YES=1; shift ;;
--delete-remote) DELETE_REMOTE=1; shift ;;
--help|-h) sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "Unknown flag: $1" >&2; exit 1 ;;
esac
done
if [ ! -d "$GSTACK_HOME/.git" ]; then
echo "gstack-brain-uninstall: nothing to do (~/.gstack/.git doesn't exist)."
exit 0
fi
REMOTE_URL=$(git -C "$GSTACK_HOME" remote get-url origin 2>/dev/null || echo "")
# ---- confirmation ----
if [ "$ASSUME_YES" != "1" ]; then
cat <<EOF
This will remove gstack-brain sync from this machine:
- Remove ~/.gstack/.git and sync config files
- Clear artifacts_sync_mode in gstack-config
- Remote: $REMOTE_URL will be $([ "$DELETE_REMOTE" = "1" ] && echo "DELETED" || echo "kept")
Local memory (learnings, plans, etc.) is NOT touched.
EOF
printf "Proceed? [y/N] "
read -r reply
case "$reply" in
y|Y|yes|Yes) ;;
*) echo "Aborted."; exit 0 ;;
esac
fi
# ---- delete remote if requested ----
if [ "$DELETE_REMOTE" = "1" ] && [ -n "$REMOTE_URL" ]; then
case "$REMOTE_URL" in
*github.com*|*@github*)
if command -v gh >/dev/null 2>&1; then
# Extract owner/repo from URL.
REPO_SLUG=$(echo "$REMOTE_URL" | sed -E 's#.*[:/]([^/:]+/[^/]+)(\.git)?$#\1#' | sed 's/\.git$//')
if [ -n "$REPO_SLUG" ]; then
echo "Deleting GitHub repo: $REPO_SLUG"
if [ "$ASSUME_YES" = "1" ]; then
gh repo delete "$REPO_SLUG" --yes 2>/dev/null || echo "gh repo delete failed; continuing local uninstall"
else
gh repo delete "$REPO_SLUG" 2>/dev/null || echo "gh repo delete failed; continuing local uninstall"
fi
fi
else
echo "--delete-remote requires the gh CLI. Skipping remote deletion."
fi
;;
*)
echo "--delete-remote only supports github.com remotes. Delete manually if needed: $REMOTE_URL"
;;
esac
fi
# ---- remove sync files ----
echo "Removing git layer and sync config files..."
rm -rf "$GSTACK_HOME/.git" 2>/dev/null || true
rm -f "$GSTACK_HOME/.gitignore" 2>/dev/null || true
rm -f "$GSTACK_HOME/.gitattributes" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-allowlist" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-privacy-map.json" 2>/dev/null || true
rm -rf "$GSTACK_HOME/.brain-queue.d" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-queue.jsonl" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-queue.jsonl.migrating" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-discover-cursor" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-last-push" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-last-pull" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-worktree-last-advance" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-skip.txt" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-sync-status.json" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-case-check" 2>/dev/null || true
rm -rf "$GSTACK_HOME/.brain-sync.lock.d" 2>/dev/null || true
# ---- unregister gbrain federated source + remove worktree (best-effort) ----
# The wireup helper handles: gbrain sources remove, git worktree remove,
# launchd plist (future). All best-effort; uninstall continues on failure.
WIREUP_BIN="$SCRIPT_DIR/gstack-gbrain-source-wireup"
if [ -x "$WIREUP_BIN" ]; then
"$WIREUP_BIN" --uninstall 2>/dev/null || true
fi
# ---- legacy consumers.json (no longer written by gstack-brain-init since v1.17.0.0) ----
rm -f "$GSTACK_HOME/consumers.json" 2>/dev/null || true
# ---- clear config keys ----
"$CONFIG_BIN" set artifacts_sync_mode off >/dev/null 2>&1 || true
"$CONFIG_BIN" set artifacts_sync_mode_prompted false >/dev/null 2>&1 || true
# ---- leave remote-helper file alone unless user asked to delete remote ----
if [ "$DELETE_REMOTE" = "1" ]; then
rm -f "$REMOTE_FILE" 2>/dev/null || true
else
if [ -f "$REMOTE_FILE" ]; then
echo "(keeping $REMOTE_FILE — remove manually if you want to forget the URL)"
fi
fi
cat <<EOF
gstack-brain uninstall complete.
Sync is off. ~/.gstack/ is a plain directory again.
Your project data, learnings, and profile are untouched.
To re-enable sync later: gstack-brain-init
EOF