Skip to content

feat(bin): prune gone branches during fleet sync - #20

Merged
kunchenguid merged 5 commits into
mainfrom
fm/fleet-prune-gone
Jun 15, 2026
Merged

kunchenguid merged 5 commits into
mainfrom
fm/fleet-prune-gone

Conversation

@kunchenguid

@kunchenguid kunchenguid commented Jun 15, 2026 •

Copy link
Copy Markdown
Owner

Intent

Port the one firstmate-shaped idea from the EveryInc compound-engineering skills the captain reviewed: fold a gone-branch prune into the EXISTING bin/fm-fleet-sync.sh (captain said fold into existing scripts, every new script is overhead - deliberately NOT a new script). Behavior: fm-fleet-sync fetches with --prune, then deletes local branches whose upstream tracking branch is gone (remote branch deleted = its PR merged), but only when safe - never the checked-out branch, never a branch that still has a worktree. SAFETY MODEL (important, do not 'fix' this): pruning relies on the 'gone + no worktree' pair, NOT on a merge-base --is-ancestor check. This is deliberate. This fleet squash-merges exclusively (the firstmate repo only allows squash merges; merge commits and rebase merges are disabled), and under a squash merge a merged branch is NEVER an ancestor of origin/ - so an ancestor gate would prune nothing at all. I reproduced exactly that: with an --is-ancestor gate the squash-merged branch is left behind; without it, it is correctly pruned, while a gone branch that still has a worktree is always kept. The no-worktree guard IS the real safety net: firstmate's teardown removes a branch's worktree only after confirming the work reached the remote, so 'gone + no worktree' already proves the work landed and nothing unlanded is ever discarded. A previous run of this very pipeline added the --is-ancestor gate; I removed it with that reproduction as evidence, so please do not reintroduce it. FM_FLEET_PRUNE=0 disables pruning. Because pruning widens one of firstmate's three sanctioned 'never write to a project' exceptions, AGENTS.md's prime-directive wording plus the README, bootstrap, and teardown descriptions were updated to match - that doc churn is intentional. Validated against constructed clones: squash-merged gone branch pruned, gone+worktree branch kept, non-gone default kept, FM_FLEET_PRUNE=0 respected.

What Changed

  • Updated fm-fleet-sync.sh to fetch with --prune and delete local branches whose upstream is gone when they are not checked out and have no linked worktree.
  • Added FM_FLEET_PRUNE=0 as an escape hatch to disable branch pruning while preserving the existing fleet fetch and safe default-branch fast-forward behavior.
  • Updated bootstrap, teardown, README, and agent instructions to describe the expanded fleet sync behavior and its safety boundaries.

Risk Assessment

✅ Low: The change is limited to one fleet-sync helper plus matching docs, with pruning guarded by fetch success, gone upstream state, current-branch exclusion, and worktree exclusion.

Testing

Inspected the changed shell/docs, then exercised the user-facing bin/fm-fleet-sync.sh CLI against constructed clones that model squash-merged gone branches, linked worktree safety, checked-out branch safety, and the FM_FLEET_PRUNE=0 opt-out; all assertions passed and the worktree stayed clean.

Evidence: Fleet prune end-to-end transcript

Shows squash-gone pruned despite not being an ancestor of origin/main, kept-worktree retained due to a linked worktree, current-gone retained because it is checked out, and optout-gone retained with FM_FLEET_PRUNE=0.

fleet prune end-to-end evidence
workspace: /Users/kunchen/.no-mistakes/worktrees/016d88035d58/01KV4VTKWG2T1GWHNK2P4ST3VR
evidence: /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KV4VTKWG2T1GWHNK2P4ST3VR/fleet-prune-e2e

precheck: squash-gone is not an ancestor of origin/main after squash merge

normal run before branches:
* current-gone  d142165 [origin/current-gone] current gone original commit
+ kept-worktree ede517d (/private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KV4VTKWG2T1GWHNK2P4ST3VR/fleet-prune-e2e/wt-kept) [origin/kept-worktree] kept worktree original commit
  main          3354c69 [origin/main: behind 1] initial
  squash-gone   c2c112f [origin/squash-gone] feature original commit

normal run output:
/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KV4VTKWG2T1GWHNK2P4ST3VR/fleet-prune-e2e/project-normal: pruned squash-gone
/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KV4VTKWG2T1GWHNK2P4ST3VR/fleet-prune-e2e/project-normal: skipped: on current-gone, expected main

normal run after branches:
* current-gone  d142165 [origin/current-gone: gone] current gone original commit
+ kept-worktree ede517d (/private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KV4VTKWG2T1GWHNK2P4ST3VR/fleet-prune-e2e/wt-kept) [origin/kept-worktree: gone] kept worktree original commit
  main          3354c69 [origin/main: behind 1] initial
assertion: squash-gone pruned even though it was not an ancestor of origin/main
assertion: kept-worktree retained because a linked worktree still uses it
assertion: current-gone retained because it is the checked-out branch
assertion: main retained

opt-out run before branches:
* main        279e188 [origin/main] squash merge feature
  optout-gone c4a7820 [origin/optout-gone] optout gone original commit

opt-out run output:
/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KV4VTKWG2T1GWHNK2P4ST3VR/fleet-prune-e2e/project-optout: already current

opt-out run after branches:
* main        279e188 [origin/main] squash merge feature
  optout-gone c4a7820 [origin/optout-gone: gone] optout gone original commit
assertion: optout-gone retained when FM_FLEET_PRUNE=0

RESULT: all fleet prune end-to-end assertions passed
/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KV4VTKWG2T1GWHNK2P4ST3VR/fleet-prune-e2e/transcript.log

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • git status --short && git diff --stat a16660de607e52829a075baa81095049d4bbf02a..HEAD
  • Inspected bin/fm-fleet-sync.sh, bin/fm-bootstrap.sh, bin/fm-teardown.sh, AGENTS.md, and README.md for the changed behavior and docs.
  • Created local bare remotes and clones under /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KV4VTKWG2T1GWHNK2P4ST3VR/fleet-prune-e2e, then ran bin/fm-fleet-sync.sh <constructed-project> to verify a squash-merged gone branch is pruned without an ancestor check, a gone branch with a linked worktree is retained, a checked-out gone branch is retained, and main remains present.
  • Ran FM_FLEET_PRUNE=0 bin/fm-fleet-sync.sh <constructed-project> to verify pruning is disabled while fetch still marks the upstream as gone.
  • git status --short
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 warning
  • ⚠️ .github/workflows/ci.yml:18 - Configured shell lint command shellcheck bin/*.sh could not be run because shellcheck is not installed in this environment.
✅ **Push** - passed

✅ No issues found.

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.
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/<default> 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.
@kunchenguid
kunchenguid merged commit dc9b798 into main Jun 15, 2026
3 checks passed
@kunchenguid
kunchenguid deleted the fm/fleet-prune-gone branch June 15, 2026 05:32
@modfreq

modfreq commented Jun 28, 2026

Copy link
Copy Markdown

Audited against code: all four slices implemented and tested (layered hued equipment render, gender-gated beards, partial-hue cloth-dye shader, splash pickers + live preview reusing the real sprite stack). Registry counts meet/exceed the PRD; full suite green (490 tests). Remaining work is the cross-cutting UO-client art bake the PRD itself flagged, not a missing slice. Closing as delivered.

vipentti pushed a commit to vipentti/firstmate that referenced this pull request Aug 5, 2026
* 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.

* no-mistakes(review): Safely prune only merged gone branches

* no-mistakes(document): Document fleet pruning behavior

* 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/<default> 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.

* no-mistakes(document): Sync fleet-prune documentation
This was referenced Aug 7, 2026
@ki-za

ki-za commented Aug 29, 2026

Copy link
Copy Markdown

Scout plan (huddle-app second mate, 2026-08-29)

Proposed Options / Plan / Blocking / Lead decisions for this ticket, from ha-settings-plan scout report.

Current state

  • docs/prompts/index.html is already the single generated, self-contained page. docs/prompts/README.md:1-12 documents direct opening and lavish-axi docs/prompts/index.html.
  • docs/prompts/sources.json:1-28 inventories exactly one source, docs/vocabulary.md, and three explicit empty categories. scripts/build-prompts.ts:35-75, 109-220 validates the inventory, embeds exact source bytes, emits inline HTML/CSS, and makes bun run prompts:check fail when the page is stale.
  • It does not render representative prompt states, a Huddle batch, expected returns per state, a queue hint, a message action, sources for sections/member configurations, user-saved settings, or extension-contributed modules. Its inline CSS also duplicates current Lume values rather than consuming the fix(supervision): opencode-aware composer-idle detection + exclude supervisor pane from stale scan #49 role-token contract.
  • docs/review/2026-08-29-view-v3.html:60-150 and its assets provide the existing visual direction: member contributions, a replyFrame, menu/queue, narrow-column presentation, and two reply layouts. They are a separate dated review page, not the prompt inventory. app/src/lib/contract/sample.ts:3-125 provides the current fake Huddle with members, a question, attachment, exact reply, and a waiting Claude Code Huddle; it is development data and cannot become review authority.
  • docs/smoke/README.md:3-10 records an existing prompt-review screenshot. The current build check passed during this scout.

Options

  • Extend the generated inventory page with data-driven states — keeps one direct-open page, exact source capture, and stale-output checking. Recommended.
  • Hand-edit index.html for each review — fastest for one revision; immediately breaks the generator’s authoritative-source boundary.
  • Reuse the running Svelte Huddle app or dated review pages — shows the app well; requires a build/server or creates a second page and cannot show every exact prompt source consistently.

Recommended Plan

Ten years on, the review page still shows the exact source and a repeatable Huddle presentation without becoming either a prompt store or a second Huddle client. The future engineer is glad that adding a state is data, not a new page.

Modules

  • Extend docs/prompts/sources.json for every exact prompt-bearing source and its expected return. Keep the existing generated docs/prompts/index.html as the only review URL.
  • Add repository-owned review-state data under docs/prompts/ for one static fake Huddle and its representative states. It contains display data only; it is neither ledger data nor settings persistence.
  • Extend scripts/build-prompts.ts to validate and render source cards plus state cards: the rendered Huddle, exact source links, shared/optional section markers once feat(bin): recurring automated review sweep across the fleet #39 supplies them, member-configuration/settings/module provenance once feat: add opencode server backend #44 supplies it, expected return, and a selectable/copyable “none of these” brief.
  • Inline the fix(supervision): opencode-aware composer-idle detection + exclude supervisor pane from stale scan #49 theme role-token declarations during generation and make the page CSS use role tokens only. The emitted page stays self-contained and opens without a build or network request.
  • Update docs/prompts/README.md with the source/state update path and retain docs/smoke/ browser evidence after review with lavish-axi.

Seams

  • Repository prompt sources remain authoritative. The generator reads exact source bytes; source cards link to the plain Markdown sections and member configurations rather than copying their text into a private prompt store.
  • feat(bin): recurring automated review sweep across the fleet #39 supplies composition and section paths. feat: add opencode server backend #44 supplies user-saved settings, extension-contributed modules, provenance, and precedence. This page renders the resolved source model and reports it; it does not define any of them.
  • The static fake Huddle presents one batch containing every member contribution and question, then its steps of presentation. It also shows one Huddle at a time, an ordered read-only waiting-Huddle hint, one pre-send message action, attachment, exact reply, and Accept. It never calls the server, changes the ledger, or treats the fake as a Huddle authority.
  • fix(supervision): opencode-aware composer-idle detection + exclude supervisor pane from stale scan #49 owns the role-token-to-swatch mapping. The review page consumes the same role tokens; it never names a theme-local swatch. Later Svelte work is compared to the page but does not import it.
  • lavish-axi docs/prompts/index.html remains the review entry point. Its response is review evidence, not page state.

Validation criteria

  • bun run prompts:build followed by bun run prompts:check succeeds; editing any source or review-state data makes prompts:check fail until regeneration. Opening the generated file directly succeeds with no build or remote dependency.
  • Every state shows its expected return and the required material: members, complete batch and step-by-step presentation, question, attachment, exact reply, Accept, one message action, and the ordered waiting-Huddle hint without a switching control.
  • Each composed prompt specification links to its exact plain Markdown sections and member configuration; shared and optional sections are distinguishable. A user-saved settings module and an extension-contributed module are visibly attributed once feat: add opencode server backend #44 supplies them.
  • The “none of these” control copies/selects a short brief that the lead can send through Lavish, rather than silently changing the fixture.
  • Capture browser smoke evidence at regular width and at 30rem: the Huddle remains a bounded scrollable column and content stays legible. Review the same generated file with lavish-axi; record the resulting review evidence with the screenshot.
  • Held-out case: add a new section or presentation mode through the feat(bin): recurring automated review sweep across the fleet #39/feat: add opencode server backend #44 source model. It appears through inventory/state data and generation; no generator branch, new HTML page, Huddle-app route, or extension-boundary-specific case is needed.

Blocking?

non-blocking. The one-page generator, static Huddle states, expected-return notes, and Lavish review loop can ship now. #39 and #44 later add their source-model entries to the same page; they do not justify a second review surface.

Lead decisions

None. The page path, generated/static posture, review command, required Huddle presentation, and evidence boundary are recorded. Exact future section/module contents remain with #39 and #44.

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.

3 participants