Skip to content

feat(bin): add a gate-merge delivery mode and a shared-machine crew rule - #1849

Open
brycehowardd wants to merge 9 commits into
kunchenguid:mainfrom
brycehowardd:fm/fm-brief-broad-kill-guard
Open

feat(bin): add a gate-merge delivery mode and a shared-machine crew rule#1849
brycehowardd wants to merge 9 commits into
kunchenguid:mainfrom
brycehowardd:fm/fm-brief-broad-kill-guard

Conversation

@brycehowardd

Copy link
Copy Markdown

Intent

The developer wanted bin/fm-brief.sh, firstmate's crewmate-brief scaffold, fixed so that briefs for a gate-merge project no longer needed hand-editing before every dispatch (roughly 18 hand-patched briefs in one night). Three specific defects had to be addressed: add a shared-machine rule next to the existing shared-stash rule forbidding pattern kills (pkill -f / killall) and requiring crews to kill only exact pids they started via $!, after one crew's pkill -f vite killed four sibling crews' sessions on 2026-08-05; make delivery mode expressible per project as gate-merge so the generated definition of done tells crews to land their own work via the merge gate instead of stopping at a ready branch for firstmate to merge; and stop generated rule 1 ("never push to any remote and never open a PR") from contradicting a gate whose final step is a push, so rule 1 and the DoD agree. Stated acceptance criteria were that a gate-merge brief scaffolds correctly with no hand editing, the broad-kill rule appears for every project, existing local-only and direct-PR behavior stays unchanged, and the scaffold's own tests cover the new mode; the developer also asked whether fm-spawn should give each crew its own process group. Constraints included reading .agents/skills/firstmate-coding-guidelines/SKILL.md before editing, working only inside the disposable worktree, and consulting data/learnings.md entries from 2026-07-29, 2026-07-30, and 2026-08-05. During the later validation run the developer decided which review findings to fix (gate-retry-vs-rule-5, making rule 5 mode-shaped to exempt the documented gate-queue retry, plus the stale README mode enumeration) and explicitly declined three others as separate work items, forbidding any edit to the AGENTS.md section 7 authority wording as a captain boundary; they also barred self-installing tools, working around lint, or using --yes.

What Changed

  • Adds gate-merge as a fourth delivery mode across the task-delivery scripts: fm-brief.sh takes a required --gate <command> (refused on every other mode) and emits a gate-shaped definition of done plus a Delivery contract: gate=<command> line, fm-promote.sh/fm-spawn.sh accept the new mode, fm-project-mode.sh parses the registry's gate=`<command>` note behind a separate --gate accessor, fm-teardown.sh records a landed-via-gate done note, and fm-merge-local.sh points gate-merge tasks at their own gate.
  • Makes the spawn enforce the posture instead of announcing it: fm-spawn.sh now requires the brief's mode line and both gate occurrences (the contract line and the worker's "Run the gate from THIS worktree" step) to appear exactly once, compares each against the command the project's registry entry authorizes, refuses a gate-merge spawn whose project is registered under a conflicting posture, and falls back to a notice only when no registry entry is readable; the rigor ranking gains gate-merge as the least-gated mode.
  • Adds rule 8 (SHARED MACHINE) to every ship and scout brief - no pattern kills via pkill -f/killall, kill only exact pids captured from $!, no bare git stash against the shared .git - reshapes rules 1, 2 and 5 so a gate-merge brief no longer contradicts its own gate, and updates README, AGENTS.md, docs/architecture.md, docs/scripts.md and the project-management skill. Tests cover the new mode in fm-brief, fm-task-delivery and fm-teardown; fm-backend.test.sh also gained fm-line-cap-lib.sh in its old-bin sibling list to fix a pre-existing harness failure surfaced by the run.

Risk Assessment

✅ Low: This commit is a small, well-tested symmetry fix that applies an already-reviewed pattern to one more read and repairs a masked test failure, leaving the branch converged with only a one-line header omission outstanding.

Testing

I drove the change the way firstmate actually uses it: registered a gate-merge project, scaffolded a brief in one command, and read the generated markdown a crewmate would receive - the definition of done names the exact registered gate command, rule 1 now points at the gate instead of forbidding the push the DoD requires (the old contradictory string is absent), rule 2 excepts the gate, and rule 5 exempts only the documented gate-queue retry window while staying byte-identical in every other mode and in scout briefs. The shared-machine rule forbidding pattern kills and bare stashes appears exactly once in all four ship modes and in scout briefs. fm-spawn then refused a hand-patched landing command, an appended second one, and a gate-merge spawn against a project registered otherwise, while the authorized brief cleared every delivery check and left no task metadata behind. A local-only brief generated from the base commit differs from the HEAD one only by the new shared-machine rule, confirming existing behavior is unchanged. Every targeted test script passed, including tests/fm-backend.test.sh which was the pre-existing failure fixed in the previous round. No screenshot or rendered-UI artifact applies: this product's user surface is a CLI plus the generated brief markdown, both captured verbatim as transcript and file artifacts.

Evidence: gate-merge operator walkthrough (full CLI transcript)

========== 0. the captain's registry entry (data/projects.md) ==========
# projects

- acme-gate [gate-merge] - internal service with its own merge gate, gate=`./scripts/merge-gate.sh` (added 2026-08-05)
- acme-lib - ordinary library (added 2026-08-05)

$ bin/fm-project-mode.sh acme-gate
gate-merge off
$ bin/fm-project-mode.sh --gate acme-gate
./scripts/merge-gate.sh

========== 1. scaffold a gate-merge brief - one command, no hand editing ==========
$ env FM_HOME=/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home /Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/bin/fm-brief.sh gate-task-1 acme-gate --mode gate-merge --gate ./scripts/merge-gate.sh
scaffolded: /var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home/data/gate-task-1/brief.md (ship, mode=gate-merge; replace {TASK})
[exit 0]

========== 2. the generated definition of done (verbatim) ==========
# Definition of done
Delivery contract: mode=gate-merge
Delivery contract: gate=./scripts/merge-gate.sh
This task ships **gate-merge**: you land your own work by running the project's merge gate. Firstmate never merges it for you, so stopping at a ready branch leaves the task unfinished.
The task is complete only when it is committed on your branch `fm/gate-task-1` AND the gate reports it landed.
Run the gate from THIS worktree, with your branch checked out: `./scripts/merge-gate.sh`
Never cd into the project's primary checkout and never run git commands there yourself; the gate is the only thing authorized to act outside this worktree.
If the gate refuses because another gate run is already live, retry IN-TURN (`sleep 30`, then run it again, for up to about 8 minutes) and land as soon as it clears. Do NOT append `paused:` and end your turn for a gate queue; that idles until a supervisor intervenes. If it is still refusing after that, append `blocked: {the gate refusal}` and stop.
A failing gate parks your branch and leaves the default branch untouched: fix what it reported and run the gate again. Never bypass it, hand-merge, or push around it.
If the gate reports an outcome you cannot read as clearly landed or clearly parked, append `blocked:` with its exact exit code and message rather than assuming either.
When the gate reports the work landed, append `done: landed via the merge gate from fm/gate-task-1` to the status file and stop.

========== 3. the generated rules that used to contradict it (rules 1, 2, 5 and the new rule 8) ==========
1. The merge gate named in Definition of done is the only thing that lands your work: never merge by hand, never push `main` or your branch yourself, and never open a PR.
2. Stay inside this worktree; modify nothing outside it yourself. Running the merge gate below is the one authorized exception, and the gate handles everything outside this worktree on its own.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
   `echo "{state}: {one short line}" >> '/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home/state/gate-task-1.status'`
   States: working, needs-decision, blocked, paused, done, failed.
   Each append wakes firstmate, so report sparingly: only phase changes a supervisor
   would act on (setup done, bug reproduced, fix implemented, validation passed) and the
   needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines;
   firstmate reads your pane for that.
   A mid-task `working:` line (including setup complete) is nonterminal: do not end the
   turn after it; continue the same stage until a defined `done:` gate under Definition of done.
   Use `paused: {why}` - distinct from `blocked:` - ONLY when you are deliberately idling on a
   known external wait you expect to clear on its own (an upstream release, a rate-limit reset,
   a scheduled window): firstmate then leaves your idle pane alone and rechecks it on a long
   cadence instead of treating it as a possible wedge. Use `blocked:` when you are stuck and need help.
5. If you hit the same obstacle twice, append `blocked: {why}` and stop; firstmate will help.
   The one exception is the gate queue: while another gate run holds the lock, a repeated refusal is the
   documented wait under Definition of done, not the same obstacle twice, so keep retrying in-turn for that
   whole window and append `blocked: {the gate refusal}` only once the window is exhausted.
6. If a decision belongs above the implementation worker (product choices, destructive actions, ask-user findings),
   append `needs-decision: {summary of options}` and stop. Firstmate will apply the configured authority and reply with the decision.
   A decision or blocker you opened stays open until a `resolved` line carrying its exact key lands; a later `done:` or `working:` line never closes it, even when the answer is what started that work.
   Firstmate's reply normally writes that closing line at answer time; when a blocker or wait clears WITHOUT a firstmate reply, append `resolved: {how it cleared}` yourself (same `[key=<slug>]` if you opened it with one) as you resume.
7. Never stop, restart, or update the shared `no-mistakes` daemon - it is one instance serving
   every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes
   daemon error, append `blocked: {the daemon error}` and stop; only firstmate manages the daemon.
8. SHARED MACHINE: other crews work on this host and in sibling worktrees of this repo at the
   same time, and worktree isolation does not cover either the process table or `.git`.
   Never `pkill`/`killall` by pattern - a pattern as ordinary as `vite` or `node` also matches
   sibling crews' processes and their agent sessions. Kill only exact pids you started yourself
   (capture `$!` when you background a dev server, then `kill "$pid"`).
   Never bare `git stash`/`git stash pop`/`git stash drop` - the stash stack lives in the shared
   `.git` and a popped stash leaves no reflog entry, so you can destroy a sibling's uncommitted
   work unrecoverably. Commit work in progress in this worktree instead - a commit here is yours alone,
   whether you are on your own branch or at the detached HEAD a scout worktree runs at.


========== 4. rule 1 no longer forbids what the definition of done requires ==========
$ grep -c "never push to any remote and never open a PR" brief.md  # the old contradiction
0

========== 5. --gate is required by gate-merge and refused everywhere else ==========
$ env FM_HOME=/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home /Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/bin/fm-brief.sh gate-task-2 acme-gate --mode gate-merge
error: --mode gate-merge requires --gate <command>; pass the landing command recorded in the project's data/projects.md entry as gate=`<command>` (for example --gate ./scripts/merge-gate.sh) so the definition of done names the exact command that lands the work
[exit 1]
$ env FM_HOME=/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home /Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/bin/fm-brief.sh gate-task-3 acme-lib --mode local-only --gate ./scripts/merge-gate.sh
error: --gate applies only to --mode gate-merge; every other delivery path lands through firstmate or a PR, so a gate command here would be recorded nowhere
[exit 1]
$ env FM_HOME=/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home /Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/bin/fm-brief.sh gate-task-4 acme-lib --mode nope
error: --mode must be one of no-mistakes, direct-PR, local-only, gate-merge (got 'nope')
[exit 1]

========== 6. the shared-machine rule is in EVERY crewmate brief (the pkill -f vite incident) ==========
gate-task-1            1 x SHARED MACHINE
shared-no-mistakes     1 x SHARED MACHINE
shared-direct-PR       1 x SHARED MACHINE
shared-local-only      1 x SHARED MACHINE
shared-scout           1 x SHARED MACHINE

-- rule 8 as the crewmate reads it --
8. SHARED MACHINE: other crews work on this host and in sibling worktrees of this repo at the
   same time, and worktree isolation does not cover either the process table or `.git`.
   Never `pkill`/`killall` by pattern - a pattern as ordinary as `vite` or `node` also matches
   sibling crews' processes and their agent sessions. Kill only exact pids you started yourself
   (capture `$!` when you background a dev server, then `kill "$pid"`).
   Never bare `git stash`/`git stash pop`/`git stash drop` - the stash stack lives in the shared
   `.git` and a popped stash leaves no reflog entry, so you can destroy a sibling's uncommitted
   work unrecoverably. Commit work in progress in this worktree instead - a commit here is yours alone,
   whether you are on your own branch or at the detached HEAD a scout worktree runs at.

========== 7. spawn enforces the registered posture and the registered gate ==========
-- 7a. a hand-patched landing command is refused --
$ fm-spawn.sh gate-task-1 <proj> claude --mode gate-merge --yolo off
error: gate mismatch for gate-task-1: acme-gate's registry entry authorizes './scripts/merge-gate.sh' but /var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home/data/gate-task-1/brief.md records gate='./scripts/merge-gate.sh' and tells the worker to run './scripts/i-made-this-up.sh'; re-scaffold the brief with the registered gate, or have the captain change the registry entry - never hand-patch one brief
[exit 1]

-- 7b. an appended second landing command is refused too --
$ fm-spawn.sh gate-task-1 <proj> claude --mode gate-merge --yolo off
error: /var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home/data/gate-task-1/brief.md must name its gate command exactly once in each of the two required places (the machine-readable "Delivery contract: gate=" line and the "Run the gate from THIS worktree" step the worker follows), but it carries 1 of the first and 2 of the second; re-scaffold it with bin/fm-brief.sh --mode gate-merge --gate './scripts/merge-gate.sh' instead of editing it, so exactly one authorized landing command reaches the worker
[exit 1]

-- 7c. gate-merge against a project registered otherwise is refused --
$ fm-spawn.sh lib-task-1 <acme-lib> claude --mode gate-merge --yolo off
error: lib-task-1 passed --mode gate-merge but acme-lib is registered no-mistakes; gate-merge lands work on the default branch with no approval step in front of it, so it ships only for a project the captain registered gate-merge - correct the flag, or have the captain re-register acme-lib first
[exit 1]

-- 7d. the authorized brief clears every delivery check (fake tmux then refuses, so nothing is created) --
$ fm-spawn.sh gate-task-1 <acme-gate> claude --mode gate-merge --yolo off
[exit 1]
task metadata written? no (stopped at the backend, past all delivery checks)

========== 8. existing local-only output is unchanged apart from the new shared-machine rule ==========
$ diff base(6c206ed) HEAD  # local-only brief
25c25
<    `echo "{state}: {one short line}" >> '/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/base-home/state/shared-local-only.status'`
---
>    `echo "{state}: {one short line}" >> '/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home/state/shared-local-only.status'`
44a45,53
> 8. SHARED MACHINE: other crews work on this host and in sibling worktrees of this repo at the
>    same time, and worktree isolation does not cover either the process table or `.git`.
>    Never `pkill`/`killall` by pattern - a pattern as ordinary as `vite` or `node` also matches
>    sibling crews' processes and their agent sessions. Kill only exact pids you started yourself
>    (capture `$!` when you background a dev server, then `kill "$pid"`).
>    Never bare `git stash`/`git stash pop`/`git stash drop` - the stash stack lives in the shared
>    `.git` and a popped stash leaves no reflog entry, so you can destroy a sibling's uncommitted
>    work unrecoverably. Commit work in progress in this worktree instead - a commit here is yours alone,
>    whether you are on your own branch or at the detached HEAD a scout worktree runs at.
47c56
< If `AGENTS.md` or `CLAUDE.md` already exists, or if this task produced durable project-intrinsic knowledge, run `/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T/fm-gate-ev.WoVSuA/bin/fm-ensure-agents-md.sh .` in the worktree.
---
> If `AGENTS.md` or `CLAUDE.md` already exists, or if this task produced durable project-intrinsic knowledge, run `/Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/bin/fm-ensure-agents-md.sh .` in the worktree.
50c59
< If you touch a project `AGENTS.md` that lacks `## Maintaining this file`, add that short self-governance section from `/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T/fm-gate-ev.WoVSuA/bin/fm-ensure-agents-md.sh` in the same pass.
---
> If you touch a project `AGENTS.md` that lacks `## Maintaining this file`, add that short self-governance section from `/Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/bin/fm-ensure-agents-md.sh` in the same pass.
[diff exit 1]

artifacts written to /var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T/no-mistakes-evidence/01KZCNQM7NDREW071DF6T7QXAT
Evidence: generated gate-merge brief (the crewmate-facing document, no hand editing)
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.

# Task
{TASK}

# Herdr lifecycle declaration - NOT ENABLED
**HARD SAFETY GATE:** this scaffold cannot inspect the task text that replaces `{TASK}` later.
If the task will start, stop, delete, restart, profile, or otherwise drive Herdr lifecycle behavior, stop and regenerate the brief with `--herdr-lab` before dispatch.
Do not add Herdr lifecycle commands to this unguarded brief by hand.

# Setup
You are in a disposable git worktree of acme-gate, at a detached HEAD on a clean default branch.

**Verify isolation before anything else.** Run `pwd -P` and `git rev-parse --show-toplevel`; both must resolve to the disposable task worktree you were launched in, such as a treehouse pool path or an Orca-managed worktree, not the primary checkout firstmate operates from.
The path check is authoritative: `git rev-parse --git-dir` and `git rev-parse --git-common-dir` can help inspect the repo, but they do not prove you are outside the primary checkout.
If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append `blocked: launched in primary checkout, not an isolated worktree` to the status file and stop.

1. First action: create your branch: `git checkout -b fm/gate-task-1`

# Rules
1. The merge gate named in Definition of done is the only thing that lands your work: never merge by hand, never push `main` or your branch yourself, and never open a PR.
2. Stay inside this worktree; modify nothing outside it yourself. Running the merge gate below is the one authorized exception, and the gate handles everything outside this worktree on its own.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
   `echo "{state}: {one short line}" >> '/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home/state/gate-task-1.status'`
   States: working, needs-decision, blocked, paused, done, failed.
   Each append wakes firstmate, so report sparingly: only phase changes a supervisor
   would act on (setup done, bug reproduced, fix implemented, validation passed) and the
   needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines;
   firstmate reads your pane for that.
   A mid-task `working:` line (including setup complete) is nonterminal: do not end the
   turn after it; continue the same stage until a defined `done:` gate under Definition of done.
   Use `paused: {why}` - distinct from `blocked:` - ONLY when you are deliberately idling on a
   known external wait you expect to clear on its own (an upstream release, a rate-limit reset,
   a scheduled window): firstmate then leaves your idle pane alone and rechecks it on a long
   cadence instead of treating it as a possible wedge. Use `blocked:` when you are stuck and need help.
5. If you hit the same obstacle twice, append `blocked: {why}` and stop; firstmate will help.
   The one exception is the gate queue: while another gate run holds the lock, a repeated refusal is the
   documented wait under Definition of done, not the same obstacle twice, so keep retrying in-turn for that
   whole window and append `blocked: {the gate refusal}` only once the window is exhausted.
6. If a decision belongs above the implementation worker (product choices, destructive actions, ask-user findings),
   append `needs-decision: {summary of options}` and stop. Firstmate will apply the configured authority and reply with the decision.
   A decision or blocker you opened stays open until a `resolved` line carrying its exact key lands; a later `done:` or `working:` line never closes it, even when the answer is what started that work.
   Firstmate's reply normally writes that closing line at answer time; when a blocker or wait clears WITHOUT a firstmate reply, append `resolved: {how it cleared}` yourself (same `[key=<slug>]` if you opened it with one) as you resume.
7. Never stop, restart, or update the shared `no-mistakes` daemon - it is one instance serving
   every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes
   daemon error, append `blocked: {the daemon error}` and stop; only firstmate manages the daemon.
8. SHARED MACHINE: other crews work on this host and in sibling worktrees of this repo at the
   same time, and worktree isolation does not cover either the process table or `.git`.
   Never `pkill`/`killall` by pattern - a pattern as ordinary as `vite` or `node` also matches
   sibling crews' processes and their agent sessions. Kill only exact pids you started yourself
   (capture `$!` when you background a dev server, then `kill "$pid"`).
   Never bare `git stash`/`git stash pop`/`git stash drop` - the stash stack lives in the shared
   `.git` and a popped stash leaves no reflog entry, so you can destroy a sibling's uncommitted
   work unrecoverably. Commit work in progress in this worktree instead - a commit here is yours alone,
   whether you are on your own branch or at the detached HEAD a scout worktree runs at.

# Project memory
If `AGENTS.md` or `CLAUDE.md` already exists, or if this task produced durable project-intrinsic knowledge, run `/Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/bin/fm-ensure-agents-md.sh .` in the worktree.
Record only project knowledge useful to almost every future session.
For anything the codebase already shows, prefer a pointer to the authoritative file, command, or doc over copying the detail.
If you touch a project `AGENTS.md` that lacks `## Maintaining this file`, add that short self-governance section from `/Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/bin/fm-ensure-agents-md.sh` in the same pass.
Keep it proportionate: skip `AGENTS.md` edits for trivial tasks that produced no durable project knowledge.

# Definition of done
Delivery contract: mode=gate-merge
Delivery contract: gate=./scripts/merge-gate.sh
This task ships **gate-merge**: you land your own work by running the project's merge gate. Firstmate never merges it for you, so stopping at a ready branch leaves the task unfinished.
The task is complete only when it is committed on your branch `fm/gate-task-1` AND the gate reports it landed.
Run the gate from THIS worktree, with your branch checked out: `./scripts/merge-gate.sh`
Never cd into the project's primary checkout and never run git commands there yourself; the gate is the only thing authorized to act outside this worktree.
If the gate refuses because another gate run is already live, retry IN-TURN (`sleep 30`, then run it again, for up to about 8 minutes) and land as soon as it clears. Do NOT append `paused:` and end your turn for a gate queue; that idles until a supervisor intervenes. If it is still refusing after that, append `blocked: {the gate refusal}` and stop.
A failing gate parks your branch and leaves the default branch untouched: fix what it reported and run the gate again. Never bypass it, hand-merge, or push around it.
If the gate reports an outcome you cannot read as clearly landed or clearly parked, append `blocked:` with its exact exit code and message rather than assuming either.
When the gate reports the work landed, append `done: landed via the merge gate from fm/gate-task-1` to the status file and stop.
Evidence: generated local-only brief (unchanged path, now carrying rule 8)
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.

# Task
{TASK}

# Herdr lifecycle declaration - NOT ENABLED
**HARD SAFETY GATE:** this scaffold cannot inspect the task text that replaces `{TASK}` later.
If the task will start, stop, delete, restart, profile, or otherwise drive Herdr lifecycle behavior, stop and regenerate the brief with `--herdr-lab` before dispatch.
Do not add Herdr lifecycle commands to this unguarded brief by hand.

# Setup
You are in a disposable git worktree of acme-lib, at a detached HEAD on a clean default branch.

**Verify isolation before anything else.** Run `pwd -P` and `git rev-parse --show-toplevel`; both must resolve to the disposable task worktree you were launched in, such as a treehouse pool path or an Orca-managed worktree, not the primary checkout firstmate operates from.
The path check is authoritative: `git rev-parse --git-dir` and `git rev-parse --git-common-dir` can help inspect the repo, but they do not prove you are outside the primary checkout.
If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append `blocked: launched in primary checkout, not an isolated worktree` to the status file and stop.

1. First action: create your branch: `git checkout -b fm/shared-local-only`

# Rules
1. Never push to any remote and never open a PR. Work only on your `fm/shared-local-only` branch; firstmate handles the merge into local `main`.
2. Stay inside this worktree; modify nothing outside it.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
   `echo "{state}: {one short line}" >> '/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home/state/shared-local-only.status'`
   States: working, needs-decision, blocked, paused, done, failed.
   Each append wakes firstmate, so report sparingly: only phase changes a supervisor
   would act on (setup done, bug reproduced, fix implemented, validation passed) and the
   needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines;
   firstmate reads your pane for that.
   A mid-task `working:` line (including setup complete) is nonterminal: do not end the
   turn after it; continue the same stage until a defined `done:` gate under Definition of done.
   Use `paused: {why}` - distinct from `blocked:` - ONLY when you are deliberately idling on a
   known external wait you expect to clear on its own (an upstream release, a rate-limit reset,
   a scheduled window): firstmate then leaves your idle pane alone and rechecks it on a long
   cadence instead of treating it as a possible wedge. Use `blocked:` when you are stuck and need help.
5. If you hit the same obstacle twice, append `blocked: {why}` and stop; firstmate will help.
6. If a decision belongs above the implementation worker (product choices, destructive actions, ask-user findings),
   append `needs-decision: {summary of options}` and stop. Firstmate will apply the configured authority and reply with the decision.
   A decision or blocker you opened stays open until a `resolved` line carrying its exact key lands; a later `done:` or `working:` line never closes it, even when the answer is what started that work.
   Firstmate's reply normally writes that closing line at answer time; when a blocker or wait clears WITHOUT a firstmate reply, append `resolved: {how it cleared}` yourself (same `[key=<slug>]` if you opened it with one) as you resume.
7. Never stop, restart, or update the shared `no-mistakes` daemon - it is one instance serving
   every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes
   daemon error, append `blocked: {the daemon error}` and stop; only firstmate manages the daemon.
8. SHARED MACHINE: other crews work on this host and in sibling worktrees of this repo at the
   same time, and worktree isolation does not cover either the process table or `.git`.
   Never `pkill`/`killall` by pattern - a pattern as ordinary as `vite` or `node` also matches
   sibling crews' processes and their agent sessions. Kill only exact pids you started yourself
   (capture `$!` when you background a dev server, then `kill "$pid"`).
   Never bare `git stash`/`git stash pop`/`git stash drop` - the stash stack lives in the shared
   `.git` and a popped stash leaves no reflog entry, so you can destroy a sibling's uncommitted
   work unrecoverably. Commit work in progress in this worktree instead - a commit here is yours alone,
   whether you are on your own branch or at the detached HEAD a scout worktree runs at.

# Project memory
If `AGENTS.md` or `CLAUDE.md` already exists, or if this task produced durable project-intrinsic knowledge, run `/Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/bin/fm-ensure-agents-md.sh .` in the worktree.
Record only project knowledge useful to almost every future session.
For anything the codebase already shows, prefer a pointer to the authoritative file, command, or doc over copying the detail.
If you touch a project `AGENTS.md` that lacks `## Maintaining this file`, add that short self-governance section from `/Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/bin/fm-ensure-agents-md.sh` in the same pass.
Keep it proportionate: skip `AGENTS.md` edits for trivial tasks that produced no durable project knowledge.

# Definition of done
Delivery contract: mode=local-only
This task ships **local-only**: no remote, no PR, no pipeline.
The task is complete only when committed on your branch `fm/shared-local-only`. Do NOT push, do NOT open a PR, do NOT merge.
Keep your branch a clean fast-forward onto the current default branch - if `main` has advanced, rebase onto it so the eventual merge stays a fast-forward.
When it is implemented and committed, append `done: ready in branch fm/shared-local-only` to the status file and stop.
The configured merge authority approves the ready branch, then firstmate merges it into local `main` through the guarded fast-forward path.
Evidence: generated scout brief (shared-machine rule reaches scouts too)
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.

# Task
{TASK}

# Herdr lifecycle declaration - NOT ENABLED
**HARD SAFETY GATE:** this scaffold cannot inspect the task text that replaces `{TASK}` later.
If the task will start, stop, delete, restart, profile, or otherwise drive Herdr lifecycle behavior, stop and regenerate the brief with `--herdr-lab` before dispatch.
Do not add Herdr lifecycle commands to this unguarded brief by hand.

# Setup
You are in a disposable git worktree of acme-lib, at a detached HEAD on a clean default branch.
This is a SCOUT task: the deliverable is a written report, not a PR.
The worktree is your laboratory - install, run, edit, and make scratch commits freely; all of it is discarded at teardown.
The report is the only thing that survives, so anything worth keeping must be in it.

# Rules
1. Never push to any remote and never open a PR.
2. Stay inside this worktree; the only files you may write outside it are the report and the status file below.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
   `echo "{state}: {one short line}" >> '/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home/state/shared-scout.status'`
   States: working, needs-decision, blocked, paused, done, failed.
   Each append wakes firstmate, so report sparingly: only phase changes a supervisor
   would act on and the needs-decision/blocked/paused/done/failed states. No step-by-step
   FYI progress lines; firstmate reads your pane for that.
   Use `paused: {why}` - distinct from `blocked:` - ONLY when you are deliberately idling on a
   known external wait you expect to clear on its own (an upstream release, a rate-limit reset):
   firstmate then leaves your idle pane alone and rechecks it on a long cadence instead of
   treating it as a possible wedge. Use `blocked:` when you are stuck and need help.
5. If you hit the same obstacle twice, append `blocked: {why}` and stop; firstmate will help.
6. If a decision belongs to a human (product choices, destructive actions),
   append `needs-decision: {summary of options}` and stop. Firstmate will reply with the decision.
   A decision or blocker you opened stays open until a `resolved` line carrying its exact key lands; a later `done:` or `working:` line never closes it, even when the answer is what started that work.
   Firstmate's reply normally writes that closing line at answer time; when a blocker or wait clears WITHOUT a firstmate reply, append `resolved: {how it cleared}` yourself (same `[key=<slug>]` if you opened it with one) as you resume.
7. Never stop, restart, or update the shared `no-mistakes` daemon - it is one instance serving
   every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes
   daemon error, append `blocked: {the daemon error}` and stop; only firstmate manages the daemon.
8. SHARED MACHINE: other crews work on this host and in sibling worktrees of this repo at the
   same time, and worktree isolation does not cover either the process table or `.git`.
   Never `pkill`/`killall` by pattern - a pattern as ordinary as `vite` or `node` also matches
   sibling crews' processes and their agent sessions. Kill only exact pids you started yourself
   (capture `$!` when you background a dev server, then `kill "$pid"`).
   Never bare `git stash`/`git stash pop`/`git stash drop` - the stash stack lives in the shared
   `.git` and a popped stash leaves no reflog entry, so you can destroy a sibling's uncommitted
   work unrecoverably. Commit work in progress in this worktree instead - a commit here is yours alone,
   whether you are on your own branch or at the detached HEAD a scout worktree runs at.

# Definition of done
Write your findings to `/var/folders/sd/0dh5g4vx3tsdbkcds9b84zd80000gn/T//fm-gate-ev.WoVSuA/fm-home/data/shared-scout/report.md`.
The report must stand alone: what you did, what you found, the evidence (commands run, output, file:line references), and what you recommend.
Before reporting done, read and follow `/Users/brycehoward/.no-mistakes/worktrees/0c4e126807a2/01KZCNQM7NDREW071DF6T7QXAT/.agents/skills/decision-hold-lifecycle/SKILL.md` and pass its shared completion gate for the report and any visual review.
When the report is complete, append `done: {one-line conclusion}` to the status file and stop.
If your findings reveal work that should ship (e.g. you reproduced a bug and the fix is clear), say so in the report; firstmate may promote this task in place, and you would then receive mode-specific ship instructions as a follow-up message.
Evidence: generated gate-merge definition of done and rules (excerpt)
# Definition of done
Delivery contract: mode=gate-merge
Delivery contract: gate=./scripts/merge-gate.sh
This task ships **gate-merge**: you land your own work by running the project's merge gate. Firstmate never merges it for you, so stopping at a ready branch leaves the task unfinished.
The task is complete only when it is committed on your branch `fm/gate-task-1` AND the gate reports it landed.
Run the gate from THIS worktree, with your branch checked out: `./scripts/merge-gate.sh`
...

# Rules
1. The merge gate named in Definition of done is the only thing that lands your work: never merge by hand, never push `main` or your branch yourself, and never open a PR.
2. Stay inside this worktree; modify nothing outside it yourself. Running the merge gate below is the one authorized exception, and the gate handles everything outside this worktree on its own.
5. If you hit the same obstacle twice, append `blocked: {why}` and stop; firstmate will help.
The one exception is the gate queue: while another gate run holds the lock, a repeated refusal is the
documented wait under Definition of done, not the same obstacle twice, ...
8. SHARED MACHINE: other crews work on this host and in sibling worktrees of this repo at the
same time, and worktree isolation does not cover either the process table or `.git`.
Never `pkill`/`killall` by pattern - a pattern as ordinary as `vite` or `node` also matches
sibling crews' processes and their agent sessions. Kill only exact pids you started yourself
(capture `$!` when you background a dev server, then `kill "$pid"`).
Evidence: fm-spawn refusals for an unauthorized gate (excerpt)
$ fm-spawn.sh gate-task-1 <proj> claude --mode gate-merge --yolo off # hand-patched step
error: gate mismatch for gate-task-1: acme-gate's registry entry authorizes './scripts/merge-gate.sh' but .../brief.md records gate='./scripts/merge-gate.sh' and tells the worker to run './scripts/i-made-this-up.sh'; re-scaffold the brief with the registered gate, or have the captain change the registry entry - never hand-patch one brief
[exit 1]

$ fm-spawn.sh gate-task-1 <proj> claude --mode gate-merge --yolo off # appended second step
error: .../brief.md must name its gate command exactly once in each of the two required places ... but it carries 1 of the first and 2 of the second; re-scaffold it with bin/fm-brief.sh --mode gate-merge --gate './scripts/merge-gate.sh' instead of editing it
[exit 1]

$ fm-spawn.sh lib-task-1 <acme-lib> claude --mode gate-merge --yolo off # project registered no-mistakes
error: lib-task-1 passed --mode gate-merge but acme-lib is registered no-mistakes; gate-merge lands work on the default branch with no approval step in front of it, so it ships only for a project the captain registered gate-merge
[exit 1]

$ fm-spawn.sh gate-task-1 <acme-gate> claude --mode gate-merge --yolo off # authorized brief
[exit 1] # cleared every delivery check, stopped at the deliberately failing fake tmux
task metadata written? no (stopped at the backend, past all delivery checks)
- Outcome: 🔧 1 issue found → auto-fixed ✅ across 2 runs (44m41s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ bin/fm-home-seed.sh:469 - The secondmate seed guards in clone_project (:469) and validate_seed_project (:496) are denylists that only refuse local-only, so a newly-registered gate-merge project is silently accepted into a secondmate home even though their own error text and docs/configuration.md:179 both say secondmate routes cover only no-mistakes and direct-PR. The remote counterparts are allowlists (bin/fm-remote-home-seed.sh:146, bin/fm-remote-home-provision.sh:216) and hard-fail a gate-merge project with "unsupported delivery mode". Local and remote secondmate provisioning therefore disagree for the new mode: fm-home-seed.sh seeds it, fm-remote-home-seed.sh refuses it. Decide whether gate-merge projects are routable to secondmates (a secondmate's crew would then run the captain's landing gate from a separate clone) and make both paths and docs/configuration.md agree; converting the local guards to the same allowlist shape the remote ones use is the fail-closed option.
  • ⚠️ bin/fm-spawn.sh:1367 - gate-merge is the first mode where the worker itself lands work on the default branch with no approval step, but the only thing standing between a mistaken intake and that outcome is this advisory notice on stderr. tests/fm-task-delivery.test.sh confirms the intended behavior: a project registered no-mistakes shipped as gate-merge prints a notice and proceeds. For every previous mode a wrong --mode at worst got a pushed branch or an unmerged PR, with a merge authority still in front of the default branch; here it grants autonomous landing authority. Compounding it, nothing ties --mode gate-merge or the arbitrary --gate &lt;command&gt; string to the project's registered posture or to the gate command that .agents/skills/project-management/SKILL.md:44 requires be recorded in the registry note - fm-brief.sh deliberately never reads the registry, and fm-spawn.sh never re-derives the gate. Consider whether a gate-merge spawn should require the project to actually be registered gate-merge (hard refusal rather than a notice), since AGENTS.md:301 makes that registration the captain's standing authorization for the gate.
  • ℹ️ bin/fm-brief.sh:426 - The gate-merge branch re-types the baseline rule-5 sentence verbatim instead of extending the default assigned four lines earlier at :407, so the same string now lives in two places and an edit to one silently diverges from the other. Appending instead keeps a single source: RULE5="$RULE5"$'\n'' The one exception is the gate queue: ...'. The new test's assert_contains "$rule5" "$baseline" would catch a divergence, but only after it ships in a hand-checked brief.
  • ℹ️ bin/fm-brief.sh:311 - The shared-machine rule's safe alternative to stashing is "Commit work in progress to your own branch instead", and it is rendered into scout briefs too (:382). A scout brief never creates a branch: its Setup says the worktree is at a detached HEAD (:355) and there is no git checkout -b step, unlike the ship brief's :503. The advice still works mechanically (a detached-HEAD commit is fine, and the worktree is scratch), but the one line that gives the alternative to a destructive shared-.git operation names something a scout does not have, which is the kind of small mismatch that invites a worker to improvise back toward git stash.

🔧 Fix: enforce the registered gate-merge posture and gate at spawn
3 issues (2 warnings, 1 info) still open:

  • ⚠️ bin/fm-brief.sh:444 - The gate-merge brief records the landing command twice: the machine-readable Delivery contract: gate=$GATE at :441 and the prose instruction Run the gate from THIS worktree, with your branch checked out: \$GATE`` at :444. bin/fm-spawn.sh:1381 verifies only the first. The worker obeys the second - it is the imperative sentence under Definition of done, and the contract line is explicitly framed as machine-readable metadata. So a brief whose prose line is hand-patched to a different command, with the contract line left alone, passes every new check and the worker runs the unauthorized command with rule 2's explicit "the gate handles everything outside this worktree" carve-out. That is exactly the hand-patching this enforcement exists to remove, and duplicating the value is what reopens it. Either have fm-spawn also require the registered gate to appear in the prose line, or stop repeating the command in the prose and have the DoD point at the contract line as the single authority.
  • ⚠️ docs/architecture.md:224 - Line 224 still says "a ship spawn that drops below the registered rigor prints a deviation notice and continues", which is now false for gate-merge - bin/fm-spawn.sh:1378 exits 1 on a conflicting registered posture. Line 225 says bin/fm-project-mode.sh is "the one registry parser for the mechanical consumers that have no task in hand: fleet sync's local-only skip and home seeding's refusal and no-mistakes initialization", but the new --gate accessor is consumed by a task-in-hand caller, which the parser's own header at bin/fm-project-mode.sh:12-13 now states. docs/scripts.md:59 carries the same stale "for fleet sync and home seeding" scope. AGENTS.md, both script headers, and .agents/skills/project-management/SKILL.md were all updated in this commit; the architecture doc the README points to as full detail was not, so it now asserts notice-and-proceed for the one mode that refuses.
  • ℹ️ bin/fm-project-mode.sh:121 - The unknown-mode branch warns "defaulting to no-mistakes off" and sets mode=no-mistakes, but on the --gate path the very next block at :127 exits 3 with no output instead of defaulting - that is the intended behavior (an unreadable annotation is not a posture anyone can act on), yet the diagnostic tells the reader the opposite happened. bin/fm-spawn.sh discards this stderr with 2>/dev/null, so the only reader is a human running the accessor directly, who is told a default was applied and then gets empty stdout and a non-zero status. Make the warn text conditional on GATE_ONLY, or emit the unverified-posture wording alongside the exit 3.

🔧 Fix: verify the registered gate in both brief occurrences
1 warning still open:

  • ⚠️ bin/fm-spawn.sh:1387 - Both gate extractions end in head -n 1 (:1387 for the contract line, :1389 for the worker's step), so only the first occurrence of each fixed shape is compared against REGISTERED_GATE. Appending a second Run the gate from THIS worktree, with your branch checked out: ./hand-patched.sh`` line below the generated one leaves the verified first copy registry-clean, passes every check, and hands the worker a brief containing an unauthorized landing command under rule 2's explicit outside-the-worktree carve-out - the same class of hand edit this commit set out to make impossible, just by appending rather than editing in place. The asymmetry is worth noting: an occurrence injected ABOVE the generated one (for example through the {TASK} placeholder, which is filled in after scaffolding and sits earlier in the file) is caught, because head -n 1 then picks the injected value and it fails the equality check. Only a later duplicate slips through. Closing it is one line: require exactly one match of each shape (grep -c ... = 1) before comparing, or compare every extracted value rather than the first.

🔧 Fix: require the registered gate exactly once per brief shape
2 infos still open:

  • ℹ️ bin/fm-spawn.sh:1358 - This commit removed every positional read from the gate comparison path (exactly-once counts, then extraction with no head/tail), but the sibling BRIEF_MODE extraction three lines above the same block still ends in head -n 1. A brief carrying two Delivery contract: mode= lines has only its first compared against the explicit --mode, so a second, contradictory one passes unnoticed - the same brief/spawn drift this block exists to prevent, on the other contract line. The impact is materially lower than the gate case and this line predates the branch: the mode is validated against a closed set and against --mode, so a duplicate cannot grant authority the way an extra landing command can, and for a gate-merge task the gate checks are independent and strict. Raising it only because firstmate's instruction for this round said to leave no positional assumption in the comparison path; scoping that to the gate is a defensible reading, but it is worth a deliberate decision rather than an oversight. Applying the same exactly-once count to the mode line would make the block uniform.
  • ℹ️ tests/fm-task-delivery.test.sh:336 - scaffold_gate_brief ends in || fail &#34;...&#34; but returns the brief path on stdout, so all six call sites invoke it as brief=$(scaffold_gate_brief &lt;id&gt;). fail() does exit 1, which inside a command substitution terminates only the subshell - the test script keeps running with brief set to the empty string. bin/fm-test-run.sh:1497 decides pass/fail purely from the script's exit code, so a genuine bin/fm-brief.sh failure here cannot fail the run through this path; it instead surfaces later as printf ... &gt;&gt; &#34;&#34; and a spawn refusal about a missing brief, and the eventual assertion message points at the wrong cause. The neighbouring e9 case at :290 calls fm-brief.sh directly with || fail outside a substitution and does not have this problem. Have the helper assign a caller-visible variable instead of printing the path, or guard the call sites with [ -n &#34;$brief&#34; ] || fail ....

🔧 Fix: count the brief's delivery contract line before comparing
1 info still open:

  • ℹ️ bin/fm-spawn.sh:12 - The usage header enumerates the outcomes for the brief's mode line as exactly two - "REFUSES a mismatch" and "a brief scaffolded before that line existed warns once and launches on the flag" - but this commit added a third at :1363, refusing a brief that carries two or more "Delivery contract: mode=" lines. The gap is visible inside the same paragraph: :19-21 of this header was updated in the previous round to state the gate's exactly-once rule explicitly, so the header now documents exactly-once for one contract line and not for its sibling. The new rule is currently captured only in the inline comment at :1355-1356, which --help does not print and which AGENTS.md section 11 does not treat as the contract surface. One clause on the mode sentence, matching the wording already used for the gate, makes the header complete.
🔧 **Test** - 1 issue found → auto-fixed ✅
  • 🚨 tests/fm-backend.test.sh:144 - tests/fm-backend.test.sh failed deterministically on fm-send --key: old vs new exit code: expected exit 1, got 0, aborting the script after ~3s. Root cause is a stale test-harness list, not this branch: base commit 6c206ed added . &#34;$SCRIPT_DIR/fm-line-cap-lib.sh&#34; to bin/fm-send.sh:103 but did not add fm-line-cap-lib.sh to OLD_BIN_UNCHANGED_SIBLINGS, so build_old_bin()'s synthetic "old bin" could not source it and died under set -eu (rc_old=1) while the current tree exited 0. bin/fm-send.sh, bin/fm-line-cap-lib.sh and tests/fm-backend.test.sh are all untouched by 6c206ed..0e18a21, so this is pre-existing on origin/main. Fixed by adding fm-line-cap-lib.sh to the sibling list; the script now runs its full assertion set (26.5s) and passes.
  • bin/fm-test-run.sh --changed --base 6c206ed (46 scripts; 1 failure, diagnosed and fixed below)
  • bin/fm-test-run.sh tests/fm-brief.test.sh tests/fm-task-delivery.test.sh tests/fm-teardown.test.sh
  • bin/fm-test-run.sh tests/fm-backend.test.sh (x4 before the fix to confirm determinism, x2 after)
  • Manual E2E: registered - acme-app [gate-merge] - ... gate=./scripts/merge-gate.sh --push ... in data/projects.md, then bin/fm-project-mode.sh acme-app and bin/fm-project-mode.sh --gate acme-app
  • Manual E2E: bin/fm-brief.sh acme-42 acme-app --mode gate-merge --gate &#39;./scripts/merge-gate.sh --push&#39; and inspected the generated brief.md verbatim
  • Manual E2E: bin/fm-spawn.sh acme-42 &lt;proj&gt; --mode gate-merge --yolo off against (a) a matching registered gate, (b) a hand-patched Run the gate from THIS worktree... step, (c) an injected second landing command, (d) a project registered no-mistakes, (e) an unregistered project; asserted no state/&lt;id&gt;.meta was written on each refusal
  • Manual E2E refusals: bin/fm-brief.sh ... --mode gate-merge with no --gate, and --mode local-only --gate ./scripts/merge-gate.sh
  • Regression diff: generated no-mistakes / direct-PR / local-only / scout briefs with bin/fm-brief.sh at base 6c206ed vs target 0e18a21 and diffed them
  • Edge case: registered and scaffolded a gate containing flags and &amp;&amp; (npm run gate -- --push &amp;&amp; echo ok) and confirmed it round-trips registry -> brief -> spawn with no mismatch
  • Repro of the pre-existing failure: rebuilt build_old_bin()'s synthetic old bin/ by hand and ran both fm-send.sh copies with stderr visible

🔧 Fix: add fm-line-cap-lib.sh to fm-backend test old-bin siblings
✅ Re-checked - no issues remain.

  • ./tests/fm-brief.test.sh (all 24 cases, including the four new gate-merge/rule-5/--gate/shared-machine cases)
  • ./tests/fm-task-delivery.test.sh (spawn gate-merge posture + gate-occurrence enforcement, promote, project-mode)
  • ./tests/fm-backend.test.sh (regression check on the round-1 old-bin sibling-list fix)
  • ./tests/fm-teardown.test.sh (gate-merge done-note branch)
  • ./tests/fm-documentation-audiences.test.sh (README/AGENTS.md/docs prose surfaces)
  • ./tests/fm-secondmate-safety.test.sh (fm-project-mode consumer after its parser refactor)
  • Manual operator walkthrough: registered a gate-merge project in a temp firstmate home, ran bin/fm-project-mode.sh acme-gate and --gate acme-gate, scaffolded a brief with bin/fm-brief.sh gate-task-1 acme-gate --mode gate-merge --gate ./scripts/merge-gate.sh, and read back the generated DoD and rules verbatim
  • Manual refusal checks: bin/fm-brief.sh --mode gate-merge without --gate, --gate on a local-only brief, and --mode nope
  • Manual shared-machine check: generated no-mistakes / direct-PR / local-only / gate-merge / scout briefs and counted the SHARED MACHINE rule in each
  • Manual spawn enforcement: bin/fm-spawn.sh &lt;id&gt; &lt;proj&gt; claude --mode gate-merge --yolo off against a hand-patched gate step, an appended second gate step, a project registered no-mistakes, and the authorized brief (fake tmux backstop, so nothing was created)
  • Manual regression diff: generated a local-only brief from base 6c206ed (via git archive) and diffed it against the HEAD-generated one
⚠️ **Document** - 1 warning
  • ⚠️ bin/fm-home-seed.sh:468 - The new gate-merge posture is not covered by the secondmate-routing contract, and the local and remote seed paths now disagree. docs/configuration.md:179, .agents/skills/secondmate-provisioning/SKILL.md:151-152, and bin/fm-home-seed.sh's own error text all state that secondmate routes support only no-mistakes and direct-PR projects, but the local guard at bin/fm-home-seed.sh:468 and :495 refuses only local-only, so a gate-merge project seeds into a secondmate home while bin/fm-remote-home-seed.sh:146 and bin/fm-remote-home-provision.sh:216 refuse the same project outright. Resolving this needs a behavior decision (either add gate-merge to the local seed refusal, or widen the documented contract), so I did not edit the docs to assert either answer.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

A project whose crews land their own work through the project's own merge
gate could not be expressed as a delivery mode, so every ship brief for one
had its whole definition of done replaced by hand before dispatch, and the
generated rule 1 ("never push to any remote") contradicted a gate whose last
step is a push. Separately, no brief warned against broad pattern kills,
after one crew's `pkill -f vite` killed four sibling crews' agent sessions.

- `bin/fm-brief.sh` gains `--mode gate-merge`, whose definition of done has
  the worker land its own work by running the project's own gate: run it from
  the task worktree, never touch the primary checkout, retry in-turn while
  another gate run holds the lock instead of idling on a declared wait, treat
  a failing gate as a parked branch, and report an unreadable outcome rather
  than assuming it landed. Rules 1 and 2 are shaped by the same mode, so the
  rules and the definition of done can no longer disagree.
- The landing command arrives as `--gate <command>`, required by and exclusive
  to gate-merge. The scaffold stays project-agnostic and never guesses a gate,
  and a gate handed to any other path is refused rather than dropped.
- Every crewmate brief, ship and scout alike, now carries a shared-machine
  rule: no pattern kills (kill only exact pids you started) and no bare stash
  operations (the stash stack lives in the shared .git). Per-crew process
  groups would not have helped - `pkill -f` selects on the command line and
  matches straight through a process-group boundary.
- `fm-spawn.sh`, `fm-promote.sh`, and `fm-project-mode.sh` accept the new mode
  so it is expressible per project as `[gate-merge]`; the spawn's rigor ranking
  places it below the approval-gated modes, so shipping it on a more rigorous
  project still prints the deviation notice. Teardown's backlog reminder
  records a landed note instead of prompting for a PR URL.
- Existing no-mistakes, direct-PR, local-only, scout, and charter output is
  byte-identical apart from the added shared-machine rule.
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