## Why
Cycle 244 of this very session hit the classic long-session git-pull
abort: `git pull --ff-only origin main` rejected because `registry.json`
had local drift. The recovery dance — `git stash push`, pull, `git stash
pop`, possibly resolve — is always the same shape. Mechanizing it
removes the friction and the 30-second decision latency every time.
## What
`scripts/safe-pull.sh` — `git stash push` (named), `git pull --ff-only`,
`git stash pop`, with clear conflict reporting. Behavior:
- Detects tracked + staged drift up front; skips stash when clean.
- Names the stash `auto-safe-pull-<UTC-timestamp>` so it's recognizable
in `git stash list`.
- On clean pop: drops the stash automatically (no orphan entries).
- On pop conflict: leaves the stash AND lists the conflicting files;
emits the exact `git stash pop` command needed to recover.
- On pre-pull failure: leaves the stash (if created) and surfaces the
underlying git error.
Flags: `--remote <name>` `--branch <name>` `--dry-run` `--no-pop`
`--verbose`
Exit codes: 0 success / 1 pop conflict / 2 pull rejected / 3 usage or
env error. Stash name is never silently lost — EXIT trap reports it if
non-zero exit fires mid-flight.
## Test
10 bats tests, all passing. Fixture builds a bare origin + two clones;
exercises clean / drift-no-overlap / drift-with-conflict /
diverged-local / dry-run / --no-pop / detached-HEAD / unknown-flag /
unstaged-tracked-edit paths.
Closes-scenario: soc-x8pl#safe-pull
Bounded-context: BC2-Loop
Evidence: scripts/safe-pull.sh
Evidence: tests/scripts/safe-pull.bats
Co-authored-by: Codex <codex@example.invalid>