Files
Kai 1415037e6a fix(scripts): pi-diff was silent about vendored shared modules
.pi/extensions/_shared/ is installed by deploy-scenario.sh from shared/extensions/
and is not in the scenario's workspace/, so pi-diff listed it as merely
"UNTRACKED in live workspace" -- no comparison at all. The file most likely to
drift, because it is the one shared between scenarios, was the one the diff had
nothing to say about.

Now compared against shared/extensions/ and reported like any other file, with a
missing source called out separately.

Also fixes the counter: the new branch incremented a variable named `changed`
while the summary reads `DIFFS`, so a detected difference printed a diff and then
claimed the installation agreed, and exited 0. Verified by planting drift: exit 1
and "1 difference(s) found", then 0 after redeploying.
2026-08-28 01:12:17 -07:00

137 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# Compare tracked configuration against the live installation.
#
# Run this before every deploy, and after every migration, to prove that the
# repository and the host agree.
#
# Usage:
# scripts/pi-diff.sh # runtime + every scenario
# scripts/pi-diff.sh runtime
# scripts/pi-diff.sh <scenario>
#
# Exit status: 0 when everything matches, 1 when anything differs.
# ---------------------------------------------------------------------------
# shellcheck source=lib/common.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh"
DIFFS=0
diff_runtime() {
head1 "runtime (~/.pi/agent)"
local live="$HOME/.pi/agent"
local tracked="$REPO_ROOT/runtime/agent"
diff_file "$tracked/settings.json" "$live/settings.json" "settings.json" || DIFFS=$((DIFFS + 1))
# models.json is rendered from a template, so compare the redacted forms.
if [ -f "$live/models.json" ]; then
local a b
a="$(mktemp)"; b="$(mktemp)"
python3 - "$tracked/models.json.template" >"$a" <<'PY'
import json, re, sys
print(re.sub(r'"apiKey":\s*".*?"', '"apiKey": "<X>"',
json.dumps(json.load(open(sys.argv[1])), ensure_ascii=False, indent=2, sort_keys=True)))
PY
python3 - "$live/models.json" >"$b" <<'PY'
import json, re, sys
print(re.sub(r'"apiKey":\s*".*?"', '"apiKey": "<X>"',
json.dumps(json.load(open(sys.argv[1])), ensure_ascii=False, indent=2, sort_keys=True)))
PY
if cmp -s "$a" "$b"; then
ok "models.json (key-redacted comparison)"
else
printf '%s models.json (key-redacted comparison)\n' "${C_YELLOW}DIFF${C_OFF}"
diff -u --label repo/models.json.template "$a" --label live/models.json "$b" | sed 's/^/ /'
DIFFS=$((DIFFS + 1))
fi
rm -f "$a" "$b"
else
warn "live ~/.pi/agent/models.json is absent"
DIFFS=$((DIFFS + 1))
fi
local rel
while IFS= read -r rel; do
diff_file "$tracked/$rel" "$live/$rel" "$rel" || DIFFS=$((DIFFS + 1))
done < <(cd "$tracked" && find extensions prompts -type f 2>/dev/null | sort)
}
diff_scenario() {
local name="$1" dir
dir="$(require_scenario "$name")"
local profile="$dir/profile.toml"
[ -f "$profile" ] || die "scenario '$name' has no profile.toml"
local workspace
workspace="$(toml_get "$profile" scenario workspace)"
[ -n "$workspace" ] || die "scenario '$name': [scenario].workspace is not set"
head1 "$name ($workspace)"
if [ ! -d "$dir/workspace" ]; then
warn "no tracked workspace/ for '$name'; nothing to compare"
return
fi
if [ ! -d "$workspace" ]; then
warn "live workspace $workspace does not exist yet"
DIFFS=$((DIFFS + 1))
return
fi
local rel
while IFS= read -r rel; do
diff_file "$dir/workspace/$rel" "$workspace/$rel" "$rel" || DIFFS=$((DIFFS + 1))
done < <(cd "$dir/workspace" && find . -type f -printf '%P\n' | sort)
# Report live files that the repository does not track, so drift is visible in
# both directions. Runtime state and toolchains are expected and are skipped.
local untracked=()
while IFS= read -r rel; do
case "$rel" in
.ccgram-uploads/*|*/__pycache__/*|*/.venv/*|.venv/*|*.pyc|*.bak-*|\
telegram-gateway/*|download.html|.pi/launch.json|*.log) continue ;;
esac
# Vendored shared modules are installed by deploy-scenario.sh and are not in
# the scenario's workspace/, so compare them against shared/extensions/
# instead of reporting them as untracked -- otherwise the one file most
# likely to drift is the one the diff stays silent about.
case "$rel" in
.pi/extensions/_shared/*)
mod="${rel#.pi/extensions/_shared/}"
src="$REPO_ROOT/shared/extensions/$mod"
if [ ! -f "$src" ]; then
untracked+=("$rel (no longer in shared/extensions)")
elif ! diff_file "$src" "$workspace/$rel" "$rel (vendored from shared/extensions)"; then
DIFFS=$((DIFFS + 1))
fi
continue
;;
esac
[ -f "$dir/workspace/$rel" ] || untracked+=("$rel")
done < <(cd "$workspace" && find . -type f -printf '%P\n' | sort)
if [ "${#untracked[@]}" -gt 0 ]; then
printf '%sUNTRACKED in live workspace:%s\n' "$C_DIM" "$C_OFF"
printf ' %s\n' "${untracked[@]}"
fi
}
TARGET="${1:-}"
if [ -z "$TARGET" ]; then
diff_runtime
while IFS= read -r s; do diff_scenario "$s"; done < <(list_scenarios)
elif [ "$TARGET" = "runtime" ]; then
diff_runtime
else
diff_scenario "$TARGET"
fi
printf '\n'
if [ "$DIFFS" -eq 0 ]; then
ok "repository and live installation agree"
else
warn "$DIFFS difference(s) found"
fi
exit $((DIFFS > 0))