feat(bootstrap): detect primary checkout drift from its own origin#5
Merged
Conversation
…cture.md, configuration.md, AGENTS.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Add a session-start bootstrap diagnostic for drift between the primary Firstmate checkout's checked-out default branch at FM_ROOT and its own origin/default branch, which existing project fleet sync and upstream-template/fork checks do not cover. The check must be detect-only: never fast-forward, checkout, merge, stash, or discard primary work; it should fetch origin under a bounded timeout, silently skip no-origin, fetch-failure, timeout, detached, and off-default states, and avoid duplicating TANGLE. On a matching branch it stays silent; on ahead-only, behind-only, or diverged history it emits a distinct SELF_DRIFT line with both commit counts, using rev-list and merge-base logic. Keep it stateless, run it in both locked and lock-refused detect-only bootstrap paths, add focused tests for every requested state including slow origin, and document proportional remediation through bootstrap-diagnostics and AGENTS.md: /updatefirstmate only for a clean fast-forward, otherwise a manual preserve-and-merge crewmate task.
What Changed
self_drift_checktobin/fm-bootstrap.sh: a detect-only diagnostic that fetchesoriginunder a bounded, configurable timeout (FM_SELF_DRIFT_BOOTSTRAP_TIMEOUT, default 10s) and compares the primary checkout's default branch againstorigin/<default>usingrev-list/merge-base, emitting aSELF_DRIFT: ... ahead, ... behind ... (ahead|behind|diverged)line when they differ. It never fetches beyond the bound, never checks out/merges/fast-forwards/stashes, and silently no-ops on no-origin, non-default/detached HEAD, fetch failure, and timeout — running only in the non-TANGLE branch of the bootstrap flow so it doesn't duplicate TANGLE's off-default detection.tests/fm-self-drift.test.shcovering matching, ahead-only, behind-only, diverged, no-origin, off-default, detached-HEAD, timeout (slow origin), and fetch-failure states.SELF_DRIFTdiagnostic and its proportional remediation (/updatefirstmatefor a clean fast-forward, otherwise a manual preserve-and-merge crewmate task) in.agents/skills/bootstrap-diagnostics/SKILL.md,AGENTS.md,docs/architecture.md, anddocs/configuration.md.Risk Assessment
✅ Low: This round only adds two well-targeted tests (detached HEAD, plain fetch failure) that correctly exercise the code paths they claim to cover, fully resolving the prior round's test-coverage gap with no source changes and no new issues.
Testing
Baseline full e2e suite (tests/*.test.sh under tmux) already passed. I additionally ran the new tests/fm-self-drift.test.sh directly (9/9 pass, covering matching/ahead/behind/diverged/no-origin/off-default/detached/fetch-failure/slow-origin), re-ran tests/fm-bootstrap.test.sh and tests/fm-captain-translation-contract.test.sh to confirm no regression (21/21 and 7/7 checks pass, identical to base-commit behavior), and captured a manual CLI transcript exercising the real bin/fm-bootstrap.sh against live git fixtures to visibly demonstrate the exact SELF_DRIFT/TANGLE output an end user sees for each drift state. All checks passed; no findings.
Evidence: CLI transcript of bin/fm-bootstrap.sh SELF_DRIFT diagnostic across matching, ahead-only, behind-only, diverged, and off-default(TANGLE) states
=== Case 1: primary matches origin -> bootstrap stays silent === (silent, as expected) === Case 2: primary has one unpushed local commit -> ahead-only, quantified === SELF_DRIFT: primary checkout default branch 'main' is 1 ahead, 0 behind origin/main (ahead) - needs attention === Case 3: origin gains a commit via writer push -> behind-only, quantified === SELF_DRIFT: primary checkout default branch 'main' is 0 ahead, 1 behind origin/main (behind) - needs attention === Case 4: primary also behind after writer push -> diverged === SELF_DRIFT: primary checkout default branch 'main' is 1 ahead, 1 behind origin/main (diverged) - needs attention === Case 5: primary stranded on a feature branch -> TANGLE only, no duplicate SELF_DRIFT === TANGLE: primary checkout on feature branch 'fm/feature' (expected 'main'); the work is safe on that ref - read-only session must leave restore work to the session holding the fleet lockPipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed ✅
tests/fm-self-drift.test.sh:1- The user intent requires: "add focused tests for every requested state including slow origin," and the requested silent-skip states are explicitly enumerated as "no-origin, fetch-failure, timeout, detached, and off-default states." tests/fm-self-drift.test.sh covers matching/ahead/behind/diverged, no-origin, off-default, and timeout (slow origin), but has no test for a detached-HEAD primary (self_drift_check's[ "$current" = "$default" ]skip at bin/fm-bootstrap.sh:212, distinct from TANGLE's own detached-HEAD handling) nor for a plain non-timeout fetch failure (thewait "$pid"nonzero-exit path at bin/fm-bootstrap.sh:232-234, as opposed to the timeout/kill path already tested by test_slow_origin_is_bounded_and_silent). Two of the five explicitly-named required states are untested.bin/fm-bootstrap.sh:214- self_drift_check's bounded-background-job timeout logic (bin/fm-bootstrap.sh:214-236: set -m save/restore, background job, jobs -r -p poll loop, kill -TERM on the process group, wait/reap) is a near-verbatim duplicate of the existing pattern in fleet_sync (bin/fm-bootstrap.sh:166-189). Could be factored into a shared helper (e.g.run_bounded(cmd, timeout) -> exit status), but the two call sites differ slightly in how they consume output/exit status, so this is a nice-to-have rather than a required cleanup.🔧 Fix: Add self-drift tests for detached HEAD and fetch failure
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"bash tests/fm-self-drift.test.shbash tests/fm-bootstrap.test.shbash tests/fm-captain-translation-contract.test.shManual CLI transcript: ran bin/fm-bootstrap.sh with FM_BOOTSTRAP_DETECT_ONLY=1 against real git fixtures for matching, ahead-only, behind-only, diverged, and off-default(TANGLE) primary states✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.