Each morning, diffs the actual commits from the last 24h against the active GSD phase plan (`.planning/**/PLAN.md`) for each project in ~/services/ + ~/linkedin-engine/, and flags work that drifted off-plan or plan items that silently went stale. Surfaces a one-line verdict per project on the tablet brief — so Ethan sees 'linkedin-engine: 3 commits on-plan, 1 drift (commit aafda98 touched 02-13 but plan still says human_needed)' before he opens a single editor.
Create ~/bin/plan-drift-sentinel as a bash+python script: (1) glob `~/{services/*,linkedin-engine,russ-telegram,bys-website}/.planning/**/PLAN.md` to find active phase plans; (2) for each repo, `git log --since=24h --name-only` to get touched files + subjects; (3) pass {plan_excerpt, commit_subjects, touched_files} to a single Haiku call via `claude -p` (batch all projects in one prompt to dodge the ~100s session-startup tax) asking 'for each project, classify as on-plan / drift / stale and give one sentence why'; (4) parse N-line numbered response; (5) append a `## Plan Drift` section to ~/Tablet/morning-brief.md (consumed by existing `morning-tablet-brief`). Cron at `30 6 * * *` writing log to ~/.claude/cache/plan-drift.log. Add a bats test with a fake runner injected via env so it never hits real CLI.
## Summary
**Created:**
- `~/bin/plan-drift-sentinel` (84 lines, executable) — globs `~/{services/*,linkedin-engine,russ-telegram,bys-website}/.planning/**/PLAN.md`, runs `git log --since=24h --name-only` per repo, batches every project into a single `claude -p --model haiku` call to dodge the ~100s session-startup tax, parses N numbered lines back, and rewrites a `## Plan Drift` section in `~/Tablet/morning-brief.md`. Parse mismatch falls back to per-project placeholder verdicts pointing at the log. Runner is injectable via `PLAN_DRIFT_RUNNER` env so tests never hit the real CLI.
- `~/bin/plan-drift-sentinel.test.sh` (4 bats tests, all green) — covers happy-path verdict write, existing-section replacement (no duplicates), no-op when no recent commits, and parse-mismatch fallback. Uses a temp HOME + fake runner shell script.
**Cron:** `30 6 * * * /home/ethan/bin/plan-drift-sentinel` installed additively (idempotent — re-running the installer no-ops if already present). Log: `~/.claude/cache/plan-drift.log`.
**Behavior:** appends to the existing `~/Tablet/morning-brief.md` consumed by `morning-tablet-brief`; the section is regex-replaced on each run rather than appended, so re-runs don't duplicate.
Next-step nothing — it's wired and will start producing verdicts the next morning a watched repo has both a `.planning/**/PLAN.md` and a 24h commit.