feat: scenarios for curator/memo-inbox/pi-grok, deploy and backup tooling

Scenarios
- memo-inbox: mirrored by copying; the live directory was not moved or modified
  and the service was not restarted. All four tracked files match byte for byte
  (pi-diff.sh reports SAME). Marked deploy = "mirror" so deploy-scenario.sh
  refuses --apply: applying a mirror would invert the direction of truth and
  could change a service in daily use.
- curator: target configuration, not yet deployed. .pi/SYSTEM.md replaces pi's
  coding-assistant prompt; durable role text is in .pi/APPEND_SYSTEM.md;
  profile.toml is the single source of truth for the launch contract.
- pi-grok: registered only. It is genuinely a coding agent, so the isolation
  baseline does not apply in full.

Corrections to the documentation, found by testing rather than by reading
- AGENTS.override.md does NOT block parent-directory context files; it only
  shadows its own directory. Verified: with an override file in the workspace, a
  marker in /tmp/AGENTS.md still reached the system prompt. The only effective
  switch is --no-context-files, so durable role text must live in
  .pi/APPEND_SYSTEM.md, which is a system-prompt file and unaffected by -nc.
  Verified end state: no coding-assistant framing, no pi-docs block, own
  identity and role text present, no parent pollution, only own skills/tools.
- PI_CODING_AGENT_DIR isolates settings/models/auth/trust/extensions/skills/
  prompts/themes under the agent directory -- stronger than the --no-* flags
  because it also repoints credentials -- but does NOT cover ~/.agents/skills.
  Measured: find-skills, modsearch and summarize still leak. So it complements
  --no-skills rather than replacing it.
- --append-system-prompt accepts a file path, which pi-grok relies on.
- cwd is what anchors .pi discovery: a probe that forgot cwd silently lost
  .pi/SYSTEM.md and kept the coding-assistant persona.

Tooling (all dry-run by default; none of them restarts a service)
- pi-diff.sh: compares tracked config against the live install in both
  directions, with a key-redacted comparison for models.json
- deploy-scenario.sh: installs a workspace and renders profile.toml into
  .pi/launch.json, then checks that every referenced path exists
- deploy-runtime.sh: renders models.json from its template, refusing placeholder
  or missing keys. Verified byte-identical to the live file
- pi-backup.sh / pi-restore.sh: archives outside the repo, sha256 manifest
  verified before any restore, live paths preserved rather than overwritten

Fixed while testing: pi-backup.sh compared the destination against the repo root
literally, so a relative --dest ./backups wrote credential archives into the work
tree. Now canonicalised with realpath; ./backups, an absolute in-repo path and
./docs/../backups are all refused.
This commit is contained in:
Kai
2026-08-26 23:17:12 -07:00
parent 7b5e0b093d
commit 07dd648b5f
20 changed files with 2611 additions and 17 deletions
+120
View File
@@ -0,0 +1,120 @@
#!/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
[ -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))