From 12039763fa465b5f87d951fa88896d6365175f4d Mon Sep 17 00:00:00 2001 From: Juan Barbat Date: Wed, 21 Oct 2026 09:27:51 -0300 Subject: [PATCH 1/2] fix(gs): count gentle-ai sync config changes in the restart hint; seed files as 0644 The closing message compared binary versions only, so a sync that rewrote managed configs still said nothing changed. seed_file inherited mktemp's 0600. Refs #69 --- bin/gs | 16 ++++++++++++++-- scripts/check-install-symlinks.sh | 1 + scripts/seed-file.sh | 2 ++ scripts/test-gs-sync.sh | 8 +++++++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/bin/gs b/bin/gs index bf88509..5229e72 100755 --- a/bin/gs +++ b/bin/gs @@ -266,7 +266,7 @@ changed_part() { } cmd_sync() { - local repo shell bin="$HOME/.local/share/gentle-ai/main" reg="$HOME/.pi/gentle-ai/dev-binary.json" name + local ai_files="" repo shell bin="$HOME/.local/share/gentle-ai/main" reg="$HOME/.pi/gentle-ai/dev-binary.json" name # Versions before the sync power the closing message: restart only on change. local shell_before ai_before engram_before shell_before=$(shell_version); ai_before=$(ai_version); engram_before=$(engram_version) @@ -353,7 +353,16 @@ cmd_sync() { else local ai_sync=(sync) [ -n "${GS_SYNC_AGENTS:-}" ] && ai_sync+=(--agents "$GS_SYNC_AGENTS") - run_quiet 'gentle-ai sync' "$ai_bin" "${ai_sync[@]}" + # Like run_quiet, but keeps the output: its file count feeds the restart hint. + local ai_out; ai_out=$(mktemp) + if "$ai_bin" "${ai_sync[@]}" >"$ai_out" 2>&1; then + printf ' %-34s ok\n' 'gentle-ai sync' + else + printf ' %-34s FAILED\n' 'gentle-ai sync' + sed 's/^/ /' "$ai_out" + fi + ai_files=$(sed -n 's/^Sync actions executed: \([0-9][0-9]*\) files* changed.*/\1/p' "$ai_out" | head -1) + rm -f "$ai_out" fi # gs sync is the only upgrade path; older homemade scripts drift the hosts apart. for name in "$HOME/.local/bin/gentle-maintainer-upgrade" "$HOME/.local/bin/gentle-sync.sh" \ @@ -385,6 +394,9 @@ cmd_sync() { if is_workshop && grep -qE 'npm:gentle-(pi|engram)|git:github.com/Gentleman-Programming/gentle-(pi|shell)' "$HOME/.pi/agent/settings.json" 2>/dev/null; then printf 'WARN: ~/.pi/agent/settings.json lists a published gentle-pi/gentle-engram again; remove it\n' fi + if [ -n "$ai_files" ] && [ "$ai_files" -gt 0 ]; then + changed="$changed managed configs ($ai_files files)" + fi if [ -n "$changed" ]; then printf 'changed:%s\n Restart Pi to pick them up.\n' "$changed" else diff --git a/scripts/check-install-symlinks.sh b/scripts/check-install-symlinks.sh index 103bf38..b1d9c36 100755 --- a/scripts/check-install-symlinks.sh +++ b/scripts/check-install-symlinks.sh @@ -65,6 +65,7 @@ printf '%s\n' seed > "$seed_src" seed_absent="$tmp_dir/seed/absent.json" seed_file "$seed_src" "$seed_absent" >/dev/null [[ -f "$seed_absent" && ! -L "$seed_absent" && "$(<"$seed_absent")" == seed ]] +[[ "$(stat -f %Lp "$seed_absent" 2>/dev/null || stat -c %a "$seed_absent")" == 644 ]] seed_link="$tmp_dir/seed-link.json" ln -s "$seed_src" "$seed_link" diff --git a/scripts/seed-file.sh b/scripts/seed-file.sh index a57fe32..da47e67 100644 --- a/scripts/seed-file.sh +++ b/scripts/seed-file.sh @@ -27,6 +27,8 @@ seed_file() { local tmp tmp="$(mktemp "$dst.seed.XXXXXX")" cp "$src" "$tmp" + # mktemp creates 0600; configs are world-readable like a plain cp. + chmod 0644 "$tmp" # mv over a symlink replaces the link itself, never its target. mv -f "$tmp" "$dst" echo " ✓ $dst seeded from $src" diff --git a/scripts/test-gs-sync.sh b/scripts/test-gs-sync.sh index 067b58a..2315c15 100644 --- a/scripts/test-gs-sync.sh +++ b/scripts/test-gs-sync.sh @@ -27,7 +27,7 @@ case "\$name \$1" in printf '#!/bin/sh\necho "engram 3.0.0"\n' >"\$GOBIN/engram"; chmod +x "\$GOBIN/engram" ;; esac; done ;; "gentle-ai --version") echo "gentle-ai 4.2.1" ;; - "gentle-ai sync") [ -n "\${FAKE_AI_SYNC_FAIL:-}" ] && { echo "Error: partial sync"; exit 1; }; echo "managed sync executed" ;; + "gentle-ai sync") [ -n "\${FAKE_AI_SYNC_FAIL:-}" ] && { echo "Error: partial sync"; exit 1; }; echo "managed sync executed"; [ -n "\${FAKE_AI_SYNC_CHANGED:-}" ] && echo "Sync actions executed: \$FAKE_AI_SYNC_CHANGED files changed"; true ;; esac exit 0 EOF @@ -51,6 +51,12 @@ engram_line=$(grep -n 'cmd/engram@main' "$log" | cut -d: -f1) sync_line=$(grep -n '^gentle-ai|sync' "$log" | cut -d: -f1) (( engram_line < sync_line )) || fail 'gentle-ai sync ran before the binaries step' "$(cat "$log")" +[[ "$out" == *'nothing changed'* ]] || fail 'no-op sync should not ask for a Pi restart' "$out" + +# 1b. Config changes from gentle-ai sync also ask for a Pi restart. +out=$(run_sync FAKE_AI_SYNC_CHANGED=3) || fail 'gs sync exited non-zero' "$out" +[[ "$out" == *'changed: managed configs (3 files)'*'Restart Pi'* ]] || fail 'config changes not reported' "$out" + # 2. GS_SYNC_AGENTS scopes the runtimes for this host. out=$(run_sync GS_SYNC_AGENTS=claude-code,opencode,pi) || fail 'scoped gs sync exited non-zero' "$out" [[ "$(ai_sync_calls)" == 'gentle-ai|sync --agents claude-code,opencode,pi' ]] || fail 'agents scope not forwarded' "$(cat "$log")" From b7762bb4840812e14c94d476965dfc9c58b604e1 Mon Sep 17 00:00:00 2001 From: Juan Barbat Date: Wed, 21 Oct 2026 09:28:57 -0300 Subject: [PATCH 2/2] test(install): probe GNU stat first; on Linux stat -f means filesystem Refs #69 --- scripts/check-install-symlinks.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check-install-symlinks.sh b/scripts/check-install-symlinks.sh index b1d9c36..365d091 100755 --- a/scripts/check-install-symlinks.sh +++ b/scripts/check-install-symlinks.sh @@ -65,7 +65,7 @@ printf '%s\n' seed > "$seed_src" seed_absent="$tmp_dir/seed/absent.json" seed_file "$seed_src" "$seed_absent" >/dev/null [[ -f "$seed_absent" && ! -L "$seed_absent" && "$(<"$seed_absent")" == seed ]] -[[ "$(stat -f %Lp "$seed_absent" 2>/dev/null || stat -c %a "$seed_absent")" == 644 ]] +[[ "$(stat -c %a "$seed_absent" 2>/dev/null || stat -f %Lp "$seed_absent")" == 644 ]] seed_link="$tmp_dir/seed-link.json" ln -s "$seed_src" "$seed_link"