From 005295fe863c697770cd0477f574f3478577d40b Mon Sep 17 00:00:00 2001 From: kunchenguid Date: Sun, 14 Jun 2026 22:06:30 -0700 Subject: [PATCH 1/5] feat(bin): prune gone branches during fleet sync fm-fleet-sync now fetches with --prune and deletes local branches whose upstream tracking branch is gone and that have no worktree, so merged feature branches stop piling up in the pooled clones. It never touches the checked-out branch or a branch with a live worktree, so it cannot discard unlanded work; FM_FLEET_PRUNE=0 disables it. Widens the fleet-sync hard-rule exception and updates the docs to match. --- AGENTS.md | 10 +++++----- README.md | 6 +++--- bin/fm-fleet-sync.sh | 43 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9f5f6e16192..8772e378a7c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,8 +19,8 @@ Hard rules, in priority order: 1. **Never write to a project.** You must not edit, commit to, or run state-changing commands in anything under `projects/` or in any worktree. You read projects to understand them; crewmates change them. - Three sanctioned exceptions: tool-driven project initialization (section 6), clean fast-forwarding a clone's local default branch to match `origin` via `bin/fm-fleet-sync.sh`, and the approved local merge for a `local-only` project, which firstmate performs with `bin/fm-merge-local.sh` once the captain approves (section 7). - The fleet sync exception advances only the checked-out local default branch and never forces, creates merge commits, stashes, or changes treehouse worktrees. + Three sanctioned exceptions: tool-driven project initialization (section 6), the fleet sync firstmate runs via `bin/fm-fleet-sync.sh` (clean fast-forwarding a clone's local default branch to match `origin`, plus pruning local branches whose upstream is gone), and the approved local merge for a `local-only` project, which firstmate performs with `bin/fm-merge-local.sh` once the captain approves (section 7). + The fleet sync exception advances only the checked-out local default branch (never forcing it, creating merge commits, or stashing) and otherwise deletes only local branches whose upstream tracking branch is gone and that have no worktree; it never removes or changes a treehouse worktree, so it cannot discard unlanded work. 2. **Never merge a PR without the captain's explicit word.** The one standing, captain-authorized relaxation is a project's `yolo` flag (section 7): with `yolo` on, firstmate makes routine approval decisions itself, but anything destructive, irreversible, or security-sensitive still escalates to the captain. 3. **Never tear down a worktree that holds unlanded work.** @@ -53,7 +53,7 @@ README.md public overview and development notes .github/workflows/ shared CI and PR enforcement, committed .agents/skills/ shared skills, committed .claude/skills symlink to .agents/skills for claude compatibility -bin/ helper scripts, committed, including fm-fleet-sync.sh for clean default-branch refreshes; read each script's header before first use +bin/ helper scripts, committed, including fm-fleet-sync.sh for clean default-branch refreshes and gone-branch pruning; read each script's header before first use config/crew-harness crewmate harness override; LOCAL, gitignored; absent or "default" = same as firstmate data/ personal fleet records; LOCAL, gitignored as a whole backlog.md task queue, dependencies, history @@ -81,7 +81,7 @@ Bootstrap is detect, then consent, then install. Never install anything the captain has not approved in this session. Run `bin/fm-bootstrap.sh`. -Bootstrap also refreshes the fleet via `bin/fm-fleet-sync.sh`: it fetches each remote-backed clone and clean-fast-forwards its local default branch when safe, best-effort and non-fatal. +Bootstrap also refreshes the fleet via `bin/fm-fleet-sync.sh`: it fetches each remote-backed clone, clean-fast-forwards its local default branch when safe, and prunes local branches whose upstream is gone and that no worktree still needs, best-effort and non-fatal. Silence means all good: say nothing and move on. Otherwise it prints one line per problem; handle each: @@ -323,7 +323,7 @@ bin/fm-teardown.sh The script refuses if the worktree holds unpushed work; treat a refusal as a stop-and-investigate, not an obstacle. Known benign case: after an external-PR task, a squash merge leaves the branch commits reachable only on the contributor's fork; add the fork as a remote and fetch (`git remote add fork && git fetch fork`), then retry - never reach for `--force`. -After a successful PR-based teardown, it also runs `bin/fm-fleet-sync.sh` for that project, best-effort, so the clone's local default catches up to the merge immediately. +After a successful PR-based teardown, it also runs `bin/fm-fleet-sync.sh` for that project, best-effort, so the clone's local default catches up to the merge and the just-merged branch, now gone on the remote and free of its worktree, is pruned immediately. Then move the task to Done in `data/backlog.md` (with the full `https://...` PR URL or local merge note and date), re-evaluate the queue, and dispatch anything that was blocked on this task. ### Scout tasks (report instead of PR) diff --git a/README.md b/README.md index 8ed1d448c1c..bf73fd21dfe 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ There is no app to install; the whole orchestrator is an `AGENTS.md` file that a The first mate dispatches, supervises, escalates only real decisions, and reports plain outcomes about work that is ready, blocked, or needs your call. - **A visible crew** - every crewmate lives in a tmux window. Watch any of them work, or type into their window to intervene; the first mate reconciles. -- **Guarded by construction** - the first mate is read-only over your projects except for clean local default-branch refreshes and approved `local-only` fast-forward merges; crewmates work in disposable [treehouse](https://github.com/kunchenguid/treehouse) worktrees. +- **Guarded by construction** - the first mate is read-only over your projects except for clean local default-branch refreshes, pruning local branches whose remote is gone, and approved `local-only` fast-forward merges; crewmates work in disposable [treehouse](https://github.com/kunchenguid/treehouse) worktrees. Ship tasks follow each project's delivery mode, and scout tasks produce local reports without pushing anything. This is not an agent harness. This is not a skill. This is not a CLI. @@ -117,7 +117,7 @@ firstmate works from any terminal - outside tmux, crewmates land in a detached ` - **Two task shapes** - ship tasks change projects and ship by project mode (`no-mistakes`, `direct-PR`, or `local-only`); scout tasks investigate, plan, reproduce bugs, or audit, then leave a report at `data//report.md` and never push. - **Project modes are explicit** - `data/projects.md` records each project's delivery mode and optional `+yolo` autonomy flag. `no-mistakes` projects run the full validation pipeline, `direct-PR` projects open PRs without that pipeline, and `local-only` projects stay local until firstmate performs an approved fast-forward merge. -- **Local clones stay fresh** - bootstrap and PR-based teardown refresh remote-backed project clones with clean default-branch fast-forwards when the clone is on the default branch and has no local work. +- **Local clones stay fresh** - bootstrap and PR-based teardown refresh remote-backed project clones with clean default-branch fast-forwards when the clone is on the default branch and has no local work, and prune local branches whose remote is gone and that no worktree still needs. - **Restart-proof** - all state lives in tmux, status files, and local markdown under `data/`. Kill the first mate session anytime; the next one reconciles and carries on. @@ -128,7 +128,7 @@ The first mate drives these; you rarely need to, but they work by hand too. | Script | Description | | ----------------- | ------------------------------------------------------------------------------------------- | | `fm-bootstrap.sh` | Detect missing toolchain pieces; refresh clones best-effort; install tools only after consent | -| `fm-fleet-sync.sh` | Fetch clones and clean-fast-forward their checked-out local default branches when safe | +| `fm-fleet-sync.sh` | Fetch clones, clean-fast-forward their checked-out default branches, and prune branches whose remote is gone | | `fm-brief.sh` | Scaffold a ship brief, or a report-only scout brief with `--scout` | | `fm-guard.sh` | Warn when tasks are in flight but the watcher liveness beacon is stale or missing | | `fm-spawn.sh` | Window → treehouse worktree → agent launched with its brief; records ship/scout task kind | diff --git a/bin/fm-fleet-sync.sh b/bin/fm-fleet-sync.sh index c0fe73af4fc..0ae1d086ad7 100755 --- a/bin/fm-fleet-sync.sh +++ b/bin/fm-fleet-sync.sh @@ -1,8 +1,12 @@ #!/usr/bin/env bash -# Refresh project clones by fast-forwarding their checked-out local default branch -# to origin/ when it is safe to do so. +# Refresh project clones: fast-forward the checked-out local default branch to +# origin/ when safe, and prune local branches whose upstream tracking +# branch is gone (the remote branch was deleted, i.e. its PR merged) and that no +# worktree still needs. # Skips local-only/no-origin projects, dirty clones, non-default checkouts, # diverged branches, and fetch/fast-forward failures without forcing or stashing. +# Pruning never deletes the checked-out branch or a branch with a live worktree, +# so it cannot discard unlanded work; set FM_FLEET_PRUNE=0 to disable it. # Usage: fm-fleet-sync.sh [] set -eu @@ -47,6 +51,37 @@ first_line() { printf '%s\n' "$1" | sed -n '1s/[[:space:]]\{1,\}/ /g;1p' } +prune_gone_branches() { + # Delete local branches whose upstream tracking branch is gone - the remote + # branch was deleted, which in this fleet means its PR merged - as long as + # nothing still needs them. Never the checked-out branch, and never a branch + # that still has a worktree (a live or not-yet-torn-down task). "Gone" plus + # "no worktree" means the work already landed, because a worktree is only + # removed once teardown confirmed the work was on the remote, so pruning never + # discards unlanded work. Set FM_FLEET_PRUNE=0 to skip pruning entirely. + [ "${FM_FLEET_PRUNE:-1}" != "0" ] || return 0 + + local worktree_branches current refline branch track + worktree_branches=$(git -C "$PROJ" worktree list --porcelain 2>/dev/null \ + | sed -n 's#^branch refs/heads/##p') + current=$(git -C "$PROJ" symbolic-ref --quiet --short HEAD 2>/dev/null || true) + + while IFS= read -r refline; do + branch=${refline%% *} + track=${refline#* } + [ "$track" = "[gone]" ] || continue + [ -n "$branch" ] || continue + [ "$branch" != "$current" ] || continue + if printf '%s\n' "$worktree_branches" | grep -Fxq -- "$branch"; then + continue + fi + if git -C "$PROJ" branch -D "$branch" >/dev/null 2>&1; then + echo "$label: pruned $branch" + fi + done < <(git -C "$PROJ" for-each-ref \ + --format='%(refname:short) %(upstream:track)' refs/heads 2>/dev/null) +} + sync_project() { PROJ=$1 label=$(project_label) @@ -70,7 +105,7 @@ sync_project() { return 0 fi - if ! fetch_output=$(git -C "$PROJ" fetch origin --quiet 2>&1); then + if ! fetch_output=$(git -C "$PROJ" fetch origin --prune --quiet 2>&1); then reason="fetch failed" if [ -n "$fetch_output" ]; then reason="$reason: $(first_line "$fetch_output")" @@ -79,6 +114,8 @@ sync_project() { return 0 fi + prune_gone_branches || true + DEFAULT=$(default_branch) || { echo "$label: skipped: cannot determine default branch" return 0 From 90fdfc5906caa6fdac27c316d30a91ed68bd9f23 Mon Sep 17 00:00:00 2001 From: kunchenguid Date: Sun, 14 Jun 2026 22:09:51 -0700 Subject: [PATCH 2/5] no-mistakes(review): Safely prune only merged gone branches --- bin/fm-fleet-sync.sh | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/bin/fm-fleet-sync.sh b/bin/fm-fleet-sync.sh index 0ae1d086ad7..d496ec33a9d 100755 --- a/bin/fm-fleet-sync.sh +++ b/bin/fm-fleet-sync.sh @@ -5,8 +5,8 @@ # worktree still needs. # Skips local-only/no-origin projects, dirty clones, non-default checkouts, # diverged branches, and fetch/fast-forward failures without forcing or stashing. -# Pruning never deletes the checked-out branch or a branch with a live worktree, -# so it cannot discard unlanded work; set FM_FLEET_PRUNE=0 to disable it. +# Pruning never deletes the checked-out branch, a branch with a live worktree, +# or a branch not merged into origin/; set FM_FLEET_PRUNE=0 to disable it. # Usage: fm-fleet-sync.sh [] set -eu @@ -55,10 +55,9 @@ prune_gone_branches() { # Delete local branches whose upstream tracking branch is gone - the remote # branch was deleted, which in this fleet means its PR merged - as long as # nothing still needs them. Never the checked-out branch, and never a branch - # that still has a worktree (a live or not-yet-torn-down task). "Gone" plus - # "no worktree" means the work already landed, because a worktree is only - # removed once teardown confirmed the work was on the remote, so pruning never - # discards unlanded work. Set FM_FLEET_PRUNE=0 to skip pruning entirely. + # that still has a worktree (a live or not-yet-torn-down task). The branch + # must also be merged into origin/. Set FM_FLEET_PRUNE=0 to skip + # pruning entirely. [ "${FM_FLEET_PRUNE:-1}" != "0" ] || return 0 local worktree_branches current refline branch track @@ -75,7 +74,10 @@ prune_gone_branches() { if printf '%s\n' "$worktree_branches" | grep -Fxq -- "$branch"; then continue fi - if git -C "$PROJ" branch -D "$branch" >/dev/null 2>&1; then + if ! git -C "$PROJ" merge-base --is-ancestor "$branch" "$BASE"; then + continue + fi + if git -C "$PROJ" branch -D -- "$branch" >/dev/null 2>&1; then echo "$label: pruned $branch" fi done < <(git -C "$PROJ" for-each-ref \ @@ -114,8 +116,6 @@ sync_project() { return 0 fi - prune_gone_branches || true - DEFAULT=$(default_branch) || { echo "$label: skipped: cannot determine default branch" return 0 @@ -126,6 +126,8 @@ sync_project() { return 0 fi + prune_gone_branches || true + cur=$(git -C "$PROJ" symbolic-ref --short HEAD 2>/dev/null || echo "") if [ "$cur" != "$DEFAULT" ]; then [ -n "$cur" ] || cur="detached HEAD" From e88600025d8e8bc32fe5d26f6b0ccd5aaffcf140 Mon Sep 17 00:00:00 2001 From: kunchenguid Date: Sun, 14 Jun 2026 22:14:55 -0700 Subject: [PATCH 3/5] no-mistakes(document): Document fleet pruning behavior --- AGENTS.md | 5 +++-- README.md | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8772e378a7c..9032cbd07ac 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ Hard rules, in priority order: You must not edit, commit to, or run state-changing commands in anything under `projects/` or in any worktree. You read projects to understand them; crewmates change them. Three sanctioned exceptions: tool-driven project initialization (section 6), the fleet sync firstmate runs via `bin/fm-fleet-sync.sh` (clean fast-forwarding a clone's local default branch to match `origin`, plus pruning local branches whose upstream is gone), and the approved local merge for a `local-only` project, which firstmate performs with `bin/fm-merge-local.sh` once the captain approves (section 7). - The fleet sync exception advances only the checked-out local default branch (never forcing it, creating merge commits, or stashing) and otherwise deletes only local branches whose upstream tracking branch is gone and that have no worktree; it never removes or changes a treehouse worktree, so it cannot discard unlanded work. + The fleet sync exception advances only the checked-out local default branch (never forcing it, creating merge commits, or stashing) and otherwise deletes only local branches whose upstream tracking branch is gone, that have no worktree, and that are merged into `origin/`; it never removes or changes a treehouse worktree, so it cannot discard unlanded work. 2. **Never merge a PR without the captain's explicit word.** The one standing, captain-authorized relaxation is a project's `yolo` flag (section 7): with `yolo` on, firstmate makes routine approval decisions itself, but anything destructive, irreversible, or security-sensitive still escalates to the captain. 3. **Never tear down a worktree that holds unlanded work.** @@ -81,7 +81,8 @@ Bootstrap is detect, then consent, then install. Never install anything the captain has not approved in this session. Run `bin/fm-bootstrap.sh`. -Bootstrap also refreshes the fleet via `bin/fm-fleet-sync.sh`: it fetches each remote-backed clone, clean-fast-forwards its local default branch when safe, and prunes local branches whose upstream is gone and that no worktree still needs, best-effort and non-fatal. +Bootstrap also refreshes the fleet via `bin/fm-fleet-sync.sh`: it fetches each remote-backed clone, clean-fast-forwards its local default branch when safe, and prunes local branches whose upstream is gone, that no worktree still needs, and that are merged into `origin/`, best-effort and non-fatal. +Set `FM_FLEET_PRUNE=0` to temporarily disable that branch pruning. Silence means all good: say nothing and move on. Otherwise it prints one line per problem; handle each: diff --git a/README.md b/README.md index bf73fd21dfe..f15612368c0 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ There is no app to install; the whole orchestrator is an `AGENTS.md` file that a The first mate dispatches, supervises, escalates only real decisions, and reports plain outcomes about work that is ready, blocked, or needs your call. - **A visible crew** - every crewmate lives in a tmux window. Watch any of them work, or type into their window to intervene; the first mate reconciles. -- **Guarded by construction** - the first mate is read-only over your projects except for clean local default-branch refreshes, pruning local branches whose remote is gone, and approved `local-only` fast-forward merges; crewmates work in disposable [treehouse](https://github.com/kunchenguid/treehouse) worktrees. +- **Guarded by construction** - the first mate is read-only over your projects except for clean local default-branch refreshes, safe pruning of local branches whose remote is gone, and approved `local-only` fast-forward merges; crewmates work in disposable [treehouse](https://github.com/kunchenguid/treehouse) worktrees. Ship tasks follow each project's delivery mode, and scout tasks produce local reports without pushing anything. This is not an agent harness. This is not a skill. This is not a CLI. @@ -117,7 +117,7 @@ firstmate works from any terminal - outside tmux, crewmates land in a detached ` - **Two task shapes** - ship tasks change projects and ship by project mode (`no-mistakes`, `direct-PR`, or `local-only`); scout tasks investigate, plan, reproduce bugs, or audit, then leave a report at `data//report.md` and never push. - **Project modes are explicit** - `data/projects.md` records each project's delivery mode and optional `+yolo` autonomy flag. `no-mistakes` projects run the full validation pipeline, `direct-PR` projects open PRs without that pipeline, and `local-only` projects stay local until firstmate performs an approved fast-forward merge. -- **Local clones stay fresh** - bootstrap and PR-based teardown refresh remote-backed project clones with clean default-branch fast-forwards when the clone is on the default branch and has no local work, and prune local branches whose remote is gone and that no worktree still needs. +- **Local clones stay fresh** - bootstrap and PR-based teardown refresh remote-backed project clones with clean default-branch fast-forwards when the clone is on the default branch and has no local work, and prune local branches whose remote is gone, that no worktree still needs, and that are merged into `origin/`. - **Restart-proof** - all state lives in tmux, status files, and local markdown under `data/`. Kill the first mate session anytime; the next one reconciles and carries on. @@ -128,7 +128,7 @@ The first mate drives these; you rarely need to, but they work by hand too. | Script | Description | | ----------------- | ------------------------------------------------------------------------------------------- | | `fm-bootstrap.sh` | Detect missing toolchain pieces; refresh clones best-effort; install tools only after consent | -| `fm-fleet-sync.sh` | Fetch clones, clean-fast-forward their checked-out default branches, and prune branches whose remote is gone | +| `fm-fleet-sync.sh` | Fetch clones, clean-fast-forward their checked-out default branches, and safely prune branches whose remote is gone | | `fm-brief.sh` | Scaffold a ship brief, or a report-only scout brief with `--scout` | | `fm-guard.sh` | Warn when tasks are in flight but the watcher liveness beacon is stale or missing | | `fm-spawn.sh` | Window → treehouse worktree → agent launched with its brief; records ship/scout task kind | @@ -150,7 +150,7 @@ The shared orchestrator behavior lives in `AGENTS.md` - edit it like any prompt Personal preferences for one captain's fleet live locally in `data/captain.md`; it is gitignored and read after `data/projects.md` during bootstrap. Harness support is a table in section 4: claude, codex, opencode, and pi are all empirically verified; new harnesses get verified through a supervised trial task before joining the table. -Watcher tuning via environment variables (defaults shown): +Runtime tuning via environment variables (defaults shown): ```sh FM_POLL=15 # seconds between watcher cycles @@ -161,6 +161,7 @@ FM_CHECK_TIMEOUT=30 # seconds allowed per slow check script FM_GUARD_GRACE=300 # seconds a stale watcher beacon may age before guard warnings FM_SIGNAL_GRACE=30 # seconds to coalesce nearby status and turn-end signals into one wake FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT=20 # seconds allowed for bootstrap's best-effort clone refresh +FM_FLEET_PRUNE=1 # set to 0 to skip pruning local branches whose upstream is gone FM_BUSY_REGEX='esc (to )?interrupt|Working\.\.\.' # busy-pane signatures, extend per harness ``` From f8451165bec5313e4eacffc331bd9d9555a506e6 Mon Sep 17 00:00:00 2001 From: kunchenguid Date: Sun, 14 Jun 2026 22:24:18 -0700 Subject: [PATCH 4/5] fix(bin): prune squash-merged gone branches, drop ancestor gate The merge-base --is-ancestor gate makes pruning a permanent no-op in this fleet: PRs are squash-merged (the firstmate repo only allows squash), so a merged branch is never an ancestor of origin/ and nothing is ever pruned. Reproduced with a squash-merge clone. Pruning now relies on the gone + no-worktree pair, which already proves the work landed - teardown removes a branch's worktree only after the work reached the remote - so it stays safe without the ancestor check. Docs updated to match. --- AGENTS.md | 4 ++-- README.md | 2 +- bin/fm-fleet-sync.sh | 21 +++++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9032cbd07ac..8e3d1cb5e2a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ Hard rules, in priority order: You must not edit, commit to, or run state-changing commands in anything under `projects/` or in any worktree. You read projects to understand them; crewmates change them. Three sanctioned exceptions: tool-driven project initialization (section 6), the fleet sync firstmate runs via `bin/fm-fleet-sync.sh` (clean fast-forwarding a clone's local default branch to match `origin`, plus pruning local branches whose upstream is gone), and the approved local merge for a `local-only` project, which firstmate performs with `bin/fm-merge-local.sh` once the captain approves (section 7). - The fleet sync exception advances only the checked-out local default branch (never forcing it, creating merge commits, or stashing) and otherwise deletes only local branches whose upstream tracking branch is gone, that have no worktree, and that are merged into `origin/`; it never removes or changes a treehouse worktree, so it cannot discard unlanded work. + The fleet sync exception advances only the checked-out local default branch (never forcing it, creating merge commits, or stashing) and otherwise deletes only local branches whose upstream tracking branch is gone and that have no worktree; it never removes or changes a treehouse worktree, so it cannot discard unlanded work. 2. **Never merge a PR without the captain's explicit word.** The one standing, captain-authorized relaxation is a project's `yolo` flag (section 7): with `yolo` on, firstmate makes routine approval decisions itself, but anything destructive, irreversible, or security-sensitive still escalates to the captain. 3. **Never tear down a worktree that holds unlanded work.** @@ -81,7 +81,7 @@ Bootstrap is detect, then consent, then install. Never install anything the captain has not approved in this session. Run `bin/fm-bootstrap.sh`. -Bootstrap also refreshes the fleet via `bin/fm-fleet-sync.sh`: it fetches each remote-backed clone, clean-fast-forwards its local default branch when safe, and prunes local branches whose upstream is gone, that no worktree still needs, and that are merged into `origin/`, best-effort and non-fatal. +Bootstrap also refreshes the fleet via `bin/fm-fleet-sync.sh`: it fetches each remote-backed clone, clean-fast-forwards its local default branch when safe, and prunes local branches whose upstream is gone and that no worktree still needs, best-effort and non-fatal. Set `FM_FLEET_PRUNE=0` to temporarily disable that branch pruning. Silence means all good: say nothing and move on. Otherwise it prints one line per problem; handle each: diff --git a/README.md b/README.md index f15612368c0..8bb679dfad2 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ firstmate works from any terminal - outside tmux, crewmates land in a detached ` - **Two task shapes** - ship tasks change projects and ship by project mode (`no-mistakes`, `direct-PR`, or `local-only`); scout tasks investigate, plan, reproduce bugs, or audit, then leave a report at `data//report.md` and never push. - **Project modes are explicit** - `data/projects.md` records each project's delivery mode and optional `+yolo` autonomy flag. `no-mistakes` projects run the full validation pipeline, `direct-PR` projects open PRs without that pipeline, and `local-only` projects stay local until firstmate performs an approved fast-forward merge. -- **Local clones stay fresh** - bootstrap and PR-based teardown refresh remote-backed project clones with clean default-branch fast-forwards when the clone is on the default branch and has no local work, and prune local branches whose remote is gone, that no worktree still needs, and that are merged into `origin/`. +- **Local clones stay fresh** - bootstrap and PR-based teardown refresh remote-backed project clones with clean default-branch fast-forwards when the clone is on the default branch and has no local work, and prune local branches whose remote is gone and that no worktree still needs. - **Restart-proof** - all state lives in tmux, status files, and local markdown under `data/`. Kill the first mate session anytime; the next one reconciles and carries on. diff --git a/bin/fm-fleet-sync.sh b/bin/fm-fleet-sync.sh index d496ec33a9d..8a824929ba6 100755 --- a/bin/fm-fleet-sync.sh +++ b/bin/fm-fleet-sync.sh @@ -5,8 +5,8 @@ # worktree still needs. # Skips local-only/no-origin projects, dirty clones, non-default checkouts, # diverged branches, and fetch/fast-forward failures without forcing or stashing. -# Pruning never deletes the checked-out branch, a branch with a live worktree, -# or a branch not merged into origin/; set FM_FLEET_PRUNE=0 to disable it. +# Pruning never deletes the checked-out branch or a branch that still has a +# worktree, so it cannot discard unlanded work; set FM_FLEET_PRUNE=0 to disable it. # Usage: fm-fleet-sync.sh [] set -eu @@ -55,9 +55,13 @@ prune_gone_branches() { # Delete local branches whose upstream tracking branch is gone - the remote # branch was deleted, which in this fleet means its PR merged - as long as # nothing still needs them. Never the checked-out branch, and never a branch - # that still has a worktree (a live or not-yet-torn-down task). The branch - # must also be merged into origin/. Set FM_FLEET_PRUNE=0 to skip - # pruning entirely. + # that still has a worktree (a live or not-yet-torn-down task). "Gone" plus + # "no worktree" already proves the work landed: teardown removes a branch's + # worktree only after confirming the work reached the remote. We deliberately + # do NOT also require the branch to be an ancestor of origin/ - PRs in + # this fleet are squash-merged, so a merged branch is never an ancestor and + # such a check would prune nothing. The no-worktree guard is the real safety + # net. Set FM_FLEET_PRUNE=0 to skip pruning entirely. [ "${FM_FLEET_PRUNE:-1}" != "0" ] || return 0 local worktree_branches current refline branch track @@ -74,9 +78,6 @@ prune_gone_branches() { if printf '%s\n' "$worktree_branches" | grep -Fxq -- "$branch"; then continue fi - if ! git -C "$PROJ" merge-base --is-ancestor "$branch" "$BASE"; then - continue - fi if git -C "$PROJ" branch -D -- "$branch" >/dev/null 2>&1; then echo "$label: pruned $branch" fi @@ -116,6 +117,8 @@ sync_project() { return 0 fi + prune_gone_branches || true + DEFAULT=$(default_branch) || { echo "$label: skipped: cannot determine default branch" return 0 @@ -126,8 +129,6 @@ sync_project() { return 0 fi - prune_gone_branches || true - cur=$(git -C "$PROJ" symbolic-ref --short HEAD 2>/dev/null || echo "") if [ "$cur" != "$DEFAULT" ]; then [ -n "$cur" ] || cur="detached HEAD" From 1d9f837131c06fce5e1c54341bc94b6dd2b32410 Mon Sep 17 00:00:00 2001 From: kunchenguid Date: Sun, 14 Jun 2026 22:29:23 -0700 Subject: [PATCH 5/5] no-mistakes(document): Sync fleet-prune documentation --- bin/fm-bootstrap.sh | 6 ++++-- bin/fm-teardown.sh | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/fm-bootstrap.sh b/bin/fm-bootstrap.sh index 0fd457966e1..621a15a1432 100755 --- a/bin/fm-bootstrap.sh +++ b/bin/fm-bootstrap.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash -# Bootstrap detection, best-effort fleet refresh, and installs. +# Bootstrap detection, best-effort fleet refresh/prune, and installs. # Usage: fm-bootstrap.sh # Detect: prints one line per problem and exits 0. Silent = all good. # Lines: "MISSING: (install: )", "NEEDS_GH_AUTH", # "CREW_HARNESS_OVERRIDE: ", "FLEET_SYNC: : skipped: ". -# The fleet refresh is bounded by FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT, default 20s. +# Fleet sync fetches, fast-forwards, and prunes gone local branches; +# it is bounded by FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT, default 20s. +# Set FM_FLEET_PRUNE=0 to skip branch pruning during that refresh. # fm-bootstrap.sh install ... # Install the named tools (only ones the captain approved). set -u diff --git a/bin/fm-teardown.sh b/bin/fm-teardown.sh index 269db282935..17a6583aafb 100755 --- a/bin/fm-teardown.sh +++ b/bin/fm-teardown.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Tear down a finished task: return the treehouse worktree, kill the tmux window, -# clear volatile state, then refresh the project's clone for PR-based ship tasks. +# clear volatile state, then refresh/prune the project's clone for PR-based ship tasks. # REFUSES if the worktree holds work not on any remote, because treehouse return # hard-resets the worktree and kills its processes. # Scout tasks (kind=scout in meta) carve out of that check: their worktree is