Skip to content

feat: sync secondmate homes to primary local head#91

Merged
kunchenguid merged 4 commits into
mainfrom
fm/sm-sync-h2
Jun 26, 2026
Merged

feat: sync secondmate homes to primary local head#91
kunchenguid merged 4 commits into
mainfrom
fm/sm-sync-h2

Conversation

@kunchenguid

Copy link
Copy Markdown
Owner

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:

  • Refactored fm-update.sh's existing ff_target into a shared library bin/fm-ff-lib.sh and gave it a base_mode parameter so BOTH sync paths share ONE implementation rather than duplicating: base_mode 'origin' keeps the existing /updatefirstmate behavior (fetch + fast-forward to origin/), and base_mode set to a local commit-ish fast-forwards to that commit with NO fetch (the new local-HEAD sync). The 'origin' sentinel deliberately keeps per-dir default-branch resolution and fetch inside ff_target rather than hoisting them into callers; a commit SHA can never collide with the literal string 'origin'.
  • The sync target is resolved from the primary's default-branch REF (refs/heads/), not HEAD, so even a primary stranded on a feature branch (the worktree tangle) yields the true default tip instead of propagating a stray feature branch to the fleet.
  • Two hook points: fm-spawn fast-forwards a secondmate worktree to the primary's HEAD before launch (covers fresh spawns and recovery-respawns); fm-bootstrap sweeps every LIVE secondmate home (state/*.meta with kind=secondmate) and fast-forwards each.
  • Live-converge ONLY on real updates (explicit captain requirement): the bootstrap sweep emits a parseable NUDGE_SECONDMATES report naming a running secondmate's window ONLY when its fast-forward actually advanced AND changed instruction files (FF_STATUS=updated AND FF_INSTR non-empty). An already-current home, or one whose advance changed no instruction files, is never nudged or disturbed. Scripts do mechanics and report; firstmate does the nudge - the split /updatefirstmate already uses. fm-update's existing secondmate nudge condition (nudge on any advance) is deliberately preserved unchanged; the stricter instruction-only condition is parameterized (nudge_requires_instr) and used only by the bootstrap sweep.
  • Guards mirror fm-update exactly: fast-forward only (never force/merge/stash); skip a dirty, diverged (HEAD not an ancestor of the base), or in-flight (on a feature branch) home, leaving its work untouched. A tracked-files fast-forward never touches the gitignored operational dirs (data/, state/, config/, projects/, .no-mistakes/), so a secondmate's backlog/projects/state are never disturbed.

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

  • Adds a shared fast-forward helper used by /updatefirstmate and secondmate local sync, supporting both origin-based updates and no-fetch local commit targets.
  • Fast-forwards secondmate homes from the primary default-branch ref during bootstrap sweeps and before secondmate launch, while skipping dirty, diverged, or in-flight homes and surfacing skipped-sync diagnostics.
  • Documents secondmate local-HEAD convergence behavior and adds hermetic coverage for sync states, nudge gating, bootstrap sweep behavior, spawn-time sync, and no-fetch guarantees.

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
ok - T1 updated: a behind home fast-forwards to the primary's local HEAD
ok - T2 current: an already-current home is a no-op and reports no instruction change
ok - T3 dirty: an uncommitted home is skipped, its edit preserved
ok - T4 diverged: a home that is not an ancestor of the primary's HEAD is skipped
ok - T5 in-flight: a home on a feature branch is skipped, its work preserved
ok - T6 no fetch: the local-HEAD sync never invokes git fetch
ok - T7 sweep nudges on a real instruction change only, but still fast-forwards
ok - T8 bootstrap sweeps live homes, nudges only the running real-instruction-change secondmate
ok - T9 bootstrap surfaces a skipped dirty live secondmate home
ok - T10 spawn fast-forwards a secondmate worktree to the primary's local HEAD before launch
ok - T11 spawn warns when pre-launch sync is skipped
# all fm-secondmate-sync tests passed
Evidence: Origin update regression test log
ok - T1 main + secondmate fast-forward (single-parent), reread + nudge signalled
ok - T3 reread gates on instruction surface, nudge on advancement
ok - T4 dirty secondmate skipped, local edit preserved
ok - T5 diverged secondmate skipped, local commit preserved
ok - T6 idempotent: a second run is a no-op
ok - T7 registry backstop resolves, dedups meta+registry, excludes the firstmate repo
ok - T9 firstmate off its default branch is skipped, not forced
ok - T10 firstmate detached HEAD is skipped
ok - T11 unsafe secondmate home is not fast-forwarded
# all fm-update tests passed
Evidence: Bootstrap and spawn CLI transcript
BOOTSTRAP LOCAL-HEAD SYNC DEMO
before primary=63a7817 sm-instr=6ad6b31 sm-readme=c9a85e1 sm-current=63a7817 sm-nonlive=6ad6b31
fm-bootstrap output:
NUDGE_SECONDMATES: firstmate:fm-sm-instr
after primary=63a7817 sm-instr=63a7817 sm-readme=63a7817 sm-current=63a7817 sm-nonlive=6ad6b31
fetch log: absent

SPAWN PRE-LAUNCH SYNC DEMO
before primary=f62c65e sm=5dd8e6f
after primary=f62c65e sm=f62c65e expected-sm=f62c65e
fetch log: absent
Evidence: Primary default-ref CLI transcript
PRIMARY DEFAULT REF SYNC DEMO
before primary_HEAD=f3658b2 main_ref=510b18d feature_HEAD=f3658b2 sm=221630b
fm-bootstrap output:
TANGLE: primary checkout on feature branch 'feature/stray' (expected 'main'); the work is safe on that ref - restore the primary with: git -C /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T//fm-default-ref-transcript.ru1dx9/world/main checkout main, then re-validate the branch in a proper worktree
NUDGE_SECONDMATES: firstmate:fm-sm
after sm=510b18d expected_main_ref=510b18d feature_HEAD=f3658b2
result: secondmate followed refs/heads/main, not primary HEAD
fetch log: absent

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 fake git shim will recursively exec itself because the test prepends fakebin to PATH before calling run_ff, so command -v git resolves 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 all sweep_live_secondmate_metas output, 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 with git diff --stat c8988bdb7bea8adc53c45233e7f021d9ebf1a7e9 3cd7f2b775cd6017bf4997029054d48b97390a12, git diff --name-only ..., and focused reads of bin/fm-ff-lib.sh, bin/fm-bootstrap.sh, bin/fm-spawn.sh, bin/fm-update.sh, and tests/fm-secondmate-sync.test.sh.
  • Ran tests/fm-secondmate-sync.test.sh and reran it to /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KW11SPQ4B0J2W9GEM862EH94/fm-secondmate-sync.test.log.
  • Ran tests/fm-update.test.sh and reran it to /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KW11SPQ4B0J2W9GEM862EH94/fm-update.test.log.
  • Ran a manual fixture transcript into secondmate-sync-cli-transcript.txt showing fm-bootstrap.sh fast-forwarding live homes, emitting NUDGE_SECONDMATES: only for the instruction-changing secondmate, leaving a non-live home untouched, and fm-spawn.sh fast-forwarding before launch; both paths showed fetch log: absent.
  • Ran a manual fixture transcript into primary-default-ref-transcript.txt with the primary checkout stranded on feature/stray; fm-bootstrap.sh reported TANGLE, synced the secondmate to refs/heads/main, not feature HEAD, and showed fetch log: absent.
  • Checked git status --short after testing; the worktree remained clean.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

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.
@kunchenguid kunchenguid merged commit 881d362 into main Jun 26, 2026
4 checks passed
@kunchenguid kunchenguid deleted the fm/sm-sync-h2 branch June 26, 2026 05:41
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant