feat: sync secondmate homes to primary local head#91
Merged
Conversation
Make every secondmate home follow the primary firstmate checkout's current default-branch commit via a purely local fast-forward (no origin fetch), so deliberate captain updates to the primary converge the fleet automatically. - Extract fm-update's ff machinery into bin/fm-ff-lib.sh; ff_target gains a base_mode parameter (origin -> origin/<default> with fetch; any commit-ish -> that local commit, no fetch) so both sync paths share one implementation. - fm-spawn: fast-forward a secondmate worktree to the primary's HEAD before launch (covers fresh spawns and recovery-respawns). - fm-bootstrap: sweep every live secondmate home to the primary's HEAD and emit NUDGE_SECONDMATES: only for running secondmates whose instructions actually changed; already-current or no-instruction-change homes are left undisturbed. - Document spawn + bootstrap behavior in AGENTS.md. - Add hermetic tests for the local-HEAD ff helper (updated/current/dirty/ diverged/in-flight), no-fetch guarantee, nudge gating, the bootstrap sweep, and the spawn hook.
leo1oel
added a commit
to leo1oel/nemo
that referenced
this pull request
Jun 26, 2026
Port upstream kunchenguid#91, adapted to the herdr lease-branch homes. Make every secondmate home follow the primary checkout's current default-branch commit via a purely local fast-forward (no origin fetch), so a freshly spawned or recovery-respawned secondmate always runs the primary's AGENTS.md/bin/skills. - bin/fm-ff-lib.sh (new): the one fast-forward implementation, extracted from fm-update.sh. ff_target gains a base_mode parameter (origin -> origin/<default> with fetch; any commit-ish -> that local commit, no fetch) merged with the herdr lease_branch handling, plus primary_head_commit (reads the default-branch ref, so a tangled primary still yields the true default tip, not a stray feature branch). - fm-update.sh: source the lib, drop the now-shared machinery, call ff_target with base_mode=origin. All 12 fm-update tests unchanged. - fm-spawn.sh: before a secondmate launch, ff the home to the primary's local HEAD on its secondmate-<id> lease branch; a dirty/diverged/wrong-branch home warns and launches as-is, never force-moved. - Fix a latent marker leak: a real herdr home carries TWO untracked seed markers (.fm-secondmate-home and .fm-secondmate-home.workspace); dirty_status ignored only the first, so every real home looked dirty and the sync would skip it. Now both are ignored. (Confirmed empirically; the old fm-update tests missed it because their stub homes carry only one marker.) - AGENTS.md sections 7/12 document the spawn-time sync and the shared ff lib. - tests/fm-ff-lib.test.sh (new): local-HEAD ff updated/current/dirty/diverged/ lease-branch, the no-fetch/no-origin guarantee, and the two-marker clean fix. The upstream fm-bootstrap session-start sweep has no analogue here (no fm-bootstrap); the spawn-time sync covers fresh spawns and recovery-respawns, and /updatefirstmate converges live homes.
leo1oel
added a commit
to leo1oel/nemo
that referenced
this pull request
Jun 26, 2026
#7) Port upstream kunchenguid#91, adapted to the herdr lease-branch homes. Make every secondmate home follow the primary checkout's current default-branch commit via a purely local fast-forward (no origin fetch), so a freshly spawned or recovery-respawned secondmate always runs the primary's AGENTS.md/bin/skills. - bin/fm-ff-lib.sh (new): the one fast-forward implementation, extracted from fm-update.sh. ff_target gains a base_mode parameter (origin -> origin/<default> with fetch; any commit-ish -> that local commit, no fetch) merged with the herdr lease_branch handling, plus primary_head_commit (reads the default-branch ref, so a tangled primary still yields the true default tip, not a stray feature branch). - fm-update.sh: source the lib, drop the now-shared machinery, call ff_target with base_mode=origin. All 12 fm-update tests unchanged. - fm-spawn.sh: before a secondmate launch, ff the home to the primary's local HEAD on its secondmate-<id> lease branch; a dirty/diverged/wrong-branch home warns and launches as-is, never force-moved. - Fix a latent marker leak: a real herdr home carries TWO untracked seed markers (.fm-secondmate-home and .fm-secondmate-home.workspace); dirty_status ignored only the first, so every real home looked dirty and the sync would skip it. Now both are ignored. (Confirmed empirically; the old fm-update tests missed it because their stub homes carry only one marker.) - AGENTS.md sections 7/12 document the spawn-time sync and the shared ff lib. - tests/fm-ff-lib.test.sh (new): local-HEAD ff updated/current/dirty/diverged/ lease-branch, the no-fetch/no-origin guarantee, and the two-marker clean fix. The upstream fm-bootstrap session-start sweep has no analogue here (no fm-bootstrap); the spawn-time sync covers fresh spawns and recovery-respawns, and /updatefirstmate converges live homes.
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
Make every secondmate firstmate home always track the PRIMARY firstmate checkout's LOCAL HEAD (its current default-branch commit), so that when the captain deliberately updates the primary via git pull or /updatefirstmate, secondmates converge to the same version automatically. This is explicitly NOT an auto-update from origin: there must be no git fetch anywhere in this sync path - the version followed is simply whatever the primary is currently on, and secondmates follow it via a purely local fast-forward (each home is a worktree of the same repo sharing one object store, so the primary's commit is already present locally).
Deliberate decisions a reviewer reading only the diff would not know:
Hard constraint honored: the hardened supervision backbone (fm-watch.sh, fm-watch-arm.sh, fm-wake-lib.sh, fm-supervise-daemon.sh, fm-tmux-lib.sh, afk skill) is NOT modified. Added hermetic tests (tests/fm-secondmate-sync.test.sh) covering the ff helper states (updated, current/no-nudge, dirty, diverged, in-flight), a no-git-fetch assertion, nudge gating, the bootstrap sweep, and the spawn hook; documented in AGENTS.md (spawn + bootstrap) and the fm-bootstrap.sh header.
What Changed
/updatefirstmateand secondmate local sync, supporting both origin-based updates and no-fetch local commit targets.Risk Assessment
✅ Low: Captain, the change is focused, preserves the guarded fast-forward model, and the new skipped-sync diagnostics match the requested behavior without obvious regressions.
Testing
Baseline diff inspection found the changed behavior isolated to the fast-forward helper, bootstrap/spawn hooks, update refactor, docs, and the new sync suite; the focused sync and update test suites passed, and the CLI transcripts demonstrate the end-user bootstrap/spawn behavior, default-ref targeting, skipped non-live home behavior, and no-fetch constraint with no working-tree residue.
Evidence: Local-HEAD sync test log
Evidence: Origin update regression test log
Evidence: Bootstrap and spawn CLI transcript
Evidence: Primary default-ref CLI transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 3 issues found → auto-fixed (2) ✅
tests/fm-secondmate-sync.test.sh:228- The fakegitshim will recursively exec itself because the test prependsfakebinto PATH before callingrun_ff, socommand -v gitresolves back to this wrapper for every git command. Capture the real git path before creating/prepending the shim and exec that path instead, or this test can hang the behavior-test job.bin/fm-bootstrap.sh:95- Bootstrap discards every status line from the secondmate sync sweep, including dirty/diverged/unsafe skips. That can make startup silent even when a live secondmate failed to converge to the primary version, leaving firstmate with no signal to inspect or recover it. Consider surfacing skipped syncs as a problem line while still suppressing current/no-instruction-change updates.bin/fm-spawn.sh:315- The spawn hook also suppresses all fast-forward output before launching a secondmate. If the home is dirty, diverged, or otherwise cannot advance, the secondmate launches on stale code with no warning even though this path is meant to make fresh/recovery spawns current. Consider reporting skipped syncs before continuing with the unchanged launch.🔧 Fix: Fix secondmate sync git shim
2 warnings still open:
bin/fm-bootstrap.sh:95- The bootstrap sweep discards allsweep_live_secondmate_metasoutput, so dirty, diverged, unsafe, or otherwise skipped secondmate homes produce no startup signal. Surface skipped sync lines while still suppressing current/no-instruction-change noise, or firstmate can silently run with stale secondmates after a primary update.bin/fm-spawn.sh:315- The spawn hook also suppresses every fast-forward status before launching a secondmate. If the home is dirty, diverged, or cannot resolve the target commit, the secondmate launches on stale code with no operator-visible warning. Report skipped syncs before continuing with the unchanged launch.🔧 Fix: Surface skipped secondmate sync diagnostics
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
Inspected the changed surface withgit diff --stat c8988bdb7bea8adc53c45233e7f021d9ebf1a7e9 3cd7f2b775cd6017bf4997029054d48b97390a12,git diff --name-only ..., and focused reads ofbin/fm-ff-lib.sh,bin/fm-bootstrap.sh,bin/fm-spawn.sh,bin/fm-update.sh, andtests/fm-secondmate-sync.test.sh.Rantests/fm-secondmate-sync.test.shand reran it to/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KW11SPQ4B0J2W9GEM862EH94/fm-secondmate-sync.test.log.Rantests/fm-update.test.shand reran it to/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KW11SPQ4B0J2W9GEM862EH94/fm-update.test.log.Ran a manual fixture transcript intosecondmate-sync-cli-transcript.txtshowingfm-bootstrap.shfast-forwarding live homes, emittingNUDGE_SECONDMATES:only for the instruction-changing secondmate, leaving a non-live home untouched, andfm-spawn.shfast-forwarding before launch; both paths showedfetch log: absent.Ran a manual fixture transcript intoprimary-default-ref-transcript.txtwith the primary checkout stranded onfeature/stray;fm-bootstrap.shreportedTANGLE, synced the secondmate torefs/heads/main, not featureHEAD, and showedfetch log: absent.Checkedgit status --shortafter testing; the worktree remained clean.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.