F2 Phase 0 Reality-Check — Mechanical Defense Against Post-Squash-Merge State Drift (v7.9.1)
The post-squash-merge state-drift pattern repeated 5 times in 4 days (2026-06-01 → 2026-06-04). Each time, state.json::tasks said pending but file changes were already on main. The next Phase 0 then scheduled "new work" that was in fact already done. F2 adds a mandatory Phase 0.1 sub-step to /pm-workflow — cross-checks each pending task against the last 30 days of git log + merged PR titles + Tier 2.2 log events. If 2+ evidence items match, the advisory "this task may already be done" surfaces BEFORE scheduling. Wall- clock under 0.1 seconds. 16 tests pass in 0.15s. Operator decides; never blocking.
What do T1 / T2 / T3 mean?▾
F2 Phase 0 Reality-Check Sub-step
The post-squash-merge state-drift pattern repeated 5 times in 4 days.
F2 is the mechanical defense.
The drift loop
- Feature work ships via squash-merged PR; main has the changes
state.json::tasksstill saypendingbecause the operator merged via web UI (which doesn't update state.json) orgh pr merge(which also doesn't)- Next session opens Phase 0 on the same feature; agent reads state.json and sees pending tasks
- Agent schedules "new work" that is in fact already done
- Operator catches the drift — usually 2-3 phases later — and runs
make close-featureto reconcile
Cost: 3-phase re-plan per drift instance.
What F2 does
Before Phase 0 schedules anything, run:
make phase-0-reality-check FEATURE=<name>The check reads state.json::tasks and cross-checks each pending / in_progress / open task against the last 30 days of:
- Git log subjects — has anyone shipped a matching commit?
- Merged PR titles (FT2 + fitme-story) — has either repo merged a matching PR?
- Tier 2.2 log events in
.claude/logs/<feature>.log.json— has anyone logged an implementation event mentioning the task ID or keywords?
If 2+ distinct evidence items hit a pending task's keywords, the advisory "this task may already be done" surfaces.
Threshold mechanics
- Noise-token filter: 50+ common English words + framework-specific noise ("the", "test", "implement", "framework", "scripts", "case-study", "v7", "v8", etc.) drop out before matching. Keeps "BRANCH_ISOLATION_VIOLATION" or "REPO_ROOT_OVERRIDE" or "T4a" as signal.
- Per-item: ≥2 distinct keywords — single-keyword matches don't count.
- Across-sources: ≥2 total evidence items — combining git + PR + log adds up.
The strict thresholds keep false-positive rate manageable.
Sequencing with F16 + F17
F2 ships same-day as F16 (try-repo harness) and F17 (last_fired_at index). All three are independent v7.9.1 candidates:
- F16 validates that gates ARE wired correctly via subprocess integration testing
- F17 materializes "when did each gate last fire?" so meta-checks stay O(1)
- F2 catches state-drift BEFORE the next Phase 0 schedules duplicate work
F1 (STATE_TASKS_FILESYSTEM_DRIFT advisory, queued) addresses the same pattern at the cycle-time gate level. F2 addresses it at the workflow level.
Shipped via
- PR #618 — script + 16 tests + SKILL.md Phase 0.1 + Makefile target + docs + case study
Where to read more
- Source case study:
docs/case-studies/f2-phase-0-reality-check-case-study.md - Spec:
docs/master-plan/infra-master-plan-2026-05-12.md§3.1 Theme A F2 - Sibling F16 (same day):
f16-try-repo-harness - Sibling F17 (same day):
f17-last-fired-at-index