Skip to content

feat: support persistent secondmates - #37

Merged
kunchenguid merged 41 commits into
mainfrom
fm/sub-firstmate-q3
Jun 22, 2026
Merged

feat: support persistent secondmates#37
kunchenguid merged 41 commits into
mainfrom
fm/sub-firstmate-q3

Conversation

@kunchenguid

@kunchenguid kunchenguid commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Intent

Pure mechanical rename of the subordinate domain-supervisor entity from 'sub-firstmate' to 'secondmate' on the existing PR #37 branch, per the captain's decision (nautical: captain -> first mate -> second mate). This is a RENAME ONLY: no behavior change, no logic change, no test additions/removals beyond renaming. The diff is intentionally balanced (487 insertions / 487 deletions) and all 48 behavior tests pass.

A deliberate, load-bearing DISAMBIGUATION governs every change, so apparent inconsistencies are intentional, not mistakes. ONLY the subordinate entity is renamed: the entity name (sub-firstmate -> secondmate), the routing registry file (firstmates.md -> secondmates.md, including parent-firstmates.md backup and all path references), the seed marker (.fm-sub-firstmate-home -> .fm-secondmate-home), the meta values kind=firstmate -> kind=secondmate and mode=firstmate -> mode=secondmate (plus every KIND/kind/child_kind bash comparison and window_kind check), the spawn/brief flag --firstmate -> --secondmate, the env vars FM_FIRSTMATE_CHARTER/SCOPE -> FM_SECONDMATE_CHARTER/SCOPE, the registry-specific identifiers (FIRSTMATE_REG -> SECONDMATE_REG, firstmate_registry_value -> secondmate_registry_value, remove_firstmate_registry_entry -> remove_secondmate_registry_entry, and the local FIRSTMATE_CHARTER/SCOPE/PROJECTS mirrors), and the test file (tests/fm-sub-firstmate.test.sh -> tests/fm-secondmate.test.sh, its test_secondmate_* functions, scaffold_secondmate_charter helper, and subordinate-lifecycle pass/fail labels like 'secondmate spawn/teardown/launch').

The MAIN firstmate role is intentionally PRESERVED and must NOT be renamed: the firstmate playbook/repo, 'the main firstmate', the generic concept of a 'firstmate home' / 'the active firstmate home' / 'the firstmate repo' (a secondmate's workspace genuinely IS a firstmate home because it runs the firstmate playbook), the generic home-validator/operation function names (validate_firstmate_home_for_spawn/removal, validate_firstmate_operational_dirs(_for_removal), verify_firstmate_home, remove_firstmate_home, cleanup_firstmate_home_children, mark_firstmate_home) and the FIRSTMATE_HOME variable and arg placeholder, the tmux session name 'firstmate' (e.g. window=firstmate:fm-domain), and fm-send/fm-peek's generic 'bare firstmate window' resolution (so test_fm_send_resolves_bare_firstmate_window_from_home_meta keeps its name). The 'child firstmate home' teardown label was preserved exactly as the original author wrote it (the original already used generic 'firstmate home' there, distinct from the top-level 'sub-firstmate home' -> 'secondmate home'); preserving that asymmetry is correct for a rename-only change.

Unrelated PR #30 content (the /afk skill, fm-supervise-daemon.sh, the AGENTS.md Sub-supervisor section, state/.afk and .subsuper-*) was intentionally left untouched - its uses of 'firstmate' mean the main role. CLAUDE.md remains a symlink to AGENTS.md; executable bits on the scripts/test are preserved (755).

What Changed

  • Adds persistent secondmate routing and lifecycle support, including secondmate home seeding, registry validation, scoped spawn/recovery, and teardown safety for isolated firstmate homes.
  • Updates firstmate supervision, brief generation, send/peek, project path resolution, wake handling, and local merge/review helpers to honor active FM_HOME boundaries and secondmate metadata.
  • Documents secondmate operations and adds focused coverage for secondmate seeding, spawn, teardown, wake queue behavior, and batch spawning.

Risk Assessment

⚠️ Medium: Captain, the review found no material issues, but the branch changes core spawn, routing, watcher, and teardown behavior, including guarded removal paths.

Testing

Captain, I inspected the supplied base-to-target diff and commit shape, ran the focused secondmate and spawn-path behavior tests, broadened to the remaining behavior scripts, and captured a manual CLI transcript showing secondmate seeding and spawn metadata working end to end. The worktree was clean after testing; evidence is in the dedicated no-mistakes evidence directory.

Evidence: secondmate behavior transcript
ok - FM_HOME parameterizes data and state paths
ok - fm-lock status is scoped per home
ok - secondmates registry records scopes and allows overlapping project clone lists
ok - home seeding records routing scope from filled charter briefs
ok - home seed validation rejects duplicate home routes
ok - home seed validation rejects duplicate id routes
ok - home seed validation rejects nested home routes
ok - home seeding accepts treehouse-acquired dash homes
ok - home seeding returns rejected acquired homes through treehouse
ok - home seeding leaves unsafe acquired active homes untouched
ok - home seeding rolls back failed clone attempts without residue
ok - home seeding refuses direct seed without filled charter text
ok - home seeding refuses unfilled placeholder charters
ok - home seeding refuses empty normalized charter fields
ok - home seeding refuses local-only projects
ok - home seeding refuses registry delimiter home paths
ok - home seeding refuses active home and repo root
ok - home seeding refuses homes marked for another id
ok - home seeding refuses homes registered to another id
ok - home seeding refuses same-id reassignment to a different home
ok - home seeding refuses registered home overlaps
ok - remote-backed subhome seeding requires a source origin
ok - remote-backed subhome seeding validates existing destination origins
ok - home seeding resolves relative source origins against the source project
ok - home seeding skips initialized existing no-mistakes clones
ok - home seeding refuses uninitialized existing no-mistakes clones
ok - home seeding refuses project destinations outside the subhome
ok - home seeding refuses operational directories outside the subhome
ok - home seeding refuses symlinked leaf files
ok - kind=secondmate spawn launches in the home and records routing meta
ok - secondmate spawn validates homes before launch
ok - secondmate spawn refuses operational directories outside the subhome
ok - fm-send resolves bare firstmate windows through this home
ok - restart recovery can respawn a secondmate from durable registry and charter
ok - secondmate teardown retires empty homes and releases routing
ok - secondmate force teardown discards child work
ok - force teardown allows operational directory symlinks inside the subhome
ok - force teardown refuses operational directory symlinks outside the subhome
ok - secondmate teardown requires seeded home marker
ok - secondmate teardown refuses homes containing registered nested homes
ok - secondmate teardown refuses nested homes from the child registry
ok - force teardown validates subhome before child cleanup
ok - force teardown refuses child worktrees inside the active home
ok - force teardown refuses child worktrees inside the firstmate repo
ok - force teardown refuses unregistered child worktree paths
ok - secondmate teardown refuses ancestor homes
ok - secondmate teardown refuses descendant homes
ok - idle kind=secondmate pane is healthy and not stale
Evidence: spawn batch behavior transcript
ok - batch dispatch re-execs and reports every id=repo pair
ok - a single id=repo pair routes through batch dispatch
ok - single-task invocation (no '=') is untouched by batch detection
ok - batch dispatch rejects an argument that is not id=repo
ok - an arg whose id part contains '/' is not treated as a batch pair
ok - FM_HOME scopes projects/ paths for single-task spawn
ok - FM_PROJECTS_OVERRIDE scopes projects/ paths for single-task spawn
Evidence: wake queue behavior transcript
ok - supervise daemon state root is scoped by FM_HOME
ok - concurrent append plus drain preserves queue records
ok - signal written while no watcher runs is caught on next run
ok - stale wake is queued before suppressor state is advanced
ok - check output is queued before cadence suppression
ok - simultaneous watcher starts leave exactly one live process
ok - two atomic drains cannot consume the same records twice
ok - drain collapses obvious duplicate heartbeat and signal records
ok - killed watcher stale lock is reclaimed
ok - live watcher lock with stale heartbeat is actionable
ok - guard warns when queued wakes are pending
ok - guard orders watcher re-arm after queued wake drain
ok - routine signal self-handles
ok - captain-relevant status verbs escalate
ok - check + unknown escalate; heartbeat self-handles
ok - transient stale self-handles and records a persistence marker
ok - stale + terminal status escalates immediately
ok - persistent stale escalates after threshold and clears its marker
ok - resumed (busy) stale clears its marker without escalating
ok - multiple escalations flush as a single batched digest
ok - batch flush measures max-delay from the first append, not the last
ok - catch-all scan escalates a missed terminal once, not twice
ok - handle_wake routes routine->self and captain->escalate
ok - INJECT_SKIP forces self-handle, bypassing captain-relevant classification
ok - is_wake_reason distinguishes watcher wake reasons from singleton-status stdout
ok - terminal-stale escalate removes its marker so housekeeping does not re-escalate
ok - captain signal escalate marks seen so the catch-all scan does not re-fire
ok - _collapse_newlines replaces newlines with literal separator
ok - afk flag absent: daemon does not inject, buffer preserved
ok - afk flag present: daemon injects with sentinel marker prefix
ok - injected digest is single-line (no embedded newlines)
ok - busy-guard defers injection when supervisor pane is busy
ok - marker detection: marker -> stay afk, no marker -> exit afk
ok - /afk invocation is exempt from afk exit (no self-cancel)
ok - should_exit_afk returns false when afk is not active
ok - strip_injection_marker removes the sentinel marker cleanly
ok - pane_input_pending detects partial input on the cursor line
ok - pane_input_pending: blank cursor line is not pending
ok - pane_input_pending: bare prompts are not pending (idle)
ok - composer guard defers injection when pane has pending input
ok - swallowed Enter: type-once + Enter-retry, no concatenation
ok - normal inject: exactly one digest, one Enter, no duplicates
ok - classify_signal dedupes against the catch-all scan seen marker
ok - classify_stale dedupes against the signal path seen marker
Evidence: AFK injection E2E transcript
ok - Scenario A: partial input defers injection; digest arrives clean after idle
ok - Scenario B: swallowed Enter produces exactly one clean digest
all e2e injection tests passed
Evidence: manual secondmate CLI demo

CLI demo shows data/secondmates.md route creation, .fm-secondmate-home marker value design, and spawn output kind=secondmate mode=secondmate with persisted meta projects=alpha.

$ fm-home-seed.sh provisions a secondmate route
scaffolded: /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T//fm-secondmate-demo.GjlaAK/main-home/data/design/brief.md (secondmate charter)
home=/private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-secondmate-demo.GjlaAK/design-secondmate-home

$ cat data/secondmates.md
- design - feature design and implementation for alpha (home: /private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-secondmate-demo.GjlaAK/design-secondmate-home; scope: feature design and implementation for alpha; projects: alpha; added 2026-06-22)

$ cat <secondmate-home>/.fm-secondmate-home
design

$ fm-spawn.sh design <secondmate-home> "echo launched-secondmate" --secondmate
spawned design harness=echo kind=secondmate mode=secondmate yolo=off window=firstmate:fm-design worktree=/private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-secondmate-demo.GjlaAK/design-secondmate-home

$ cat state/design.meta
window=firstmate:fm-design
worktree=/private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-secondmate-demo.GjlaAK/design-secondmate-home
project=/private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-secondmate-demo.GjlaAK/design-secondmate-home
harness=echo
kind=secondmate
mode=secondmate
yolo=off
home=/private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-secondmate-demo.GjlaAK/design-secondmate-home
projects=alpha

$ tmux commands issued by spawn
has-session -t firstmate
list-windows -t firstmate -F #{window_name}
new-window -d -t firstmate -n fm-design -c /private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-secondmate-demo.GjlaAK/design-secondmate-home
send-keys -t firstmate:fm-design -l FM_ROOT_OVERRIDE= FM_STATE_OVERRIDE= FM_DATA_OVERRIDE= FM_PROJECTS_OVERRIDE= FM_CONFIG_OVERRIDE= FM_HOME='/private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/fm-secondmate-demo.GjlaAK/design-secondmate-home' echo launched-secondmate
send-keys -t firstmate:fm-design Enter

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed (2) ✅
  • ⚠️ bin/fm-brief.sh:83 - The generated status command leaves the redirected path unquoted, so any FM_HOME/FM_STATE_OVERRIDE containing spaces makes crewmates append to the wrong path or fail; render the generated command with a quoted or shell-escaped status-file path in all brief variants.

🔧 Fix: Captain, quote generated status paths
1 warning still open:

  • ⚠️ bin/fm-spawn.sh:291 - FM_HOME makes projects/ an operational directory under the active home, but ordinary and batch spawns still resolve projects/foo relative to the caller's cwd. When firstmate is run from the shared repo with FM_HOME pointing at a secondmate or external home, this will either fail to spawn or target the wrong clone while writing meta into the active home; resolve projects/* through $FM_PROJECTS_OVERRIDE/$FM_HOME/projects before cd.

🔧 Fix: Captain, resolve FM_HOME project paths
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • git status --short --branch
  • git diff --stat b7c933e795f2fee3e744e0a44f32c5038378d58a..a84850ccb01462c9cdb54aef35c9829eb3a7a28a
  • git diff --name-status b7c933e795f2fee3e744e0a44f32c5038378d58a..a84850ccb01462c9cdb54aef35c9829eb3a7a28a
  • git log --oneline --decorate --max-count=12
  • tests/fm-secondmate.test.sh 2>&1 | tee /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRF3YFM70QS6FYR19KAXZSN/fm-secondmate.test.log
  • tests/fm-spawn-batch.test.sh 2>&1 | tee /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRF3YFM70QS6FYR19KAXZSN/fm-spawn-batch.test.log
  • tests/fm-wake-queue.test.sh 2>&1 | tee /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRF3YFM70QS6FYR19KAXZSN/fm-wake-queue.test.log
  • tests/fm-afk-inject-e2e.test.sh 2>&1 | tee /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVRF3YFM70QS6FYR19KAXZSN/fm-afk-inject-e2e.test.log
  • Manual CLI demo: seeded a secondmate route with fm-home-seed.sh, spawned it with fm-spawn.sh --secondmate through fake tmux, and captured registry, marker, meta, and launch transcript in secondmate-cli-demo.log.
  • git status --short
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 warning
  • ⚠️ .github/workflows/ci.yml:18 - Configured CI linter shellcheck bin/*.sh tests/*.sh could not be run because no shellcheck executable is available on PATH or in standard local tool paths.
✅ **Push** - passed

✅ No issues found.

@kunchenguid kunchenguid changed the title feat: support persistent sub-firstmates feat: support scoped persistent sub-firstmates Jun 22, 2026
@kunchenguid kunchenguid changed the title feat: support scoped persistent sub-firstmates feat: support persistent sub-firstmate routing Jun 22, 2026
@kunchenguid
kunchenguid force-pushed the fm/sub-firstmate-q3 branch from 102e5ff to 3725d6b Compare June 22, 2026 18:54
Mechanical rename of the subordinate domain-supervisor entity from
"sub-firstmate" to "secondmate" (captain -> first mate -> second mate).

Renamed: the entity name, the routing registry (firstmates.md ->
secondmates.md), the seed marker (.fm-sub-firstmate-home ->
.fm-secondmate-home), the kind= and mode= meta values, the --secondmate
spawn/brief flag, the FM_SECONDMATE_CHARTER / FM_SECONDMATE_SCOPE env
vars, and the registry-specific identifiers, plus the test file.

Preserved (the main role): the firstmate playbook/repo, "the main
firstmate", a firstmate home, the active firstmate home, the firstmate
session name, and fm-send's generic bare-firstmate-window resolution.

No behavior change; all behavior tests pass.
@kunchenguid kunchenguid changed the title feat: support persistent sub-firstmate routing feat: support persistent secondmate routing Jun 22, 2026
@kunchenguid kunchenguid changed the title feat: support persistent secondmate routing feat: support persistent secondmates Jun 22, 2026
@kunchenguid
kunchenguid merged commit 6794691 into main Jun 22, 2026
5 checks passed
@kunchenguid
kunchenguid deleted the fm/sub-firstmate-q3 branch June 22, 2026 21:39
e-jung added a commit to e-jung/firstmate that referenced this pull request Jun 22, 2026
…, not just local main

A local-only-registered project whose task pushes its work to a fork
(upstream-contribution PRs) was falsely refused by fm-teardown.sh: the
local-only branch short-circuited to a strict local-main check before
the 'on a remote' check ran, so fork-pushed work that IS safely on a
remote looked unlanded. This bit us 3x this session (no-mistakes x2,
treehouse orphan).

The fix unifies the safety check across modes: HEAD --not --remotes
(empty == on any remote, fork included) is the primary gate for every
mode. The local-main check now runs only as a fallback for local-only
projects whose work is genuinely on no remote at all. The real safety
property - refuse truly unpushed work - is preserved, and --force is
unchanged.

Re-applied onto the kunchenguid#37 teardown rewrite, which already added
FM_ROOT_OVERRIDE / FM_STATE_OVERRIDE support (so the new tests can drive
the script in isolation); this change is now purely the local-only
fork-remote eligibility fix.

Motivating incidents:
- no-mistakes teardown false-refused twice during this session's 8-PR
  upstream-contribution batch
- a treehouse orphan stuck behind the same false refuse

Tests (tests/fm-teardown.test.sh):
- local-only + HEAD on a fork remote-tracking branch -> ALLOW (the fix)
- local-only + truly unpushed work (no remote)      -> REFUSE (safety)
- local-only + work merged into local main          -> ALLOW  (no regression)
- no-mistakes + HEAD on origin                       -> ALLOW  (no regression)
- no-mistakes + truly unpushed                       -> REFUSE (no regression)
- local-only + unpushed + --force                    -> ALLOW  (escape hatch)

AI disclosure: Human-reviewed.
kunchenguid pushed a commit that referenced this pull request Jun 22, 2026
…, not just local main (#31)

* fix(teardown): allow teardown when work is on any remote (incl. fork), not just local main

A local-only-registered project whose task pushes its work to a fork
(upstream-contribution PRs) was falsely refused by fm-teardown.sh: the
local-only branch short-circuited to a strict local-main check before
the 'on a remote' check ran, so fork-pushed work that IS safely on a
remote looked unlanded. This bit us 3x this session (no-mistakes x2,
treehouse orphan).

The fix unifies the safety check across modes: HEAD --not --remotes
(empty == on any remote, fork included) is the primary gate for every
mode. The local-main check now runs only as a fallback for local-only
projects whose work is genuinely on no remote at all. The real safety
property - refuse truly unpushed work - is preserved, and --force is
unchanged.

Re-applied onto the #37 teardown rewrite, which already added
FM_ROOT_OVERRIDE / FM_STATE_OVERRIDE support (so the new tests can drive
the script in isolation); this change is now purely the local-only
fork-remote eligibility fix.

Motivating incidents:
- no-mistakes teardown false-refused twice during this session's 8-PR
  upstream-contribution batch
- a treehouse orphan stuck behind the same false refuse

Tests (tests/fm-teardown.test.sh):
- local-only + HEAD on a fork remote-tracking branch -> ALLOW (the fix)
- local-only + truly unpushed work (no remote)      -> REFUSE (safety)
- local-only + work merged into local main          -> ALLOW  (no regression)
- no-mistakes + HEAD on origin                       -> ALLOW  (no regression)
- no-mistakes + truly unpushed                       -> REFUSE (no regression)
- local-only + unpushed + --force                    -> ALLOW  (escape hatch)

AI disclosure: Human-reviewed.

* no-mistakes(test): Wire fm-teardown.test.sh into CI and README

---------

Co-authored-by: e-jung <8334081+e-jung@users.noreply.github.com>
leo1oel referenced this pull request in leo1oel/nemo Jun 23, 2026
Bring the upstream secondmate feature (PRs #37/#42/#43/#31/kunchenguid#45) to
herdr-backend, rewriting every treehouse/tmux mechanism into the herdr
equivalents this branch already uses. A secondmate is a crewmate whose
workspace is an isolated firstmate home (its own FM_HOME) and whose brief
is a charter; it runs the same spawn/brief/status/watch/teardown/recovery
lifecycle and is idle by default.

treehouse -> herdr mapping:
- Home leasing -> a herdr worktree of the firstmate repo. fm-home-seed.sh
  `-` runs `herdr worktree create --cwd $FM_ROOT --branch secondmate-<id>`
  and records the worktree path + open_workspace_id; herdr never recycles a
  worktree, so persistence is automatic and all lease/return bookkeeping is
  dropped. The workspace id is stored beside the home marker
  (.fm-secondmate-home.workspace) so retirement can find it.
- Retirement / seed rollback -> `herdr worktree remove --workspace <ws>
  --force`. An explicit <home> path stays a plain directory home (a git
  clone, no herdr worktree); spawn opens a workspace for it on the fly via
  `herdr workspace create` and teardown closes it with `herdr workspace
  close` then deletes the dir.
- firstmate_home_has_treehouse_slot -> "does the home record a workspace
  marker"; if yes remove via herdr, else plain rm.

New: bin/fm-home-seed.sh (charter fill, project cloning, no-mistakes init,
registry edits, transactional rollback, validate subcommand all ported
as-is), bin/fm-backlog-handoff.sh (no treehouse/tmux; near-verbatim).

Edits (re-implemented on the diverged herdr-backend files, not applied as
upstream hunks): fm-spawn.sh (--secondmate path, FM_HOME indirection,
registry/home resolution, launch in home, meta records
home=/home_workspace=/projects=), fm-teardown.sh (kind=secondmate
retirement via herdr, child-work refusal + --force discard, plus #31:
allow teardown when work is on any remote incl. a fork), fm-brief.sh
(--secondmate charter scaffold), fm-watch.sh (skip stale-pane wakes for
kind=secondmate), fm-project-mode.sh (honor FM_HOME/FM_DATA_OVERRIDE so a
secondmate resolves its own projects.md). kunchenguid#45: dropped the ':' from the afk
skill description. Skipped fm-bootstrap.sh / fm-harness.sh (removed on this
branch).

Docs: AGENTS.md + README.md gain herdr-flavored secondmate sections
(concept, secondmates.md routing table, fm-home-seed usage, idle-by-default
contract, backlog handoff, kind=secondmate recovery, watch idle) with no
treehouse/tmux wording.

Tests: new herdr-stubbed tests/fm-secondmate.test.sh drives the real
scripts against a stub `herdr` over the full lifecycle (seed `-`, register,
validate dup ids/homes/overlap, spawn, backlog handoff, retire);
tests/fm-teardown.test.sh ports the #31 any-remote/fork matrix to herdr.
shellcheck bin/*.sh tests/*.sh clean; 5/5 test scripts pass.
vipentti pushed a commit to vipentti/firstmate that referenced this pull request Aug 5, 2026
* Add sub-firstmate lifecycle support

* no-mistakes(review): Captain, fix sub-firstmate home isolation

* no-mistakes(review): Captain, isolate sub-firstmate supervision

* no-mistakes(review): Captain, fix sub-firstmate recovery and charter precedence

* no-mistakes(review): Captain, retire sub-firstmates safely

* no-mistakes(review): Captain, seed local-only clones without origin

* no-mistakes(review): Captain, validate remote-backed subhome origins

* no-mistakes(review): Captain, harden sub-firstmate home safety

* no-mistakes(review): Captain, harden sub-firstmate teardown and seeding

* no-mistakes(document): document sub-firstmate operations

* no-mistakes(lint): Shell lint clean

* no-mistakes: apply CI fixes

* Revise sub-firstmate routing scopes

* no-mistakes(review): Scope fm-send to current home

* no-mistakes(review): Captain, validate sub-firstmate homes and scoped peeks

* no-mistakes(review): Captain, harden forced child teardown

* no-mistakes(review): Captain, block local-only sub-firstmate seeding

* no-mistakes(document): Sync sub-firstmate scope docs

* Refine sub-firstmate seeding

* no-mistakes(review): Harden sub-firstmate home rollback

* no-mistakes(review): Harden sub-firstmate home rollback

* no-mistakes(review): Harden sub-firstmate home boundaries

* no-mistakes(review): Captain, harden sub-firstmate project destinations

* no-mistakes(review): Harden sub-firstmate seeding

* no-mistakes(review): Reject nested sub-firstmate homes

* no-mistakes(review): Captain, harden sub-firstmate home isolation

* no-mistakes(review): Guard sub-firstmate id reseeding

* Require filled sub-firstmate charters

* Derive sub-firstmate registry scope from charter

* Harden sub-firstmate registry parsing

* Reject symlinked sub-firstmate seed files

* Harden sub-firstmate launch and teardown paths

* no-mistakes(review): Fix sub-firstmate supervision validation

* no-mistakes(review): Captain, align teardown symlink validation

* no-mistakes(document): Sync sub-firstmate documentation

* no-mistakes(lint): Shell lint clean

* refactor: rename sub-firstmate to secondmate

Mechanical rename of the subordinate domain-supervisor entity from
"sub-firstmate" to "secondmate" (captain -> first mate -> second mate).

Renamed: the entity name, the routing registry (firstmates.md ->
secondmates.md), the seed marker (.fm-sub-firstmate-home ->
.fm-secondmate-home), the kind= and mode= meta values, the --secondmate
spawn/brief flag, the FM_SECONDMATE_CHARTER / FM_SECONDMATE_SCOPE env
vars, and the registry-specific identifiers, plus the test file.

Preserved (the main role): the firstmate playbook/repo, "the main
firstmate", a firstmate home, the active firstmate home, the firstmate
session name, and fm-send's generic bare-firstmate-window resolution.

No behavior change; all behavior tests pass.

* no-mistakes(review): Captain, quote generated status paths

* no-mistakes(review): Captain, resolve FM_HOME project paths

* no-mistakes(document): Sync secondmate documentation

* no-mistakes: apply CI fixes
vipentti pushed a commit to vipentti/firstmate that referenced this pull request Aug 5, 2026
…, not just local main (kunchenguid#31)

* fix(teardown): allow teardown when work is on any remote (incl. fork), not just local main

A local-only-registered project whose task pushes its work to a fork
(upstream-contribution PRs) was falsely refused by fm-teardown.sh: the
local-only branch short-circuited to a strict local-main check before
the 'on a remote' check ran, so fork-pushed work that IS safely on a
remote looked unlanded. This bit us 3x this session (no-mistakes x2,
treehouse orphan).

The fix unifies the safety check across modes: HEAD --not --remotes
(empty == on any remote, fork included) is the primary gate for every
mode. The local-main check now runs only as a fallback for local-only
projects whose work is genuinely on no remote at all. The real safety
property - refuse truly unpushed work - is preserved, and --force is
unchanged.

Re-applied onto the kunchenguid#37 teardown rewrite, which already added
FM_ROOT_OVERRIDE / FM_STATE_OVERRIDE support (so the new tests can drive
the script in isolation); this change is now purely the local-only
fork-remote eligibility fix.

Motivating incidents:
- no-mistakes teardown false-refused twice during this session's 8-PR
  upstream-contribution batch
- a treehouse orphan stuck behind the same false refuse

Tests (tests/fm-teardown.test.sh):
- local-only + HEAD on a fork remote-tracking branch -> ALLOW (the fix)
- local-only + truly unpushed work (no remote)      -> REFUSE (safety)
- local-only + work merged into local main          -> ALLOW  (no regression)
- no-mistakes + HEAD on origin                       -> ALLOW  (no regression)
- no-mistakes + truly unpushed                       -> REFUSE (no regression)
- local-only + unpushed + --force                    -> ALLOW  (escape hatch)

AI disclosure: Human-reviewed.

* no-mistakes(test): Wire fm-teardown.test.sh into CI and README

---------

Co-authored-by: e-jung <8334081+e-jung@users.noreply.github.com>
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