diff --git a/bin/gstack-brain-sync b/bin/gstack-brain-sync index f8a80da6c..66056e10a 100755 --- a/bin/gstack-brain-sync +++ b/bin/gstack-brain-sync @@ -783,27 +783,23 @@ stage_paths() { # spellings, and a retained local commit can be pushed with an empty queue. # Also records the tree's case-colliding names for skill start. Without # python3 no drain can stage anything, so there is no new tree to check. A -# passing verdict is cached by the commit ids of HEAD and every ref (origin's -# included, the comparison baseline), so the empty-queue drain at every skill -# boundary skips the tree walk until one of them moves. +# passing verdict is cached by HEAD's target and the commit ids of every ref +# (origin's included, the comparison baseline), so the empty-queue drain at +# every skill boundary skips the tree walk until one of them moves. validate_outgoing_paths() { - local out err key cached cache="$GSTACK_HOME/.brain-case-check" + local out key cached cache="$GSTACK_HOME/.brain-case-check" command -v python3 >/dev/null 2>&1 || return 0 - key=$(git -C "$GSTACK_HOME" show-ref --head 2>/dev/null | cksum) + key=$({ head -1 "$GSTACK_HOME/.git/HEAD"; git -C "$GSTACK_HOME" show-ref --head; } 2>/dev/null | cksum) cached=$(head -1 "$cache" 2>/dev/null || true) if [ "${cached%% *}" = "$key" ]; then [ -n "$ST_CASE_NAMES" ] || ST_CASE_NAMES=${cached#* } return 0 fi - err=$(mktemp "${TMPDIR:-/tmp}/brain-sync-outgoing.XXXXXX") || return 0 - if ! out=$(python3 "$SCRIPT_DIR/gstack-brain-sync-index.py" --outgoing "$GSTACK_HOME" 2>"$err"); then + if ! out=$(python3 "$SCRIPT_DIR/gstack-brain-sync-index.py" --outgoing "$GSTACK_HOME"); then ST_CASE_NAMES=${out%$'\r'} write_status "error" "outgoing artifact path validation refused: a local commit adds a path that differs only by letter case from a path on the remote; local commits and queue preserved. Repair: https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#artifact-case-collision" - cat "$err" >&2 - rm -f "$err" return 1 fi - rm -f "$err" out=${out%$'\r'} printf '%s\t%s\n' "$key" "$out" > "$cache" 2>/dev/null || true [ -n "$ST_CASE_NAMES" ] || ST_CASE_NAMES=$out diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 23cb3d1d7..d802e845b 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -658,19 +658,21 @@ unaffected; only the sync holds files. 2. Fold the other spelling's tracked files into the kept one. Read both spellings from git, because a Mac's working tree shows only one of them, and the working files too, because they hold the held, unsynced writes. - This loop folds JSONL logs (concatenated, duplicate lines dropped) and - prints every other file for you to decide: + This loop covers the dropped spelling's tracked files and its untracked + ones (held writes that never synced). It folds JSONL logs (concatenated, + duplicate lines dropped) and prints every other file for you to decide: ```bash cd ~/.gstack keep=projects/MyApp; drop=projects/myapp - git -c core.ignorecase=false ls-files -- "$drop" | while IFS= read -r f; do + { git -c core.ignorecase=false ls-files -- "$drop"; git -c core.ignorecase=false ls-files --others -- "$drop"; } | sort -u | while IFS= read -r f; do rel=${f#"$drop"/} case "$rel" in *.jsonl) ;; *) echo "decide by hand: $f"; continue ;; esac { git show ":$keep/$rel"; git show ":$f"; cat "$keep/$rel" "$f"; } 2>/dev/null | awk '!seen[$0]++' > "$HOME/.gstack-fold.tmp" mkdir -p "$keep/$(dirname "$rel")" && mv "$HOME/.gstack-fold.tmp" "$keep/$rel" git -c core.ignorecase=false add -f -- "$keep/$rel" - git -c core.ignorecase=false rm -q --cached -- "$f" + git -c core.ignorecase=false rm -q --cached --ignore-unmatch -- "$f" + echo "folded: $keep/$rel" done ``` @@ -678,7 +680,7 @@ unaffected; only the sync holds files. `decisions.active.json`), compare `git show ":$keep/"` with `git show ":$drop/"`, write the content to keep to `"$keep/"`, then run the same `git -c core.ignorecase=false add -f -- "$keep/"` - and `git -c core.ignorecase=false rm -q --cached -- "$drop/"`. Never + and `git -c core.ignorecase=false rm -q --cached --ignore-unmatch -- "$drop/"`. Never concatenate a JSON snapshot. Then commit and push: ```bash @@ -686,12 +688,15 @@ unaffected; only the sync holds files. git push origin HEAD ``` - On Linux the two folders are separate on disk: remove the dropped folder - after the commit (`rm -r "$drop"`). On a Mac or Windows they are the same - folder, so do not remove anything. + On Linux the two folders are separate on disk: after the commit, check + that `git -c core.ignorecase=false ls-files --others -- "$drop"` lists + nothing you have not folded or moved, then remove the dropped folder + (`rm -r "$drop"`). On a Mac or Windows they are the same folder, so do + not remove anything. 3. Clear the held records of the dropped spelling and queue the kept files: `gstack-brain-sync --skip-file ` for each held path that - `--status` lists, then `gstack-brain-enqueue ` and + `--status` lists, then `gstack-brain-enqueue ` for each + `folded:` line and each file you moved by hand, and `gstack-brain-sync --once`. 4. On every other machine, let the next skill start pull the repair (or run `git -C ~/.gstack pull`). The line disappears once no tracked path